worldedit = worldedit or {} local minetest = minetest --local copy of global --wip: test the entire API again to make sure it works --wip: remove env parameter where no longer needed in chat commands module --modifies positions `pos1` and `pos2` so that each component of `pos1` is less than or equal to its corresponding conent of `pos2`, returning two new positions worldedit.sort_pos = function(pos1, pos2) pos1 = {x=pos1.x, y=pos1.y, z=pos1.z} pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} if pos1.x > pos2.x then pos2.x, pos1.x = pos1.x, pos2.x end if pos1.y > pos2.y then pos2.y, pos1.y = pos1.y, pos2.y end if pos1.z > pos2.z then pos2.z, pos1.z = pos1.z, pos2.z end return pos1, pos2 end --determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume worldedit.volume = function(pos1, pos2) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1) end --sets a region defined by positions `pos1` and `pos2` to `nodename`, returning the number of nodes filled worldedit.set = function(pos1, pos2, nodename) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) --set up voxel manipulator local manip = minetest.get_voxel_manip() manip:read_from_map(pos1, pos2) --fill nodes table with node to be set local nodes = {} local node_id = minetest.get_content_id(nodename) for i = 1, (pos2.x - pos1.x) * (pos2.y - pos1.y) * (pos2.z - pos1.z) do nodes[i] = node_id end --update map nodes manip:set_data(nodes) manip:write_to_map() manip:update_map() return worldedit.volume(pos1, pos2) end --replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced worldedit.replace = function(pos1, pos2, searchnode, replacenode) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local node = {name=replacenode} local add_node = minetest.add_node local nodes = minetest.find_nodes_in_area(pos1, pos2, searchnode) for _, pos in ipairs(nodes) do add_node(pos, node) end return #nodes end --replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced worldedit.replaceinverse = function(pos1, pos2, searchnode, replacenode) --wip: use voxelmanip get_data for this local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local pos = {x=pos1.x, y=0, z=0} local node = {name=replacenode} local get_node, add_node = minetest.get_node, minetest.add_node local count = 0 while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local name = get_node(pos).name if name ~= "ignore" and name ~= searchnode then add_node(pos, node) count = count + 1 end pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return count end --copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes copied worldedit.copy = function(pos1, pos2, axis, amount, env) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) if env == nil then env = minetest.env end --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) if amount < 0 then local pos = {x=pos1.x, y=0, z=0} while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local node = env:get_node(pos) --obtain current node local meta = env:get_meta(pos):to_table() --get meta of current node local value = pos[axis] --store current position pos[axis] = value + amount --move along axis env:add_node(pos, node) --copy node to new position env:get_meta(pos):from_table(meta) --set metadata of new node pos[axis] = value --restore old position pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end else local pos = {x=pos2.x, y=0, z=0} while pos.x >= pos1.x do pos.y = pos2.y while pos.y >= pos1.y do pos.z = pos2.z while pos.z >= pos1.z do local node = minetest.env:get_node(pos) --obtain current node local meta = env:get_meta(pos):to_table() --get meta of current node local value = pos[axis] --store current position pos[axis] = value + amount --move along axis minetest.env:add_node(pos, node) --copy node to new position env:get_meta(pos):from_table(meta) --set metadata of new node pos[axis] = value --restore old position pos.z = pos.z - 1 end pos.y = pos.y - 1 end pos.x = pos.x - 1 end end return worldedit.volume(pos1, pos2) end --moves the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes moved worldedit.move = function(pos1, pos2, axis, amount, env) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) if env == nil then env = minetest.env end --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) if amount < 0 then local pos = {x=pos1.x, y=0, z=0} while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local node = env:get_node(pos) --obtain current node local meta = env:get_meta(pos):to_table() --get metadata of current node env:remove_node(pos) local value = pos[axis] --store current position pos[axis] = value + amount --move along axis env:add_node(pos, node) --move node to new position env:get_meta(pos):from_table(meta) --set metadata of new node pos[axis] = value --restore old position pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end else local pos = {x=pos2.x, y=0, z=0} while pos.x >= pos1.x do pos.y = pos2.y while pos.y >= pos1.y do pos.z = pos2.z while pos.z >= pos1.z do local node = env:get_node(pos) --obtain current node local meta = env:get_meta(pos):to_table() --get metadata of current node env:remove_node(pos) local value = pos[axis] --store current position pos[axis] = value + amount --move along axis env:add_node(pos, node) --move node to new position env:get_meta(pos):from_table(meta) --set metadata of new node pos[axis] = value --restore old position pos.z = pos.z - 1 end pos.y = pos.y - 1 end pos.x = pos.x - 1 end end return worldedit.volume(pos1, pos2) end --duplicates the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") `count` times, returning the number of nodes stacked worldedit.stack = function(pos1, pos2, axis, count, env) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local length = pos2[axis] - pos1[axis] + 1 if count < 0 then count = -count length = -length end local amount = 0 local copy = worldedit.copy for i = 1, count do amount = amount + length copy(pos1, pos2, axis, amount, env) end return worldedit.volume(pos1, pos2) end --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 worldedit.scale = function(pos1, pos2, factor) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) --prepare schematic of large node local get_node, get_meta, place_schematic = minetest.get_node, minetest.get_meta, minetest.place_schematic local placeholder_node = {name="", param1=0, param2=0} local nodes = {} for i = 1, size ^ 3 do nodes[i] = placeholder_node end local schematic = {size={x=size, y=size, z=size}, data=nodes} local pos = {x=pos2.x, y=0, z=0} local bigpos = {x=0, y=0, z=0} size = factor - 1 while pos.x >= pos1.x do pos.y = pos2.y while pos.y >= pos1.y do pos.z = pos2.z while pos.z >= pos1.z do local node = get_node(pos) --obtain current node local meta = get_meta(pos):to_table() --get meta of current node local value = pos[axis] --store current position 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 --create large node placeholder_node[1], placeholder_node[3] = node.name, node.param2 bigpos.x, bigpos.y, bigpos.z = posx, posy, posz place_schematic(bigpos, schematic) for x = 0, size do --fill in large node meta for y = 0, size do for z = 0, size do bigpos.x, bigpos.y, bigpos.z = posx + x, posy + y, posz + z get_meta(bigpos):from_table(meta) --set metadata of new node end end end pos.z = pos.z - 1 end pos.y = pos.y - 1 end pos.x = pos.x - 1 end 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} return worldedit.volume(pos1, pos2), pos1, newpos2 end --transposes a region defined by the positions `pos1` and `pos2` between the `axis1` and `axis2` axes, returning the number of nodes transposed, the new transposed position 1, and the new transposed position 2 worldedit.transpose = function(pos1, pos2, axis1, axis2, env) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local compare local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2] if extent1 > extent2 then compare = function(extent1, extent2) return extent1 > extent2 end else compare = function(extent1, extent2) return extent1 < extent2 end end --calculate the new position 2 after transposition local newpos2 = {x=pos2.x, y=pos2.y, z=pos2.z} newpos2[axis1] = pos1[axis1] + extent2 newpos2[axis2] = pos1[axis2] + extent1 local pos = {x=pos1.x, y=0, z=0} local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2] if compare(extent1, extent2) then --transpose only if below the diagonal local node1 = get_node(pos) local meta1 = get_meta(pos):to_table() local value1, value2 = pos[axis1], pos[axis2] --save position values pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents local node2 = get_node(pos) local meta2 = get_meta(pos):to_table() add_node(pos, node1) get_meta(pos):from_table(meta1) pos[axis1], pos[axis2] = value1, value2 --restore position values add_node(pos, node2) get_meta(pos):from_table(meta2) end pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return worldedit.volume(pos1, pos2), pos1, newpos2 end --flips a region defined by the positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z"), returning the number of nodes flipped worldedit.flip = function(pos1, pos2, axis, env) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) --wip: flip the region slice by slice along the flip axis using schematic method local pos = {x=pos1.x, y=0, z=0} local start = pos1[axis] + pos2[axis] pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2) if env == nil then env = minetest.env end while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local node1 = env:get_node(pos) local meta1 = env:get_meta(pos):to_table() local value = pos[axis] pos[axis] = start - value local node2 = env:get_node(pos) local meta2 = env:get_meta(pos):to_table() env:add_node(pos, node1) env:get_meta(pos):from_table(meta1) pos[axis] = value env:add_node(pos, node2) env:get_meta(pos):from_table(meta2) pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return worldedit.volume(pos1, pos2) end --rotates a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise around axis `axis` (90 degree increment), returning the number of nodes rotated worldedit.rotate = function(pos1, pos2, axis, angle, env) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local axis1, axis2 if axis == "x" then axis1, axis2 = "z", "y" elseif axis == "y" then axis1, axis2 = "x", "z" else --axis == "z" axis1, axis2 = "y", "x" end angle = angle % 360 local count if angle == 90 then worldedit.flip(pos1, pos2, axis1, env) count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env) elseif angle == 180 then worldedit.flip(pos1, pos2, axis1, env) count = worldedit.flip(pos1, pos2, axis2, env) elseif angle == 270 then worldedit.flip(pos1, pos2, axis2, env) count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env) end return count, pos1, pos2 end --rotates all oriented nodes in a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise (90 degree increment) around the Y axis, returning the number of nodes oriented worldedit.orient = function(pos1, pos2, angle, env) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local registered_nodes = minetest.registered_nodes local wallmounted = { [90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3}, [180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4}, [270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2} } local facedir = { [90]={[0]=1, [1]=2, [2]=3, [3]=0}, [180]={[0]=2, [1]=3, [2]=0, [3]=1}, [270]={[0]=3, [1]=0, [2]=1, [3]=2} } angle = angle % 360 if angle == 0 then return 0 end local wallmounted_substitution = wallmounted[angle] local facedir_substitution = facedir[angle] local count = 0 local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node local pos = {x=pos1.x, y=0, z=0} while pos.x <= pos2.x do pos.y = pos1.y while pos.y <= pos2.y do pos.z = pos1.z while pos.z <= pos2.z do local node = get_node(pos) local def = registered_nodes[node.name] if def then if def.paramtype2 == "wallmounted" then node.param2 = wallmounted_substitution[node.param2] local meta = get_meta(pos):to_table() add_node(pos, node) get_meta(pos):from_table(meta) count = count + 1 elseif def.paramtype2 == "facedir" then node.param2 = facedir_substitution[node.param2] local meta = get_meta(pos):to_table() add_node(pos, node) get_meta(pos):from_table(meta) count = count + 1 end end pos.z = pos.z + 1 end pos.y = pos.y + 1 end pos.x = pos.x + 1 end return count end --fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated worldedit.fixlight = function(pos1, pos2, env) local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local nodes = minetest.find_nodes_in_area(pos1, pos2, "air") local dig_node = minetest.dig_node for _, pos in ipairs(nodes) do dig_node(pos) end return #nodes end