mirror of
https://github.com/Uberi/Minetest-WorldEdit.git
synced 2024-12-04 13:23:43 +01:00
Split up some files in worldedit mod
This commit is contained in:
parent
dc1150fe3d
commit
acb3ecefe4
@ -6,3 +6,10 @@ read_globals = {"minetest", "vector", "VoxelArea", "ItemStack",
|
||||
globals = {"worldedit"}
|
||||
-- Ignore these errors until someone decides to fix them
|
||||
ignore = {"212", "213", "411", "412", "421", "422", "431", "432", "631"}
|
||||
|
||||
files["worldedit/test"] = {
|
||||
read_globals = {"testnode1", "testnode2", "testnode3", "area", "check", "place_pattern"},
|
||||
}
|
||||
files["worldedit/test/init.lua"] = {
|
||||
globals = {"testnode1", "testnode2", "testnode3", "area", "check", "place_pattern"},
|
||||
}
|
||||
|
@ -39,6 +39,6 @@ if minetest.settings:get_bool("log_mods") then
|
||||
end
|
||||
|
||||
if minetest.settings:get_bool("worldedit_run_tests") then
|
||||
dofile(path .. "/test.lua")
|
||||
dofile(path .. "/test/init.lua")
|
||||
minetest.after(0, worldedit.run_tests)
|
||||
end
|
||||
|
@ -98,51 +98,6 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
|
||||
end
|
||||
|
||||
|
||||
local function deferred_execution(next_one, finished)
|
||||
-- Allocate 100% of server step for execution (might lag a little)
|
||||
local allocated_usecs =
|
||||
tonumber(minetest.settings:get("dedicated_server_step"):split(" ")[1]) * 1000000
|
||||
local function f()
|
||||
local deadline = minetest.get_us_time() + allocated_usecs
|
||||
repeat
|
||||
local is_done = next_one()
|
||||
if is_done then
|
||||
if finished then
|
||||
finished()
|
||||
end
|
||||
return
|
||||
end
|
||||
until minetest.get_us_time() >= deadline
|
||||
minetest.after(0, f)
|
||||
end
|
||||
f()
|
||||
end
|
||||
|
||||
--- Duplicates a region `amount` times with offset vector `direction`.
|
||||
-- Stacking is spread across server steps.
|
||||
-- @return The number of nodes stacked.
|
||||
function worldedit.stack2(pos1, pos2, direction, amount, finished)
|
||||
-- Protect arguments from external changes during execution
|
||||
pos1 = table.copy(pos1)
|
||||
pos2 = table.copy(pos2)
|
||||
direction = table.copy(direction)
|
||||
|
||||
local i = 0
|
||||
local translated = vector.new()
|
||||
local function step()
|
||||
translated.x = translated.x + direction.x
|
||||
translated.y = translated.y + direction.y
|
||||
translated.z = translated.z + direction.z
|
||||
worldedit.copy2(pos1, pos2, translated)
|
||||
i = i + 1
|
||||
return i >= amount
|
||||
end
|
||||
deferred_execution(step, finished)
|
||||
|
||||
return worldedit.volume(pos1, pos2) * amount
|
||||
end
|
||||
|
||||
|
||||
--- Copies a region along `axis` by `amount` nodes.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
@ -307,316 +262,6 @@ function worldedit.move(pos1, pos2, axis, amount)
|
||||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
--- Duplicates a region along `axis` `amount` times.
|
||||
-- Stacking is spread across server steps.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param axis Axis direction, "x", "y", or "z".
|
||||
-- @param count
|
||||
-- @return The number of nodes stacked.
|
||||
function worldedit.stack(pos1, pos2, axis, count, finished)
|
||||
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 i, distance = 0, 0
|
||||
local function step()
|
||||
distance = distance + length
|
||||
worldedit.copy(pos1, pos2, axis, distance)
|
||||
i = i + 1
|
||||
return i >= count
|
||||
end
|
||||
deferred_execution(step, finished)
|
||||
|
||||
return worldedit.volume(pos1, pos2) * count
|
||||
end
|
||||
|
||||
|
||||
--- Stretches a region by a factor of positive integers along the X, Y, and Z
|
||||
-- axes, respectively, with `pos1` as the origin.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param stretch_x Amount to stretch along X axis.
|
||||
-- @param stretch_y Amount to stretch along Y axis.
|
||||
-- @param stretch_z Amount to stretch along Z axis.
|
||||
-- @return The number of nodes scaled.
|
||||
-- @return The new scaled position 1.
|
||||
-- @return The new scaled position 2.
|
||||
function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
-- 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=255, param2=0}
|
||||
local nodes = {}
|
||||
for i = 1, stretch_x * stretch_y * stretch_z do
|
||||
nodes[i] = placeholder_node
|
||||
end
|
||||
local schematic = {size=vector.new(stretch_x, stretch_y, stretch_z), data=nodes}
|
||||
|
||||
local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1
|
||||
|
||||
local new_pos2 = {
|
||||
x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,
|
||||
y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,
|
||||
z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,
|
||||
}
|
||||
worldedit.keep_loaded(pos1, new_pos2)
|
||||
|
||||
local pos = vector.new(pos2.x, 0, 0)
|
||||
local big_pos = vector.new()
|
||||
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) -- Get current node
|
||||
local meta = get_meta(pos):to_table() -- Get meta of current node
|
||||
|
||||
-- Calculate far corner of the big node
|
||||
local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x
|
||||
local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y
|
||||
local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z
|
||||
|
||||
-- Create large node
|
||||
placeholder_node.name = node.name
|
||||
placeholder_node.param2 = node.param2
|
||||
big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z
|
||||
place_schematic(big_pos, schematic)
|
||||
|
||||
-- Fill in large node meta
|
||||
if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
|
||||
-- Node has meta fields
|
||||
for x = 0, size_x do
|
||||
for y = 0, size_y do
|
||||
for z = 0, size_z do
|
||||
big_pos.x = pos_x + x
|
||||
big_pos.y = pos_y + y
|
||||
big_pos.z = pos_z + z
|
||||
-- Set metadata of new node
|
||||
get_meta(big_pos):from_table(meta)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
pos.z = pos.z - 1
|
||||
end
|
||||
pos.y = pos.y - 1
|
||||
end
|
||||
pos.x = pos.x - 1
|
||||
end
|
||||
return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2
|
||||
end
|
||||
|
||||
|
||||
--- Transposes a region between two axes.
|
||||
-- @return The number of nodes transposed.
|
||||
-- @return The new transposed position 1.
|
||||
-- @return The new transposed position 2.
|
||||
function worldedit.transpose(pos1, pos2, axis1, axis2)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local 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 new_pos2 = vector.new(pos2)
|
||||
new_pos2[axis1] = pos1[axis1] + extent2
|
||||
new_pos2[axis2] = pos1[axis2] + extent1
|
||||
|
||||
local upper_bound = vector.new(pos2)
|
||||
if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end
|
||||
if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end
|
||||
worldedit.keep_loaded(pos1, upper_bound)
|
||||
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
local get_node, get_meta, set_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.set_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()
|
||||
set_node(pos, node1)
|
||||
get_meta(pos):from_table(meta1)
|
||||
pos[axis1], pos[axis2] = value1, value2 -- Restore position values
|
||||
set_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, new_pos2
|
||||
end
|
||||
|
||||
|
||||
--- Flips a region along `axis`.
|
||||
-- @return The number of nodes flipped.
|
||||
function worldedit.flip(pos1, pos2, axis)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
--- TODO: Flip the region slice by slice along the flip axis using schematic method.
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
local start = pos1[axis] + pos2[axis]
|
||||
pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
|
||||
local get_node, get_meta, set_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.set_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 node1 = get_node(pos)
|
||||
local meta1 = get_meta(pos):to_table()
|
||||
local value = pos[axis] -- Save position
|
||||
pos[axis] = start - value -- Shift position
|
||||
local node2 = get_node(pos)
|
||||
local meta2 = get_meta(pos):to_table()
|
||||
set_node(pos, node1)
|
||||
get_meta(pos):from_table(meta1)
|
||||
pos[axis] = value -- Restore position
|
||||
set_node(pos, node2)
|
||||
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 clockwise around an axis.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param axis Axis ("x", "y", or "z").
|
||||
-- @param angle Angle in degrees (90 degree increments only).
|
||||
-- @return The number of nodes rotated.
|
||||
-- @return The new first position.
|
||||
-- @return The new second position.
|
||||
function worldedit.rotate(pos1, pos2, axis, angle)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local other1, other2 = worldedit.get_axis_others(axis)
|
||||
angle = angle % 360
|
||||
|
||||
local count
|
||||
if angle == 90 then
|
||||
worldedit.flip(pos1, pos2, other1)
|
||||
count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
|
||||
elseif angle == 180 then
|
||||
worldedit.flip(pos1, pos2, other1)
|
||||
count = worldedit.flip(pos1, pos2, other2)
|
||||
elseif angle == 270 then
|
||||
worldedit.flip(pos1, pos2, other2)
|
||||
count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
|
||||
else
|
||||
error("Only 90 degree increments are supported!")
|
||||
end
|
||||
return count, pos1, pos2
|
||||
end
|
||||
|
||||
|
||||
--- Rotates all oriented nodes in a region clockwise around the Y axis.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param angle Angle in degrees (90 degree increments only).
|
||||
-- @return The number of nodes oriented.
|
||||
function worldedit.orient(pos1, pos2, angle)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local registered_nodes = minetest.registered_nodes
|
||||
|
||||
local wallmounted = {
|
||||
[90] = {0, 1, 5, 4, 2, 3, 0, 0},
|
||||
[180] = {0, 1, 3, 2, 5, 4, 0, 0},
|
||||
[270] = {0, 1, 4, 5, 3, 2, 0, 0}
|
||||
}
|
||||
local facedir = {
|
||||
[90] = { 1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16,
|
||||
9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22},
|
||||
[180] = { 2, 3, 0, 1, 10, 11, 8, 9, 6, 7, 4, 5,
|
||||
18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21},
|
||||
[270] = { 3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14,
|
||||
7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20}
|
||||
}
|
||||
|
||||
angle = angle % 360
|
||||
if angle == 0 then
|
||||
return 0
|
||||
end
|
||||
if angle % 90 ~= 0 then
|
||||
error("Only 90 degree increments are supported!")
|
||||
end
|
||||
local wallmounted_substitution = wallmounted[angle]
|
||||
local facedir_substitution = facedir[angle]
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local count = 0
|
||||
local get_node, swap_node = minetest.get_node, minetest.swap_node
|
||||
local pos = vector.new(pos1.x, 0, 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
|
||||
local paramtype2 = def.paramtype2
|
||||
if paramtype2 == "wallmounted" or
|
||||
paramtype2 == "colorwallmounted" then
|
||||
local orient = node.param2 % 8
|
||||
node.param2 = node.param2 - orient +
|
||||
wallmounted_substitution[orient + 1]
|
||||
swap_node(pos, node)
|
||||
count = count + 1
|
||||
elseif paramtype2 == "facedir" or
|
||||
paramtype2 == "colorfacedir" then
|
||||
local orient = node.param2 % 32
|
||||
node.param2 = node.param2 - orient +
|
||||
facedir_substitution[orient + 1]
|
||||
swap_node(pos, node)
|
||||
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
|
||||
|
||||
|
||||
--- Attempts to fix the lighting in a region.
|
||||
-- @return The number of nodes updated.
|
||||
|
@ -1,7 +1,8 @@
|
||||
-- TODO: don't shit individual variables into the globals
|
||||
|
||||
---------------------
|
||||
-- Helpers
|
||||
---------------------
|
||||
|
||||
local vec = vector.new
|
||||
local vecw = function(axis, n, base)
|
||||
local ret = vec(base)
|
||||
@ -16,9 +17,9 @@ local set_node = minetest.set_node
|
||||
-- Nodes
|
||||
---------------------
|
||||
local air = "air"
|
||||
local testnode1
|
||||
local testnode2
|
||||
local testnode3
|
||||
rawset(_G, "testnode1", "")
|
||||
rawset(_G, "testnode2", "")
|
||||
rawset(_G, "testnode3", "")
|
||||
-- Loads nodenames to use for tests
|
||||
local function init_nodes()
|
||||
testnode1 = minetest.registered_aliases["mapgen_stone"]
|
||||
@ -27,7 +28,7 @@ local function init_nodes()
|
||||
assert(testnode1 and testnode2 and testnode3)
|
||||
end
|
||||
-- Writes repeating pattern into given area
|
||||
local function place_pattern(pos1, pos2, pattern)
|
||||
rawset(_G, "place_pattern", function(pos1, pos2, pattern)
|
||||
local pos = vec()
|
||||
local node = {name=""}
|
||||
local i = 1
|
||||
@ -43,14 +44,14 @@ local function place_pattern(pos1, pos2, pattern)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
---------------------
|
||||
-- Area management
|
||||
---------------------
|
||||
assert(minetest.get_mapgen_setting("mg_name") == "singlenode")
|
||||
local area = {}
|
||||
rawset(_G, "area", {})
|
||||
do
|
||||
local areamin, areamax
|
||||
local off
|
||||
@ -151,7 +152,7 @@ end
|
||||
---------------------
|
||||
-- Checks
|
||||
---------------------
|
||||
local check = {}
|
||||
rawset(_G, "check", {})
|
||||
-- Check that all nodes in [pos1, pos2] are the node(s) specified
|
||||
check.filled = function(pos1, pos2, nodes)
|
||||
if type(nodes) == "string" then
|
||||
@ -218,7 +219,7 @@ end
|
||||
-- The actual tests
|
||||
---------------------
|
||||
local tests = {}
|
||||
local function register_test(name, func, opts)
|
||||
worldedit.register_test = function(name, func, opts)
|
||||
assert(type(name) == "string")
|
||||
assert(func == nil or type(func) == "function")
|
||||
if not opts then
|
||||
@ -230,6 +231,7 @@ local function register_test(name, func, opts)
|
||||
opts.func = func
|
||||
table.insert(tests, opts)
|
||||
end
|
||||
local register_test = worldedit.register_test
|
||||
-- How this works:
|
||||
-- register_test registers a test with a name and function
|
||||
-- The function should return if the test passes or otherwise cause a Lua error
|
||||
@ -279,270 +281,10 @@ register_test("pattern", function()
|
||||
end)
|
||||
|
||||
|
||||
register_test("Generic node manipulations")
|
||||
register_test("worldedit.set", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local m = area.margin(1)
|
||||
|
||||
worldedit.set(pos1, pos2, testnode1)
|
||||
|
||||
check.filled(pos1, pos2, testnode1)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
register_test("worldedit.set mix", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local m = area.margin(1)
|
||||
|
||||
worldedit.set(pos1, pos2, {testnode1, testnode2})
|
||||
|
||||
check.filled(pos1, pos2, {testnode1, testnode2})
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
register_test("worldedit.replace", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local half1, half2 = area.split(pos1, pos2)
|
||||
|
||||
worldedit.set(pos1, half1, testnode1)
|
||||
worldedit.set(half2, pos2, testnode2)
|
||||
worldedit.replace(pos1, pos2, testnode1, testnode3)
|
||||
|
||||
check.not_filled(pos1, pos2, testnode1)
|
||||
check.filled(pos1, half1, testnode3)
|
||||
check.filled(half2, pos2, testnode2)
|
||||
end)
|
||||
|
||||
register_test("worldedit.replace inverse", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local half1, half2 = area.split(pos1, pos2)
|
||||
|
||||
worldedit.set(pos1, half1, testnode1)
|
||||
worldedit.set(half2, pos2, testnode2)
|
||||
worldedit.replace(pos1, pos2, testnode1, testnode3, true)
|
||||
|
||||
check.filled(pos1, half1, testnode1)
|
||||
check.filled(half2, pos2, testnode3)
|
||||
end)
|
||||
|
||||
-- FIXME?: this one looks overcomplicated
|
||||
register_test("worldedit.copy", function()
|
||||
local pos1, pos2 = area.get(4)
|
||||
local axis, n = area.dir(2)
|
||||
local m = area.margin(1)
|
||||
local b = pos1[axis]
|
||||
|
||||
-- create one slice with testnode1, one with testnode2
|
||||
worldedit.set(pos1, vecw(axis, b + 1, pos2), testnode1)
|
||||
worldedit.set(vecw(axis, b + 2, pos1), pos2, testnode2)
|
||||
worldedit.copy(pos1, pos2, axis, n)
|
||||
|
||||
-- should have three slices now
|
||||
check.filled(pos1, vecw(axis, b + 1, pos2), testnode1)
|
||||
check.filled(vecw(axis, b + 2, pos1), pos2, testnode1)
|
||||
check.filled(vecw(axis, b + 4, pos1), vector.add(pos2, vecw(axis, n)), testnode2)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
register_test("worldedit.copy2", function()
|
||||
local pos1, pos2 = area.get(6)
|
||||
local m1 = area.margin(1)
|
||||
local pos1_, pos2_ = area.get(6)
|
||||
local m2 = area.margin(1)
|
||||
|
||||
local pattern = {testnode1, testnode2, testnode3, testnode1, testnode2}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
worldedit.copy2(pos1, pos2, vector.subtract(pos1_, pos1))
|
||||
|
||||
check.pattern(pos1, pos2, pattern)
|
||||
check.pattern(pos1_, pos2_, pattern)
|
||||
check.filled2(m1, air)
|
||||
check.filled2(m2, air)
|
||||
end)
|
||||
|
||||
register_test("worldedit.move (overlap)", function()
|
||||
local pos1, pos2 = area.get(7)
|
||||
local axis, n = area.dir(2)
|
||||
local m = area.margin(1)
|
||||
|
||||
local pattern = {testnode2, testnode1, testnode2, testnode3, testnode3}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
worldedit.move(pos1, pos2, axis, n)
|
||||
|
||||
check.filled(pos1, vecw(axis, pos1[axis] + n - 1, pos2), air)
|
||||
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
register_test("worldedit.move", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local axis, n = area.dir(10)
|
||||
local m = area.margin(1)
|
||||
|
||||
local pattern = {testnode1, testnode3, testnode3, testnode2}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
worldedit.move(pos1, pos2, axis, n)
|
||||
|
||||
check.filled(pos1, pos2, air)
|
||||
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
-- TODO: the rest (also testing param2 + metadata)
|
||||
|
||||
register_test("Schematics")
|
||||
register_test("worldedit.read_header", function()
|
||||
local value = '5,foo,BAR,-1,234:the content'
|
||||
local version, header, content = worldedit.read_header(value)
|
||||
assert(version == 5)
|
||||
assert(#header == 4)
|
||||
assert(header[1] == "foo" and header[2] == "BAR")
|
||||
assert(header[3] == "-1" and header[4] == "234")
|
||||
assert(content == "the content")
|
||||
end)
|
||||
|
||||
register_test("worldedit.allocate", function()
|
||||
local value = '3:-1 0 0 dummy 0 0\n0 0 4 dummy 0 0\n0 1 0 dummy 0 0'
|
||||
local pos1, pos2, count = worldedit.allocate(vec(1, 1, 1), value)
|
||||
assert(vector.equals(pos1, vec(0, 1, 1)))
|
||||
assert(vector.equals(pos2, vec(1, 2, 5)))
|
||||
assert(count == 3)
|
||||
end)
|
||||
|
||||
do
|
||||
local function output_weird(numbers, body)
|
||||
local s = {"return {"}
|
||||
for _, parts in ipairs(numbers) do
|
||||
s[#s+1] = "{"
|
||||
for _, n in ipairs(parts) do
|
||||
s[#s+1] = string.format(" {%d},", n)
|
||||
end
|
||||
s[#s+1] = "},"
|
||||
end
|
||||
return table.concat(s, "\n") .. table.concat(body, "\n") .. "}"
|
||||
end
|
||||
local fmt1p = '{\n ["x"]=%d,\n ["y"]=%d,\n ["z"]=%d,\n},'
|
||||
local fmt1n = '{\n ["name"]="%s",\n},'
|
||||
local fmt4 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["meta"] = { ["fields"] = { }, ["inventory"] = { } }, ["param2"] = 0, ["param1"] = 0, ["name"] = "%s" }'
|
||||
local fmt5 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["name"] = "%s" }'
|
||||
local fmt51 = '{[r2]=0,x=%d,y=%d,z=%d,name=r%d}'
|
||||
local fmt52 = '{x=%d,y=%d,z=%d,name=_[%d]}'
|
||||
local test_data = {
|
||||
-- used by WorldEdit 0.2 (first public release)
|
||||
{
|
||||
name = "v1", ver = 1,
|
||||
gen = function(pat)
|
||||
local numbers = {
|
||||
{2, 3, 4, 5, 6},
|
||||
{7, 8}, {9, 10}, {11, 12},
|
||||
{13, 14}, {15, 16}
|
||||
}
|
||||
return output_weird(numbers, {
|
||||
fmt1p:format(0, 0, 0),
|
||||
fmt1n:format(pat[1]),
|
||||
fmt1p:format(0, 1, 0),
|
||||
fmt1n:format(pat[3]),
|
||||
fmt1p:format(1, 1, 0),
|
||||
fmt1n:format(pat[1]),
|
||||
fmt1p:format(1, 0, 1),
|
||||
fmt1n:format(pat[3]),
|
||||
fmt1p:format(0, 1, 1),
|
||||
fmt1n:format(pat[1]),
|
||||
})
|
||||
end
|
||||
},
|
||||
|
||||
-- v2: missing because I couldn't find any code in my archives that actually wrote this format
|
||||
|
||||
{
|
||||
name = "v3", ver = 3,
|
||||
gen = function(pat)
|
||||
assert(pat[2] == air)
|
||||
return table.concat({
|
||||
"0 0 0 " .. pat[1] .. " 0 0",
|
||||
"0 1 0 " .. pat[3] .. " 0 0",
|
||||
"1 1 0 " .. pat[1] .. " 0 0",
|
||||
"1 0 1 " .. pat[3] .. " 0 0",
|
||||
"0 1 1 " .. pat[1] .. " 0 0",
|
||||
}, "\n")
|
||||
end
|
||||
},
|
||||
|
||||
{
|
||||
name = "v4", ver = 4,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
"return { " .. fmt4:format(0, 0, 0, pat[1]),
|
||||
fmt4:format(0, 1, 0, pat[3]),
|
||||
fmt4:format(1, 1, 0, pat[1]),
|
||||
fmt4:format(1, 0, 1, pat[3]),
|
||||
fmt4:format(0, 1, 1, pat[1]) .. " }",
|
||||
}, ", ")
|
||||
end
|
||||
},
|
||||
|
||||
-- like v4 but no meta and param (if empty)
|
||||
{
|
||||
name = "v5 (pre-5.6)", ver = 5,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
"5:return { " .. fmt5:format(0, 0, 0, pat[1]),
|
||||
fmt5:format(0, 1, 0, pat[3]),
|
||||
fmt5:format(1, 1, 0, pat[1]),
|
||||
fmt5:format(1, 0, 1, pat[3]),
|
||||
fmt5:format(0, 1, 1, pat[1]) .. " }",
|
||||
}, ", ")
|
||||
end
|
||||
},
|
||||
|
||||
-- reworked engine serialization in 5.6
|
||||
{
|
||||
name = "v5 (5.6)", ver = 5,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
'5:r1="' .. pat[1] .. '";r2="param1";r3="' .. pat[3] .. '";return {'
|
||||
.. fmt51:format(0, 0, 0, 1),
|
||||
fmt51:format(0, 1, 0, 3),
|
||||
fmt51:format(1, 1, 0, 1),
|
||||
fmt51:format(1, 0, 1, 3),
|
||||
fmt51:format(0, 1, 1, 1) .. "}",
|
||||
}, ",")
|
||||
end
|
||||
},
|
||||
|
||||
-- small changes on engine side again
|
||||
{
|
||||
name = "v5 (post-5.7)", ver = 5,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
'5:local _={};_[1]="' .. pat[1] .. '";_[3]="' .. pat[3] .. '";return {'
|
||||
.. fmt52:format(0, 0, 0, 1),
|
||||
fmt52:format(0, 1, 0, 3),
|
||||
fmt52:format(1, 1, 0, 1),
|
||||
fmt52:format(1, 0, 1, 3),
|
||||
fmt52:format(0, 1, 1, 1) .. "}",
|
||||
}, ",")
|
||||
end
|
||||
},
|
||||
}
|
||||
for _, e in ipairs(test_data) do
|
||||
register_test("worldedit.deserialize " .. e.name, function()
|
||||
local pos1, pos2 = area.get(2)
|
||||
local m = area.margin(1)
|
||||
|
||||
local pat = {testnode3, air, testnode2}
|
||||
local value = e.gen(pat)
|
||||
assert(type(value) == "string")
|
||||
|
||||
local version = worldedit.read_header(value)
|
||||
assert(version == e.ver, "version: got " .. tostring(version) .. " expected " .. e.ver)
|
||||
local count = worldedit.deserialize(pos1, value)
|
||||
assert(count ~= nil and count > 0)
|
||||
|
||||
check.pattern(pos1, pos2, pat)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
end
|
||||
for _, name in ipairs({
|
||||
"manipulations", "schematic"
|
||||
}) do
|
||||
dofile(minetest.get_modpath("worldedit") .. "/test/" .. name .. ".lua")
|
||||
end
|
||||
|
||||
|
121
worldedit/test/manipulations.lua
Normal file
121
worldedit/test/manipulations.lua
Normal file
@ -0,0 +1,121 @@
|
||||
---------------------
|
||||
local vec = vector.new
|
||||
local vecw = function(axis, n, base)
|
||||
local ret = vec(base)
|
||||
ret[axis] = n
|
||||
return ret
|
||||
end
|
||||
local air = "air"
|
||||
---------------------
|
||||
|
||||
|
||||
worldedit.register_test("Generic node manipulations")
|
||||
worldedit.register_test("worldedit.set", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local m = area.margin(1)
|
||||
|
||||
worldedit.set(pos1, pos2, testnode1)
|
||||
|
||||
check.filled(pos1, pos2, testnode1)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.set mix", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local m = area.margin(1)
|
||||
|
||||
worldedit.set(pos1, pos2, {testnode1, testnode2})
|
||||
|
||||
check.filled(pos1, pos2, {testnode1, testnode2})
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.replace", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local half1, half2 = area.split(pos1, pos2)
|
||||
|
||||
worldedit.set(pos1, half1, testnode1)
|
||||
worldedit.set(half2, pos2, testnode2)
|
||||
worldedit.replace(pos1, pos2, testnode1, testnode3)
|
||||
|
||||
check.not_filled(pos1, pos2, testnode1)
|
||||
check.filled(pos1, half1, testnode3)
|
||||
check.filled(half2, pos2, testnode2)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.replace inverse", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local half1, half2 = area.split(pos1, pos2)
|
||||
|
||||
worldedit.set(pos1, half1, testnode1)
|
||||
worldedit.set(half2, pos2, testnode2)
|
||||
worldedit.replace(pos1, pos2, testnode1, testnode3, true)
|
||||
|
||||
check.filled(pos1, half1, testnode1)
|
||||
check.filled(half2, pos2, testnode3)
|
||||
end)
|
||||
|
||||
-- FIXME?: this one looks overcomplicated
|
||||
worldedit.register_test("worldedit.copy", function()
|
||||
local pos1, pos2 = area.get(4)
|
||||
local axis, n = area.dir(2)
|
||||
local m = area.margin(1)
|
||||
local b = pos1[axis]
|
||||
|
||||
-- create one slice with testnode1, one with testnode2
|
||||
worldedit.set(pos1, vecw(axis, b + 1, pos2), testnode1)
|
||||
worldedit.set(vecw(axis, b + 2, pos1), pos2, testnode2)
|
||||
worldedit.copy(pos1, pos2, axis, n)
|
||||
|
||||
-- should have three slices now
|
||||
check.filled(pos1, vecw(axis, b + 1, pos2), testnode1)
|
||||
check.filled(vecw(axis, b + 2, pos1), pos2, testnode1)
|
||||
check.filled(vecw(axis, b + 4, pos1), vector.add(pos2, vecw(axis, n)), testnode2)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.copy2", function()
|
||||
local pos1, pos2 = area.get(6)
|
||||
local m1 = area.margin(1)
|
||||
local pos1_, pos2_ = area.get(6)
|
||||
local m2 = area.margin(1)
|
||||
|
||||
local pattern = {testnode1, testnode2, testnode3, testnode1, testnode2}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
worldedit.copy2(pos1, pos2, vector.subtract(pos1_, pos1))
|
||||
|
||||
check.pattern(pos1, pos2, pattern)
|
||||
check.pattern(pos1_, pos2_, pattern)
|
||||
check.filled2(m1, air)
|
||||
check.filled2(m2, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.move (overlap)", function()
|
||||
local pos1, pos2 = area.get(7)
|
||||
local axis, n = area.dir(2)
|
||||
local m = area.margin(1)
|
||||
|
||||
local pattern = {testnode2, testnode1, testnode2, testnode3, testnode3}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
worldedit.move(pos1, pos2, axis, n)
|
||||
|
||||
check.filled(pos1, vecw(axis, pos1[axis] + n - 1, pos2), air)
|
||||
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.move", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local axis, n = area.dir(10)
|
||||
local m = area.margin(1)
|
||||
|
||||
local pattern = {testnode1, testnode3, testnode3, testnode2}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
worldedit.move(pos1, pos2, axis, n)
|
||||
|
||||
check.filled(pos1, pos2, air)
|
||||
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
-- TODO: the rest (also testing param2 + metadata)
|
162
worldedit/test/schematic.lua
Normal file
162
worldedit/test/schematic.lua
Normal file
@ -0,0 +1,162 @@
|
||||
---------------------
|
||||
local vec = vector.new
|
||||
local air = "air"
|
||||
---------------------
|
||||
|
||||
|
||||
local function output_weird(numbers, body)
|
||||
local s = {"return {"}
|
||||
for _, parts in ipairs(numbers) do
|
||||
s[#s+1] = "{"
|
||||
for _, n in ipairs(parts) do
|
||||
s[#s+1] = string.format(" {%d},", n)
|
||||
end
|
||||
s[#s+1] = "},"
|
||||
end
|
||||
return table.concat(s, "\n") .. table.concat(body, "\n") .. "}"
|
||||
end
|
||||
|
||||
local fmt1p = '{\n ["x"]=%d,\n ["y"]=%d,\n ["z"]=%d,\n},'
|
||||
local fmt1n = '{\n ["name"]="%s",\n},'
|
||||
local fmt4 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["meta"] = { ["fields"] = { }, ["inventory"] = { } }, ["param2"] = 0, ["param1"] = 0, ["name"] = "%s" }'
|
||||
local fmt5 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["name"] = "%s" }'
|
||||
local fmt51 = '{[r2]=0,x=%d,y=%d,z=%d,name=r%d}'
|
||||
local fmt52 = '{x=%d,y=%d,z=%d,name=_[%d]}'
|
||||
|
||||
local test_data = {
|
||||
-- used by WorldEdit 0.2 (first public release)
|
||||
{
|
||||
name = "v1", ver = 1,
|
||||
gen = function(pat)
|
||||
local numbers = {
|
||||
{2, 3, 4, 5, 6},
|
||||
{7, 8}, {9, 10}, {11, 12},
|
||||
{13, 14}, {15, 16}
|
||||
}
|
||||
return output_weird(numbers, {
|
||||
fmt1p:format(0, 0, 0),
|
||||
fmt1n:format(pat[1]),
|
||||
fmt1p:format(0, 1, 0),
|
||||
fmt1n:format(pat[3]),
|
||||
fmt1p:format(1, 1, 0),
|
||||
fmt1n:format(pat[1]),
|
||||
fmt1p:format(1, 0, 1),
|
||||
fmt1n:format(pat[3]),
|
||||
fmt1p:format(0, 1, 1),
|
||||
fmt1n:format(pat[1]),
|
||||
})
|
||||
end
|
||||
},
|
||||
|
||||
-- v2: missing because I couldn't find any code in my archives that actually wrote this format
|
||||
|
||||
{
|
||||
name = "v3", ver = 3,
|
||||
gen = function(pat)
|
||||
assert(pat[2] == air)
|
||||
return table.concat({
|
||||
"0 0 0 " .. pat[1] .. " 0 0",
|
||||
"0 1 0 " .. pat[3] .. " 0 0",
|
||||
"1 1 0 " .. pat[1] .. " 0 0",
|
||||
"1 0 1 " .. pat[3] .. " 0 0",
|
||||
"0 1 1 " .. pat[1] .. " 0 0",
|
||||
}, "\n")
|
||||
end
|
||||
},
|
||||
|
||||
{
|
||||
name = "v4", ver = 4,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
"return { " .. fmt4:format(0, 0, 0, pat[1]),
|
||||
fmt4:format(0, 1, 0, pat[3]),
|
||||
fmt4:format(1, 1, 0, pat[1]),
|
||||
fmt4:format(1, 0, 1, pat[3]),
|
||||
fmt4:format(0, 1, 1, pat[1]) .. " }",
|
||||
}, ", ")
|
||||
end
|
||||
},
|
||||
|
||||
-- like v4 but no meta and param (if empty)
|
||||
{
|
||||
name = "v5 (pre-5.6)", ver = 5,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
"5:return { " .. fmt5:format(0, 0, 0, pat[1]),
|
||||
fmt5:format(0, 1, 0, pat[3]),
|
||||
fmt5:format(1, 1, 0, pat[1]),
|
||||
fmt5:format(1, 0, 1, pat[3]),
|
||||
fmt5:format(0, 1, 1, pat[1]) .. " }",
|
||||
}, ", ")
|
||||
end
|
||||
},
|
||||
|
||||
-- reworked engine serialization in 5.6
|
||||
{
|
||||
name = "v5 (5.6)", ver = 5,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
'5:r1="' .. pat[1] .. '";r2="param1";r3="' .. pat[3] .. '";return {'
|
||||
.. fmt51:format(0, 0, 0, 1),
|
||||
fmt51:format(0, 1, 0, 3),
|
||||
fmt51:format(1, 1, 0, 1),
|
||||
fmt51:format(1, 0, 1, 3),
|
||||
fmt51:format(0, 1, 1, 1) .. "}",
|
||||
}, ",")
|
||||
end
|
||||
},
|
||||
|
||||
-- small changes on engine side again
|
||||
{
|
||||
name = "v5 (post-5.7)", ver = 5,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
'5:local _={};_[1]="' .. pat[1] .. '";_[3]="' .. pat[3] .. '";return {'
|
||||
.. fmt52:format(0, 0, 0, 1),
|
||||
fmt52:format(0, 1, 0, 3),
|
||||
fmt52:format(1, 1, 0, 1),
|
||||
fmt52:format(1, 0, 1, 3),
|
||||
fmt52:format(0, 1, 1, 1) .. "}",
|
||||
}, ",")
|
||||
end
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
worldedit.register_test("Schematics")
|
||||
worldedit.register_test("worldedit.read_header", function()
|
||||
local value = '5,foo,BAR,-1,234:the content'
|
||||
local version, header, content = worldedit.read_header(value)
|
||||
assert(version == 5)
|
||||
assert(#header == 4)
|
||||
assert(header[1] == "foo" and header[2] == "BAR")
|
||||
assert(header[3] == "-1" and header[4] == "234")
|
||||
assert(content == "the content")
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.allocate", function()
|
||||
local value = '3:-1 0 0 dummy 0 0\n0 0 4 dummy 0 0\n0 1 0 dummy 0 0'
|
||||
local pos1, pos2, count = worldedit.allocate(vec(1, 1, 1), value)
|
||||
assert(vector.equals(pos1, vec(0, 1, 1)))
|
||||
assert(vector.equals(pos2, vec(1, 2, 5)))
|
||||
assert(count == 3)
|
||||
end)
|
||||
|
||||
for _, e in ipairs(test_data) do
|
||||
worldedit.register_test("worldedit.deserialize " .. e.name, function()
|
||||
local pos1, pos2 = area.get(2)
|
||||
local m = area.margin(1)
|
||||
|
||||
local pat = {testnode3, air, testnode2}
|
||||
local value = e.gen(pat)
|
||||
assert(type(value) == "string")
|
||||
|
||||
local version = worldedit.read_header(value)
|
||||
assert(version == e.ver, "version: got " .. tostring(version) .. " expected " .. e.ver)
|
||||
local count = worldedit.deserialize(pos1, value)
|
||||
assert(count ~= nil and count > 0)
|
||||
|
||||
check.pattern(pos1, pos2, pat)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
end
|
357
worldedit/transformations.lua
Normal file
357
worldedit/transformations.lua
Normal file
@ -0,0 +1,357 @@
|
||||
--- Node transformations.
|
||||
-- @module worldedit.transformations
|
||||
|
||||
worldedit.deferred_execution = function(next_one, finished)
|
||||
-- Allocate 80% of server step for execution
|
||||
local allocated_usecs =
|
||||
tonumber(minetest.settings:get("dedicated_server_step"):split(" ")[1]) * 1000000 * 0.8
|
||||
local function f()
|
||||
local deadline = minetest.get_us_time() + allocated_usecs
|
||||
repeat
|
||||
local is_done = next_one()
|
||||
if is_done then
|
||||
if finished then
|
||||
finished()
|
||||
end
|
||||
return
|
||||
end
|
||||
until minetest.get_us_time() >= deadline
|
||||
minetest.after(0, f)
|
||||
end
|
||||
f()
|
||||
end
|
||||
|
||||
--- Duplicates a region `amount` times with offset vector `direction`.
|
||||
-- Stacking is spread across server steps.
|
||||
-- @return The number of nodes stacked.
|
||||
function worldedit.stack2(pos1, pos2, direction, amount, finished)
|
||||
-- Protect arguments from external changes during execution
|
||||
pos1 = vector.copy(pos1)
|
||||
pos2 = vector.copy(pos2)
|
||||
direction = vector.copy(direction)
|
||||
|
||||
local i = 0
|
||||
local translated = vector.new()
|
||||
local function step()
|
||||
translated.x = translated.x + direction.x
|
||||
translated.y = translated.y + direction.y
|
||||
translated.z = translated.z + direction.z
|
||||
worldedit.copy2(pos1, pos2, translated)
|
||||
i = i + 1
|
||||
return i >= amount
|
||||
end
|
||||
worldedit.deferred_execution(step, finished)
|
||||
|
||||
return worldedit.volume(pos1, pos2) * amount
|
||||
end
|
||||
|
||||
|
||||
--- Duplicates a region along `axis` `amount` times.
|
||||
-- Stacking is spread across server steps.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param axis Axis direction, "x", "y", or "z".
|
||||
-- @param count
|
||||
-- @return The number of nodes stacked.
|
||||
function worldedit.stack(pos1, pos2, axis, count, finished)
|
||||
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 i, distance = 0, 0
|
||||
local function step()
|
||||
distance = distance + length
|
||||
worldedit.copy(pos1, pos2, axis, distance)
|
||||
i = i + 1
|
||||
return i >= count
|
||||
end
|
||||
worldedit.deferred_execution(step, finished)
|
||||
|
||||
return worldedit.volume(pos1, pos2) * count
|
||||
end
|
||||
|
||||
|
||||
--- Stretches a region by a factor of positive integers along the X, Y, and Z
|
||||
-- axes, respectively, with `pos1` as the origin.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param stretch_x Amount to stretch along X axis.
|
||||
-- @param stretch_y Amount to stretch along Y axis.
|
||||
-- @param stretch_z Amount to stretch along Z axis.
|
||||
-- @return The number of nodes scaled.
|
||||
-- @return The new scaled position 1.
|
||||
-- @return The new scaled position 2.
|
||||
function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
-- 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=255, param2=0}
|
||||
local nodes = {}
|
||||
for i = 1, stretch_x * stretch_y * stretch_z do
|
||||
nodes[i] = placeholder_node
|
||||
end
|
||||
local schematic = {size=vector.new(stretch_x, stretch_y, stretch_z), data=nodes}
|
||||
|
||||
local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1
|
||||
|
||||
local new_pos2 = {
|
||||
x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,
|
||||
y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,
|
||||
z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,
|
||||
}
|
||||
worldedit.keep_loaded(pos1, new_pos2)
|
||||
|
||||
local pos = vector.new(pos2.x, 0, 0)
|
||||
local big_pos = vector.new()
|
||||
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) -- Get current node
|
||||
local meta = get_meta(pos):to_table() -- Get meta of current node
|
||||
|
||||
-- Calculate far corner of the big node
|
||||
local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x
|
||||
local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y
|
||||
local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z
|
||||
|
||||
-- Create large node
|
||||
placeholder_node.name = node.name
|
||||
placeholder_node.param2 = node.param2
|
||||
big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z
|
||||
place_schematic(big_pos, schematic)
|
||||
|
||||
-- Fill in large node meta
|
||||
if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
|
||||
-- Node has meta fields
|
||||
for x = 0, size_x do
|
||||
for y = 0, size_y do
|
||||
for z = 0, size_z do
|
||||
big_pos.x = pos_x + x
|
||||
big_pos.y = pos_y + y
|
||||
big_pos.z = pos_z + z
|
||||
-- Set metadata of new node
|
||||
get_meta(big_pos):from_table(meta)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
pos.z = pos.z - 1
|
||||
end
|
||||
pos.y = pos.y - 1
|
||||
end
|
||||
pos.x = pos.x - 1
|
||||
end
|
||||
return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2
|
||||
end
|
||||
|
||||
|
||||
--- Transposes a region between two axes.
|
||||
-- @return The number of nodes transposed.
|
||||
-- @return The new transposed position 1.
|
||||
-- @return The new transposed position 2.
|
||||
function worldedit.transpose(pos1, pos2, axis1, axis2)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local 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 new_pos2 = vector.new(pos2)
|
||||
new_pos2[axis1] = pos1[axis1] + extent2
|
||||
new_pos2[axis2] = pos1[axis2] + extent1
|
||||
|
||||
local upper_bound = vector.new(pos2)
|
||||
if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end
|
||||
if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end
|
||||
worldedit.keep_loaded(pos1, upper_bound)
|
||||
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
local get_node, get_meta, set_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.set_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()
|
||||
set_node(pos, node1)
|
||||
get_meta(pos):from_table(meta1)
|
||||
pos[axis1], pos[axis2] = value1, value2 -- Restore position values
|
||||
set_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, new_pos2
|
||||
end
|
||||
|
||||
|
||||
--- Flips a region along `axis`.
|
||||
-- @return The number of nodes flipped.
|
||||
function worldedit.flip(pos1, pos2, axis)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
--- TODO: Flip the region slice by slice along the flip axis using schematic method.
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
local start = pos1[axis] + pos2[axis]
|
||||
pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
|
||||
local get_node, get_meta, set_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.set_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 node1 = get_node(pos)
|
||||
local meta1 = get_meta(pos):to_table()
|
||||
local value = pos[axis] -- Save position
|
||||
pos[axis] = start - value -- Shift position
|
||||
local node2 = get_node(pos)
|
||||
local meta2 = get_meta(pos):to_table()
|
||||
set_node(pos, node1)
|
||||
get_meta(pos):from_table(meta1)
|
||||
pos[axis] = value -- Restore position
|
||||
set_node(pos, node2)
|
||||
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 clockwise around an axis.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param axis Axis ("x", "y", or "z").
|
||||
-- @param angle Angle in degrees (90 degree increments only).
|
||||
-- @return The number of nodes rotated.
|
||||
-- @return The new first position.
|
||||
-- @return The new second position.
|
||||
function worldedit.rotate(pos1, pos2, axis, angle)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local other1, other2 = worldedit.get_axis_others(axis)
|
||||
angle = angle % 360
|
||||
|
||||
local count
|
||||
if angle == 90 then
|
||||
worldedit.flip(pos1, pos2, other1)
|
||||
count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
|
||||
elseif angle == 180 then
|
||||
worldedit.flip(pos1, pos2, other1)
|
||||
count = worldedit.flip(pos1, pos2, other2)
|
||||
elseif angle == 270 then
|
||||
worldedit.flip(pos1, pos2, other2)
|
||||
count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
|
||||
else
|
||||
error("Only 90 degree increments are supported!")
|
||||
end
|
||||
return count, pos1, pos2
|
||||
end
|
||||
|
||||
|
||||
--- Rotates all oriented nodes in a region clockwise around the Y axis.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param angle Angle in degrees (90 degree increments only).
|
||||
-- @return The number of nodes oriented.
|
||||
function worldedit.orient(pos1, pos2, angle)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local registered_nodes = minetest.registered_nodes
|
||||
|
||||
local wallmounted = {
|
||||
[90] = {0, 1, 5, 4, 2, 3, 0, 0},
|
||||
[180] = {0, 1, 3, 2, 5, 4, 0, 0},
|
||||
[270] = {0, 1, 4, 5, 3, 2, 0, 0}
|
||||
}
|
||||
local facedir = {
|
||||
[90] = { 1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16,
|
||||
9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22},
|
||||
[180] = { 2, 3, 0, 1, 10, 11, 8, 9, 6, 7, 4, 5,
|
||||
18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21},
|
||||
[270] = { 3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14,
|
||||
7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20}
|
||||
}
|
||||
|
||||
angle = angle % 360
|
||||
if angle == 0 then
|
||||
return 0
|
||||
end
|
||||
if angle % 90 ~= 0 then
|
||||
error("Only 90 degree increments are supported!")
|
||||
end
|
||||
local wallmounted_substitution = wallmounted[angle]
|
||||
local facedir_substitution = facedir[angle]
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local count = 0
|
||||
local get_node, swap_node = minetest.get_node, minetest.swap_node
|
||||
local pos = vector.new(pos1.x, 0, 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
|
||||
local paramtype2 = def.paramtype2
|
||||
if paramtype2 == "wallmounted" or
|
||||
paramtype2 == "colorwallmounted" then
|
||||
local orient = node.param2 % 8
|
||||
node.param2 = node.param2 - orient +
|
||||
wallmounted_substitution[orient + 1]
|
||||
swap_node(pos, node)
|
||||
count = count + 1
|
||||
elseif paramtype2 == "facedir" or
|
||||
paramtype2 == "colorfacedir" then
|
||||
local orient = node.param2 % 32
|
||||
node.param2 = node.param2 - orient +
|
||||
facedir_substitution[orient + 1]
|
||||
swap_node(pos, node)
|
||||
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
|
Loading…
Reference in New Issue
Block a user