1 worldedit = worldedit or {}
\r
2 local minetest = minetest --local copy of global
\r
4 --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
5 worldedit.sort_pos = function(pos1, pos2)
\r
6 pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}
\r
7 pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
\r
8 if pos1.x > pos2.x then
\r
9 pos2.x, pos1.x = pos1.x, pos2.x
\r
11 if pos1.y > pos2.y then
\r
12 pos2.y, pos1.y = pos1.y, pos2.y
\r
14 if pos1.z > pos2.z then
\r
15 pos2.z, pos1.z = pos1.z, pos2.z
\r
20 --determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume
\r
21 worldedit.volume = function(pos1, pos2)
\r
22 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
23 return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1)
\r
26 --sets a region defined by positions `pos1` and `pos2` to `nodename`, returning the number of nodes filled
\r
27 worldedit.set = function(pos1, pos2, nodename)
\r
28 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
30 --set up voxel manipulator
\r
31 local manip = minetest.get_voxel_manip()
\r
32 local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)
\r
33 local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})
\r
35 --fill emerged area with ignore
\r
37 local ignore = minetest.get_content_id("ignore")
\r
38 for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do
\r
42 --fill selected area with node
\r
43 local node_id = minetest.get_content_id(nodename)
\r
44 for i in area:iterp(pos1, pos2) do
\r
49 manip:set_data(nodes)
\r
50 manip:write_to_map()
\r
53 return worldedit.volume(pos1, pos2)
\r
56 --replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced
\r
57 worldedit.replace = function(pos1, pos2, searchnode, replacenode)
\r
58 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
60 --set up voxel manipulator
\r
61 local manip = minetest.get_voxel_manip()
\r
62 local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)
\r
63 local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})
\r
65 local nodes = manip:get_data()
\r
66 local searchnode_id = minetest.get_content_id(searchnode)
\r
67 local replacenode_id = minetest.get_content_id(replacenode)
\r
69 for i in area:iterp(pos1, pos2) do --replace searchnode with replacenode
\r
70 if nodes[i] == searchnode_id then
\r
71 nodes[i] = replacenode_id
\r
77 manip:set_data(nodes)
\r
78 manip:write_to_map()
\r
84 --replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced
\r
85 worldedit.replaceinverse = function(pos1, pos2, searchnode, replacenode)
\r
86 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
88 --set up voxel manipulator
\r
89 local manip = minetest.get_voxel_manip()
\r
90 local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)
\r
91 local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})
\r
93 local nodes = manip:get_data()
\r
94 local searchnode_id = minetest.get_content_id(searchnode)
\r
95 local replacenode_id = minetest.get_content_id(replacenode)
\r
97 for i in area:iterp(pos1, pos2) do --replace anything that is not searchnode with replacenode
\r
98 if nodes[i] ~= searchnode_id then
\r
99 nodes[i] = replacenode_id
\r
105 manip:set_data(nodes)
\r
106 manip:write_to_map()
\r
112 worldedit.copy = function(pos1, pos2, axis, amount)
\r
113 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
115 if amount == 0 then
\r
119 local other1, other2
\r
120 if axis == "x" then
\r
121 other1, other2 = "y", "z"
\r
122 elseif axis == "y" then
\r
123 other1, other2 = "x", "z"
\r
125 other1, other2 = "x", "y"
\r
128 --make area stay loaded
\r
129 local manip = minetest.get_voxel_manip()
\r
130 manip:read_from_map(pos1, pos2)
\r
132 --prepare slice along axis
\r
135 [other1]=pos2[other1] - pos1[other1] + 1,
\r
136 [other2]=pos2[other2] - pos1[other2] + 1,
\r
139 local schematic = {size=extent, data=nodes}
\r
141 local currentpos = {x=pos1.x, y=pos1.y, z=pos1.z}
\r
142 local stride = {x=1, y=extent.x, z=extent.x * extent.y}
\r
143 local get_node = minetest.get_node
\r
144 for index1 = 1, extent[axis] do --go through each slice
\r
145 --copy slice into schematic
\r
146 local newindex1 = (index1 + offset[axis]) * stride[axis] + 1 --offset contributed by axis plus 1 to make it 1-indexed
\r
147 for index2 = 1, extent[other1] do
\r
148 local newindex2 = newindex1 + (index2 + offset[other1]) * stride[other1]
\r
149 for index3 = 1, extent[other2] do
\r
150 local i = newindex2 + (index3 + offset[other2]) * stride[other2]
\r
151 local node = get_node(pos)
\r
152 node.param1 = 255 --node will always appear
\r
157 --copy schematic to target
\r
158 currentpos[axis] = currentpos[axis] + amount
\r
159 place_schematic(currentpos, schematic)
\r
163 currentpos[axis] = currentpos[axis] + 1
\r
165 return worldedit.volume(pos1, pos2)
\r
168 --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
169 worldedit.copy = function(pos1, pos2, axis, amount)
\r
170 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
172 --make area stay loaded
\r
173 local manip = minetest.get_voxel_manip()
\r
174 manip:read_from_map(pos1, pos2)
\r
176 local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
\r
178 local pos = {x=pos1.x, y=0, z=0}
\r
179 while pos.x <= pos2.x do
\r
181 while pos.y <= pos2.y do
\r
183 while pos.z <= pos2.z do
\r
184 local node = get_node(pos) --obtain current node
\r
185 local meta = get_meta(pos):to_table() --get meta of current node
\r
186 local value = pos[axis] --store current position
\r
187 pos[axis] = value + amount --move along axis
\r
188 add_node(pos, node) --copy node to new position
\r
189 get_meta(pos):from_table(meta) --set metadata of new node
\r
190 pos[axis] = value --restore old position
\r
198 local pos = {x=pos2.x, y=0, z=0}
\r
199 while pos.x >= pos1.x do
\r
201 while pos.y >= pos1.y do
\r
203 while pos.z >= pos1.z do
\r
204 local node = get_node(pos) --obtain current node
\r
205 local meta = get_meta(pos):to_table() --get meta of current node
\r
206 local value = pos[axis] --store current position
\r
207 pos[axis] = value + amount --move along axis
\r
208 add_node(pos, node) --copy node to new position
\r
209 get_meta(pos):from_table(meta) --set metadata of new node
\r
210 pos[axis] = value --restore old position
\r
218 return worldedit.volume(pos1, pos2)
\r
221 --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
222 worldedit.move = function(pos1, pos2, axis, amount)
\r
223 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
225 --make area stay loaded
\r
226 local manip = minetest.get_voxel_manip()
\r
227 manip:read_from_map(pos1, pos2)
\r
229 --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
230 local get_node, get_meta, add_node, remove_node = minetest.get_node, minetest.get_meta, minetest.add_node, minetest.remove_node
\r
232 local pos = {x=pos1.x, y=0, z=0}
\r
233 while pos.x <= pos2.x do
\r
235 while pos.y <= pos2.y do
\r
237 while pos.z <= pos2.z do
\r
238 local node = get_node(pos) --obtain current node
\r
239 local meta = get_meta(pos):to_table() --get metadata of current node
\r
241 local value = pos[axis] --store current position
\r
242 pos[axis] = value + amount --move along axis
\r
243 add_node(pos, node) --move node to new position
\r
244 get_meta(pos):from_table(meta) --set metadata of new node
\r
245 pos[axis] = value --restore old position
\r
253 local pos = {x=pos2.x, y=0, z=0}
\r
254 while pos.x >= pos1.x do
\r
256 while pos.y >= pos1.y do
\r
258 while pos.z >= pos1.z do
\r
259 local node = get_node(pos) --obtain current node
\r
260 local meta = get_meta(pos):to_table() --get metadata of current node
\r
262 local value = pos[axis] --store current position
\r
263 pos[axis] = value + amount --move along axis
\r
264 add_node(pos, node) --move node to new position
\r
265 get_meta(pos):from_table(meta) --set metadata of new node
\r
266 pos[axis] = value --restore old position
\r
274 return worldedit.volume(pos1, pos2)
\r
277 --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
278 worldedit.stack = function(pos1, pos2, axis, count)
\r
279 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
280 local length = pos2[axis] - pos1[axis] + 1
\r
286 local copy = worldedit.copy
\r
287 for i = 1, count do
\r
288 amount = amount + length
\r
289 copy(pos1, pos2, axis, amount)
\r
291 return worldedit.volume(pos1, pos2) * count
\r
294 --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
295 worldedit.scale = function(pos1, pos2, factor)
\r
296 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
298 --prepare schematic of large node
\r
299 local get_node, get_meta, place_schematic = minetest.get_node, minetest.get_meta, minetest.place_schematic
\r
300 local placeholder_node = {name="", param1=255, param2=0}
\r
302 for i = 1, factor ^ 3 do
\r
303 nodes[i] = placeholder_node
\r
305 local schematic = {size={x=factor, y=factor, z=factor}, data=nodes}
\r
307 local size = factor - 1
\r
309 --make area stay loaded
\r
310 local manip = minetest.get_voxel_manip()
\r
311 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
312 manip:read_from_map(pos1, new_pos2)
\r
314 local pos = {x=pos2.x, y=0, z=0}
\r
315 local bigpos = {x=0, y=0, z=0}
\r
316 while pos.x >= pos1.x do
\r
318 while pos.y >= pos1.y do
\r
320 while pos.z >= pos1.z do
\r
321 local node = get_node(pos) --obtain current node
\r
322 local meta = get_meta(pos):to_table() --get meta of current node
\r
324 local value = pos[axis] --store current position
\r
325 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
327 --create large node
\r
328 placeholder_node.name = node.name
\r
329 placeholder_node.param2 = node.param2
\r
330 bigpos.x, bigpos.y, bigpos.z = posx, posy, posz
\r
331 place_schematic(bigpos, schematic)
\r
333 --fill in large node meta
\r
334 if next(meta.fields) ~= nil and next(meta.inventory) ~= nil then --node has meta fields
\r
338 bigpos.x, bigpos.y, bigpos.z = posx + x, posy + y, posz + z
\r
339 get_meta(bigpos):from_table(meta) --set metadata of new node
\r
350 return worldedit.volume(pos1, pos2) * (factor ^ 3), pos1, new_pos2
\r
353 --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
354 worldedit.transpose = function(pos1, pos2, axis1, axis2)
\r
355 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
358 local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]
\r
360 if extent1 > extent2 then
\r
361 compare = function(extent1, extent2)
\r
362 return extent1 > extent2
\r
365 compare = function(extent1, extent2)
\r
366 return extent1 < extent2
\r
370 --calculate the new position 2 after transposition
\r
371 local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
\r
372 new_pos2[axis1] = pos1[axis1] + extent2
\r
373 new_pos2[axis2] = pos1[axis2] + extent1
\r
375 --make area stay loaded
\r
376 local manip = minetest.get_voxel_manip()
\r
377 local upperbound = {x=pos2.x, y=pos2.y, z=pos2.z}
\r
378 if upperbound[axis1] < new_pos2[axis1] then upperbound[axis1] = new_pos2[axis1] end
\r
379 if upperbound[axis2] < new_pos2[axis2] then upperbound[axis2] = new_pos2[axis2] end
\r
380 manip:read_from_map(pos1, upperbound)
\r
382 local pos = {x=pos1.x, y=0, z=0}
\r
383 local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
\r
384 while pos.x <= pos2.x do
\r
386 while pos.y <= pos2.y do
\r
388 while pos.z <= pos2.z do
\r
389 local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
\r
390 if compare(extent1, extent2) then --transpose only if below the diagonal
\r
391 local node1 = get_node(pos)
\r
392 local meta1 = get_meta(pos):to_table()
\r
393 local value1, value2 = pos[axis1], pos[axis2] --save position values
\r
394 pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents
\r
395 local node2 = get_node(pos)
\r
396 local meta2 = get_meta(pos):to_table()
\r
397 add_node(pos, node1)
\r
398 get_meta(pos):from_table(meta1)
\r
399 pos[axis1], pos[axis2] = value1, value2 --restore position values
\r
400 add_node(pos, node2)
\r
401 get_meta(pos):from_table(meta2)
\r
409 return worldedit.volume(pos1, pos2), pos1, new_pos2
\r
412 --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
413 worldedit.flip = function(pos1, pos2, axis)
\r
414 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
416 --make area stay loaded
\r
417 local manip = minetest.get_voxel_manip()
\r
418 manip:read_from_map(pos1, pos2)
\r
420 --wip: flip the region slice by slice along the flip axis using schematic method
\r
421 local pos = {x=pos1.x, y=0, z=0}
\r
422 local start = pos1[axis] + pos2[axis]
\r
423 pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
\r
424 local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
\r
425 while pos.x <= pos2.x do
\r
427 while pos.y <= pos2.y do
\r
429 while pos.z <= pos2.z do
\r
430 local node1 = get_node(pos)
\r
431 local meta1 = get_meta(pos):to_table()
\r
432 local value = pos[axis]
\r
433 pos[axis] = start - value
\r
434 local node2 = get_node(pos)
\r
435 local meta2 = get_meta(pos):to_table()
\r
436 add_node(pos, node1)
\r
437 get_meta(pos):from_table(meta1)
\r
439 add_node(pos, node2)
\r
440 get_meta(pos):from_table(meta2)
\r
447 return worldedit.volume(pos1, pos2)
\r
450 --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
451 worldedit.rotate = function(pos1, pos2, axis, angle)
\r
452 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
455 if axis == "x" then
\r
456 axis1, axis2 = "z", "y"
\r
457 elseif axis == "y" then
\r
458 axis1, axis2 = "x", "z"
\r
460 axis1, axis2 = "y", "x"
\r
462 angle = angle % 360
\r
465 if angle == 90 then
\r
466 worldedit.flip(pos1, pos2, axis1)
\r
467 count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
\r
468 elseif angle == 180 then
\r
469 worldedit.flip(pos1, pos2, axis1)
\r
470 count = worldedit.flip(pos1, pos2, axis2)
\r
471 elseif angle == 270 then
\r
472 worldedit.flip(pos1, pos2, axis2)
\r
473 count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
\r
475 return count, pos1, pos2
\r
478 --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
479 worldedit.orient = function(pos1, pos2, angle) --wip: support 6D facedir rotation along arbitrary axis
\r
480 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
481 local registered_nodes = minetest.registered_nodes
\r
483 local wallmounted = {
\r
484 [90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3},
\r
485 [180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4},
\r
486 [270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2}
\r
489 [90]={[0]=1, [1]=2, [2]=3, [3]=0},
\r
490 [180]={[0]=2, [1]=3, [2]=0, [3]=1},
\r
491 [270]={[0]=3, [1]=0, [2]=1, [3]=2}
\r
494 angle = angle % 360
\r
498 local wallmounted_substitution = wallmounted[angle]
\r
499 local facedir_substitution = facedir[angle]
\r
501 --make area stay loaded
\r
502 local manip = minetest.get_voxel_manip()
\r
503 manip:read_from_map(pos1, pos2)
\r
506 local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node
\r
507 local pos = {x=pos1.x, y=0, z=0}
\r
508 while pos.x <= pos2.x do
\r
510 while pos.y <= pos2.y do
\r
512 while pos.z <= pos2.z do
\r
513 local node = get_node(pos)
\r
514 local def = registered_nodes[node.name]
\r
516 if def.paramtype2 == "wallmounted" then
\r
517 node.param2 = wallmounted_substitution[node.param2]
\r
518 local meta = get_meta(pos):to_table()
\r
519 add_node(pos, node)
\r
520 get_meta(pos):from_table(meta)
\r
522 elseif def.paramtype2 == "facedir" then
\r
523 node.param2 = facedir_substitution[node.param2]
\r
524 local meta = get_meta(pos):to_table()
\r
525 add_node(pos, node)
\r
526 get_meta(pos):from_table(meta)
\r
539 --fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated
\r
540 worldedit.fixlight = function(pos1, pos2)
\r
541 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
543 --make area stay loaded
\r
544 local manip = minetest.get_voxel_manip()
\r
545 manip:read_from_map(pos1, pos2)
\r
547 local nodes = minetest.find_nodes_in_area(pos1, pos2, "air")
\r
548 local dig_node = minetest.dig_node
\r
549 for _, pos in ipairs(nodes) do
\r
555 --clears all objects in a region defined by the positions `pos1` and `pos2`, returning the number of objects cleared
\r
556 worldedit.clearobjects = function(pos1, pos2)
\r
557 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
559 --make area stay loaded
\r
560 local manip = minetest.get_voxel_manip()
\r
561 manip:read_from_map(pos1, pos2)
\r
563 local pos1x, pos1y, pos1z = pos1.x, pos1.y, pos1.z
\r
564 local pos2x, pos2y, pos2z = pos2.x + 1, pos2.y + 1, pos2.z + 1
\r
565 local center = {x=(pos1x + pos2x) / 2, y=(pos1y + pos2y) / 2, z=(pos1z + pos2z) / 2} --center of region
\r
566 local radius = ((center.x - pos1x + 0.5) + (center.y - pos1y + 0.5) + (center.z - pos1z + 0.5)) ^ 0.5 --bounding sphere radius
\r
568 for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do --all objects in bounding sphere
\r
569 local entity = obj:get_luaentity()
\r
570 if not (entity and entity.name:find("^worldedit:")) then --avoid WorldEdit entities
\r
571 local pos = obj:getpos()
\r
572 if pos.x >= pos1x and pos.x <= pos2x
\r
573 and pos.y >= pos1y and pos.y <= pos2y
\r
574 and pos.z >= pos1z and pos.z <= pos2z then --inside region
\r