]> git.lizzy.rs Git - worldedit.git/blobdiff - worldedit/manipulations.lua
Use minetest.global_exists for LuaJIT check
[worldedit.git] / worldedit / manipulations.lua
index 53fea6fa8ae32d0374cc8aa6d6cabd3b11d13690..54dc8885031f26a246e8dcc47a83804bfae93800 100644 (file)
-worldedit = worldedit or {}\r
-local minetest = minetest --local copy of global\r
+--- Generic node manipulations.\r
+-- @module worldedit.manipulations\r
 \r
---wip: fix the queue\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
-       --set up voxel manipulator\r
-       local manip = minetest.get_voxel_manip()\r
-       local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
-       local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
-\r
-       --fill emerged area with ignore\r
-       local nodes = {}\r
-       local ignore = minetest.get_content_id("ignore")\r
-       for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do\r
-               nodes[i] = ignore\r
-       end\r
-\r
-       --fill selected area with node\r
-       local node_id = minetest.get_content_id(nodename)\r
-       for i in area:iterp(pos1, pos2) do\r
-               nodes[i] = node_id\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
-       --update map nodes\r
-       manip:set_data(nodes)\r
-       manip:write_to_map()\r
-       manip:update_map()\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)\r
-       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\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
-       --set up voxel manipulator\r
-       local manip = minetest.get_voxel_manip()\r
-       local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
-       local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
+       local manip, area = mh.init(pos1, pos2)\r
+       local param2_data = manip:get_param2_data()\r
 \r
-       local nodes = manip:get_data()\r
-       local searchnode_id = minetest.get_content_id(searchnode)\r
-       local replacenode_id = minetest.get_content_id(replacenode)\r
-       local count = 0\r
-       for i in area:iterp(pos1, pos2) do --replace searchnode with replacenode\r
-               if nodes[i] == searchnode_id then\r
-                       nodes[i] = replacenode_id\r
-                       count = count + 1\r
-               end\r
+       -- Set param2 for every node\r
+       for i in area:iterp(pos1, pos2) do\r
+               param2_data[i] = param2\r
        end\r
 \r
-       --update map nodes\r
-       manip:set_data(nodes)\r
+       -- Update map\r
+       manip:set_param2_data(param2_data)\r
        manip:write_to_map()\r
        manip:update_map()\r
 \r
-       return count\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)\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
 \r
-       --set up voxel manipulator\r
-       local manip = minetest.get_voxel_manip()\r
-       local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
-       local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\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 nodes = manip:get_data()\r
-       local searchnode_id = minetest.get_content_id(searchnode)\r
-       local replacenode_id = minetest.get_content_id(replacenode)\r
        local count = 0\r
-       for i in area:iterp(pos1, pos2) do --replace anything that is not searchnode with replacenode\r
-               if nodes[i] ~= searchnode_id then\r
-                       nodes[i] = replacenode_id\r
-                       count = count + 1\r
+\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
                end\r
        end\r
 \r
-       --update map nodes\r
-       manip:set_data(nodes)\r
-       manip:write_to_map()\r
-       manip:update_map()\r
+       mh.finish(manip, data)\r
 \r
        return count\r
 end\r
 \r
-worldedit.copy = function(pos1, pos2, axis, amount)\r
-       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
-       if amount == 0 then\r
-               return\r
+local function deferred_execution(next_one, finished)\r
+       -- Allocate 100% of server step for execution (might lag a little)\r
+       local allocated_usecs =\r
+               tonumber(minetest.settings:get("dedicated_server_step")) * 1000000\r
+       local function f()\r
+               local deadline = minetest.get_us_time() + allocated_usecs\r
+               repeat\r
+                       local is_done = next_one()\r
+                       if is_done then\r
+                               if finished then\r
+                                       finished()\r
+                               end\r
+                               return\r
+                       end\r
+               until minetest.get_us_time() >= deadline\r
+               minetest.after(0, f)\r
        end\r
+       f()\r
+end\r
 \r
-       local other1, other2\r
-       if axis == "x" then\r
-               other1, other2 = "y", "z"\r
-       elseif axis == "y" then\r
-               other1, other2 = "x", "z"\r
-       else --axis == "z"\r
-               other1, other2 = "x", "y"\r
+--- Duplicates a region `amount` times with offset vector `direction`.\r
+-- Stacking is spread across server steps.\r
+-- @return The number of nodes stacked.\r
+function worldedit.stack2(pos1, pos2, direction, amount, finished)\r
+       -- Protect arguments from external changes during execution\r
+       pos1 = table.copy(pos1)\r
+       pos2 = table.copy(pos2)\r
+       direction = table.copy(direction)\r
+\r
+       local i = 0\r
+       local translated = {x=0, y=0, z=0}\r
+       local function step()\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
+               i = i + 1\r
+               return i >= amount\r
        end\r
+       deferred_execution(step, finished)\r
 \r
-       --make area stay loaded\r
-       local manip = minetest.get_voxel_manip()\r
-       manip:read_from_map(pos1, pos2)\r
+       return worldedit.volume(pos1, pos2) * amount\r
+end\r
 \r
-       --prepare slice along axis\r
-       local extent = {\r
-               [axis] = 1,\r
-               [other1]=pos2[other1] - pos1[other1] + 1,\r
-               [other2]=pos2[other2] - pos1[other2] + 1,\r
-       }\r
-       local nodes = {}\r
-       local schematic = {size=extent, data=nodes}\r
-\r
-       local currentpos = {x=pos1.x, y=pos1.y, z=pos1.z}\r
-       local stride = {x=1, y=extent.x, z=extent.x * extent.y}\r
-       local get_node = minetest.get_node\r
-       for index1 = 1, extent[axis] do --go through each slice\r
-               --copy slice into schematic\r
-               local newindex1 = (index1 + offset[axis]) * stride[axis] + 1 --offset contributed by axis plus 1 to make it 1-indexed\r
-               for index2 = 1, extent[other1] do\r
-                       local newindex2 = newindex1 + (index2 + offset[other1]) * stride[other1]\r
-                       for index3 = 1, extent[other2] do\r
-                               local i = newindex2 + (index3 + offset[other2]) * stride[other2]\r
-                               nodes[i] = get_node(pos)\r
-                       end\r
-               end\r
 \r
-               --copy schematic to target\r
-               currentpos[axis] = currentpos[axis] + amount\r
-               place_schematic(currentpos, schematic)\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
 \r
-               --wip: copy meta\r
+       -- Decide if we need to copy stuff backwards (only applies to metadata)\r
+       local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1)\r
 \r
-               currentpos[axis] = currentpos[axis] + 1\r
-       end\r
-       return worldedit.volume(pos1, pos2)\r
+       local off = {x=0, y=0, z=0}\r
+       off[axis] = amount\r
+       return worldedit.copy2(pos1, pos2, off, backwards)\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)\r
+--- Copies a region by offset vector `off`.\r
+-- @param pos1\r
+-- @param pos2\r
+-- @param off\r
+-- @param meta_backwards (not officially part of API)\r
+-- @return The number of nodes copied.\r
+function worldedit.copy2(pos1, pos2, off, meta_backwards)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
-       --make area stay loaded\r
-       local manip = minetest.get_voxel_manip()\r
-       manip:read_from_map(pos1, pos2)\r
-\r
-       local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node\r
-       if amount < 0 then\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 = 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
-                                       add_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
+       local src_manip, src_area = mh.init(pos1, pos2)\r
+       local src_stride = {x=1, y=src_area.ystride, z=src_area.zstride}\r
+       local src_offset = vector.subtract(pos1, src_area.MinEdge)\r
+\r
+       local dpos1 = vector.add(pos1, off)\r
+       local dpos2 = vector.add(pos2, off)\r
+       local dim = vector.add(vector.subtract(pos2, pos1), 1)\r
+\r
+       local dst_manip, dst_area = mh.init(dpos1, dpos2)\r
+       local dst_stride = {x=1, y=dst_area.ystride, z=dst_area.zstride}\r
+       local dst_offset = vector.subtract(dpos1, dst_area.MinEdge)\r
+\r
+       local function do_copy(src_data, dst_data)\r
+               for z = 0, dim.z-1 do\r
+                       local src_index_z = (src_offset.z + z) * src_stride.z + 1 -- +1 for 1-based indexing\r
+                       local dst_index_z = (dst_offset.z + z) * dst_stride.z + 1\r
+                       for y = 0, dim.y-1 do\r
+                               local src_index_y = src_index_z + (src_offset.y + y) * src_stride.y\r
+                               local dst_index_y = dst_index_z + (dst_offset.y + y) * dst_stride.y\r
+                               -- Copy entire row at once\r
+                               local src_index_x = src_index_y + src_offset.x\r
+                               local dst_index_x = dst_index_y + dst_offset.x\r
+                               for x = 0, dim.x-1 do\r
+                                       dst_data[dst_index_x + x] = src_data[src_index_x + x]\r
                                end\r
-                               pos.y = pos.y + 1\r
                        end\r
-                       pos.x = pos.x + 1\r
                end\r
+       end\r
+\r
+       -- Copy node data\r
+       local src_data = src_manip:get_data()\r
+       local dst_data = dst_manip:get_data()\r
+       do_copy(src_data, dst_data)\r
+       dst_manip:set_data(dst_data)\r
+\r
+       -- Copy param1\r
+       src_manip:get_light_data(src_data)\r
+       dst_manip:get_light_data(dst_data)\r
+       do_copy(src_data, dst_data)\r
+       dst_manip:set_light_data(dst_data)\r
+\r
+       -- Copy param2\r
+       src_manip:get_param2_data(src_data)\r
+       dst_manip:get_param2_data(dst_data)\r
+       do_copy(src_data, dst_data)\r
+       dst_manip:set_param2_data(dst_data)\r
+\r
+       mh.finish(dst_manip)\r
+       src_data = nil\r
+       dst_data = nil\r
+\r
+       -- Copy metadata\r
+       local get_meta = minetest.get_meta\r
+       if meta_backwards then\r
+       for z = dim.z-1, 0, -1 do\r
+               for y = dim.y-1, 0, -1 do\r
+                       for x = dim.x-1, 0, -1 do\r
+                               local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}\r
+                               local meta = get_meta(pos):to_table()\r
+                               pos = vector.add(pos, off)\r
+                               get_meta(pos):from_table(meta)\r
+                       end\r
+               end\r
+       end\r
        else\r
-               local pos = {x=pos2.x, 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 = 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
-                                       add_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
+       for z = 0, dim.z-1 do\r
+               for y = 0, dim.y-1 do\r
+                       for x = 0, dim.x-1 do\r
+                               local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}\r
+                               local meta = get_meta(pos):to_table()\r
+                               pos = vector.add(pos, off)\r
+                               get_meta(pos):from_table(meta)\r
                        end\r
-                       pos.x = pos.x - 1\r
                end\r
        end\r
+       end\r
+\r
        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)\r
+--- Deletes all node metadata in the region\r
+-- @param pos1\r
+-- @param pos2\r
+-- @return The number of nodes that had their meta deleted.\r
+function worldedit.delete_meta(pos1, pos2)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
-       --make area stay loaded\r
-       local manip = minetest.get_voxel_manip()\r
-       manip:read_from_map(pos1, pos2)\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
-       local get_node, get_meta, add_node, remove_node = minetest.get_node, minetest.get_meta, minetest.add_node, minetest.remove_node\r
-       if amount < 0 then\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 = get_node(pos) --obtain current node\r
-                                       local meta = get_meta(pos):to_table() --get metadata of current node\r
-                                       remove_node(pos)\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       add_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
-                       end\r
-                       pos.x = pos.x + 1\r
+       local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)\r
+       local get_meta = minetest.get_meta\r
+       for _, pos in ipairs(meta_positions) do\r
+               get_meta(pos):from_table(nil)\r
+       end\r
+\r
+       return #meta_positions\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
+\r
+       local dim = vector.add(vector.subtract(pos2, pos1), 1)\r
+       local overlap = math.abs(amount) < dim[axis]\r
+       -- Decide if we need to copy metadata backwards\r
+       local backwards = overlap and amount > 0\r
+\r
+       local function nuke_area(my_off, my_dim)\r
+               if my_dim.x == 0 or my_dim.y == 0 or my_dim.z == 0 then\r
+                       return\r
                end\r
+               local my_pos1 = vector.add(pos1, my_off)\r
+               local my_pos2 = vector.subtract(vector.add(my_pos1, my_dim), 1)\r
+               worldedit.set(my_pos1, my_pos2, "air")\r
+               worldedit.delete_meta(my_pos1, my_pos2)\r
+       end\r
+\r
+       -- Copy stuff to new location\r
+       local off = {x=0, y=0, z=0}\r
+       off[axis] = amount\r
+       worldedit.copy2(pos1, pos2, off, backwards)\r
+       -- Nuke old area\r
+       if not overlap then\r
+               nuke_area({x=0, y=0, z=0}, dim)\r
        else\r
-               local pos = {x=pos2.x, 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 = get_node(pos) --obtain current node\r
-                                       local meta = get_meta(pos):to_table() --get metadata of current node\r
-                                       remove_node(pos)\r
-                                       local value = pos[axis] --store current position\r
-                                       pos[axis] = value + amount --move along axis\r
-                                       add_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
-                       end\r
-                       pos.x = pos.x - 1\r
+               -- Source and destination region are overlapping, which means we can't\r
+               -- blindly delete the [pos1, pos2] area\r
+               local leftover = vector.new(dim) -- size of the leftover slice\r
+               leftover[axis] = math.abs(amount)\r
+               if amount > 0 then\r
+                       nuke_area({x=0, y=0, z=0}, leftover)\r
+               else\r
+                       local top = {x=0, y=0, z=0} -- offset of the leftover slice from pos1\r
+                       top[axis] = dim[axis] - math.abs(amount)\r
+                       nuke_area(top, leftover)\r
                end\r
        end\r
+\r
        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)\r
+--- Duplicates a region along `axis` `amount` times.\r
+-- Stacking is spread across server steps.\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, finished)\r
        local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
        local length = pos2[axis] - pos1[axis] + 1\r
        if count < 0 then\r
                count = -count\r
                length = -length\r
        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)\r
+\r
+       local i, distance = 0, 0\r
+       local function step()\r
+               distance = distance + length\r
+               worldedit.copy(pos1, pos2, axis, distance)\r
+               i = i + 1\r
+               return i >= count\r
        end\r
+       deferred_execution(step, finished)\r
+\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)\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
 \r
-       --prepare schematic of large node\r
-       local get_node, get_meta, place_schematic = minetest.get_node, minetest.get_meta, minetest.place_schematic\r
-       local placeholder_node = {name="", param1=0, param2=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, factor ^ 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=factor, y=factor, z=factor}, data=nodes}\r
+       local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes}\r
 \r
-       local size = factor - 1\r
+       local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1\r
 \r
-       --make area stay loaded\r
-       local manip = minetest.get_voxel_manip()\r
-       local new_pos2 = {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
-       manip:read_from_map(pos1, new_pos2)\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
+       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 = get_node(pos) --obtain current node\r
-                               local meta = get_meta(pos):to_table() --get meta of current node\r
+                               local node = get_node(pos) -- Get current node\r
+                               local meta = 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
+                               -- 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
+                               -- Create large node\r
                                placeholder_node.name = node.name\r
-                               placeholder_node.param1, placeholder_node.param2 = node.param1, node.param2\r
-                               bigpos.x, bigpos.y, bigpos.z = posx, posy, posz\r
-                               place_schematic(bigpos, schematic)\r
-\r
-                               --fill in large node meta\r
-                               if next(meta.fields) ~= nil and next(meta.inventory) ~= nil then --node has meta fields\r
-                                       for x = 0, size do\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
-                                                               get_meta(bigpos):from_table(meta) --set metadata of new node\r
-                                                       end\r
-                                               end\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
@@ -347,11 +411,15 @@ worldedit.scale = function(pos1, pos2, factor)
                end\r
                pos.x = pos.x - 1\r
        end\r
-       return worldedit.volume(pos1, pos2) * (factor ^ 3), pos1, new_pos2\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)\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
@@ -367,37 +435,36 @@ worldedit.transpose = function(pos1, pos2, axis1, axis2)
                end\r
        end\r
 \r
-       --calculate the new position 2 after transposition\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
-       --make area stay loaded\r
-       local manip = minetest.get_voxel_manip()\r
-       local upperbound = {x=pos2.x, y=pos2.y, z=pos2.z}\r
-       if upperbound[axis1] < new_pos2[axis1] then upperbound[axis1] = new_pos2[axis1] end\r
-       if upperbound[axis2] < new_pos2[axis2] then upperbound[axis2] = new_pos2[axis2] end\r
-       manip:read_from_map(pos1, upperbound)\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
-       local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node\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
+                               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 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
-                                       add_node(pos, node1)\r
+                                       set_node(pos, node1)\r
                                        get_meta(pos):from_table(meta1)\r
-                                       pos[axis1], pos[axis2] = value1, value2 --restore position values\r
-                                       add_node(pos, node2)\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
@@ -409,19 +476,20 @@ worldedit.transpose = function(pos1, pos2, axis1, axis2)
        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)\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
-       --make area stay loaded\r
-       local manip = minetest.get_voxel_manip()\r
-       manip:read_from_map(pos1, pos2)\r
+       worldedit.keep_loaded(pos1, pos2)\r
 \r
-       --wip: flip the region slice by slice along the flip axis using schematic method\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
-       local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node\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
@@ -429,14 +497,14 @@ worldedit.flip = function(pos1, pos2, axis)
                        while pos.z <= pos2.z do\r
                                local node1 = get_node(pos)\r
                                local meta1 = get_meta(pos):to_table()\r
-                               local value = pos[axis]\r
-                               pos[axis] = start - value\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
-                               add_node(pos, node1)\r
+                               set_node(pos, node1)\r
                                get_meta(pos):from_table(meta1)\r
-                               pos[axis] = value\r
-                               add_node(pos, node2)\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
@@ -447,63 +515,75 @@ worldedit.flip = function(pos1, pos2, axis)
        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)\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)\r
-               count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)\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)\r
-               count = worldedit.flip(pos1, pos2, axis2)\r
+               worldedit.flip(pos1, pos2, other1)\r
+               count = worldedit.flip(pos1, pos2, other2)\r
        elseif angle == 270 then\r
-               worldedit.flip(pos1, pos2, axis2)\r
-               count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)\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) --wip: support 6D facedir rotation along arbitrary axis\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 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
-       --make area stay loaded\r
-       local manip = minetest.get_voxel_manip()\r
-       manip:read_from_map(pos1, pos2)\r
+       worldedit.keep_loaded(pos1, pos2)\r
 \r
        local count = 0\r
-       local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node\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
@@ -513,17 +593,20 @@ worldedit.orient = function(pos1, pos2, angle) --wip: support 6D facedir rotatio
                                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 = get_meta(pos):to_table()\r
-                                               add_node(pos, node)\r
-                                               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 = get_meta(pos):to_table()\r
-                                               add_node(pos, node)\r
-                                               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
@@ -536,42 +619,73 @@ worldedit.orient = function(pos1, pos2, angle) --wip: support 6D facedir rotatio
        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)\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
 \r
-       --make area stay loaded\r
-       local manip = minetest.get_voxel_manip()\r
-       manip:read_from_map(pos1, pos2)\r
+       local vmanip = minetest.get_voxel_manip(pos1, pos2)\r
+       vmanip:write_to_map()\r
+       vmanip:update_map() -- this updates the lighting\r
 \r
-       local nodes = minetest.find_nodes_in_area(pos1, pos2, "air")\r
-       local dig_node = minetest.dig_node\r
-       for _, pos in ipairs(nodes) do\r
-               dig_node(pos)\r
-       end\r
-       return #nodes\r
+       return worldedit.volume(pos1, pos2)\r
 end\r
 \r
---clears all objects in a region defined by the positions `pos1` and `pos2`, returning the number of objects cleared\r
-worldedit.clearobjects = function(pos1, pos2)\r
-       local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
 \r
-       --make area stay loaded\r
-       local manip = minetest.get_voxel_manip()\r
-       manip:read_from_map(pos1, pos2)\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
-       local pos1x, pos1y, pos1z = pos1.x, pos1.y, pos1.z\r
-       local pos2x, pos2y, pos2z = pos2.x + 1, pos2.y + 1, pos2.z + 1\r
-       local center = {x=(pos1x + pos2x) / 2, y=(pos1y + pos2y) / 2, z=(pos1z + pos2z) / 2} --center of region\r
-       local radius = ((center.x - pos1x + 0.5) + (center.y - pos1y + 0.5) + (center.z - pos1z + 0.5)) ^ 0.5 --bounding sphere radius\r
-       local count = 0\r
-       for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do --all objects in bounding sphere\r
+       worldedit.keep_loaded(pos1, pos2)\r
+\r
+       local function should_delete(obj)\r
+               -- Avoid players and WorldEdit entities\r
+               if obj:is_player() then\r
+                       return false\r
+               end\r
                local entity = obj:get_luaentity()\r
-               if not (entity and entity.name:find("^worldedit:")) then --avoid WorldEdit entities\r
-                       local pos = obj:getpos()\r
-                       if pos.x >= pos1x and pos.x <= pos2x\r
-                       and pos.y >= pos1y and pos.y <= pos2y\r
-                       and pos.z >= pos1z and pos.z <= pos2z then --inside region\r
+               return not entity or not entity.name:find("^worldedit:")\r
+       end\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
+       local count = 0\r
+       if minetest.get_objects_in_area then\r
+               local objects = minetest.get_objects_in_area({x=pos1x, y=pos1y, z=pos1z},\r
+                       {x=pos2x, y=pos2y, z=pos2z})\r
+\r
+               for _, obj in pairs(objects) do\r
+                       if should_delete(obj) then\r
+                               obj:remove()\r
+                               count = count + 1\r
+                       end\r
+               end\r
+               return count\r
+       end\r
+\r
+       -- Fallback implementation via get_objects_inside_radius\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
+       for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do\r
+               if should_delete(obj) 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
@@ -579,3 +693,4 @@ worldedit.clearobjects = function(pos1, pos2)
        end\r
        return count\r
 end\r
+\r