Compare commits

...

3 Commits

Author SHA1 Message Date
sfan5 7f7e928dd9 Switch bare vectors to vector.new() 2023-06-09 14:49:58 +02:00
luk3yx 1a9f66f091 Fix back button in some worldedit_gui pages 2023-06-09 13:59:09 +02:00
sfan5 7a5d76a9bc Add comprehensive schematic deserialization unit tests 2023-06-09 13:02:37 +02:00
10 changed files with 217 additions and 60 deletions

View File

@ -31,7 +31,7 @@ function worldedit.luatransform(pos1, pos2, code)
worldedit.keep_loaded(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0}
local pos = vector.new(pos1.x, 0, 0)
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do

View File

@ -5,8 +5,8 @@
-- `pos1` is less than or equal to the corresponding component of `pos2`.
-- Returns the new positions.
function worldedit.sort_pos(pos1, pos2)
pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}
pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
pos1 = vector.new(pos1.x, pos1.y, pos1.z)
pos2 = vector.new(pos2.x, pos2.y, pos2.z)
if pos1.x > pos2.x then
pos2.x, pos1.x = pos1.x, pos2.x
end

View File

@ -128,7 +128,7 @@ function worldedit.stack2(pos1, pos2, direction, amount, finished)
direction = table.copy(direction)
local i = 0
local translated = {x=0, y=0, z=0}
local translated = vector.new()
local function step()
translated.x = translated.x + direction.x
translated.y = translated.y + direction.y
@ -155,7 +155,7 @@ function worldedit.copy(pos1, pos2, axis, amount)
-- Decide if we need to copy stuff backwards (only applies to metadata)
local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1)
local off = {x=0, y=0, z=0}
local off = vector.new()
off[axis] = amount
return worldedit.copy2(pos1, pos2, off, backwards)
end
@ -170,7 +170,7 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local src_manip, src_area = mh.init(pos1, pos2)
local src_stride = {x=1, y=src_area.ystride, z=src_area.zstride}
local src_stride = vector.new(1, src_area.ystride, src_area.zstride)
local src_offset = vector.subtract(pos1, src_area.MinEdge)
local dpos1 = vector.add(pos1, off)
@ -178,7 +178,7 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards)
local dim = vector.add(vector.subtract(pos2, pos1), 1)
local dst_manip, dst_area = mh.init(dpos1, dpos2)
local dst_stride = {x=1, y=dst_area.ystride, z=dst_area.zstride}
local dst_stride = vector.new(1, dst_area.ystride, dst_area.zstride)
local dst_offset = vector.subtract(dpos1, dst_area.MinEdge)
local function do_copy(src_data, dst_data)
@ -226,7 +226,7 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards)
for z = dim.z-1, 0, -1 do
for y = dim.y-1, 0, -1 do
for x = dim.x-1, 0, -1 do
local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}
local pos = vector.new(pos1.x+x, pos1.y+y, pos1.z+z)
local meta = get_meta(pos):to_table()
pos = vector.add(pos, off)
get_meta(pos):from_table(meta)
@ -237,7 +237,7 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards)
for z = 0, dim.z-1 do
for y = 0, dim.y-1 do
for x = 0, dim.x-1 do
local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}
local pos = vector.new(pos1.x+x, pos1.y+y, pos1.z+z)
local meta = get_meta(pos):to_table()
pos = vector.add(pos, off)
get_meta(pos):from_table(meta)
@ -286,21 +286,21 @@ function worldedit.move(pos1, pos2, axis, amount)
end
-- Copy stuff to new location
local off = {x=0, y=0, z=0}
local off = vector.new()
off[axis] = amount
worldedit.copy2(pos1, pos2, off, backwards)
-- Nuke old area
if not overlap then
nuke_area({x=0, y=0, z=0}, dim)
nuke_area(vector.new(), dim)
else
-- Source and destination region are overlapping, which means we can't
-- blindly delete the [pos1, pos2] area
local leftover = vector.new(dim) -- size of the leftover slice
leftover[axis] = math.abs(amount)
if amount > 0 then
nuke_area({x=0, y=0, z=0}, leftover)
nuke_area(vector.new(), leftover)
else
local top = {x=0, y=0, z=0} -- offset of the leftover slice from pos1
local top = vector.new() -- offset of the leftover slice from pos1
top[axis] = dim[axis] - math.abs(amount)
nuke_area(top, leftover)
end
@ -358,7 +358,7 @@ function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)
for i = 1, stretch_x * stretch_y * stretch_z do
nodes[i] = placeholder_node
end
local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes}
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
@ -369,8 +369,8 @@ function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)
}
worldedit.keep_loaded(pos1, new_pos2)
local pos = {x=pos2.x, y=0, z=0}
local big_pos = {x=0, y=0, z=0}
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
@ -436,16 +436,16 @@ function worldedit.transpose(pos1, pos2, axis1, axis2)
end
-- Calculate the new position 2 after transposition
local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
local new_pos2 = vector.new(pos2)
new_pos2[axis1] = pos1[axis1] + extent2
new_pos2[axis2] = pos1[axis2] + extent1
local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z}
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 = {x=pos1.x, y=0, z=0}
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
@ -485,7 +485,7 @@ function worldedit.flip(pos1, pos2, axis)
worldedit.keep_loaded(pos1, pos2)
--- TODO: Flip the region slice by slice along the flip axis using schematic method.
local pos = {x=pos1.x, y=0, z=0}
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,
@ -584,7 +584,7 @@ function worldedit.orient(pos1, pos2, angle)
local count = 0
local get_node, swap_node = minetest.get_node, minetest.swap_node
local pos = {x=pos1.x, y=0, z=0}
local pos = vector.new(pos1.x, 0, 0)
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
@ -650,13 +650,12 @@ function worldedit.clear_objects(pos1, pos2)
end
-- Offset positions to include full nodes (positions are in the center of nodes)
local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5
local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5
pos1 = vector.add(pos1, -0.5)
pos2 = vector.add(pos1, 0.5)
local count = 0
if minetest.get_objects_in_area then
local objects = minetest.get_objects_in_area({x=pos1x, y=pos1y, z=pos1z},
{x=pos2x, y=pos2y, z=pos2z})
local objects = minetest.get_objects_in_area(pos1, pos2)
for _, obj in pairs(objects) do
if should_delete(obj) then
@ -670,21 +669,21 @@ function worldedit.clear_objects(pos1, pos2)
-- Fallback implementation via get_objects_inside_radius
-- Center of region
local center = {
x = pos1x + ((pos2x - pos1x) / 2),
y = pos1y + ((pos2y - pos1y) / 2),
z = pos1z + ((pos2z - pos1z) / 2)
x = pos1.x + ((pos2.x - pos1.x) / 2),
y = pos1.y + ((pos2.y - pos1.y) / 2),
z = pos1.z + ((pos2.z - pos1.z) / 2)
}
-- Bounding sphere radius
local radius = math.sqrt(
(center.x - pos1x) ^ 2 +
(center.y - pos1y) ^ 2 +
(center.z - pos1z) ^ 2)
(center.x - pos1.x) ^ 2 +
(center.y - pos1.y) ^ 2 +
(center.z - pos1.z) ^ 2)
for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do
if should_delete(obj) then
local pos = obj:get_pos()
if pos.x >= pos1x and pos.x <= pos2x and
pos.y >= pos1y and pos.y <= pos2y and
pos.z >= pos1z and pos.z <= pos2z then
if pos.x >= pos1.x and pos.x <= pos2.x and
pos.y >= pos1.y and pos.y <= pos2.y and
pos.z >= pos1.z and pos.z <= pos2.z then
-- Inside region
obj:remove()
count = count + 1

View File

@ -20,7 +20,7 @@ function worldedit.cube(pos, width, height, length, node_name, hollow)
-- Add cube
local node_id = minetest.get_content_id(node_name)
local stride = {x=1, y=area.ystride, z=area.zstride}
local stride = vector.new(1, area.ystride, area.zstride)
local offset = vector.subtract(basepos, area.MinEdge)
local count = 0
@ -149,7 +149,7 @@ function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, holl
end
-- Handle negative lengths
local current_pos = {x=pos.x, y=pos.y, z=pos.z}
local current_pos = vector.new(pos)
if length < 0 then
length = -length
current_pos[axis] = current_pos[axis] - length
@ -162,7 +162,7 @@ function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, holl
-- Add desired shape (anything inbetween cylinder & cone)
local node_id = minetest.get_content_id(node_name)
local stride = {x=1, y=area.ystride, z=area.zstride}
local stride = vector.new(1, area.ystride, area.zstride)
local offset = {
x = current_pos.x - area.MinEdge.x,
y = current_pos.y - area.MinEdge.y,
@ -225,7 +225,7 @@ function worldedit.pyramid(pos, axis, height, node_name, hollow)
-- Add pyramid
local node_id = minetest.get_content_id(node_name)
local stride = {x=1, y=area.ystride, z=area.zstride}
local stride = vector.new(1, area.ystride, area.zstride)
local offset = {
x = pos.x - area.MinEdge.x,
y = pos.y - area.MinEdge.y,
@ -271,7 +271,7 @@ function worldedit.spiral(pos, length, height, spacer, node_name)
-- Set up variables
local node_id = minetest.get_content_id(node_name)
local stride = {x=1, y=area.ystride, z=area.zstride}
local stride = vector.new(1, area.ystride, area.zstride)
local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
local i = offset_z * stride.z + offset_y * stride.y + offset_x + 1

View File

@ -66,7 +66,7 @@ function worldedit.serialize(pos1, pos2)
has_meta[hash_node_position(meta_positions[i])] = true
end
local pos = {x=pos1.x, y=0, z=0}
local pos = vector.new(pos1.x, 0, 0)
local count = 0
local result = {}
while pos.x <= pos2.x do
@ -235,9 +235,7 @@ function worldedit.allocate_with_nodes(origin_pos, nodes)
if y > pos2y then pos2y = y end
if z > pos2z then pos2z = z end
end
local pos1 = {x=pos1x, y=pos1y, z=pos1z}
local pos2 = {x=pos2x, y=pos2y, z=pos2z}
return pos1, pos2, #nodes
return vector.new(pos1x, pos1y, pos1z), vector.new(pos2x, pos2y, pos2z), #nodes
end

View File

@ -87,10 +87,10 @@ do
-- Returns an usable area [pos1, pos2] that does not overlap previous ones
area.get = function(sizex, sizey, sizez)
local size
if sizey == nil or sizez == nil then
size = {x=sizex, y=sizex, z=sizex}
if sizey == nil and sizez == nil then
size = vector.new(sizex, sizex, sizex)
else
size = {x=sizex, y=sizey, z=sizez}
size = vector.new(sizex, sizey, sizez)
end
local pos1 = vector.add(areamin, off)
local pos2 = vector.subtract(vector.add(pos1, size), 1)
@ -387,6 +387,160 @@ 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
end
---------------------
-- Main function
@ -406,7 +560,7 @@ worldedit.run_tests = function()
for x = 0, math.floor(wanted.x/16) do
for y = 0, math.floor(wanted.y/16) do
for z = 0, math.floor(wanted.z/16) do
assert(minetest.forceload_block({x=x*16, y=y*16, z=z*16}, true))
assert(minetest.forceload_block(vector.new(x*16, y*16, z*16), true))
end
end
end

View File

@ -19,7 +19,7 @@ function worldedit.hide(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0}
local pos = vector.new(pos1.x, 0, 0)
local get_node, get_meta, swap_node = minetest.get_node,
minetest.get_meta, minetest.swap_node
while pos.x <= pos2.x do
@ -79,7 +79,7 @@ function worldedit.highlight(pos1, pos2, node_name)
worldedit.keep_loaded(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0}
local pos = vector.new(pos1.x, 0, 0)
local get_node, get_meta, swap_node = minetest.get_node,
minetest.get_meta, minetest.swap_node
local count = 0

View File

@ -27,7 +27,7 @@ local brush_on_use = function(itemstack, placer)
end
local raybegin = vector.add(placer:get_pos(),
{x=0, y=placer:get_properties().eye_height, z=0})
vector.new(0, placer:get_properties().eye_height, 0))
local rayend = vector.add(raybegin, vector.multiply(placer:get_look_dir(), BRUSH_MAX_DIST))
local ray = minetest.raycast(raybegin, rayend, false, true)
local pointed_thing = ray:next()

View File

@ -488,7 +488,7 @@ worldedit.register_command("fixedpos", {
if found == nil then
return false
end
return true, flag, {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
return true, flag, vector.new(tonumber(x), tonumber(y), tonumber(z))
end,
func = function(name, flag, pos)
if flag == "set1" then
@ -1047,7 +1047,7 @@ worldedit.register_command("stack2", {
return false, "invalid increments: " .. param
end
return true, tonumber(repetitions), {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
return true, tonumber(repetitions), vector.new(tonumber(x), tonumber(y), tonumber(z))
end,
nodes_needed = function(name, repetitions, offset)
return check_region(name) * repetitions
@ -1219,13 +1219,16 @@ worldedit.register_command("drain", {
-- TODO: make an API function for this
local count = 0
local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
local get_node, remove_node = minetest.get_node, minetest.remove_node
for x = pos1.x, pos2.x do
for y = pos1.y, pos2.y do
for z = pos1.z, pos2.z do
local n = minetest.get_node({x=x, y=y, z=z}).name
local p = vector.new(x, y, z)
local n = get_node(p).name
local d = minetest.registered_nodes[n]
if d ~= nil and (d["drawtype"] == "liquid" or d["drawtype"] == "flowingliquid") then
minetest.remove_node({x=x, y=y, z=z})
if d ~= nil and (d.drawtype == "liquid" or d.drawtype == "flowingliquid") then
remove_node(p)
count = count + 1
end
end
@ -1263,13 +1266,15 @@ local function clearcut(pos1, pos2)
local count = 0
local prev, any
local get_node, remove_node = minetest.get_node, minetest.remove_node
for x = pos1.x, pos2.x do
for z = pos1.z, pos2.z do
prev = false
any = false
-- first pass: remove floating nodes that would be left over
for y = pos1.y, pos2.y do
local n = minetest.get_node({x=x, y=y, z=z}).name
local pos = vector.new(x, y, z)
local n = get_node(pos).name
if plants[n] then
prev = true
any = true
@ -1277,7 +1282,7 @@ local function clearcut(pos1, pos2)
local def = minetest.registered_nodes[n] or {}
local groups = def.groups or {}
if groups.attached_node or (def.buildable_to and groups.falling_node) then
minetest.remove_node({x=x, y=y, z=z})
remove_node(pos)
count = count + 1
else
prev = false
@ -1288,9 +1293,10 @@ local function clearcut(pos1, pos2)
-- second pass: remove plants, top-to-bottom to avoid item drops
if any then
for y = pos2.y, pos1.y, -1 do
local n = minetest.get_node({x=x, y=y, z=z}).name
local pos = vector.new(x, y, z)
local n = get_node(pos).name
if plants[n] then
minetest.remove_node({x=x, y=y, z=z})
remove_node(pos)
count = count + 1
end
end

View File

@ -42,7 +42,7 @@ Example:
worldedit.register_gui_handler = function(identifier, handler)
local enabled = true
minetest.register_on_player_receive_fields(function(player, formname, fields)
if not enabled then return false end
if not enabled or formname ~= "" or fields.worldedit_gui then return false end
enabled = false
minetest.after(0.2, function() enabled = true end)
local name = player:get_player_name()