mirror of
				https://github.com/Uberi/Minetest-WorldEdit.git
				synced 2025-11-04 06:35:28 +01:00 
			
		
		
		
	Split up some files in worldedit mod
This commit is contained in:
		@@ -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
 | 
			
		||||
		Reference in New Issue
	
	Block a user