Compare commits
	
		
			62 Commits
		
	
	
		
			c8afa95542
			...
			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 | 
							
								
								
									
										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 ./ | ||||||
							
								
								
									
										24
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,11 +1,27 @@ | |||||||
| name: test | name: "Test" | ||||||
|  |  | ||||||
| on: [push, pull_request] | on: [push, pull_request] | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   test: |   test: | ||||||
|  |     name: "Unit Tests ${{ matrix.cfg.image }}" | ||||||
|     runs-on: ubuntu-latest |     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: |     steps: | ||||||
|     - uses: actions/checkout@v2 |     - uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |     - uses: actions/checkout@v4 | ||||||
|  |       with: | ||||||
|  |         repository: 'minetest/minetest_game' | ||||||
|  |         path: minetest_game | ||||||
|  |       if: ${{ matrix.cfg.mtg }} | ||||||
|  |  | ||||||
|     - name: Run tests |     - name: Run tests | ||||||
|       run: MINETEST_VER=latest ./.util/run_tests.sh |       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"}, | ||||||
|  | } | ||||||
| @@ -1,29 +1,41 @@ | |||||||
| #!/bin/bash | #!/bin/bash | ||||||
| tempdir=/tmp/mt | tempdir=$(mktemp -d) | ||||||
| confpath=$tempdir/minetest.conf | confpath=$tempdir/minetest.conf | ||||||
| worldpath=$tempdir/world | worldpath=$tempdir/world | ||||||
|  | trap 'rm -rf "$tempdir"' EXIT | ||||||
|  |  | ||||||
| use_docker=y | [ -f worldedit/mod.conf ] || { echo "Must be run in modpack root folder." >&2; exit 1; } | ||||||
| [ -x ../../bin/minetestserver ] && use_docker= |  | ||||||
|  |  | ||||||
| rm -rf $tempdir | mtserver= | ||||||
| mkdir -p $worldpath | if [ "$1" == "--docker" ]; then | ||||||
| # the docker image doesn't have devtest | 	command -v docker >/dev/null || { echo "Docker is not installed." >&2; exit 1; } | ||||||
| [ -n "$use_docker" ] || printf '%s\n' gameid=devtest >$worldpath/world.mt | 	[ -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' mg_name=singlenode '[end_of_params]' >$worldpath/map_meta.txt | ||||||
| printf '%s\n' worldedit_run_tests=true max_forceloaded_blocks=9999 >$confpath | printf '%s\n' worldedit_run_tests=true max_forceloaded_blocks=9999 >$confpath | ||||||
|  |  | ||||||
| if [ -n "$use_docker" ]; then | if [ -z "$mtserver" ]; then | ||||||
| 	chmod -R 777 $tempdir | 	chmod -R 777 $tempdir | ||||||
| 	docker run --rm -i \ | 	[ -z "$DOCKER_IMAGE" ] && DOCKER_IMAGE="ghcr.io/minetest/minetest:master" | ||||||
| 		-v $confpath:/etc/minetest/minetest.conf \ | 	vol=( | ||||||
| 		-v $tempdir:/var/lib/minetest/.minetest \ | 		-v "$confpath":/etc/minetest/minetest.conf | ||||||
| 		-v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit \ | 		-v "$tempdir":/var/lib/minetest/.minetest | ||||||
| 		registry.gitlab.com/minetest/minetest/server:${MINETEST_VER} | 		-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 | else | ||||||
| 	mkdir $worldpath/worldmods | 	mkdir $worldpath/worldmods | ||||||
| 	ln -s "$PWD/worldedit" $worldpath/worldmods/worldedit | 	ln -s "$PWD/worldedit" $worldpath/worldmods/worldedit | ||||||
| 	../../bin/minetestserver --config $confpath --world $worldpath --logfile /dev/null | 	$mtserver --config "$confpath" --world "$worldpath" --logfile /dev/null | ||||||
| fi | fi | ||||||
|  |  | ||||||
| test -f $worldpath/tests_ok || exit 1 | test -f $worldpath/tests_ok || exit 1 | ||||||
|   | |||||||
| @@ -409,14 +409,15 @@ Load nodes from "(world folder)/schems/`<file>`.we" with position 1 of the curre | |||||||
|  |  | ||||||
| ### `//lua <code>` | ### `//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.pos1[name] = vector.new(0, 0, 0) | ||||||
|     //lua worldedit.rotate(worldedit.pos1["singleplayer"], worldedit.pos2["singleplayer"], "y", 90) |     //lua worldedit.rotate(worldedit.pos1["jones"], worldedit.pos2["jones"], "y", 90) | ||||||
|  |     //lua player:set_pos(worldedit.pos2[name]) | ||||||
|  |  | ||||||
| ### `//luatransform <code>` | ### `//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 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 |     //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 some random filename | ||||||
|     //mtschemcreate huge_base |     //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. | 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 |     //mtschemplace huge_base | ||||||
|  |  | ||||||
| ### `//mtschemprob start/finish/get` | ### `//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. | 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). | # 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. | 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. | 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). | or ask on the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) (slower but more likely to get help). | ||||||
|  |  | ||||||
| Usage | Usage | ||||||
|   | |||||||
| @@ -227,11 +227,19 @@ Code | |||||||
| ---- | ---- | ||||||
| Contained in code.lua, this module allows arbitrary Lua code to be used with WorldEdit. | 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) | ### error = worldedit.luatransform(pos1, pos2, code) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,2 +1,3 @@ | |||||||
| name = Minetest-WorldEdit | name = Minetest-WorldEdit | ||||||
| description = WorldEdit is an in-game world editor. Use it to repair griefing, or just create awesome buildings in seconds. | 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 | -- @module worldedit.code | ||||||
|  |  | ||||||
| --- Executes `code` as a Lua chunk in the global namespace. | --- Executes `code` as a Lua chunk in the global namespace. | ||||||
| -- @return An error message if the code fails, or nil on success. | -- the code will be encapsulated into a function with parameters | ||||||
| function worldedit.lua(code) | --  * name (the name of the player issuing the //lua command) | ||||||
| 	local func, err = loadstring(code) | --  * player (the player object of the player) | ||||||
| 	if not func then  -- Syntax error | --  * 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 | 		return err | ||||||
| 	end | 	end | ||||||
| 	local good, err = pcall(func) | 	local func = factory() | ||||||
| 	if not good then  -- Runtime error | 	local player, pos | ||||||
| 		return err | 	if name then | ||||||
|  | 		player = minetest.get_player_by_name(name) | ||||||
|  | 		if player then | ||||||
|  | 			pos = vector.round(player:get_pos()) | ||||||
|  | 		end | ||||||
| 	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 | end | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -31,7 +43,7 @@ function worldedit.luatransform(pos1, pos2, code) | |||||||
|  |  | ||||||
| 	worldedit.keep_loaded(pos1, pos2) | 	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 | 	while pos.x <= pos2.x do | ||||||
| 		pos.y = pos1.y | 		pos.y = pos1.y | ||||||
| 		while pos.y <= pos2.y do | 		while pos.y <= pos2.y do | ||||||
| @@ -39,7 +51,7 @@ function worldedit.luatransform(pos1, pos2, code) | |||||||
| 			while pos.z <= pos2.z do | 			while pos.z <= pos2.z do | ||||||
| 				local good, err = pcall(func, pos) | 				local good, err = pcall(func, pos) | ||||||
| 				if not good then -- Runtime error | 				if not good then -- Runtime error | ||||||
| 					return err | 					return tostring(err) | ||||||
| 				end | 				end | ||||||
| 				pos.z = pos.z + 1 | 				pos.z = pos.z + 1 | ||||||
| 			end | 			end | ||||||
|   | |||||||
| @@ -1,12 +1,21 @@ | |||||||
| --- Common functions [INTERNAL].  All of these functions are internal! | --- Common functions [INTERNAL].  All of these functions are internal! | ||||||
| -- @module worldedit.common | -- @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 | --- Copies and modifies positions `pos1` and `pos2` so that each component of | ||||||
| -- `pos1` is less than or equal to the corresponding component of `pos2`. | -- `pos1` is less than or equal to the corresponding component of `pos2`. | ||||||
| -- Returns the new positions. | -- Returns the new positions. | ||||||
| function worldedit.sort_pos(pos1, pos2) | function worldedit.sort_pos(pos1, pos2) | ||||||
| 	pos1 = {x=pos1.x, y=pos1.y, z=pos1.z} | 	pos1 = vector.copy(pos1) | ||||||
| 	pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} | 	pos2 = vector.copy(pos2) | ||||||
| 	if pos1.x > pos2.x then | 	if pos1.x > pos2.x then | ||||||
| 		pos2.x, pos1.x = pos1.x, pos2.x | 		pos2.x, pos1.x = pos1.x, pos2.x | ||||||
| 	end | 	end | ||||||
| @@ -45,12 +54,22 @@ function worldedit.get_axis_others(axis) | |||||||
| end | 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) | function worldedit.keep_loaded(pos1, pos2) | ||||||
| 	-- Create a vmanip and read the area from map, this | 	-- rough estimate, a MapNode is 4 bytes in the engine | ||||||
| 	-- causes all MapBlocks to be loaded into memory. | 	if worldedit.volume(pos1, pos2) > 268400000 then | ||||||
| 	-- This doesn't actually *keep* them loaded, unlike the name implies. | 		print("[WorldEdit] Requested to load an area bigger than 1GB, refusing. The subsequent operation may fail.") | ||||||
| 	local manip = minetest.get_voxel_manip() | 		return | ||||||
| 	manip:read_from_map(pos1, pos2) | 	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 | end | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -65,7 +84,7 @@ function mh.get_empty_data(area) | |||||||
| 	-- only partially modified aren't overwriten. | 	-- only partially modified aren't overwriten. | ||||||
| 	local data = {} | 	local data = {} | ||||||
| 	local c_ignore = minetest.get_content_id("ignore") | 	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 | 		data[i] = c_ignore | ||||||
| 	end | 	end | ||||||
| 	return data | 	return data | ||||||
| @@ -117,3 +136,72 @@ function mh.finish(manip, data) | |||||||
| 	manip:update_map() | 	manip:update_map() | ||||||
| end | 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 | 	return count | ||||||
| end | end | ||||||
|  |  | ||||||
| function worldedit.metaload(originpos, filename) | function worldedit.metaload(originpos, file_name) | ||||||
| 	deprecated("load") | 	deprecated("load") | ||||||
| 	filename = minetest.get_worldpath() .. "/schems/" .. file .. ".wem" | 	local file_path = minetest.get_worldpath() .. | ||||||
| 	local file, err = io.open(filename, "wb") | 		"/schems/" .. file_name .. ".wem" | ||||||
| 	if err then return 0 end | 	local file, err = io.open(file_path, "wb") | ||||||
|  | 	if err then | ||||||
|  | 		return 0 | ||||||
|  | 	end | ||||||
| 	local data = file:read("*a") | 	local data = file:read("*a") | ||||||
| 	return worldedit.deserialize(originpos, data) | 	return worldedit.deserialize(originpos, data) | ||||||
| end | end | ||||||
|   | |||||||
| @@ -2,16 +2,16 @@ | |||||||
| worldedit.cuboid_volumetric_expand = function(name, amount) | worldedit.cuboid_volumetric_expand = function(name, amount) | ||||||
| 	local pos1 = worldedit.pos1[name] | 	local pos1 = worldedit.pos1[name] | ||||||
| 	local pos2 = worldedit.pos2[name] | 	local pos2 = worldedit.pos2[name] | ||||||
| 	 |  | ||||||
| 	if pos1 == nil or pos2 == nil then | 	if pos1 == nil or pos2 == nil then | ||||||
| 		return false, "Undefined cuboid" | 		return false, "Undefined cuboid" | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	local delta1 = vector.new() | 	local delta1 = vector.new() | ||||||
| 	local delta2 = vector.new() | 	local delta2 = vector.new() | ||||||
| 	local delta_dir1 | 	local delta_dir1 | ||||||
| 	local delta_dir2 | 	local delta_dir2 | ||||||
| 	 |  | ||||||
| 	delta1 = vector.add(delta1, amount) | 	delta1 = vector.add(delta1, amount) | ||||||
| 	delta2 = vector.add(delta2, amount) | 	delta2 = vector.add(delta2, amount) | ||||||
| 	delta_dir1, delta_dir2 = worldedit.get_expansion_directions(pos1, pos2) | 	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) | 	delta2 = vector.multiply(delta2, delta_dir2) | ||||||
| 	worldedit.pos1[name] = vector.add(pos1, delta1) | 	worldedit.pos1[name] = vector.add(pos1, delta1) | ||||||
| 	worldedit.pos2[name] = vector.add(pos2, delta2) | 	worldedit.pos2[name] = vector.add(pos2, delta2) | ||||||
| 	 |  | ||||||
| 	return true | 	return true | ||||||
| end | end | ||||||
|  |  | ||||||
| @@ -28,18 +28,18 @@ end | |||||||
| worldedit.cuboid_linear_expand = function(name, axis, direction, amount) | worldedit.cuboid_linear_expand = function(name, axis, direction, amount) | ||||||
| 	local pos1 = worldedit.pos1[name] | 	local pos1 = worldedit.pos1[name] | ||||||
| 	local pos2 = worldedit.pos2[name] | 	local pos2 = worldedit.pos2[name] | ||||||
| 	 |  | ||||||
| 	if pos1 == nil or pos2 == nil then | 	if pos1 == nil or pos2 == nil then | ||||||
| 		return false, "undefined cuboid" | 		return false, "undefined cuboid" | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	if direction ~= 1 and direction ~= -1 then | 	if direction ~= 1 and direction ~= -1 then | ||||||
| 		return false, "invalid marker" | 		return false, "invalid marker" | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	local marker = worldedit.marker_get_closest_to_axis(name, axis, direction) | 	local marker = worldedit.marker_get_closest_to_axis(name, axis, direction) | ||||||
| 	local deltavect = vector.new() | 	local deltavect = vector.new() | ||||||
| 	 |  | ||||||
| 	if axis == 'x' then | 	if axis == 'x' then | ||||||
| 		deltavect.x = amount * direction | 		deltavect.x = amount * direction | ||||||
| 	elseif axis == 'y' then | 	elseif axis == 'y' then | ||||||
| @@ -49,7 +49,7 @@ worldedit.cuboid_linear_expand = function(name, axis, direction, amount) | |||||||
| 	else | 	else | ||||||
| 		return false, "invalid axis" | 		return false, "invalid axis" | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	worldedit.marker_move(name, marker, deltavect) | 	worldedit.marker_move(name, marker, deltavect) | ||||||
| 	return true | 	return true | ||||||
| end | end | ||||||
| @@ -59,11 +59,13 @@ end | |||||||
| worldedit.cuboid_shift = function(name, axis, amount) | worldedit.cuboid_shift = function(name, axis, amount) | ||||||
| 	local pos1 = worldedit.pos1[name] | 	local pos1 = worldedit.pos1[name] | ||||||
| 	local pos2 = worldedit.pos2[name] | 	local pos2 = worldedit.pos2[name] | ||||||
| 	 |  | ||||||
| 	if pos1 == nil or pos2 == nil then | 	if pos1 == nil or pos2 == nil then | ||||||
| 		return false, "undefined cuboid" | 		return false, "undefined cuboid" | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
|  | 	assert(not rawequal(pos1, pos2)) -- vectors must not alias | ||||||
|  |  | ||||||
| 	if axis == 'x' then | 	if axis == 'x' then | ||||||
| 		worldedit.pos1[name].x = pos1.x + amount | 		worldedit.pos1[name].x = pos1.x + amount | ||||||
| 		worldedit.pos2[name].x = pos2.x + amount | 		worldedit.pos2[name].x = pos2.x + amount | ||||||
| @@ -76,7 +78,7 @@ worldedit.cuboid_shift = function(name, axis, amount) | |||||||
| 	else | 	else | ||||||
| 		return false, "invalid axis" | 		return false, "invalid axis" | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	return true | 	return true | ||||||
| end | end | ||||||
|  |  | ||||||
| @@ -86,7 +88,7 @@ worldedit.marker_move = function(name, marker, deltavector) | |||||||
| 	if marker ~= 1 and marker ~= 2 then | 	if marker ~= 1 and marker ~= 2 then | ||||||
| 		return false | 		return false | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	if marker == 1 then | 	if marker == 1 then | ||||||
| 		local pos = worldedit.pos1[name] | 		local pos = worldedit.pos1[name] | ||||||
| 		worldedit.pos1[name] = vector.add(deltavector, pos) | 		worldedit.pos1[name] = vector.add(deltavector, pos) | ||||||
| @@ -94,7 +96,7 @@ worldedit.marker_move = function(name, marker, deltavector) | |||||||
| 		local pos = worldedit.pos2[name] | 		local pos = worldedit.pos2[name] | ||||||
| 		worldedit.pos2[name] = vector.add(deltavector, pos) | 		worldedit.pos2[name] = vector.add(deltavector, pos) | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	return true | 	return true | ||||||
| end | end | ||||||
|  |  | ||||||
| @@ -134,10 +136,11 @@ end | |||||||
|  |  | ||||||
| -- Return the marker that is closest to the player | -- Return the marker that is closest to the player | ||||||
| worldedit.marker_get_closest_to_player = function(name) | 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 dist1 = vector.distance(playerpos, worldedit.pos1[name]) | ||||||
| 	local dist2 = vector.distance(playerpos, worldedit.pos2[name]) | 	local dist2 = vector.distance(playerpos, worldedit.pos2[name]) | ||||||
| 	 |  | ||||||
| 	if dist1 < dist2 then | 	if dist1 < dist2 then | ||||||
| 		return 1 | 		return 1 | ||||||
| 	else | 	else | ||||||
| @@ -150,7 +153,7 @@ end | |||||||
| worldedit.marker_get_closest_to_axis = function(name, axis, direction) | worldedit.marker_get_closest_to_axis = function(name, axis, direction) | ||||||
| 	local pos1 = vector.new() | 	local pos1 = vector.new() | ||||||
| 	local pos2 = vector.new() | 	local pos2 = vector.new() | ||||||
| 	 |  | ||||||
| 	if direction ~= 1 and direction ~= -1 then | 	if direction ~= 1 and direction ~= -1 then | ||||||
| 		return nil | 		return nil | ||||||
| 	end | 	end | ||||||
| @@ -185,20 +188,20 @@ worldedit.marker_get_closest_to_axis = function(name, axis, direction) | |||||||
| end | 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 | -- directions according to faced direction | ||||||
| worldedit.translate_direction = function(name, direction) | worldedit.translate_direction = function(name, direction) | ||||||
| 	local axis, dir = worldedit.player_axis(name) | 	local axis, dir = worldedit.player_axis(name) | ||||||
| 	local resaxis, resdir | 	local resaxis, resdir | ||||||
| 	 |  | ||||||
| 	if direction == "up" then | 	if direction == "up" then | ||||||
| 		return 'y', 1 | 		return 'y', 1 | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	if direction == "down" then | 	if direction == "down" then | ||||||
| 		return 'y', -1 | 		return 'y', -1 | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	if direction == "front" then | 	if direction == "front" then | ||||||
| 		if axis == "y" then | 		if axis == "y" then | ||||||
| 			resaxis = nil | 			resaxis = nil | ||||||
| @@ -208,7 +211,7 @@ worldedit.translate_direction = function(name, direction) | |||||||
| 			resdir = dir | 			resdir = dir | ||||||
| 		end | 		end | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	if direction == "back" then | 	if direction == "back" then | ||||||
| 		if axis == "y" then | 		if axis == "y" then | ||||||
| 			resaxis = nil | 			resaxis = nil | ||||||
| @@ -218,7 +221,7 @@ worldedit.translate_direction = function(name, direction) | |||||||
| 			resdir = -dir | 			resdir = -dir | ||||||
| 		end | 		end | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	if direction == "left" then | 	if direction == "left" then | ||||||
| 		if axis == 'x' then | 		if axis == 'x' then | ||||||
| 			resaxis = 'z' | 			resaxis = 'z' | ||||||
| @@ -228,7 +231,7 @@ worldedit.translate_direction = function(name, direction) | |||||||
| 			resdir = -dir | 			resdir = -dir | ||||||
| 		end | 		end | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	if direction == "right" then | 	if direction == "right" then | ||||||
| 		if axis == 'x' then | 		if axis == 'x' then | ||||||
| 			resaxis = 'z' | 			resaxis = 'z' | ||||||
| @@ -238,6 +241,6 @@ worldedit.translate_direction = function(name, direction) | |||||||
| 			resdir = dir | 			resdir = dir | ||||||
| 		end | 		end | ||||||
| 	end | 	end | ||||||
| 	 |  | ||||||
| 	return resaxis, resdir | 	return resaxis, resdir | ||||||
| end | end | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ end | |||||||
| dofile(path .. "/common.lua") | dofile(path .. "/common.lua") | ||||||
| load_module(path .. "/manipulations.lua") | load_module(path .. "/manipulations.lua") | ||||||
| load_module(path .. "/primitives.lua") | load_module(path .. "/primitives.lua") | ||||||
|  | load_module(path .. "/transformations.lua") | ||||||
| load_module(path .. "/visualization.lua") | load_module(path .. "/visualization.lua") | ||||||
| load_module(path .. "/serialization.lua") | load_module(path .. "/serialization.lua") | ||||||
| load_module(path .. "/code.lua") | load_module(path .. "/code.lua") | ||||||
| @@ -39,6 +40,6 @@ if minetest.settings:get_bool("log_mods") then | |||||||
| end | end | ||||||
|  |  | ||||||
| if minetest.settings:get_bool("worldedit_run_tests") then | if minetest.settings:get_bool("worldedit_run_tests") then | ||||||
| 	dofile(path .. "/test.lua") | 	dofile(path .. "/test/init.lua") | ||||||
| 	minetest.after(0, worldedit.run_tests) | 	minetest.after(0, worldedit.run_tests) | ||||||
| end | end | ||||||
|   | |||||||
| @@ -98,51 +98,6 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse) | |||||||
| end | 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. | --- Copies a region along `axis` by `amount` nodes. | ||||||
| -- @param pos1 | -- @param pos1 | ||||||
| -- @param pos2 | -- @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) | 	-- Decide if we need to copy stuff backwards (only applies to metadata) | ||||||
| 	local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1) | 	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 | 	off[axis] = amount | ||||||
| 	return worldedit.copy2(pos1, pos2, off, backwards) | 	return worldedit.copy2(pos1, pos2, off, backwards) | ||||||
| end | end | ||||||
| @@ -170,7 +125,7 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards) | |||||||
| 	local pos1, pos2 = worldedit.sort_pos(pos1, pos2) | 	local pos1, pos2 = worldedit.sort_pos(pos1, pos2) | ||||||
|  |  | ||||||
| 	local src_manip, src_area = mh.init(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 src_offset = vector.subtract(pos1, src_area.MinEdge) | ||||||
|  |  | ||||||
| 	local dpos1 = vector.add(pos1, off) | 	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 dim = vector.add(vector.subtract(pos2, pos1), 1) | ||||||
|  |  | ||||||
| 	local dst_manip, dst_area = mh.init(dpos1, dpos2) | 	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 dst_offset = vector.subtract(dpos1, dst_area.MinEdge) | ||||||
|  |  | ||||||
| 	local function do_copy(src_data, dst_data) | 	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) | 	dst_manip:set_param2_data(dst_data) | ||||||
|  |  | ||||||
| 	mh.finish(dst_manip) | 	mh.finish(dst_manip) | ||||||
| 	src_data = nil |  | ||||||
| 	dst_data = nil |  | ||||||
|  |  | ||||||
| 	-- Copy metadata | 	-- Copy metadata | ||||||
| 	local get_meta = minetest.get_meta | 	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 z = dim.z-1, 0, -1 do | ||||||
| 		for y = dim.y-1, 0, -1 do | 		for y = dim.y-1, 0, -1 do | ||||||
| 			for x = dim.x-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() | 				local meta = get_meta(pos):to_table() | ||||||
| 				pos = vector.add(pos, off) | 				pos = vector.add(pos, off) | ||||||
| 				get_meta(pos):from_table(meta) | 				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 z = 0, dim.z-1 do | ||||||
| 		for y = 0, dim.y-1 do | 		for y = 0, dim.y-1 do | ||||||
| 			for x = 0, dim.x-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() | 				local meta = get_meta(pos):to_table() | ||||||
| 				pos = vector.add(pos, off) | 				pos = vector.add(pos, off) | ||||||
| 				get_meta(pos):from_table(meta) | 				get_meta(pos):from_table(meta) | ||||||
| @@ -286,21 +239,21 @@ function worldedit.move(pos1, pos2, axis, amount) | |||||||
| 	end | 	end | ||||||
|  |  | ||||||
| 	-- Copy stuff to new location | 	-- Copy stuff to new location | ||||||
| 	local off = {x=0, y=0, z=0} | 	local off = vector.new() | ||||||
| 	off[axis] = amount | 	off[axis] = amount | ||||||
| 	worldedit.copy2(pos1, pos2, off, backwards) | 	worldedit.copy2(pos1, pos2, off, backwards) | ||||||
| 	-- Nuke old area | 	-- Nuke old area | ||||||
| 	if not overlap then | 	if not overlap then | ||||||
| 		nuke_area({x=0, y=0, z=0}, dim) | 		nuke_area(vector.new(), dim) | ||||||
| 	else | 	else | ||||||
| 		-- Source and destination region are overlapping, which means we can't | 		-- Source and destination region are overlapping, which means we can't | ||||||
| 		-- blindly delete the [pos1, pos2] area | 		-- blindly delete the [pos1, pos2] area | ||||||
| 		local leftover = vector.new(dim) -- size of the leftover slice | 		local leftover = vector.new(dim) -- size of the leftover slice | ||||||
| 		leftover[axis] = math.abs(amount) | 		leftover[axis] = math.abs(amount) | ||||||
| 		if amount > 0 then | 		if amount > 0 then | ||||||
| 			nuke_area({x=0, y=0, z=0}, leftover) | 			nuke_area(vector.new(), leftover) | ||||||
| 		else | 		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) | 			top[axis] = dim[axis] - math.abs(amount) | ||||||
| 			nuke_area(top, leftover) | 			nuke_area(top, leftover) | ||||||
| 		end | 		end | ||||||
| @@ -309,316 +262,6 @@ function worldedit.move(pos1, pos2, axis, amount) | |||||||
| 	return worldedit.volume(pos1, pos2) | 	return worldedit.volume(pos1, pos2) | ||||||
| end | 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. | --- Attempts to fix the lighting in a region. | ||||||
| -- @return The number of nodes updated. | -- @return The number of nodes updated. | ||||||
| @@ -646,17 +289,16 @@ function worldedit.clear_objects(pos1, pos2) | |||||||
| 			return false | 			return false | ||||||
| 		end | 		end | ||||||
| 		local entity = obj:get_luaentity() | 		local entity = obj:get_luaentity() | ||||||
| 		return not entity or not entity.name:find("^worldedit:") | 		return not (entity and entity.name:find("^worldedit:")) | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
| 	-- Offset positions to include full nodes (positions are in the center of nodes) | 	-- 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 | 	pos1 = vector.add(pos1, -0.5) | ||||||
| 	local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5 | 	pos2 = vector.add(pos2, 0.5) | ||||||
|  |  | ||||||
| 	local count = 0 | 	local count = 0 | ||||||
| 	if minetest.get_objects_in_area then | 	if minetest.get_objects_in_area then | ||||||
| 		local objects = minetest.get_objects_in_area({x=pos1x, y=pos1y, z=pos1z}, | 		local objects = minetest.get_objects_in_area(pos1, pos2) | ||||||
| 			{x=pos2x, y=pos2y, z=pos2z}) |  | ||||||
|  |  | ||||||
| 		for _, obj in pairs(objects) do | 		for _, obj in pairs(objects) do | ||||||
| 			if should_delete(obj) then | 			if should_delete(obj) then | ||||||
| @@ -670,21 +312,22 @@ function worldedit.clear_objects(pos1, pos2) | |||||||
| 	-- Fallback implementation via get_objects_inside_radius | 	-- Fallback implementation via get_objects_inside_radius | ||||||
| 	-- Center of region | 	-- Center of region | ||||||
| 	local center = { | 	local center = { | ||||||
| 		x = pos1x + ((pos2x - pos1x) / 2), | 		x = pos1.x + ((pos2.x - pos1.x) / 2), | ||||||
| 		y = pos1y + ((pos2y - pos1y) / 2), | 		y = pos1.y + ((pos2.y - pos1.y) / 2), | ||||||
| 		z = pos1z + ((pos2z - pos1z) / 2) | 		z = pos1.z + ((pos2.z - pos1.z) / 2) | ||||||
| 	} | 	} | ||||||
| 	-- Bounding sphere radius | 	-- Bounding sphere radius | ||||||
| 	local radius = math.sqrt( | 	local radius = math.sqrt( | ||||||
| 			(center.x - pos1x) ^ 2 + | 			(center.x - pos1.x) ^ 2 + | ||||||
| 			(center.y - pos1y) ^ 2 + | 			(center.y - pos1.y) ^ 2 + | ||||||
| 			(center.z - pos1z) ^ 2) | 			(center.z - pos1.z) ^ 2) | ||||||
| 	for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do | 	local objects = minetest.get_objects_inside_radius(center, radius) | ||||||
|  | 	for _, obj in pairs(objects) do | ||||||
| 		if should_delete(obj) then | 		if should_delete(obj) then | ||||||
| 			local pos = obj:get_pos() | 			local pos = obj:get_pos() | ||||||
| 			if pos.x >= pos1x and pos.x <= pos2x and | 			if pos.x >= pos1.x and pos.x <= pos2.x and | ||||||
| 					pos.y >= pos1y and pos.y <= pos2y and | 					pos.y >= pos1.y and pos.y <= pos2.y and | ||||||
| 					pos.z >= pos1z and pos.z <= pos2z then | 					pos.z >= pos1.z and pos.z <= pos2.z then | ||||||
| 				-- Inside region | 				-- Inside region | ||||||
| 				obj:remove() | 				obj:remove() | ||||||
| 				count = count + 1 | 				count = count + 1 | ||||||
| @@ -693,4 +336,3 @@ function worldedit.clear_objects(pos1, pos2) | |||||||
| 	end | 	end | ||||||
| 	return count | 	return count | ||||||
| end | end | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,31 +14,26 @@ local mh = worldedit.manip_helpers | |||||||
| -- @return The number of nodes added. | -- @return The number of nodes added. | ||||||
| function worldedit.cube(pos, width, height, length, node_name, hollow) | function worldedit.cube(pos, width, height, length, node_name, hollow) | ||||||
| 	-- Set up voxel manipulator | 	-- Set up voxel manipulator | ||||||
| 	local basepos = vector.subtract(pos, {x=math.floor(width/2), y=0, z=math.floor(length/2)}) | 	local basepos = vector.subtract(pos, | ||||||
| 	local manip, area = mh.init(basepos, vector.add(basepos, {x=width, y=height, z=length})) | 		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) | 	local data = mh.get_empty_data(area) | ||||||
|  |  | ||||||
| 	-- Add cube | 	-- Add cube | ||||||
| 	local node_id = minetest.get_content_id(node_name) | 	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 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 | 	for vi in iterfunc do | ||||||
| 		local index_z = (offset.z + z) * stride.z + 1 -- +1 for 1-based indexing | 		data[vi] = node_id | ||||||
| 		for y = 0, height-1 do | 		count = count + 1 | ||||||
| 			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 |  | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
| 	mh.finish(manip, data) | 	mh.finish(manip, data) | ||||||
| @@ -149,7 +144,7 @@ function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, holl | |||||||
| 	end | 	end | ||||||
|  |  | ||||||
| 	-- Handle negative lengths | 	-- 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 | 	if length < 0 then | ||||||
| 		length = -length | 		length = -length | ||||||
| 		current_pos[axis] = current_pos[axis] - 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) | 	-- Add desired shape (anything inbetween cylinder & cone) | ||||||
| 	local node_id = minetest.get_content_id(node_name) | 	local node_id = minetest.get_content_id(node_name) | ||||||
| 	local stride = {x=1, y=area.ystride, z=area.zstride} | 	local stride = vector.new(1, area.ystride, area.zstride) | ||||||
| 	local offset = { | 	local offset = vector.subtract(current_pos, area.MinEdge) | ||||||
| 		x = current_pos.x - area.MinEdge.x, |  | ||||||
| 		y = current_pos.y - area.MinEdge.y, |  | ||||||
| 		z = current_pos.z - area.MinEdge.z, |  | ||||||
| 	} |  | ||||||
| 	local count = 0 | 	local count = 0 | ||||||
| 	for i = 0, length - 1 do | 	for i = 0, length - 1 do | ||||||
| 		-- Calulate radius for this "height" in the cylinder | 		-- Calulate radius for this "height" in the cylinder | ||||||
| @@ -225,12 +216,8 @@ function worldedit.pyramid(pos, axis, height, node_name, hollow) | |||||||
|  |  | ||||||
| 	-- Add pyramid | 	-- Add pyramid | ||||||
| 	local node_id = minetest.get_content_id(node_name) | 	local node_id = minetest.get_content_id(node_name) | ||||||
| 	local stride = {x=1, y=area.ystride, z=area.zstride} | 	local stride = vector.new(1, area.ystride, area.zstride) | ||||||
| 	local offset = { | 	local offset = vector.subtract(pos, area.MinEdge) | ||||||
| 		x = pos.x - area.MinEdge.x, |  | ||||||
| 		y = pos.y - area.MinEdge.y, |  | ||||||
| 		z = pos.z - area.MinEdge.z, |  | ||||||
| 	} |  | ||||||
| 	local size = math.abs(height * step) | 	local size = math.abs(height * step) | ||||||
| 	local count = 0 | 	local count = 0 | ||||||
| 	-- For each level of the pyramid | 	-- For each level of the pyramid | ||||||
| @@ -242,8 +229,8 @@ function worldedit.pyramid(pos, axis, height, node_name, hollow) | |||||||
| 			for index3 = -size, size do | 			for index3 = -size, size do | ||||||
| 				local i = new_index2 + (index3 + offset[other2]) * stride[other2] | 				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 | 				if (not hollow or size - math.abs(index2) < 2 or size - math.abs(index3) < 2) then | ||||||
| 				       data[i] = node_id | 					data[i] = node_id | ||||||
| 				       count = count + 1 | 					count = count + 1 | ||||||
| 				end | 				end | ||||||
| 			end | 			end | ||||||
| 		end | 		end | ||||||
| @@ -271,9 +258,9 @@ function worldedit.spiral(pos, length, height, spacer, node_name) | |||||||
|  |  | ||||||
| 	-- Set up variables | 	-- Set up variables | ||||||
| 	local node_id = minetest.get_content_id(node_name) | 	local node_id = minetest.get_content_id(node_name) | ||||||
| 	local stride = {x=1, y=area.ystride, z=area.zstride} | 	local stride = vector.new(1, area.ystride, area.zstride) | ||||||
| 	local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z | 	local offset = vector.subtract(pos, area.MinEdge) | ||||||
| 	local i = offset_z * stride.z + offset_y * stride.y + offset_x + 1 | 	local i = offset.z * stride.z + offset.y * stride.y + offset.x + 1 | ||||||
|  |  | ||||||
| 	-- Add first column | 	-- Add first column | ||||||
| 	local count = height | 	local count = height | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ function worldedit.serialize(pos1, pos2) | |||||||
| 		has_meta[hash_node_position(meta_positions[i])] = true | 		has_meta[hash_node_position(meta_positions[i])] = true | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
| 	local pos = {x=pos1.x, y=0, z=0} | 	local pos = vector.new(pos1.x, 0, 0) | ||||||
| 	local count = 0 | 	local count = 0 | ||||||
| 	local result = {} | 	local result = {} | ||||||
| 	while pos.x <= pos2.x do | 	while pos.x <= pos2.x do | ||||||
| @@ -114,12 +114,15 @@ function worldedit.serialize(pos1, pos2) | |||||||
| 	return LATEST_SERIALIZATION_HEADER .. result, count | 	return LATEST_SERIALIZATION_HEADER .. result, count | ||||||
| end | 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 function deserialize_workaround(content) | ||||||
| 	local nodes | 	local nodes, err | ||||||
| 	if not minetest.global_exists("jit") then | 	if not minetest.global_exists("jit") then | ||||||
| 		nodes = minetest.deserialize(content, true) | 		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 | 	else | ||||||
| 		-- XXX: This is a filthy hack that works surprisingly well | 		-- XXX: This is a filthy hack that works surprisingly well | ||||||
| 		-- in LuaJIT, `minetest.deserialize` will fail due to the register limit | 		-- 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 escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end) | ||||||
| 		local startpos, startpos1 = 1, 1 | 		local startpos, startpos1 = 1, 1 | ||||||
| 		local endpos | 		local endpos | ||||||
|  | 		local entry | ||||||
| 		while true do -- go through each individual node entry (except the last) | 		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 | 			if not startpos then | ||||||
| 				break | 				break | ||||||
| 			end | 			end | ||||||
| 			local current = content:sub(startpos1, startpos) | 			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) | 			table.insert(nodes, entry) | ||||||
| 			startpos, startpos1 = endpos, endpos | 			startpos, startpos1 = endpos, endpos | ||||||
| 		end | 		end | ||||||
| 		local entry = minetest.deserialize("return " .. content:sub(startpos1), true) -- process the last entry | 		if not err then | ||||||
| 		table.insert(nodes, entry) | 			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 | 	end | ||||||
| 	return nodes | 	return nodes | ||||||
| end | end | ||||||
| @@ -148,7 +160,7 @@ end | |||||||
| --- Loads the schematic in `value` into a node list in the latest format. | --- 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. | -- @return A node list in the latest format, or nil on failure. | ||||||
| local function load_schematic(value) | local function load_schematic(value) | ||||||
| 	local version, header, content = worldedit.read_header(value) | 	local version, _, content = worldedit.read_header(value) | ||||||
| 	local nodes = {} | 	local nodes = {} | ||||||
| 	if version == 1 or version == 2 then -- Original flat table format | 	if version == 1 or version == 2 then -- Original flat table format | ||||||
| 		local tables = minetest.deserialize(content, true) | 		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 y > pos2y then pos2y = y end | ||||||
| 		if z > pos2z then pos2z = z end | 		if z > pos2z then pos2z = z end | ||||||
| 	end | 	end | ||||||
| 	local pos1 = {x=pos1x, y=pos1y, z=pos1z} | 	return vector.new(pos1x, pos1y, pos1z), vector.new(pos2x, pos2y, pos2z), #nodes | ||||||
| 	local pos2 = {x=pos2x, y=pos2y, z=pos2z} |  | ||||||
| 	return pos1, pos2, #nodes |  | ||||||
| end | end | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -240,14 +250,16 @@ function worldedit.deserialize(origin_pos, value) | |||||||
| 	worldedit.keep_loaded(pos1, pos2) | 	worldedit.keep_loaded(pos1, pos2) | ||||||
|  |  | ||||||
| 	local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z | 	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 add_node, get_meta = minetest.add_node, minetest.get_meta | ||||||
|  | 	local registered_nodes = minetest.registered_nodes | ||||||
| 	for i, entry in ipairs(nodes) do | 	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 | 		if registered_nodes[entry.name] then | ||||||
| 		-- Entry acts as both position and node | 			entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z | ||||||
| 		add_node(entry, entry) | 			-- Entry acts as both position and node | ||||||
| 		if entry.meta then | 			add_node(entry, entry) | ||||||
| 			get_meta(entry):from_table(entry.meta) | 			if entry.meta then | ||||||
|  | 				get_meta(entry):from_table(entry.meta) | ||||||
|  | 			end | ||||||
| 		end | 		end | ||||||
| 	end | 	end | ||||||
| 	return #nodes | 	return #nodes | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
|  | -- TODO: don't shit individual variables into the globals | ||||||
|  | 
 | ||||||
| --------------------- | --------------------- | ||||||
| -- Helpers | -- Helpers | ||||||
| --------------------- | --------------------- | ||||||
| 
 |  | ||||||
| local vec = vector.new | local vec = vector.new | ||||||
| local vecw = function(axis, n, base) | local vecw = function(axis, n, base) | ||||||
| 	local ret = vec(base) | 	local ret = vec(base) | ||||||
| @@ -16,9 +17,9 @@ local set_node = minetest.set_node | |||||||
| -- Nodes | -- Nodes | ||||||
| --------------------- | --------------------- | ||||||
| local air = "air" | local air = "air" | ||||||
| local testnode1 | rawset(_G, "testnode1", "") | ||||||
| local testnode2 | rawset(_G, "testnode2", "") | ||||||
| local testnode3 | rawset(_G, "testnode3", "") | ||||||
| -- Loads nodenames to use for tests | -- Loads nodenames to use for tests | ||||||
| local function init_nodes() | local function init_nodes() | ||||||
| 	testnode1 = minetest.registered_aliases["mapgen_stone"] | 	testnode1 = minetest.registered_aliases["mapgen_stone"] | ||||||
| @@ -27,7 +28,7 @@ local function init_nodes() | |||||||
| 	assert(testnode1 and testnode2 and testnode3) | 	assert(testnode1 and testnode2 and testnode3) | ||||||
| end | end | ||||||
| -- Writes repeating pattern into given area | -- Writes repeating pattern into given area | ||||||
| local function place_pattern(pos1, pos2, pattern) | rawset(_G, "place_pattern", function(pos1, pos2, pattern) | ||||||
| 	local pos = vec() | 	local pos = vec() | ||||||
| 	local node = {name=""} | 	local node = {name=""} | ||||||
| 	local i = 1 | 	local i = 1 | ||||||
| @@ -43,14 +44,14 @@ local function place_pattern(pos1, pos2, pattern) | |||||||
| 	end | 	end | ||||||
| 	end | 	end | ||||||
| 	end | 	end | ||||||
| end | end) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| --------------------- | --------------------- | ||||||
| -- Area management | -- Area management | ||||||
| --------------------- | --------------------- | ||||||
| assert(minetest.get_mapgen_setting("mg_name") == "singlenode") | assert(minetest.get_mapgen_setting("mg_name") == "singlenode") | ||||||
| local area = {} | rawset(_G, "area", {}) | ||||||
| do | do | ||||||
| 	local areamin, areamax | 	local areamin, areamax | ||||||
| 	local off | 	local off | ||||||
| @@ -71,6 +72,9 @@ do | |||||||
| 	end | 	end | ||||||
| 	-- Reset area contents and state | 	-- Reset area contents and state | ||||||
| 	area.clear = function() | 	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 vmanip = minetest.get_voxel_manip(areamin, areamax) | ||||||
| 		local vpos1, vpos2 = vmanip:get_emerged_area() | 		local vpos1, vpos2 = vmanip:get_emerged_area() | ||||||
| 		local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1) | 		local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1) | ||||||
| @@ -87,10 +91,10 @@ do | |||||||
| 	-- Returns an usable area [pos1, pos2] that does not overlap previous ones | 	-- Returns an usable area [pos1, pos2] that does not overlap previous ones | ||||||
| 	area.get = function(sizex, sizey, sizez) | 	area.get = function(sizex, sizey, sizez) | ||||||
| 		local size | 		local size | ||||||
| 		if sizey == nil or sizez == nil then | 		if sizey == nil and sizez == nil then | ||||||
| 			size = {x=sizex, y=sizex, z=sizex} | 			size = vec(sizex, sizex, sizex) | ||||||
| 		else | 		else | ||||||
| 			size = {x=sizex, y=sizey, z=sizez} | 			size = vec(sizex, sizey, sizez) | ||||||
| 		end | 		end | ||||||
| 		local pos1 = vector.add(areamin, off) | 		local pos1 = vector.add(areamin, off) | ||||||
| 		local pos2 = vector.subtract(vector.add(pos1, size), 1) | 		local pos2 = vector.subtract(vector.add(pos1, size), 1) | ||||||
| @@ -148,7 +152,7 @@ end | |||||||
| --------------------- | --------------------- | ||||||
| -- Checks | -- Checks | ||||||
| --------------------- | --------------------- | ||||||
| local check = {} | rawset(_G, "check", {}) | ||||||
| -- Check that all nodes in [pos1, pos2] are the node(s) specified | -- Check that all nodes in [pos1, pos2] are the node(s) specified | ||||||
| check.filled = function(pos1, pos2, nodes) | check.filled = function(pos1, pos2, nodes) | ||||||
| 	if type(nodes) == "string" then | 	if type(nodes) == "string" then | ||||||
| @@ -215,7 +219,7 @@ end | |||||||
| -- The actual tests | -- The actual tests | ||||||
| --------------------- | --------------------- | ||||||
| local tests = {} | local tests = {} | ||||||
| local function register_test(name, func, opts) | worldedit.register_test = function(name, func, opts) | ||||||
| 	assert(type(name) == "string") | 	assert(type(name) == "string") | ||||||
| 	assert(func == nil or type(func) == "function") | 	assert(func == nil or type(func) == "function") | ||||||
| 	if not opts then | 	if not opts then | ||||||
| @@ -227,6 +231,7 @@ local function register_test(name, func, opts) | |||||||
| 	opts.func = func | 	opts.func = func | ||||||
| 	table.insert(tests, opts) | 	table.insert(tests, opts) | ||||||
| end | end | ||||||
|  | local register_test = worldedit.register_test | ||||||
| -- How this works: | -- How this works: | ||||||
| --   register_test registers a test with a name and function | --   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 function should return if the test passes or otherwise cause a Lua error | ||||||
| @@ -241,8 +246,8 @@ end | |||||||
| register_test("Internal self-test") | register_test("Internal self-test") | ||||||
| register_test("is area loaded?", function() | register_test("is area loaded?", function() | ||||||
| 	local pos1, _ = area.get(1) | 	local pos1, _ = area.get(1) | ||||||
| 	assert(get_node(pos1).name == "air") | 	assert(get_node(pos1).name == air) | ||||||
| end, {dry=true}) | end) | ||||||
| 
 | 
 | ||||||
| register_test("area.split", function() | register_test("area.split", function() | ||||||
| 	for i = 2, 6 do | 	for i = 2, 6 do | ||||||
| @@ -255,7 +260,7 @@ register_test("area.split", function() | |||||||
| 			assert((half1.z - pos1.z) == (pos2.z - half2.z)) -- divided equally | 			assert((half1.z - pos1.z) == (pos2.z - half2.z)) -- divided equally | ||||||
| 		end | 		end | ||||||
| 	end | 	end | ||||||
| end, {dry=true}) | end) | ||||||
| 
 | 
 | ||||||
| register_test("check.filled", function() | register_test("check.filled", function() | ||||||
| 	local pos1, pos2 = area.get(1, 2, 1) | 	local pos1, pos2 = area.get(1, 2, 1) | ||||||
| @@ -276,118 +281,49 @@ register_test("pattern", function() | |||||||
| end) | end) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| register_test("Generic node manipulations") | for _, name in ipairs({ | ||||||
| register_test("worldedit.set", function() | 	"manipulations", "primitives", "schematic" | ||||||
| 	local pos1, pos2 = area.get(10) | }) do | ||||||
| 	local m = area.margin(1) | 	dofile(minetest.get_modpath("worldedit") .. "/test/" .. name .. ".lua") | ||||||
|  | end | ||||||
| 
 | 
 | ||||||
| 	worldedit.set(pos1, pos2, testnode1) |  | ||||||
| 
 | 
 | ||||||
| 	check.filled(pos1, pos2, testnode1) | register_test("Code") | ||||||
| 	check.filled2(m, air) | 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) | end) | ||||||
| 
 | 
 | ||||||
| register_test("worldedit.set mix", function() | register_test("worldedit.luatransform", function() | ||||||
| 	local pos1, pos2 = area.get(10) | 	local pos1, pos2 = area.get(2) | ||||||
| 	local m = area.margin(1) |  | ||||||
| 
 | 
 | ||||||
| 	worldedit.set(pos1, pos2, {testnode1, testnode2}) | 	-- syntax error | ||||||
|  | 	local err = worldedit.luatransform(pos1, pos2, "?") | ||||||
|  | 	assert(err:find("unexpected symbol")) | ||||||
| 
 | 
 | ||||||
| 	check.filled(pos1, pos2, {testnode1, testnode2}) | 	-- runtime error | ||||||
| 	check.filled2(m, air) | 	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) | end) | ||||||
| 
 | 
 | ||||||
| register_test("worldedit.replace", function() |  | ||||||
| 	local pos1, pos2 = area.get(10) |  | ||||||
| 	local half1, half2 = area.split(pos1, pos2) |  | ||||||
| 
 |  | ||||||
| 	worldedit.set(pos1, half1, testnode1) |  | ||||||
| 	worldedit.set(half2, pos2, testnode2) |  | ||||||
| 	worldedit.replace(pos1, pos2, testnode1, testnode3) |  | ||||||
| 
 |  | ||||||
| 	check.not_filled(pos1, pos2, testnode1) |  | ||||||
| 	check.filled(pos1, half1, testnode3) |  | ||||||
| 	check.filled(half2, pos2, testnode2) |  | ||||||
| end) |  | ||||||
| 
 |  | ||||||
| register_test("worldedit.replace inverse", function() |  | ||||||
| 	local pos1, pos2 = area.get(10) |  | ||||||
| 	local half1, half2 = area.split(pos1, pos2) |  | ||||||
| 
 |  | ||||||
| 	worldedit.set(pos1, half1, testnode1) |  | ||||||
| 	worldedit.set(half2, pos2, testnode2) |  | ||||||
| 	worldedit.replace(pos1, pos2, testnode1, testnode3, true) |  | ||||||
| 
 |  | ||||||
| 	check.filled(pos1, half1, testnode1) |  | ||||||
| 	check.filled(half2, pos2, testnode3) |  | ||||||
| end) |  | ||||||
| 
 |  | ||||||
| -- FIXME?: this one looks overcomplicated |  | ||||||
| register_test("worldedit.copy", function() |  | ||||||
| 	local pos1, pos2 = area.get(4) |  | ||||||
| 	local axis, n = area.dir(2) |  | ||||||
| 	local m = area.margin(1) |  | ||||||
| 	local b = pos1[axis] |  | ||||||
| 
 |  | ||||||
| 	-- create one slice with testnode1, one with testnode2 |  | ||||||
| 	worldedit.set(pos1, vecw(axis, b + 1, pos2), testnode1) |  | ||||||
| 	worldedit.set(vecw(axis, b + 2, pos1), pos2, testnode2) |  | ||||||
| 	worldedit.copy(pos1, pos2, axis, n) |  | ||||||
| 
 |  | ||||||
| 	-- should have three slices now |  | ||||||
| 	check.filled(pos1, vecw(axis, b + 1, pos2), testnode1) |  | ||||||
| 	check.filled(vecw(axis, b + 2, pos1), pos2, testnode1) |  | ||||||
| 	check.filled(vecw(axis, b + 4, pos1), vector.add(pos2, vecw(axis, n)), testnode2) |  | ||||||
| 	check.filled2(m, "air") |  | ||||||
| end) |  | ||||||
| 
 |  | ||||||
| register_test("worldedit.copy2", function() |  | ||||||
| 	local pos1, pos2 = area.get(6) |  | ||||||
| 	local m1 = area.margin(1) |  | ||||||
| 	local pos1_, pos2_ = area.get(6) |  | ||||||
| 	local m2 = area.margin(1) |  | ||||||
| 
 |  | ||||||
| 	local pattern = {testnode1, testnode2, testnode3, testnode1, testnode2} |  | ||||||
| 	place_pattern(pos1, pos2, pattern) |  | ||||||
| 	worldedit.copy2(pos1, pos2, vector.subtract(pos1_, pos1)) |  | ||||||
| 
 |  | ||||||
| 	check.pattern(pos1, pos2, pattern) |  | ||||||
| 	check.pattern(pos1_, pos2_, pattern) |  | ||||||
| 	check.filled2(m1, "air") |  | ||||||
| 	check.filled2(m2, "air") |  | ||||||
| end) |  | ||||||
| 
 |  | ||||||
| register_test("worldedit.move (overlap)", function() |  | ||||||
| 	local pos1, pos2 = area.get(7) |  | ||||||
| 	local axis, n = area.dir(2) |  | ||||||
| 	local m = area.margin(1) |  | ||||||
| 
 |  | ||||||
| 	local pattern = {testnode2, testnode1, testnode2, testnode3, testnode3} |  | ||||||
| 	place_pattern(pos1, pos2, pattern) |  | ||||||
| 	worldedit.move(pos1, pos2, axis, n) |  | ||||||
| 
 |  | ||||||
| 	check.filled(pos1, vecw(axis, pos1[axis] + n - 1, pos2), "air") |  | ||||||
| 	check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern) |  | ||||||
| 	check.filled2(m, "air") |  | ||||||
| end) |  | ||||||
| 
 |  | ||||||
| register_test("worldedit.move", function() |  | ||||||
| 	local pos1, pos2 = area.get(10) |  | ||||||
| 	local axis, n = area.dir(10) |  | ||||||
| 	local m = area.margin(1) |  | ||||||
| 
 |  | ||||||
| 	local pattern = {testnode1, testnode3, testnode3, testnode2} |  | ||||||
| 	place_pattern(pos1, pos2, pattern) |  | ||||||
| 	worldedit.move(pos1, pos2, axis, n) |  | ||||||
| 
 |  | ||||||
| 	check.filled(pos1, pos2, "air") |  | ||||||
| 	check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern) |  | ||||||
| 	check.filled2(m, "air") |  | ||||||
| end) |  | ||||||
| 
 |  | ||||||
| -- TODO: the rest (also testing param2 + metadata) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| --------------------- | --------------------- | ||||||
| -- Main function | -- Main function | ||||||
| --------------------- | --------------------- | ||||||
| @@ -406,7 +342,7 @@ worldedit.run_tests = function() | |||||||
| 	for x = 0, math.floor(wanted.x/16) do | 	for x = 0, math.floor(wanted.x/16) do | ||||||
| 	for y = 0, math.floor(wanted.y/16) do | 	for y = 0, math.floor(wanted.y/16) do | ||||||
| 	for z = 0, math.floor(wanted.z/16) do | 	for z = 0, math.floor(wanted.z/16) do | ||||||
| 		assert(minetest.forceload_block({x=x*16, y=y*16, z=z*16}, true)) | 		assert(minetest.forceload_block(vec(x*16, y*16, z*16), true, -1)) | ||||||
| 	end | 	end | ||||||
| 	end | 	end | ||||||
| 	end | 	end | ||||||
| @@ -418,9 +354,7 @@ worldedit.run_tests = function() | |||||||
| 				local s = "---- " .. test.name .. " " | 				local s = "---- " .. test.name .. " " | ||||||
| 				print(s .. string.rep("-", 60 - #s)) | 				print(s .. string.rep("-", 60 - #s)) | ||||||
| 			else | 			else | ||||||
| 				if not test.dry then | 				area.clear() | ||||||
| 					area.clear() |  | ||||||
| 				end |  | ||||||
| 				local ok, err = pcall(test.func) | 				local ok, err = pcall(test.func) | ||||||
| 				print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL")) | 				print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL")) | ||||||
| 				if not ok then | 				if not ok then | ||||||
							
								
								
									
										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) | 	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, | 	local get_node, get_meta, swap_node = minetest.get_node, | ||||||
| 			minetest.get_meta, minetest.swap_node | 			minetest.get_meta, minetest.swap_node | ||||||
| 	while pos.x <= pos2.x do | 	while pos.x <= pos2.x do | ||||||
| @@ -79,7 +79,7 @@ function worldedit.highlight(pos1, pos2, node_name) | |||||||
|  |  | ||||||
| 	worldedit.keep_loaded(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, | 	local get_node, get_meta, swap_node = minetest.get_node, | ||||||
| 			minetest.get_meta, minetest.swap_node | 			minetest.get_meta, minetest.swap_node | ||||||
| 	local count = 0 | 	local count = 0 | ||||||
|   | |||||||
| @@ -1,8 +1,4 @@ | |||||||
| if minetest.raycast == nil then | local S = minetest.get_translator("worldedit_brush") | ||||||
| 	error( |  | ||||||
| 		"worldedit_brush requires at least Minetest 5.0" |  | ||||||
| 	) |  | ||||||
| end |  | ||||||
|  |  | ||||||
| local BRUSH_MAX_DIST = 150 | local BRUSH_MAX_DIST = 150 | ||||||
| local brush_on_use = function(itemstack, placer) | local brush_on_use = function(itemstack, placer) | ||||||
| @@ -12,7 +8,8 @@ local brush_on_use = function(itemstack, placer) | |||||||
| 	local cmd = meta:get_string("command") | 	local cmd = meta:get_string("command") | ||||||
| 	if cmd == "" then | 	if cmd == "" then | ||||||
| 		worldedit.player_notify(name, | 		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 | 		return false | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
| @@ -22,17 +19,17 @@ local brush_on_use = function(itemstack, placer) | |||||||
| 	local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs) | 	local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs) | ||||||
| 	if not has_privs then | 	if not has_privs then | ||||||
| 		worldedit.player_notify(name, | 		worldedit.player_notify(name, | ||||||
| 			"Missing privileges: " .. table.concat(missing_privs, ", ")) | 			S("Missing privileges: @1", table.concat(missing_privs, ", ")), "error") | ||||||
| 		return false | 		return false | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
| 	local raybegin = vector.add(placer:get_pos(), | 	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 rayend = vector.add(raybegin, vector.multiply(placer:get_look_dir(), BRUSH_MAX_DIST)) | ||||||
| 	local ray = minetest.raycast(raybegin, rayend, false, true) | 	local ray = minetest.raycast(raybegin, rayend, false, true) | ||||||
| 	local pointed_thing = ray:next() | 	local pointed_thing = ray:next() | ||||||
| 	if pointed_thing == nil then | 	if pointed_thing == nil then | ||||||
| 		worldedit.player_notify(name, "Too far away.") | 		worldedit.player_notify(name, S("Too far away."), "error") | ||||||
| 		return false | 		return false | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
| @@ -41,17 +38,17 @@ local brush_on_use = function(itemstack, placer) | |||||||
| 	worldedit.pos2[name] = nil | 	worldedit.pos2[name] = nil | ||||||
| 	worldedit.marker_update(name) | 	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) | 	assert(cmddef.require_pos < 2) | ||||||
| 	local parsed = {cmddef.parse(meta:get_string("params"))} | 	local parsed = {cmddef.parse(meta:get_string("params"))} | ||||||
| 	if not table.remove(parsed, 1) then return false end -- shouldn't happen | 	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", | 	minetest.log("action", string.format("%s uses WorldEdit brush (//%s) at %s", | ||||||
| 		name, cmd, minetest.pos_to_string(pointed_thing.under))) | 		name, cmd, minetest.pos_to_string(pointed_thing.under))) | ||||||
| 	cmddef.func(name, unpack(parsed)) | 	cmddef.func(name, unpack(parsed)) | ||||||
| @@ -61,7 +58,7 @@ local brush_on_use = function(itemstack, placer) | |||||||
| end | end | ||||||
|  |  | ||||||
| minetest.register_tool(":worldedit:brush", { | minetest.register_tool(":worldedit:brush", { | ||||||
| 	description = "WorldEdit Brush", | 	description = S("WorldEdit Brush"), | ||||||
| 	inventory_image = "worldedit_brush.png", | 	inventory_image = "worldedit_brush.png", | ||||||
| 	stack_max = 1, -- no need to stack these (metadata prevents this anyway) | 	stack_max = 1, -- no need to stack these (metadata prevents this anyway) | ||||||
| 	range = 0, | 	range = 0, | ||||||
| @@ -74,7 +71,7 @@ minetest.register_tool(":worldedit:brush", { | |||||||
| worldedit.register_command("brush", { | worldedit.register_command("brush", { | ||||||
| 	privs = {worldedit=true}, | 	privs = {worldedit=true}, | ||||||
| 	params = "none/<cmd> [parameters]", | 	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) | 	parse = function(param) | ||||||
| 		local found, _, cmd, params = param:find("^([^%s]+)%s+(.+)$") | 		local found, _, cmd, params = param:find("^([^%s]+)%s+(.+)$") | ||||||
| 		if not found then | 		if not found then | ||||||
| @@ -87,39 +84,39 @@ worldedit.register_command("brush", { | |||||||
| 		return true, cmd, params | 		return true, cmd, params | ||||||
| 	end, | 	end, | ||||||
| 	func = function(name, cmd, params) | 	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 | 		if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then | ||||||
| 			worldedit.player_notify(name, "Not holding brush item.") | 			return false, S("Not holding brush item.") | ||||||
| 			return |  | ||||||
| 		end | 		end | ||||||
|  |  | ||||||
| 		cmd = cmd:lower() | 		cmd = cmd:lower() | ||||||
| 		local meta = itemstack:get_meta() | 		local meta = itemstack:get_meta() | ||||||
| 		if cmd == "none" then | 		if cmd == "none" then | ||||||
| 			meta:from_table(nil) | 			meta:from_table(nil) | ||||||
| 			worldedit.player_notify(name, "Brush assignment cleared.") | 			worldedit.player_notify(name, S("Brush assignment cleared."), "ok") | ||||||
| 		else | 		else | ||||||
| 			local cmddef = worldedit.registered_commands[cmd] | 			local cmddef = worldedit.registered_commands[cmd] | ||||||
| 			if cmddef == nil or cmddef.require_pos ~= 1 then | 			if cmddef == nil or cmddef.require_pos ~= 1 then | ||||||
| 				worldedit.player_notify(name, "//" .. cmd .. " cannot be used with brushes") | 				return false, S("@1 cannot be used with brushes", | ||||||
| 				return | 					minetest.colorize("#00ffff", "//"..cmd)) | ||||||
| 			end | 			end | ||||||
|  |  | ||||||
| 			-- Try parsing command params so we can give the user feedback | 			-- Try parsing command params so we can give the user feedback | ||||||
| 			local ok, err = cmddef.parse(params) | 			local ok, err = cmddef.parse(params) | ||||||
| 			if not ok then | 			if not ok then | ||||||
| 				err = err or "invalid usage" | 				err = err or S("invalid usage") | ||||||
| 				worldedit.player_notify(name, "Error with brush command: " .. err) | 				return false, S("Error with command: @1", err) | ||||||
| 				return |  | ||||||
| 			end | 			end | ||||||
|  |  | ||||||
| 			meta:set_string("command", cmd) | 			meta:set_string("command", cmd) | ||||||
| 			meta:set_string("params", params) | 			meta:set_string("params", params) | ||||||
| 			local fullcmd = "//" .. cmd .. " " .. params | 			local fullcmd = minetest.colorize("#00ffff", "//"..cmd) .. " " .. params | ||||||
| 			meta:set_string("description", | 			meta:set_string("description", | ||||||
| 				minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd) | 				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 | 		end | ||||||
| 		minetest.get_player_by_name(name):set_wielded_item(itemstack) | 		player:set_wielded_item(itemstack) | ||||||
| 	end, | 	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", { | worldedit.register_command("outset", { | ||||||
| 	params = "[h/v] <amount>", | 	params = "[h/v] <amount>", | ||||||
| 	description = "Outset the selected region.", | 	description = S("Outset the selected region."), | ||||||
|  | 	category = S("Region operations"), | ||||||
| 	privs = {worldedit=true}, | 	privs = {worldedit=true}, | ||||||
| 	require_pos = 2, | 	require_pos = 2, | ||||||
| 	parse = function(param) | 	parse = function(param) | ||||||
| @@ -11,7 +14,7 @@ worldedit.register_command("outset", { | |||||||
|  |  | ||||||
| 		local hv_test = dir:find("[^hv]+") | 		local hv_test = dir:find("[^hv]+") | ||||||
| 		if hv_test ~= nil then | 		if hv_test ~= nil then | ||||||
| 			return false, "Invalid direction." | 			return false, S("Invalid direction: @1", dir) | ||||||
| 		end | 		end | ||||||
|  |  | ||||||
| 		return true, dir, tonumber(amount) | 		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)) | ||||||
| 			assert(worldedit.cuboid_linear_expand(name, 'y', -1, amount)) | 			assert(worldedit.cuboid_linear_expand(name, 'y', -1, amount)) | ||||||
| 		else | 		else | ||||||
| 			return false, "Invalid number of arguments" | 			return false, S("Invalid number of arguments") | ||||||
| 		end | 		end | ||||||
|  |  | ||||||
| 		worldedit.marker_update(name) | 		worldedit.marker_update(name) | ||||||
| 		return true, "Region outset by " .. amount .. " blocks" | 		return true, S("Region outset by @1 nodes", amount) | ||||||
|       end, |       end, | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  |  | ||||||
| worldedit.register_command("inset", { | worldedit.register_command("inset", { | ||||||
| 	params = "[h/v] <amount>", | 	params = "[h/v] <amount>", | ||||||
| 	description = "Inset the selected region.", | 	description = S("Inset the selected region."), | ||||||
|  | 	category = S("Region operations"), | ||||||
| 	privs = {worldedit=true}, | 	privs = {worldedit=true}, | ||||||
| 	require_pos = 2, | 	require_pos = 2, | ||||||
| 	parse = function(param) | 	parse = function(param) | ||||||
| @@ -48,7 +52,7 @@ worldedit.register_command("inset", { | |||||||
| 			return false | 			return false | ||||||
| 		end | 		end | ||||||
| 		if dir:find("[^hv]") ~= nil then | 		if dir:find("[^hv]") ~= nil then | ||||||
| 			return false, "Invalid direction." | 			return false, S("Invalid direction: @1", dir) | ||||||
| 		end | 		end | ||||||
|  |  | ||||||
| 		return true, dir, tonumber(amount) | 		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)) | ||||||
| 			assert(worldedit.cuboid_linear_expand(name, 'y', -1, -amount)) | 			assert(worldedit.cuboid_linear_expand(name, 'y', -1, -amount)) | ||||||
| 		else | 		else | ||||||
| 			return false, "Invalid number of arguments" | 			return false, S("Invalid number of arguments") | ||||||
| 		end | 		end | ||||||
|  |  | ||||||
| 		worldedit.marker_update(name) | 		worldedit.marker_update(name) | ||||||
| 		return true, "Region inset by " .. amount .. " blocks" | 		return true, S("Region inset by @1 nodes", amount) | ||||||
|       end, |       end, | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  |  | ||||||
| worldedit.register_command("shift", { | worldedit.register_command("shift", { | ||||||
| 	params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>", | 	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}, | 	privs = {worldedit=true}, | ||||||
| 	require_pos = 2, | 	require_pos = 2, | ||||||
| 	parse = function(param) | 	parse = function(param) | ||||||
| @@ -98,20 +103,21 @@ worldedit.register_command("shift", { | |||||||
| 		end | 		end | ||||||
|  |  | ||||||
| 		if axis == nil or dir == nil then | 		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 | 		end | ||||||
|  |  | ||||||
| 		assert(worldedit.cuboid_shift(name, axis, amount * dir)) | 		assert(worldedit.cuboid_shift(name, axis, amount * dir)) | ||||||
| 		worldedit.marker_update(name) | 		worldedit.marker_update(name) | ||||||
|  |  | ||||||
| 		return true, "Region shifted by " .. amount .. " nodes" | 		return true, S("Region shifted by @1 nodes", amount) | ||||||
|       end, |       end, | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  |  | ||||||
| worldedit.register_command("expand", { | worldedit.register_command("expand", { | ||||||
| 	params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]", | 	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}, | 	privs = {worldedit=true}, | ||||||
| 	require_pos = 2, | 	require_pos = 2, | ||||||
| 	parse = function(param) | 	parse = function(param) | ||||||
| @@ -135,7 +141,7 @@ worldedit.register_command("expand", { | |||||||
| 			axis, dir = worldedit.translate_direction(name, direction) | 			axis, dir = worldedit.translate_direction(name, direction) | ||||||
|  |  | ||||||
| 			if axis == nil or dir == nil then | 			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 | 			end | ||||||
| 		else | 		else | ||||||
| 			if direction == "?" then | 			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, amount) | ||||||
| 		worldedit.cuboid_linear_expand(name, axis, -dir, rev_amount) | 		worldedit.cuboid_linear_expand(name, axis, -dir, rev_amount) | ||||||
| 		worldedit.marker_update(name) | 		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, | 	end, | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  |  | ||||||
| worldedit.register_command("contract", { | worldedit.register_command("contract", { | ||||||
| 	params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]", | 	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}, | 	privs = {worldedit=true}, | ||||||
| 	require_pos = 2, | 	require_pos = 2, | ||||||
| 	parse = function(param) | 	parse = function(param) | ||||||
| @@ -184,7 +191,7 @@ worldedit.register_command("contract", { | |||||||
| 			axis, dir = worldedit.translate_direction(name, direction) | 			axis, dir = worldedit.translate_direction(name, direction) | ||||||
|  |  | ||||||
| 			if axis == nil or dir == nil then | 			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 | 			end | ||||||
| 		else | 		else | ||||||
| 			if direction == "?" then | 			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, -amount) | ||||||
| 		worldedit.cuboid_linear_expand(name, axis, -dir, -rev_amount) | 		worldedit.cuboid_linear_expand(name, axis, -dir, -rev_amount) | ||||||
| 		worldedit.marker_update(name) | 		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, | 	end, | ||||||
| }) | }) | ||||||
|  |  | ||||||
| worldedit.register_command("cubeapply", { | worldedit.register_command("cubeapply", { | ||||||
| 	params = "<size>/(<sizex> <sizey> <sizez>) <command> [parameters]", | 	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}, | 	privs = {worldedit=true}, | ||||||
| 	require_pos = 1, | 	require_pos = 1, | ||||||
| 	parse = function(param) | 	parse = function(param) | ||||||
| @@ -230,7 +237,8 @@ worldedit.register_command("cubeapply", { | |||||||
| 		end | 		end | ||||||
| 		local cmddef = worldedit.registered_commands[cmd] | 		local cmddef = worldedit.registered_commands[cmd] | ||||||
| 		if cmddef == nil or cmddef.require_pos ~= 2 then | 		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 | 		end | ||||||
| 		-- run parsing of target command | 		-- run parsing of target command | ||||||
| 		local parsed = {cmddef.parse(args)} | 		local parsed = {cmddef.parse(args)} | ||||||
| @@ -241,15 +249,14 @@ worldedit.register_command("cubeapply", { | |||||||
| 	end, | 	end, | ||||||
| 	nodes_needed = function(name, sidex, sidey, sidez, cmd, parsed) | 	nodes_needed = function(name, sidex, sidey, sidez, cmd, parsed) | ||||||
| 		-- its not possible to defer to the target command at this point | 		-- its not possible to defer to the target command at this point | ||||||
|  | 		-- FIXME: why not? | ||||||
| 		return sidex * sidey * sidez | 		return sidex * sidey * sidez | ||||||
| 	end, | 	end, | ||||||
| 	func = function(name, sidex, sidey, sidez, cmd, parsed) | 	func = function(name, sidex, sidey, sidez, cmd, parsed) | ||||||
| 		local cmddef = assert(worldedit.registered_commands[cmd]) | 		local cmddef = assert(worldedit.registered_commands[cmd]) | ||||||
| 		local success, missing_privs = minetest.check_player_privs(name, cmddef.privs) | 		local success, missing_privs = minetest.check_player_privs(name, cmddef.privs) | ||||||
| 		if not success then | 		if not success then | ||||||
| 			worldedit.player_notify(name, "Missing privileges: " .. | 			return false, S("Missing privileges: @1", table.concat(missing_privs, ", ")) | ||||||
| 				table.concat(missing_privs, ", ")) |  | ||||||
| 			return |  | ||||||
| 		end | 		end | ||||||
|  |  | ||||||
| 		-- update region to be the cuboid the user wanted | 		-- 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 | --marks worldedit region position 1 | ||||||
| worldedit.mark_pos1 = function(name, region_too) | 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 | 	if worldedit.marker1[name] ~= nil then --marker already exists | ||||||
| 		worldedit.marker1[name]:remove() --remove marker | 		worldedit.marker1[name]:remove() --remove marker | ||||||
| 		worldedit.marker1[name] = nil | 		worldedit.marker1[name] = nil | ||||||
| 	end | 	end | ||||||
| 	if pos1 ~= nil then | 	if pos1 ~= nil then | ||||||
| 		--make area stay loaded | 		worldedit.keep_loaded(pos1, pos1) | ||||||
| 		local manip = minetest.get_voxel_manip() |  | ||||||
| 		manip:read_from_map(pos1, pos1) |  | ||||||
| 
 | 
 | ||||||
| 		--add marker | 		--add marker | ||||||
| 		worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel) | 		worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel) | ||||||
| @@ -30,16 +28,14 @@ end | |||||||
| 
 | 
 | ||||||
| --marks worldedit region position 2 | --marks worldedit region position 2 | ||||||
| worldedit.mark_pos2 = function(name, region_too) | 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 | 	if worldedit.marker2[name] ~= nil then --marker already exists | ||||||
| 		worldedit.marker2[name]:remove() --remove marker | 		worldedit.marker2[name]:remove() --remove marker | ||||||
| 		worldedit.marker2[name] = nil | 		worldedit.marker2[name] = nil | ||||||
| 	end | 	end | ||||||
| 	if pos2 ~= nil then | 	if pos2 ~= nil then | ||||||
| 		--make area stay loaded | 		worldedit.keep_loaded(pos2, pos2) | ||||||
| 		local manip = minetest.get_voxel_manip() |  | ||||||
| 		manip:read_from_map(pos2, pos2) |  | ||||||
| 
 | 
 | ||||||
| 		--add marker | 		--add marker | ||||||
| 		worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel) | 		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 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 | 		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 | 		-- TODO maybe we could skip this actually? | ||||||
| 		local manip = minetest.get_voxel_manip() | 		worldedit.keep_loaded(pos1, pos2) | ||||||
| 		manip:read_from_map(pos1, pos2) |  | ||||||
| 
 | 
 | ||||||
| 		local markers = {} | 		local markers = {} | ||||||
| 
 | 
 | ||||||
| 		--XY plane markers | 		--XY plane markers | ||||||
| 		for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do | 		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) | 			local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel) | ||||||
| 			if marker ~= nil then | 			if marker ~= nil then | ||||||
| 				marker:set_properties({ | 				marker:set_properties({ | ||||||
| @@ -99,7 +94,7 @@ worldedit.mark_region = function(name) | |||||||
| 
 | 
 | ||||||
| 		--YZ plane markers | 		--YZ plane markers | ||||||
| 		for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do | 		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) | 			local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel) | ||||||
| 			if marker ~= nil then | 			if marker ~= nil then | ||||||
| 				marker:set_properties({ | 				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 = {} | local safe_region_callback = {} | ||||||
|  |  | ||||||
| --`count` is the number of nodes that would possibly be modified | --`count` is the number of nodes that would possibly be modified | ||||||
| --`callback` is a callback to run when the user confirms | --`callback` is a callback to run when the user confirms | ||||||
| local function safe_region(name, count, callback) | local function safe_region(name, count, callback) | ||||||
| 	if count < 20000 then | 	if safe_region_limit <= 0 or count < safe_region_limit then | ||||||
| 		return callback() | 		return callback() | ||||||
| 	end | 	end | ||||||
|  |  | ||||||
| 	--save callback to call later | 	-- save callback to call later | ||||||
| 	safe_region_callback[name] = callback | 	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 | end | ||||||
|  |  | ||||||
| local function reset_pending(name) | local function reset_pending(name) | ||||||
| @@ -18,11 +33,11 @@ end | |||||||
|  |  | ||||||
| minetest.register_chatcommand("/y", { | minetest.register_chatcommand("/y", { | ||||||
| 	params = "", | 	params = "", | ||||||
| 	description = "Confirm a pending operation", | 	description = S("Confirm a pending operation"), | ||||||
| 	func = function(name) | 	func = function(name) | ||||||
| 		local callback = safe_region_callback[name] | 		local callback = safe_region_callback[name] | ||||||
| 		if not callback then | 		if not callback then | ||||||
| 			worldedit.player_notify(name, "no operation pending") | 			worldedit.player_notify(name, S("no operation pending"), "error") | ||||||
| 			return | 			return | ||||||
| 		end | 		end | ||||||
|  |  | ||||||
| @@ -33,10 +48,10 @@ minetest.register_chatcommand("/y", { | |||||||
|  |  | ||||||
| minetest.register_chatcommand("/n", { | minetest.register_chatcommand("/n", { | ||||||
| 	params = "", | 	params = "", | ||||||
| 	description = "Abort a pending operation", | 	description = S("Abort a pending operation"), | ||||||
| 	func = function(name) | 	func = function(name) | ||||||
| 		if not safe_region_callback[name] then | 		if not safe_region_callback[name] then | ||||||
| 			worldedit.player_notify(name, "no operation pending") | 			worldedit.player_notify(name, S("no operation pending"), "error") | ||||||
| 			return | 			return | ||||||
| 		end | 		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) | local function above_or_under(placer, pointed_thing) | ||||||
| 	if placer:get_player_control().sneak then | 	if placer:get_player_control().sneak then | ||||||
| 		return pointed_thing.above | 		return pointed_thing.above | ||||||
| @@ -9,11 +11,18 @@ end | |||||||
| local punched_air_time = {} | local punched_air_time = {} | ||||||
|  |  | ||||||
| minetest.register_tool(":worldedit:wand", { | 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", | 	inventory_image = "worldedit_wand.png", | ||||||
| 	stack_max = 1, -- there is no need to have more than one | 	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 | 	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) | 	on_use = function(itemstack, placer, pointed_thing) | ||||||
| 		if placer == nil or pointed_thing == nil then return end | 		if placer == nil or pointed_thing == nil then return end | ||||||
| 		local name = placer:get_player_name() | 		local name = placer:get_player_name() | ||||||
| @@ -32,7 +41,7 @@ minetest.register_tool(":worldedit:wand", { | |||||||
| 			local entity = pointed_thing.ref:get_luaentity() | 			local entity = pointed_thing.ref:get_luaentity() | ||||||
| 			if entity and entity.name == "worldedit:pos2" then | 			if entity and entity.name == "worldedit:pos2" then | ||||||
| 				-- set pos1 = pos2 | 				-- set pos1 = pos2 | ||||||
| 				worldedit.pos1[name] = worldedit.pos2[name] | 				worldedit.pos1[name] = vector.copy(worldedit.pos2[name]) | ||||||
| 				worldedit.mark_pos1(name) | 				worldedit.mark_pos1(name) | ||||||
| 			end | 			end | ||||||
| 		end | 		end | ||||||
| @@ -57,7 +66,7 @@ minetest.register_tool(":worldedit:wand", { | |||||||
| 		local entity = pointed_thing.ref:get_luaentity() | 		local entity = pointed_thing.ref:get_luaentity() | ||||||
| 		if entity and entity.name == "worldedit:pos1" then | 		if entity and entity.name == "worldedit:pos1" then | ||||||
| 			-- set pos2 = pos1 | 			-- set pos2 = pos1 | ||||||
| 			worldedit.pos2[name] = worldedit.pos1[name] | 			worldedit.pos2[name] = vector.copy(worldedit.pos1[name]) | ||||||
| 			worldedit.mark_pos2(name) | 			worldedit.mark_pos2(name) | ||||||
| 		end | 		end | ||||||
| 		return itemstack -- nothing consumed, nothing changed | 		return itemstack -- nothing consumed, nothing changed | ||||||
|   | |||||||
| @@ -140,7 +140,7 @@ local function execute_worldedit_command(command_name, player_name, params) | |||||||
| 	assert(chatcmd, "unknown command: " .. command_name) | 	assert(chatcmd, "unknown command: " .. command_name) | ||||||
| 	local _, msg = chatcmd.func(player_name, params) | 	local _, msg = chatcmd.func(player_name, params) | ||||||
| 	if msg then | 	if msg then | ||||||
| 		 worldedit.player_notify(player_name, msg) | 		minetest.chat_send_player(player_name, msg) | ||||||
| 	end | 	end | ||||||
| end | end | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,10 +4,10 @@ worldedit = worldedit or {} | |||||||
| Example: | Example: | ||||||
|  |  | ||||||
|     worldedit.register_gui_function("worldedit_gui_hollow_cylinder", { |     worldedit.register_gui_function("worldedit_gui_hollow_cylinder", { | ||||||
|     	name = "Make Hollow Cylinder", | 		name = "Make Hollow Cylinder", | ||||||
|     	privs = {worldedit=true}, | 		privs = {worldedit=true}, | ||||||
|     	get_formspec = function(name) return "some formspec here" end, | 		get_formspec = function(name) return "some formspec here" end, | ||||||
|     	on_select = function(name) print(name .. " clicked the button!") 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. | Use `nil` for the `options` parameter to unregister the function associated with the given identifier. | ||||||
| @@ -35,14 +35,14 @@ end | |||||||
| Example: | Example: | ||||||
|  |  | ||||||
|     worldedit.register_gui_handler("worldedit_gui_hollow_cylinder", function(name, fields) |     worldedit.register_gui_handler("worldedit_gui_hollow_cylinder", function(name, fields) | ||||||
|     	print(minetest.serialize(fields)) | 		print(minetest.serialize(fields)) | ||||||
|     end) |     end) | ||||||
| ]] | ]] | ||||||
|  |  | ||||||
| worldedit.register_gui_handler = function(identifier, handler) | worldedit.register_gui_handler = function(identifier, handler) | ||||||
| 	local enabled = true | 	local enabled = true | ||||||
| 	minetest.register_on_player_receive_fields(function(player, formname, fields) | 	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 | 		enabled = false | ||||||
| 		minetest.after(0.2, function() enabled = true end) | 		minetest.after(0.2, function() enabled = true end) | ||||||
| 		local name = player:get_player_name() | 		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", { | 	unified_inventory.register_button("worldedit_gui", { | ||||||
| 		type = "image", | 		type = "image", | ||||||
| 		image = "inventory_plus_worldedit_gui.png", | 		image = "inventory_plus_worldedit_gui.png", | ||||||
|  | 		tooltip = "Edit your World!", | ||||||
| 		condition = function(player) | 		condition = function(player) | ||||||
| 			return minetest.check_player_privs(player:get_player_name(), {worldedit=true}) | 			return minetest.check_player_privs(player:get_player_name(), {worldedit=true}) | ||||||
| 		end, | 		end, | ||||||
| @@ -193,7 +194,8 @@ elseif minetest.global_exists("sfinv") then -- sfinv installed | |||||||
| 		get = function(self, player, context) | 		get = function(self, player, context) | ||||||
| 			local can_worldedit = minetest.check_player_privs(player, {worldedit=true}) | 			local can_worldedit = minetest.check_player_privs(player, {worldedit=true}) | ||||||
| 			local fs = orig_get(self, player, context) | 			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 | 		end | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| @@ -262,7 +264,7 @@ worldedit.register_gui_handler("worldedit_gui", function(name, fields) | |||||||
| 			--ensure player has permission to perform action | 			--ensure player has permission to perform action | ||||||
| 			local has_privs, missing_privs = minetest.check_player_privs(name, entry.privs) | 			local has_privs, missing_privs = minetest.check_player_privs(name, entry.privs) | ||||||
| 			if not has_privs then | 			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 | 				return false | ||||||
| 			end | 			end | ||||||
| 			if entry.on_select then | 			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 |