Compare commits
	
		
			68 Commits
		
	
	
		
			2f26fb7645
			...
			ceb2c2cded
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					ceb2c2cded | ||
| 
						 | 
					ae77f73650 | ||
| 
						 | 
					28374f4f27 | ||
| 
						 | 
					fb7a37e87c | ||
| 
						 | 
					57e7d4c488 | ||
| 
						 | 
					e20a5a4e09 | ||
| 
						 | 
					f8442ef9fd | ||
| 
						 | 
					86de970552 | ||
| 
						 | 
					4c8d42bf7b | ||
| 
						 | 
					5a00c07c68 | ||
| 
						 | 
					cda772b18d | ||
| 
						 | 
					8c758671bc | ||
| 
						 | 
					456ce8c800 | ||
| 
						 | 
					8065e3d804 | ||
| 
						 | 
					60b6b205ad | ||
| 
						 | 
					1d8d9a704f | ||
| 
						 | 
					d13ba673fe | ||
| 
						 | 
					acb3ecefe4 | ||
| 
						 | 
					dc1150fe3d | ||
| 
						 | 
					74663869f7 | ||
| 
						 | 
					4b470bdae6 | ||
| 
						 | 
					75d101116d | ||
| 
						 | 
					860d4a267d | ||
| 
						 | 
					372847e774 | ||
| 
						 | 
					883caff58d | ||
| 
						 | 
					41d53180b1 | ||
| 
						 | 
					602f175cc0 | ||
| 
						 | 
					e6fac23c53 | ||
| 
						 | 
					5914eab20b | ||
| 
						 | 
					002dc462d6 | ||
| 
						 | 
					a713efe051 | ||
| 
						 | 
					469c3bf70b | ||
| 
						 | 
					f75700ed76 | ||
| 
						 | 
					575bfca67a | ||
| 
						 | 
					eac05e3133 | ||
| 
						 | 
					41efbaf210 | ||
| 
						 | 
					045c7510bf | ||
| 
						 | 
					b90eeb1e68 | ||
| 
						 | 
					341014f94a | ||
| 
						 | 
					b84aa8508a | ||
| 
						 | 
					17df0bbf71 | ||
| 
						 | 
					36b14413e0 | ||
| 
						 | 
					1fc6d93112 | ||
| 
						 | 
					ccfb6b4d61 | ||
| 
						 | 
					8f60e6f729 | ||
| 
						 | 
					8f86a2120c | ||
| 
						 | 
					b4202ea779 | ||
| 
						 | 
					689ff90a78 | ||
| 
						 | 
					bf55f52197 | ||
| 
						 | 
					79e5e64c44 | ||
| 
						 | 
					375fbf3c68 | ||
| 
						 | 
					cc3aab00bc | ||
| 
						 | 
					eff01bc8e7 | ||
| 
						 | 
					099d5047bd | ||
| 
						 | 
					7f7e928dd9 | ||
| 
						 | 
					1a9f66f091 | ||
| 
						 | 
					7a5d76a9bc | ||
| 
						 | 
					5260f595c6 | ||
| 
						 | 
					7a645eba05 | ||
| 
						 | 
					9417f2bbf1 | ||
| 
						 | 
					abc9efeeb8 | ||
| 
						 | 
					c223ca4cec | ||
| 
						 | 
					c8afa95542 | ||
| 
						 | 
					670e421f57 | ||
| 
						 | 
					770601dd5d | ||
| 
						 | 
					2f2f5a7def | ||
| 
						 | 
					7f87f1658e | ||
| 
						 | 
					4378750498 | 
							
								
								
									
										15
									
								
								.github/workflows/check.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,15 @@
 | 
			
		||||
name: "Check"
 | 
			
		||||
on: [push, pull_request]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  lint:
 | 
			
		||||
    name: "Luacheck"
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v4
 | 
			
		||||
    - name: apt
 | 
			
		||||
      run: sudo apt-get install -y luarocks
 | 
			
		||||
    - name: luacheck install
 | 
			
		||||
      run: luarocks install --local luacheck
 | 
			
		||||
    - name: luacheck run
 | 
			
		||||
      run: $HOME/.luarocks/bin/luacheck ./
 | 
			
		||||
							
								
								
									
										27
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,27 @@
 | 
			
		||||
name: "Test"
 | 
			
		||||
on: [push, pull_request]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    name: "Unit Tests ${{ matrix.cfg.image }}"
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    timeout-minutes: 5
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        cfg:
 | 
			
		||||
          - { image: 'registry.gitlab.com/minetest/minetest/server:5.0.1', mtg: false }
 | 
			
		||||
          - { image: 'registry.gitlab.com/minetest/minetest/server:5.5.1', mtg: false }
 | 
			
		||||
          - { image: '', mtg: true } # latest mater
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v4
 | 
			
		||||
 | 
			
		||||
    - uses: actions/checkout@v4
 | 
			
		||||
      with:
 | 
			
		||||
        repository: 'minetest/minetest_game'
 | 
			
		||||
        path: minetest_game
 | 
			
		||||
      if: ${{ matrix.cfg.mtg }}
 | 
			
		||||
 | 
			
		||||
    - name: Run tests
 | 
			
		||||
      run: ./.util/run_tests.sh --docker
 | 
			
		||||
      env:
 | 
			
		||||
        DOCKER_IMAGE: "${{ matrix.cfg.image }}"
 | 
			
		||||
							
								
								
									
										28
									
								
								.luacheckrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,28 @@
 | 
			
		||||
read_globals = {
 | 
			
		||||
	"minetest", "VoxelArea", "ItemStack",
 | 
			
		||||
	"unified_inventory", "sfinv", "smart_inventory", "inventory_plus",
 | 
			
		||||
	"dump",
 | 
			
		||||
 | 
			
		||||
	table = {fields = {"copy", "indexof", "insert_all"}},
 | 
			
		||||
	vector = {fields = {
 | 
			
		||||
		-- as of 5.0
 | 
			
		||||
		"new", "direction", "distance", "length", "normalize", "floor", "round",
 | 
			
		||||
		"apply", "equals", "sort", "add", "subtract", "multiply", "divide",
 | 
			
		||||
		-- polyfilled
 | 
			
		||||
		"copy"
 | 
			
		||||
	}},
 | 
			
		||||
}
 | 
			
		||||
globals = {"worldedit"}
 | 
			
		||||
 | 
			
		||||
-- Ignore these errors until someone decides to fix them
 | 
			
		||||
ignore = {"212", "213", "411", "412", "421", "422", "431", "432", "631"}
 | 
			
		||||
 | 
			
		||||
files["worldedit/common.lua"] = {
 | 
			
		||||
	globals = {"vector"},
 | 
			
		||||
}
 | 
			
		||||
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"},
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								.util/run_tests.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						@@ -0,0 +1,42 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
tempdir=$(mktemp -d)
 | 
			
		||||
confpath=$tempdir/minetest.conf
 | 
			
		||||
worldpath=$tempdir/world
 | 
			
		||||
trap 'rm -rf "$tempdir"' EXIT
 | 
			
		||||
 | 
			
		||||
[ -f worldedit/mod.conf ] || { echo "Must be run in modpack root folder." >&2; exit 1; }
 | 
			
		||||
 | 
			
		||||
mtserver=
 | 
			
		||||
if [ "$1" == "--docker" ]; then
 | 
			
		||||
	command -v docker >/dev/null || { echo "Docker is not installed." >&2; exit 1; }
 | 
			
		||||
	[ -d minetest_game ] || echo "A source checkout of minetest_game was not found. This can fail if your docker image does not ship a game." >&2;
 | 
			
		||||
else
 | 
			
		||||
	mtserver=$(command -v minetestserver)
 | 
			
		||||
	[[ -z "$mtserver" && -x ../../bin/minetestserver ]] && mtserver=../../bin/minetestserver
 | 
			
		||||
	[ -z "$mtserver" ] && { echo "To run the test outside of Docker, an installation of minetestserver is required." >&2; exit 1; }
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
mkdir $worldpath
 | 
			
		||||
printf '%s\n' mg_name=singlenode '[end_of_params]' >$worldpath/map_meta.txt
 | 
			
		||||
printf '%s\n' worldedit_run_tests=true max_forceloaded_blocks=9999 >$confpath
 | 
			
		||||
 | 
			
		||||
if [ -z "$mtserver" ]; then
 | 
			
		||||
	chmod -R 777 $tempdir
 | 
			
		||||
	[ -z "$DOCKER_IMAGE" ] && DOCKER_IMAGE="ghcr.io/minetest/minetest:master"
 | 
			
		||||
	vol=(
 | 
			
		||||
		-v "$confpath":/etc/minetest/minetest.conf
 | 
			
		||||
		-v "$tempdir":/var/lib/minetest/.minetest
 | 
			
		||||
		-v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit
 | 
			
		||||
	)
 | 
			
		||||
	[ -d minetest_game ] && vol+=(
 | 
			
		||||
		-v "$PWD/minetest_game":/var/lib/minetest/.minetest/games/minetest_game
 | 
			
		||||
	)
 | 
			
		||||
	docker run --rm -i "${vol[@]}" "$DOCKER_IMAGE"
 | 
			
		||||
else
 | 
			
		||||
	mkdir $worldpath/worldmods
 | 
			
		||||
	ln -s "$PWD/worldedit" $worldpath/worldmods/worldedit
 | 
			
		||||
	$mtserver --config "$confpath" --world "$worldpath" --logfile /dev/null
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
test -f $worldpath/tests_ok || exit 1
 | 
			
		||||
exit 0
 | 
			
		||||
@@ -409,14 +409,15 @@ Load nodes from "(world folder)/schems/`<file>`.we" with position 1 of the curre
 | 
			
		||||
 | 
			
		||||
### `//lua <code>`
 | 
			
		||||
 | 
			
		||||
Executes `<code>` as a Lua chunk in the global namespace.
 | 
			
		||||
Executes `<code>` as a Lua chunk in the global namespace with the variables `name`, `player` and `pos` (= player position) available.
 | 
			
		||||
 | 
			
		||||
    //lua worldedit.pos1["singleplayer"] = {x=0, y=0, z=0}
 | 
			
		||||
    //lua worldedit.rotate(worldedit.pos1["singleplayer"], worldedit.pos2["singleplayer"], "y", 90)
 | 
			
		||||
    //lua worldedit.pos1[name] = vector.new(0, 0, 0)
 | 
			
		||||
    //lua worldedit.rotate(worldedit.pos1["jones"], worldedit.pos2["jones"], "y", 90)
 | 
			
		||||
    //lua player:set_pos(worldedit.pos2[name])
 | 
			
		||||
 | 
			
		||||
### `//luatransform <code>`
 | 
			
		||||
 | 
			
		||||
Executes `<code>` as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region.
 | 
			
		||||
Executes `<code>` as a Lua chunk in the global namespace with the variable `pos` available, for each node in the current WorldEdit region.
 | 
			
		||||
 | 
			
		||||
    //luatransform minetest.swap_node(pos, {name="default:stone"})
 | 
			
		||||
    //luatransform if minetest.get_node(pos).name == "air" then minetest.add_node(pos, {name="default:water_source"}) end
 | 
			
		||||
@@ -428,11 +429,12 @@ Save the current WorldEdit region using the Minetest Schematic format to "(world
 | 
			
		||||
    //mtschemcreate some random filename
 | 
			
		||||
    //mtschemcreate huge_base
 | 
			
		||||
 | 
			
		||||
### `//mtschemplace <file>`
 | 
			
		||||
### `//mtschemplace <file> [rotation]`
 | 
			
		||||
 | 
			
		||||
Load nodes from "(world folder)/schems/`<file>`.mts" with position 1 of the current WorldEdit region as the origin.
 | 
			
		||||
Valid values for `[rotation]` are 0, 90, 180 and 270.
 | 
			
		||||
 | 
			
		||||
    //mtschemplace some random filename
 | 
			
		||||
    //mtschemplace a_tree 270
 | 
			
		||||
    //mtschemplace huge_base
 | 
			
		||||
 | 
			
		||||
### `//mtschemprob start/finish/get`
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ WorldEdit v1.3
 | 
			
		||||
==============
 | 
			
		||||
The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more.
 | 
			
		||||
 | 
			
		||||
For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) at the Minetest forums.
 | 
			
		||||
For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?t=572) at the Minetest forums.
 | 
			
		||||
 | 
			
		||||
# New users should see the [tutorial](Tutorial.md).
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +23,7 @@ There is a nice installation guide over at the [Minetest Wiki](http://wiki.minet
 | 
			
		||||
8. You should have a mod selection screen. Select the one named something like `Minetest-WorldEdit` by left clicking once and press the **Enable Modpack** button.
 | 
			
		||||
9. Press the **Save** button. You can now use WorldEdit in that world. Repeat steps 7 to 9 to enable WorldEdit for other worlds too.
 | 
			
		||||
 | 
			
		||||
If you are having trouble, try asking for help in the [IRC channel](https://webchat.freenode.net/?channels=#minetest) (faster but may not always have helpers online)
 | 
			
		||||
If you are having trouble, try asking for help in the [IRC channel](https://web.libera.chat/#minetest) (faster but may not always have helpers online)
 | 
			
		||||
or ask on the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) (slower but more likely to get help).
 | 
			
		||||
 | 
			
		||||
Usage
 | 
			
		||||
 
 | 
			
		||||
@@ -227,11 +227,19 @@ Code
 | 
			
		||||
----
 | 
			
		||||
Contained in code.lua, this module allows arbitrary Lua code to be used with WorldEdit.
 | 
			
		||||
 | 
			
		||||
### error = worldedit.lua(code)
 | 
			
		||||
### error = worldedit.lua(code, name)
 | 
			
		||||
 | 
			
		||||
Executes `code` as a Lua chunk in the global namespace.
 | 
			
		||||
the given code gets encapsulated into a function with parameters `name`, `player`, `pos`
 | 
			
		||||
where
 | 
			
		||||
 * `name` is a playername or `nil`
 | 
			
		||||
 * `player` is the player object of the above player if applicable, otherwise `nil`
 | 
			
		||||
 * `pos` is the position of the aforementioned player (if applicable, otherwise `nil`) rounded to integers
 | 
			
		||||
 | 
			
		||||
Returns an error if the code fails or nil otherwise.
 | 
			
		||||
the resulting function is then executed as a Lua chunk in the global namespace.
 | 
			
		||||
 | 
			
		||||
The return is
 | 
			
		||||
 * a string in case of an error
 | 
			
		||||
 * a tuple of `nil` and return of the function converted to a string in case of success
 | 
			
		||||
 | 
			
		||||
### error = worldedit.luatransform(pos1, pos2, code)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,2 +1,3 @@
 | 
			
		||||
name = Minetest-WorldEdit
 | 
			
		||||
description = WorldEdit is an in-game world editor. Use it to repair griefing, or just create awesome buildings in seconds.
 | 
			
		||||
min_minetest_version = 5.0
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								settingtypes.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,4 @@
 | 
			
		||||
#    For operations that potentially affect more than the specified amount of nodes
 | 
			
		||||
#    WorldEdit will require additional confirmation via //y before proceeding.
 | 
			
		||||
#    Set to 0 to disable the confirmation in all cases.
 | 
			
		||||
worldedit_safe_region_limit (Limit for safe region warning) int 20000
 | 
			
		||||
@@ -2,17 +2,29 @@
 | 
			
		||||
-- @module worldedit.code
 | 
			
		||||
 | 
			
		||||
--- Executes `code` as a Lua chunk in the global namespace.
 | 
			
		||||
-- @return An error message if the code fails, or nil on success.
 | 
			
		||||
function worldedit.lua(code)
 | 
			
		||||
	local func, err = loadstring(code)
 | 
			
		||||
	if not func then  -- Syntax error
 | 
			
		||||
-- the code will be encapsulated into a function with parameters
 | 
			
		||||
--  * name (the name of the player issuing the //lua command)
 | 
			
		||||
--  * player (the player object of the player)
 | 
			
		||||
--  * pos (the position of the player rounded to integers)
 | 
			
		||||
-- @return string in case of error, tuple of nil, return of code as string in case of success
 | 
			
		||||
function worldedit.lua(code, name)
 | 
			
		||||
	local factory, err = loadstring("return function(name, player, pos)\n" .. code .. "\nend")
 | 
			
		||||
	if not factory then -- Syntax error
 | 
			
		||||
		return err
 | 
			
		||||
	end
 | 
			
		||||
	local good, err = pcall(func)
 | 
			
		||||
	if not good then  -- Runtime error
 | 
			
		||||
		return err
 | 
			
		||||
	local func = factory()
 | 
			
		||||
	local player, pos
 | 
			
		||||
	if name then
 | 
			
		||||
		player = minetest.get_player_by_name(name)
 | 
			
		||||
		if player then
 | 
			
		||||
			pos = vector.round(player:get_pos())
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	return nil
 | 
			
		||||
	local good, err = pcall(func, name, player, pos)
 | 
			
		||||
	if not good then -- Runtime error
 | 
			
		||||
		return tostring(err)
 | 
			
		||||
	end
 | 
			
		||||
	return nil, dump(err)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -31,7 +43,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
 | 
			
		||||
@@ -39,7 +51,7 @@ function worldedit.luatransform(pos1, pos2, code)
 | 
			
		||||
			while pos.z <= pos2.z do
 | 
			
		||||
				local good, err = pcall(func, pos)
 | 
			
		||||
				if not good then -- Runtime error
 | 
			
		||||
					return err
 | 
			
		||||
					return tostring(err)
 | 
			
		||||
				end
 | 
			
		||||
				pos.z = pos.z + 1
 | 
			
		||||
			end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,21 @@
 | 
			
		||||
--- Common functions [INTERNAL].  All of these functions are internal!
 | 
			
		||||
-- @module worldedit.common
 | 
			
		||||
 | 
			
		||||
-- Polyfill for vector.copy (added in 5.5.0)
 | 
			
		||||
if not vector.copy then
 | 
			
		||||
	local vnew = vector.new
 | 
			
		||||
	vector.copy = function(v)
 | 
			
		||||
		return vnew(v.x, v.y, v.z)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
--- Copies and modifies positions `pos1` and `pos2` so that each component of
 | 
			
		||||
-- `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.copy(pos1)
 | 
			
		||||
	pos2 = vector.copy(pos2)
 | 
			
		||||
	if pos1.x > pos2.x then
 | 
			
		||||
		pos2.x, pos1.x = pos1.x, pos2.x
 | 
			
		||||
	end
 | 
			
		||||
@@ -45,12 +54,22 @@ function worldedit.get_axis_others(axis)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- Create a vmanip and read the area from map, this causes all
 | 
			
		||||
-- MapBlocks to be loaded into memory synchronously.
 | 
			
		||||
-- This doesn't actually *keep* them loaded, unlike the name implies.
 | 
			
		||||
function worldedit.keep_loaded(pos1, pos2)
 | 
			
		||||
	-- Create a vmanip and read the area from map, this
 | 
			
		||||
	-- causes all MapBlocks to be loaded into memory.
 | 
			
		||||
	-- This doesn't actually *keep* them loaded, unlike the name implies.
 | 
			
		||||
	local manip = minetest.get_voxel_manip()
 | 
			
		||||
	manip:read_from_map(pos1, pos2)
 | 
			
		||||
	-- rough estimate, a MapNode is 4 bytes in the engine
 | 
			
		||||
	if worldedit.volume(pos1, pos2) > 268400000 then
 | 
			
		||||
		print("[WorldEdit] Requested to load an area bigger than 1GB, refusing. The subsequent operation may fail.")
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
	if minetest.load_area then
 | 
			
		||||
		-- same effect but without unnecessary data copying
 | 
			
		||||
		minetest.load_area(pos1, pos2)
 | 
			
		||||
	else
 | 
			
		||||
		local manip = minetest.get_voxel_manip()
 | 
			
		||||
		manip:read_from_map(pos1, pos2)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -65,7 +84,7 @@ function mh.get_empty_data(area)
 | 
			
		||||
	-- only partially modified aren't overwriten.
 | 
			
		||||
	local data = {}
 | 
			
		||||
	local c_ignore = minetest.get_content_id("ignore")
 | 
			
		||||
	for i = 1, worldedit.volume(area.MinEdge, area.MaxEdge) do
 | 
			
		||||
	for i = 1, area:getVolume() do
 | 
			
		||||
		data[i] = c_ignore
 | 
			
		||||
	end
 | 
			
		||||
	return data
 | 
			
		||||
@@ -117,3 +136,72 @@ function mh.finish(manip, data)
 | 
			
		||||
	manip:update_map()
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- returns an iterator that returns voxelarea indices for a hollow cuboid
 | 
			
		||||
function mh.iter_hollowcuboid(area, minx, miny, minz, maxx, maxy, maxz)
 | 
			
		||||
	local i = area:index(minx, miny, minz) - 1
 | 
			
		||||
	local xrange = maxx - minx + 1
 | 
			
		||||
	local nextaction = i + 1 + xrange
 | 
			
		||||
	local do_hole = false
 | 
			
		||||
 | 
			
		||||
	local y = 0
 | 
			
		||||
	local ydiff = maxy - miny
 | 
			
		||||
	local ystride = area.ystride
 | 
			
		||||
	local ymultistride = ydiff * ystride
 | 
			
		||||
 | 
			
		||||
	local z = 0
 | 
			
		||||
	local zdiff = maxz - minz
 | 
			
		||||
	local zstride = area.zstride
 | 
			
		||||
	local zcorner = true
 | 
			
		||||
 | 
			
		||||
	return function()
 | 
			
		||||
		-- continue i until it needs to jump ystride
 | 
			
		||||
		i = i + 1
 | 
			
		||||
		if i ~= nextaction then
 | 
			
		||||
			return i
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		-- add the x offset if y (and z) are not 0 or maxy (or maxz)
 | 
			
		||||
		if do_hole then
 | 
			
		||||
			do_hole = false
 | 
			
		||||
			i = i + xrange - 2
 | 
			
		||||
			nextaction = i + 1
 | 
			
		||||
			return i
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		-- continue y until maxy is exceeded
 | 
			
		||||
		y = y+1
 | 
			
		||||
		if y ~= ydiff + 1 then
 | 
			
		||||
			i = i + ystride - xrange
 | 
			
		||||
			if zcorner
 | 
			
		||||
			or y == ydiff then
 | 
			
		||||
				nextaction = i + xrange
 | 
			
		||||
			else
 | 
			
		||||
				nextaction = i + 1
 | 
			
		||||
				do_hole = true
 | 
			
		||||
			end
 | 
			
		||||
			return i
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		-- continue z until maxz is exceeded
 | 
			
		||||
		z = z+1
 | 
			
		||||
		if z == zdiff + 1 then
 | 
			
		||||
			-- hollowcuboid finished, return nil
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		-- set i to index(minx, miny, minz + z) - 1
 | 
			
		||||
		i = i + zstride - (ymultistride + xrange)
 | 
			
		||||
		zcorner = z == zdiff
 | 
			
		||||
 | 
			
		||||
		-- y is 0, so traverse the xs
 | 
			
		||||
		y = 0
 | 
			
		||||
		nextaction = i + xrange
 | 
			
		||||
		return i
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function mh.iterp_hollowcuboid(area, minp, maxp)
 | 
			
		||||
	return mh.iter_hollowcuboid(area, minp.x, minp.y, minp.z,
 | 
			
		||||
		maxp.x, maxp.y, maxp.z)
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -24,11 +24,14 @@ function worldedit.metasave(pos1, pos2, filename)
 | 
			
		||||
	return count
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function worldedit.metaload(originpos, filename)
 | 
			
		||||
function worldedit.metaload(originpos, file_name)
 | 
			
		||||
	deprecated("load")
 | 
			
		||||
	filename = minetest.get_worldpath() .. "/schems/" .. file .. ".wem"
 | 
			
		||||
	local file, err = io.open(filename, "wb")
 | 
			
		||||
	if err then return 0 end
 | 
			
		||||
	local file_path = minetest.get_worldpath() ..
 | 
			
		||||
		"/schems/" .. file_name .. ".wem"
 | 
			
		||||
	local file, err = io.open(file_path, "wb")
 | 
			
		||||
	if err then
 | 
			
		||||
		return 0
 | 
			
		||||
	end
 | 
			
		||||
	local data = file:read("*a")
 | 
			
		||||
	return worldedit.deserialize(originpos, data)
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -2,16 +2,16 @@
 | 
			
		||||
worldedit.cuboid_volumetric_expand = function(name, amount)
 | 
			
		||||
	local pos1 = worldedit.pos1[name]
 | 
			
		||||
	local pos2 = worldedit.pos2[name]
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if pos1 == nil or pos2 == nil then
 | 
			
		||||
		return false, "Undefined cuboid"
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	local delta1 = vector.new()
 | 
			
		||||
	local delta2 = vector.new()
 | 
			
		||||
	local delta_dir1
 | 
			
		||||
	local delta_dir2
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	delta1 = vector.add(delta1, amount)
 | 
			
		||||
	delta2 = vector.add(delta2, amount)
 | 
			
		||||
	delta_dir1, delta_dir2 = worldedit.get_expansion_directions(pos1, pos2)
 | 
			
		||||
@@ -19,7 +19,7 @@ worldedit.cuboid_volumetric_expand = function(name, amount)
 | 
			
		||||
	delta2 = vector.multiply(delta2, delta_dir2)
 | 
			
		||||
	worldedit.pos1[name] = vector.add(pos1, delta1)
 | 
			
		||||
	worldedit.pos2[name] = vector.add(pos2, delta2)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@@ -28,18 +28,18 @@ end
 | 
			
		||||
worldedit.cuboid_linear_expand = function(name, axis, direction, amount)
 | 
			
		||||
	local pos1 = worldedit.pos1[name]
 | 
			
		||||
	local pos2 = worldedit.pos2[name]
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if pos1 == nil or pos2 == nil then
 | 
			
		||||
		return false, "undefined cuboid"
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if direction ~= 1 and direction ~= -1 then
 | 
			
		||||
		return false, "invalid marker"
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	local marker = worldedit.marker_get_closest_to_axis(name, axis, direction)
 | 
			
		||||
	local deltavect = vector.new()
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if axis == 'x' then
 | 
			
		||||
		deltavect.x = amount * direction
 | 
			
		||||
	elseif axis == 'y' then
 | 
			
		||||
@@ -49,7 +49,7 @@ worldedit.cuboid_linear_expand = function(name, axis, direction, amount)
 | 
			
		||||
	else
 | 
			
		||||
		return false, "invalid axis"
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	worldedit.marker_move(name, marker, deltavect)
 | 
			
		||||
	return true
 | 
			
		||||
end
 | 
			
		||||
@@ -59,11 +59,13 @@ end
 | 
			
		||||
worldedit.cuboid_shift = function(name, axis, amount)
 | 
			
		||||
	local pos1 = worldedit.pos1[name]
 | 
			
		||||
	local pos2 = worldedit.pos2[name]
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if pos1 == nil or pos2 == nil then
 | 
			
		||||
		return false, "undefined cuboid"
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	assert(not rawequal(pos1, pos2)) -- vectors must not alias
 | 
			
		||||
 | 
			
		||||
	if axis == 'x' then
 | 
			
		||||
		worldedit.pos1[name].x = pos1.x + amount
 | 
			
		||||
		worldedit.pos2[name].x = pos2.x + amount
 | 
			
		||||
@@ -76,7 +78,7 @@ worldedit.cuboid_shift = function(name, axis, amount)
 | 
			
		||||
	else
 | 
			
		||||
		return false, "invalid axis"
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@@ -86,7 +88,7 @@ worldedit.marker_move = function(name, marker, deltavector)
 | 
			
		||||
	if marker ~= 1 and marker ~= 2 then
 | 
			
		||||
		return false
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if marker == 1 then
 | 
			
		||||
		local pos = worldedit.pos1[name]
 | 
			
		||||
		worldedit.pos1[name] = vector.add(deltavector, pos)
 | 
			
		||||
@@ -94,7 +96,7 @@ worldedit.marker_move = function(name, marker, deltavector)
 | 
			
		||||
		local pos = worldedit.pos2[name]
 | 
			
		||||
		worldedit.pos2[name] = vector.add(deltavector, pos)
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@@ -134,10 +136,11 @@ end
 | 
			
		||||
 | 
			
		||||
-- Return the marker that is closest to the player
 | 
			
		||||
worldedit.marker_get_closest_to_player = function(name)
 | 
			
		||||
	local playerpos = minetest.get_player_by_name(name):get_pos()
 | 
			
		||||
	local player = assert(minetest.get_player_by_name(name))
 | 
			
		||||
	local playerpos = player:get_pos()
 | 
			
		||||
	local dist1 = vector.distance(playerpos, worldedit.pos1[name])
 | 
			
		||||
	local dist2 = vector.distance(playerpos, worldedit.pos2[name])
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if dist1 < dist2 then
 | 
			
		||||
		return 1
 | 
			
		||||
	else
 | 
			
		||||
@@ -150,7 +153,7 @@ end
 | 
			
		||||
worldedit.marker_get_closest_to_axis = function(name, axis, direction)
 | 
			
		||||
	local pos1 = vector.new()
 | 
			
		||||
	local pos2 = vector.new()
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if direction ~= 1 and direction ~= -1 then
 | 
			
		||||
		return nil
 | 
			
		||||
	end
 | 
			
		||||
@@ -185,20 +188,20 @@ worldedit.marker_get_closest_to_axis = function(name, axis, direction)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
-- Translates up, down, left, right, front, back to their corresponding axes and 
 | 
			
		||||
-- Translates up, down, left, right, front, back to their corresponding axes and
 | 
			
		||||
-- directions according to faced direction
 | 
			
		||||
worldedit.translate_direction = function(name, direction)
 | 
			
		||||
	local axis, dir = worldedit.player_axis(name)
 | 
			
		||||
	local resaxis, resdir
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if direction == "up" then
 | 
			
		||||
		return 'y', 1
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if direction == "down" then
 | 
			
		||||
		return 'y', -1
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if direction == "front" then
 | 
			
		||||
		if axis == "y" then
 | 
			
		||||
			resaxis = nil
 | 
			
		||||
@@ -208,7 +211,7 @@ worldedit.translate_direction = function(name, direction)
 | 
			
		||||
			resdir = dir
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if direction == "back" then
 | 
			
		||||
		if axis == "y" then
 | 
			
		||||
			resaxis = nil
 | 
			
		||||
@@ -218,7 +221,7 @@ worldedit.translate_direction = function(name, direction)
 | 
			
		||||
			resdir = -dir
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if direction == "left" then
 | 
			
		||||
		if axis == 'x' then
 | 
			
		||||
			resaxis = 'z'
 | 
			
		||||
@@ -228,7 +231,7 @@ worldedit.translate_direction = function(name, direction)
 | 
			
		||||
			resdir = -dir
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	if direction == "right" then
 | 
			
		||||
		if axis == 'x' then
 | 
			
		||||
			resaxis = 'z'
 | 
			
		||||
@@ -238,6 +241,6 @@ worldedit.translate_direction = function(name, direction)
 | 
			
		||||
			resdir = dir
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	return resaxis, resdir
 | 
			
		||||
end
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ end
 | 
			
		||||
dofile(path .. "/common.lua")
 | 
			
		||||
load_module(path .. "/manipulations.lua")
 | 
			
		||||
load_module(path .. "/primitives.lua")
 | 
			
		||||
load_module(path .. "/transformations.lua")
 | 
			
		||||
load_module(path .. "/visualization.lua")
 | 
			
		||||
load_module(path .. "/serialization.lua")
 | 
			
		||||
load_module(path .. "/code.lua")
 | 
			
		||||
@@ -38,3 +39,7 @@ if minetest.settings:get_bool("log_mods") then
 | 
			
		||||
	print("[WorldEdit] Loaded!")
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
if minetest.settings:get_bool("worldedit_run_tests") then
 | 
			
		||||
	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")) * 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 = {x=0, y=0, z=0}
 | 
			
		||||
	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
 | 
			
		||||
@@ -155,7 +110,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 +125,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 +133,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)
 | 
			
		||||
@@ -217,8 +172,6 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards)
 | 
			
		||||
	dst_manip:set_param2_data(dst_data)
 | 
			
		||||
 | 
			
		||||
	mh.finish(dst_manip)
 | 
			
		||||
	src_data = nil
 | 
			
		||||
	dst_data = nil
 | 
			
		||||
 | 
			
		||||
	-- Copy metadata
 | 
			
		||||
	local get_meta = minetest.get_meta
 | 
			
		||||
@@ -226,7 +179,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 +190,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 +239,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
 | 
			
		||||
@@ -309,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={x=stretch_x, y=stretch_y, z=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 = {x=pos2.x, y=0, z=0}
 | 
			
		||||
	local big_pos = {x=0, y=0, z=0}
 | 
			
		||||
	while pos.x >= pos1.x do
 | 
			
		||||
		pos.y = pos2.y
 | 
			
		||||
		while pos.y >= pos1.y do
 | 
			
		||||
			pos.z = pos2.z
 | 
			
		||||
			while pos.z >= pos1.z do
 | 
			
		||||
				local node = 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 = {x=pos2.x, y=pos2.y, z=pos2.z}
 | 
			
		||||
	new_pos2[axis1] = pos1[axis1] + extent2
 | 
			
		||||
	new_pos2[axis2] = pos1[axis2] + extent1
 | 
			
		||||
 | 
			
		||||
	local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z}
 | 
			
		||||
	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 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 = {x=pos1.x, y=0, z=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 = {x=pos1.x, y=0, z=0}
 | 
			
		||||
	while pos.x <= pos2.x do
 | 
			
		||||
		pos.y = pos1.y
 | 
			
		||||
		while pos.y <= pos2.y do
 | 
			
		||||
			pos.z = pos1.z
 | 
			
		||||
			while pos.z <= pos2.z do
 | 
			
		||||
				local node = get_node(pos)
 | 
			
		||||
				local def = registered_nodes[node.name]
 | 
			
		||||
				if def then
 | 
			
		||||
					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.
 | 
			
		||||
@@ -640,31 +283,51 @@ function worldedit.clear_objects(pos1, pos2)
 | 
			
		||||
 | 
			
		||||
	worldedit.keep_loaded(pos1, pos2)
 | 
			
		||||
 | 
			
		||||
	-- 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
 | 
			
		||||
	local function should_delete(obj)
 | 
			
		||||
		-- Avoid players and WorldEdit entities
 | 
			
		||||
		if obj:is_player() then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		local entity = obj:get_luaentity()
 | 
			
		||||
		return not (entity and entity.name:find("^worldedit:"))
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	-- Offset positions to include full nodes (positions are in the center of nodes)
 | 
			
		||||
	pos1 = vector.add(pos1, -0.5)
 | 
			
		||||
	pos2 = vector.add(pos2, 0.5)
 | 
			
		||||
 | 
			
		||||
	local count = 0
 | 
			
		||||
	if minetest.get_objects_in_area then
 | 
			
		||||
		local objects = minetest.get_objects_in_area(pos1, pos2)
 | 
			
		||||
 | 
			
		||||
		for _, obj in pairs(objects) do
 | 
			
		||||
			if should_delete(obj) then
 | 
			
		||||
				obj:remove()
 | 
			
		||||
				count = count + 1
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
		return count
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	-- 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)
 | 
			
		||||
	local count = 0
 | 
			
		||||
	for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do
 | 
			
		||||
		local entity = obj:get_luaentity()
 | 
			
		||||
		-- Avoid players and WorldEdit entities
 | 
			
		||||
		if not obj:is_player() and (not entity or
 | 
			
		||||
				not entity.name:find("^worldedit:")) then
 | 
			
		||||
			(center.x - pos1.x) ^ 2 +
 | 
			
		||||
			(center.y - pos1.y) ^ 2 +
 | 
			
		||||
			(center.z - pos1.z) ^ 2)
 | 
			
		||||
	local objects = minetest.get_objects_inside_radius(center, radius)
 | 
			
		||||
	for _, obj in pairs(objects) 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
 | 
			
		||||
@@ -673,4 +336,3 @@ function worldedit.clear_objects(pos1, pos2)
 | 
			
		||||
	end
 | 
			
		||||
	return count
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,31 +14,26 @@ local mh = worldedit.manip_helpers
 | 
			
		||||
-- @return The number of nodes added.
 | 
			
		||||
function worldedit.cube(pos, width, height, length, node_name, hollow)
 | 
			
		||||
	-- Set up voxel manipulator
 | 
			
		||||
	local basepos = vector.subtract(pos, {x=math.floor(width/2), y=0, z=math.floor(length/2)})
 | 
			
		||||
	local manip, area = mh.init(basepos, vector.add(basepos, {x=width, y=height, z=length}))
 | 
			
		||||
	local basepos = vector.subtract(pos,
 | 
			
		||||
		vector.new(math.floor(width / 2), 0, math.floor(length / 2)))
 | 
			
		||||
	local endpos = vector.add(basepos,
 | 
			
		||||
		vector.new(width - 1, height - 1, length - 1))
 | 
			
		||||
	local manip, area = mh.init(basepos, endpos)
 | 
			
		||||
	local data = mh.get_empty_data(area)
 | 
			
		||||
 | 
			
		||||
	-- Add cube
 | 
			
		||||
	local node_id = minetest.get_content_id(node_name)
 | 
			
		||||
	local stride = {x=1, y=area.ystride, z=area.zstride}
 | 
			
		||||
	local offset = vector.subtract(basepos, area.MinEdge)
 | 
			
		||||
	local count = 0
 | 
			
		||||
	local iterfunc
 | 
			
		||||
	if hollow then
 | 
			
		||||
		iterfunc = mh.iterp_hollowcuboid(area, basepos, endpos)
 | 
			
		||||
	else
 | 
			
		||||
		iterfunc = area:iterp(basepos, endpos)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	for z = 0, length-1 do
 | 
			
		||||
		local index_z = (offset.z + z) * stride.z + 1 -- +1 for 1-based indexing
 | 
			
		||||
		for y = 0, height-1 do
 | 
			
		||||
			local index_y = index_z + (offset.y + y) * stride.y
 | 
			
		||||
			for x = 0, width-1 do
 | 
			
		||||
				local is_wall = z == 0 or z == length-1
 | 
			
		||||
					or y == 0 or y == height-1
 | 
			
		||||
					or x == 0 or x == width-1
 | 
			
		||||
				if not hollow or is_wall then
 | 
			
		||||
					local i = index_y + (offset.x + x)
 | 
			
		||||
					data[i] = node_id
 | 
			
		||||
					count = count + 1
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
	for vi in iterfunc do
 | 
			
		||||
		data[vi] = node_id
 | 
			
		||||
		count = count + 1
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	mh.finish(manip, data)
 | 
			
		||||
@@ -149,7 +144,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,12 +157,8 @@ 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 offset = {
 | 
			
		||||
		x = current_pos.x - area.MinEdge.x,
 | 
			
		||||
		y = current_pos.y - area.MinEdge.y,
 | 
			
		||||
		z = current_pos.z - area.MinEdge.z,
 | 
			
		||||
	}
 | 
			
		||||
	local stride = vector.new(1, area.ystride, area.zstride)
 | 
			
		||||
	local offset = vector.subtract(current_pos, area.MinEdge)
 | 
			
		||||
	local count = 0
 | 
			
		||||
	for i = 0, length - 1 do
 | 
			
		||||
		-- Calulate radius for this "height" in the cylinder
 | 
			
		||||
@@ -225,12 +216,8 @@ 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 offset = {
 | 
			
		||||
		x = pos.x - area.MinEdge.x,
 | 
			
		||||
		y = pos.y - area.MinEdge.y,
 | 
			
		||||
		z = pos.z - area.MinEdge.z,
 | 
			
		||||
	}
 | 
			
		||||
	local stride = vector.new(1, area.ystride, area.zstride)
 | 
			
		||||
	local offset = vector.subtract(pos, area.MinEdge)
 | 
			
		||||
	local size = math.abs(height * step)
 | 
			
		||||
	local count = 0
 | 
			
		||||
	-- For each level of the pyramid
 | 
			
		||||
@@ -242,8 +229,8 @@ function worldedit.pyramid(pos, axis, height, node_name, hollow)
 | 
			
		||||
			for index3 = -size, size do
 | 
			
		||||
				local i = new_index2 + (index3 + offset[other2]) * stride[other2]
 | 
			
		||||
				if (not hollow or size - math.abs(index2) < 2 or size - math.abs(index3) < 2) then
 | 
			
		||||
				       data[i] = node_id
 | 
			
		||||
				       count = count + 1
 | 
			
		||||
					data[i] = node_id
 | 
			
		||||
					count = count + 1
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
@@ -271,9 +258,9 @@ 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 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
 | 
			
		||||
	local stride = vector.new(1, area.ystride, area.zstride)
 | 
			
		||||
	local offset = vector.subtract(pos, area.MinEdge)
 | 
			
		||||
	local i = offset.z * stride.z + offset.y * stride.y + offset.x + 1
 | 
			
		||||
 | 
			
		||||
	-- Add first column
 | 
			
		||||
	local count = height
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -114,12 +114,15 @@ function worldedit.serialize(pos1, pos2)
 | 
			
		||||
	return LATEST_SERIALIZATION_HEADER .. result, count
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
-- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile)
 | 
			
		||||
-- by ChillCode, available under the MIT license.
 | 
			
		||||
local function deserialize_workaround(content)
 | 
			
		||||
	local nodes
 | 
			
		||||
	if not jit then
 | 
			
		||||
		nodes = minetest.deserialize(content, true)
 | 
			
		||||
	local nodes, err
 | 
			
		||||
	if not minetest.global_exists("jit") then
 | 
			
		||||
		nodes, err = minetest.deserialize(content, true)
 | 
			
		||||
	elseif not content:match("^%s*return%s*{") then
 | 
			
		||||
		-- The data doesn't look like we expect it to so we can't apply the workaround.
 | 
			
		||||
		-- hope for the best
 | 
			
		||||
		minetest.log("warning", "WorldEdit: deserializing data but can't apply LuaJIT workaround")
 | 
			
		||||
		nodes, err = minetest.deserialize(content, true)
 | 
			
		||||
	else
 | 
			
		||||
		-- XXX: This is a filthy hack that works surprisingly well
 | 
			
		||||
		-- in LuaJIT, `minetest.deserialize` will fail due to the register limit
 | 
			
		||||
@@ -129,18 +132,27 @@ local function deserialize_workaround(content)
 | 
			
		||||
		local escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)
 | 
			
		||||
		local startpos, startpos1 = 1, 1
 | 
			
		||||
		local endpos
 | 
			
		||||
		local entry
 | 
			
		||||
		while true do -- go through each individual node entry (except the last)
 | 
			
		||||
			startpos, endpos = escaped:find("},%s*{", startpos)
 | 
			
		||||
			startpos, endpos = escaped:find("}%s*,%s*{", startpos)
 | 
			
		||||
			if not startpos then
 | 
			
		||||
				break
 | 
			
		||||
			end
 | 
			
		||||
			local current = content:sub(startpos1, startpos)
 | 
			
		||||
			local entry = minetest.deserialize("return " .. current, true)
 | 
			
		||||
			entry, err = minetest.deserialize("return " .. current, true)
 | 
			
		||||
			if not entry then
 | 
			
		||||
				break
 | 
			
		||||
			end
 | 
			
		||||
			table.insert(nodes, entry)
 | 
			
		||||
			startpos, startpos1 = endpos, endpos
 | 
			
		||||
		end
 | 
			
		||||
		local entry = minetest.deserialize("return " .. content:sub(startpos1), true) -- process the last entry
 | 
			
		||||
		table.insert(nodes, entry)
 | 
			
		||||
		if not err then
 | 
			
		||||
			entry = minetest.deserialize("return " .. content:sub(startpos1), true) -- process the last entry
 | 
			
		||||
			table.insert(nodes, entry)
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	if err then
 | 
			
		||||
		minetest.log("warning", "WorldEdit: deserialize: " .. err)
 | 
			
		||||
	end
 | 
			
		||||
	return nodes
 | 
			
		||||
end
 | 
			
		||||
@@ -148,7 +160,7 @@ end
 | 
			
		||||
--- Loads the schematic in `value` into a node list in the latest format.
 | 
			
		||||
-- @return A node list in the latest format, or nil on failure.
 | 
			
		||||
local function load_schematic(value)
 | 
			
		||||
	local version, header, content = worldedit.read_header(value)
 | 
			
		||||
	local version, _, content = worldedit.read_header(value)
 | 
			
		||||
	local nodes = {}
 | 
			
		||||
	if version == 1 or version == 2 then -- Original flat table format
 | 
			
		||||
		local tables = minetest.deserialize(content, true)
 | 
			
		||||
@@ -223,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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -240,14 +250,16 @@ function worldedit.deserialize(origin_pos, value)
 | 
			
		||||
	worldedit.keep_loaded(pos1, pos2)
 | 
			
		||||
 | 
			
		||||
	local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
 | 
			
		||||
	local count = 0
 | 
			
		||||
	local add_node, get_meta = minetest.add_node, minetest.get_meta
 | 
			
		||||
	local registered_nodes = minetest.registered_nodes
 | 
			
		||||
	for i, entry in ipairs(nodes) do
 | 
			
		||||
		entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
 | 
			
		||||
		-- Entry acts as both position and node
 | 
			
		||||
		add_node(entry, entry)
 | 
			
		||||
		if entry.meta then
 | 
			
		||||
			get_meta(entry):from_table(entry.meta)
 | 
			
		||||
		if registered_nodes[entry.name] then
 | 
			
		||||
			entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
 | 
			
		||||
			-- Entry acts as both position and node
 | 
			
		||||
			add_node(entry, entry)
 | 
			
		||||
			if entry.meta then
 | 
			
		||||
				get_meta(entry):from_table(entry.meta)
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	return #nodes
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										382
									
								
								worldedit/test/init.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,382 @@
 | 
			
		||||
-- TODO: don't shit individual variables into the globals
 | 
			
		||||
 | 
			
		||||
---------------------
 | 
			
		||||
-- Helpers
 | 
			
		||||
---------------------
 | 
			
		||||
local vec = vector.new
 | 
			
		||||
local vecw = function(axis, n, base)
 | 
			
		||||
	local ret = vec(base)
 | 
			
		||||
	ret[axis] = n
 | 
			
		||||
	return ret
 | 
			
		||||
end
 | 
			
		||||
local pos2str = minetest.pos_to_string
 | 
			
		||||
local get_node = minetest.get_node
 | 
			
		||||
local set_node = minetest.set_node
 | 
			
		||||
 | 
			
		||||
---------------------
 | 
			
		||||
-- Nodes
 | 
			
		||||
---------------------
 | 
			
		||||
local air = "air"
 | 
			
		||||
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"]
 | 
			
		||||
	testnode2 = minetest.registered_aliases["mapgen_dirt"]
 | 
			
		||||
	testnode3 = minetest.registered_aliases["mapgen_cobble"] or minetest.registered_aliases["mapgen_dirt_with_grass"]
 | 
			
		||||
	assert(testnode1 and testnode2 and testnode3)
 | 
			
		||||
end
 | 
			
		||||
-- Writes repeating pattern into given area
 | 
			
		||||
rawset(_G, "place_pattern", function(pos1, pos2, pattern)
 | 
			
		||||
	local pos = vec()
 | 
			
		||||
	local node = {name=""}
 | 
			
		||||
	local i = 1
 | 
			
		||||
	for z = pos1.z, pos2.z do
 | 
			
		||||
		pos.z = z
 | 
			
		||||
	for y = pos1.y, pos2.y do
 | 
			
		||||
		pos.y = y
 | 
			
		||||
	for x = pos1.x, pos2.x do
 | 
			
		||||
		pos.x = x
 | 
			
		||||
		node.name = pattern[i]
 | 
			
		||||
		set_node(pos, node)
 | 
			
		||||
		i = i % #pattern + 1
 | 
			
		||||
	end
 | 
			
		||||
	end
 | 
			
		||||
	end
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
---------------------
 | 
			
		||||
-- Area management
 | 
			
		||||
---------------------
 | 
			
		||||
assert(minetest.get_mapgen_setting("mg_name") == "singlenode")
 | 
			
		||||
rawset(_G, "area", {})
 | 
			
		||||
do
 | 
			
		||||
	local areamin, areamax
 | 
			
		||||
	local off
 | 
			
		||||
	local c_air = minetest.get_content_id(air)
 | 
			
		||||
	local vbuffer = {}
 | 
			
		||||
	-- Assign a new area for use, will emerge and then call ready()
 | 
			
		||||
	area.assign = function(min, max, ready)
 | 
			
		||||
		areamin = min
 | 
			
		||||
		areamax = max
 | 
			
		||||
		minetest.emerge_area(min, max, function(bpos, action, remaining)
 | 
			
		||||
			assert(action ~= minetest.EMERGE_ERRORED)
 | 
			
		||||
			if remaining > 0 then return end
 | 
			
		||||
			minetest.after(0, function()
 | 
			
		||||
				area.clear()
 | 
			
		||||
				ready()
 | 
			
		||||
			end)
 | 
			
		||||
		end)
 | 
			
		||||
	end
 | 
			
		||||
	-- Reset area contents and state
 | 
			
		||||
	area.clear = function()
 | 
			
		||||
		if off and vector.equals(off, vec(0, 0, 0)) then
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
		local vmanip = minetest.get_voxel_manip(areamin, areamax)
 | 
			
		||||
		local vpos1, vpos2 = vmanip:get_emerged_area()
 | 
			
		||||
		local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1)
 | 
			
		||||
		if #vbuffer ~= vcount then
 | 
			
		||||
			vbuffer = {}
 | 
			
		||||
			for i = 1, vcount do
 | 
			
		||||
				vbuffer[i] = c_air
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
		vmanip:set_data(vbuffer)
 | 
			
		||||
		vmanip:write_to_map()
 | 
			
		||||
		off = vec(0, 0, 0)
 | 
			
		||||
	end
 | 
			
		||||
	-- Returns an usable area [pos1, pos2] that does not overlap previous ones
 | 
			
		||||
	area.get = function(sizex, sizey, sizez)
 | 
			
		||||
		local size
 | 
			
		||||
		if sizey == nil and sizez == nil then
 | 
			
		||||
			size = vec(sizex, sizex, sizex)
 | 
			
		||||
		else
 | 
			
		||||
			size = vec(sizex, sizey, sizez)
 | 
			
		||||
		end
 | 
			
		||||
		local pos1 = vector.add(areamin, off)
 | 
			
		||||
		local pos2 = vector.subtract(vector.add(pos1, size), 1)
 | 
			
		||||
		if pos2.x > areamax.x or pos2.y > areamax.y or pos2.z > areamax.z then
 | 
			
		||||
			error("Internal failure: out of space")
 | 
			
		||||
		end
 | 
			
		||||
		off = vector.add(off, size)
 | 
			
		||||
		return pos1, pos2
 | 
			
		||||
	end
 | 
			
		||||
	-- Returns an axis and count (= n) relative to the last-requested area that is unoccupied
 | 
			
		||||
	area.dir = function(n)
 | 
			
		||||
		local pos1 = vector.add(areamin, off)
 | 
			
		||||
		if pos1.x + n <= areamax.x then
 | 
			
		||||
			off.x = off.x + n
 | 
			
		||||
			return "x", n
 | 
			
		||||
		elseif pos1.x + n <= areamax.y then
 | 
			
		||||
			off.y = off.y + n
 | 
			
		||||
			return "y", n
 | 
			
		||||
		elseif pos1.z + n <= areamax.z then
 | 
			
		||||
			off.z = off.z + n
 | 
			
		||||
			return "z", n
 | 
			
		||||
		end
 | 
			
		||||
		error("Internal failure: out of space")
 | 
			
		||||
	end
 | 
			
		||||
	-- Returns [XYZ] margin (list of pos pairs) of n around last-requested area
 | 
			
		||||
	-- (may actually be larger but doesn't matter)
 | 
			
		||||
	area.margin = function(n)
 | 
			
		||||
		local pos1, pos2 = area.get(n)
 | 
			
		||||
		return {
 | 
			
		||||
			{ vec(areamin.x, areamin.y, pos1.z), pos2 }, -- X/Y
 | 
			
		||||
			{ vec(areamin.x, pos1.y, areamin.z), pos2 }, -- X/Z
 | 
			
		||||
			{ vec(pos1.x, areamin.y, areamin.z), pos2 }, -- Y/Z
 | 
			
		||||
		}
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
-- Split an existing area into two non-overlapping [pos1, half1], [half2, pos2] parts; returns half1, half2
 | 
			
		||||
area.split = function(pos1, pos2)
 | 
			
		||||
	local axis
 | 
			
		||||
	if pos2.x - pos1.x >= 1 then
 | 
			
		||||
		axis = "x"
 | 
			
		||||
	elseif pos2.y - pos1.y >= 1 then
 | 
			
		||||
		axis = "y"
 | 
			
		||||
	elseif pos2.z - pos1.z >= 1 then
 | 
			
		||||
		axis = "z"
 | 
			
		||||
	else
 | 
			
		||||
		error("Internal failure: area too small to split")
 | 
			
		||||
	end
 | 
			
		||||
	local hspan = math.floor((pos2[axis] - pos1[axis] + 1) / 2)
 | 
			
		||||
	local half1 = vecw(axis, pos1[axis] + hspan - 1, pos2)
 | 
			
		||||
	local half2 = vecw(axis, pos1[axis] + hspan, pos2)
 | 
			
		||||
	return half1, half2
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
---------------------
 | 
			
		||||
-- Checks
 | 
			
		||||
---------------------
 | 
			
		||||
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
 | 
			
		||||
		nodes = { nodes }
 | 
			
		||||
	end
 | 
			
		||||
	local _, counts = minetest.find_nodes_in_area(pos1, pos2, nodes)
 | 
			
		||||
	local total = worldedit.volume(pos1, pos2)
 | 
			
		||||
	local sum = 0
 | 
			
		||||
	for _, n in pairs(counts) do
 | 
			
		||||
		sum = sum + n
 | 
			
		||||
	end
 | 
			
		||||
	if sum ~= total then
 | 
			
		||||
		error((total - sum) .. " " .. table.concat(nodes, ",") .. " nodes missing in " ..
 | 
			
		||||
			pos2str(pos1) .. " -> " .. pos2str(pos2))
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
-- Check that none of the nodes in [pos1, pos2] are the node(s) specified
 | 
			
		||||
check.not_filled = function(pos1, pos2, nodes)
 | 
			
		||||
	if type(nodes) == "string" then
 | 
			
		||||
		nodes = { nodes }
 | 
			
		||||
	end
 | 
			
		||||
	local _, counts = minetest.find_nodes_in_area(pos1, pos2, nodes)
 | 
			
		||||
	for nodename, n in pairs(counts) do
 | 
			
		||||
		if n ~= 0 then
 | 
			
		||||
			error(counts[nodename] .. " " .. nodename .. " nodes found in " ..
 | 
			
		||||
				pos2str(pos1) .. " -> " .. pos2str(pos2))
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
-- Check that all of the areas are only made of node(s) specified
 | 
			
		||||
check.filled2 = function(list, nodes)
 | 
			
		||||
	for _, pos in ipairs(list) do
 | 
			
		||||
		check.filled(pos[1], pos[2], nodes)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
-- Check that none of the areas contain the node(s) specified
 | 
			
		||||
check.not_filled2 = function(list, nodes)
 | 
			
		||||
	for _, pos in ipairs(list) do
 | 
			
		||||
		check.not_filled(pos[1], pos[2], nodes)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
-- Checks presence of a repeating pattern in [pos1, po2] (cf. place_pattern)
 | 
			
		||||
check.pattern = function(pos1, pos2, pattern)
 | 
			
		||||
	local pos = vec()
 | 
			
		||||
	local i = 1
 | 
			
		||||
	for z = pos1.z, pos2.z do
 | 
			
		||||
		pos.z = z
 | 
			
		||||
	for y = pos1.y, pos2.y do
 | 
			
		||||
		pos.y = y
 | 
			
		||||
	for x = pos1.x, pos2.x do
 | 
			
		||||
		pos.x = x
 | 
			
		||||
		local node = get_node(pos)
 | 
			
		||||
		if node.name ~= pattern[i] then
 | 
			
		||||
			error(pattern[i] .. " not found at " .. pos2str(pos) .. " (i=" .. i .. ")")
 | 
			
		||||
		end
 | 
			
		||||
		i = i % #pattern + 1
 | 
			
		||||
	end
 | 
			
		||||
	end
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
---------------------
 | 
			
		||||
-- The actual tests
 | 
			
		||||
---------------------
 | 
			
		||||
local tests = {}
 | 
			
		||||
worldedit.register_test = function(name, func, opts)
 | 
			
		||||
	assert(type(name) == "string")
 | 
			
		||||
	assert(func == nil or type(func) == "function")
 | 
			
		||||
	if not opts then
 | 
			
		||||
		opts = {}
 | 
			
		||||
	else
 | 
			
		||||
		opts = table.copy(opts)
 | 
			
		||||
	end
 | 
			
		||||
	opts.name = name
 | 
			
		||||
	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
 | 
			
		||||
--   The basic structure is: get areas + do operations + check results
 | 
			
		||||
-- Helpers:
 | 
			
		||||
--   area.get must be used to retrieve areas that can be operated on (these will be cleared before each test)
 | 
			
		||||
--   check.filled / check.not_filled can be used to check the result
 | 
			
		||||
--   area.margin + check.filled2 is useful to make sure nodes weren't placed too far
 | 
			
		||||
--   place_pattern + check.pattern is useful to test ops that operate on existing data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_test("Internal self-test")
 | 
			
		||||
register_test("is area loaded?", function()
 | 
			
		||||
	local pos1, _ = area.get(1)
 | 
			
		||||
	assert(get_node(pos1).name == air)
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
register_test("area.split", function()
 | 
			
		||||
	for i = 2, 6 do
 | 
			
		||||
		local pos1, pos2 = area.get(1, 1, i)
 | 
			
		||||
		local half1, half2 = area.split(pos1, pos2)
 | 
			
		||||
		assert(pos1.x == half1.x and pos1.y == half1.y)
 | 
			
		||||
		assert(half1.x == half2.x and half1.y == half2.y)
 | 
			
		||||
		assert(half1.z + 1 == half2.z)
 | 
			
		||||
		if i % 2 == 0 then
 | 
			
		||||
			assert((half1.z - pos1.z) == (pos2.z - half2.z)) -- divided equally
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
register_test("check.filled", function()
 | 
			
		||||
	local pos1, pos2 = area.get(1, 2, 1)
 | 
			
		||||
	set_node(pos1, {name=testnode1})
 | 
			
		||||
	set_node(pos2, {name=testnode2})
 | 
			
		||||
	check.filled(pos1, pos1, testnode1)
 | 
			
		||||
	check.filled(pos1, pos2, {testnode1, testnode2})
 | 
			
		||||
	check.not_filled(pos1, pos1, air)
 | 
			
		||||
	check.not_filled(pos1, pos2, {air, testnode3})
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
register_test("pattern", function()
 | 
			
		||||
	local pos1, pos2 = area.get(3, 2, 1)
 | 
			
		||||
	local pattern = {testnode1, testnode3}
 | 
			
		||||
	place_pattern(pos1, pos2, pattern)
 | 
			
		||||
	assert(get_node(pos1).name == testnode1)
 | 
			
		||||
	check.pattern(pos1, pos2, pattern)
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
for _, name in ipairs({
 | 
			
		||||
	"manipulations", "primitives", "schematic"
 | 
			
		||||
}) do
 | 
			
		||||
	dofile(minetest.get_modpath("worldedit") .. "/test/" .. name .. ".lua")
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register_test("Code")
 | 
			
		||||
register_test("worldedit.lua", function()
 | 
			
		||||
	-- syntax error
 | 
			
		||||
	local err, ret = worldedit.lua("?")
 | 
			
		||||
	assert(ret == nil)
 | 
			
		||||
	assert(err:find("unexpected symbol"))
 | 
			
		||||
 | 
			
		||||
	-- runtime error
 | 
			
		||||
	local err, ret = worldedit.lua("error(1234)")
 | 
			
		||||
	assert(ret == nil)
 | 
			
		||||
	assert(err:find("1234"))
 | 
			
		||||
 | 
			
		||||
	-- normal operation
 | 
			
		||||
	local err, ret = worldedit.lua("return name..tostring(player == nil)..tostring(pos == nil)", "nobody")
 | 
			
		||||
	assert(err == nil)
 | 
			
		||||
	assert(ret == "\"nobodytruetrue\"")
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
register_test("worldedit.luatransform", function()
 | 
			
		||||
	local pos1, pos2 = area.get(2)
 | 
			
		||||
 | 
			
		||||
	-- syntax error
 | 
			
		||||
	local err = worldedit.luatransform(pos1, pos2, "?")
 | 
			
		||||
	assert(err:find("unexpected symbol"))
 | 
			
		||||
 | 
			
		||||
	-- runtime error
 | 
			
		||||
	local err = worldedit.luatransform(pos1, pos2, "error(2345)")
 | 
			
		||||
	assert(err:find("2345"))
 | 
			
		||||
 | 
			
		||||
	-- normal operation
 | 
			
		||||
	local err = worldedit.luatransform(pos1, pos2,
 | 
			
		||||
		"minetest.swap_node(pos, {name=" .. ("%q"):format(testnode1) .. "})")
 | 
			
		||||
	assert(err == nil)
 | 
			
		||||
	check.filled(pos1, pos1, testnode1)
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
---------------------
 | 
			
		||||
-- Main function
 | 
			
		||||
---------------------
 | 
			
		||||
worldedit.run_tests = function()
 | 
			
		||||
	do
 | 
			
		||||
		local v = minetest.get_version()
 | 
			
		||||
		print("Running " .. #tests .. " tests for WorldEdit " ..
 | 
			
		||||
			worldedit.version_string .. " on " .. v.project .. " " .. (v.hash or v.string))
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	init_nodes()
 | 
			
		||||
 | 
			
		||||
	-- emerge area from (0,0,0) ~ (56,56,56) and keep it loaded
 | 
			
		||||
	-- Note: making this area smaller speeds up tests
 | 
			
		||||
	local wanted = vec(56, 56, 56)
 | 
			
		||||
	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(vec(x*16, y*16, z*16), true, -1))
 | 
			
		||||
	end
 | 
			
		||||
	end
 | 
			
		||||
	end
 | 
			
		||||
	area.assign(vec(0, 0, 0), wanted, function()
 | 
			
		||||
 | 
			
		||||
		local failed = 0
 | 
			
		||||
		for _, test in ipairs(tests) do
 | 
			
		||||
			if not test.func then
 | 
			
		||||
				local s = "---- " .. test.name .. " "
 | 
			
		||||
				print(s .. string.rep("-", 60 - #s))
 | 
			
		||||
			else
 | 
			
		||||
				area.clear()
 | 
			
		||||
				local ok, err = pcall(test.func)
 | 
			
		||||
				print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL"))
 | 
			
		||||
				if not ok then
 | 
			
		||||
					print("   " .. err)
 | 
			
		||||
					failed = failed + 1
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		print("Done, " .. failed .. " tests failed.")
 | 
			
		||||
		if failed == 0 then
 | 
			
		||||
			io.close(io.open(minetest.get_worldpath() .. "/tests_ok", "w"))
 | 
			
		||||
		end
 | 
			
		||||
		minetest.request_shutdown()
 | 
			
		||||
	end)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
-- for debug purposes
 | 
			
		||||
minetest.register_on_joinplayer(function(player)
 | 
			
		||||
	minetest.set_player_privs(player:get_player_name(),
 | 
			
		||||
		minetest.string_to_privs("fly,fast,noclip,basic_debug,debug,interact"))
 | 
			
		||||
end)
 | 
			
		||||
minetest.register_on_punchnode(function(pos, node, puncher)
 | 
			
		||||
	minetest.chat_send_player(puncher:get_player_name(), pos2str(pos))
 | 
			
		||||
end)
 | 
			
		||||
							
								
								
									
										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)
 | 
			
		||||
							
								
								
									
										59
									
								
								worldedit/test/primitives.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,59 @@
 | 
			
		||||
---------------------
 | 
			
		||||
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("Primitives")
 | 
			
		||||
worldedit.register_test("worldedit.cube", function()
 | 
			
		||||
	local pos1, pos2 = area.get(6, 5, 4)
 | 
			
		||||
	local m = area.margin(1)
 | 
			
		||||
 | 
			
		||||
	local center = vec(pos1.x + 3, pos1.y, pos1.z + 2)
 | 
			
		||||
 | 
			
		||||
	worldedit.cube(center, 6, 5, 4, testnode2)
 | 
			
		||||
 | 
			
		||||
	check.filled(pos1, pos2, testnode2)
 | 
			
		||||
	check.filled2(m, air)
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
worldedit.register_test("worldedit.cube hollow small", function()
 | 
			
		||||
	for n = 1, 2 do
 | 
			
		||||
		local pos1, pos2 = area.get(n)
 | 
			
		||||
		local m = area.margin(1)
 | 
			
		||||
 | 
			
		||||
		local center = vec(pos1.x + math.floor(n/2), pos1.y, pos1.z + math.floor(n/2))
 | 
			
		||||
 | 
			
		||||
		worldedit.cube(center, n, n, n, testnode1, true)
 | 
			
		||||
 | 
			
		||||
		check.filled(pos1, pos2, testnode1) -- filled entirely
 | 
			
		||||
		check.filled2(m, air)
 | 
			
		||||
	end
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
worldedit.register_test("worldedit.cube hollow", function()
 | 
			
		||||
	local pos1, pos2 = area.get(6, 5, 4)
 | 
			
		||||
	local m = area.margin(1)
 | 
			
		||||
 | 
			
		||||
	local center = vec(pos1.x + 3, pos1.y, pos1.z + 2)
 | 
			
		||||
 | 
			
		||||
	worldedit.cube(center, 6, 5, 4, testnode1, true)
 | 
			
		||||
 | 
			
		||||
	check.filled(vector.add(pos1, vec(1,1,1)), vector.subtract(pos2, vec(1,1,1)), air)
 | 
			
		||||
	check.filled2({
 | 
			
		||||
		{ vecw("x", pos2.x, pos1), pos2 },
 | 
			
		||||
		{ vecw("y", pos2.y, pos1), pos2 },
 | 
			
		||||
		{ vecw("z", pos2.z, pos1), pos2 },
 | 
			
		||||
		{ pos1, vecw("x", pos1.x, pos2) },
 | 
			
		||||
		{ pos1, vecw("y", pos1.y, pos2) },
 | 
			
		||||
		{ pos1, vecw("z", pos1.z, pos2) },
 | 
			
		||||
	}, testnode1)
 | 
			
		||||
	check.filled2(m, air)
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 442 B  | 
							
								
								
									
										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
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,4 @@
 | 
			
		||||
if minetest.raycast == nil then
 | 
			
		||||
	error(
 | 
			
		||||
		"worldedit_brush requires at least Minetest 5.0"
 | 
			
		||||
	)
 | 
			
		||||
end
 | 
			
		||||
local S = minetest.get_translator("worldedit_brush")
 | 
			
		||||
 | 
			
		||||
local BRUSH_MAX_DIST = 150
 | 
			
		||||
local brush_on_use = function(itemstack, placer)
 | 
			
		||||
@@ -12,7 +8,8 @@ local brush_on_use = function(itemstack, placer)
 | 
			
		||||
	local cmd = meta:get_string("command")
 | 
			
		||||
	if cmd == "" then
 | 
			
		||||
		worldedit.player_notify(name,
 | 
			
		||||
			"This brush is not bound, use //brush to bind a command to it.")
 | 
			
		||||
			S("This brush is not bound, use @1 to bind a command to it.",
 | 
			
		||||
			minetest.colorize("#00ffff", "//brush")), "info")
 | 
			
		||||
		return false
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
@@ -22,17 +19,17 @@ local brush_on_use = function(itemstack, placer)
 | 
			
		||||
	local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs)
 | 
			
		||||
	if not has_privs then
 | 
			
		||||
		worldedit.player_notify(name,
 | 
			
		||||
			"Missing privileges: " .. table.concat(missing_privs, ", "))
 | 
			
		||||
			S("Missing privileges: @1", table.concat(missing_privs, ", ")), "error")
 | 
			
		||||
		return false
 | 
			
		||||
	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()
 | 
			
		||||
	if pointed_thing == nil then
 | 
			
		||||
		worldedit.player_notify(name, "Too far away.")
 | 
			
		||||
		worldedit.player_notify(name, S("Too far away."), "error")
 | 
			
		||||
		return false
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
@@ -41,17 +38,17 @@ local brush_on_use = function(itemstack, placer)
 | 
			
		||||
	worldedit.pos2[name] = nil
 | 
			
		||||
	worldedit.marker_update(name)
 | 
			
		||||
 | 
			
		||||
	-- this isn't really clean...
 | 
			
		||||
	local player_notify_old = worldedit.player_notify
 | 
			
		||||
	worldedit.player_notify = function(name, msg)
 | 
			
		||||
		if string.match(msg, "^%d") then return end -- discard "1234 nodes added."
 | 
			
		||||
		return player_notify_old(name, msg)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	assert(cmddef.require_pos < 2)
 | 
			
		||||
	local parsed = {cmddef.parse(meta:get_string("params"))}
 | 
			
		||||
	if not table.remove(parsed, 1) then return false end -- shouldn't happen
 | 
			
		||||
 | 
			
		||||
	-- discard success messages
 | 
			
		||||
	local player_notify_old = worldedit.player_notify
 | 
			
		||||
	worldedit.player_notify = function(name, msg, typ)
 | 
			
		||||
		if typ == "ok" then return end
 | 
			
		||||
		return player_notify_old(name, msg, typ)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	minetest.log("action", string.format("%s uses WorldEdit brush (//%s) at %s",
 | 
			
		||||
		name, cmd, minetest.pos_to_string(pointed_thing.under)))
 | 
			
		||||
	cmddef.func(name, unpack(parsed))
 | 
			
		||||
@@ -61,7 +58,7 @@ local brush_on_use = function(itemstack, placer)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
minetest.register_tool(":worldedit:brush", {
 | 
			
		||||
	description = "WorldEdit Brush",
 | 
			
		||||
	description = S("WorldEdit Brush"),
 | 
			
		||||
	inventory_image = "worldedit_brush.png",
 | 
			
		||||
	stack_max = 1, -- no need to stack these (metadata prevents this anyway)
 | 
			
		||||
	range = 0,
 | 
			
		||||
@@ -74,7 +71,7 @@ minetest.register_tool(":worldedit:brush", {
 | 
			
		||||
worldedit.register_command("brush", {
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	params = "none/<cmd> [parameters]",
 | 
			
		||||
	description = "Assign command to WorldEdit brush item",
 | 
			
		||||
	description = S("Assign command to WorldEdit brush item or clear assignment using 'none'"),
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, cmd, params = param:find("^([^%s]+)%s+(.+)$")
 | 
			
		||||
		if not found then
 | 
			
		||||
@@ -87,39 +84,39 @@ worldedit.register_command("brush", {
 | 
			
		||||
		return true, cmd, params
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, cmd, params)
 | 
			
		||||
		local itemstack = minetest.get_player_by_name(name):get_wielded_item()
 | 
			
		||||
		local player = minetest.get_player_by_name(name)
 | 
			
		||||
		if not player then return end
 | 
			
		||||
		local itemstack = player:get_wielded_item()
 | 
			
		||||
		if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then
 | 
			
		||||
			worldedit.player_notify(name, "Not holding brush item.")
 | 
			
		||||
			return
 | 
			
		||||
			return false, S("Not holding brush item.")
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		cmd = cmd:lower()
 | 
			
		||||
		local meta = itemstack:get_meta()
 | 
			
		||||
		if cmd == "none" then
 | 
			
		||||
			meta:from_table(nil)
 | 
			
		||||
			worldedit.player_notify(name, "Brush assignment cleared.")
 | 
			
		||||
			worldedit.player_notify(name, S("Brush assignment cleared."), "ok")
 | 
			
		||||
		else
 | 
			
		||||
			local cmddef = worldedit.registered_commands[cmd]
 | 
			
		||||
			if cmddef == nil or cmddef.require_pos ~= 1 then
 | 
			
		||||
				worldedit.player_notify(name, "//" .. cmd .. " cannot be used with brushes")
 | 
			
		||||
				return
 | 
			
		||||
				return false, S("@1 cannot be used with brushes",
 | 
			
		||||
					minetest.colorize("#00ffff", "//"..cmd))
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			-- Try parsing command params so we can give the user feedback
 | 
			
		||||
			local ok, err = cmddef.parse(params)
 | 
			
		||||
			if not ok then
 | 
			
		||||
				err = err or "invalid usage"
 | 
			
		||||
				worldedit.player_notify(name, "Error with brush command: " .. err)
 | 
			
		||||
				return
 | 
			
		||||
				err = err or S("invalid usage")
 | 
			
		||||
				return false, S("Error with command: @1", err)
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			meta:set_string("command", cmd)
 | 
			
		||||
			meta:set_string("params", params)
 | 
			
		||||
			local fullcmd = "//" .. cmd .. " " .. params
 | 
			
		||||
			local fullcmd = minetest.colorize("#00ffff", "//"..cmd) .. " " .. params
 | 
			
		||||
			meta:set_string("description",
 | 
			
		||||
				minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd)
 | 
			
		||||
			worldedit.player_notify(name, "Brush assigned to command: " .. fullcmd)
 | 
			
		||||
			worldedit.player_notify(name, S("Brush assigned to command: @1", fullcmd), "ok")
 | 
			
		||||
		end
 | 
			
		||||
		minetest.get_player_by_name(name):set_wielded_item(itemstack)
 | 
			
		||||
		player:set_wielded_item(itemstack)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								worldedit_brush/locale/template.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
			
		||||
# textdomain: worldedit_brush
 | 
			
		||||
This brush is not bound, use @1 to bind a command to it.=
 | 
			
		||||
Missing privileges: @1=
 | 
			
		||||
Too far away.=
 | 
			
		||||
WorldEdit Brush=
 | 
			
		||||
Assign command to WorldEdit brush item or clear assignment using 'none'=
 | 
			
		||||
Not holding brush item.=
 | 
			
		||||
Brush assignment cleared.=
 | 
			
		||||
@1 cannot be used with brushes=
 | 
			
		||||
invalid usage=
 | 
			
		||||
Error with command: @1=
 | 
			
		||||
Brush assigned to command: @1=
 | 
			
		||||
							
								
								
									
										12
									
								
								worldedit_brush/locale/worldedit_brush.de.tr
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,12 @@
 | 
			
		||||
# textdomain: worldedit_brush
 | 
			
		||||
This brush is not bound, use @1 to bind a command to it.=Dieser Pinsel ist nicht konfiguriert, nutzen Sie @1, um einen Befehl an ihn zu binden.
 | 
			
		||||
Missing privileges: @1=Fehlende Privilegien: @1
 | 
			
		||||
Too far away.=Zu weit weg.
 | 
			
		||||
WorldEdit Brush=WorldEdit-Pinsel
 | 
			
		||||
Assign command to WorldEdit brush item or clear assignment using 'none'=Befehl an WorldEdit-Pinsel binden oder Zuweisung mittels „none“ aufheben
 | 
			
		||||
Not holding brush item.=Kein Pinsel in der Hand.
 | 
			
		||||
Brush assignment cleared.=Pinsel-Zuweisung aufgehoben.
 | 
			
		||||
@1 cannot be used with brushes=@1 kann nicht mit Pinseln verwendet werden
 | 
			
		||||
invalid usage=Ungültige Verwendung
 | 
			
		||||
Error with command: @1=Befehl ist fehlerhaft: @1
 | 
			
		||||
Brush assigned to command: @1=Pinsel an Befehl gebunden: @1
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 301 B  | 
							
								
								
									
										1
									
								
								worldedit_commands/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1 +0,0 @@
 | 
			
		||||
*~
 | 
			
		||||
							
								
								
									
										59
									
								
								worldedit_commands/code.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,59 @@
 | 
			
		||||
local S = minetest.get_translator("worldedit_commands")
 | 
			
		||||
 | 
			
		||||
local function check_region(name)
 | 
			
		||||
	return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("lua", {
 | 
			
		||||
	params = "<code>",
 | 
			
		||||
	description = S("Executes <code> as a Lua chunk in the global namespace"),
 | 
			
		||||
	category = S("Code"),
 | 
			
		||||
	privs = {worldedit=true, server=true},
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		if param == "" then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		return true, param
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, param)
 | 
			
		||||
		-- shorthand like in the Lua interpreter
 | 
			
		||||
		if param:sub(1, 1) == "=" then
 | 
			
		||||
			param = "return " .. param:sub(2)
 | 
			
		||||
		end
 | 
			
		||||
		local err, ret = worldedit.lua(param, name)
 | 
			
		||||
		if err == nil then
 | 
			
		||||
			minetest.log("action", name .. " executed " .. param)
 | 
			
		||||
			if ret ~= "nil" then
 | 
			
		||||
				worldedit.player_notify(name, "code successfully executed, returned " .. ret, "info")
 | 
			
		||||
			else
 | 
			
		||||
				worldedit.player_notify(name, "code successfully executed", "ok")
 | 
			
		||||
			end
 | 
			
		||||
		else
 | 
			
		||||
			minetest.log("action", name .. " tried to execute " .. param)
 | 
			
		||||
			worldedit.player_notify(name, "code error: " .. err, "error")
 | 
			
		||||
		end
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("luatransform", {
 | 
			
		||||
	params = "<code>",
 | 
			
		||||
	description = S("Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region"),
 | 
			
		||||
	category = S("Code"),
 | 
			
		||||
	privs = {worldedit=true, server=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		return true, param
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, param)
 | 
			
		||||
		local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)
 | 
			
		||||
		if err then
 | 
			
		||||
			worldedit.player_notify(name, "code error: " .. err, "error")
 | 
			
		||||
			minetest.log("action", name.." tried to execute luatransform "..param)
 | 
			
		||||
		else
 | 
			
		||||
			worldedit.player_notify(name, "code successfully executed", "ok")
 | 
			
		||||
			minetest.log("action", name.." executed luatransform "..param)
 | 
			
		||||
		end
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
local S = minetest.get_translator("worldedit_commands")
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("outset", {
 | 
			
		||||
	params = "[h/v] <amount>",
 | 
			
		||||
	description = "Outset the selected region.",
 | 
			
		||||
	description = S("Outset the selected region."),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
@@ -11,7 +14,7 @@ worldedit.register_command("outset", {
 | 
			
		||||
 | 
			
		||||
		local hv_test = dir:find("[^hv]+")
 | 
			
		||||
		if hv_test ~= nil then
 | 
			
		||||
			return false, "Invalid direction."
 | 
			
		||||
			return false, S("Invalid direction: @1", dir)
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		return true, dir, tonumber(amount)
 | 
			
		||||
@@ -28,18 +31,19 @@ worldedit.register_command("outset", {
 | 
			
		||||
			assert(worldedit.cuboid_linear_expand(name, 'y', 1, amount))
 | 
			
		||||
			assert(worldedit.cuboid_linear_expand(name, 'y', -1, amount))
 | 
			
		||||
		else
 | 
			
		||||
			return false, "Invalid number of arguments"
 | 
			
		||||
			return false, S("Invalid number of arguments")
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
		return true, "Region outset by " .. amount .. " blocks"
 | 
			
		||||
		return true, S("Region outset by @1 nodes", amount)
 | 
			
		||||
      end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("inset", {
 | 
			
		||||
	params = "[h/v] <amount>",
 | 
			
		||||
	description = "Inset the selected region.",
 | 
			
		||||
	description = S("Inset the selected region."),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
@@ -48,7 +52,7 @@ worldedit.register_command("inset", {
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		if dir:find("[^hv]") ~= nil then
 | 
			
		||||
			return false, "Invalid direction."
 | 
			
		||||
			return false, S("Invalid direction: @1", dir)
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		return true, dir, tonumber(amount)
 | 
			
		||||
@@ -65,18 +69,19 @@ worldedit.register_command("inset", {
 | 
			
		||||
			assert(worldedit.cuboid_linear_expand(name, 'y', 1, -amount))
 | 
			
		||||
			assert(worldedit.cuboid_linear_expand(name, 'y', -1, -amount))
 | 
			
		||||
		else
 | 
			
		||||
			return false, "Invalid number of arguments"
 | 
			
		||||
			return false, S("Invalid number of arguments")
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
		return true, "Region inset by " .. amount .. " blocks"
 | 
			
		||||
		return true, S("Region inset by @1 nodes", amount)
 | 
			
		||||
      end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("shift", {
 | 
			
		||||
	params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>",
 | 
			
		||||
	description = "Shifts the selection area without moving its contents",
 | 
			
		||||
	description = S("Shifts the selection area without moving its contents"),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
@@ -98,20 +103,21 @@ worldedit.register_command("shift", {
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		if axis == nil or dir == nil then
 | 
			
		||||
			return false, "Invalid if looking straight up or down"
 | 
			
		||||
			return false, S("Invalid if looking straight up or down")
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		assert(worldedit.cuboid_shift(name, axis, amount * dir))
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
 | 
			
		||||
		return true, "Region shifted by " .. amount .. " nodes"
 | 
			
		||||
		return true, S("Region shifted by @1 nodes", amount)
 | 
			
		||||
      end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("expand", {
 | 
			
		||||
	params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
 | 
			
		||||
	description = "Expands the selection in the selected absolute or relative axis",
 | 
			
		||||
	description = S("Expands the selection in the selected absolute or relative axis"),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
@@ -135,7 +141,7 @@ worldedit.register_command("expand", {
 | 
			
		||||
			axis, dir = worldedit.translate_direction(name, direction)
 | 
			
		||||
 | 
			
		||||
			if axis == nil or dir == nil then
 | 
			
		||||
				return false, "Invalid if looking straight up or down"
 | 
			
		||||
				return false, S("Invalid if looking straight up or down")
 | 
			
		||||
			end
 | 
			
		||||
		else
 | 
			
		||||
			if direction == "?" then
 | 
			
		||||
@@ -153,14 +159,15 @@ worldedit.register_command("expand", {
 | 
			
		||||
		worldedit.cuboid_linear_expand(name, axis, dir, amount)
 | 
			
		||||
		worldedit.cuboid_linear_expand(name, axis, -dir, rev_amount)
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
		return true, "Region expanded by " .. (amount + rev_amount) .. " nodes"
 | 
			
		||||
		return true, S("Region expanded by @1 nodes", amount + rev_amount)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("contract", {
 | 
			
		||||
	params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
 | 
			
		||||
	description = "Contracts the selection in the selected absolute or relative axis",
 | 
			
		||||
	description = S("Contracts the selection in the selected absolute or relative axis"),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
@@ -184,7 +191,7 @@ worldedit.register_command("contract", {
 | 
			
		||||
			axis, dir = worldedit.translate_direction(name, direction)
 | 
			
		||||
 | 
			
		||||
			if axis == nil or dir == nil then
 | 
			
		||||
				return false, "Invalid if looking straight up or down"
 | 
			
		||||
				return false, S("Invalid if looking straight up or down")
 | 
			
		||||
			end
 | 
			
		||||
		else
 | 
			
		||||
			if direction == "?" then
 | 
			
		||||
@@ -202,13 +209,13 @@ worldedit.register_command("contract", {
 | 
			
		||||
		worldedit.cuboid_linear_expand(name, axis, dir, -amount)
 | 
			
		||||
		worldedit.cuboid_linear_expand(name, axis, -dir, -rev_amount)
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
		return true, "Region contracted by " .. (amount + rev_amount) .. " nodes"
 | 
			
		||||
		return true, S("Region contracted by @1 nodes", amount + rev_amount)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("cubeapply", {
 | 
			
		||||
	params = "<size>/(<sizex> <sizey> <sizez>) <command> [parameters]",
 | 
			
		||||
	description = "Select a cube with side length <size> around position 1 and run <command> on region",
 | 
			
		||||
	description = S("Select a cube with side length <size> around position 1 and run <command> on region"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
@@ -230,7 +237,8 @@ worldedit.register_command("cubeapply", {
 | 
			
		||||
		end
 | 
			
		||||
		local cmddef = worldedit.registered_commands[cmd]
 | 
			
		||||
		if cmddef == nil or cmddef.require_pos ~= 2 then
 | 
			
		||||
			return false, "invalid usage: //" .. cmd .. " cannot be used with cubeapply"
 | 
			
		||||
			return false, S("invalid usage: @1 cannot be used with cubeapply",
 | 
			
		||||
				minetest.colorize("#00ffff", "//"..cmd))
 | 
			
		||||
		end
 | 
			
		||||
		-- run parsing of target command
 | 
			
		||||
		local parsed = {cmddef.parse(args)}
 | 
			
		||||
@@ -241,15 +249,14 @@ worldedit.register_command("cubeapply", {
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = function(name, sidex, sidey, sidez, cmd, parsed)
 | 
			
		||||
		-- its not possible to defer to the target command at this point
 | 
			
		||||
		-- FIXME: why not?
 | 
			
		||||
		return sidex * sidey * sidez
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, sidex, sidey, sidez, cmd, parsed)
 | 
			
		||||
		local cmddef = assert(worldedit.registered_commands[cmd])
 | 
			
		||||
		local success, missing_privs = minetest.check_player_privs(name, cmddef.privs)
 | 
			
		||||
		if not success then
 | 
			
		||||
			worldedit.player_notify(name, "Missing privileges: " ..
 | 
			
		||||
				table.concat(missing_privs, ", "))
 | 
			
		||||
			return
 | 
			
		||||
			return false, S("Missing privileges: @1", table.concat(missing_privs, ", "))
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		-- update region to be the cuboid the user wanted
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										153
									
								
								worldedit_commands/locale/template.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,153 @@
 | 
			
		||||
# textdomain: worldedit_commands
 | 
			
		||||
Outset the selected region.=
 | 
			
		||||
Invalid direction: @1=
 | 
			
		||||
Invalid number of arguments=
 | 
			
		||||
Region outset by @1 nodes=
 | 
			
		||||
Inset the selected region.=
 | 
			
		||||
Region inset by @1 nodes=
 | 
			
		||||
Shifts the selection area without moving its contents=
 | 
			
		||||
Invalid if looking straight up or down=
 | 
			
		||||
Region shifted by @1 nodes=
 | 
			
		||||
Expands the selection in the selected absolute or relative axis=
 | 
			
		||||
Region expanded by @1 nodes=
 | 
			
		||||
Contracts the selection in the selected absolute or relative axis=
 | 
			
		||||
Region contracted by @1 nodes=
 | 
			
		||||
Select a cube with side length <size> around position 1 and run <command> on region=
 | 
			
		||||
invalid usage: @1 cannot be used with cubeapply=
 | 
			
		||||
Missing privileges: @1=
 | 
			
		||||
Region operations=
 | 
			
		||||
Can use WorldEdit commands=
 | 
			
		||||
no region selected=
 | 
			
		||||
no position 1 selected=
 | 
			
		||||
invalid usage=
 | 
			
		||||
ERROR: the operation was cancelled because the region has changed.=
 | 
			
		||||
Could not open file "@1"=
 | 
			
		||||
Invalid file format!=
 | 
			
		||||
Schematic was created with a newer version of WorldEdit.=
 | 
			
		||||
Get information about the WorldEdit mod=
 | 
			
		||||
WorldEdit @1 is available on this server. Type @2 to get a list of commands, or find more information at @3=
 | 
			
		||||
Get help for WorldEdit commands=
 | 
			
		||||
alias to @1=
 | 
			
		||||
You are not allowed to use any WorldEdit commands.=
 | 
			
		||||
Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=
 | 
			
		||||
Available commands:@n=
 | 
			
		||||
Command not available: =
 | 
			
		||||
Enable or disable node inspection=
 | 
			
		||||
inspector: inspection enabled for @1, currently facing the @2 axis=
 | 
			
		||||
inspector: inspection disabled=
 | 
			
		||||
inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=
 | 
			
		||||
Reset the region so that it is empty=
 | 
			
		||||
region reset=
 | 
			
		||||
Show markers at the region positions=
 | 
			
		||||
region marked=
 | 
			
		||||
Hide markers if currently shown=
 | 
			
		||||
region unmarked=
 | 
			
		||||
Set WorldEdit region position @1 to the player's location=
 | 
			
		||||
position @1 set to @2=
 | 
			
		||||
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region=
 | 
			
		||||
unknown subcommand: @1=
 | 
			
		||||
select positions by punching two nodes=
 | 
			
		||||
select position @1 by punching a node=
 | 
			
		||||
position @1: @2=
 | 
			
		||||
position @1 not set=
 | 
			
		||||
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=
 | 
			
		||||
Display the volume of the current WorldEdit region=
 | 
			
		||||
current region has a volume of @1 nodes (@2*@3*@4)=
 | 
			
		||||
Remove all MapBlocks (16x16x16) containing the selected area from the map=
 | 
			
		||||
Node manipulation=
 | 
			
		||||
Area deleted.=
 | 
			
		||||
There was an error during deletion of the area.=
 | 
			
		||||
Set the current WorldEdit region to <node>=
 | 
			
		||||
invalid node name: @1=
 | 
			
		||||
@1 nodes set=
 | 
			
		||||
Set param2 of all nodes in the current WorldEdit region to <param2>=
 | 
			
		||||
Param2 is out of range (must be between 0 and 255 inclusive!)=
 | 
			
		||||
@1 nodes altered=
 | 
			
		||||
Fill the current WorldEdit region with a random mix of <node1>, ...=
 | 
			
		||||
invalid search node name: @1=
 | 
			
		||||
invalid replace node name: @1=
 | 
			
		||||
Replace all instances of <search node> with <replace node> in the current WorldEdit region=
 | 
			
		||||
@1 nodes replaced=
 | 
			
		||||
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=
 | 
			
		||||
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
 | 
			
		||||
Shapes=
 | 
			
		||||
@1 nodes added=
 | 
			
		||||
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
 | 
			
		||||
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
 | 
			
		||||
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
 | 
			
		||||
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
 | 
			
		||||
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
 | 
			
		||||
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
 | 
			
		||||
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
 | 
			
		||||
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
 | 
			
		||||
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
 | 
			
		||||
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=
 | 
			
		||||
Copy the current WorldEdit region along the given axis by <amount> nodes=
 | 
			
		||||
Transformations=
 | 
			
		||||
@1 nodes copied=
 | 
			
		||||
Move the current WorldEdit region along the given axis by <amount> nodes=
 | 
			
		||||
@1 nodes moved=
 | 
			
		||||
Stack the current WorldEdit region along the given axis <count> times=
 | 
			
		||||
@1 nodes stacked=
 | 
			
		||||
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=
 | 
			
		||||
invalid count: @1=
 | 
			
		||||
invalid increments: @1=
 | 
			
		||||
Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=
 | 
			
		||||
invalid scaling factors: @1=
 | 
			
		||||
@1 nodes stretched=
 | 
			
		||||
Transpose the current WorldEdit region along the given axes=
 | 
			
		||||
invalid usage: axes must be different=
 | 
			
		||||
@1 nodes transposed=
 | 
			
		||||
Flip the current WorldEdit region along the given axis=
 | 
			
		||||
@1 nodes flipped=
 | 
			
		||||
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=
 | 
			
		||||
invalid usage: angle must be multiple of 90=
 | 
			
		||||
@1 nodes rotated=
 | 
			
		||||
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=
 | 
			
		||||
@1 nodes oriented=
 | 
			
		||||
Fix the lighting in the current WorldEdit region=
 | 
			
		||||
@1 nodes updated=
 | 
			
		||||
Remove any fluid node within the current WorldEdit region=
 | 
			
		||||
Remove any plant, tree or foliage-like nodes in the selected region=
 | 
			
		||||
@1 nodes removed=
 | 
			
		||||
Hide all nodes in the current WorldEdit region non-destructively=
 | 
			
		||||
@1 nodes hidden=
 | 
			
		||||
Suppress all <node> in the current WorldEdit region non-destructively=
 | 
			
		||||
@1 nodes suppressed=
 | 
			
		||||
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=
 | 
			
		||||
@1 nodes highlighted=
 | 
			
		||||
Restores nodes hidden with WorldEdit in the current WorldEdit region=
 | 
			
		||||
@1 nodes restored=
 | 
			
		||||
Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=
 | 
			
		||||
Save the current WorldEdit region to "(world folder)/schems/<file>.we"=
 | 
			
		||||
Schematics=
 | 
			
		||||
Disallowed file name: @1=
 | 
			
		||||
Could not save file to "@1"=
 | 
			
		||||
@1 nodes saved=
 | 
			
		||||
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=
 | 
			
		||||
Schematic empty, nothing allocated=
 | 
			
		||||
@1 nodes allocated=
 | 
			
		||||
Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=
 | 
			
		||||
Loading failed!=
 | 
			
		||||
@1 nodes loaded=
 | 
			
		||||
Executes <code> as a Lua chunk in the global namespace=
 | 
			
		||||
Code=
 | 
			
		||||
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=
 | 
			
		||||
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=
 | 
			
		||||
Failed to create Minetest schematic=
 | 
			
		||||
Saved Minetest schematic to @1=
 | 
			
		||||
Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=
 | 
			
		||||
failed to place Minetest schematic=
 | 
			
		||||
placed Minetest schematic @1 at @2=
 | 
			
		||||
Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=
 | 
			
		||||
select Minetest schematic probability values by punching nodes=
 | 
			
		||||
finished Minetest schematic probability selection=
 | 
			
		||||
currently set node probabilities:=
 | 
			
		||||
invalid node probability given, not saved=
 | 
			
		||||
Clears all objects within the WorldEdit region=
 | 
			
		||||
@1 objects cleared=
 | 
			
		||||
WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=
 | 
			
		||||
Confirm a pending operation=
 | 
			
		||||
no operation pending=
 | 
			
		||||
Abort a pending operation=
 | 
			
		||||
WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=
 | 
			
		||||
							
								
								
									
										164
									
								
								worldedit_commands/locale/worldedit_commands.de.tr
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,164 @@
 | 
			
		||||
# textdomain: worldedit_commands
 | 
			
		||||
##
 | 
			
		||||
# Übersetzungshinweise:
 | 
			
		||||
# node = Block
 | 
			
		||||
# command = Befehl
 | 
			
		||||
# region = Gebiet
 | 
			
		||||
# position 1/2 = Position 1/2
 | 
			
		||||
# schematic = Schematic
 | 
			
		||||
# Der Anwender wird gesiezt.
 | 
			
		||||
# Diese Datei auf Rechtschreibfehler prüfen (unter Linux):
 | 
			
		||||
# sed -rne 's|^.*[^@]=(.*)$|\1|gp' worldedit_commands/locale/worldedit_commands.de.tr | sed -re 's/\b(WorldEdit|Schematic|Minetest)\b/Beispiel/g' | LANG=de_DE.UTF-8 hunspell -L
 | 
			
		||||
##
 | 
			
		||||
Outset the selected region.=Ausgewähltes Gebiet vergrößern.
 | 
			
		||||
Invalid direction: @1=Ungültige Richtung: @1
 | 
			
		||||
Invalid number of arguments=Ungültige Anzahl der Argumente
 | 
			
		||||
Region outset by @1 nodes=Gebiet um @1 Blöcke vergrößert
 | 
			
		||||
Inset the selected region.=Ausgewähltes Gebiet verkleinern.
 | 
			
		||||
Region inset by @1 nodes=Gebiet um @1 Blöcke verkleinert
 | 
			
		||||
Shifts the selection area without moving its contents=Verschiebt das ausgewählte Gebiet ohne dessen Inhalt
 | 
			
		||||
Invalid if looking straight up or down=Nicht zulässig bei gerader Blickrichtung nach unten oder oben
 | 
			
		||||
Region shifted by @1 nodes=Gebiet um @1 Blöcke verschoben
 | 
			
		||||
Expands the selection in the selected absolute or relative axis=Erweitert das Gebiet in Richtung einer absoluten oder relativen Achse
 | 
			
		||||
Region expanded by @1 nodes=Gebiet um @1 Blöcke erweitert
 | 
			
		||||
Contracts the selection in the selected absolute or relative axis=Schrumpft das Gebiet in Richtung einer absoluten oder relativen Achse
 | 
			
		||||
Region contracted by @1 nodes=Gebiet um @1 Blöcke geschrumpft
 | 
			
		||||
Select a cube with side length <size> around position 1 and run <command> on region=Einen Würfel mit Seitenlänge <size> um Position 1 auswählen und <command> auf diesem Gebiet ausführen
 | 
			
		||||
invalid usage: @1 cannot be used with cubeapply=Ungültige Verwendung: @1 kann nicht mit cubeapply verwendet werden
 | 
			
		||||
Missing privileges: @1=Fehlende Privilegien: @1
 | 
			
		||||
Region operations=Gebietsoperationen
 | 
			
		||||
Can use WorldEdit commands=Kann WorldEdit-Befehle benutzen
 | 
			
		||||
no region selected=Kein Gebiet ausgewählt
 | 
			
		||||
no position 1 selected=Keine Position 1 ausgewählt
 | 
			
		||||
invalid usage=Ungültige Verwendung
 | 
			
		||||
ERROR: the operation was cancelled because the region has changed.=FEHLER: Der Vorgang wurde abgebrochen, weil das Gebiet verändert wurde.
 | 
			
		||||
Could not open file "@1"=Konnte die Datei „@1“ nicht öffnen
 | 
			
		||||
Invalid file format!=Ungültiges Dateiformat!
 | 
			
		||||
Schematic was created with a newer version of WorldEdit.=Schematic wurde mit einer neueren Version von WorldEdit erstellt.
 | 
			
		||||
Get information about the WorldEdit mod=Informationen über die WorldEdit-Mod erhalten
 | 
			
		||||
WorldEdit @1 is available on this server. Type @2 to get a list of commands, or find more information at @3=WorldEdit @1 ist auf diesem Server verfügbar. Nutzen Sie @2, um eine Liste der Befehle zu erhalten, oder finden Sie weitere Informationen unter @3
 | 
			
		||||
Get help for WorldEdit commands=Hilfe für WorldEdit-Befehle erhalten
 | 
			
		||||
alias to @1=Alias für @1
 | 
			
		||||
You are not allowed to use any WorldEdit commands.=Ihnen ist nicht erlaubt, WorldEdit-Befehle zu nutzen.
 | 
			
		||||
Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=Verfügbare Befehle: @1@n„@2“ benutzen, um mehr Informationen zu erhalten, oder „@3“, um alles aufzulisten.
 | 
			
		||||
Available commands:@n=Verfügbare Befehle:@n
 | 
			
		||||
Command not available: =Befehl nicht verfügbar: 
 | 
			
		||||
Enable or disable node inspection=Inspizieren von Blöcken ein- oder ausschalten
 | 
			
		||||
inspector: inspection enabled for @1, currently facing the @2 axis=Inspektur: für @1 aktiviert, aktuell der @2-Achse zugewandt
 | 
			
		||||
inspector: inspection disabled=Inspektur: deaktiviert
 | 
			
		||||
inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=Inspektur: @1 bei @2 (param1@=@3, param2@=@4, einfallendes Licht@=@5), der „@6“-Achse zugewandt, gehauen
 | 
			
		||||
Reset the region so that it is empty=Gebiet zurücksetzen, sodass es leer ist
 | 
			
		||||
region reset=Gebiet zurückgesetzt
 | 
			
		||||
Show markers at the region positions=Markierungen bei den Gebietspositionen anzeigen
 | 
			
		||||
region marked=Gebiet markiert
 | 
			
		||||
Hide markers if currently shown=Markierungen verstecken, falls derzeit sichtbar
 | 
			
		||||
region unmarked=Gebiet nicht mehr markiert
 | 
			
		||||
Set WorldEdit region position @1 to the player's location=Position @1 des WorldEdit-Gebiets auf die Position des Spielers setzen
 | 
			
		||||
position @1 set to @2=Position @1 auf @2 gesetzt
 | 
			
		||||
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region=WorldEdit-Gebiet, WorldEdit-Position 1 oder WorldEdit-Position 2 durch Schlagen von Blöcken setzen oder aktuelles WorldEdit-Gebiet anzeigen
 | 
			
		||||
unknown subcommand: @1=Unbekannter Unterbefehl: @1
 | 
			
		||||
select positions by punching two nodes=Wählen Sie die Position durch Hauen zweier Blöcke
 | 
			
		||||
select position @1 by punching a node=Wählen Sie Position @1 durch Hauen eines Blocks
 | 
			
		||||
position @1: @2=Position @1: @2
 | 
			
		||||
position @1 not set=Position @1 ist nicht gesetzt
 | 
			
		||||
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=Eine Position des WorldEdit-Gebiets auf (<x>, <y>, <z>) setzen
 | 
			
		||||
Display the volume of the current WorldEdit region=Volumen des aktuellen WorldEdit-Gebiets anzeigen
 | 
			
		||||
current region has a volume of @1 nodes (@2*@3*@4)=Das aktuelle Gebiet hat ein Volumen von @1 Blöcken (@2×@3×@4)
 | 
			
		||||
Remove all MapBlocks (16x16x16) containing the selected area from the map=Alle Kartenblöcke (16×16×16) entfernen, die das gewählte Gebiet enthalten
 | 
			
		||||
Node manipulation=Block-Manipulation
 | 
			
		||||
Area deleted.=Gebiet gelöscht.
 | 
			
		||||
There was an error during deletion of the area.=Während des Löschens des Gebiets trat ein Fehler auf.
 | 
			
		||||
Set the current WorldEdit region to <node>=Das aktuelle WorldEdit-Gebiet mit <node> füllen
 | 
			
		||||
invalid node name: @1=Ungültiger Blockname: @1
 | 
			
		||||
@1 nodes set=@1 Blöcke gesetzt
 | 
			
		||||
Set param2 of all nodes in the current WorldEdit region to <param2>=Den param2 aller Blocke im aktuellen WorldEdit-Gebiet auf <param2> setzen
 | 
			
		||||
Param2 is out of range (must be between 0 and 255 inclusive!)=Param2 muss zwischen 0 und 255 (inklusiv) sein
 | 
			
		||||
@1 nodes altered=@1 Blöcke verändert
 | 
			
		||||
Fill the current WorldEdit region with a random mix of <node1>, ...=Das aktuelle WorldEdit-Gebiet mit einer zufälligen Mischung aus <node1>, … füllen
 | 
			
		||||
invalid search node name: @1=Ungültiger Blockname für die Suche: @1
 | 
			
		||||
invalid replace node name: @1=Ungültiger Blockname für das Ersetzen: @1
 | 
			
		||||
Replace all instances of <search node> with <replace node> in the current WorldEdit region=Alle Vorkommen von <search node> im aktuellen WorldEdit-Gebiet mit <replace node> ersetzen
 | 
			
		||||
@1 nodes replaced=@1 Blöcke ersetzt
 | 
			
		||||
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=Alle Blöcke außer <search node> im aktuellen WorldEdit-Gebiet mit <replace node> ersetzen
 | 
			
		||||
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Einen hohlen Würfel mit der Unterseite zentriert auf WorldEdit-Position 1 setzen, mit den Abmessungen <width>×<height>×<length>, bestehend aus <node>.
 | 
			
		||||
Shapes=Formen
 | 
			
		||||
@1 nodes added=@1 Blöcke hinzugefügt
 | 
			
		||||
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Einen Würfel mit der Unterseite zentriert auf WorldEdit-Position 1 setzen, mit den Abmessungen <width>×<height>×<length>, bestehend aus <node>.
 | 
			
		||||
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine hohle Kugel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node>
 | 
			
		||||
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine Kugel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node>
 | 
			
		||||
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine hohle Kuppel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node>
 | 
			
		||||
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine Kuppel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node>
 | 
			
		||||
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Einen hohlen Zylinder zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <length>, Bodenradius <radius1> (und oberen Radius [radius2]) setzen, bestehend aus <node>
 | 
			
		||||
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Einen Zylinder zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <length>, Bodenradius <radius1> (und oberen Radius [radius2]) setzen, bestehend aus <node>
 | 
			
		||||
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Eine hohle Pyramide zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <height> setzen, bestehend aus <node>
 | 
			
		||||
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Eine Pyramide zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <height> setzen, bestehend aus <node>
 | 
			
		||||
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=Eine Spirale zentriert auf WorldEdit-Position 1 mit Seitenlänge <length>, Höhe <height>, Wandabstand <space> setzen, bestehend aus <node>
 | 
			
		||||
Copy the current WorldEdit region along the given axis by <amount> nodes=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse um <amount> Blöcke kopieren
 | 
			
		||||
Transformations=Transformationen
 | 
			
		||||
@1 nodes copied=@1 Blöcke kopiert
 | 
			
		||||
Move the current WorldEdit region along the given axis by <amount> nodes=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse um <amount> Blöcke verschieben
 | 
			
		||||
@1 nodes moved=@1 Blöcke verschoben
 | 
			
		||||
Stack the current WorldEdit region along the given axis <count> times=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse <count> mal stapeln
 | 
			
		||||
@1 nodes stacked=@1 Blöcke gestapelt
 | 
			
		||||
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Das aktuelle WorldEdit-Gebiet <count> mal um <x>, <y>, <z> versetzt stapeln
 | 
			
		||||
invalid count: @1=Ungültige Anzahl: @1
 | 
			
		||||
invalid increments: @1=Ungültige Schrittweite: @1
 | 
			
		||||
Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=Die aktuellen WorldEdit-Positionen und -Region um einen Faktor von <stretchx>, <stretchy>, <stretchz> entlang der X-, Y- und Z-Achsen skalieren, mit Position 1 als Ausgangspunkt
 | 
			
		||||
invalid scaling factors: @1=Ungültige Skalierungsfaktoren: @1
 | 
			
		||||
@1 nodes stretched=@1 Blöcke gestreckt
 | 
			
		||||
Transpose the current WorldEdit region along the given axes=Das aktuelle WorldEdit-Gebiet entlang den gegebenen Achsen transponieren
 | 
			
		||||
invalid usage: axes must be different=Ungültige Verwendung: Achsen müssen verschieden sein
 | 
			
		||||
@1 nodes transposed=@1 Blöcke transponiert
 | 
			
		||||
Flip the current WorldEdit region along the given axis=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse spiegeln
 | 
			
		||||
@1 nodes flipped=@1 Blöcke gespiegelt
 | 
			
		||||
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=Das aktuelle WorldEdit-Gebiet um der gegebenen Achse um den Winkel <angle> rotieren (90-Grad-Schritte)
 | 
			
		||||
invalid usage: angle must be multiple of 90=Ungültige Verwendung: Winkel muss ein Vielfaches von 90 sein
 | 
			
		||||
@1 nodes rotated=@1 Blöcke rotiert
 | 
			
		||||
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=Ausgerichtete Blöcke im aktuellen WorldEdit-Gebiet um die Y-Achse mit dem Winkel <angle> rotieren (90-Grad-Schritte)
 | 
			
		||||
@1 nodes oriented=@1 Blöcke ausgerichtet
 | 
			
		||||
Fix the lighting in the current WorldEdit region=Belichtung im aktuellen WorldEdit-Gebiet korrigieren
 | 
			
		||||
@1 nodes updated=@1 Blöcke verändert
 | 
			
		||||
Remove any fluid node within the current WorldEdit region=Alle flüssigen Blöcke im WorldEdit-Gebiet entfernen
 | 
			
		||||
Remove any plant, tree or foliage-like nodes in the selected region=Alle pflanzen-, baum- oder laubähnliche Blöcke im WorldEdit-Gebiet entfernen
 | 
			
		||||
@1 nodes removed=@1 Blöcke entfernt
 | 
			
		||||
Hide all nodes in the current WorldEdit region non-destructively=Alle Blöcke im WorldEdit-Gebiet nicht-destruktiv verstecken
 | 
			
		||||
@1 nodes hidden=@1 Blöcke versteckt
 | 
			
		||||
Suppress all <node> in the current WorldEdit region non-destructively=Alle Blöcke vom Typ <node> in der aktuellen WorldEdit-Region nicht-destruktiv verstecken
 | 
			
		||||
@1 nodes suppressed=@1 Blöcke versteckt
 | 
			
		||||
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Alle Blöcke vom Typ <node> im WorldEdit-Gebiet hervorheben, indem alle anderen nicht-destruktiv versteckt werden
 | 
			
		||||
@1 nodes highlighted=@1 Blöcke hervorgehoben
 | 
			
		||||
Restores nodes hidden with WorldEdit in the current WorldEdit region=Stellt die von WorldEdit versteckten Blöcke im aktuellen WorldEdit-Gebiet wieder her
 | 
			
		||||
@1 nodes restored=@1 Blöcke wiederhergestellt
 | 
			
		||||
Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=Warnung: Das Schematic hat überschüssigen freien Platz und WIRD nach dem Laden falsch ausgerichtet sein. Um dies zu vermeiden, verkleinern Sie das Gebiet, sodass es die zu speichernden Blöcke exakt umfasst.
 | 
			
		||||
Save the current WorldEdit region to "(world folder)/schems/<file>.we"=Das aktuelle WorldEdit-Gebiet in „(Weltordner)/schems/<file>.we“ speichern
 | 
			
		||||
Schematics=Schematic
 | 
			
		||||
Disallowed file name: @1=Nicht erlaubter Dateiname: @1
 | 
			
		||||
Could not save file to "@1"=Konnte die Datei nicht unter „@1“ speichern
 | 
			
		||||
@1 nodes saved=@1 Blöcke gespeichert
 | 
			
		||||
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=Das aktuelle WorldEdit-Gebiet anhand der Blöcke in „(Weltordner)/schems/<file>.we“ setzen
 | 
			
		||||
Schematic empty, nothing allocated=Schematic leer, nichts reserviert
 | 
			
		||||
@1 nodes allocated=@1 Blöcke reserviert
 | 
			
		||||
Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=Blöcke aus „(Weltordner)/schems/<file>[.we[m]]“ mit Position 1 als Ausgangspunkt laden
 | 
			
		||||
Loading failed!=Laden fehlgeschlagen!
 | 
			
		||||
@1 nodes loaded=@1 Blöcke geladen
 | 
			
		||||
Executes <code> as a Lua chunk in the global namespace=<code> als einen Lua-Chunk im globalen Namensraum ausführen
 | 
			
		||||
Code=Code
 | 
			
		||||
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=Führt <code> als einen Lua-Chunk im globalen Namensraum aus, wobei die Variable pos verfügbar ist, für jeden Block im aktuellen WorldEdit-Gebiet
 | 
			
		||||
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=Das aktuelle WorldEdit-Gebiet als Minetest-Schematic in „(Weltordner)/schems/<filename>.mts“ speichern
 | 
			
		||||
Failed to create Minetest schematic=Konnte Minetest-Schematic nicht erstellen
 | 
			
		||||
Saved Minetest schematic to @1=Minetest-Schematic in @1 gespeichert
 | 
			
		||||
Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=Blöcke aus „(Weltordner)/schems/<file>.mts“ mit Position 1 des aktuellen WorldEdit-Gebiets als Ausgangspunkt laden
 | 
			
		||||
failed to place Minetest schematic=Konnte Minetest-Schematic nicht platzieren
 | 
			
		||||
placed Minetest schematic @1 at @2=Minetest-Schematic @1 bei @2 platziert
 | 
			
		||||
Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=Startet die Eingabe von Block-Wahrscheinlichkeiten für Minetest-Schematics, holt die Blöcke, welche Wahrscheinlichkeiten gesetzt haben, oder beendet die Eingabe von Block-Wahrscheinlichkeiten
 | 
			
		||||
select Minetest schematic probability values by punching nodes=Minetest-Schematic-Wahrscheinlichkeitswerte mit Hauen eingeben
 | 
			
		||||
finished Minetest schematic probability selection=Minetest-Schematic-Wahrscheinlichkeitsauswahl abgeschlossen
 | 
			
		||||
currently set node probabilities:=Momentan gesetzte Block-Wahrscheinlichkeiten:
 | 
			
		||||
invalid node probability given, not saved=Ungültige Block-Wahrscheinlichkeit übergeben, nicht gespeichert
 | 
			
		||||
Clears all objects within the WorldEdit region=Löscht alle Objekte innerhalb des WorldEdit-Gebiets
 | 
			
		||||
@1 objects cleared=@1 Objekte gelöscht
 | 
			
		||||
WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=WARNUNG: Dieser Vorgang könnte bis zu @1 Blöcke betreffen; @2 zum Fortfahren oder @3 zum Abbrechen
 | 
			
		||||
Confirm a pending operation=Einen ausstehenden Vorgang bestätigen
 | 
			
		||||
no operation pending=Kein Vorgang ausstehend
 | 
			
		||||
Abort a pending operation=Einen ausstehenden Vorgang abbrechen
 | 
			
		||||
WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=WorldEdit-Zauberstab@nSetzen der 1. Position mit Linksklick, der 2. mit Rechtsklick
 | 
			
		||||
							
								
								
									
										155
									
								
								worldedit_commands/locale/worldedit_commands.ru.tr
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,155 @@
 | 
			
		||||
# textdomain: worldedit_commands
 | 
			
		||||
 | 
			
		||||
### init.lua ###
 | 
			
		||||
Can use WorldEdit commands=Возможность редактировать мир с помощью команд WorldEdit
 | 
			
		||||
 | 
			
		||||
no region selected=не выделен регион
 | 
			
		||||
no position 1 selected=не установлена позиция региона 1
 | 
			
		||||
invalid usage=не верное использование команды
 | 
			
		||||
Could not open file "@1"=Не удаётся открыть файл "@1"
 | 
			
		||||
Invalid file format!=Не верный формат файла!
 | 
			
		||||
Schematic was created with a newer version of WorldEdit.=Схема была создана с использованием более новой версии WorldEdit.
 | 
			
		||||
 | 
			
		||||
Get information about the WorldEdit mod=Вывести информацию о WorldEdit
 | 
			
		||||
WorldEdit @1 is available on this server. Type @2 to get a list of commands, or get more information at @3=WorldEdit @1 доступен на этом сервере. Наберите команду @2 чтобы увидеть список команд, больше информации по ссылке @3
 | 
			
		||||
Get help for WorldEdit commands=Вывести информацию об использовании команд WorldEdit
 | 
			
		||||
You are not allowed to use any WorldEdit commands.=У вас нет привилегий, чтобы использовать команды WorldEdit.
 | 
			
		||||
Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=Доступные команды: @1@nИспользуйте '@2' для получения информации по команде или '@3' для вывода подсказок по всем командам.
 | 
			
		||||
Available commands:@n=Доступные команды:@n
 | 
			
		||||
Command not available: =Команда не найдена:
 | 
			
		||||
Enable or disable node inspection=Включить/отключить инспекцию блоков
 | 
			
		||||
inspector: inspection enabled for @1, currently facing the @2 axis=inspector: инспекция включена для @1, текущий взор в направлении оси @2
 | 
			
		||||
inspector: inspection disabled=inspector: инспекция отключена
 | 
			
		||||
inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=inspector: @1 в @2 (param1@=@3, param2@=@4, received light@=@5) ударен по поверхности @6
 | 
			
		||||
Reset the region so that it is empty=Сбросить выделение области
 | 
			
		||||
region reset=регион сброшен
 | 
			
		||||
Show markers at the region positions=Отобразить маркеры выделенной области
 | 
			
		||||
region marked=маркеры отображены
 | 
			
		||||
Hide markers if currently shown=Скрыть маркеры выделенной области
 | 
			
		||||
region unmarked=маркеры скрыты
 | 
			
		||||
Set WorldEdit region position @1 to the player's location=Установить маркер @1 для WorldEdit-региона в месте нахождения игрока
 | 
			
		||||
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region=Выделить WorldEdit-регион или установить маркеры для WorldEdit-региона, либо отобразить уже выбранную область
 | 
			
		||||
unknown subcommand: @1=неизвестная подкоманда: @1
 | 
			
		||||
select positions by punching two nodes=выберите позиции, ударив по блокам
 | 
			
		||||
select position @1 by punching a node=выберите позицию @1, ударив по блоку
 | 
			
		||||
position @1: @2=позиция @1: @2
 | 
			
		||||
position @1 not set=позиция @1 не установлена
 | 
			
		||||
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=Установить маркер для WorldEdit в позиции (<x>, <y>, <z>)
 | 
			
		||||
position @1 set to @2=позиция @1 установлена в @2
 | 
			
		||||
Display the volume of the current WorldEdit region=Вывести информацию об объёме текущей выделенной области WorldEdit (кол-во нод, размеры)
 | 
			
		||||
current region has a volume of @1 nodes (@2*@3*@4)=текущий регион имеет объем @1 нод (@2*@3*@4)
 | 
			
		||||
Remove all MapBlocks (16x16x16) containing the selected area from the map=Удалить MapBlocks (16x16x16), содержащие выбранную область
 | 
			
		||||
Area deleted.=Область удалена.
 | 
			
		||||
There was an error during deletion of the area.=Что-то пошло не так при удалении области.
 | 
			
		||||
Set the current WorldEdit region to <node>=Заполнить выбранный WorldEdit-регион указанным типом блоков
 | 
			
		||||
invalid node name: @1=неверное название блока: @1
 | 
			
		||||
@1 nodes set=@1 блок(а/ов) установленно
 | 
			
		||||
Set param2 of all nodes in the current WorldEdit region to <param2>=Проставить param2 для всех блоков в текущем WorldEdit-регионе
 | 
			
		||||
Param2 is out of range (must be between 0 and 255 inclusive!)=Значение param2 должно быть от 0 до 255 (включительно!)
 | 
			
		||||
@1 nodes altered=изменено @1 блок(а/ов)
 | 
			
		||||
Fill the current WorldEdit region with a random mix of <node1>, ...=Заполнить выбранный WorldEdit-регион смесью указанных типов блоков
 | 
			
		||||
invalid search node name: @1=неверное название блока-поиска: @1
 | 
			
		||||
invalid replace node name: @1=неверное название блока-замены: @1
 | 
			
		||||
Replace all instances of <search node> with <replace node> in the current WorldEdit region=Заменить все блоки <search node> на <replace node> в выбранной WorldEdit-области
 | 
			
		||||
@1 nodes replaced=заменено @1 нод(а/ы)
 | 
			
		||||
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=Заменить все блоки, кроме <search node>, на <replace node> в выбранной WorldEdit-области
 | 
			
		||||
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Установить полый куб с центром нижней грани в позиции 1 и указанными размерами, состоящий из блоков <node>.
 | 
			
		||||
@1 nodes added=добавлен(о) @1 блок(а/ов)
 | 
			
		||||
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Установить куб с центром нижней грани в позиции 1 и указанными размерами, состоящий из блоков <node>.
 | 
			
		||||
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить полую сферу с центром в WorldEdit-позиции 1 радиусом <radius>, состоящую из блоков <node>
 | 
			
		||||
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить сферу с центром в WorldEdit-позиции 1 радиусом <radius>, состоящую из блоков <node>
 | 
			
		||||
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить полый купол с центром в WorldEdit-позиции 1 радиусом <radius>, состоящий из блоков <node>
 | 
			
		||||
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить купол с центром в WorldEdit-позиции 1 радиусом <radius>, состоящий из блоков <node>
 | 
			
		||||
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Установить полый цилиндр вдоль указанной оси, с центром в позиции 1, высотой/длинной <length>, с радиусом основания <radius> (и радиусом вершины [radius 2]), состоящий из блоков <node>
 | 
			
		||||
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Установить цилиндр вдоль указанной оси, с центром в позиции 1, высотой/длинной <length>, с радиусом основания <radius> (и радиусом вершины [radius 2]), состоящий из блоков <node>
 | 
			
		||||
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Установить полую пирамиду вдоль указанной оси, с центром в WorldEdit-позиции 1 высотой <height>, состоящую из блоков <node>
 | 
			
		||||
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Установить пирамиду вдоль указанной оси, с центром в WorldEdit-позиции 1 высотой <height>, состоящую из блоков <node>
 | 
			
		||||
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=Установить спираль с центром в WorldEdit-позиции 1 шириной <length>, высотой <height> и с расстоянием между витками <space>, состоящую из блоков <node>
 | 
			
		||||
Copy the current WorldEdit region along the given axis by <amount> nodes=Копировать текущий WorldEdit-регион со смещением вдоль указанной оси (x/y/z) на <amount> блоков
 | 
			
		||||
@1 nodes copied=скопировано @1 нод(а/ы)
 | 
			
		||||
Move the current WorldEdit region along the given axis by <amount> nodes=Переместить текущий WorldEdit-регион вдоль указанной оси (x/y/z) на <amount> блоков
 | 
			
		||||
@1 nodes moved=перемещено @1 нод(а/ы)
 | 
			
		||||
Stack the current WorldEdit region along the given axis <count> times=Размножить текущий WorldEdit-регион вдоль указанной оси <count> раз
 | 
			
		||||
@1 nodes stacked=размножено @1 нод(а/ы)
 | 
			
		||||
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Размножить текущий WorldEdit-регион <count> раз с шагом <x>, <y>, <z> по соответствующим осям
 | 
			
		||||
invalid count: @1=неверное количество: @1
 | 
			
		||||
invalid increments: @1=неверные приращения(шаг)
 | 
			
		||||
Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=Масштабировать текущий WorldEdit-регион с коэффициентами <stretchx>, <stretchy>, <stretchz> вдоль осей X, Y и Z, используя WorldEdit-позицию 1 в качестве точки отсчёта
 | 
			
		||||
invalid scaling factors: @1=неверные коэффициенты масштабирования: @1
 | 
			
		||||
@1 nodes stretched=масштабировано @1 нод(а/ы)
 | 
			
		||||
Transpose the current WorldEdit region along the given axes=Транспонировать текущий WorldEdit-регион по заданным осям.
 | 
			
		||||
invalid usage: axes must be different=недопустимое использование: оси должны быть разными
 | 
			
		||||
@1 nodes transposed=транспонировано @1 нод(а/ы)
 | 
			
		||||
Flip the current WorldEdit region along the given axis=Перевернуть/Отразить текущий WorldEdit-регион вдоль указанной оси
 | 
			
		||||
@1 nodes flipped=отражено @1 нод(а/ы)
 | 
			
		||||
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=Повернуть текущий WorldEdit-регион вокруг оси <axis> на угол <angle> (шаг - 90 градусов)
 | 
			
		||||
invalid usage: angle must be multiple of 90=недопустимое использование: угол должен быть кратен 90
 | 
			
		||||
@1 nodes rotated=повёрнуто @1 нод(а/ы)
 | 
			
		||||
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=Повернуть блоки в текущем WorldEdit-регионе вокруг оси Y на угол <angle> (шаг - 90 градусов)
 | 
			
		||||
@1 nodes oriented=повёрнуто @1 нод(а/ы)
 | 
			
		||||
Fix the lighting in the current WorldEdit region=Исправить освещение в текущем WorldEdit-регионе
 | 
			
		||||
@1 nodes updated=обновлено @1 нод(а/ы)
 | 
			
		||||
Remove any fluid node within the current WorldEdit region=Удалить любые жидкости в текущем WorldEdit-регионе
 | 
			
		||||
Remove any plant, tree or foliage-like nodes in the selected region=Удалить любые растения, деревья или листье-подобные ноды в текущем WorldEdit-регионе
 | 
			
		||||
@1 nodes removed=удалено @1 нод(а/ы)
 | 
			
		||||
Hide all nodes in the current WorldEdit region non-destructively=Скрыть узлы текущего WorldEdit-региона, не удаляя их
 | 
			
		||||
@1 nodes hidden=скрыто @1 нод(а/ы)
 | 
			
		||||
Suppress all <node> in the current WorldEdit region non-destructively=Скрыть все блоки <node> в текущем WorldEdit-регионе, не удаляя их
 | 
			
		||||
@1 nodes suppressed=скрыто @1 нод(а/ы)
 | 
			
		||||
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Скрыть все блоки, кроме <node>, в текущем WorldEdit-регионе, не удаляя их
 | 
			
		||||
@1 nodes highlighted="подсвечено" @1 нод(а/ы)
 | 
			
		||||
Restores nodes hidden with WorldEdit in the current WorldEdit region=Восстановить скрытые WorldEdit'ом узлы в текущем WorldEdit-регионе
 | 
			
		||||
@1 nodes restored=восстановлено @1 нод(а/ы)
 | 
			
		||||
Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=Предупреждение: Схема содержит слишком много свободного места и будет смещена при размещении или загрузке. Чтобы избежать этого, уменьшите область так, чтобы она охватывала именно те узлы, которые необходимо сохранить.
 | 
			
		||||
Save the current WorldEdit region to "(world folder)/schems/<file>.we"=Сохранить текущий WorldEdit-регион в файл "(world folder)/schems/<file>.we"
 | 
			
		||||
Disallowed file name: @1=Недопустимое имя файла: @1
 | 
			
		||||
Could not save file to "@1"=Не удалось сохранить файл в "@1"
 | 
			
		||||
@1 nodes saved=сохранено @1 нод(а/ы)
 | 
			
		||||
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=Выделить область, определённую узлами из "(world folder)/schems/<file>.we", как текущий WorldEdit-регион
 | 
			
		||||
Schematic empty, nothing allocated=Схема пуста, ничего не выделено
 | 
			
		||||
@1 nodes allocated=выделено @1 нод(а/ы)
 | 
			
		||||
Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=Загрузить регион из "(world folder)/schems/<file>[.we[m]]" с WorldEdit-позицией 1 в качестве точки отсчёта
 | 
			
		||||
Loading failed!=Не удалось загрузить!
 | 
			
		||||
@1 nodes loaded=загружено @1 нод(а/ы)
 | 
			
		||||
Executes <code> as a Lua chunk in the global namespace=Выполнить <code> как Lua-код в глобальном пространстве имён
 | 
			
		||||
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=Выполнить <code> как Lua-код в глобальном пространстве имён, с доступом к переменным позиций для каждого блока в текущем WordEdit-регионе
 | 
			
		||||
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=Сохранить текущий WorldEdit-регион с использованием сжатия в формат Minetest Schematic в файл "(world folder)/schems/<filename>.mts"
 | 
			
		||||
Failed to create Minetest schematic=Не удалось создать Minetest-схему
 | 
			
		||||
Saved Minetest schematic to @1=Minetest-схема сохранена в @1
 | 
			
		||||
Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=Загрузить блоки из "(world folder)/schems/<file>.mts" с WorldEdit-позицией 1 в качестве точки отсчёта
 | 
			
		||||
failed to place Minetest schematic=не удалось загрузить Minetest-схему
 | 
			
		||||
placed Minetest schematic @1 at @2=Minetest-схема @1 загружена в @2
 | 
			
		||||
Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=Начать запись вероятностей для Minetest Schematic, ударяя по блокам, закончить запись вероятностей или вывести уже записанные вероятности
 | 
			
		||||
select Minetest schematic probability values by punching nodes=выберите значения вероятностей для Minetest-схемы, ударя по нодам
 | 
			
		||||
finished Minetest schematic probability selection=выбор вероятностей для Minetest-схемы завершен
 | 
			
		||||
currently set node probabilities:=заданные вероятности нод на данный момент:
 | 
			
		||||
invalid node probability given, not saved=недопустимая вероятность ноды, не сохранена
 | 
			
		||||
Clears all objects within the WorldEdit region=Очистить все объекты в текущем WorldEdit-регионе
 | 
			
		||||
@1 objects cleared=очищено @1 объектов
 | 
			
		||||
 | 
			
		||||
### safe.lua ###
 | 
			
		||||
WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=ПРЕДУПРЕЖДЕНИЕ: эта операция может затронуть до @1 нод; введите @2 для продолжения или @3 для отмены
 | 
			
		||||
Confirm a pending operation=Подтвердить отложенную операцию
 | 
			
		||||
no operation pending=нет ожидающей операции
 | 
			
		||||
Abort a pending operation=Отклонить отложенную операцию
 | 
			
		||||
 | 
			
		||||
### cuboid.lua ###
 | 
			
		||||
Outset the selected region.=Расширить выделение региона.
 | 
			
		||||
Invalid direction: @1=Недопустимое направление: @1
 | 
			
		||||
Invalid number of arguments=Недопустимое количество аргументов
 | 
			
		||||
Region outset by @1 nodes=Регион расширен на @1 нод(у/ы)
 | 
			
		||||
Inset the selected region.=Сузить выделение региона.
 | 
			
		||||
Region inset by @1 nodes=Регион сужен на @1 нод(у/ы)
 | 
			
		||||
Shifts the selection area without moving its contents=Сдвинуть выделение региона без перемещения его содержимого
 | 
			
		||||
Invalid if looking straight up or down=Недопустимо, если смотреть прямо вверх или вниз
 | 
			
		||||
Region shifted by @1 nodes=Регион сдвинут на @1 нод(у/ы)
 | 
			
		||||
Expands the selection in the selected absolute or relative axis=Увеличить выделение региона по выбранной абсолютной или относительной оси
 | 
			
		||||
Region expanded by @1 nodes=Регион увеличен на @1 нод(у/ы)
 | 
			
		||||
Contracts the selection in the selected absolute or relative axis=Уменьшить выделение региона по выбранной абсолютной или относительной оси
 | 
			
		||||
Region contracted by @1 nodes=Регион уменьшен на @1 нод(у/ы)
 | 
			
		||||
Select a cube with side length <size> around position 1 and run <command> on region=Выделить куб с длиной стороны <size> вокруг позиции 1 и запустите <команду> в области
 | 
			
		||||
invalid usage: @1 cannot be used with cubeapply=недопустимое использование: @1 не может быть применено в cubeapply
 | 
			
		||||
Missing privileges: @1=Отсутствуют привилегии: @1
 | 
			
		||||
 | 
			
		||||
### wand.lua ###
 | 
			
		||||
WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=Инструмент WorldEdit Wand@nЛевая кнопка мыши, чтобы установить 1-ю позицию, правая кнопка мыши, чтобы установить 2-ю
 | 
			
		||||
							
								
								
									
										366
									
								
								worldedit_commands/manipulations.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,366 @@
 | 
			
		||||
local S = minetest.get_translator("worldedit_commands")
 | 
			
		||||
 | 
			
		||||
local function check_region(name)
 | 
			
		||||
	return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("deleteblocks", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Remove all MapBlocks (16x16x16) containing the selected area from the map"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
		local success = minetest.delete_area(pos1, pos2)
 | 
			
		||||
		if success then
 | 
			
		||||
			return true, S("Area deleted.")
 | 
			
		||||
		else
 | 
			
		||||
			return false, S("There was an error during deletion of the area.")
 | 
			
		||||
		end
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("clearobjects", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Clears all objects within the WorldEdit region"),
 | 
			
		||||
	category = S("Node manipulation"), -- not really, but it doesn't fit anywhere else
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
		return true, S("@1 objects cleared", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("set", {
 | 
			
		||||
	params = "<node>",
 | 
			
		||||
	description = S("Set the current WorldEdit region to <node>"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local node = worldedit.normalize_nodename(param)
 | 
			
		||||
		if not node then
 | 
			
		||||
			return false, S("invalid node name: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		return true, node
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, node)
 | 
			
		||||
		local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node)
 | 
			
		||||
		return true, S("@1 nodes set", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("param2", {
 | 
			
		||||
	params = "<param2>",
 | 
			
		||||
	description = S("Set param2 of all nodes in the current WorldEdit region to <param2>"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local param2 = tonumber(param)
 | 
			
		||||
		if not param2 then
 | 
			
		||||
			return false
 | 
			
		||||
		elseif param2 < 0 or param2 > 255 then
 | 
			
		||||
			return false, S("Param2 is out of range (must be between 0 and 255 inclusive!)")
 | 
			
		||||
		end
 | 
			
		||||
		return true, param2
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, param2)
 | 
			
		||||
		local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2)
 | 
			
		||||
		return true, S("@1 nodes altered", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("mix", {
 | 
			
		||||
	params = "<node1> [count1] <node2> [count2] ...",
 | 
			
		||||
	description = S("Fill the current WorldEdit region with a random mix of <node1>, ..."),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local nodes = {}
 | 
			
		||||
		for nodename in param:gmatch("[^%s]+") do
 | 
			
		||||
			if tonumber(nodename) ~= nil and #nodes > 0 then
 | 
			
		||||
				local last_node = nodes[#nodes]
 | 
			
		||||
				for i = 1, tonumber(nodename) do
 | 
			
		||||
					nodes[#nodes + 1] = last_node
 | 
			
		||||
				end
 | 
			
		||||
			else
 | 
			
		||||
				local node = worldedit.normalize_nodename(nodename)
 | 
			
		||||
				if not node then
 | 
			
		||||
					return false, S("invalid node name: @1", nodename)
 | 
			
		||||
				end
 | 
			
		||||
				nodes[#nodes + 1] = node
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
		if #nodes == 0 then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		return true, nodes
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, nodes)
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
		local count = worldedit.set(pos1, pos2, nodes)
 | 
			
		||||
		return true, S("@1 nodes set", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
local check_replace = function(param)
 | 
			
		||||
	local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$")
 | 
			
		||||
	if found == nil then
 | 
			
		||||
		return false
 | 
			
		||||
	end
 | 
			
		||||
	local newsearchnode = worldedit.normalize_nodename(searchnode)
 | 
			
		||||
	if not newsearchnode then
 | 
			
		||||
		return false, S("invalid search node name: @1", searchnode)
 | 
			
		||||
	end
 | 
			
		||||
	local newreplacenode = worldedit.normalize_nodename(replacenode)
 | 
			
		||||
	if not newreplacenode then
 | 
			
		||||
		return false, S("invalid replace node name: @1", replacenode)
 | 
			
		||||
	end
 | 
			
		||||
	return true, newsearchnode, newreplacenode
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("replace", {
 | 
			
		||||
	params = "<search node> <replace node>",
 | 
			
		||||
	description = S("Replace all instances of <search node> with <replace node> in the current WorldEdit region"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = check_replace,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, search_node, replace_node)
 | 
			
		||||
		local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
 | 
			
		||||
				search_node, replace_node)
 | 
			
		||||
		return true, S("@1 nodes replaced", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("replaceinverse", {
 | 
			
		||||
	params = "<search node> <replace node>",
 | 
			
		||||
	description = S("Replace all nodes other than <search node> with <replace node> in the current WorldEdit region"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = check_replace,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, search_node, replace_node)
 | 
			
		||||
		local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
 | 
			
		||||
				search_node, replace_node, true)
 | 
			
		||||
		return true, S("@1 nodes replaced", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("fixlight", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Fix the lighting in the current WorldEdit region"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
		return true, S("@1 nodes updated", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
local drain_cache
 | 
			
		||||
 | 
			
		||||
local function drain(pos1, pos2)
 | 
			
		||||
	if drain_cache == nil then
 | 
			
		||||
		drain_cache = {}
 | 
			
		||||
		for name, d in pairs(minetest.registered_nodes) do
 | 
			
		||||
			if d.drawtype == "liquid" or d.drawtype == "flowingliquid" then
 | 
			
		||||
				drain_cache[name] = true
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	pos1, pos2 = worldedit.sort_pos(pos1, pos2)
 | 
			
		||||
	local count = 0
 | 
			
		||||
 | 
			
		||||
	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 p = vector.new(x, y, z)
 | 
			
		||||
		local n = get_node(p).name
 | 
			
		||||
		if drain_cache[n] then
 | 
			
		||||
			remove_node(p)
 | 
			
		||||
			count = count + 1
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	end
 | 
			
		||||
	end
 | 
			
		||||
	return count
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("drain", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Remove any fluid node within the current WorldEdit region"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local count = drain(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
		return true, S("@1 nodes updated", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
local clearcut_cache
 | 
			
		||||
 | 
			
		||||
local function clearcut(pos1, pos2)
 | 
			
		||||
	-- decide which nodes we consider plants
 | 
			
		||||
	if clearcut_cache == nil then
 | 
			
		||||
		clearcut_cache = {}
 | 
			
		||||
		for name, def in pairs(minetest.registered_nodes) do
 | 
			
		||||
			local groups = def.groups or {}
 | 
			
		||||
			if (
 | 
			
		||||
				-- the groups say so
 | 
			
		||||
				groups.flower or groups.grass or groups.flora or groups.plant or
 | 
			
		||||
				groups.leaves or groups.tree or groups.leafdecay or groups.sapling or
 | 
			
		||||
				-- drawtype heuristic
 | 
			
		||||
				(def.is_ground_content and def.buildable_to and
 | 
			
		||||
					(def.sunlight_propagates or not def.walkable)
 | 
			
		||||
					and def.drawtype == "plantlike") or
 | 
			
		||||
				-- if it's flammable, it probably needs to go too
 | 
			
		||||
				(def.is_ground_content and not def.walkable and groups.flammable)
 | 
			
		||||
			) then
 | 
			
		||||
				clearcut_cache[name] = true
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	local plants = clearcut_cache
 | 
			
		||||
 | 
			
		||||
	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 pos = vector.new(x, y, z)
 | 
			
		||||
			local n = get_node(pos).name
 | 
			
		||||
			if plants[n] then
 | 
			
		||||
				prev = true
 | 
			
		||||
				any = true
 | 
			
		||||
			elseif prev then
 | 
			
		||||
				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
 | 
			
		||||
					remove_node(pos)
 | 
			
		||||
					count = count + 1
 | 
			
		||||
				else
 | 
			
		||||
					prev = false
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		-- second pass: remove plants, top-to-bottom to avoid item drops
 | 
			
		||||
		if any then
 | 
			
		||||
			for y = pos2.y, pos1.y, -1 do
 | 
			
		||||
				local pos = vector.new(x, y, z)
 | 
			
		||||
				local n = get_node(pos).name
 | 
			
		||||
				if plants[n] then
 | 
			
		||||
					remove_node(pos)
 | 
			
		||||
					count = count + 1
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	return count
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("clearcut", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Remove any plant, tree or foliage-like nodes in the selected region"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
		local count = clearcut(pos1, pos2)
 | 
			
		||||
		return true, S("@1 nodes removed", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("hide", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Hide all nodes in the current WorldEdit region non-destructively"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
		return true, S("@1 nodes hidden", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("suppress", {
 | 
			
		||||
	params = "<node>",
 | 
			
		||||
	description = S("Suppress all <node> in the current WorldEdit region non-destructively"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local node = worldedit.normalize_nodename(param)
 | 
			
		||||
		if not node then
 | 
			
		||||
			return false, S("invalid node name: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		return true, node
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, node)
 | 
			
		||||
		local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)
 | 
			
		||||
		return true, S("@1 nodes suppressed", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("highlight", {
 | 
			
		||||
	params = "<node>",
 | 
			
		||||
	description = S("Highlight <node> in the current WorldEdit region by hiding everything else non-destructively"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local node = worldedit.normalize_nodename(param)
 | 
			
		||||
		if not node then
 | 
			
		||||
			return false, S("invalid node name: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		return true, node
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, node)
 | 
			
		||||
		local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node)
 | 
			
		||||
		return true, S("@1 nodes highlighted", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("restore", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Restores nodes hidden with WorldEdit in the current WorldEdit region"),
 | 
			
		||||
	category = S("Node manipulation"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
		return true, S("@1 nodes restored", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
@@ -6,16 +6,14 @@ local init_sentinel = "new" .. tostring(math.random(99999))
 | 
			
		||||
 | 
			
		||||
--marks worldedit region position 1
 | 
			
		||||
worldedit.mark_pos1 = function(name, region_too)
 | 
			
		||||
	local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
	local pos1 = worldedit.pos1[name]
 | 
			
		||||
 | 
			
		||||
	if worldedit.marker1[name] ~= nil then --marker already exists
 | 
			
		||||
		worldedit.marker1[name]:remove() --remove marker
 | 
			
		||||
		worldedit.marker1[name] = nil
 | 
			
		||||
	end
 | 
			
		||||
	if pos1 ~= nil then
 | 
			
		||||
		--make area stay loaded
 | 
			
		||||
		local manip = minetest.get_voxel_manip()
 | 
			
		||||
		manip:read_from_map(pos1, pos1)
 | 
			
		||||
		worldedit.keep_loaded(pos1, pos1)
 | 
			
		||||
 | 
			
		||||
		--add marker
 | 
			
		||||
		worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel)
 | 
			
		||||
@@ -30,16 +28,14 @@ end
 | 
			
		||||
 | 
			
		||||
--marks worldedit region position 2
 | 
			
		||||
worldedit.mark_pos2 = function(name, region_too)
 | 
			
		||||
	local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
	local pos2 = worldedit.pos2[name]
 | 
			
		||||
 | 
			
		||||
	if worldedit.marker2[name] ~= nil then --marker already exists
 | 
			
		||||
		worldedit.marker2[name]:remove() --remove marker
 | 
			
		||||
		worldedit.marker2[name] = nil
 | 
			
		||||
	end
 | 
			
		||||
	if pos2 ~= nil then
 | 
			
		||||
		--make area stay loaded
 | 
			
		||||
		local manip = minetest.get_voxel_manip()
 | 
			
		||||
		manip:read_from_map(pos2, pos2)
 | 
			
		||||
		worldedit.keep_loaded(pos2, pos2)
 | 
			
		||||
 | 
			
		||||
		--add marker
 | 
			
		||||
		worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel)
 | 
			
		||||
@@ -77,15 +73,14 @@ worldedit.mark_region = function(name)
 | 
			
		||||
		local thickness = 0.2
 | 
			
		||||
		local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
 | 
			
		||||
 | 
			
		||||
		--make area stay loaded
 | 
			
		||||
		local manip = minetest.get_voxel_manip()
 | 
			
		||||
		manip:read_from_map(pos1, pos2)
 | 
			
		||||
		-- TODO maybe we could skip this actually?
 | 
			
		||||
		worldedit.keep_loaded(pos1, pos2)
 | 
			
		||||
 | 
			
		||||
		local markers = {}
 | 
			
		||||
 | 
			
		||||
		--XY plane markers
 | 
			
		||||
		for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
 | 
			
		||||
			local entpos = {x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}
 | 
			
		||||
			local entpos = vector.new(pos1.x + sizex - 0.5, pos1.y + sizey - 0.5, z)
 | 
			
		||||
			local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel)
 | 
			
		||||
			if marker ~= nil then
 | 
			
		||||
				marker:set_properties({
 | 
			
		||||
@@ -99,7 +94,7 @@ worldedit.mark_region = function(name)
 | 
			
		||||
 | 
			
		||||
		--YZ plane markers
 | 
			
		||||
		for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
 | 
			
		||||
			local entpos = {x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}
 | 
			
		||||
			local entpos = vector.new(x, pos1.y + sizey - 0.5, pos1.z + sizez - 0.5)
 | 
			
		||||
			local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel)
 | 
			
		||||
			if marker ~= nil then
 | 
			
		||||
				marker:set_properties({
 | 
			
		||||
							
								
								
									
										85
									
								
								worldedit_commands/nodename.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,85 @@
 | 
			
		||||
-- Strips any kind of escape codes (translation, colors) from a string
 | 
			
		||||
-- https://github.com/minetest/minetest/blob/53dd7819277c53954d1298dfffa5287c306db8d0/src/util/string.cpp#L777
 | 
			
		||||
local function strip_escapes(input)
 | 
			
		||||
	local s = function(idx) return input:sub(idx, idx) end
 | 
			
		||||
	local out = ""
 | 
			
		||||
	local i = 1
 | 
			
		||||
	while i <= #input do
 | 
			
		||||
		if s(i) == "\027" then -- escape sequence
 | 
			
		||||
			i = i + 1
 | 
			
		||||
			if s(i) == "(" then -- enclosed
 | 
			
		||||
				i = i + 1
 | 
			
		||||
				while i <= #input and s(i) ~= ")" do
 | 
			
		||||
					if s(i) == "\\" then
 | 
			
		||||
						i = i + 2
 | 
			
		||||
					else
 | 
			
		||||
						i = i + 1
 | 
			
		||||
					end
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
		else
 | 
			
		||||
			out = out .. s(i)
 | 
			
		||||
		end
 | 
			
		||||
		i = i + 1
 | 
			
		||||
	end
 | 
			
		||||
	--print(("%q -> %q"):format(input, out))
 | 
			
		||||
	return out
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function string_endswith(full, part)
 | 
			
		||||
	return full:find(part, 1, true) == #full - #part + 1
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local description_cache = nil
 | 
			
		||||
 | 
			
		||||
-- normalizes node "description" `nodename`, returning a string (or nil)
 | 
			
		||||
worldedit.normalize_nodename = function(nodename)
 | 
			
		||||
	nodename = nodename:gsub("^%s*(.-)%s*$", "%1") -- strip spaces
 | 
			
		||||
	if nodename == "" then return nil end
 | 
			
		||||
 | 
			
		||||
	local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases
 | 
			
		||||
	if minetest.registered_nodes[fullname] or fullname == "air" then -- full name
 | 
			
		||||
		return fullname
 | 
			
		||||
	end
 | 
			
		||||
	nodename = nodename:lower()
 | 
			
		||||
 | 
			
		||||
	for key, _ in pairs(minetest.registered_nodes) do
 | 
			
		||||
		if string_endswith(key:lower(), ":" .. nodename) then -- matches name (w/o mod part)
 | 
			
		||||
			return key
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	if description_cache == nil then
 | 
			
		||||
		-- cache stripped descriptions
 | 
			
		||||
		description_cache = {}
 | 
			
		||||
		for key, value in pairs(minetest.registered_nodes) do
 | 
			
		||||
			local desc = strip_escapes(value.description):gsub("\n.*", "", 1):lower()
 | 
			
		||||
			if desc ~= "" then
 | 
			
		||||
				description_cache[key] = desc
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	for key, desc in pairs(description_cache) do
 | 
			
		||||
		if desc == nodename then -- matches description
 | 
			
		||||
			return key
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	for key, desc in pairs(description_cache) do
 | 
			
		||||
		if desc == nodename .. " block" then
 | 
			
		||||
			-- fuzzy description match (e.g. "Steel" == "Steel Block")
 | 
			
		||||
			return key
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local match = nil
 | 
			
		||||
	for key, value in pairs(description_cache) do
 | 
			
		||||
		if value:find(nodename, 1, true) ~= nil then
 | 
			
		||||
			if match ~= nil then
 | 
			
		||||
				return nil
 | 
			
		||||
			end
 | 
			
		||||
			match = key -- substring description match (only if no ambiguities)
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	return match
 | 
			
		||||
end
 | 
			
		||||
							
								
								
									
										276
									
								
								worldedit_commands/primitives.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,276 @@
 | 
			
		||||
local S = minetest.get_translator("worldedit_commands")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
local check_cube = function(param)
 | 
			
		||||
	local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
 | 
			
		||||
	if found == nil then
 | 
			
		||||
		return false
 | 
			
		||||
	end
 | 
			
		||||
	local node = worldedit.normalize_nodename(nodename)
 | 
			
		||||
	if not node then
 | 
			
		||||
		return false, S("invalid node name: @1", nodename)
 | 
			
		||||
	end
 | 
			
		||||
	return true, tonumber(w), tonumber(h), tonumber(l), node
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("hollowcube", {
 | 
			
		||||
	params = "<width> <height> <length> <node>",
 | 
			
		||||
	description = S("Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>."),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_cube,
 | 
			
		||||
	nodes_needed = function(name, w, h, l, node)
 | 
			
		||||
		return w * h * l
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, w, h, l, node)
 | 
			
		||||
		local count = worldedit.cube(worldedit.pos1[name], w, h, l, node, true)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("cube", {
 | 
			
		||||
	params = "<width> <height> <length> <node>",
 | 
			
		||||
	description = S("Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>."),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_cube,
 | 
			
		||||
	nodes_needed = function(name, w, h, l, node)
 | 
			
		||||
		return w * h * l
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, w, h, l, node)
 | 
			
		||||
		local count = worldedit.cube(worldedit.pos1[name], w, h, l, node)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
local check_sphere = function(param)
 | 
			
		||||
	local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
 | 
			
		||||
	if found == nil then
 | 
			
		||||
		return false
 | 
			
		||||
	end
 | 
			
		||||
	local node = worldedit.normalize_nodename(nodename)
 | 
			
		||||
	if not node then
 | 
			
		||||
		return false, S("invalid node name: @1", nodename)
 | 
			
		||||
	end
 | 
			
		||||
	return true, tonumber(radius), node
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("hollowsphere", {
 | 
			
		||||
	params = "<radius> <node>",
 | 
			
		||||
	description = S("Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_sphere,
 | 
			
		||||
	nodes_needed = function(name, radius, node)
 | 
			
		||||
		return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, radius, node)
 | 
			
		||||
		local count = worldedit.sphere(worldedit.pos1[name], radius, node, true)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("sphere", {
 | 
			
		||||
	params = "<radius> <node>",
 | 
			
		||||
	description = S("Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_sphere,
 | 
			
		||||
	nodes_needed = function(name, radius, node)
 | 
			
		||||
		return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, radius, node)
 | 
			
		||||
		local count = worldedit.sphere(worldedit.pos1[name], radius, node)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
local check_dome = function(param)
 | 
			
		||||
	local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
 | 
			
		||||
	if found == nil then
 | 
			
		||||
		return false
 | 
			
		||||
	end
 | 
			
		||||
	local node = worldedit.normalize_nodename(nodename)
 | 
			
		||||
	if not node then
 | 
			
		||||
		return false, S("invalid node name: @1", nodename)
 | 
			
		||||
	end
 | 
			
		||||
	return true, tonumber(radius), node
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("hollowdome", {
 | 
			
		||||
	params = "<radius> <node>",
 | 
			
		||||
	description = S("Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_dome,
 | 
			
		||||
	nodes_needed = function(name, radius, node)
 | 
			
		||||
		return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, radius, node)
 | 
			
		||||
		local count = worldedit.dome(worldedit.pos1[name], radius, node, true)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("dome", {
 | 
			
		||||
	params = "<radius> <node>",
 | 
			
		||||
	description = S("Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_dome,
 | 
			
		||||
	nodes_needed = function(name, radius, node)
 | 
			
		||||
		return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, radius, node)
 | 
			
		||||
		local count = worldedit.dome(worldedit.pos1[name], radius, node)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
local check_cylinder = function(param)
 | 
			
		||||
	-- two radii
 | 
			
		||||
	local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
 | 
			
		||||
	if found == nil then
 | 
			
		||||
		-- single radius
 | 
			
		||||
		found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")
 | 
			
		||||
		radius2 = radius1
 | 
			
		||||
	end
 | 
			
		||||
	if found == nil then
 | 
			
		||||
		return false
 | 
			
		||||
	end
 | 
			
		||||
	local node = worldedit.normalize_nodename(nodename)
 | 
			
		||||
	if not node then
 | 
			
		||||
		return false, S("invalid node name: @1", nodename)
 | 
			
		||||
	end
 | 
			
		||||
	return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("hollowcylinder", {
 | 
			
		||||
	params = "x/y/z/? <length> <radius1> [radius2] <node>",
 | 
			
		||||
	description = S("Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>"),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_cylinder,
 | 
			
		||||
	nodes_needed = function(name, axis, length, radius1, radius2, node)
 | 
			
		||||
		local radius = math.max(radius1, radius2)
 | 
			
		||||
		return math.ceil(math.pi * (radius ^ 2) * length)
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, axis, length, radius1, radius2, node)
 | 
			
		||||
		if axis == "?" then
 | 
			
		||||
			local sign
 | 
			
		||||
			axis, sign = worldedit.player_axis(name)
 | 
			
		||||
			length = length * sign
 | 
			
		||||
		end
 | 
			
		||||
		local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node, true)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("cylinder", {
 | 
			
		||||
	params = "x/y/z/? <length> <radius1> [radius2] <node>",
 | 
			
		||||
	description = S("Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>"),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_cylinder,
 | 
			
		||||
	nodes_needed = function(name, axis, length, radius1, radius2, node)
 | 
			
		||||
		local radius = math.max(radius1, radius2)
 | 
			
		||||
		return math.ceil(math.pi * (radius ^ 2) * length)
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, axis, length, radius1, radius2, node)
 | 
			
		||||
		if axis == "?" then
 | 
			
		||||
			local sign
 | 
			
		||||
			axis, sign = worldedit.player_axis(name)
 | 
			
		||||
			length = length * sign
 | 
			
		||||
		end
 | 
			
		||||
		local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
local check_pyramid = function(param)
 | 
			
		||||
	local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")
 | 
			
		||||
	if found == nil then
 | 
			
		||||
		return false
 | 
			
		||||
	end
 | 
			
		||||
	local node = worldedit.normalize_nodename(nodename)
 | 
			
		||||
	if not node then
 | 
			
		||||
		return false, S("invalid node name: @1", nodename)
 | 
			
		||||
	end
 | 
			
		||||
	return true, axis, tonumber(height), node
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("hollowpyramid", {
 | 
			
		||||
	params = "x/y/z/? <height> <node>",
 | 
			
		||||
	description = S("Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>"),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_pyramid,
 | 
			
		||||
	nodes_needed = function(name, axis, height, node)
 | 
			
		||||
		return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, axis, height, node)
 | 
			
		||||
		if axis == "?" then
 | 
			
		||||
			local sign
 | 
			
		||||
			axis, sign = worldedit.player_axis(name)
 | 
			
		||||
			height = height * sign
 | 
			
		||||
		end
 | 
			
		||||
		local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("pyramid", {
 | 
			
		||||
	params = "x/y/z/? <height> <node>",
 | 
			
		||||
	description = S("Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>"),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = check_pyramid,
 | 
			
		||||
	nodes_needed = function(name, axis, height, node)
 | 
			
		||||
		return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, axis, height, node)
 | 
			
		||||
		if axis == "?" then
 | 
			
		||||
			local sign
 | 
			
		||||
			axis, sign = worldedit.player_axis(name)
 | 
			
		||||
			height = height * sign
 | 
			
		||||
		end
 | 
			
		||||
		local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("spiral", {
 | 
			
		||||
	params = "<length> <height> <space> <node>",
 | 
			
		||||
	description = S("Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>"),
 | 
			
		||||
	category = S("Shapes"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		local node = worldedit.normalize_nodename(nodename)
 | 
			
		||||
		if not node then
 | 
			
		||||
			return false, S("invalid node name: @1", nodename)
 | 
			
		||||
		end
 | 
			
		||||
		return true, tonumber(length), tonumber(height), tonumber(space), node
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = function(name, length, height, space, node)
 | 
			
		||||
		return (length + space) * height -- TODO: this is not the upper bound
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, length, height, space, node)
 | 
			
		||||
		local count = worldedit.spiral(worldedit.pos1[name], length, height, space, node)
 | 
			
		||||
		return true, S("@1 nodes added", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										234
									
								
								worldedit_commands/region.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,234 @@
 | 
			
		||||
local S = minetest.get_translator("worldedit_commands")
 | 
			
		||||
 | 
			
		||||
worldedit.set_pos = {}
 | 
			
		||||
worldedit.inspect = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("inspect", {
 | 
			
		||||
	params = "[on/off/1/0/true/false/yes/no/enable/disable]",
 | 
			
		||||
	description = S("Enable or disable node inspection"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then
 | 
			
		||||
			return true, true
 | 
			
		||||
		elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then
 | 
			
		||||
			return true, false
 | 
			
		||||
		end
 | 
			
		||||
		return false
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, enable)
 | 
			
		||||
		if enable then
 | 
			
		||||
			worldedit.inspect[name] = true
 | 
			
		||||
			local axis, sign = worldedit.player_axis(name)
 | 
			
		||||
			worldedit.player_notify(name, S(
 | 
			
		||||
				"inspector: inspection enabled for @1, currently facing the @2 axis",
 | 
			
		||||
				name,
 | 
			
		||||
				axis .. (sign > 0 and "+" or "-")
 | 
			
		||||
			), "info")
 | 
			
		||||
		else
 | 
			
		||||
			worldedit.inspect[name] = nil
 | 
			
		||||
			worldedit.player_notify(name, S("inspector: inspection disabled"), "info")
 | 
			
		||||
		end
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
local VEC_6DIRS = {
 | 
			
		||||
	vector.new( 1, 0, 0),
 | 
			
		||||
	vector.new(-1, 0, 0),
 | 
			
		||||
	vector.new( 0, 1, 0),
 | 
			
		||||
	vector.new( 0,-1, 0),
 | 
			
		||||
	vector.new( 0, 0, 1),
 | 
			
		||||
	vector.new( 0, 0,-1),
 | 
			
		||||
}
 | 
			
		||||
local function get_node_rlight(pos)
 | 
			
		||||
	local ret = 0
 | 
			
		||||
	for _, v in ipairs(VEC_6DIRS) do
 | 
			
		||||
		ret = math.max(ret, minetest.get_node_light(vector.add(pos, v)))
 | 
			
		||||
	end
 | 
			
		||||
	return ret
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
minetest.register_on_punchnode(function(pos, node, puncher)
 | 
			
		||||
	local name = puncher:get_player_name()
 | 
			
		||||
	if worldedit.inspect[name] then
 | 
			
		||||
		local axis, sign = worldedit.player_axis(name)
 | 
			
		||||
		local message = S(
 | 
			
		||||
			"inspector: @1 at @2 (param1=@3, param2=@4, received light=@5) punched facing the @6 axis",
 | 
			
		||||
			node.name,
 | 
			
		||||
			minetest.pos_to_string(pos),
 | 
			
		||||
			node.param1,
 | 
			
		||||
			node.param2,
 | 
			
		||||
			get_node_rlight(pos),
 | 
			
		||||
			axis .. (sign > 0 and "+" or "-")
 | 
			
		||||
		)
 | 
			
		||||
		worldedit.player_notify(name, message, "info")
 | 
			
		||||
	end
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("mark", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Show markers at the region positions"),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
		return true, S("region marked")
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("unmark", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Hide markers if currently shown"),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
		worldedit.pos1[name] = nil
 | 
			
		||||
		worldedit.pos2[name] = nil
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
		worldedit.pos1[name] = pos1
 | 
			
		||||
		worldedit.pos2[name] = pos2
 | 
			
		||||
		return true, S("region unmarked")
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
local function set_pos1(name, pos)
 | 
			
		||||
	assert(pos)
 | 
			
		||||
	worldedit.pos1[name] = pos
 | 
			
		||||
	worldedit.mark_pos1(name)
 | 
			
		||||
	worldedit.player_notify(name, S("position @1 set to @2", 1, minetest.pos_to_string(pos)), "ok")
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function set_pos2(name, pos)
 | 
			
		||||
	assert(pos)
 | 
			
		||||
	worldedit.pos2[name] = pos
 | 
			
		||||
	worldedit.mark_pos2(name)
 | 
			
		||||
	worldedit.player_notify(name, S("position @1 set to @2", 2, minetest.pos_to_string(pos)), "ok")
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("pos1", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Set WorldEdit region position @1 to the player's location", 1),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local player = minetest.get_player_by_name(name)
 | 
			
		||||
		if not player then return end
 | 
			
		||||
		set_pos1(name, vector.round(player:get_pos()))
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("pos2", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Set WorldEdit region position @1 to the player's location", 2),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local player = minetest.get_player_by_name(name)
 | 
			
		||||
		if not player then return end
 | 
			
		||||
		set_pos2(name, vector.round(player:get_pos()))
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("p", {
 | 
			
		||||
	params = "set/set1/set2/get",
 | 
			
		||||
	description = S("Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region"),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		if param == "set" or param == "set1" or param == "set2" or param == "get" then
 | 
			
		||||
			return true, param
 | 
			
		||||
		end
 | 
			
		||||
		return false, S("unknown subcommand: @1", param)
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, param)
 | 
			
		||||
		local msg
 | 
			
		||||
		if param == "set" then --set both WorldEdit positions
 | 
			
		||||
			worldedit.set_pos[name] = "pos1"
 | 
			
		||||
			msg = S("select positions by punching two nodes")
 | 
			
		||||
		elseif param == "set1" then --set WorldEdit position 1
 | 
			
		||||
			worldedit.set_pos[name] = "pos1only"
 | 
			
		||||
			msg = S("select position @1 by punching a node", 1)
 | 
			
		||||
		elseif param == "set2" then --set WorldEdit position 2
 | 
			
		||||
			worldedit.set_pos[name] = "pos2"
 | 
			
		||||
			msg = S("select position @1 by punching a node", 2)
 | 
			
		||||
		elseif param == "get" then --display current WorldEdit positions
 | 
			
		||||
			if worldedit.pos1[name] ~= nil then
 | 
			
		||||
				msg = S("position @1: @2", 1, minetest.pos_to_string(worldedit.pos1[name]))
 | 
			
		||||
			else
 | 
			
		||||
				msg = S("position @1 not set", 1)
 | 
			
		||||
			end
 | 
			
		||||
			msg = msg .. "\n"
 | 
			
		||||
			if worldedit.pos2[name] ~= nil then
 | 
			
		||||
				msg = msg .. S("position @1: @2", 2, minetest.pos_to_string(worldedit.pos2[name]))
 | 
			
		||||
			else
 | 
			
		||||
				msg = msg .. S("position @1 not set", 2)
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
		if msg then
 | 
			
		||||
			worldedit.player_notify(name, msg, "info")
 | 
			
		||||
		end
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("fixedpos", {
 | 
			
		||||
	params = "set1/set2 <x> <y> <z>",
 | 
			
		||||
	description = S("Set a WorldEdit region position to the position at (<x>, <y>, <z>)"),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		return true, flag, vector.new(tonumber(x), tonumber(y), tonumber(z))
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, flag, pos)
 | 
			
		||||
		if flag == "set1" then
 | 
			
		||||
			set_pos1(name, pos)
 | 
			
		||||
		else --flag == "set2"
 | 
			
		||||
			set_pos2(name, pos)
 | 
			
		||||
		end
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
minetest.register_on_punchnode(function(pos, node, puncher)
 | 
			
		||||
	local name = puncher:get_player_name()
 | 
			
		||||
	if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position
 | 
			
		||||
		if worldedit.set_pos[name] == "pos1" then --setting position 1
 | 
			
		||||
			set_pos1(name, pos)
 | 
			
		||||
			worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation
 | 
			
		||||
		elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only
 | 
			
		||||
			set_pos1(name, pos)
 | 
			
		||||
			worldedit.set_pos[name] = nil --finished setting positions
 | 
			
		||||
		elseif worldedit.set_pos[name] == "pos2" then --setting position 2
 | 
			
		||||
			set_pos2(name, pos)
 | 
			
		||||
			worldedit.set_pos[name] = nil --finished setting positions
 | 
			
		||||
		elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities
 | 
			
		||||
			worldedit.prob_pos[name] = pos
 | 
			
		||||
			minetest.show_formspec(name, "prob_val_enter", "field[text;;]")
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("volume", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = S("Display the volume of the current WorldEdit region"),
 | 
			
		||||
	category = S("Region operations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
 | 
			
		||||
		local volume = worldedit.volume(pos1, pos2)
 | 
			
		||||
		local abs = math.abs
 | 
			
		||||
		worldedit.player_notify(name, S(
 | 
			
		||||
			"current region has a volume of @1 nodes (@2*@3*@4)",
 | 
			
		||||
			volume,
 | 
			
		||||
			abs(pos2.x - pos1.x) + 1,
 | 
			
		||||
			abs(pos2.y - pos1.y) + 1,
 | 
			
		||||
			abs(pos2.z - pos1.z) + 1
 | 
			
		||||
		), "info")
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
@@ -1,15 +1,30 @@
 | 
			
		||||
local S = minetest.get_translator("worldedit_commands")
 | 
			
		||||
 | 
			
		||||
local safe_region_limit = tonumber(minetest.settings:get("worldedit_safe_region_limit") or "20000")
 | 
			
		||||
 | 
			
		||||
local safe_region_callback = {}
 | 
			
		||||
 | 
			
		||||
--`count` is the number of nodes that would possibly be modified
 | 
			
		||||
--`callback` is a callback to run when the user confirms
 | 
			
		||||
local function safe_region(name, count, callback)
 | 
			
		||||
	if count < 20000 then
 | 
			
		||||
	if safe_region_limit <= 0 or count < safe_region_limit then
 | 
			
		||||
		return callback()
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	--save callback to call later
 | 
			
		||||
	-- save callback to call later
 | 
			
		||||
	safe_region_callback[name] = callback
 | 
			
		||||
	worldedit.player_notify(name, "WARNING: this operation could affect up to " .. count .. " nodes; type //y to continue or //n to cancel")
 | 
			
		||||
 | 
			
		||||
	local count_str = tostring(count)
 | 
			
		||||
	-- highlight millions, 1 mln <=> 100x100x100 cube
 | 
			
		||||
	if #count_str > 6 then
 | 
			
		||||
		count_str = minetest.colorize("#f33", count_str:sub(1, -7)) .. count_str:sub(-6, -1)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local yes_cmd = minetest.colorize("#00ffff", "//y")
 | 
			
		||||
	local no_cmd = minetest.colorize("#00ffff", "//n")
 | 
			
		||||
	local msg = S("WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel",
 | 
			
		||||
		count_str, yes_cmd, no_cmd)
 | 
			
		||||
	worldedit.player_notify(name, msg, "info")
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function reset_pending(name)
 | 
			
		||||
@@ -18,11 +33,11 @@ end
 | 
			
		||||
 | 
			
		||||
minetest.register_chatcommand("/y", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = "Confirm a pending operation",
 | 
			
		||||
	description = S("Confirm a pending operation"),
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		local callback = safe_region_callback[name]
 | 
			
		||||
		if not callback then
 | 
			
		||||
			worldedit.player_notify(name, "no operation pending")
 | 
			
		||||
			worldedit.player_notify(name, S("no operation pending"), "error")
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
@@ -33,10 +48,10 @@ minetest.register_chatcommand("/y", {
 | 
			
		||||
 | 
			
		||||
minetest.register_chatcommand("/n", {
 | 
			
		||||
	params = "",
 | 
			
		||||
	description = "Abort a pending operation",
 | 
			
		||||
	description = S("Abort a pending operation"),
 | 
			
		||||
	func = function(name)
 | 
			
		||||
		if not safe_region_callback[name] then
 | 
			
		||||
			worldedit.player_notify(name, "no operation pending")
 | 
			
		||||
			worldedit.player_notify(name, S("no operation pending"), "error")
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										284
									
								
								worldedit_commands/schematics.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,284 @@
 | 
			
		||||
local S = minetest.get_translator("worldedit_commands")
 | 
			
		||||
 | 
			
		||||
worldedit.prob_pos = {}
 | 
			
		||||
worldedit.prob_list = {}
 | 
			
		||||
 | 
			
		||||
local function check_region(name)
 | 
			
		||||
	return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function check_filename(name)
 | 
			
		||||
	return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function open_schematic(name, param)
 | 
			
		||||
	-- find the file in the world path
 | 
			
		||||
	local testpaths = {
 | 
			
		||||
		minetest.get_worldpath() .. "/schems/" .. param,
 | 
			
		||||
		minetest.get_worldpath() .. "/schems/" .. param .. ".we",
 | 
			
		||||
		minetest.get_worldpath() .. "/schems/" .. param .. ".wem",
 | 
			
		||||
	}
 | 
			
		||||
	local file, err
 | 
			
		||||
	for index, path in ipairs(testpaths) do
 | 
			
		||||
		file, err = io.open(path, "rb")
 | 
			
		||||
		if not err then
 | 
			
		||||
			break
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	if err then
 | 
			
		||||
		worldedit.player_notify(name, S("Could not open file \"@1\"", param), "error")
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
	local value = file:read("*a")
 | 
			
		||||
	file:close()
 | 
			
		||||
 | 
			
		||||
	local version = worldedit.read_header(value)
 | 
			
		||||
	if version == nil or version == 0 then
 | 
			
		||||
		worldedit.player_notify(name, S("Invalid file format!"), "error")
 | 
			
		||||
		return
 | 
			
		||||
	elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
 | 
			
		||||
		worldedit.player_notify(name, S("Schematic was created with a newer version of WorldEdit."), "error")
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	return value
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function detect_misaligned_schematic(name, pos1, pos2)
 | 
			
		||||
	pos1 = worldedit.sort_pos(pos1, pos2)
 | 
			
		||||
	-- Check that allocate/save can position the schematic correctly
 | 
			
		||||
	-- The expected behaviour is that the (0,0,0) corner of the schematic stays
 | 
			
		||||
	-- at pos1, this only works when the minimum position is actually present
 | 
			
		||||
	-- in the schematic.
 | 
			
		||||
	local node = minetest.get_node(pos1)
 | 
			
		||||
	local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"
 | 
			
		||||
	if not have_node_at_origin then
 | 
			
		||||
		worldedit.player_notify(name,
 | 
			
		||||
			S("Warning: The schematic contains excessive free space and WILL be "..
 | 
			
		||||
			"misaligned when allocated or loaded. To avoid this, shrink your "..
 | 
			
		||||
			"area to cover exactly the nodes to be saved.")
 | 
			
		||||
		)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("save", {
 | 
			
		||||
	params = "<file>",
 | 
			
		||||
	description = S("Save the current WorldEdit region to \"(world folder)/schems/<file>.we\""),
 | 
			
		||||
	category = S("Schematics"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		if param == "" then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		if not check_filename(param) then
 | 
			
		||||
			return false, S("Disallowed file name: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		return true, param
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, param)
 | 
			
		||||
		local result, count = worldedit.serialize(worldedit.pos1[name],
 | 
			
		||||
				worldedit.pos2[name])
 | 
			
		||||
		detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
 | 
			
		||||
		local path = minetest.get_worldpath() .. "/schems"
 | 
			
		||||
		-- Create directory if it does not already exist
 | 
			
		||||
		minetest.mkdir(path)
 | 
			
		||||
 | 
			
		||||
		local filename = path .. "/" .. param .. ".we"
 | 
			
		||||
		local file, err = io.open(filename, "wb")
 | 
			
		||||
		if err ~= nil then
 | 
			
		||||
			return false, S("Could not save file to \"@1\"", filename)
 | 
			
		||||
		end
 | 
			
		||||
		file:write(result)
 | 
			
		||||
		file:flush()
 | 
			
		||||
		file:close()
 | 
			
		||||
 | 
			
		||||
		return true, S("@1 nodes saved", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("allocate", {
 | 
			
		||||
	params = "<file>",
 | 
			
		||||
	description = S("Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region"),
 | 
			
		||||
	category = S("Schematics"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		if param == "" then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		if not check_filename(param) then
 | 
			
		||||
			return false, S("Disallowed file name: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		return true, param
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, param)
 | 
			
		||||
		local pos = worldedit.pos1[name]
 | 
			
		||||
 | 
			
		||||
		local value = open_schematic(name, param)
 | 
			
		||||
		if not value then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		local nodepos1, nodepos2, count = worldedit.allocate(pos, value)
 | 
			
		||||
		if not nodepos1 then
 | 
			
		||||
			return false, S("Schematic empty, nothing allocated")
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		worldedit.pos1[name] = nodepos1
 | 
			
		||||
		worldedit.pos2[name] = nodepos2
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
 | 
			
		||||
		return true, S("@1 nodes allocated", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("load", {
 | 
			
		||||
	params = "<file>",
 | 
			
		||||
	description = S("Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin"),
 | 
			
		||||
	category = S("Schematics"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		if param == "" then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		if not check_filename(param) then
 | 
			
		||||
			return false, S("Disallowed file name: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		return true, param
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, param)
 | 
			
		||||
		local pos = worldedit.pos1[name]
 | 
			
		||||
 | 
			
		||||
		local value = open_schematic(name, param)
 | 
			
		||||
		if not value then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		local count = worldedit.deserialize(pos, value)
 | 
			
		||||
		if count == nil then
 | 
			
		||||
			return false, S("Loading failed!")
 | 
			
		||||
		end
 | 
			
		||||
		return true, S("@1 nodes loaded", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("mtschemcreate", {
 | 
			
		||||
	params = "<file>",
 | 
			
		||||
	description = S("Save the current WorldEdit region using the Minetest "..
 | 
			
		||||
		"Schematic format to \"(world folder)/schems/<filename>.mts\""),
 | 
			
		||||
	category = S("Schematics"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		if param == "" then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		if not check_filename(param) then
 | 
			
		||||
			return false, S("Disallowed file name: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		return true, param
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, param)
 | 
			
		||||
		local path = minetest.get_worldpath() .. "/schems"
 | 
			
		||||
		-- Create directory if it does not already exist
 | 
			
		||||
		minetest.mkdir(path)
 | 
			
		||||
 | 
			
		||||
		local filename = path .. "/" .. param .. ".mts"
 | 
			
		||||
		local ret = minetest.create_schematic(worldedit.pos1[name],
 | 
			
		||||
				worldedit.pos2[name], worldedit.prob_list[name],
 | 
			
		||||
				filename)
 | 
			
		||||
		worldedit.prob_list[name] = {}
 | 
			
		||||
		if ret == nil then
 | 
			
		||||
			return false, S("Failed to create Minetest schematic")
 | 
			
		||||
		end
 | 
			
		||||
		return true, S("Saved Minetest schematic to @1", param)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("mtschemplace", {
 | 
			
		||||
	params = "<file>",
 | 
			
		||||
	description = S("Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin"),
 | 
			
		||||
	category = S("Schematics"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 1,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, filename, rotation = param:find("^(.+)%s+([012789]+)$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			filename = param
 | 
			
		||||
		elseif rotation ~= "0" and rotation ~= "90" and rotation ~= "180" and rotation ~= "270" then
 | 
			
		||||
			return false, S("Invalid rotation: @1", rotation)
 | 
			
		||||
		end
 | 
			
		||||
		if not check_filename(filename) then
 | 
			
		||||
			return false, S("Disallowed file name: @1", filename)
 | 
			
		||||
		end
 | 
			
		||||
		return true, filename, rotation
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, filename, rotation)
 | 
			
		||||
		local pos = worldedit.pos1[name]
 | 
			
		||||
 | 
			
		||||
		local path = minetest.get_worldpath() .. "/schems/" .. filename .. ".mts"
 | 
			
		||||
		if minetest.place_schematic(pos, path, rotation) == nil then
 | 
			
		||||
			return false, S("failed to place Minetest schematic")
 | 
			
		||||
		end
 | 
			
		||||
		return true, S("placed Minetest schematic @1 at @2",
 | 
			
		||||
			filename, minetest.pos_to_string(pos))
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("mtschemprob", {
 | 
			
		||||
	params = "start/finish/get",
 | 
			
		||||
	description = S("Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry"),
 | 
			
		||||
	category = S("Schematics"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		if param ~= "start" and param ~= "finish" and param ~= "get" then
 | 
			
		||||
			return false, S("unknown subcommand: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		return true, param
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, param)
 | 
			
		||||
		if param == "start" then --start probability setting
 | 
			
		||||
			worldedit.set_pos[name] = "prob"
 | 
			
		||||
			worldedit.prob_list[name] = {}
 | 
			
		||||
			worldedit.player_notify(name, S("select Minetest schematic probability values by punching nodes"), "info")
 | 
			
		||||
		elseif param == "finish" then --finish probability setting
 | 
			
		||||
			worldedit.set_pos[name] = nil
 | 
			
		||||
			worldedit.player_notify(name, S("finished Minetest schematic probability selection"), "info")
 | 
			
		||||
		elseif param == "get" then --get all nodes that had probabilities set on them
 | 
			
		||||
			local text = ""
 | 
			
		||||
			local problist = worldedit.prob_list[name]
 | 
			
		||||
			if problist == nil then
 | 
			
		||||
				return
 | 
			
		||||
			end
 | 
			
		||||
			for k,v in pairs(problist) do
 | 
			
		||||
				local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100
 | 
			
		||||
				text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "
 | 
			
		||||
			end
 | 
			
		||||
			worldedit.player_notify(name, S("currently set node probabilities:") .. "\n" .. text, "info")
 | 
			
		||||
		end
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
 | 
			
		||||
	if formname == "prob_val_enter" then
 | 
			
		||||
		local name = player:get_player_name()
 | 
			
		||||
		local problist = worldedit.prob_list[name]
 | 
			
		||||
		if problist == nil then
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
		local e = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}
 | 
			
		||||
		if e.pos == nil or e.prob == nil or e.prob < 0 or e.prob > 256 then
 | 
			
		||||
			worldedit.player_notify(name, S("invalid node probability given, not saved"), "error")
 | 
			
		||||
			return
 | 
			
		||||
		end
 | 
			
		||||
		problist[#problist+1] = e
 | 
			
		||||
	end
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 147 B After Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 142 B After Width: | Height: | Size: 100 B  | 
| 
		 Before Width: | Height: | Size: 157 B After Width: | Height: | Size: 112 B  | 
							
								
								
									
										
											BIN
										
									
								
								worldedit_commands/textures/worldedit_wand.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 303 B  | 
							
								
								
									
										269
									
								
								worldedit_commands/transform.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,269 @@
 | 
			
		||||
local S = minetest.get_translator("worldedit_commands")
 | 
			
		||||
 | 
			
		||||
local function check_region(name)
 | 
			
		||||
	return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("copy", {
 | 
			
		||||
	params = "x/y/z/? <amount>",
 | 
			
		||||
	description = S("Copy the current WorldEdit region along the given axis by <amount> nodes"),
 | 
			
		||||
	category = S("Transformations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		return true, axis, tonumber(amount)
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = function(name, axis, amount)
 | 
			
		||||
		return check_region(name) * 2
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, axis, amount)
 | 
			
		||||
		if axis == "?" then
 | 
			
		||||
			local sign
 | 
			
		||||
			axis, sign = worldedit.player_axis(name)
 | 
			
		||||
			amount = amount * sign
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)
 | 
			
		||||
		return true, S("@1 nodes copied", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("move", {
 | 
			
		||||
	params = "x/y/z/? <amount>",
 | 
			
		||||
	description = S("Move the current WorldEdit region along the given axis by <amount> nodes"),
 | 
			
		||||
	category = S("Transformations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		return true, axis, tonumber(amount)
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = function(name, axis, amount)
 | 
			
		||||
		return check_region(name) * 2
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, axis, amount)
 | 
			
		||||
		if axis == "?" then
 | 
			
		||||
			local sign
 | 
			
		||||
			axis, sign = worldedit.player_axis(name)
 | 
			
		||||
			amount = amount * sign
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
		local count = worldedit.move(pos1, pos2, axis, amount)
 | 
			
		||||
 | 
			
		||||
		pos1[axis] = pos1[axis] + amount
 | 
			
		||||
		pos2[axis] = pos2[axis] + amount
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
		return true, S("@1 nodes moved", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("stack", {
 | 
			
		||||
	params = "x/y/z/? <count>",
 | 
			
		||||
	description = S("Stack the current WorldEdit region along the given axis <count> times"),
 | 
			
		||||
	category = S("Transformations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		return true, axis, tonumber(repetitions)
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = function(name, axis, repetitions)
 | 
			
		||||
		return check_region(name) * math.abs(repetitions)
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, axis, repetitions)
 | 
			
		||||
		if axis == "?" then
 | 
			
		||||
			local sign
 | 
			
		||||
			axis, sign = worldedit.player_axis(name)
 | 
			
		||||
			repetitions = repetitions * sign
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
		local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)
 | 
			
		||||
		worldedit.stack(pos1, pos2, axis, repetitions, function()
 | 
			
		||||
			worldedit.player_notify(name, S("@1 nodes stacked", count), "ok")
 | 
			
		||||
		end)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("stack2", {
 | 
			
		||||
	params = "<count> <x> <y> <z>",
 | 
			
		||||
	description = S("Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>"),
 | 
			
		||||
	category = S("Transformations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local repetitions, incs = param:match("(%d+)%s*(.+)")
 | 
			
		||||
		if repetitions == nil then
 | 
			
		||||
			return false, S("invalid count: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")
 | 
			
		||||
		if x == nil then
 | 
			
		||||
			return false, S("invalid increments: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		return true, tonumber(repetitions), vector.new(tonumber(x), tonumber(y), tonumber(z))
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = function(name, repetitions, offset)
 | 
			
		||||
		return check_region(name) * repetitions
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, repetitions, offset)
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
		local count = worldedit.volume(pos1, pos2) * repetitions
 | 
			
		||||
		worldedit.stack2(pos1, pos2, offset, repetitions, function()
 | 
			
		||||
			worldedit.player_notify(name, S("@1 nodes stacked", count), "ok")
 | 
			
		||||
		end)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("stretch", {
 | 
			
		||||
	params = "<stretchx> <stretchy> <stretchz>",
 | 
			
		||||
	description = S("Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin"),
 | 
			
		||||
	category = S("Transformations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)
 | 
			
		||||
		if stretchx == 0 or stretchy == 0 or stretchz == 0 then
 | 
			
		||||
			return false, S("invalid scaling factors: @1", param)
 | 
			
		||||
		end
 | 
			
		||||
		return true, stretchx, stretchy, stretchz
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = function(name, stretchx, stretchy, stretchz)
 | 
			
		||||
		return check_region(name) * stretchx * stretchy * stretchz
 | 
			
		||||
	end,
 | 
			
		||||
	func = function(name, stretchx, stretchy, stretchz)
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
		local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
 | 
			
		||||
 | 
			
		||||
		--reset markers to scaled positions
 | 
			
		||||
		worldedit.pos1[name] = pos1
 | 
			
		||||
		worldedit.pos2[name] = pos2
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
 | 
			
		||||
		return true, S("@1 nodes stretched", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("transpose", {
 | 
			
		||||
	params = "x/y/z/? x/y/z/?",
 | 
			
		||||
	description = S("Transpose the current WorldEdit region along the given axes"),
 | 
			
		||||
	category = S("Transformations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			return false
 | 
			
		||||
		elseif axis1 == axis2 then
 | 
			
		||||
			return false, S("invalid usage: axes must be different")
 | 
			
		||||
		end
 | 
			
		||||
		return true, axis1, axis2
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, axis1, axis2)
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
		if axis1 == "?" then axis1 = worldedit.player_axis(name) end
 | 
			
		||||
		if axis2 == "?" then axis2 = worldedit.player_axis(name) end
 | 
			
		||||
		local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
 | 
			
		||||
 | 
			
		||||
		--reset markers to transposed positions
 | 
			
		||||
		worldedit.pos1[name] = pos1
 | 
			
		||||
		worldedit.pos2[name] = pos2
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
 | 
			
		||||
		return true, S("@1 nodes transposed", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("flip", {
 | 
			
		||||
	params = "x/y/z/?",
 | 
			
		||||
	description = S("Flip the current WorldEdit region along the given axis"),
 | 
			
		||||
	category = S("Transformations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		return true, param
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, param)
 | 
			
		||||
		if param == "?" then param = worldedit.player_axis(name) end
 | 
			
		||||
		local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)
 | 
			
		||||
		return true, S("@1 nodes flipped", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("rotate", {
 | 
			
		||||
	params = "x/y/z/? <angle>",
 | 
			
		||||
	description = S("Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)"),
 | 
			
		||||
	category = S("Transformations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		angle = tonumber(angle)
 | 
			
		||||
		if angle % 90 ~= 0 or angle % 360 == 0 then
 | 
			
		||||
			return false, S("invalid usage: angle must be multiple of 90")
 | 
			
		||||
		end
 | 
			
		||||
		return true, axis, angle
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, axis, angle)
 | 
			
		||||
		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
 | 
			
		||||
		if axis == "?" then axis = worldedit.player_axis(name) end
 | 
			
		||||
		local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)
 | 
			
		||||
 | 
			
		||||
		--reset markers to rotated positions
 | 
			
		||||
		worldedit.pos1[name] = pos1
 | 
			
		||||
		worldedit.pos2[name] = pos2
 | 
			
		||||
		worldedit.marker_update(name)
 | 
			
		||||
 | 
			
		||||
		return true, S("@1 nodes rotated", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_command("orient", {
 | 
			
		||||
	params = "<angle>",
 | 
			
		||||
	description = S("Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)"),
 | 
			
		||||
	category = S("Transformations"),
 | 
			
		||||
	privs = {worldedit=true},
 | 
			
		||||
	require_pos = 2,
 | 
			
		||||
	parse = function(param)
 | 
			
		||||
		local found, _, angle = param:find("^([+-]?%d+)$")
 | 
			
		||||
		if found == nil then
 | 
			
		||||
			return false
 | 
			
		||||
		end
 | 
			
		||||
		angle = tonumber(angle)
 | 
			
		||||
		if angle % 90 ~= 0 then
 | 
			
		||||
			return false, S("invalid usage: angle must be multiple of 90")
 | 
			
		||||
		end
 | 
			
		||||
		return true, angle
 | 
			
		||||
	end,
 | 
			
		||||
	nodes_needed = check_region,
 | 
			
		||||
	func = function(name, angle)
 | 
			
		||||
		local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)
 | 
			
		||||
		return true, S("@1 nodes oriented", count)
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
local S = minetest.get_translator("worldedit_commands")
 | 
			
		||||
 | 
			
		||||
local function above_or_under(placer, pointed_thing)
 | 
			
		||||
	if placer:get_player_control().sneak then
 | 
			
		||||
		return pointed_thing.above
 | 
			
		||||
@@ -9,11 +11,18 @@ end
 | 
			
		||||
local punched_air_time = {}
 | 
			
		||||
 | 
			
		||||
minetest.register_tool(":worldedit:wand", {
 | 
			
		||||
	description = "WorldEdit Wand tool\nLeft-click to set 1st position, right-click to set 2nd",
 | 
			
		||||
	description = S("WorldEdit Wand tool\nLeft-click to set 1st position, right-click to set 2nd"),
 | 
			
		||||
	inventory_image = "worldedit_wand.png",
 | 
			
		||||
	stack_max = 1, -- there is no need to have more than one
 | 
			
		||||
	liquids_pointable = true, -- ground with only water on can be selected as well
 | 
			
		||||
 | 
			
		||||
	-- ignore marker cube so the clicking on the position markers works reliably
 | 
			
		||||
	pointabilities = {
 | 
			
		||||
		objects = {
 | 
			
		||||
			["worldedit:region_cube"] = false
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	on_use = function(itemstack, placer, pointed_thing)
 | 
			
		||||
		if placer == nil or pointed_thing == nil then return end
 | 
			
		||||
		local name = placer:get_player_name()
 | 
			
		||||
@@ -32,7 +41,7 @@ minetest.register_tool(":worldedit:wand", {
 | 
			
		||||
			local entity = pointed_thing.ref:get_luaentity()
 | 
			
		||||
			if entity and entity.name == "worldedit:pos2" then
 | 
			
		||||
				-- set pos1 = pos2
 | 
			
		||||
				worldedit.pos1[name] = worldedit.pos2[name]
 | 
			
		||||
				worldedit.pos1[name] = vector.copy(worldedit.pos2[name])
 | 
			
		||||
				worldedit.mark_pos1(name)
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
@@ -57,7 +66,7 @@ minetest.register_tool(":worldedit:wand", {
 | 
			
		||||
		local entity = pointed_thing.ref:get_luaentity()
 | 
			
		||||
		if entity and entity.name == "worldedit:pos1" then
 | 
			
		||||
			-- set pos2 = pos1
 | 
			
		||||
			worldedit.pos2[name] = worldedit.pos1[name]
 | 
			
		||||
			worldedit.pos2[name] = vector.copy(worldedit.pos1[name])
 | 
			
		||||
			worldedit.mark_pos2(name)
 | 
			
		||||
		end
 | 
			
		||||
		return itemstack -- nothing consumed, nothing changed
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ local gui_count2 = {} --mapping of player names to a quantity (arbitrary strings
 | 
			
		||||
local gui_count3 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values)
 | 
			
		||||
local gui_angle = {} --mapping of player names to an angle (one of 90, 180, 270, representing the angle in degrees clockwise)
 | 
			
		||||
local gui_filename = {} --mapping of player names to file names
 | 
			
		||||
local gui_param2 = {} --mapping of player names to param2 values
 | 
			
		||||
 | 
			
		||||
--set default values
 | 
			
		||||
setmetatable(gui_nodename1, {__index = function() return "Cobblestone" end})
 | 
			
		||||
@@ -25,6 +26,7 @@ setmetatable(gui_count2,     {__index = function() return "6" end})
 | 
			
		||||
setmetatable(gui_count3,     {__index = function() return "4" end})
 | 
			
		||||
setmetatable(gui_angle,     {__index = function() return 90 end})
 | 
			
		||||
setmetatable(gui_filename,  {__index = function() return "building" end})
 | 
			
		||||
setmetatable(gui_param2,    {__index = function() return "0" end})
 | 
			
		||||
 | 
			
		||||
local axis_indices = {["X axis"]=1, ["Y axis"]=2, ["Z axis"]=3, ["Look direction"]=4}
 | 
			
		||||
local axis_values = {"x", "y", "z", "?"}
 | 
			
		||||
@@ -138,7 +140,7 @@ local function execute_worldedit_command(command_name, player_name, params)
 | 
			
		||||
	assert(chatcmd, "unknown command: " .. command_name)
 | 
			
		||||
	local _, msg = chatcmd.func(player_name, params)
 | 
			
		||||
	if msg then
 | 
			
		||||
		 worldedit.player_notify(player_name, msg)
 | 
			
		||||
		minetest.chat_send_player(player_name, msg)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@@ -904,3 +906,31 @@ worldedit.register_gui_function("worldedit_gui_clearobjects", {
 | 
			
		||||
		execute_worldedit_command("clearobjects", name, "")
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_gui_function("worldedit_gui_param2", {
 | 
			
		||||
	name = "Set Param2",
 | 
			
		||||
	privs = we_privs("param2"),
 | 
			
		||||
	get_formspec = function(name)
 | 
			
		||||
		local value = gui_param2[name] or "0"
 | 
			
		||||
		return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_param2") ..
 | 
			
		||||
			"textarea[0.5,1;5,2;;;Some values may break the node!]"..
 | 
			
		||||
			string.format("field[0.5,2.5;2,0.8;worldedit_gui_param2_value;New Param2;%s]", minetest.formspec_escape(value)) ..
 | 
			
		||||
			"field_close_on_enter[worldedit_gui_param2_value;false]" ..
 | 
			
		||||
			"button_exit[3.5,2.5;3,0.8;worldedit_gui_param2_submit;Set Param2]"
 | 
			
		||||
	end,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
worldedit.register_gui_handler("worldedit_gui_param2", function(name, fields)
 | 
			
		||||
	local cg = {
 | 
			
		||||
		worldedit_gui_param2_value = gui_param2,
 | 
			
		||||
	}
 | 
			
		||||
	local ret = handle_changes(name, "worldedit_gui_param2", fields, cg)
 | 
			
		||||
	if fields.worldedit_gui_param2_submit then
 | 
			
		||||
		copy_changes(name, fields, cg)
 | 
			
		||||
		worldedit.show_page(name, "worldedit_gui_param2")
 | 
			
		||||
 | 
			
		||||
		execute_worldedit_command("param2", name, gui_param2[name])
 | 
			
		||||
		return true
 | 
			
		||||
	end
 | 
			
		||||
	return ret
 | 
			
		||||
end)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,10 @@ worldedit = worldedit or {}
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
    worldedit.register_gui_function("worldedit_gui_hollow_cylinder", {
 | 
			
		||||
    	name = "Make Hollow Cylinder",
 | 
			
		||||
    	privs = {worldedit=true},
 | 
			
		||||
    	get_formspec = function(name) return "some formspec here" end,
 | 
			
		||||
    	on_select = function(name) print(name .. " clicked the button!") end,
 | 
			
		||||
		name = "Make Hollow Cylinder",
 | 
			
		||||
		privs = {worldedit=true},
 | 
			
		||||
		get_formspec = function(name) return "some formspec here" end,
 | 
			
		||||
		on_select = function(name) print(name .. " clicked the button!") end,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
Use `nil` for the `options` parameter to unregister the function associated with the given identifier.
 | 
			
		||||
@@ -35,14 +35,14 @@ end
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
    worldedit.register_gui_handler("worldedit_gui_hollow_cylinder", function(name, fields)
 | 
			
		||||
    	print(minetest.serialize(fields))
 | 
			
		||||
		print(minetest.serialize(fields))
 | 
			
		||||
    end)
 | 
			
		||||
]]
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
@@ -80,6 +80,7 @@ if minetest.global_exists("unified_inventory") then -- unified inventory install
 | 
			
		||||
	unified_inventory.register_button("worldedit_gui", {
 | 
			
		||||
		type = "image",
 | 
			
		||||
		image = "inventory_plus_worldedit_gui.png",
 | 
			
		||||
		tooltip = "Edit your World!",
 | 
			
		||||
		condition = function(player)
 | 
			
		||||
			return minetest.check_player_privs(player:get_player_name(), {worldedit=true})
 | 
			
		||||
		end,
 | 
			
		||||
@@ -193,7 +194,8 @@ elseif minetest.global_exists("sfinv") then -- sfinv installed
 | 
			
		||||
		get = function(self, player, context)
 | 
			
		||||
			local can_worldedit = minetest.check_player_privs(player, {worldedit=true})
 | 
			
		||||
			local fs = orig_get(self, player, context)
 | 
			
		||||
			return fs .. (can_worldedit and "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]" or "")
 | 
			
		||||
			return fs .. (can_worldedit and "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]" ..
 | 
			
		||||
				"tooltip[worldedit_gui;Edit your World!]" or "")
 | 
			
		||||
		end
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
@@ -216,7 +218,7 @@ elseif minetest.global_exists("sfinv") then -- sfinv installed
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
else
 | 
			
		||||
	error(
 | 
			
		||||
	return minetest.log("error",
 | 
			
		||||
		"worldedit_gui requires a supported gui management mod to be installed.\n"..
 | 
			
		||||
		"To use the it you need to either:\n"..
 | 
			
		||||
		"* use minetest_game or another sfinv-compatible subgame\n"..
 | 
			
		||||
@@ -262,7 +264,7 @@ worldedit.register_gui_handler("worldedit_gui", function(name, fields)
 | 
			
		||||
			--ensure player has permission to perform action
 | 
			
		||||
			local has_privs, missing_privs = minetest.check_player_privs(name, entry.privs)
 | 
			
		||||
			if not has_privs then
 | 
			
		||||
				worldedit.player_notify(name, "you are not allowed to use this function (missing privileges: " .. table.concat(missing_privs, ", ") .. ")")
 | 
			
		||||
				worldedit.player_notify(name, "you are not allowed to use this function (missing privileges: " .. table.concat(missing_privs, ", ") .. ")", "error")
 | 
			
		||||
				return false
 | 
			
		||||
			end
 | 
			
		||||
			if entry.on_select then
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB  | 
| 
		 Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 382 B  |