]> git.lizzy.rs Git - worldedit.git/blobdiff - worldedit/manipulations.lua
Replace more deprecated functions
[worldedit.git] / worldedit / manipulations.lua
index 0a13206c4a5f4ad90f0e396d59b0b4e7d97271a8..76bb13bea8e7404e7e5fe2701fe03f95a7fc529f 100644 (file)
-worldedit = worldedit or {}\r
+--- Generic node manipulations.\r
+-- @module worldedit.manipulations\r
 \r
---wip: test the entire API again to make sure it works\r
---wip: remove env parameter where no longer needed in chat commands module\r
+local mh = worldedit.manip_helpers\r
 \r
---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\r
-worldedit.sort_pos = function(pos1, pos2)\r
-       pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}\r
-       pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
-       if pos1.x > pos2.x then\r
-               pos2.x, pos1.x = pos1.x, pos2.x\r
-       end\r
-       if pos1.y > pos2.y then\r
-               pos2.y, pos1.y = pos1.y, pos2.y\r
-       end\r
-       if pos1.z > pos2.z then\r
-               pos2.z, pos1.z = pos1.z, pos2.z\r
-       end\r
-       return pos1, pos2\r
-end\r
 \r
---determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume\r
-worldedit.volume = function(pos1, pos2)\r
-       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1)\r
-end\r
+--- Sets a region to `node_names`.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param node_names Node name or list of node names.\r
+-- @return The number of nodes set.\r
+function worldedit.set(pos1, pos2, node_names)\r
+       pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
---sets a region defined by positions `pos1` and `pos2` to `nodename`, returning the number of nodes filled\r
-worldedit.set = function(pos1, pos2, nodename)\r
-       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
+       local manip, area = mh.init(pos1, pos2)\r
+       local data = mh.get_empty_data(area)\r
 \r
-       local size = {x=pos2.x - pos1.x, y=pos2.y - pos1.y, z=pos2.z - pos1.z}\r
-       local nodes = {}\r
-\r
-       --fill nodes table with node to be set\r
-       local node = {nodename, 0, 0}\r
-       for i = 1, (size.x * size.y * size.z) do\r
-               nodes[i] = node\r
+       if type(node_names) == "string" then -- Only one type of node\r
+               local id = minetest.get_content_id(node_names)\r
+               -- Fill area with node\r
+               for i in area:iterp(pos1, pos2) do\r
+                       data[i] = id\r
+               end\r
+       else -- Several types of nodes specified\r
+               local node_ids = {}\r
+               for i, v in ipairs(node_names) do\r
+                       node_ids[i] = minetest.get_content_id(v)\r
+               end\r
+               -- Fill area randomly with nodes\r
+               local id_count, rand = #node_ids, math.random\r
+               for i in area:iterp(pos1, pos2) do\r
+                       data[i] = node_ids[rand(id_count)]\r
+               end\r
        end\r
 \r
-       minetest.place_schematic(pos1, {size=size, data=nodes})\r
+       mh.finish(manip, data)\r
+\r
        return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced\r
-worldedit.replace = function(pos1, pos2, searchnode, replacenode, env)\r
-       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
+--- Sets param2 of a region.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param param2 Value of param2 to set\r
+-- @return The number of nodes set.\r
+function worldedit.set_param2(pos1, pos2, param2)\r
+       pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
+\r
+       local manip, area = mh.init(pos1, pos2)\r
+       local param2_data = manip:get_param2_data()\r
 \r
-       local node = {name=replacenode}\r
-       local nodes = minetest.find_nodes_in_area(pos1, pos2, searchnode)\r
-       for _, pos in ipairs(nodes) do\r
-               env:add_node(pos, node)\r
+       -- Set param2 for every node\r
+       for i in area:iterp(pos1, pos2) do\r
+               param2_data[i] = param2\r
        end\r
-       return #nodes\r
+\r
+       -- Update map\r
+       manip:set_param2_data(param2_data)\r
+       manip:write_to_map()\r
+       manip:update_map()\r
+\r
+       return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced\r
-worldedit.replaceinverse = function(pos1, pos2, searchnode, replacenode, env)\r
+--- Replaces all instances of `search_node` with `replace_node` in a region.\r
+-- When `inverse` is `true`, replaces all instances that are NOT `search_node`.\r
+-- @return The number of nodes replaced.\r
+function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
 \r
-       if minetest.registered_nodes[searchnode] == nil then\r
-               searchnode = "default:" .. searchnode\r
-       end\r
+       local manip, area = mh.init(pos1, pos2)\r
+       local data = manip:get_data()\r
+\r
+       local search_id = minetest.get_content_id(search_node)\r
+       local replace_id = minetest.get_content_id(replace_node)\r
 \r
-       local pos = {x=pos1.x, y=0, z=0}\r
-       local node = {name=replacenode}\r
        local count = 0\r
-       while pos.x <= pos2.x do --wip: see if this can be sped up like worldedit.replace\r
-               pos.y = pos1.y\r
-               while pos.y <= pos2.y do\r
-                       pos.z = pos1.z\r
-                       while pos.z <= pos2.z do\r
-                               local name = env:get_node(pos).name\r
-                               if name ~= "ignore" and name ~= searchnode then\r
-                                       env:add_node(pos, node)\r
-                                       count = count + 1\r
-                               end\r
-                               pos.z = pos.z + 1\r
+\r
+       --- TODO: This could be shortened by checking `inverse` in the loop,\r
+       -- but that would have a speed penalty.  Is the penalty big enough\r
+       -- to matter?\r
+       if not inverse then\r
+               for i in area:iterp(pos1, pos2) do\r
+                       if data[i] == search_id then\r
+                               data[i] = replace_id\r
+                               count = count + 1\r
+                       end\r
+               end\r
+       else\r
+               for i in area:iterp(pos1, pos2) do\r
+                       if data[i] ~= search_id then\r
+                               data[i] = replace_id\r
+                               count = count + 1\r
                        end\r
-                       pos.y = pos.y + 1\r
                end\r
-               pos.x = pos.x + 1\r
        end\r
+\r
+       mh.finish(manip, data)\r
+\r
        return count\r
 end\r
 \r
---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\r
-worldedit.copy = function(pos1, pos2, axis, amount, env)\r
+\r
+--- Duplicates a region `amount` times with offset vector `direction`.\r
+-- Stacking is spread across server steps, one copy per step.\r
+-- @return The number of nodes stacked.\r
+function worldedit.stack2(pos1, pos2, direction, amount, finished)\r
+       local i = 0\r
+       local translated = {x=0, y=0, z=0}\r
+       local function next_one()\r
+               if i < amount then\r
+                       i = i + 1\r
+                       translated.x = translated.x + direction.x\r
+                       translated.y = translated.y + direction.y\r
+                       translated.z = translated.z + direction.z\r
+                       worldedit.copy2(pos1, pos2, translated)\r
+                       minetest.after(0, next_one)\r
+               else\r
+                       if finished then\r
+                               finished()\r
+                       end\r
+               end\r
+       end\r
+       next_one()\r
+       return worldedit.volume(pos1, pos2) * amount\r
+end\r
+\r
+\r
+--- Copies a region along `axis` by `amount` nodes.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param axis Axis ("x", "y", or "z")\r
+-- @param amount\r
+-- @return The number of nodes copied.\r
+function worldedit.copy(pos1, pos2, axis, amount)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
 \r
-       --wip: copy slice by slice using schematic method in the copy 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)\r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       local get_node, get_meta, set_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node\r
+       -- Copy things backwards when negative to avoid corruption.\r
+       -- FIXME: Lots of code duplication here.\r
        if amount < 0 then\r
-               local pos = {x=pos1.x, y=0, z=0}\r
+               local pos = {}\r
+               pos.x = pos1.x\r
                while pos.x <= pos2.x do\r
                        pos.y = pos1.y\r
                        while pos.y <= pos2.y do\r
                                pos.z = pos1.z\r
                                while pos.z <= pos2.z do\r
-                                       local node = env:get_node(pos) --obtain current node\r
-                                       local meta = env:get_meta(pos):to_table() --get meta of current node\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       env:add_node(pos, node) --copy node to new position\r
-                                       env:get_meta(pos):from_table(meta) --set metadata of new node\r
-                                       pos[axis] = value --restore old position\r
+                                       local node = get_node(pos) -- Obtain current node\r
+                                       local meta = get_meta(pos):to_table() -- Get meta of current node\r
+                                       local value = pos[axis] -- Store current position\r
+                                       pos[axis] = value + amount -- Move along axis\r
+                                       set_node(pos, node) -- Copy node to new position\r
+                                       get_meta(pos):from_table(meta) -- Set metadata of new node\r
+                                       pos[axis] = value -- Restore old position\r
                                        pos.z = pos.z + 1\r
                                end\r
                                pos.y = pos.y + 1\r
@@ -113,19 +163,20 @@ worldedit.copy = function(pos1, pos2, axis, amount, env)
                        pos.x = pos.x + 1\r
                end\r
        else\r
-               local pos = {x=pos2.x, y=0, z=0}\r
+               local pos = {}\r
+               pos.x = pos2.x\r
                while pos.x >= pos1.x do\r
                        pos.y = pos2.y\r
                        while pos.y >= pos1.y do\r
                                pos.z = pos2.z\r
                                while pos.z >= pos1.z do\r
-                                       local node = minetest.env:get_node(pos) --obtain current node\r
-                                       local meta = env:get_meta(pos):to_table() --get meta of current node\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       minetest.env:add_node(pos, node) --copy node to new position\r
-                                       env:get_meta(pos):from_table(meta) --set metadata of new node\r
-                                       pos[axis] = value --restore old position\r
+                                       local node = get_node(pos) -- Obtain current node\r
+                                       local meta = get_meta(pos):to_table() -- Get meta of current node\r
+                                       local value = pos[axis] -- Store current position\r
+                                       pos[axis] = value + amount -- Move along axis\r
+                                       set_node(pos, node) -- Copy node to new position\r
+                                       get_meta(pos):from_table(meta) -- Set metadata of new node\r
+                                       pos[axis] = value -- Restore old position\r
                                        pos.z = pos.z - 1\r
                                end\r
                                pos.y = pos.y - 1\r
@@ -136,27 +187,70 @@ worldedit.copy = function(pos1, pos2, axis, amount, env)
        return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---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\r
-worldedit.move = function(pos1, pos2, axis, amount, env)\r
+--- Copies a region by offset vector `off`.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param off\r
+-- @return The number of nodes copied.\r
+function worldedit.copy2(pos1, pos2, off)\r
+       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
+\r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       local get_node, get_meta, set_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node\r
+       local pos = {}\r
+       pos.x = pos2.x\r
+       while pos.x >= pos1.x do\r
+               pos.y = pos2.y\r
+               while pos.y >= pos1.y do\r
+                       pos.z = pos2.z\r
+                       while pos.z >= pos1.z do\r
+                               local node = get_node(pos) -- Obtain current node\r
+                               local meta = get_meta(pos):to_table() -- Get meta of current node\r
+                               local newpos = vector.add(pos, off) -- Calculate new position\r
+                               set_node(newpos, node) -- Copy node to new position\r
+                               get_meta(newpos):from_table(meta) -- Set metadata of new node\r
+                               pos.z = pos.z - 1\r
+                       end\r
+                       pos.y = pos.y - 1\r
+               end\r
+               pos.x = pos.x - 1\r
+       end\r
+       return worldedit.volume(pos1, pos2)\r
+end\r
+\r
+--- Moves a region along `axis` by `amount` nodes.\r
+-- @return The number of nodes moved.\r
+function worldedit.move(pos1, pos2, axis, amount)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
 \r
-       --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)\r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       --- TODO: Move slice by slice using schematic method in the move axis\r
+       -- and transfer metadata in separate loop (and if the amount is\r
+       -- greater than the length in the axis, copy whole thing at a time and\r
+       -- erase original after, using schematic method).\r
+       local get_node, get_meta, set_node, remove_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node, minetest.remove_node\r
+       -- Copy things backwards when negative to avoid corruption.\r
+       --- FIXME: Lots of code duplication here.\r
        if amount < 0 then\r
-               local pos = {x=pos1.x, y=0, z=0}\r
+               local pos = {}\r
+               pos.x = pos1.x\r
                while pos.x <= pos2.x do\r
                        pos.y = pos1.y\r
                        while pos.y <= pos2.y do\r
                                pos.z = pos1.z\r
                                while pos.z <= pos2.z do\r
-                                       local node = env:get_node(pos) --obtain current node\r
-                                       local meta = env:get_meta(pos):to_table() --get metadata of current node\r
-                                       env:remove_node(pos)\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       env:add_node(pos, node) --move node to new position\r
-                                       env:get_meta(pos):from_table(meta) --set metadata of new node\r
-                                       pos[axis] = value --restore old position\r
+                                       local node = get_node(pos) -- Obtain current node\r
+                                       local meta = get_meta(pos):to_table() -- Get metadata of current node\r
+                                       remove_node(pos) -- Remove current node\r
+                                       local value = pos[axis] -- Store current position\r
+                                       pos[axis] = value + amount -- Move along axis\r
+                                       set_node(pos, node) -- Move node to new position\r
+                                       get_meta(pos):from_table(meta) -- Set metadata of new node\r
+                                       pos[axis] = value -- Restore old position\r
                                        pos.z = pos.z + 1\r
                                end\r
                                pos.y = pos.y + 1\r
@@ -164,20 +258,21 @@ worldedit.move = function(pos1, pos2, axis, amount, env)
                        pos.x = pos.x + 1\r
                end\r
        else\r
-               local pos = {x=pos2.x, y=0, z=0}\r
+               local pos = {}\r
+               pos.x = pos2.x\r
                while pos.x >= pos1.x do\r
                        pos.y = pos2.y\r
                        while pos.y >= pos1.y do\r
                                pos.z = pos2.z\r
                                while pos.z >= pos1.z do\r
-                                       local node = env:get_node(pos) --obtain current node\r
-                                       local meta = env:get_meta(pos):to_table() --get metadata of current node\r
-                                       env:remove_node(pos)\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       env:add_node(pos, node) --move node to new position\r
-                                       env:get_meta(pos):from_table(meta) --set metadata of new node\r
-                                       pos[axis] = value --restore old position\r
+                                       local node = get_node(pos) -- Obtain current node\r
+                                       local meta = get_meta(pos):to_table() -- Get metadata of current node\r
+                                       remove_node(pos) -- Remove current node\r
+                                       local value = pos[axis] -- Store current position\r
+                                       pos[axis] = value + amount -- Move along axis\r
+                                       set_node(pos, node) -- Move node to new position\r
+                                       get_meta(pos):from_table(meta) -- Set metadata of new node\r
+                                       pos[axis] = value -- Restore old position\r
                                        pos.z = pos.z - 1\r
                                end\r
                                pos.y = pos.y - 1\r
@@ -188,8 +283,15 @@ worldedit.move = function(pos1, pos2, axis, amount, env)
        return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---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\r
-worldedit.stack = function(pos1, pos2, axis, count, env)\r
+\r
+--- Duplicates a region along `axis` `amount` times.\r
+-- Stacking is spread across server steps, one copy per step.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param axis Axis direction, "x", "y", or "z".\r
+-- @param count\r
+-- @return The number of nodes stacked.\r
+function worldedit.stack(pos1, pos2, axis, count)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
        local length = pos2[axis] - pos1[axis] + 1\r
        if count < 0 then\r
@@ -198,51 +300,86 @@ worldedit.stack = function(pos1, pos2, axis, count, env)
        end\r
        local amount = 0\r
        local copy = worldedit.copy\r
-       for i = 1, count do\r
-               amount = amount + length\r
-               copy(pos1, pos2, axis, amount, env)\r
+       local i = 1\r
+       local function next_one()\r
+               if i <= count then\r
+                       i = i + 1\r
+                       amount = amount + length\r
+                       copy(pos1, pos2, axis, amount)\r
+                       minetest.after(0, next_one)\r
+               end\r
        end\r
-       return worldedit.volume(pos1, pos2)\r
+       next_one()\r
+       return worldedit.volume(pos1, pos2) * count\r
 end\r
 \r
---scales the region defined by positions `pos1` and `pos2` by an factor of positive integer `factor` with `pos1` as the origin, returning the number of nodes scaled, the new scaled position 1, and the new scaled position 2\r
-worldedit.scale = function(pos1, pos2, factor, env)\r
+\r
+--- Stretches a region by a factor of positive integers along the X, Y, and Z\r
+-- axes, respectively, with `pos1` as the origin.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param stretch_x Amount to stretch along X axis.\r
+-- @param stretch_y Amount to stretch along Y axis.\r
+-- @param stretch_z Amount to stretch along Z axis.\r
+-- @return The number of nodes scaled.\r
+-- @return The new scaled position 1.\r
+-- @return The new scaled position 2.\r
+function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
 \r
-       --prepare schematic of large node\r
-       local place_schematic = minetest.place_schematic\r
-       local placeholder_node = {"", 0, 0}\r
+       -- Prepare schematic of large node\r
+       local get_node, get_meta, place_schematic = minetest.get_node,\r
+                       minetest.get_meta, minetest.place_schematic\r
+       local placeholder_node = {name="", param1=255, param2=0}\r
        local nodes = {}\r
-       for i = 1, size ^ 3 do\r
+       for i = 1, stretch_x * stretch_y * stretch_z do\r
                nodes[i] = placeholder_node\r
        end\r
-       local schematic = {size={x=size, y=size, z=size}, data=nodes}\r
+       local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes}\r
+\r
+       local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1\r
+\r
+       local new_pos2 = {\r
+               x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,\r
+               y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,\r
+               z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,\r
+       }\r
+       worldedit.keep_loaded(pos1, new_pos2)\r
 \r
        local pos = {x=pos2.x, y=0, z=0}\r
-       local bigpos = {x=0, y=0, z=0}\r
-       size = factor - 1\r
+       local big_pos = {x=0, y=0, z=0}\r
        while pos.x >= pos1.x do\r
                pos.y = pos2.y\r
                while pos.y >= pos1.y do\r
                        pos.z = pos2.z\r
                        while pos.z >= pos1.z do\r
-                               local node = env:get_node(pos) --obtain current node\r
-                               local meta = env:get_meta(pos):to_table() --get meta of current node\r
-\r
-                               local value = pos[axis] --store current position\r
-                               local posx, posy, posz = pos1.x + (pos.x - pos1.x) * factor, pos1.y + (pos.y - pos1.y) * factor, pos1.z + (pos.z - pos1.z) * factor\r
-\r
-                               --create large node\r
-                               placeholder_node[1], placeholder_node[3] = node.name, node.param2\r
-                               bigpos.x, bigpos.y, bigpos.z = posx, posy, posz\r
-                               place_schematic(bigpos, schematic)\r
-                               for x = 0, size do --fill in large node meta\r
-                                       for y = 0, size do\r
-                                               for z = 0, size do\r
-                                                       bigpos.x, bigpos.y, bigpos.z = posx + x, posy + y, posz + z\r
-                                                       env:get_meta(bigpos):from_table(meta) --set metadata of new node\r
-                                               end\r
+                               local node = get_node(pos) -- Get current node\r
+                               local meta = get_meta(pos):to_table() -- Get meta of current node\r
+\r
+                               -- Calculate far corner of the big node\r
+                               local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x\r
+                               local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y\r
+                               local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z\r
+\r
+                               -- Create large node\r
+                               placeholder_node.name = node.name\r
+                               placeholder_node.param2 = node.param2\r
+                               big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z\r
+                               place_schematic(big_pos, schematic)\r
+\r
+                               -- Fill in large node meta\r
+                               if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then\r
+                                       -- Node has meta fields\r
+                                       for x = 0, size_x do\r
+                                       for y = 0, size_y do\r
+                                       for z = 0, size_z do\r
+                                               big_pos.x = pos_x + x\r
+                                               big_pos.y = pos_y + y\r
+                                               big_pos.z = pos_z + z\r
+                                               -- Set metadata of new node\r
+                                               get_meta(big_pos):from_table(meta)\r
+                                       end\r
+                                       end\r
                                        end\r
                                end\r
                                pos.z = pos.z - 1\r
@@ -251,12 +388,15 @@ worldedit.scale = function(pos1, pos2, factor, env)
                end\r
                pos.x = pos.x - 1\r
        end\r
-       local newpos2 = {x=pos1.x + (pos2.x - pos1.x) * factor + size, y=pos1.y + (pos2.y - pos1.y) * factor + size, z=pos1.z + (pos2.z - pos1.z) * factor + size}\r
-       return worldedit.volume(pos1, pos2), pos1, newpos2\r
+       return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2\r
 end\r
 \r
---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\r
-worldedit.transpose = function(pos1, pos2, axis1, axis2, env)\r
+\r
+--- Transposes a region between two axes.\r
+-- @return The number of nodes transposed.\r
+-- @return The new transposed position 1.\r
+-- @return The new transposed position 2.\r
+function worldedit.transpose(pos1, pos2, axis1, axis2)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
        local compare\r
@@ -272,31 +412,37 @@ worldedit.transpose = function(pos1, pos2, axis1, axis2, env)
                end\r
        end\r
 \r
-       --calculate the new position 2 after transposition\r
-       local newpos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
-       newpos2[axis1] = pos1[axis1] + extent2\r
-       newpos2[axis2] = pos1[axis2] + extent1\r
+       -- Calculate the new position 2 after transposition\r
+       local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}\r
+       new_pos2[axis1] = pos1[axis1] + extent2\r
+       new_pos2[axis2] = pos1[axis2] + extent1\r
+\r
+       local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z}\r
+       if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end\r
+       if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end\r
+       worldedit.keep_loaded(pos1, upper_bound)\r
 \r
        local pos = {x=pos1.x, y=0, z=0}\r
-       if env == nil then env = minetest.env end\r
+       local get_node, get_meta, set_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node\r
        while pos.x <= pos2.x do\r
                pos.y = pos1.y\r
                while pos.y <= pos2.y do\r
                        pos.z = pos1.z\r
                        while pos.z <= pos2.z do\r
                                local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]\r
-                               if compare(extent1, extent2) then --transpose only if below the diagonal\r
-                                       local node1 = env:get_node(pos)\r
-                                       local meta1 = env:get_meta(pos):to_table()\r
-                                       local value1, value2 = pos[axis1], pos[axis2] --save position values\r
-                                       pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents\r
-                                       local node2 = env:get_node(pos)\r
-                                       local meta2 = env:get_meta(pos):to_table()\r
-                                       env:add_node(pos, node1)\r
-                                       env:get_meta(pos):from_table(meta1)\r
-                                       pos[axis1], pos[axis2] = value1, value2 --restore position values\r
-                                       env:add_node(pos, node2)\r
-                                       env:get_meta(pos):from_table(meta2)\r
+                               if compare(extent1, extent2) then -- Transpose only if below the diagonal\r
+                                       local node1 = get_node(pos)\r
+                                       local meta1 = get_meta(pos):to_table()\r
+                                       local value1, value2 = pos[axis1], pos[axis2] -- Save position values\r
+                                       pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents\r
+                                       local node2 = get_node(pos)\r
+                                       local meta2 = get_meta(pos):to_table()\r
+                                       set_node(pos, node1)\r
+                                       get_meta(pos):from_table(meta1)\r
+                                       pos[axis1], pos[axis2] = value1, value2 -- Restore position values\r
+                                       set_node(pos, node2)\r
+                                       get_meta(pos):from_table(meta2)\r
                                end\r
                                pos.z = pos.z + 1\r
                        end\r
@@ -304,34 +450,39 @@ worldedit.transpose = function(pos1, pos2, axis1, axis2, env)
                end\r
                pos.x = pos.x + 1\r
        end\r
-       return worldedit.volume(pos1, pos2), pos1, newpos2\r
+       return worldedit.volume(pos1, pos2), pos1, new_pos2\r
 end\r
 \r
---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\r
-worldedit.flip = function(pos1, pos2, axis, env)\r
+\r
+--- Flips a region along `axis`.\r
+-- @return The number of nodes flipped.\r
+function worldedit.flip(pos1, pos2, axis)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
-       --wip: flip the region slice by slice along the flip axis using schematic method\r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       --- TODO: Flip the region slice by slice along the flip axis using schematic method.\r
        local pos = {x=pos1.x, y=0, z=0}\r
        local start = pos1[axis] + pos2[axis]\r
        pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)\r
-       if env == nil then env = minetest.env end\r
+       local get_node, get_meta, set_node = minetest.get_node,\r
+                       minetest.get_meta, minetest.set_node\r
        while pos.x <= pos2.x do\r
                pos.y = pos1.y\r
                while pos.y <= pos2.y do\r
                        pos.z = pos1.z\r
                        while pos.z <= pos2.z do\r
-                               local node1 = env:get_node(pos)\r
-                               local meta1 = env:get_meta(pos):to_table()\r
-                               local value = pos[axis]\r
-                               pos[axis] = start - value\r
-                               local node2 = env:get_node(pos)\r
-                               local meta2 = env:get_meta(pos):to_table()\r
-                               env:add_node(pos, node1)\r
-                               env:get_meta(pos):from_table(meta1)\r
-                               pos[axis] = value\r
-                               env:add_node(pos, node2)\r
-                               env:get_meta(pos):from_table(meta2)\r
+                               local node1 = get_node(pos)\r
+                               local meta1 = get_meta(pos):to_table()\r
+                               local value = pos[axis] -- Save position\r
+                               pos[axis] = start - value -- Shift position\r
+                               local node2 = get_node(pos)\r
+                               local meta2 = get_meta(pos):to_table()\r
+                               set_node(pos, node1)\r
+                               get_meta(pos):from_table(meta1)\r
+                               pos[axis] = value -- Restore position\r
+                               set_node(pos, node2)\r
+                               get_meta(pos):from_table(meta2)\r
                                pos.z = pos.z + 1\r
                        end\r
                        pos.y = pos.y + 1\r
@@ -341,78 +492,98 @@ worldedit.flip = function(pos1, pos2, axis, env)
        return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---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\r
-worldedit.rotate = function(pos1, pos2, axis, angle, env)\r
+\r
+--- Rotates a region clockwise around an axis.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param axis Axis ("x", "y", or "z").\r
+-- @param angle Angle in degrees (90 degree increments only).\r
+-- @return The number of nodes rotated.\r
+-- @return The new first position.\r
+-- @return The new second position.\r
+function worldedit.rotate(pos1, pos2, axis, angle)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
-       local axis1, axis2\r
-       if axis == "x" then\r
-               axis1, axis2 = "z", "y"\r
-       elseif axis == "y" then\r
-               axis1, axis2 = "x", "z"\r
-       else --axis == "z"\r
-               axis1, axis2 = "y", "x"\r
-       end\r
+       local other1, other2 = worldedit.get_axis_others(axis)\r
        angle = angle % 360\r
 \r
        local count\r
        if angle == 90 then\r
-               worldedit.flip(pos1, pos2, axis1, env)\r
-               count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env)\r
+               worldedit.flip(pos1, pos2, other1)\r
+               count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)\r
        elseif angle == 180 then\r
-               worldedit.flip(pos1, pos2, axis1, env)\r
-               count = worldedit.flip(pos1, pos2, axis2, env)\r
+               worldedit.flip(pos1, pos2, other1)\r
+               count = worldedit.flip(pos1, pos2, other2)\r
        elseif angle == 270 then\r
-               worldedit.flip(pos1, pos2, axis2, env)\r
-               count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env)\r
+               worldedit.flip(pos1, pos2, other2)\r
+               count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)\r
+       else\r
+               error("Only 90 degree increments are supported!")\r
        end\r
        return count, pos1, pos2\r
 end\r
 \r
---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\r
-worldedit.orient = function(pos1, pos2, angle, env)\r
+\r
+--- Rotates all oriented nodes in a region clockwise around the Y axis.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param angle Angle in degrees (90 degree increments only).\r
+-- @return The number of nodes oriented.\r
+function worldedit.orient(pos1, pos2, angle)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       local nodes = minetest.registered_nodes\r
-       if env == nil then env = minetest.env end\r
+       local registered_nodes = minetest.registered_nodes\r
+\r
        local wallmounted = {\r
-               [90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3},\r
-               [180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4},\r
-               [270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2}\r
+               [90]  = {0, 1, 5, 4, 2, 3, 0, 0},\r
+               [180] = {0, 1, 3, 2, 5, 4, 0, 0},\r
+               [270] = {0, 1, 4, 5, 3, 2, 0, 0}\r
        }\r
        local facedir = {\r
-               [90]={[0]=1, [1]=2, [2]=3, [3]=0},\r
-               [180]={[0]=2, [1]=3, [2]=0, [3]=1},\r
-               [270]={[0]=3, [1]=0, [2]=1, [3]=2}\r
+               [90]  = { 1,  2,  3,  0, 13, 14, 15, 12, 17, 18, 19, 16,\r
+                                 9, 10, 11,  8,  5,  6,  7,  4, 23, 20, 21, 22},\r
+               [180] = { 2,  3,  0,  1, 10, 11,  8,  9,  6,  7,  4,  5,\r
+                                18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21},\r
+               [270] = { 3,  0,  1,  2, 19, 16, 17, 18, 15, 12, 13, 14,\r
+                                 7,  4,  5,  6, 11,  8,  9, 10, 21, 22, 23, 20}\r
        }\r
 \r
        angle = angle % 360\r
        if angle == 0 then\r
                return 0\r
        end\r
+       if angle % 90 ~= 0 then\r
+               error("Only 90 degree increments are supported!")\r
+       end\r
        local wallmounted_substitution = wallmounted[angle]\r
        local facedir_substitution = facedir[angle]\r
 \r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
        local count = 0\r
+       local get_node, swap_node = minetest.get_node, minetest.swap_node\r
        local pos = {x=pos1.x, y=0, z=0}\r
        while pos.x <= pos2.x do\r
                pos.y = pos1.y\r
                while pos.y <= pos2.y do\r
                        pos.z = pos1.z\r
                        while pos.z <= pos2.z do\r
-                               local node = env:get_node(pos)\r
-                               local def = nodes[node.name]\r
+                               local node = get_node(pos)\r
+                               local def = registered_nodes[node.name]\r
                                if def then\r
-                                       if def.paramtype2 == "wallmounted" then\r
-                                               node.param2 = wallmounted_substitution[node.param2]\r
-                                               local meta = env:get_meta(pos):to_table()\r
-                                               env:add_node(pos, node)\r
-                                               env:get_meta(pos):from_table(meta)\r
+                                       local paramtype2 = def.paramtype2\r
+                                       if paramtype2 == "wallmounted" or\r
+                                                       paramtype2 == "colorwallmounted" then\r
+                                               local orient = node.param2 % 8\r
+                                               node.param2 = node.param2 - orient +\r
+                                                               wallmounted_substitution[orient + 1]\r
+                                               swap_node(pos, node)\r
                                                count = count + 1\r
-                                       elseif def.paramtype2 == "facedir" then\r
-                                               node.param2 = facedir_substitution[node.param2]\r
-                                               local meta = env:get_meta(pos):to_table()\r
-                                               env:add_node(pos, node)\r
-                                               env:get_meta(pos):from_table(meta)\r
+                                       elseif paramtype2 == "facedir" or\r
+                                                       paramtype2 == "colorfacedir" then\r
+                                               local orient = node.param2 % 32\r
+                                               node.param2 = node.param2 - orient +\r
+                                                               facedir_substitution[orient + 1]\r
+                                               swap_node(pos, node)\r
                                                count = count + 1\r
                                        end\r
                                end\r
@@ -425,23 +596,58 @@ worldedit.orient = function(pos1, pos2, angle, env)
        return count\r
 end\r
 \r
---fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated\r
-worldedit.fixlight = function(pos1, pos2, env)\r
+\r
+--- Attempts to fix the lighting in a region.\r
+-- @return The number of nodes updated.\r
+function worldedit.fixlight(pos1, pos2)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
-       if env == nil then env = minetest.env end\r
-       local count = 0\r
 \r
-       local pos = {x=pos1.x, y=pos2.y, z=0}\r
-       while pos.x <= pos2.x do\r
-               pos.z = pos1.z\r
-               while pos.z <= pos2.z do\r
-                       if env:get_node(pos).name == "air" then\r
-                               env:dig_node(pos)\r
+       local vmanip = minetest.get_voxel_manip(pos1, pos2)\r
+       vmanip:write_to_map()\r
+       vmanip:update_map() -- this updates the lighting\r
+\r
+       return worldedit.volume(pos1, pos2)\r
+end\r
+\r
+\r
+--- Clears all objects in a region.\r
+-- @return The number of objects cleared.\r
+function worldedit.clear_objects(pos1, pos2)\r
+       pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
+\r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       -- Offset positions to include full nodes (positions are in the center of nodes)\r
+       local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5\r
+       local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5\r
+\r
+       -- Center of region\r
+       local center = {\r
+               x = pos1x + ((pos2x - pos1x) / 2),\r
+               y = pos1y + ((pos2y - pos1y) / 2),\r
+               z = pos1z + ((pos2z - pos1z) / 2)\r
+       }\r
+       -- Bounding sphere radius\r
+       local radius = math.sqrt(\r
+                       (center.x - pos1x) ^ 2 +\r
+                       (center.y - pos1y) ^ 2 +\r
+                       (center.z - pos1z) ^ 2)\r
+       local count = 0\r
+       for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do\r
+               local entity = obj:get_luaentity()\r
+               -- Avoid players and WorldEdit entities\r
+               if not obj:is_player() and (not entity or\r
+                               not entity.name:find("^worldedit:")) then\r
+                       local pos = obj:get_pos()\r
+                       if pos.x >= pos1x and pos.x <= pos2x and\r
+                                       pos.y >= pos1y and pos.y <= pos2y and\r
+                                       pos.z >= pos1z and pos.z <= pos2z then\r
+                               -- Inside region\r
+                               obj:remove()\r
                                count = count + 1\r
                        end\r
-                       pos.z = pos.z + 1\r
                end\r
-               pos.x = pos.x + 1\r
        end\r
        return count\r
 end\r
+\r