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] | ||||
|  | ||||
| jobs: | ||||
|   test: | ||||
|     name: "Unit Tests ${{ matrix.cfg.image }}" | ||||
|     runs-on: ubuntu-latest | ||||
|     timeout-minutes: 5 | ||||
|     strategy: | ||||
|       matrix: | ||||
|         cfg: | ||||
|           - { image: 'registry.gitlab.com/minetest/minetest/server:5.0.1', mtg: false } | ||||
|           - { image: 'registry.gitlab.com/minetest/minetest/server:5.5.1', mtg: false } | ||||
|           - { image: '', mtg: true } # latest mater | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|     - uses: actions/checkout@v4 | ||||
|  | ||||
|     - uses: actions/checkout@v4 | ||||
|       with: | ||||
|         repository: 'minetest/minetest_game' | ||||
|         path: minetest_game | ||||
|       if: ${{ matrix.cfg.mtg }} | ||||
|  | ||||
|     - 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 | ||||
| tempdir=/tmp/mt | ||||
| tempdir=$(mktemp -d) | ||||
| confpath=$tempdir/minetest.conf | ||||
| worldpath=$tempdir/world | ||||
| trap 'rm -rf "$tempdir"' EXIT | ||||
|  | ||||
| use_docker=y | ||||
| [ -x ../../bin/minetestserver ] && use_docker= | ||||
| [ -f worldedit/mod.conf ] || { echo "Must be run in modpack root folder." >&2; exit 1; } | ||||
|  | ||||
| rm -rf $tempdir | ||||
| mkdir -p $worldpath | ||||
| # the docker image doesn't have devtest | ||||
| [ -n "$use_docker" ] || printf '%s\n' gameid=devtest >$worldpath/world.mt | ||||
| mtserver= | ||||
| if [ "$1" == "--docker" ]; then | ||||
| 	command -v docker >/dev/null || { echo "Docker is not installed." >&2; exit 1; } | ||||
| 	[ -d minetest_game ] || echo "A source checkout of minetest_game was not found. This can fail if your docker image does not ship a game." >&2; | ||||
| else | ||||
| 	mtserver=$(command -v minetestserver) | ||||
| 	[[ -z "$mtserver" && -x ../../bin/minetestserver ]] && mtserver=../../bin/minetestserver | ||||
| 	[ -z "$mtserver" ] && { echo "To run the test outside of Docker, an installation of minetestserver is required." >&2; exit 1; } | ||||
| fi | ||||
|  | ||||
| mkdir $worldpath | ||||
| printf '%s\n' mg_name=singlenode '[end_of_params]' >$worldpath/map_meta.txt | ||||
| printf '%s\n' worldedit_run_tests=true max_forceloaded_blocks=9999 >$confpath | ||||
|  | ||||
| if [ -n "$use_docker" ]; then | ||||
| if [ -z "$mtserver" ]; then | ||||
| 	chmod -R 777 $tempdir | ||||
| 	docker run --rm -i \ | ||||
| 		-v $confpath:/etc/minetest/minetest.conf \ | ||||
| 		-v $tempdir:/var/lib/minetest/.minetest \ | ||||
| 		-v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit \ | ||||
| 		registry.gitlab.com/minetest/minetest/server:${MINETEST_VER} | ||||
| 	[ -z "$DOCKER_IMAGE" ] && DOCKER_IMAGE="ghcr.io/minetest/minetest:master" | ||||
| 	vol=( | ||||
| 		-v "$confpath":/etc/minetest/minetest.conf | ||||
| 		-v "$tempdir":/var/lib/minetest/.minetest | ||||
| 		-v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit | ||||
| 	) | ||||
| 	[ -d minetest_game ] && vol+=( | ||||
| 		-v "$PWD/minetest_game":/var/lib/minetest/.minetest/games/minetest_game | ||||
| 	) | ||||
| 	docker run --rm -i "${vol[@]}" "$DOCKER_IMAGE" | ||||
| else | ||||
| 	mkdir $worldpath/worldmods | ||||
| 	ln -s "$PWD/worldedit" $worldpath/worldmods/worldedit | ||||
| 	../../bin/minetestserver --config $confpath --world $worldpath --logfile /dev/null | ||||
| 	$mtserver --config "$confpath" --world "$worldpath" --logfile /dev/null | ||||
| fi | ||||
|  | ||||
| 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>` | ||||
|  | ||||
| Executes `<code>` as a Lua chunk in the global namespace. | ||||
| Executes `<code>` as a Lua chunk in the global namespace with the variables `name`, `player` and `pos` (= player position) available. | ||||
|  | ||||
|     //lua worldedit.pos1["singleplayer"] = {x=0, y=0, z=0} | ||||
|     //lua worldedit.rotate(worldedit.pos1["singleplayer"], worldedit.pos2["singleplayer"], "y", 90) | ||||
|     //lua worldedit.pos1[name] = vector.new(0, 0, 0) | ||||
|     //lua worldedit.rotate(worldedit.pos1["jones"], worldedit.pos2["jones"], "y", 90) | ||||
|     //lua player:set_pos(worldedit.pos2[name]) | ||||
|  | ||||
| ### `//luatransform <code>` | ||||
|  | ||||
| Executes `<code>` as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region. | ||||
| Executes `<code>` as a Lua chunk in the global namespace with the variable `pos` available, for each node in the current WorldEdit region. | ||||
|  | ||||
|     //luatransform minetest.swap_node(pos, {name="default:stone"}) | ||||
|     //luatransform if minetest.get_node(pos).name == "air" then minetest.add_node(pos, {name="default:water_source"}) end | ||||
| @@ -428,11 +429,12 @@ Save the current WorldEdit region using the Minetest Schematic format to "(world | ||||
|     //mtschemcreate some random filename | ||||
|     //mtschemcreate huge_base | ||||
|  | ||||
| ### `//mtschemplace <file>` | ||||
| ### `//mtschemplace <file> [rotation]` | ||||
|  | ||||
| Load nodes from "(world folder)/schems/`<file>`.mts" with position 1 of the current WorldEdit region as the origin. | ||||
| Valid values for `[rotation]` are 0, 90, 180 and 270. | ||||
|  | ||||
|     //mtschemplace some random filename | ||||
|     //mtschemplace a_tree 270 | ||||
|     //mtschemplace huge_base | ||||
|  | ||||
| ### `//mtschemprob start/finish/get` | ||||
|   | ||||
| @@ -2,7 +2,7 @@ WorldEdit v1.3 | ||||
| ============== | ||||
| The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more. | ||||
|  | ||||
| For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) at the Minetest forums. | ||||
| For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?t=572) at the Minetest forums. | ||||
|  | ||||
| # New users should see the [tutorial](Tutorial.md). | ||||
|  | ||||
| @@ -23,7 +23,7 @@ There is a nice installation guide over at the [Minetest Wiki](http://wiki.minet | ||||
| 8. You should have a mod selection screen. Select the one named something like `Minetest-WorldEdit` by left clicking once and press the **Enable Modpack** button. | ||||
| 9. Press the **Save** button. You can now use WorldEdit in that world. Repeat steps 7 to 9 to enable WorldEdit for other worlds too. | ||||
|  | ||||
| If you are having trouble, try asking for help in the [IRC channel](https://webchat.freenode.net/?channels=#minetest) (faster but may not always have helpers online) | ||||
| If you are having trouble, try asking for help in the [IRC channel](https://web.libera.chat/#minetest) (faster but may not always have helpers online) | ||||
| or ask on the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) (slower but more likely to get help). | ||||
|  | ||||
| Usage | ||||
|   | ||||
| @@ -227,11 +227,19 @@ Code | ||||
| ---- | ||||
| Contained in code.lua, this module allows arbitrary Lua code to be used with WorldEdit. | ||||
|  | ||||
| ### error = worldedit.lua(code) | ||||
| ### error = worldedit.lua(code, name) | ||||
|  | ||||
| Executes `code` as a Lua chunk in the global namespace. | ||||
| the given code gets encapsulated into a function with parameters `name`, `player`, `pos` | ||||
| where | ||||
|  * `name` is a playername or `nil` | ||||
|  * `player` is the player object of the above player if applicable, otherwise `nil` | ||||
|  * `pos` is the position of the aforementioned player (if applicable, otherwise `nil`) rounded to integers | ||||
|  | ||||
| Returns an error if the code fails or nil otherwise. | ||||
| the resulting function is then executed as a Lua chunk in the global namespace. | ||||
|  | ||||
| The return is | ||||
|  * a string in case of an error | ||||
|  * a tuple of `nil` and return of the function converted to a string in case of success | ||||
|  | ||||
| ### error = worldedit.luatransform(pos1, pos2, code) | ||||
|  | ||||
|   | ||||
| @@ -1,2 +1,3 @@ | ||||
| name = Minetest-WorldEdit | ||||
| description = WorldEdit is an in-game world editor. Use it to repair griefing, or just create awesome buildings in seconds. | ||||
| min_minetest_version = 5.0 | ||||
|   | ||||
							
								
								
									
										4
									
								
								settingtypes.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| #    For operations that potentially affect more than the specified amount of nodes | ||||
| #    WorldEdit will require additional confirmation via //y before proceeding. | ||||
| #    Set to 0 to disable the confirmation in all cases. | ||||
| worldedit_safe_region_limit (Limit for safe region warning) int 20000 | ||||
| @@ -2,17 +2,29 @@ | ||||
| -- @module worldedit.code | ||||
|  | ||||
| --- Executes `code` as a Lua chunk in the global namespace. | ||||
| -- @return An error message if the code fails, or nil on success. | ||||
| function worldedit.lua(code) | ||||
| 	local func, err = loadstring(code) | ||||
| 	if not func then  -- Syntax error | ||||
| -- the code will be encapsulated into a function with parameters | ||||
| --  * name (the name of the player issuing the //lua command) | ||||
| --  * player (the player object of the player) | ||||
| --  * pos (the position of the player rounded to integers) | ||||
| -- @return string in case of error, tuple of nil, return of code as string in case of success | ||||
| function worldedit.lua(code, name) | ||||
| 	local factory, err = loadstring("return function(name, player, pos)\n" .. code .. "\nend") | ||||
| 	if not factory then -- Syntax error | ||||
| 		return err | ||||
| 	end | ||||
| 	local good, err = pcall(func) | ||||
| 	local func = factory() | ||||
| 	local player, pos | ||||
| 	if name then | ||||
| 		player = minetest.get_player_by_name(name) | ||||
| 		if player then | ||||
| 			pos = vector.round(player:get_pos()) | ||||
| 		end | ||||
| 	end | ||||
| 	local good, err = pcall(func, name, player, pos) | ||||
| 	if not good then -- Runtime error | ||||
| 		return err | ||||
| 		return tostring(err) | ||||
| 	end | ||||
| 	return nil | ||||
| 	return nil, dump(err) | ||||
| end | ||||
|  | ||||
|  | ||||
| @@ -31,7 +43,7 @@ function worldedit.luatransform(pos1, pos2, code) | ||||
|  | ||||
| 	worldedit.keep_loaded(pos1, pos2) | ||||
|  | ||||
| 	local pos = {x=pos1.x, y=0, z=0} | ||||
| 	local pos = vector.new(pos1.x, 0, 0) | ||||
| 	while pos.x <= pos2.x do | ||||
| 		pos.y = pos1.y | ||||
| 		while pos.y <= pos2.y do | ||||
| @@ -39,7 +51,7 @@ function worldedit.luatransform(pos1, pos2, code) | ||||
| 			while pos.z <= pos2.z do | ||||
| 				local good, err = pcall(func, pos) | ||||
| 				if not good then -- Runtime error | ||||
| 					return err | ||||
| 					return tostring(err) | ||||
| 				end | ||||
| 				pos.z = pos.z + 1 | ||||
| 			end | ||||
|   | ||||
| @@ -1,12 +1,21 @@ | ||||
| --- Common functions [INTERNAL].  All of these functions are internal! | ||||
| -- @module worldedit.common | ||||
|  | ||||
| -- Polyfill for vector.copy (added in 5.5.0) | ||||
| if not vector.copy then | ||||
| 	local vnew = vector.new | ||||
| 	vector.copy = function(v) | ||||
| 		return vnew(v.x, v.y, v.z) | ||||
| 	end | ||||
| end | ||||
|  | ||||
|  | ||||
| --- Copies and modifies positions `pos1` and `pos2` so that each component of | ||||
| -- `pos1` is less than or equal to the corresponding component of `pos2`. | ||||
| -- Returns the new positions. | ||||
| function worldedit.sort_pos(pos1, pos2) | ||||
| 	pos1 = {x=pos1.x, y=pos1.y, z=pos1.z} | ||||
| 	pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} | ||||
| 	pos1 = vector.copy(pos1) | ||||
| 	pos2 = vector.copy(pos2) | ||||
| 	if pos1.x > pos2.x then | ||||
| 		pos2.x, pos1.x = pos1.x, pos2.x | ||||
| 	end | ||||
| @@ -45,13 +54,23 @@ function worldedit.get_axis_others(axis) | ||||
| end | ||||
|  | ||||
|  | ||||
| function worldedit.keep_loaded(pos1, pos2) | ||||
| 	-- Create a vmanip and read the area from map, this | ||||
| 	-- causes all MapBlocks to be loaded into memory. | ||||
| -- 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) | ||||
| 	-- rough estimate, a MapNode is 4 bytes in the engine | ||||
| 	if worldedit.volume(pos1, pos2) > 268400000 then | ||||
| 		print("[WorldEdit] Requested to load an area bigger than 1GB, refusing. The subsequent operation may fail.") | ||||
| 		return | ||||
| 	end | ||||
| 	if minetest.load_area then | ||||
| 		-- same effect but without unnecessary data copying | ||||
| 		minetest.load_area(pos1, pos2) | ||||
| 	else | ||||
| 		local manip = minetest.get_voxel_manip() | ||||
| 		manip:read_from_map(pos1, pos2) | ||||
| 	end | ||||
| end | ||||
|  | ||||
|  | ||||
| local mh = {} | ||||
| @@ -65,7 +84,7 @@ function mh.get_empty_data(area) | ||||
| 	-- only partially modified aren't overwriten. | ||||
| 	local data = {} | ||||
| 	local c_ignore = minetest.get_content_id("ignore") | ||||
| 	for i = 1, worldedit.volume(area.MinEdge, area.MaxEdge) do | ||||
| 	for i = 1, area:getVolume() do | ||||
| 		data[i] = c_ignore | ||||
| 	end | ||||
| 	return data | ||||
| @@ -117,3 +136,72 @@ function mh.finish(manip, data) | ||||
| 	manip:update_map() | ||||
| end | ||||
|  | ||||
|  | ||||
| -- returns an iterator that returns voxelarea indices for a hollow cuboid | ||||
| function mh.iter_hollowcuboid(area, minx, miny, minz, maxx, maxy, maxz) | ||||
| 	local i = area:index(minx, miny, minz) - 1 | ||||
| 	local xrange = maxx - minx + 1 | ||||
| 	local nextaction = i + 1 + xrange | ||||
| 	local do_hole = false | ||||
|  | ||||
| 	local y = 0 | ||||
| 	local ydiff = maxy - miny | ||||
| 	local ystride = area.ystride | ||||
| 	local ymultistride = ydiff * ystride | ||||
|  | ||||
| 	local z = 0 | ||||
| 	local zdiff = maxz - minz | ||||
| 	local zstride = area.zstride | ||||
| 	local zcorner = true | ||||
|  | ||||
| 	return function() | ||||
| 		-- continue i until it needs to jump ystride | ||||
| 		i = i + 1 | ||||
| 		if i ~= nextaction then | ||||
| 			return i | ||||
| 		end | ||||
|  | ||||
| 		-- add the x offset if y (and z) are not 0 or maxy (or maxz) | ||||
| 		if do_hole then | ||||
| 			do_hole = false | ||||
| 			i = i + xrange - 2 | ||||
| 			nextaction = i + 1 | ||||
| 			return i | ||||
| 		end | ||||
|  | ||||
| 		-- continue y until maxy is exceeded | ||||
| 		y = y+1 | ||||
| 		if y ~= ydiff + 1 then | ||||
| 			i = i + ystride - xrange | ||||
| 			if zcorner | ||||
| 			or y == ydiff then | ||||
| 				nextaction = i + xrange | ||||
| 			else | ||||
| 				nextaction = i + 1 | ||||
| 				do_hole = true | ||||
| 			end | ||||
| 			return i | ||||
| 		end | ||||
|  | ||||
| 		-- continue z until maxz is exceeded | ||||
| 		z = z+1 | ||||
| 		if z == zdiff + 1 then | ||||
| 			-- hollowcuboid finished, return nil | ||||
| 			return | ||||
| 		end | ||||
|  | ||||
| 		-- set i to index(minx, miny, minz + z) - 1 | ||||
| 		i = i + zstride - (ymultistride + xrange) | ||||
| 		zcorner = z == zdiff | ||||
|  | ||||
| 		-- y is 0, so traverse the xs | ||||
| 		y = 0 | ||||
| 		nextaction = i + xrange | ||||
| 		return i | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function mh.iterp_hollowcuboid(area, minp, maxp) | ||||
| 	return mh.iter_hollowcuboid(area, minp.x, minp.y, minp.z, | ||||
| 		maxp.x, maxp.y, maxp.z) | ||||
| end | ||||
|   | ||||
| @@ -24,11 +24,14 @@ function worldedit.metasave(pos1, pos2, filename) | ||||
| 	return count | ||||
| end | ||||
|  | ||||
| function worldedit.metaload(originpos, filename) | ||||
| function worldedit.metaload(originpos, file_name) | ||||
| 	deprecated("load") | ||||
| 	filename = minetest.get_worldpath() .. "/schems/" .. file .. ".wem" | ||||
| 	local file, err = io.open(filename, "wb") | ||||
| 	if err then return 0 end | ||||
| 	local file_path = minetest.get_worldpath() .. | ||||
| 		"/schems/" .. file_name .. ".wem" | ||||
| 	local file, err = io.open(file_path, "wb") | ||||
| 	if err then | ||||
| 		return 0 | ||||
| 	end | ||||
| 	local data = file:read("*a") | ||||
| 	return worldedit.deserialize(originpos, data) | ||||
| end | ||||
|   | ||||
| @@ -64,6 +64,8 @@ worldedit.cuboid_shift = function(name, axis, amount) | ||||
| 		return false, "undefined cuboid" | ||||
| 	end | ||||
|  | ||||
| 	assert(not rawequal(pos1, pos2)) -- vectors must not alias | ||||
|  | ||||
| 	if axis == 'x' then | ||||
| 		worldedit.pos1[name].x = pos1.x + amount | ||||
| 		worldedit.pos2[name].x = pos2.x + amount | ||||
| @@ -134,7 +136,8 @@ end | ||||
|  | ||||
| -- Return the marker that is closest to the player | ||||
| worldedit.marker_get_closest_to_player = function(name) | ||||
| 	local playerpos = minetest.get_player_by_name(name):get_pos() | ||||
| 	local player = assert(minetest.get_player_by_name(name)) | ||||
| 	local playerpos = player:get_pos() | ||||
| 	local dist1 = vector.distance(playerpos, worldedit.pos1[name]) | ||||
| 	local dist2 = vector.distance(playerpos, worldedit.pos2[name]) | ||||
|  | ||||
|   | ||||
| @@ -27,6 +27,7 @@ end | ||||
| dofile(path .. "/common.lua") | ||||
| load_module(path .. "/manipulations.lua") | ||||
| load_module(path .. "/primitives.lua") | ||||
| load_module(path .. "/transformations.lua") | ||||
| load_module(path .. "/visualization.lua") | ||||
| load_module(path .. "/serialization.lua") | ||||
| load_module(path .. "/code.lua") | ||||
| @@ -39,6 +40,6 @@ if minetest.settings:get_bool("log_mods") then | ||||
| end | ||||
|  | ||||
| if minetest.settings:get_bool("worldedit_run_tests") then | ||||
| 	dofile(path .. "/test.lua") | ||||
| 	dofile(path .. "/test/init.lua") | ||||
| 	minetest.after(0, worldedit.run_tests) | ||||
| end | ||||
|   | ||||
| @@ -98,51 +98,6 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse) | ||||
| end | ||||
|  | ||||
|  | ||||
| local function deferred_execution(next_one, finished) | ||||
| 	-- Allocate 100% of server step for execution (might lag a little) | ||||
| 	local allocated_usecs = | ||||
| 		tonumber(minetest.settings:get("dedicated_server_step")) * 1000000 | ||||
| 	local function f() | ||||
| 		local deadline = minetest.get_us_time() + allocated_usecs | ||||
| 		repeat | ||||
| 			local is_done = next_one() | ||||
| 			if is_done then | ||||
| 				if finished then | ||||
| 					finished() | ||||
| 				end | ||||
| 				return | ||||
| 			end | ||||
| 		until minetest.get_us_time() >= deadline | ||||
| 		minetest.after(0, f) | ||||
| 	end | ||||
| 	f() | ||||
| end | ||||
|  | ||||
| --- Duplicates a region `amount` times with offset vector `direction`. | ||||
| -- Stacking is spread across server steps. | ||||
| -- @return The number of nodes stacked. | ||||
| function worldedit.stack2(pos1, pos2, direction, amount, finished) | ||||
| 	-- Protect arguments from external changes during execution | ||||
| 	pos1 = table.copy(pos1) | ||||
| 	pos2 = table.copy(pos2) | ||||
| 	direction = table.copy(direction) | ||||
|  | ||||
| 	local i = 0 | ||||
| 	local translated = {x=0, y=0, z=0} | ||||
| 	local function step() | ||||
| 		translated.x = translated.x + direction.x | ||||
| 		translated.y = translated.y + direction.y | ||||
| 		translated.z = translated.z + direction.z | ||||
| 		worldedit.copy2(pos1, pos2, translated) | ||||
| 		i = i + 1 | ||||
| 		return i >= amount | ||||
| 	end | ||||
| 	deferred_execution(step, finished) | ||||
|  | ||||
| 	return worldedit.volume(pos1, pos2) * amount | ||||
| end | ||||
|  | ||||
|  | ||||
| --- Copies a region along `axis` by `amount` nodes. | ||||
| -- @param pos1 | ||||
| -- @param pos2 | ||||
| @@ -155,7 +110,7 @@ function worldedit.copy(pos1, pos2, axis, amount) | ||||
| 	-- Decide if we need to copy stuff backwards (only applies to metadata) | ||||
| 	local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1) | ||||
|  | ||||
| 	local off = {x=0, y=0, z=0} | ||||
| 	local off = vector.new() | ||||
| 	off[axis] = amount | ||||
| 	return worldedit.copy2(pos1, pos2, off, backwards) | ||||
| end | ||||
| @@ -170,7 +125,7 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards) | ||||
| 	local pos1, pos2 = worldedit.sort_pos(pos1, pos2) | ||||
|  | ||||
| 	local src_manip, src_area = mh.init(pos1, pos2) | ||||
| 	local src_stride = {x=1, y=src_area.ystride, z=src_area.zstride} | ||||
| 	local src_stride = vector.new(1, src_area.ystride, src_area.zstride) | ||||
| 	local src_offset = vector.subtract(pos1, src_area.MinEdge) | ||||
|  | ||||
| 	local dpos1 = vector.add(pos1, off) | ||||
| @@ -178,7 +133,7 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards) | ||||
| 	local dim = vector.add(vector.subtract(pos2, pos1), 1) | ||||
|  | ||||
| 	local dst_manip, dst_area = mh.init(dpos1, dpos2) | ||||
| 	local dst_stride = {x=1, y=dst_area.ystride, z=dst_area.zstride} | ||||
| 	local dst_stride = vector.new(1, dst_area.ystride, dst_area.zstride) | ||||
| 	local dst_offset = vector.subtract(dpos1, dst_area.MinEdge) | ||||
|  | ||||
| 	local function do_copy(src_data, dst_data) | ||||
| @@ -217,8 +172,6 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards) | ||||
| 	dst_manip:set_param2_data(dst_data) | ||||
|  | ||||
| 	mh.finish(dst_manip) | ||||
| 	src_data = nil | ||||
| 	dst_data = nil | ||||
|  | ||||
| 	-- Copy metadata | ||||
| 	local get_meta = minetest.get_meta | ||||
| @@ -226,7 +179,7 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards) | ||||
| 	for z = dim.z-1, 0, -1 do | ||||
| 		for y = dim.y-1, 0, -1 do | ||||
| 			for x = dim.x-1, 0, -1 do | ||||
| 				local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z} | ||||
| 				local pos = vector.new(pos1.x+x, pos1.y+y, pos1.z+z) | ||||
| 				local meta = get_meta(pos):to_table() | ||||
| 				pos = vector.add(pos, off) | ||||
| 				get_meta(pos):from_table(meta) | ||||
| @@ -237,7 +190,7 @@ function worldedit.copy2(pos1, pos2, off, meta_backwards) | ||||
| 	for z = 0, dim.z-1 do | ||||
| 		for y = 0, dim.y-1 do | ||||
| 			for x = 0, dim.x-1 do | ||||
| 				local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z} | ||||
| 				local pos = vector.new(pos1.x+x, pos1.y+y, pos1.z+z) | ||||
| 				local meta = get_meta(pos):to_table() | ||||
| 				pos = vector.add(pos, off) | ||||
| 				get_meta(pos):from_table(meta) | ||||
| @@ -286,21 +239,21 @@ function worldedit.move(pos1, pos2, axis, amount) | ||||
| 	end | ||||
|  | ||||
| 	-- Copy stuff to new location | ||||
| 	local off = {x=0, y=0, z=0} | ||||
| 	local off = vector.new() | ||||
| 	off[axis] = amount | ||||
| 	worldedit.copy2(pos1, pos2, off, backwards) | ||||
| 	-- Nuke old area | ||||
| 	if not overlap then | ||||
| 		nuke_area({x=0, y=0, z=0}, dim) | ||||
| 		nuke_area(vector.new(), dim) | ||||
| 	else | ||||
| 		-- Source and destination region are overlapping, which means we can't | ||||
| 		-- blindly delete the [pos1, pos2] area | ||||
| 		local leftover = vector.new(dim) -- size of the leftover slice | ||||
| 		leftover[axis] = math.abs(amount) | ||||
| 		if amount > 0 then | ||||
| 			nuke_area({x=0, y=0, z=0}, leftover) | ||||
| 			nuke_area(vector.new(), leftover) | ||||
| 		else | ||||
| 			local top = {x=0, y=0, z=0} -- offset of the leftover slice from pos1 | ||||
| 			local top = vector.new() -- offset of the leftover slice from pos1 | ||||
| 			top[axis] = dim[axis] - math.abs(amount) | ||||
| 			nuke_area(top, leftover) | ||||
| 		end | ||||
| @@ -309,316 +262,6 @@ function worldedit.move(pos1, pos2, axis, amount) | ||||
| 	return worldedit.volume(pos1, pos2) | ||||
| end | ||||
|  | ||||
| --- Duplicates a region along `axis` `amount` times. | ||||
| -- Stacking is spread across server steps. | ||||
| -- @param pos1 | ||||
| -- @param pos2 | ||||
| -- @param axis Axis direction, "x", "y", or "z". | ||||
| -- @param count | ||||
| -- @return The number of nodes stacked. | ||||
| function worldedit.stack(pos1, pos2, axis, count, finished) | ||||
| 	local pos1, pos2 = worldedit.sort_pos(pos1, pos2) | ||||
| 	local length = pos2[axis] - pos1[axis] + 1 | ||||
| 	if count < 0 then | ||||
| 		count = -count | ||||
| 		length = -length | ||||
| 	end | ||||
|  | ||||
| 	local i, distance = 0, 0 | ||||
| 	local function step() | ||||
| 		distance = distance + length | ||||
| 		worldedit.copy(pos1, pos2, axis, distance) | ||||
| 		i = i + 1 | ||||
| 		return i >= count | ||||
| 	end | ||||
| 	deferred_execution(step, finished) | ||||
|  | ||||
| 	return worldedit.volume(pos1, pos2) * count | ||||
| end | ||||
|  | ||||
|  | ||||
| --- Stretches a region by a factor of positive integers along the X, Y, and Z | ||||
| -- axes, respectively, with `pos1` as the origin. | ||||
| -- @param pos1 | ||||
| -- @param pos2 | ||||
| -- @param stretch_x Amount to stretch along X axis. | ||||
| -- @param stretch_y Amount to stretch along Y axis. | ||||
| -- @param stretch_z Amount to stretch along Z axis. | ||||
| -- @return The number of nodes scaled. | ||||
| -- @return The new scaled position 1. | ||||
| -- @return The new scaled position 2. | ||||
| function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z) | ||||
| 	local pos1, pos2 = worldedit.sort_pos(pos1, pos2) | ||||
|  | ||||
| 	-- Prepare schematic of large node | ||||
| 	local get_node, get_meta, place_schematic = minetest.get_node, | ||||
| 			minetest.get_meta, minetest.place_schematic | ||||
| 	local placeholder_node = {name="", param1=255, param2=0} | ||||
| 	local nodes = {} | ||||
| 	for i = 1, stretch_x * stretch_y * stretch_z do | ||||
| 		nodes[i] = placeholder_node | ||||
| 	end | ||||
| 	local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes} | ||||
|  | ||||
| 	local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1 | ||||
|  | ||||
| 	local new_pos2 = { | ||||
| 		x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x, | ||||
| 		y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y, | ||||
| 		z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z, | ||||
| 	} | ||||
| 	worldedit.keep_loaded(pos1, new_pos2) | ||||
|  | ||||
| 	local pos = {x=pos2.x, y=0, z=0} | ||||
| 	local big_pos = {x=0, y=0, z=0} | ||||
| 	while pos.x >= pos1.x do | ||||
| 		pos.y = pos2.y | ||||
| 		while pos.y >= pos1.y do | ||||
| 			pos.z = pos2.z | ||||
| 			while pos.z >= pos1.z do | ||||
| 				local node = get_node(pos) -- Get current node | ||||
| 				local meta = get_meta(pos):to_table() -- Get meta of current node | ||||
|  | ||||
| 				-- Calculate far corner of the big node | ||||
| 				local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x | ||||
| 				local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y | ||||
| 				local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z | ||||
|  | ||||
| 				-- Create large node | ||||
| 				placeholder_node.name = node.name | ||||
| 				placeholder_node.param2 = node.param2 | ||||
| 				big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z | ||||
| 				place_schematic(big_pos, schematic) | ||||
|  | ||||
| 				-- Fill in large node meta | ||||
| 				if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then | ||||
| 					-- Node has meta fields | ||||
| 					for x = 0, size_x do | ||||
| 					for y = 0, size_y do | ||||
| 					for z = 0, size_z do | ||||
| 						big_pos.x = pos_x + x | ||||
| 						big_pos.y = pos_y + y | ||||
| 						big_pos.z = pos_z + z | ||||
| 						-- Set metadata of new node | ||||
| 						get_meta(big_pos):from_table(meta) | ||||
| 					end | ||||
| 					end | ||||
| 					end | ||||
| 				end | ||||
| 				pos.z = pos.z - 1 | ||||
| 			end | ||||
| 			pos.y = pos.y - 1 | ||||
| 		end | ||||
| 		pos.x = pos.x - 1 | ||||
| 	end | ||||
| 	return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2 | ||||
| end | ||||
|  | ||||
|  | ||||
| --- Transposes a region between two axes. | ||||
| -- @return The number of nodes transposed. | ||||
| -- @return The new transposed position 1. | ||||
| -- @return The new transposed position 2. | ||||
| function worldedit.transpose(pos1, pos2, axis1, axis2) | ||||
| 	local pos1, pos2 = worldedit.sort_pos(pos1, pos2) | ||||
|  | ||||
| 	local compare | ||||
| 	local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2] | ||||
|  | ||||
| 	if extent1 > extent2 then | ||||
| 		compare = function(extent1, extent2) | ||||
| 			return extent1 > extent2 | ||||
| 		end | ||||
| 	else | ||||
| 		compare = function(extent1, extent2) | ||||
| 			return extent1 < extent2 | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	-- Calculate the new position 2 after transposition | ||||
| 	local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} | ||||
| 	new_pos2[axis1] = pos1[axis1] + extent2 | ||||
| 	new_pos2[axis2] = pos1[axis2] + extent1 | ||||
|  | ||||
| 	local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z} | ||||
| 	if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end | ||||
| 	if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end | ||||
| 	worldedit.keep_loaded(pos1, upper_bound) | ||||
|  | ||||
| 	local pos = {x=pos1.x, y=0, z=0} | ||||
| 	local get_node, get_meta, set_node = minetest.get_node, | ||||
| 			minetest.get_meta, minetest.set_node | ||||
| 	while pos.x <= pos2.x do | ||||
| 		pos.y = pos1.y | ||||
| 		while pos.y <= pos2.y do | ||||
| 			pos.z = pos1.z | ||||
| 			while pos.z <= pos2.z do | ||||
| 				local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2] | ||||
| 				if compare(extent1, extent2) then -- Transpose only if below the diagonal | ||||
| 					local node1 = get_node(pos) | ||||
| 					local meta1 = get_meta(pos):to_table() | ||||
| 					local value1, value2 = pos[axis1], pos[axis2] -- Save position values | ||||
| 					pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents | ||||
| 					local node2 = get_node(pos) | ||||
| 					local meta2 = get_meta(pos):to_table() | ||||
| 					set_node(pos, node1) | ||||
| 					get_meta(pos):from_table(meta1) | ||||
| 					pos[axis1], pos[axis2] = value1, value2 -- Restore position values | ||||
| 					set_node(pos, node2) | ||||
| 					get_meta(pos):from_table(meta2) | ||||
| 				end | ||||
| 				pos.z = pos.z + 1 | ||||
| 			end | ||||
| 			pos.y = pos.y + 1 | ||||
| 		end | ||||
| 		pos.x = pos.x + 1 | ||||
| 	end | ||||
| 	return worldedit.volume(pos1, pos2), pos1, new_pos2 | ||||
| end | ||||
|  | ||||
|  | ||||
| --- Flips a region along `axis`. | ||||
| -- @return The number of nodes flipped. | ||||
| function worldedit.flip(pos1, pos2, axis) | ||||
| 	local pos1, pos2 = worldedit.sort_pos(pos1, pos2) | ||||
|  | ||||
| 	worldedit.keep_loaded(pos1, pos2) | ||||
|  | ||||
| 	--- TODO: Flip the region slice by slice along the flip axis using schematic method. | ||||
| 	local pos = {x=pos1.x, y=0, z=0} | ||||
| 	local start = pos1[axis] + pos2[axis] | ||||
| 	pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2) | ||||
| 	local get_node, get_meta, set_node = minetest.get_node, | ||||
| 			minetest.get_meta, minetest.set_node | ||||
| 	while pos.x <= pos2.x do | ||||
| 		pos.y = pos1.y | ||||
| 		while pos.y <= pos2.y do | ||||
| 			pos.z = pos1.z | ||||
| 			while pos.z <= pos2.z do | ||||
| 				local node1 = get_node(pos) | ||||
| 				local meta1 = get_meta(pos):to_table() | ||||
| 				local value = pos[axis] -- Save position | ||||
| 				pos[axis] = start - value -- Shift position | ||||
| 				local node2 = get_node(pos) | ||||
| 				local meta2 = get_meta(pos):to_table() | ||||
| 				set_node(pos, node1) | ||||
| 				get_meta(pos):from_table(meta1) | ||||
| 				pos[axis] = value -- Restore position | ||||
| 				set_node(pos, node2) | ||||
| 				get_meta(pos):from_table(meta2) | ||||
| 				pos.z = pos.z + 1 | ||||
| 			end | ||||
| 			pos.y = pos.y + 1 | ||||
| 		end | ||||
| 		pos.x = pos.x + 1 | ||||
| 	end | ||||
| 	return worldedit.volume(pos1, pos2) | ||||
| end | ||||
|  | ||||
|  | ||||
| --- Rotates a region clockwise around an axis. | ||||
| -- @param pos1 | ||||
| -- @param pos2 | ||||
| -- @param axis Axis ("x", "y", or "z"). | ||||
| -- @param angle Angle in degrees (90 degree increments only). | ||||
| -- @return The number of nodes rotated. | ||||
| -- @return The new first position. | ||||
| -- @return The new second position. | ||||
| function worldedit.rotate(pos1, pos2, axis, angle) | ||||
| 	local pos1, pos2 = worldedit.sort_pos(pos1, pos2) | ||||
|  | ||||
| 	local other1, other2 = worldedit.get_axis_others(axis) | ||||
| 	angle = angle % 360 | ||||
|  | ||||
| 	local count | ||||
| 	if angle == 90 then | ||||
| 		worldedit.flip(pos1, pos2, other1) | ||||
| 		count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2) | ||||
| 	elseif angle == 180 then | ||||
| 		worldedit.flip(pos1, pos2, other1) | ||||
| 		count = worldedit.flip(pos1, pos2, other2) | ||||
| 	elseif angle == 270 then | ||||
| 		worldedit.flip(pos1, pos2, other2) | ||||
| 		count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2) | ||||
| 	else | ||||
| 		error("Only 90 degree increments are supported!") | ||||
| 	end | ||||
| 	return count, pos1, pos2 | ||||
| end | ||||
|  | ||||
|  | ||||
| --- Rotates all oriented nodes in a region clockwise around the Y axis. | ||||
| -- @param pos1 | ||||
| -- @param pos2 | ||||
| -- @param angle Angle in degrees (90 degree increments only). | ||||
| -- @return The number of nodes oriented. | ||||
| function worldedit.orient(pos1, pos2, angle) | ||||
| 	local pos1, pos2 = worldedit.sort_pos(pos1, pos2) | ||||
| 	local registered_nodes = minetest.registered_nodes | ||||
|  | ||||
| 	local wallmounted = { | ||||
| 		[90]  = {0, 1, 5, 4, 2, 3, 0, 0}, | ||||
| 		[180] = {0, 1, 3, 2, 5, 4, 0, 0}, | ||||
| 		[270] = {0, 1, 4, 5, 3, 2, 0, 0} | ||||
| 	} | ||||
| 	local facedir = { | ||||
| 		[90]  = { 1,  2,  3,  0, 13, 14, 15, 12, 17, 18, 19, 16, | ||||
| 				  9, 10, 11,  8,  5,  6,  7,  4, 23, 20, 21, 22}, | ||||
| 		[180] = { 2,  3,  0,  1, 10, 11,  8,  9,  6,  7,  4,  5, | ||||
| 				 18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21}, | ||||
| 		[270] = { 3,  0,  1,  2, 19, 16, 17, 18, 15, 12, 13, 14, | ||||
| 				  7,  4,  5,  6, 11,  8,  9, 10, 21, 22, 23, 20} | ||||
| 	} | ||||
|  | ||||
| 	angle = angle % 360 | ||||
| 	if angle == 0 then | ||||
| 		return 0 | ||||
| 	end | ||||
| 	if angle % 90 ~= 0 then | ||||
| 		error("Only 90 degree increments are supported!") | ||||
| 	end | ||||
| 	local wallmounted_substitution = wallmounted[angle] | ||||
| 	local facedir_substitution = facedir[angle] | ||||
|  | ||||
| 	worldedit.keep_loaded(pos1, pos2) | ||||
|  | ||||
| 	local count = 0 | ||||
| 	local get_node, swap_node = minetest.get_node, minetest.swap_node | ||||
| 	local pos = {x=pos1.x, y=0, z=0} | ||||
| 	while pos.x <= pos2.x do | ||||
| 		pos.y = pos1.y | ||||
| 		while pos.y <= pos2.y do | ||||
| 			pos.z = pos1.z | ||||
| 			while pos.z <= pos2.z do | ||||
| 				local node = get_node(pos) | ||||
| 				local def = registered_nodes[node.name] | ||||
| 				if def then | ||||
| 					local paramtype2 = def.paramtype2 | ||||
| 					if paramtype2 == "wallmounted" or | ||||
| 							paramtype2 == "colorwallmounted" then | ||||
| 						local orient = node.param2 % 8 | ||||
| 						node.param2 = node.param2 - orient + | ||||
| 								wallmounted_substitution[orient + 1] | ||||
| 						swap_node(pos, node) | ||||
| 						count = count + 1 | ||||
| 					elseif paramtype2 == "facedir" or | ||||
| 							paramtype2 == "colorfacedir" then | ||||
| 						local orient = node.param2 % 32 | ||||
| 						node.param2 = node.param2 - orient + | ||||
| 								facedir_substitution[orient + 1] | ||||
| 						swap_node(pos, node) | ||||
| 						count = count + 1 | ||||
| 					end | ||||
| 				end | ||||
| 				pos.z = pos.z + 1 | ||||
| 			end | ||||
| 			pos.y = pos.y + 1 | ||||
| 		end | ||||
| 		pos.x = pos.x + 1 | ||||
| 	end | ||||
| 	return count | ||||
| end | ||||
|  | ||||
|  | ||||
| --- Attempts to fix the lighting in a region. | ||||
| -- @return The number of nodes updated. | ||||
| @@ -646,17 +289,16 @@ function worldedit.clear_objects(pos1, pos2) | ||||
| 			return false | ||||
| 		end | ||||
| 		local entity = obj:get_luaentity() | ||||
| 		return not entity or not entity.name:find("^worldedit:") | ||||
| 		return not (entity and entity.name:find("^worldedit:")) | ||||
| 	end | ||||
|  | ||||
| 	-- Offset positions to include full nodes (positions are in the center of nodes) | ||||
| 	local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5 | ||||
| 	local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5 | ||||
| 	pos1 = vector.add(pos1, -0.5) | ||||
| 	pos2 = vector.add(pos2, 0.5) | ||||
|  | ||||
| 	local count = 0 | ||||
| 	if minetest.get_objects_in_area then | ||||
| 		local objects = minetest.get_objects_in_area({x=pos1x, y=pos1y, z=pos1z}, | ||||
| 			{x=pos2x, y=pos2y, z=pos2z}) | ||||
| 		local objects = minetest.get_objects_in_area(pos1, pos2) | ||||
|  | ||||
| 		for _, obj in pairs(objects) do | ||||
| 			if should_delete(obj) then | ||||
| @@ -670,21 +312,22 @@ function worldedit.clear_objects(pos1, pos2) | ||||
| 	-- Fallback implementation via get_objects_inside_radius | ||||
| 	-- Center of region | ||||
| 	local center = { | ||||
| 		x = pos1x + ((pos2x - pos1x) / 2), | ||||
| 		y = pos1y + ((pos2y - pos1y) / 2), | ||||
| 		z = pos1z + ((pos2z - pos1z) / 2) | ||||
| 		x = pos1.x + ((pos2.x - pos1.x) / 2), | ||||
| 		y = pos1.y + ((pos2.y - pos1.y) / 2), | ||||
| 		z = pos1.z + ((pos2.z - pos1.z) / 2) | ||||
| 	} | ||||
| 	-- Bounding sphere radius | ||||
| 	local radius = math.sqrt( | ||||
| 			(center.x - pos1x) ^ 2 + | ||||
| 			(center.y - pos1y) ^ 2 + | ||||
| 			(center.z - pos1z) ^ 2) | ||||
| 	for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do | ||||
| 			(center.x - pos1.x) ^ 2 + | ||||
| 			(center.y - pos1.y) ^ 2 + | ||||
| 			(center.z - pos1.z) ^ 2) | ||||
| 	local objects = minetest.get_objects_inside_radius(center, radius) | ||||
| 	for _, obj in pairs(objects) do | ||||
| 		if should_delete(obj) then | ||||
| 			local pos = obj:get_pos() | ||||
| 			if pos.x >= pos1x and pos.x <= pos2x and | ||||
| 					pos.y >= pos1y and pos.y <= pos2y and | ||||
| 					pos.z >= pos1z and pos.z <= pos2z then | ||||
| 			if pos.x >= pos1.x and pos.x <= pos2.x and | ||||
| 					pos.y >= pos1.y and pos.y <= pos2.y and | ||||
| 					pos.z >= pos1.z and pos.z <= pos2.z then | ||||
| 				-- Inside region | ||||
| 				obj:remove() | ||||
| 				count = count + 1 | ||||
| @@ -693,4 +336,3 @@ function worldedit.clear_objects(pos1, pos2) | ||||
| 	end | ||||
| 	return count | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -14,32 +14,27 @@ local mh = worldedit.manip_helpers | ||||
| -- @return The number of nodes added. | ||||
| function worldedit.cube(pos, width, height, length, node_name, hollow) | ||||
| 	-- Set up voxel manipulator | ||||
| 	local basepos = vector.subtract(pos, {x=math.floor(width/2), y=0, z=math.floor(length/2)}) | ||||
| 	local manip, area = mh.init(basepos, vector.add(basepos, {x=width, y=height, z=length})) | ||||
| 	local basepos = vector.subtract(pos, | ||||
| 		vector.new(math.floor(width / 2), 0, math.floor(length / 2))) | ||||
| 	local endpos = vector.add(basepos, | ||||
| 		vector.new(width - 1, height - 1, length - 1)) | ||||
| 	local manip, area = mh.init(basepos, endpos) | ||||
| 	local data = mh.get_empty_data(area) | ||||
|  | ||||
| 	-- Add cube | ||||
| 	local node_id = minetest.get_content_id(node_name) | ||||
| 	local stride = {x=1, y=area.ystride, z=area.zstride} | ||||
| 	local offset = vector.subtract(basepos, area.MinEdge) | ||||
| 	local count = 0 | ||||
| 	local iterfunc | ||||
| 	if hollow then | ||||
| 		iterfunc = mh.iterp_hollowcuboid(area, basepos, endpos) | ||||
| 	else | ||||
| 		iterfunc = area:iterp(basepos, endpos) | ||||
| 	end | ||||
|  | ||||
| 	for z = 0, length-1 do | ||||
| 		local index_z = (offset.z + z) * stride.z + 1 -- +1 for 1-based indexing | ||||
| 		for y = 0, height-1 do | ||||
| 			local index_y = index_z + (offset.y + y) * stride.y | ||||
| 			for x = 0, width-1 do | ||||
| 				local is_wall = z == 0 or z == length-1 | ||||
| 					or y == 0 or y == height-1 | ||||
| 					or x == 0 or x == width-1 | ||||
| 				if not hollow or is_wall then | ||||
| 					local i = index_y + (offset.x + x) | ||||
| 					data[i] = node_id | ||||
| 	for vi in iterfunc do | ||||
| 		data[vi] = node_id | ||||
| 		count = count + 1 | ||||
| 	end | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	mh.finish(manip, data) | ||||
| 	return count | ||||
| @@ -149,7 +144,7 @@ function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, holl | ||||
| 	end | ||||
|  | ||||
| 	-- Handle negative lengths | ||||
| 	local current_pos = {x=pos.x, y=pos.y, z=pos.z} | ||||
| 	local current_pos = vector.new(pos) | ||||
| 	if length < 0 then | ||||
| 		length = -length | ||||
| 		current_pos[axis] = current_pos[axis] - length | ||||
| @@ -162,12 +157,8 @@ function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, holl | ||||
|  | ||||
| 	-- Add desired shape (anything inbetween cylinder & cone) | ||||
| 	local node_id = minetest.get_content_id(node_name) | ||||
| 	local stride = {x=1, y=area.ystride, z=area.zstride} | ||||
| 	local offset = { | ||||
| 		x = current_pos.x - area.MinEdge.x, | ||||
| 		y = current_pos.y - area.MinEdge.y, | ||||
| 		z = current_pos.z - area.MinEdge.z, | ||||
| 	} | ||||
| 	local stride = vector.new(1, area.ystride, area.zstride) | ||||
| 	local offset = vector.subtract(current_pos, area.MinEdge) | ||||
| 	local count = 0 | ||||
| 	for i = 0, length - 1 do | ||||
| 		-- Calulate radius for this "height" in the cylinder | ||||
| @@ -225,12 +216,8 @@ function worldedit.pyramid(pos, axis, height, node_name, hollow) | ||||
|  | ||||
| 	-- Add pyramid | ||||
| 	local node_id = minetest.get_content_id(node_name) | ||||
| 	local stride = {x=1, y=area.ystride, z=area.zstride} | ||||
| 	local offset = { | ||||
| 		x = pos.x - area.MinEdge.x, | ||||
| 		y = pos.y - area.MinEdge.y, | ||||
| 		z = pos.z - area.MinEdge.z, | ||||
| 	} | ||||
| 	local stride = vector.new(1, area.ystride, area.zstride) | ||||
| 	local offset = vector.subtract(pos, area.MinEdge) | ||||
| 	local size = math.abs(height * step) | ||||
| 	local count = 0 | ||||
| 	-- For each level of the pyramid | ||||
| @@ -271,9 +258,9 @@ function worldedit.spiral(pos, length, height, spacer, node_name) | ||||
|  | ||||
| 	-- Set up variables | ||||
| 	local node_id = minetest.get_content_id(node_name) | ||||
| 	local stride = {x=1, y=area.ystride, z=area.zstride} | ||||
| 	local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z | ||||
| 	local i = offset_z * stride.z + offset_y * stride.y + offset_x + 1 | ||||
| 	local stride = vector.new(1, area.ystride, area.zstride) | ||||
| 	local offset = vector.subtract(pos, area.MinEdge) | ||||
| 	local i = offset.z * stride.z + offset.y * stride.y + offset.x + 1 | ||||
|  | ||||
| 	-- Add first column | ||||
| 	local count = height | ||||
|   | ||||
| @@ -66,7 +66,7 @@ function worldedit.serialize(pos1, pos2) | ||||
| 		has_meta[hash_node_position(meta_positions[i])] = true | ||||
| 	end | ||||
|  | ||||
| 	local pos = {x=pos1.x, y=0, z=0} | ||||
| 	local pos = vector.new(pos1.x, 0, 0) | ||||
| 	local count = 0 | ||||
| 	local result = {} | ||||
| 	while pos.x <= pos2.x do | ||||
| @@ -114,12 +114,15 @@ function worldedit.serialize(pos1, pos2) | ||||
| 	return LATEST_SERIALIZATION_HEADER .. result, count | ||||
| end | ||||
|  | ||||
| -- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) | ||||
| -- by ChillCode, available under the MIT license. | ||||
| local function deserialize_workaround(content) | ||||
| 	local nodes | ||||
| 	local nodes, err | ||||
| 	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 | ||||
| 		-- XXX: This is a filthy hack that works surprisingly well | ||||
| 		-- in LuaJIT, `minetest.deserialize` will fail due to the register limit | ||||
| @@ -129,26 +132,35 @@ local function deserialize_workaround(content) | ||||
| 		local escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end) | ||||
| 		local startpos, startpos1 = 1, 1 | ||||
| 		local endpos | ||||
| 		local entry | ||||
| 		while true do -- go through each individual node entry (except the last) | ||||
| 			startpos, endpos = escaped:find("},%s*{", startpos) | ||||
| 			startpos, endpos = escaped:find("}%s*,%s*{", startpos) | ||||
| 			if not startpos then | ||||
| 				break | ||||
| 			end | ||||
| 			local current = content:sub(startpos1, startpos) | ||||
| 			local entry = minetest.deserialize("return " .. current, true) | ||||
| 			entry, err = minetest.deserialize("return " .. current, true) | ||||
| 			if not entry then | ||||
| 				break | ||||
| 			end | ||||
| 			table.insert(nodes, entry) | ||||
| 			startpos, startpos1 = endpos, endpos | ||||
| 		end | ||||
| 		local entry = minetest.deserialize("return " .. content:sub(startpos1), true) -- process the last entry | ||||
| 		if not err then | ||||
| 			entry = minetest.deserialize("return " .. content:sub(startpos1), true) -- process the last entry | ||||
| 			table.insert(nodes, entry) | ||||
| 		end | ||||
| 	end | ||||
| 	if err then | ||||
| 		minetest.log("warning", "WorldEdit: deserialize: " .. err) | ||||
| 	end | ||||
| 	return nodes | ||||
| end | ||||
|  | ||||
| --- Loads the schematic in `value` into a node list in the latest format. | ||||
| -- @return A node list in the latest format, or nil on failure. | ||||
| local function load_schematic(value) | ||||
| 	local version, header, content = worldedit.read_header(value) | ||||
| 	local version, _, content = worldedit.read_header(value) | ||||
| 	local nodes = {} | ||||
| 	if version == 1 or version == 2 then -- Original flat table format | ||||
| 		local tables = minetest.deserialize(content, true) | ||||
| @@ -223,9 +235,7 @@ function worldedit.allocate_with_nodes(origin_pos, nodes) | ||||
| 		if y > pos2y then pos2y = y end | ||||
| 		if z > pos2z then pos2z = z end | ||||
| 	end | ||||
| 	local pos1 = {x=pos1x, y=pos1y, z=pos1z} | ||||
| 	local pos2 = {x=pos2x, y=pos2y, z=pos2z} | ||||
| 	return pos1, pos2, #nodes | ||||
| 	return vector.new(pos1x, pos1y, pos1z), vector.new(pos2x, pos2y, pos2z), #nodes | ||||
| end | ||||
|  | ||||
|  | ||||
| @@ -240,9 +250,10 @@ function worldedit.deserialize(origin_pos, value) | ||||
| 	worldedit.keep_loaded(pos1, pos2) | ||||
|  | ||||
| 	local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z | ||||
| 	local count = 0 | ||||
| 	local add_node, get_meta = minetest.add_node, minetest.get_meta | ||||
| 	local registered_nodes = minetest.registered_nodes | ||||
| 	for i, entry in ipairs(nodes) do | ||||
| 		if registered_nodes[entry.name] then | ||||
| 			entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z | ||||
| 			-- Entry acts as both position and node | ||||
| 			add_node(entry, entry) | ||||
| @@ -250,6 +261,7 @@ function worldedit.deserialize(origin_pos, value) | ||||
| 				get_meta(entry):from_table(entry.meta) | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	return #nodes | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| -- TODO: don't shit individual variables into the globals | ||||
| 
 | ||||
| --------------------- | ||||
| -- Helpers | ||||
| --------------------- | ||||
| 
 | ||||
| local vec = vector.new | ||||
| local vecw = function(axis, n, base) | ||||
| 	local ret = vec(base) | ||||
| @@ -16,9 +17,9 @@ local set_node = minetest.set_node | ||||
| -- Nodes | ||||
| --------------------- | ||||
| local air = "air" | ||||
| local testnode1 | ||||
| local testnode2 | ||||
| local testnode3 | ||||
| rawset(_G, "testnode1", "") | ||||
| rawset(_G, "testnode2", "") | ||||
| rawset(_G, "testnode3", "") | ||||
| -- Loads nodenames to use for tests | ||||
| local function init_nodes() | ||||
| 	testnode1 = minetest.registered_aliases["mapgen_stone"] | ||||
| @@ -27,7 +28,7 @@ local function init_nodes() | ||||
| 	assert(testnode1 and testnode2 and testnode3) | ||||
| end | ||||
| -- Writes repeating pattern into given area | ||||
| local function place_pattern(pos1, pos2, pattern) | ||||
| rawset(_G, "place_pattern", function(pos1, pos2, pattern) | ||||
| 	local pos = vec() | ||||
| 	local node = {name=""} | ||||
| 	local i = 1 | ||||
| @@ -43,14 +44,14 @@ local function place_pattern(pos1, pos2, pattern) | ||||
| 	end | ||||
| 	end | ||||
| 	end | ||||
| end | ||||
| end) | ||||
| 
 | ||||
| 
 | ||||
| --------------------- | ||||
| -- Area management | ||||
| --------------------- | ||||
| assert(minetest.get_mapgen_setting("mg_name") == "singlenode") | ||||
| local area = {} | ||||
| rawset(_G, "area", {}) | ||||
| do | ||||
| 	local areamin, areamax | ||||
| 	local off | ||||
| @@ -71,6 +72,9 @@ do | ||||
| 	end | ||||
| 	-- Reset area contents and state | ||||
| 	area.clear = function() | ||||
| 		if off and vector.equals(off, vec(0, 0, 0)) then | ||||
| 			return | ||||
| 		end | ||||
| 		local vmanip = minetest.get_voxel_manip(areamin, areamax) | ||||
| 		local vpos1, vpos2 = vmanip:get_emerged_area() | ||||
| 		local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1) | ||||
| @@ -87,10 +91,10 @@ do | ||||
| 	-- Returns an usable area [pos1, pos2] that does not overlap previous ones | ||||
| 	area.get = function(sizex, sizey, sizez) | ||||
| 		local size | ||||
| 		if sizey == nil or sizez == nil then | ||||
| 			size = {x=sizex, y=sizex, z=sizex} | ||||
| 		if sizey == nil and sizez == nil then | ||||
| 			size = vec(sizex, sizex, sizex) | ||||
| 		else | ||||
| 			size = {x=sizex, y=sizey, z=sizez} | ||||
| 			size = vec(sizex, sizey, sizez) | ||||
| 		end | ||||
| 		local pos1 = vector.add(areamin, off) | ||||
| 		local pos2 = vector.subtract(vector.add(pos1, size), 1) | ||||
| @@ -148,7 +152,7 @@ end | ||||
| --------------------- | ||||
| -- Checks | ||||
| --------------------- | ||||
| local check = {} | ||||
| rawset(_G, "check", {}) | ||||
| -- Check that all nodes in [pos1, pos2] are the node(s) specified | ||||
| check.filled = function(pos1, pos2, nodes) | ||||
| 	if type(nodes) == "string" then | ||||
| @@ -215,7 +219,7 @@ end | ||||
| -- The actual tests | ||||
| --------------------- | ||||
| local tests = {} | ||||
| local function register_test(name, func, opts) | ||||
| worldedit.register_test = function(name, func, opts) | ||||
| 	assert(type(name) == "string") | ||||
| 	assert(func == nil or type(func) == "function") | ||||
| 	if not opts then | ||||
| @@ -227,6 +231,7 @@ local function register_test(name, func, opts) | ||||
| 	opts.func = func | ||||
| 	table.insert(tests, opts) | ||||
| end | ||||
| local register_test = worldedit.register_test | ||||
| -- How this works: | ||||
| --   register_test registers a test with a name and function | ||||
| --   The function should return if the test passes or otherwise cause a Lua error | ||||
| @@ -241,8 +246,8 @@ end | ||||
| register_test("Internal self-test") | ||||
| register_test("is area loaded?", function() | ||||
| 	local pos1, _ = area.get(1) | ||||
| 	assert(get_node(pos1).name == "air") | ||||
| end, {dry=true}) | ||||
| 	assert(get_node(pos1).name == air) | ||||
| end) | ||||
| 
 | ||||
| register_test("area.split", function() | ||||
| 	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 | ||||
| 		end | ||||
| 	end | ||||
| end, {dry=true}) | ||||
| end) | ||||
| 
 | ||||
| register_test("check.filled", function() | ||||
| 	local pos1, pos2 = area.get(1, 2, 1) | ||||
| @@ -276,118 +281,49 @@ register_test("pattern", function() | ||||
| end) | ||||
| 
 | ||||
| 
 | ||||
| register_test("Generic node manipulations") | ||||
| register_test("worldedit.set", function() | ||||
| 	local pos1, pos2 = area.get(10) | ||||
| 	local m = area.margin(1) | ||||
| for _, name in ipairs({ | ||||
| 	"manipulations", "primitives", "schematic" | ||||
| }) do | ||||
| 	dofile(minetest.get_modpath("worldedit") .. "/test/" .. name .. ".lua") | ||||
| end | ||||
| 
 | ||||
| 	worldedit.set(pos1, pos2, testnode1) | ||||
| 
 | ||||
| 	check.filled(pos1, pos2, testnode1) | ||||
| 	check.filled2(m, air) | ||||
| register_test("Code") | ||||
| register_test("worldedit.lua", function() | ||||
| 	-- syntax error | ||||
| 	local err, ret = worldedit.lua("?") | ||||
| 	assert(ret == nil) | ||||
| 	assert(err:find("unexpected symbol")) | ||||
| 
 | ||||
| 	-- runtime error | ||||
| 	local err, ret = worldedit.lua("error(1234)") | ||||
| 	assert(ret == nil) | ||||
| 	assert(err:find("1234")) | ||||
| 
 | ||||
| 	-- normal operation | ||||
| 	local err, ret = worldedit.lua("return name..tostring(player == nil)..tostring(pos == nil)", "nobody") | ||||
| 	assert(err == nil) | ||||
| 	assert(ret == "\"nobodytruetrue\"") | ||||
| end) | ||||
| 
 | ||||
| register_test("worldedit.set mix", function() | ||||
| 	local pos1, pos2 = area.get(10) | ||||
| 	local m = area.margin(1) | ||||
| register_test("worldedit.luatransform", function() | ||||
| 	local pos1, pos2 = area.get(2) | ||||
| 
 | ||||
| 	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}) | ||||
| 	check.filled2(m, air) | ||||
| 	-- runtime error | ||||
| 	local err = worldedit.luatransform(pos1, pos2, "error(2345)") | ||||
| 	assert(err:find("2345")) | ||||
| 
 | ||||
| 	-- normal operation | ||||
| 	local err = worldedit.luatransform(pos1, pos2, | ||||
| 		"minetest.swap_node(pos, {name=" .. ("%q"):format(testnode1) .. "})") | ||||
| 	assert(err == nil) | ||||
| 	check.filled(pos1, pos1, testnode1) | ||||
| end) | ||||
| 
 | ||||
| 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 | ||||
| --------------------- | ||||
| @@ -406,7 +342,7 @@ worldedit.run_tests = function() | ||||
| 	for x = 0, math.floor(wanted.x/16) do | ||||
| 	for y = 0, math.floor(wanted.y/16) do | ||||
| 	for z = 0, math.floor(wanted.z/16) do | ||||
| 		assert(minetest.forceload_block({x=x*16, y=y*16, z=z*16}, true)) | ||||
| 		assert(minetest.forceload_block(vec(x*16, y*16, z*16), true, -1)) | ||||
| 	end | ||||
| 	end | ||||
| 	end | ||||
| @@ -418,9 +354,7 @@ worldedit.run_tests = function() | ||||
| 				local s = "---- " .. test.name .. " " | ||||
| 				print(s .. string.rep("-", 60 - #s)) | ||||
| 			else | ||||
| 				if not test.dry then | ||||
| 				area.clear() | ||||
| 				end | ||||
| 				local ok, err = pcall(test.func) | ||||
| 				print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL")) | ||||
| 				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) | ||||
|  | ||||
| 	local pos = {x=pos1.x, y=0, z=0} | ||||
| 	local pos = vector.new(pos1.x, 0, 0) | ||||
| 	local get_node, get_meta, swap_node = minetest.get_node, | ||||
| 			minetest.get_meta, minetest.swap_node | ||||
| 	while pos.x <= pos2.x do | ||||
| @@ -79,7 +79,7 @@ function worldedit.highlight(pos1, pos2, node_name) | ||||
|  | ||||
| 	worldedit.keep_loaded(pos1, pos2) | ||||
|  | ||||
| 	local pos = {x=pos1.x, y=0, z=0} | ||||
| 	local pos = vector.new(pos1.x, 0, 0) | ||||
| 	local get_node, get_meta, swap_node = minetest.get_node, | ||||
| 			minetest.get_meta, minetest.swap_node | ||||
| 	local count = 0 | ||||
|   | ||||
| @@ -1,8 +1,4 @@ | ||||
| if minetest.raycast == nil then | ||||
| 	error( | ||||
| 		"worldedit_brush requires at least Minetest 5.0" | ||||
| 	) | ||||
| end | ||||
| local S = minetest.get_translator("worldedit_brush") | ||||
|  | ||||
| local BRUSH_MAX_DIST = 150 | ||||
| local brush_on_use = function(itemstack, placer) | ||||
| @@ -12,7 +8,8 @@ local brush_on_use = function(itemstack, placer) | ||||
| 	local cmd = meta:get_string("command") | ||||
| 	if cmd == "" then | ||||
| 		worldedit.player_notify(name, | ||||
| 			"This brush is not bound, use //brush to bind a command to it.") | ||||
| 			S("This brush is not bound, use @1 to bind a command to it.", | ||||
| 			minetest.colorize("#00ffff", "//brush")), "info") | ||||
| 		return false | ||||
| 	end | ||||
|  | ||||
| @@ -22,17 +19,17 @@ local brush_on_use = function(itemstack, placer) | ||||
| 	local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs) | ||||
| 	if not has_privs then | ||||
| 		worldedit.player_notify(name, | ||||
| 			"Missing privileges: " .. table.concat(missing_privs, ", ")) | ||||
| 			S("Missing privileges: @1", table.concat(missing_privs, ", ")), "error") | ||||
| 		return false | ||||
| 	end | ||||
|  | ||||
| 	local raybegin = vector.add(placer:get_pos(), | ||||
| 		{x=0, y=placer:get_properties().eye_height, z=0}) | ||||
| 		vector.new(0, placer:get_properties().eye_height, 0)) | ||||
| 	local rayend = vector.add(raybegin, vector.multiply(placer:get_look_dir(), BRUSH_MAX_DIST)) | ||||
| 	local ray = minetest.raycast(raybegin, rayend, false, true) | ||||
| 	local pointed_thing = ray:next() | ||||
| 	if pointed_thing == nil then | ||||
| 		worldedit.player_notify(name, "Too far away.") | ||||
| 		worldedit.player_notify(name, S("Too far away."), "error") | ||||
| 		return false | ||||
| 	end | ||||
|  | ||||
| @@ -41,17 +38,17 @@ local brush_on_use = function(itemstack, placer) | ||||
| 	worldedit.pos2[name] = nil | ||||
| 	worldedit.marker_update(name) | ||||
|  | ||||
| 	-- this isn't really clean... | ||||
| 	local player_notify_old = worldedit.player_notify | ||||
| 	worldedit.player_notify = function(name, msg) | ||||
| 		if string.match(msg, "^%d") then return end -- discard "1234 nodes added." | ||||
| 		return player_notify_old(name, msg) | ||||
| 	end | ||||
|  | ||||
| 	assert(cmddef.require_pos < 2) | ||||
| 	local parsed = {cmddef.parse(meta:get_string("params"))} | ||||
| 	if not table.remove(parsed, 1) then return false end -- shouldn't happen | ||||
|  | ||||
| 	-- discard success messages | ||||
| 	local player_notify_old = worldedit.player_notify | ||||
| 	worldedit.player_notify = function(name, msg, typ) | ||||
| 		if typ == "ok" then return end | ||||
| 		return player_notify_old(name, msg, typ) | ||||
| 	end | ||||
|  | ||||
| 	minetest.log("action", string.format("%s uses WorldEdit brush (//%s) at %s", | ||||
| 		name, cmd, minetest.pos_to_string(pointed_thing.under))) | ||||
| 	cmddef.func(name, unpack(parsed)) | ||||
| @@ -61,7 +58,7 @@ local brush_on_use = function(itemstack, placer) | ||||
| end | ||||
|  | ||||
| minetest.register_tool(":worldedit:brush", { | ||||
| 	description = "WorldEdit Brush", | ||||
| 	description = S("WorldEdit Brush"), | ||||
| 	inventory_image = "worldedit_brush.png", | ||||
| 	stack_max = 1, -- no need to stack these (metadata prevents this anyway) | ||||
| 	range = 0, | ||||
| @@ -74,7 +71,7 @@ minetest.register_tool(":worldedit:brush", { | ||||
| worldedit.register_command("brush", { | ||||
| 	privs = {worldedit=true}, | ||||
| 	params = "none/<cmd> [parameters]", | ||||
| 	description = "Assign command to WorldEdit brush item", | ||||
| 	description = S("Assign command to WorldEdit brush item or clear assignment using 'none'"), | ||||
| 	parse = function(param) | ||||
| 		local found, _, cmd, params = param:find("^([^%s]+)%s+(.+)$") | ||||
| 		if not found then | ||||
| @@ -87,39 +84,39 @@ worldedit.register_command("brush", { | ||||
| 		return true, cmd, params | ||||
| 	end, | ||||
| 	func = function(name, cmd, params) | ||||
| 		local itemstack = minetest.get_player_by_name(name):get_wielded_item() | ||||
| 		local player = minetest.get_player_by_name(name) | ||||
| 		if not player then return end | ||||
| 		local itemstack = player:get_wielded_item() | ||||
| 		if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then | ||||
| 			worldedit.player_notify(name, "Not holding brush item.") | ||||
| 			return | ||||
| 			return false, S("Not holding brush item.") | ||||
| 		end | ||||
|  | ||||
| 		cmd = cmd:lower() | ||||
| 		local meta = itemstack:get_meta() | ||||
| 		if cmd == "none" then | ||||
| 			meta:from_table(nil) | ||||
| 			worldedit.player_notify(name, "Brush assignment cleared.") | ||||
| 			worldedit.player_notify(name, S("Brush assignment cleared."), "ok") | ||||
| 		else | ||||
| 			local cmddef = worldedit.registered_commands[cmd] | ||||
| 			if cmddef == nil or cmddef.require_pos ~= 1 then | ||||
| 				worldedit.player_notify(name, "//" .. cmd .. " cannot be used with brushes") | ||||
| 				return | ||||
| 				return false, S("@1 cannot be used with brushes", | ||||
| 					minetest.colorize("#00ffff", "//"..cmd)) | ||||
| 			end | ||||
|  | ||||
| 			-- Try parsing command params so we can give the user feedback | ||||
| 			local ok, err = cmddef.parse(params) | ||||
| 			if not ok then | ||||
| 				err = err or "invalid usage" | ||||
| 				worldedit.player_notify(name, "Error with brush command: " .. err) | ||||
| 				return | ||||
| 				err = err or S("invalid usage") | ||||
| 				return false, S("Error with command: @1", err) | ||||
| 			end | ||||
|  | ||||
| 			meta:set_string("command", cmd) | ||||
| 			meta:set_string("params", params) | ||||
| 			local fullcmd = "//" .. cmd .. " " .. params | ||||
| 			local fullcmd = minetest.colorize("#00ffff", "//"..cmd) .. " " .. params | ||||
| 			meta:set_string("description", | ||||
| 				minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd) | ||||
| 			worldedit.player_notify(name, "Brush assigned to command: " .. fullcmd) | ||||
| 			worldedit.player_notify(name, S("Brush assigned to command: @1", fullcmd), "ok") | ||||
| 		end | ||||
| 		minetest.get_player_by_name(name):set_wielded_item(itemstack) | ||||
| 		player:set_wielded_item(itemstack) | ||||
| 	end, | ||||
| }) | ||||
|   | ||||
							
								
								
									
										12
									
								
								worldedit_brush/locale/template.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | ||||
| # textdomain: worldedit_brush | ||||
| This brush is not bound, use @1 to bind a command to it.= | ||||
| Missing privileges: @1= | ||||
| Too far away.= | ||||
| WorldEdit Brush= | ||||
| Assign command to WorldEdit brush item or clear assignment using 'none'= | ||||
| Not holding brush item.= | ||||
| Brush assignment cleared.= | ||||
| @1 cannot be used with brushes= | ||||
| invalid usage= | ||||
| Error with command: @1= | ||||
| Brush assigned to command: @1= | ||||
							
								
								
									
										12
									
								
								worldedit_brush/locale/worldedit_brush.de.tr
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,12 @@ | ||||
| # textdomain: worldedit_brush | ||||
| This brush is not bound, use @1 to bind a command to it.=Dieser Pinsel ist nicht konfiguriert, nutzen Sie @1, um einen Befehl an ihn zu binden. | ||||
| Missing privileges: @1=Fehlende Privilegien: @1 | ||||
| Too far away.=Zu weit weg. | ||||
| WorldEdit Brush=WorldEdit-Pinsel | ||||
| Assign command to WorldEdit brush item or clear assignment using 'none'=Befehl an WorldEdit-Pinsel binden oder Zuweisung mittels „none“ aufheben | ||||
| Not holding brush item.=Kein Pinsel in der Hand. | ||||
| Brush assignment cleared.=Pinsel-Zuweisung aufgehoben. | ||||
| @1 cannot be used with brushes=@1 kann nicht mit Pinseln verwendet werden | ||||
| invalid usage=Ungültige Verwendung | ||||
| Error with command: @1=Befehl ist fehlerhaft: @1 | ||||
| Brush assigned to command: @1=Pinsel an Befehl gebunden: @1 | ||||
| Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 301 B | 
							
								
								
									
										1
									
								
								worldedit_commands/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1 +0,0 @@ | ||||
| *~ | ||||
							
								
								
									
										59
									
								
								worldedit_commands/code.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,59 @@ | ||||
| local S = minetest.get_translator("worldedit_commands") | ||||
|  | ||||
| local function check_region(name) | ||||
| 	return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| end | ||||
|  | ||||
|  | ||||
| worldedit.register_command("lua", { | ||||
| 	params = "<code>", | ||||
| 	description = S("Executes <code> as a Lua chunk in the global namespace"), | ||||
| 	category = S("Code"), | ||||
| 	privs = {worldedit=true, server=true}, | ||||
| 	parse = function(param) | ||||
| 		if param == "" then | ||||
| 			return false | ||||
| 		end | ||||
| 		return true, param | ||||
| 	end, | ||||
| 	func = function(name, param) | ||||
| 		-- shorthand like in the Lua interpreter | ||||
| 		if param:sub(1, 1) == "=" then | ||||
| 			param = "return " .. param:sub(2) | ||||
| 		end | ||||
| 		local err, ret = worldedit.lua(param, name) | ||||
| 		if err == nil then | ||||
| 			minetest.log("action", name .. " executed " .. param) | ||||
| 			if ret ~= "nil" then | ||||
| 				worldedit.player_notify(name, "code successfully executed, returned " .. ret, "info") | ||||
| 			else | ||||
| 				worldedit.player_notify(name, "code successfully executed", "ok") | ||||
| 			end | ||||
| 		else | ||||
| 			minetest.log("action", name .. " tried to execute " .. param) | ||||
| 			worldedit.player_notify(name, "code error: " .. err, "error") | ||||
| 		end | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("luatransform", { | ||||
| 	params = "<code>", | ||||
| 	description = S("Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region"), | ||||
| 	category = S("Code"), | ||||
| 	privs = {worldedit=true, server=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		return true, param | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, param) | ||||
| 		local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param) | ||||
| 		if err then | ||||
| 			worldedit.player_notify(name, "code error: " .. err, "error") | ||||
| 			minetest.log("action", name.." tried to execute luatransform "..param) | ||||
| 		else | ||||
| 			worldedit.player_notify(name, "code successfully executed", "ok") | ||||
| 			minetest.log("action", name.." executed luatransform "..param) | ||||
| 		end | ||||
| 	end, | ||||
| }) | ||||
| @@ -1,6 +1,9 @@ | ||||
| local S = minetest.get_translator("worldedit_commands") | ||||
|  | ||||
| worldedit.register_command("outset", { | ||||
| 	params = "[h/v] <amount>", | ||||
| 	description = "Outset the selected region.", | ||||
| 	description = S("Outset the selected region."), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| @@ -11,7 +14,7 @@ worldedit.register_command("outset", { | ||||
|  | ||||
| 		local hv_test = dir:find("[^hv]+") | ||||
| 		if hv_test ~= nil then | ||||
| 			return false, "Invalid direction." | ||||
| 			return false, S("Invalid direction: @1", dir) | ||||
| 		end | ||||
|  | ||||
| 		return true, dir, tonumber(amount) | ||||
| @@ -28,18 +31,19 @@ worldedit.register_command("outset", { | ||||
| 			assert(worldedit.cuboid_linear_expand(name, 'y', 1, amount)) | ||||
| 			assert(worldedit.cuboid_linear_expand(name, 'y', -1, amount)) | ||||
| 		else | ||||
| 			return false, "Invalid number of arguments" | ||||
| 			return false, S("Invalid number of arguments") | ||||
| 		end | ||||
|  | ||||
| 		worldedit.marker_update(name) | ||||
| 		return true, "Region outset by " .. amount .. " blocks" | ||||
| 		return true, S("Region outset by @1 nodes", amount) | ||||
|       end, | ||||
| }) | ||||
|  | ||||
|  | ||||
| worldedit.register_command("inset", { | ||||
| 	params = "[h/v] <amount>", | ||||
| 	description = "Inset the selected region.", | ||||
| 	description = S("Inset the selected region."), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| @@ -48,7 +52,7 @@ worldedit.register_command("inset", { | ||||
| 			return false | ||||
| 		end | ||||
| 		if dir:find("[^hv]") ~= nil then | ||||
| 			return false, "Invalid direction." | ||||
| 			return false, S("Invalid direction: @1", dir) | ||||
| 		end | ||||
|  | ||||
| 		return true, dir, tonumber(amount) | ||||
| @@ -65,18 +69,19 @@ worldedit.register_command("inset", { | ||||
| 			assert(worldedit.cuboid_linear_expand(name, 'y', 1, -amount)) | ||||
| 			assert(worldedit.cuboid_linear_expand(name, 'y', -1, -amount)) | ||||
| 		else | ||||
| 			return false, "Invalid number of arguments" | ||||
| 			return false, S("Invalid number of arguments") | ||||
| 		end | ||||
|  | ||||
| 		worldedit.marker_update(name) | ||||
| 		return true, "Region inset by " .. amount .. " blocks" | ||||
| 		return true, S("Region inset by @1 nodes", amount) | ||||
|       end, | ||||
| }) | ||||
|  | ||||
|  | ||||
| worldedit.register_command("shift", { | ||||
| 	params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>", | ||||
| 	description = "Shifts the selection area without moving its contents", | ||||
| 	description = S("Shifts the selection area without moving its contents"), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| @@ -98,20 +103,21 @@ worldedit.register_command("shift", { | ||||
| 		end | ||||
|  | ||||
| 		if axis == nil or dir == nil then | ||||
| 			return false, "Invalid if looking straight up or down" | ||||
| 			return false, S("Invalid if looking straight up or down") | ||||
| 		end | ||||
|  | ||||
| 		assert(worldedit.cuboid_shift(name, axis, amount * dir)) | ||||
| 		worldedit.marker_update(name) | ||||
|  | ||||
| 		return true, "Region shifted by " .. amount .. " nodes" | ||||
| 		return true, S("Region shifted by @1 nodes", amount) | ||||
|       end, | ||||
| }) | ||||
|  | ||||
|  | ||||
| worldedit.register_command("expand", { | ||||
| 	params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]", | ||||
| 	description = "Expands the selection in the selected absolute or relative axis", | ||||
| 	description = S("Expands the selection in the selected absolute or relative axis"), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| @@ -135,7 +141,7 @@ worldedit.register_command("expand", { | ||||
| 			axis, dir = worldedit.translate_direction(name, direction) | ||||
|  | ||||
| 			if axis == nil or dir == nil then | ||||
| 				return false, "Invalid if looking straight up or down" | ||||
| 				return false, S("Invalid if looking straight up or down") | ||||
| 			end | ||||
| 		else | ||||
| 			if direction == "?" then | ||||
| @@ -153,14 +159,15 @@ worldedit.register_command("expand", { | ||||
| 		worldedit.cuboid_linear_expand(name, axis, dir, amount) | ||||
| 		worldedit.cuboid_linear_expand(name, axis, -dir, rev_amount) | ||||
| 		worldedit.marker_update(name) | ||||
| 		return true, "Region expanded by " .. (amount + rev_amount) .. " nodes" | ||||
| 		return true, S("Region expanded by @1 nodes", amount + rev_amount) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
|  | ||||
| worldedit.register_command("contract", { | ||||
| 	params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]", | ||||
| 	description = "Contracts the selection in the selected absolute or relative axis", | ||||
| 	description = S("Contracts the selection in the selected absolute or relative axis"), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| @@ -184,7 +191,7 @@ worldedit.register_command("contract", { | ||||
| 			axis, dir = worldedit.translate_direction(name, direction) | ||||
|  | ||||
| 			if axis == nil or dir == nil then | ||||
| 				return false, "Invalid if looking straight up or down" | ||||
| 				return false, S("Invalid if looking straight up or down") | ||||
| 			end | ||||
| 		else | ||||
| 			if direction == "?" then | ||||
| @@ -202,13 +209,13 @@ worldedit.register_command("contract", { | ||||
| 		worldedit.cuboid_linear_expand(name, axis, dir, -amount) | ||||
| 		worldedit.cuboid_linear_expand(name, axis, -dir, -rev_amount) | ||||
| 		worldedit.marker_update(name) | ||||
| 		return true, "Region contracted by " .. (amount + rev_amount) .. " nodes" | ||||
| 		return true, S("Region contracted by @1 nodes", amount + rev_amount) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("cubeapply", { | ||||
| 	params = "<size>/(<sizex> <sizey> <sizez>) <command> [parameters]", | ||||
| 	description = "Select a cube with side length <size> around position 1 and run <command> on region", | ||||
| 	description = S("Select a cube with side length <size> around position 1 and run <command> on region"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = function(param) | ||||
| @@ -230,7 +237,8 @@ worldedit.register_command("cubeapply", { | ||||
| 		end | ||||
| 		local cmddef = worldedit.registered_commands[cmd] | ||||
| 		if cmddef == nil or cmddef.require_pos ~= 2 then | ||||
| 			return false, "invalid usage: //" .. cmd .. " cannot be used with cubeapply" | ||||
| 			return false, S("invalid usage: @1 cannot be used with cubeapply", | ||||
| 				minetest.colorize("#00ffff", "//"..cmd)) | ||||
| 		end | ||||
| 		-- run parsing of target command | ||||
| 		local parsed = {cmddef.parse(args)} | ||||
| @@ -241,15 +249,14 @@ worldedit.register_command("cubeapply", { | ||||
| 	end, | ||||
| 	nodes_needed = function(name, sidex, sidey, sidez, cmd, parsed) | ||||
| 		-- its not possible to defer to the target command at this point | ||||
| 		-- FIXME: why not? | ||||
| 		return sidex * sidey * sidez | ||||
| 	end, | ||||
| 	func = function(name, sidex, sidey, sidez, cmd, parsed) | ||||
| 		local cmddef = assert(worldedit.registered_commands[cmd]) | ||||
| 		local success, missing_privs = minetest.check_player_privs(name, cmddef.privs) | ||||
| 		if not success then | ||||
| 			worldedit.player_notify(name, "Missing privileges: " .. | ||||
| 				table.concat(missing_privs, ", ")) | ||||
| 			return | ||||
| 			return false, S("Missing privileges: @1", table.concat(missing_privs, ", ")) | ||||
| 		end | ||||
|  | ||||
| 		-- update region to be the cuboid the user wanted | ||||
|   | ||||
							
								
								
									
										153
									
								
								worldedit_commands/locale/template.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,153 @@ | ||||
| # textdomain: worldedit_commands | ||||
| Outset the selected region.= | ||||
| Invalid direction: @1= | ||||
| Invalid number of arguments= | ||||
| Region outset by @1 nodes= | ||||
| Inset the selected region.= | ||||
| Region inset by @1 nodes= | ||||
| Shifts the selection area without moving its contents= | ||||
| Invalid if looking straight up or down= | ||||
| Region shifted by @1 nodes= | ||||
| Expands the selection in the selected absolute or relative axis= | ||||
| Region expanded by @1 nodes= | ||||
| Contracts the selection in the selected absolute or relative axis= | ||||
| Region contracted by @1 nodes= | ||||
| Select a cube with side length <size> around position 1 and run <command> on region= | ||||
| invalid usage: @1 cannot be used with cubeapply= | ||||
| Missing privileges: @1= | ||||
| Region operations= | ||||
| Can use WorldEdit commands= | ||||
| no region selected= | ||||
| no position 1 selected= | ||||
| invalid usage= | ||||
| ERROR: the operation was cancelled because the region has changed.= | ||||
| Could not open file "@1"= | ||||
| Invalid file format!= | ||||
| Schematic was created with a newer version of WorldEdit.= | ||||
| Get information about the WorldEdit mod= | ||||
| WorldEdit @1 is available on this server. Type @2 to get a list of commands, or find more information at @3= | ||||
| Get help for WorldEdit commands= | ||||
| alias to @1= | ||||
| You are not allowed to use any WorldEdit commands.= | ||||
| Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.= | ||||
| Available commands:@n= | ||||
| Command not available: = | ||||
| Enable or disable node inspection= | ||||
| inspector: inspection enabled for @1, currently facing the @2 axis= | ||||
| inspector: inspection disabled= | ||||
| inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis= | ||||
| Reset the region so that it is empty= | ||||
| region reset= | ||||
| Show markers at the region positions= | ||||
| region marked= | ||||
| Hide markers if currently shown= | ||||
| region unmarked= | ||||
| Set WorldEdit region position @1 to the player's location= | ||||
| position @1 set to @2= | ||||
| Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region= | ||||
| unknown subcommand: @1= | ||||
| select positions by punching two nodes= | ||||
| select position @1 by punching a node= | ||||
| position @1: @2= | ||||
| position @1 not set= | ||||
| Set a WorldEdit region position to the position at (<x>, <y>, <z>)= | ||||
| Display the volume of the current WorldEdit region= | ||||
| current region has a volume of @1 nodes (@2*@3*@4)= | ||||
| Remove all MapBlocks (16x16x16) containing the selected area from the map= | ||||
| Node manipulation= | ||||
| Area deleted.= | ||||
| There was an error during deletion of the area.= | ||||
| Set the current WorldEdit region to <node>= | ||||
| invalid node name: @1= | ||||
| @1 nodes set= | ||||
| Set param2 of all nodes in the current WorldEdit region to <param2>= | ||||
| Param2 is out of range (must be between 0 and 255 inclusive!)= | ||||
| @1 nodes altered= | ||||
| Fill the current WorldEdit region with a random mix of <node1>, ...= | ||||
| invalid search node name: @1= | ||||
| invalid replace node name: @1= | ||||
| Replace all instances of <search node> with <replace node> in the current WorldEdit region= | ||||
| @1 nodes replaced= | ||||
| Replace all nodes other than <search node> with <replace node> in the current WorldEdit region= | ||||
| Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.= | ||||
| Shapes= | ||||
| @1 nodes added= | ||||
| Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.= | ||||
| Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>= | ||||
| Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>= | ||||
| Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>= | ||||
| Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>= | ||||
| Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>= | ||||
| Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>= | ||||
| Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>= | ||||
| Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>= | ||||
| Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>= | ||||
| Copy the current WorldEdit region along the given axis by <amount> nodes= | ||||
| Transformations= | ||||
| @1 nodes copied= | ||||
| Move the current WorldEdit region along the given axis by <amount> nodes= | ||||
| @1 nodes moved= | ||||
| Stack the current WorldEdit region along the given axis <count> times= | ||||
| @1 nodes stacked= | ||||
| Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>= | ||||
| invalid count: @1= | ||||
| invalid increments: @1= | ||||
| Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin= | ||||
| invalid scaling factors: @1= | ||||
| @1 nodes stretched= | ||||
| Transpose the current WorldEdit region along the given axes= | ||||
| invalid usage: axes must be different= | ||||
| @1 nodes transposed= | ||||
| Flip the current WorldEdit region along the given axis= | ||||
| @1 nodes flipped= | ||||
| Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)= | ||||
| invalid usage: angle must be multiple of 90= | ||||
| @1 nodes rotated= | ||||
| Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)= | ||||
| @1 nodes oriented= | ||||
| Fix the lighting in the current WorldEdit region= | ||||
| @1 nodes updated= | ||||
| Remove any fluid node within the current WorldEdit region= | ||||
| Remove any plant, tree or foliage-like nodes in the selected region= | ||||
| @1 nodes removed= | ||||
| Hide all nodes in the current WorldEdit region non-destructively= | ||||
| @1 nodes hidden= | ||||
| Suppress all <node> in the current WorldEdit region non-destructively= | ||||
| @1 nodes suppressed= | ||||
| Highlight <node> in the current WorldEdit region by hiding everything else non-destructively= | ||||
| @1 nodes highlighted= | ||||
| Restores nodes hidden with WorldEdit in the current WorldEdit region= | ||||
| @1 nodes restored= | ||||
| Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.= | ||||
| Save the current WorldEdit region to "(world folder)/schems/<file>.we"= | ||||
| Schematics= | ||||
| Disallowed file name: @1= | ||||
| Could not save file to "@1"= | ||||
| @1 nodes saved= | ||||
| Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region= | ||||
| Schematic empty, nothing allocated= | ||||
| @1 nodes allocated= | ||||
| Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin= | ||||
| Loading failed!= | ||||
| @1 nodes loaded= | ||||
| Executes <code> as a Lua chunk in the global namespace= | ||||
| Code= | ||||
| Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region= | ||||
| Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"= | ||||
| Failed to create Minetest schematic= | ||||
| Saved Minetest schematic to @1= | ||||
| Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin= | ||||
| failed to place Minetest schematic= | ||||
| placed Minetest schematic @1 at @2= | ||||
| Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry= | ||||
| select Minetest schematic probability values by punching nodes= | ||||
| finished Minetest schematic probability selection= | ||||
| currently set node probabilities:= | ||||
| invalid node probability given, not saved= | ||||
| Clears all objects within the WorldEdit region= | ||||
| @1 objects cleared= | ||||
| WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel= | ||||
| Confirm a pending operation= | ||||
| no operation pending= | ||||
| Abort a pending operation= | ||||
| WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd= | ||||
							
								
								
									
										164
									
								
								worldedit_commands/locale/worldedit_commands.de.tr
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,164 @@ | ||||
| # textdomain: worldedit_commands | ||||
| ## | ||||
| # Übersetzungshinweise: | ||||
| # node = Block | ||||
| # command = Befehl | ||||
| # region = Gebiet | ||||
| # position 1/2 = Position 1/2 | ||||
| # schematic = Schematic | ||||
| # Der Anwender wird gesiezt. | ||||
| # Diese Datei auf Rechtschreibfehler prüfen (unter Linux): | ||||
| # sed -rne 's|^.*[^@]=(.*)$|\1|gp' worldedit_commands/locale/worldedit_commands.de.tr | sed -re 's/\b(WorldEdit|Schematic|Minetest)\b/Beispiel/g' | LANG=de_DE.UTF-8 hunspell -L | ||||
| ## | ||||
| Outset the selected region.=Ausgewähltes Gebiet vergrößern. | ||||
| Invalid direction: @1=Ungültige Richtung: @1 | ||||
| Invalid number of arguments=Ungültige Anzahl der Argumente | ||||
| Region outset by @1 nodes=Gebiet um @1 Blöcke vergrößert | ||||
| Inset the selected region.=Ausgewähltes Gebiet verkleinern. | ||||
| Region inset by @1 nodes=Gebiet um @1 Blöcke verkleinert | ||||
| Shifts the selection area without moving its contents=Verschiebt das ausgewählte Gebiet ohne dessen Inhalt | ||||
| Invalid if looking straight up or down=Nicht zulässig bei gerader Blickrichtung nach unten oder oben | ||||
| Region shifted by @1 nodes=Gebiet um @1 Blöcke verschoben | ||||
| Expands the selection in the selected absolute or relative axis=Erweitert das Gebiet in Richtung einer absoluten oder relativen Achse | ||||
| Region expanded by @1 nodes=Gebiet um @1 Blöcke erweitert | ||||
| Contracts the selection in the selected absolute or relative axis=Schrumpft das Gebiet in Richtung einer absoluten oder relativen Achse | ||||
| Region contracted by @1 nodes=Gebiet um @1 Blöcke geschrumpft | ||||
| Select a cube with side length <size> around position 1 and run <command> on region=Einen Würfel mit Seitenlänge <size> um Position 1 auswählen und <command> auf diesem Gebiet ausführen | ||||
| invalid usage: @1 cannot be used with cubeapply=Ungültige Verwendung: @1 kann nicht mit cubeapply verwendet werden | ||||
| Missing privileges: @1=Fehlende Privilegien: @1 | ||||
| Region operations=Gebietsoperationen | ||||
| Can use WorldEdit commands=Kann WorldEdit-Befehle benutzen | ||||
| no region selected=Kein Gebiet ausgewählt | ||||
| no position 1 selected=Keine Position 1 ausgewählt | ||||
| invalid usage=Ungültige Verwendung | ||||
| ERROR: the operation was cancelled because the region has changed.=FEHLER: Der Vorgang wurde abgebrochen, weil das Gebiet verändert wurde. | ||||
| Could not open file "@1"=Konnte die Datei „@1“ nicht öffnen | ||||
| Invalid file format!=Ungültiges Dateiformat! | ||||
| Schematic was created with a newer version of WorldEdit.=Schematic wurde mit einer neueren Version von WorldEdit erstellt. | ||||
| Get information about the WorldEdit mod=Informationen über die WorldEdit-Mod erhalten | ||||
| WorldEdit @1 is available on this server. Type @2 to get a list of commands, or find more information at @3=WorldEdit @1 ist auf diesem Server verfügbar. Nutzen Sie @2, um eine Liste der Befehle zu erhalten, oder finden Sie weitere Informationen unter @3 | ||||
| Get help for WorldEdit commands=Hilfe für WorldEdit-Befehle erhalten | ||||
| alias to @1=Alias für @1 | ||||
| You are not allowed to use any WorldEdit commands.=Ihnen ist nicht erlaubt, WorldEdit-Befehle zu nutzen. | ||||
| Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=Verfügbare Befehle: @1@n„@2“ benutzen, um mehr Informationen zu erhalten, oder „@3“, um alles aufzulisten. | ||||
| Available commands:@n=Verfügbare Befehle:@n | ||||
| Command not available: =Befehl nicht verfügbar:  | ||||
| Enable or disable node inspection=Inspizieren von Blöcken ein- oder ausschalten | ||||
| inspector: inspection enabled for @1, currently facing the @2 axis=Inspektur: für @1 aktiviert, aktuell der @2-Achse zugewandt | ||||
| inspector: inspection disabled=Inspektur: deaktiviert | ||||
| inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=Inspektur: @1 bei @2 (param1@=@3, param2@=@4, einfallendes Licht@=@5), der „@6“-Achse zugewandt, gehauen | ||||
| Reset the region so that it is empty=Gebiet zurücksetzen, sodass es leer ist | ||||
| region reset=Gebiet zurückgesetzt | ||||
| Show markers at the region positions=Markierungen bei den Gebietspositionen anzeigen | ||||
| region marked=Gebiet markiert | ||||
| Hide markers if currently shown=Markierungen verstecken, falls derzeit sichtbar | ||||
| region unmarked=Gebiet nicht mehr markiert | ||||
| Set WorldEdit region position @1 to the player's location=Position @1 des WorldEdit-Gebiets auf die Position des Spielers setzen | ||||
| position @1 set to @2=Position @1 auf @2 gesetzt | ||||
| Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region=WorldEdit-Gebiet, WorldEdit-Position 1 oder WorldEdit-Position 2 durch Schlagen von Blöcken setzen oder aktuelles WorldEdit-Gebiet anzeigen | ||||
| unknown subcommand: @1=Unbekannter Unterbefehl: @1 | ||||
| select positions by punching two nodes=Wählen Sie die Position durch Hauen zweier Blöcke | ||||
| select position @1 by punching a node=Wählen Sie Position @1 durch Hauen eines Blocks | ||||
| position @1: @2=Position @1: @2 | ||||
| position @1 not set=Position @1 ist nicht gesetzt | ||||
| Set a WorldEdit region position to the position at (<x>, <y>, <z>)=Eine Position des WorldEdit-Gebiets auf (<x>, <y>, <z>) setzen | ||||
| Display the volume of the current WorldEdit region=Volumen des aktuellen WorldEdit-Gebiets anzeigen | ||||
| current region has a volume of @1 nodes (@2*@3*@4)=Das aktuelle Gebiet hat ein Volumen von @1 Blöcken (@2×@3×@4) | ||||
| Remove all MapBlocks (16x16x16) containing the selected area from the map=Alle Kartenblöcke (16×16×16) entfernen, die das gewählte Gebiet enthalten | ||||
| Node manipulation=Block-Manipulation | ||||
| Area deleted.=Gebiet gelöscht. | ||||
| There was an error during deletion of the area.=Während des Löschens des Gebiets trat ein Fehler auf. | ||||
| Set the current WorldEdit region to <node>=Das aktuelle WorldEdit-Gebiet mit <node> füllen | ||||
| invalid node name: @1=Ungültiger Blockname: @1 | ||||
| @1 nodes set=@1 Blöcke gesetzt | ||||
| Set param2 of all nodes in the current WorldEdit region to <param2>=Den param2 aller Blocke im aktuellen WorldEdit-Gebiet auf <param2> setzen | ||||
| Param2 is out of range (must be between 0 and 255 inclusive!)=Param2 muss zwischen 0 und 255 (inklusiv) sein | ||||
| @1 nodes altered=@1 Blöcke verändert | ||||
| Fill the current WorldEdit region with a random mix of <node1>, ...=Das aktuelle WorldEdit-Gebiet mit einer zufälligen Mischung aus <node1>, … füllen | ||||
| invalid search node name: @1=Ungültiger Blockname für die Suche: @1 | ||||
| invalid replace node name: @1=Ungültiger Blockname für das Ersetzen: @1 | ||||
| Replace all instances of <search node> with <replace node> in the current WorldEdit region=Alle Vorkommen von <search node> im aktuellen WorldEdit-Gebiet mit <replace node> ersetzen | ||||
| @1 nodes replaced=@1 Blöcke ersetzt | ||||
| Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=Alle Blöcke außer <search node> im aktuellen WorldEdit-Gebiet mit <replace node> ersetzen | ||||
| Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Einen hohlen Würfel mit der Unterseite zentriert auf WorldEdit-Position 1 setzen, mit den Abmessungen <width>×<height>×<length>, bestehend aus <node>. | ||||
| Shapes=Formen | ||||
| @1 nodes added=@1 Blöcke hinzugefügt | ||||
| Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Einen Würfel mit der Unterseite zentriert auf WorldEdit-Position 1 setzen, mit den Abmessungen <width>×<height>×<length>, bestehend aus <node>. | ||||
| Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine hohle Kugel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node> | ||||
| Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine Kugel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node> | ||||
| Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine hohle Kuppel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node> | ||||
| Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine Kuppel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node> | ||||
| Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Einen hohlen Zylinder zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <length>, Bodenradius <radius1> (und oberen Radius [radius2]) setzen, bestehend aus <node> | ||||
| Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Einen Zylinder zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <length>, Bodenradius <radius1> (und oberen Radius [radius2]) setzen, bestehend aus <node> | ||||
| Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Eine hohle Pyramide zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <height> setzen, bestehend aus <node> | ||||
| Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Eine Pyramide zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <height> setzen, bestehend aus <node> | ||||
| Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=Eine Spirale zentriert auf WorldEdit-Position 1 mit Seitenlänge <length>, Höhe <height>, Wandabstand <space> setzen, bestehend aus <node> | ||||
| Copy the current WorldEdit region along the given axis by <amount> nodes=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse um <amount> Blöcke kopieren | ||||
| Transformations=Transformationen | ||||
| @1 nodes copied=@1 Blöcke kopiert | ||||
| Move the current WorldEdit region along the given axis by <amount> nodes=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse um <amount> Blöcke verschieben | ||||
| @1 nodes moved=@1 Blöcke verschoben | ||||
| Stack the current WorldEdit region along the given axis <count> times=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse <count> mal stapeln | ||||
| @1 nodes stacked=@1 Blöcke gestapelt | ||||
| Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Das aktuelle WorldEdit-Gebiet <count> mal um <x>, <y>, <z> versetzt stapeln | ||||
| invalid count: @1=Ungültige Anzahl: @1 | ||||
| invalid increments: @1=Ungültige Schrittweite: @1 | ||||
| Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=Die aktuellen WorldEdit-Positionen und -Region um einen Faktor von <stretchx>, <stretchy>, <stretchz> entlang der X-, Y- und Z-Achsen skalieren, mit Position 1 als Ausgangspunkt | ||||
| invalid scaling factors: @1=Ungültige Skalierungsfaktoren: @1 | ||||
| @1 nodes stretched=@1 Blöcke gestreckt | ||||
| Transpose the current WorldEdit region along the given axes=Das aktuelle WorldEdit-Gebiet entlang den gegebenen Achsen transponieren | ||||
| invalid usage: axes must be different=Ungültige Verwendung: Achsen müssen verschieden sein | ||||
| @1 nodes transposed=@1 Blöcke transponiert | ||||
| Flip the current WorldEdit region along the given axis=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse spiegeln | ||||
| @1 nodes flipped=@1 Blöcke gespiegelt | ||||
| Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=Das aktuelle WorldEdit-Gebiet um der gegebenen Achse um den Winkel <angle> rotieren (90-Grad-Schritte) | ||||
| invalid usage: angle must be multiple of 90=Ungültige Verwendung: Winkel muss ein Vielfaches von 90 sein | ||||
| @1 nodes rotated=@1 Blöcke rotiert | ||||
| Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=Ausgerichtete Blöcke im aktuellen WorldEdit-Gebiet um die Y-Achse mit dem Winkel <angle> rotieren (90-Grad-Schritte) | ||||
| @1 nodes oriented=@1 Blöcke ausgerichtet | ||||
| Fix the lighting in the current WorldEdit region=Belichtung im aktuellen WorldEdit-Gebiet korrigieren | ||||
| @1 nodes updated=@1 Blöcke verändert | ||||
| Remove any fluid node within the current WorldEdit region=Alle flüssigen Blöcke im WorldEdit-Gebiet entfernen | ||||
| Remove any plant, tree or foliage-like nodes in the selected region=Alle pflanzen-, baum- oder laubähnliche Blöcke im WorldEdit-Gebiet entfernen | ||||
| @1 nodes removed=@1 Blöcke entfernt | ||||
| Hide all nodes in the current WorldEdit region non-destructively=Alle Blöcke im WorldEdit-Gebiet nicht-destruktiv verstecken | ||||
| @1 nodes hidden=@1 Blöcke versteckt | ||||
| Suppress all <node> in the current WorldEdit region non-destructively=Alle Blöcke vom Typ <node> in der aktuellen WorldEdit-Region nicht-destruktiv verstecken | ||||
| @1 nodes suppressed=@1 Blöcke versteckt | ||||
| Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Alle Blöcke vom Typ <node> im WorldEdit-Gebiet hervorheben, indem alle anderen nicht-destruktiv versteckt werden | ||||
| @1 nodes highlighted=@1 Blöcke hervorgehoben | ||||
| Restores nodes hidden with WorldEdit in the current WorldEdit region=Stellt die von WorldEdit versteckten Blöcke im aktuellen WorldEdit-Gebiet wieder her | ||||
| @1 nodes restored=@1 Blöcke wiederhergestellt | ||||
| Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=Warnung: Das Schematic hat überschüssigen freien Platz und WIRD nach dem Laden falsch ausgerichtet sein. Um dies zu vermeiden, verkleinern Sie das Gebiet, sodass es die zu speichernden Blöcke exakt umfasst. | ||||
| Save the current WorldEdit region to "(world folder)/schems/<file>.we"=Das aktuelle WorldEdit-Gebiet in „(Weltordner)/schems/<file>.we“ speichern | ||||
| Schematics=Schematic | ||||
| Disallowed file name: @1=Nicht erlaubter Dateiname: @1 | ||||
| Could not save file to "@1"=Konnte die Datei nicht unter „@1“ speichern | ||||
| @1 nodes saved=@1 Blöcke gespeichert | ||||
| Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=Das aktuelle WorldEdit-Gebiet anhand der Blöcke in „(Weltordner)/schems/<file>.we“ setzen | ||||
| Schematic empty, nothing allocated=Schematic leer, nichts reserviert | ||||
| @1 nodes allocated=@1 Blöcke reserviert | ||||
| Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=Blöcke aus „(Weltordner)/schems/<file>[.we[m]]“ mit Position 1 als Ausgangspunkt laden | ||||
| Loading failed!=Laden fehlgeschlagen! | ||||
| @1 nodes loaded=@1 Blöcke geladen | ||||
| Executes <code> as a Lua chunk in the global namespace=<code> als einen Lua-Chunk im globalen Namensraum ausführen | ||||
| Code=Code | ||||
| Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=Führt <code> als einen Lua-Chunk im globalen Namensraum aus, wobei die Variable pos verfügbar ist, für jeden Block im aktuellen WorldEdit-Gebiet | ||||
| Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=Das aktuelle WorldEdit-Gebiet als Minetest-Schematic in „(Weltordner)/schems/<filename>.mts“ speichern | ||||
| Failed to create Minetest schematic=Konnte Minetest-Schematic nicht erstellen | ||||
| Saved Minetest schematic to @1=Minetest-Schematic in @1 gespeichert | ||||
| Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=Blöcke aus „(Weltordner)/schems/<file>.mts“ mit Position 1 des aktuellen WorldEdit-Gebiets als Ausgangspunkt laden | ||||
| failed to place Minetest schematic=Konnte Minetest-Schematic nicht platzieren | ||||
| placed Minetest schematic @1 at @2=Minetest-Schematic @1 bei @2 platziert | ||||
| Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=Startet die Eingabe von Block-Wahrscheinlichkeiten für Minetest-Schematics, holt die Blöcke, welche Wahrscheinlichkeiten gesetzt haben, oder beendet die Eingabe von Block-Wahrscheinlichkeiten | ||||
| select Minetest schematic probability values by punching nodes=Minetest-Schematic-Wahrscheinlichkeitswerte mit Hauen eingeben | ||||
| finished Minetest schematic probability selection=Minetest-Schematic-Wahrscheinlichkeitsauswahl abgeschlossen | ||||
| currently set node probabilities:=Momentan gesetzte Block-Wahrscheinlichkeiten: | ||||
| invalid node probability given, not saved=Ungültige Block-Wahrscheinlichkeit übergeben, nicht gespeichert | ||||
| Clears all objects within the WorldEdit region=Löscht alle Objekte innerhalb des WorldEdit-Gebiets | ||||
| @1 objects cleared=@1 Objekte gelöscht | ||||
| WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=WARNUNG: Dieser Vorgang könnte bis zu @1 Blöcke betreffen; @2 zum Fortfahren oder @3 zum Abbrechen | ||||
| Confirm a pending operation=Einen ausstehenden Vorgang bestätigen | ||||
| no operation pending=Kein Vorgang ausstehend | ||||
| Abort a pending operation=Einen ausstehenden Vorgang abbrechen | ||||
| WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=WorldEdit-Zauberstab@nSetzen der 1. Position mit Linksklick, der 2. mit Rechtsklick | ||||
							
								
								
									
										155
									
								
								worldedit_commands/locale/worldedit_commands.ru.tr
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,155 @@ | ||||
| # textdomain: worldedit_commands | ||||
|  | ||||
| ### init.lua ### | ||||
| Can use WorldEdit commands=Возможность редактировать мир с помощью команд WorldEdit | ||||
|  | ||||
| no region selected=не выделен регион | ||||
| no position 1 selected=не установлена позиция региона 1 | ||||
| invalid usage=не верное использование команды | ||||
| Could not open file "@1"=Не удаётся открыть файл "@1" | ||||
| Invalid file format!=Не верный формат файла! | ||||
| Schematic was created with a newer version of WorldEdit.=Схема была создана с использованием более новой версии WorldEdit. | ||||
|  | ||||
| Get information about the WorldEdit mod=Вывести информацию о WorldEdit | ||||
| WorldEdit @1 is available on this server. Type @2 to get a list of commands, or get more information at @3=WorldEdit @1 доступен на этом сервере. Наберите команду @2 чтобы увидеть список команд, больше информации по ссылке @3 | ||||
| Get help for WorldEdit commands=Вывести информацию об использовании команд WorldEdit | ||||
| You are not allowed to use any WorldEdit commands.=У вас нет привилегий, чтобы использовать команды WorldEdit. | ||||
| Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=Доступные команды: @1@nИспользуйте '@2' для получения информации по команде или '@3' для вывода подсказок по всем командам. | ||||
| Available commands:@n=Доступные команды:@n | ||||
| Command not available: =Команда не найдена: | ||||
| Enable or disable node inspection=Включить/отключить инспекцию блоков | ||||
| inspector: inspection enabled for @1, currently facing the @2 axis=inspector: инспекция включена для @1, текущий взор в направлении оси @2 | ||||
| inspector: inspection disabled=inspector: инспекция отключена | ||||
| inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=inspector: @1 в @2 (param1@=@3, param2@=@4, received light@=@5) ударен по поверхности @6 | ||||
| Reset the region so that it is empty=Сбросить выделение области | ||||
| region reset=регион сброшен | ||||
| Show markers at the region positions=Отобразить маркеры выделенной области | ||||
| region marked=маркеры отображены | ||||
| Hide markers if currently shown=Скрыть маркеры выделенной области | ||||
| region unmarked=маркеры скрыты | ||||
| Set WorldEdit region position @1 to the player's location=Установить маркер @1 для WorldEdit-региона в месте нахождения игрока | ||||
| Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region=Выделить WorldEdit-регион или установить маркеры для WorldEdit-региона, либо отобразить уже выбранную область | ||||
| unknown subcommand: @1=неизвестная подкоманда: @1 | ||||
| select positions by punching two nodes=выберите позиции, ударив по блокам | ||||
| select position @1 by punching a node=выберите позицию @1, ударив по блоку | ||||
| position @1: @2=позиция @1: @2 | ||||
| position @1 not set=позиция @1 не установлена | ||||
| Set a WorldEdit region position to the position at (<x>, <y>, <z>)=Установить маркер для WorldEdit в позиции (<x>, <y>, <z>) | ||||
| position @1 set to @2=позиция @1 установлена в @2 | ||||
| Display the volume of the current WorldEdit region=Вывести информацию об объёме текущей выделенной области WorldEdit (кол-во нод, размеры) | ||||
| current region has a volume of @1 nodes (@2*@3*@4)=текущий регион имеет объем @1 нод (@2*@3*@4) | ||||
| Remove all MapBlocks (16x16x16) containing the selected area from the map=Удалить MapBlocks (16x16x16), содержащие выбранную область | ||||
| Area deleted.=Область удалена. | ||||
| There was an error during deletion of the area.=Что-то пошло не так при удалении области. | ||||
| Set the current WorldEdit region to <node>=Заполнить выбранный WorldEdit-регион указанным типом блоков | ||||
| invalid node name: @1=неверное название блока: @1 | ||||
| @1 nodes set=@1 блок(а/ов) установленно | ||||
| Set param2 of all nodes in the current WorldEdit region to <param2>=Проставить param2 для всех блоков в текущем WorldEdit-регионе | ||||
| Param2 is out of range (must be between 0 and 255 inclusive!)=Значение param2 должно быть от 0 до 255 (включительно!) | ||||
| @1 nodes altered=изменено @1 блок(а/ов) | ||||
| Fill the current WorldEdit region with a random mix of <node1>, ...=Заполнить выбранный WorldEdit-регион смесью указанных типов блоков | ||||
| invalid search node name: @1=неверное название блока-поиска: @1 | ||||
| invalid replace node name: @1=неверное название блока-замены: @1 | ||||
| Replace all instances of <search node> with <replace node> in the current WorldEdit region=Заменить все блоки <search node> на <replace node> в выбранной WorldEdit-области | ||||
| @1 nodes replaced=заменено @1 нод(а/ы) | ||||
| Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=Заменить все блоки, кроме <search node>, на <replace node> в выбранной WorldEdit-области | ||||
| Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Установить полый куб с центром нижней грани в позиции 1 и указанными размерами, состоящий из блоков <node>. | ||||
| @1 nodes added=добавлен(о) @1 блок(а/ов) | ||||
| Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Установить куб с центром нижней грани в позиции 1 и указанными размерами, состоящий из блоков <node>. | ||||
| Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить полую сферу с центром в WorldEdit-позиции 1 радиусом <radius>, состоящую из блоков <node> | ||||
| Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить сферу с центром в WorldEdit-позиции 1 радиусом <radius>, состоящую из блоков <node> | ||||
| Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить полый купол с центром в WorldEdit-позиции 1 радиусом <radius>, состоящий из блоков <node> | ||||
| Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить купол с центром в WorldEdit-позиции 1 радиусом <radius>, состоящий из блоков <node> | ||||
| Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Установить полый цилиндр вдоль указанной оси, с центром в позиции 1, высотой/длинной <length>, с радиусом основания <radius> (и радиусом вершины [radius 2]), состоящий из блоков <node> | ||||
| Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Установить цилиндр вдоль указанной оси, с центром в позиции 1, высотой/длинной <length>, с радиусом основания <radius> (и радиусом вершины [radius 2]), состоящий из блоков <node> | ||||
| Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Установить полую пирамиду вдоль указанной оси, с центром в WorldEdit-позиции 1 высотой <height>, состоящую из блоков <node> | ||||
| Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Установить пирамиду вдоль указанной оси, с центром в WorldEdit-позиции 1 высотой <height>, состоящую из блоков <node> | ||||
| Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=Установить спираль с центром в WorldEdit-позиции 1 шириной <length>, высотой <height> и с расстоянием между витками <space>, состоящую из блоков <node> | ||||
| Copy the current WorldEdit region along the given axis by <amount> nodes=Копировать текущий WorldEdit-регион со смещением вдоль указанной оси (x/y/z) на <amount> блоков | ||||
| @1 nodes copied=скопировано @1 нод(а/ы) | ||||
| Move the current WorldEdit region along the given axis by <amount> nodes=Переместить текущий WorldEdit-регион вдоль указанной оси (x/y/z) на <amount> блоков | ||||
| @1 nodes moved=перемещено @1 нод(а/ы) | ||||
| Stack the current WorldEdit region along the given axis <count> times=Размножить текущий WorldEdit-регион вдоль указанной оси <count> раз | ||||
| @1 nodes stacked=размножено @1 нод(а/ы) | ||||
| Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Размножить текущий WorldEdit-регион <count> раз с шагом <x>, <y>, <z> по соответствующим осям | ||||
| invalid count: @1=неверное количество: @1 | ||||
| invalid increments: @1=неверные приращения(шаг) | ||||
| Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=Масштабировать текущий WorldEdit-регион с коэффициентами <stretchx>, <stretchy>, <stretchz> вдоль осей X, Y и Z, используя WorldEdit-позицию 1 в качестве точки отсчёта | ||||
| invalid scaling factors: @1=неверные коэффициенты масштабирования: @1 | ||||
| @1 nodes stretched=масштабировано @1 нод(а/ы) | ||||
| Transpose the current WorldEdit region along the given axes=Транспонировать текущий WorldEdit-регион по заданным осям. | ||||
| invalid usage: axes must be different=недопустимое использование: оси должны быть разными | ||||
| @1 nodes transposed=транспонировано @1 нод(а/ы) | ||||
| Flip the current WorldEdit region along the given axis=Перевернуть/Отразить текущий WorldEdit-регион вдоль указанной оси | ||||
| @1 nodes flipped=отражено @1 нод(а/ы) | ||||
| Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=Повернуть текущий WorldEdit-регион вокруг оси <axis> на угол <angle> (шаг - 90 градусов) | ||||
| invalid usage: angle must be multiple of 90=недопустимое использование: угол должен быть кратен 90 | ||||
| @1 nodes rotated=повёрнуто @1 нод(а/ы) | ||||
| Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=Повернуть блоки в текущем WorldEdit-регионе вокруг оси Y на угол <angle> (шаг - 90 градусов) | ||||
| @1 nodes oriented=повёрнуто @1 нод(а/ы) | ||||
| Fix the lighting in the current WorldEdit region=Исправить освещение в текущем WorldEdit-регионе | ||||
| @1 nodes updated=обновлено @1 нод(а/ы) | ||||
| Remove any fluid node within the current WorldEdit region=Удалить любые жидкости в текущем WorldEdit-регионе | ||||
| Remove any plant, tree or foliage-like nodes in the selected region=Удалить любые растения, деревья или листье-подобные ноды в текущем WorldEdit-регионе | ||||
| @1 nodes removed=удалено @1 нод(а/ы) | ||||
| Hide all nodes in the current WorldEdit region non-destructively=Скрыть узлы текущего WorldEdit-региона, не удаляя их | ||||
| @1 nodes hidden=скрыто @1 нод(а/ы) | ||||
| Suppress all <node> in the current WorldEdit region non-destructively=Скрыть все блоки <node> в текущем WorldEdit-регионе, не удаляя их | ||||
| @1 nodes suppressed=скрыто @1 нод(а/ы) | ||||
| Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Скрыть все блоки, кроме <node>, в текущем WorldEdit-регионе, не удаляя их | ||||
| @1 nodes highlighted="подсвечено" @1 нод(а/ы) | ||||
| Restores nodes hidden with WorldEdit in the current WorldEdit region=Восстановить скрытые WorldEdit'ом узлы в текущем WorldEdit-регионе | ||||
| @1 nodes restored=восстановлено @1 нод(а/ы) | ||||
| Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=Предупреждение: Схема содержит слишком много свободного места и будет смещена при размещении или загрузке. Чтобы избежать этого, уменьшите область так, чтобы она охватывала именно те узлы, которые необходимо сохранить. | ||||
| Save the current WorldEdit region to "(world folder)/schems/<file>.we"=Сохранить текущий WorldEdit-регион в файл "(world folder)/schems/<file>.we" | ||||
| Disallowed file name: @1=Недопустимое имя файла: @1 | ||||
| Could not save file to "@1"=Не удалось сохранить файл в "@1" | ||||
| @1 nodes saved=сохранено @1 нод(а/ы) | ||||
| Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=Выделить область, определённую узлами из "(world folder)/schems/<file>.we", как текущий WorldEdit-регион | ||||
| Schematic empty, nothing allocated=Схема пуста, ничего не выделено | ||||
| @1 nodes allocated=выделено @1 нод(а/ы) | ||||
| Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=Загрузить регион из "(world folder)/schems/<file>[.we[m]]" с WorldEdit-позицией 1 в качестве точки отсчёта | ||||
| Loading failed!=Не удалось загрузить! | ||||
| @1 nodes loaded=загружено @1 нод(а/ы) | ||||
| Executes <code> as a Lua chunk in the global namespace=Выполнить <code> как Lua-код в глобальном пространстве имён | ||||
| Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=Выполнить <code> как Lua-код в глобальном пространстве имён, с доступом к переменным позиций для каждого блока в текущем WordEdit-регионе | ||||
| Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=Сохранить текущий WorldEdit-регион с использованием сжатия в формат Minetest Schematic в файл "(world folder)/schems/<filename>.mts" | ||||
| Failed to create Minetest schematic=Не удалось создать Minetest-схему | ||||
| Saved Minetest schematic to @1=Minetest-схема сохранена в @1 | ||||
| Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=Загрузить блоки из "(world folder)/schems/<file>.mts" с WorldEdit-позицией 1 в качестве точки отсчёта | ||||
| failed to place Minetest schematic=не удалось загрузить Minetest-схему | ||||
| placed Minetest schematic @1 at @2=Minetest-схема @1 загружена в @2 | ||||
| Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=Начать запись вероятностей для Minetest Schematic, ударяя по блокам, закончить запись вероятностей или вывести уже записанные вероятности | ||||
| select Minetest schematic probability values by punching nodes=выберите значения вероятностей для Minetest-схемы, ударя по нодам | ||||
| finished Minetest schematic probability selection=выбор вероятностей для Minetest-схемы завершен | ||||
| currently set node probabilities:=заданные вероятности нод на данный момент: | ||||
| invalid node probability given, not saved=недопустимая вероятность ноды, не сохранена | ||||
| Clears all objects within the WorldEdit region=Очистить все объекты в текущем WorldEdit-регионе | ||||
| @1 objects cleared=очищено @1 объектов | ||||
|  | ||||
| ### safe.lua ### | ||||
| WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=ПРЕДУПРЕЖДЕНИЕ: эта операция может затронуть до @1 нод; введите @2 для продолжения или @3 для отмены | ||||
| Confirm a pending operation=Подтвердить отложенную операцию | ||||
| no operation pending=нет ожидающей операции | ||||
| Abort a pending operation=Отклонить отложенную операцию | ||||
|  | ||||
| ### cuboid.lua ### | ||||
| Outset the selected region.=Расширить выделение региона. | ||||
| Invalid direction: @1=Недопустимое направление: @1 | ||||
| Invalid number of arguments=Недопустимое количество аргументов | ||||
| Region outset by @1 nodes=Регион расширен на @1 нод(у/ы) | ||||
| Inset the selected region.=Сузить выделение региона. | ||||
| Region inset by @1 nodes=Регион сужен на @1 нод(у/ы) | ||||
| Shifts the selection area without moving its contents=Сдвинуть выделение региона без перемещения его содержимого | ||||
| Invalid if looking straight up or down=Недопустимо, если смотреть прямо вверх или вниз | ||||
| Region shifted by @1 nodes=Регион сдвинут на @1 нод(у/ы) | ||||
| Expands the selection in the selected absolute or relative axis=Увеличить выделение региона по выбранной абсолютной или относительной оси | ||||
| Region expanded by @1 nodes=Регион увеличен на @1 нод(у/ы) | ||||
| Contracts the selection in the selected absolute or relative axis=Уменьшить выделение региона по выбранной абсолютной или относительной оси | ||||
| Region contracted by @1 nodes=Регион уменьшен на @1 нод(у/ы) | ||||
| Select a cube with side length <size> around position 1 and run <command> on region=Выделить куб с длиной стороны <size> вокруг позиции 1 и запустите <команду> в области | ||||
| invalid usage: @1 cannot be used with cubeapply=недопустимое использование: @1 не может быть применено в cubeapply | ||||
| Missing privileges: @1=Отсутствуют привилегии: @1 | ||||
|  | ||||
| ### wand.lua ### | ||||
| WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=Инструмент WorldEdit Wand@nЛевая кнопка мыши, чтобы установить 1-ю позицию, правая кнопка мыши, чтобы установить 2-ю | ||||
							
								
								
									
										366
									
								
								worldedit_commands/manipulations.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,366 @@ | ||||
| local S = minetest.get_translator("worldedit_commands") | ||||
|  | ||||
| local function check_region(name) | ||||
| 	return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| end | ||||
|  | ||||
|  | ||||
| worldedit.register_command("deleteblocks", { | ||||
| 	params = "", | ||||
| 	description = S("Remove all MapBlocks (16x16x16) containing the selected area from the map"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name) | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 		local success = minetest.delete_area(pos1, pos2) | ||||
| 		if success then | ||||
| 			return true, S("Area deleted.") | ||||
| 		else | ||||
| 			return false, S("There was an error during deletion of the area.") | ||||
| 		end | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("clearobjects", { | ||||
| 	params = "", | ||||
| 	description = S("Clears all objects within the WorldEdit region"), | ||||
| 	category = S("Node manipulation"), -- not really, but it doesn't fit anywhere else | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name) | ||||
| 		local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| 		return true, S("@1 objects cleared", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("set", { | ||||
| 	params = "<node>", | ||||
| 	description = S("Set the current WorldEdit region to <node>"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local node = worldedit.normalize_nodename(param) | ||||
| 		if not node then | ||||
| 			return false, S("invalid node name: @1", param) | ||||
| 		end | ||||
| 		return true, node | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, node) | ||||
| 		local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node) | ||||
| 		return true, S("@1 nodes set", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("param2", { | ||||
| 	params = "<param2>", | ||||
| 	description = S("Set param2 of all nodes in the current WorldEdit region to <param2>"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local param2 = tonumber(param) | ||||
| 		if not param2 then | ||||
| 			return false | ||||
| 		elseif param2 < 0 or param2 > 255 then | ||||
| 			return false, S("Param2 is out of range (must be between 0 and 255 inclusive!)") | ||||
| 		end | ||||
| 		return true, param2 | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, param2) | ||||
| 		local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2) | ||||
| 		return true, S("@1 nodes altered", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("mix", { | ||||
| 	params = "<node1> [count1] <node2> [count2] ...", | ||||
| 	description = S("Fill the current WorldEdit region with a random mix of <node1>, ..."), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local nodes = {} | ||||
| 		for nodename in param:gmatch("[^%s]+") do | ||||
| 			if tonumber(nodename) ~= nil and #nodes > 0 then | ||||
| 				local last_node = nodes[#nodes] | ||||
| 				for i = 1, tonumber(nodename) do | ||||
| 					nodes[#nodes + 1] = last_node | ||||
| 				end | ||||
| 			else | ||||
| 				local node = worldedit.normalize_nodename(nodename) | ||||
| 				if not node then | ||||
| 					return false, S("invalid node name: @1", nodename) | ||||
| 				end | ||||
| 				nodes[#nodes + 1] = node | ||||
| 			end | ||||
| 		end | ||||
| 		if #nodes == 0 then | ||||
| 			return false | ||||
| 		end | ||||
| 		return true, nodes | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, nodes) | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 		local count = worldedit.set(pos1, pos2, nodes) | ||||
| 		return true, S("@1 nodes set", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| local check_replace = function(param) | ||||
| 	local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$") | ||||
| 	if found == nil then | ||||
| 		return false | ||||
| 	end | ||||
| 	local newsearchnode = worldedit.normalize_nodename(searchnode) | ||||
| 	if not newsearchnode then | ||||
| 		return false, S("invalid search node name: @1", searchnode) | ||||
| 	end | ||||
| 	local newreplacenode = worldedit.normalize_nodename(replacenode) | ||||
| 	if not newreplacenode then | ||||
| 		return false, S("invalid replace node name: @1", replacenode) | ||||
| 	end | ||||
| 	return true, newsearchnode, newreplacenode | ||||
| end | ||||
|  | ||||
| worldedit.register_command("replace", { | ||||
| 	params = "<search node> <replace node>", | ||||
| 	description = S("Replace all instances of <search node> with <replace node> in the current WorldEdit region"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = check_replace, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, search_node, replace_node) | ||||
| 		local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name], | ||||
| 				search_node, replace_node) | ||||
| 		return true, S("@1 nodes replaced", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("replaceinverse", { | ||||
| 	params = "<search node> <replace node>", | ||||
| 	description = S("Replace all nodes other than <search node> with <replace node> in the current WorldEdit region"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = check_replace, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, search_node, replace_node) | ||||
| 		local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name], | ||||
| 				search_node, replace_node, true) | ||||
| 		return true, S("@1 nodes replaced", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("fixlight", { | ||||
| 	params = "", | ||||
| 	description = S("Fix the lighting in the current WorldEdit region"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name) | ||||
| 		local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| 		return true, S("@1 nodes updated", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| local drain_cache | ||||
|  | ||||
| local function drain(pos1, pos2) | ||||
| 	if drain_cache == nil then | ||||
| 		drain_cache = {} | ||||
| 		for name, d in pairs(minetest.registered_nodes) do | ||||
| 			if d.drawtype == "liquid" or d.drawtype == "flowingliquid" then | ||||
| 				drain_cache[name] = true | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	pos1, pos2 = worldedit.sort_pos(pos1, pos2) | ||||
| 	local count = 0 | ||||
|  | ||||
| 	local get_node, remove_node = minetest.get_node, minetest.remove_node | ||||
| 	for x = pos1.x, pos2.x do | ||||
| 	for y = pos1.y, pos2.y do | ||||
| 	for z = pos1.z, pos2.z do | ||||
| 		local p = vector.new(x, y, z) | ||||
| 		local n = get_node(p).name | ||||
| 		if drain_cache[n] then | ||||
| 			remove_node(p) | ||||
| 			count = count + 1 | ||||
| 		end | ||||
| 	end | ||||
| 	end | ||||
| 	end | ||||
| 	return count | ||||
| end | ||||
|  | ||||
| worldedit.register_command("drain", { | ||||
| 	params = "", | ||||
| 	description = S("Remove any fluid node within the current WorldEdit region"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name) | ||||
| 		local count = drain(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| 		return true, S("@1 nodes updated", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| local clearcut_cache | ||||
|  | ||||
| local function clearcut(pos1, pos2) | ||||
| 	-- decide which nodes we consider plants | ||||
| 	if clearcut_cache == nil then | ||||
| 		clearcut_cache = {} | ||||
| 		for name, def in pairs(minetest.registered_nodes) do | ||||
| 			local groups = def.groups or {} | ||||
| 			if ( | ||||
| 				-- the groups say so | ||||
| 				groups.flower or groups.grass or groups.flora or groups.plant or | ||||
| 				groups.leaves or groups.tree or groups.leafdecay or groups.sapling or | ||||
| 				-- drawtype heuristic | ||||
| 				(def.is_ground_content and def.buildable_to and | ||||
| 					(def.sunlight_propagates or not def.walkable) | ||||
| 					and def.drawtype == "plantlike") or | ||||
| 				-- if it's flammable, it probably needs to go too | ||||
| 				(def.is_ground_content and not def.walkable and groups.flammable) | ||||
| 			) then | ||||
| 				clearcut_cache[name] = true | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	local plants = clearcut_cache | ||||
|  | ||||
| 	local count = 0 | ||||
| 	local prev, any | ||||
|  | ||||
| 	local get_node, remove_node = minetest.get_node, minetest.remove_node | ||||
| 	for x = pos1.x, pos2.x do | ||||
| 	for z = pos1.z, pos2.z do | ||||
| 		prev = false | ||||
| 		any = false | ||||
| 		-- first pass: remove floating nodes that would be left over | ||||
| 		for y = pos1.y, pos2.y do | ||||
| 			local pos = vector.new(x, y, z) | ||||
| 			local n = get_node(pos).name | ||||
| 			if plants[n] then | ||||
| 				prev = true | ||||
| 				any = true | ||||
| 			elseif prev then | ||||
| 				local def = minetest.registered_nodes[n] or {} | ||||
| 				local groups = def.groups or {} | ||||
| 				if groups.attached_node or (def.buildable_to and groups.falling_node) then | ||||
| 					remove_node(pos) | ||||
| 					count = count + 1 | ||||
| 				else | ||||
| 					prev = false | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
|  | ||||
| 		-- second pass: remove plants, top-to-bottom to avoid item drops | ||||
| 		if any then | ||||
| 			for y = pos2.y, pos1.y, -1 do | ||||
| 				local pos = vector.new(x, y, z) | ||||
| 				local n = get_node(pos).name | ||||
| 				if plants[n] then | ||||
| 					remove_node(pos) | ||||
| 					count = count + 1 | ||||
| 				end | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	end | ||||
|  | ||||
| 	return count | ||||
| end | ||||
|  | ||||
| worldedit.register_command("clearcut", { | ||||
| 	params = "", | ||||
| 	description = S("Remove any plant, tree or foliage-like nodes in the selected region"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name) | ||||
| 		local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| 		local count = clearcut(pos1, pos2) | ||||
| 		return true, S("@1 nodes removed", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("hide", { | ||||
| 	params = "", | ||||
| 	description = S("Hide all nodes in the current WorldEdit region non-destructively"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name) | ||||
| 		local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| 		return true, S("@1 nodes hidden", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("suppress", { | ||||
| 	params = "<node>", | ||||
| 	description = S("Suppress all <node> in the current WorldEdit region non-destructively"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local node = worldedit.normalize_nodename(param) | ||||
| 		if not node then | ||||
| 			return false, S("invalid node name: @1", param) | ||||
| 		end | ||||
| 		return true, node | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, node) | ||||
| 		local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node) | ||||
| 		return true, S("@1 nodes suppressed", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("highlight", { | ||||
| 	params = "<node>", | ||||
| 	description = S("Highlight <node> in the current WorldEdit region by hiding everything else non-destructively"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local node = worldedit.normalize_nodename(param) | ||||
| 		if not node then | ||||
| 			return false, S("invalid node name: @1", param) | ||||
| 		end | ||||
| 		return true, node | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, node) | ||||
| 		local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node) | ||||
| 		return true, S("@1 nodes highlighted", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("restore", { | ||||
| 	params = "", | ||||
| 	description = S("Restores nodes hidden with WorldEdit in the current WorldEdit region"), | ||||
| 	category = S("Node manipulation"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name) | ||||
| 		local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| 		return true, S("@1 nodes restored", count) | ||||
| 	end, | ||||
| }) | ||||
| @@ -6,16 +6,14 @@ local init_sentinel = "new" .. tostring(math.random(99999)) | ||||
| 
 | ||||
| --marks worldedit region position 1 | ||||
| worldedit.mark_pos1 = function(name, region_too) | ||||
| 	local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 	local pos1 = worldedit.pos1[name] | ||||
| 
 | ||||
| 	if worldedit.marker1[name] ~= nil then --marker already exists | ||||
| 		worldedit.marker1[name]:remove() --remove marker | ||||
| 		worldedit.marker1[name] = nil | ||||
| 	end | ||||
| 	if pos1 ~= nil then | ||||
| 		--make area stay loaded | ||||
| 		local manip = minetest.get_voxel_manip() | ||||
| 		manip:read_from_map(pos1, pos1) | ||||
| 		worldedit.keep_loaded(pos1, pos1) | ||||
| 
 | ||||
| 		--add marker | ||||
| 		worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel) | ||||
| @@ -30,16 +28,14 @@ end | ||||
| 
 | ||||
| --marks worldedit region position 2 | ||||
| worldedit.mark_pos2 = function(name, region_too) | ||||
| 	local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 	local pos2 = worldedit.pos2[name] | ||||
| 
 | ||||
| 	if worldedit.marker2[name] ~= nil then --marker already exists | ||||
| 		worldedit.marker2[name]:remove() --remove marker | ||||
| 		worldedit.marker2[name] = nil | ||||
| 	end | ||||
| 	if pos2 ~= nil then | ||||
| 		--make area stay loaded | ||||
| 		local manip = minetest.get_voxel_manip() | ||||
| 		manip:read_from_map(pos2, pos2) | ||||
| 		worldedit.keep_loaded(pos2, pos2) | ||||
| 
 | ||||
| 		--add marker | ||||
| 		worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel) | ||||
| @@ -77,15 +73,14 @@ worldedit.mark_region = function(name) | ||||
| 		local thickness = 0.2 | ||||
| 		local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2 | ||||
| 
 | ||||
| 		--make area stay loaded | ||||
| 		local manip = minetest.get_voxel_manip() | ||||
| 		manip:read_from_map(pos1, pos2) | ||||
| 		-- TODO maybe we could skip this actually? | ||||
| 		worldedit.keep_loaded(pos1, pos2) | ||||
| 
 | ||||
| 		local markers = {} | ||||
| 
 | ||||
| 		--XY plane markers | ||||
| 		for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do | ||||
| 			local entpos = {x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z} | ||||
| 			local entpos = vector.new(pos1.x + sizex - 0.5, pos1.y + sizey - 0.5, z) | ||||
| 			local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel) | ||||
| 			if marker ~= nil then | ||||
| 				marker:set_properties({ | ||||
| @@ -99,7 +94,7 @@ worldedit.mark_region = function(name) | ||||
| 
 | ||||
| 		--YZ plane markers | ||||
| 		for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do | ||||
| 			local entpos = {x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5} | ||||
| 			local entpos = vector.new(x, pos1.y + sizey - 0.5, pos1.z + sizez - 0.5) | ||||
| 			local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel) | ||||
| 			if marker ~= nil then | ||||
| 				marker:set_properties({ | ||||
							
								
								
									
										85
									
								
								worldedit_commands/nodename.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,85 @@ | ||||
| -- Strips any kind of escape codes (translation, colors) from a string | ||||
| -- https://github.com/minetest/minetest/blob/53dd7819277c53954d1298dfffa5287c306db8d0/src/util/string.cpp#L777 | ||||
| local function strip_escapes(input) | ||||
| 	local s = function(idx) return input:sub(idx, idx) end | ||||
| 	local out = "" | ||||
| 	local i = 1 | ||||
| 	while i <= #input do | ||||
| 		if s(i) == "\027" then -- escape sequence | ||||
| 			i = i + 1 | ||||
| 			if s(i) == "(" then -- enclosed | ||||
| 				i = i + 1 | ||||
| 				while i <= #input and s(i) ~= ")" do | ||||
| 					if s(i) == "\\" then | ||||
| 						i = i + 2 | ||||
| 					else | ||||
| 						i = i + 1 | ||||
| 					end | ||||
| 				end | ||||
| 			end | ||||
| 		else | ||||
| 			out = out .. s(i) | ||||
| 		end | ||||
| 		i = i + 1 | ||||
| 	end | ||||
| 	--print(("%q -> %q"):format(input, out)) | ||||
| 	return out | ||||
| end | ||||
|  | ||||
| local function string_endswith(full, part) | ||||
| 	return full:find(part, 1, true) == #full - #part + 1 | ||||
| end | ||||
|  | ||||
| local description_cache = nil | ||||
|  | ||||
| -- normalizes node "description" `nodename`, returning a string (or nil) | ||||
| worldedit.normalize_nodename = function(nodename) | ||||
| 	nodename = nodename:gsub("^%s*(.-)%s*$", "%1") -- strip spaces | ||||
| 	if nodename == "" then return nil end | ||||
|  | ||||
| 	local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases | ||||
| 	if minetest.registered_nodes[fullname] or fullname == "air" then -- full name | ||||
| 		return fullname | ||||
| 	end | ||||
| 	nodename = nodename:lower() | ||||
|  | ||||
| 	for key, _ in pairs(minetest.registered_nodes) do | ||||
| 		if string_endswith(key:lower(), ":" .. nodename) then -- matches name (w/o mod part) | ||||
| 			return key | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	if description_cache == nil then | ||||
| 		-- cache stripped descriptions | ||||
| 		description_cache = {} | ||||
| 		for key, value in pairs(minetest.registered_nodes) do | ||||
| 			local desc = strip_escapes(value.description):gsub("\n.*", "", 1):lower() | ||||
| 			if desc ~= "" then | ||||
| 				description_cache[key] = desc | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	for key, desc in pairs(description_cache) do | ||||
| 		if desc == nodename then -- matches description | ||||
| 			return key | ||||
| 		end | ||||
| 	end | ||||
| 	for key, desc in pairs(description_cache) do | ||||
| 		if desc == nodename .. " block" then | ||||
| 			-- fuzzy description match (e.g. "Steel" == "Steel Block") | ||||
| 			return key | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
| 	local match = nil | ||||
| 	for key, value in pairs(description_cache) do | ||||
| 		if value:find(nodename, 1, true) ~= nil then | ||||
| 			if match ~= nil then | ||||
| 				return nil | ||||
| 			end | ||||
| 			match = key -- substring description match (only if no ambiguities) | ||||
| 		end | ||||
| 	end | ||||
| 	return match | ||||
| end | ||||
							
								
								
									
										276
									
								
								worldedit_commands/primitives.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,276 @@ | ||||
| local S = minetest.get_translator("worldedit_commands") | ||||
|  | ||||
|  | ||||
| local check_cube = function(param) | ||||
| 	local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$") | ||||
| 	if found == nil then | ||||
| 		return false | ||||
| 	end | ||||
| 	local node = worldedit.normalize_nodename(nodename) | ||||
| 	if not node then | ||||
| 		return false, S("invalid node name: @1", nodename) | ||||
| 	end | ||||
| 	return true, tonumber(w), tonumber(h), tonumber(l), node | ||||
| end | ||||
|  | ||||
| worldedit.register_command("hollowcube", { | ||||
| 	params = "<width> <height> <length> <node>", | ||||
| 	description = S("Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>."), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_cube, | ||||
| 	nodes_needed = function(name, w, h, l, node) | ||||
| 		return w * h * l | ||||
| 	end, | ||||
| 	func = function(name, w, h, l, node) | ||||
| 		local count = worldedit.cube(worldedit.pos1[name], w, h, l, node, true) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("cube", { | ||||
| 	params = "<width> <height> <length> <node>", | ||||
| 	description = S("Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>."), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_cube, | ||||
| 	nodes_needed = function(name, w, h, l, node) | ||||
| 		return w * h * l | ||||
| 	end, | ||||
| 	func = function(name, w, h, l, node) | ||||
| 		local count = worldedit.cube(worldedit.pos1[name], w, h, l, node) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| local check_sphere = function(param) | ||||
| 	local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") | ||||
| 	if found == nil then | ||||
| 		return false | ||||
| 	end | ||||
| 	local node = worldedit.normalize_nodename(nodename) | ||||
| 	if not node then | ||||
| 		return false, S("invalid node name: @1", nodename) | ||||
| 	end | ||||
| 	return true, tonumber(radius), node | ||||
| end | ||||
|  | ||||
| worldedit.register_command("hollowsphere", { | ||||
| 	params = "<radius> <node>", | ||||
| 	description = S("Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>"), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_sphere, | ||||
| 	nodes_needed = function(name, radius, node) | ||||
| 		return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere | ||||
| 	end, | ||||
| 	func = function(name, radius, node) | ||||
| 		local count = worldedit.sphere(worldedit.pos1[name], radius, node, true) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("sphere", { | ||||
| 	params = "<radius> <node>", | ||||
| 	description = S("Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>"), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_sphere, | ||||
| 	nodes_needed = function(name, radius, node) | ||||
| 		return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere | ||||
| 	end, | ||||
| 	func = function(name, radius, node) | ||||
| 		local count = worldedit.sphere(worldedit.pos1[name], radius, node) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| local check_dome = function(param) | ||||
| 	local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") | ||||
| 	if found == nil then | ||||
| 		return false | ||||
| 	end | ||||
| 	local node = worldedit.normalize_nodename(nodename) | ||||
| 	if not node then | ||||
| 		return false, S("invalid node name: @1", nodename) | ||||
| 	end | ||||
| 	return true, tonumber(radius), node | ||||
| end | ||||
|  | ||||
| worldedit.register_command("hollowdome", { | ||||
| 	params = "<radius> <node>", | ||||
| 	description = S("Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>"), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_dome, | ||||
| 	nodes_needed = function(name, radius, node) | ||||
| 		return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome | ||||
| 	end, | ||||
| 	func = function(name, radius, node) | ||||
| 		local count = worldedit.dome(worldedit.pos1[name], radius, node, true) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("dome", { | ||||
| 	params = "<radius> <node>", | ||||
| 	description = S("Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>"), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_dome, | ||||
| 	nodes_needed = function(name, radius, node) | ||||
| 		return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome | ||||
| 	end, | ||||
| 	func = function(name, radius, node) | ||||
| 		local count = worldedit.dome(worldedit.pos1[name], radius, node) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| local check_cylinder = function(param) | ||||
| 	-- two radii | ||||
| 	local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$") | ||||
| 	if found == nil then | ||||
| 		-- single radius | ||||
| 		found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$") | ||||
| 		radius2 = radius1 | ||||
| 	end | ||||
| 	if found == nil then | ||||
| 		return false | ||||
| 	end | ||||
| 	local node = worldedit.normalize_nodename(nodename) | ||||
| 	if not node then | ||||
| 		return false, S("invalid node name: @1", nodename) | ||||
| 	end | ||||
| 	return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node | ||||
| end | ||||
|  | ||||
| worldedit.register_command("hollowcylinder", { | ||||
| 	params = "x/y/z/? <length> <radius1> [radius2] <node>", | ||||
| 	description = S("Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>"), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_cylinder, | ||||
| 	nodes_needed = function(name, axis, length, radius1, radius2, node) | ||||
| 		local radius = math.max(radius1, radius2) | ||||
| 		return math.ceil(math.pi * (radius ^ 2) * length) | ||||
| 	end, | ||||
| 	func = function(name, axis, length, radius1, radius2, node) | ||||
| 		if axis == "?" then | ||||
| 			local sign | ||||
| 			axis, sign = worldedit.player_axis(name) | ||||
| 			length = length * sign | ||||
| 		end | ||||
| 		local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node, true) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("cylinder", { | ||||
| 	params = "x/y/z/? <length> <radius1> [radius2] <node>", | ||||
| 	description = S("Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>"), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_cylinder, | ||||
| 	nodes_needed = function(name, axis, length, radius1, radius2, node) | ||||
| 		local radius = math.max(radius1, radius2) | ||||
| 		return math.ceil(math.pi * (radius ^ 2) * length) | ||||
| 	end, | ||||
| 	func = function(name, axis, length, radius1, radius2, node) | ||||
| 		if axis == "?" then | ||||
| 			local sign | ||||
| 			axis, sign = worldedit.player_axis(name) | ||||
| 			length = length * sign | ||||
| 		end | ||||
| 		local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| local check_pyramid = function(param) | ||||
| 	local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$") | ||||
| 	if found == nil then | ||||
| 		return false | ||||
| 	end | ||||
| 	local node = worldedit.normalize_nodename(nodename) | ||||
| 	if not node then | ||||
| 		return false, S("invalid node name: @1", nodename) | ||||
| 	end | ||||
| 	return true, axis, tonumber(height), node | ||||
| end | ||||
|  | ||||
| worldedit.register_command("hollowpyramid", { | ||||
| 	params = "x/y/z/? <height> <node>", | ||||
| 	description = S("Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>"), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_pyramid, | ||||
| 	nodes_needed = function(name, axis, height, node) | ||||
| 		return math.ceil(((height * 2 + 1) ^ 2) * height / 3) | ||||
| 	end, | ||||
| 	func = function(name, axis, height, node) | ||||
| 		if axis == "?" then | ||||
| 			local sign | ||||
| 			axis, sign = worldedit.player_axis(name) | ||||
| 			height = height * sign | ||||
| 		end | ||||
| 		local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("pyramid", { | ||||
| 	params = "x/y/z/? <height> <node>", | ||||
| 	description = S("Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>"), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = check_pyramid, | ||||
| 	nodes_needed = function(name, axis, height, node) | ||||
| 		return math.ceil(((height * 2 + 1) ^ 2) * height / 3) | ||||
| 	end, | ||||
| 	func = function(name, axis, height, node) | ||||
| 		if axis == "?" then | ||||
| 			local sign | ||||
| 			axis, sign = worldedit.player_axis(name) | ||||
| 			height = height * sign | ||||
| 		end | ||||
| 		local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("spiral", { | ||||
| 	params = "<length> <height> <space> <node>", | ||||
| 	description = S("Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>"), | ||||
| 	category = S("Shapes"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = function(param) | ||||
| 		local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$") | ||||
| 		if found == nil then | ||||
| 			return false | ||||
| 		end | ||||
| 		local node = worldedit.normalize_nodename(nodename) | ||||
| 		if not node then | ||||
| 			return false, S("invalid node name: @1", nodename) | ||||
| 		end | ||||
| 		return true, tonumber(length), tonumber(height), tonumber(space), node | ||||
| 	end, | ||||
| 	nodes_needed = function(name, length, height, space, node) | ||||
| 		return (length + space) * height -- TODO: this is not the upper bound | ||||
| 	end, | ||||
| 	func = function(name, length, height, space, node) | ||||
| 		local count = worldedit.spiral(worldedit.pos1[name], length, height, space, node) | ||||
| 		return true, S("@1 nodes added", count) | ||||
| 	end, | ||||
| }) | ||||
							
								
								
									
										234
									
								
								worldedit_commands/region.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,234 @@ | ||||
| local S = minetest.get_translator("worldedit_commands") | ||||
|  | ||||
| worldedit.set_pos = {} | ||||
| worldedit.inspect = {} | ||||
|  | ||||
|  | ||||
| worldedit.register_command("inspect", { | ||||
| 	params = "[on/off/1/0/true/false/yes/no/enable/disable]", | ||||
| 	description = S("Enable or disable node inspection"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	parse = function(param) | ||||
| 		if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then | ||||
| 			return true, true | ||||
| 		elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then | ||||
| 			return true, false | ||||
| 		end | ||||
| 		return false | ||||
| 	end, | ||||
| 	func = function(name, enable) | ||||
| 		if enable then | ||||
| 			worldedit.inspect[name] = true | ||||
| 			local axis, sign = worldedit.player_axis(name) | ||||
| 			worldedit.player_notify(name, S( | ||||
| 				"inspector: inspection enabled for @1, currently facing the @2 axis", | ||||
| 				name, | ||||
| 				axis .. (sign > 0 and "+" or "-") | ||||
| 			), "info") | ||||
| 		else | ||||
| 			worldedit.inspect[name] = nil | ||||
| 			worldedit.player_notify(name, S("inspector: inspection disabled"), "info") | ||||
| 		end | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| local VEC_6DIRS = { | ||||
| 	vector.new( 1, 0, 0), | ||||
| 	vector.new(-1, 0, 0), | ||||
| 	vector.new( 0, 1, 0), | ||||
| 	vector.new( 0,-1, 0), | ||||
| 	vector.new( 0, 0, 1), | ||||
| 	vector.new( 0, 0,-1), | ||||
| } | ||||
| local function get_node_rlight(pos) | ||||
| 	local ret = 0 | ||||
| 	for _, v in ipairs(VEC_6DIRS) do | ||||
| 		ret = math.max(ret, minetest.get_node_light(vector.add(pos, v))) | ||||
| 	end | ||||
| 	return ret | ||||
| end | ||||
|  | ||||
| minetest.register_on_punchnode(function(pos, node, puncher) | ||||
| 	local name = puncher:get_player_name() | ||||
| 	if worldedit.inspect[name] then | ||||
| 		local axis, sign = worldedit.player_axis(name) | ||||
| 		local message = S( | ||||
| 			"inspector: @1 at @2 (param1=@3, param2=@4, received light=@5) punched facing the @6 axis", | ||||
| 			node.name, | ||||
| 			minetest.pos_to_string(pos), | ||||
| 			node.param1, | ||||
| 			node.param2, | ||||
| 			get_node_rlight(pos), | ||||
| 			axis .. (sign > 0 and "+" or "-") | ||||
| 		) | ||||
| 		worldedit.player_notify(name, message, "info") | ||||
| 	end | ||||
| end) | ||||
|  | ||||
|  | ||||
| worldedit.register_command("mark", { | ||||
| 	params = "", | ||||
| 	description = S("Show markers at the region positions"), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	func = function(name) | ||||
| 		worldedit.marker_update(name) | ||||
| 		return true, S("region marked") | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("unmark", { | ||||
| 	params = "", | ||||
| 	description = S("Hide markers if currently shown"), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	func = function(name) | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 		worldedit.pos1[name] = nil | ||||
| 		worldedit.pos2[name] = nil | ||||
| 		worldedit.marker_update(name) | ||||
| 		worldedit.pos1[name] = pos1 | ||||
| 		worldedit.pos2[name] = pos2 | ||||
| 		return true, S("region unmarked") | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| local function set_pos1(name, pos) | ||||
| 	assert(pos) | ||||
| 	worldedit.pos1[name] = pos | ||||
| 	worldedit.mark_pos1(name) | ||||
| 	worldedit.player_notify(name, S("position @1 set to @2", 1, minetest.pos_to_string(pos)), "ok") | ||||
| end | ||||
|  | ||||
| local function set_pos2(name, pos) | ||||
| 	assert(pos) | ||||
| 	worldedit.pos2[name] = pos | ||||
| 	worldedit.mark_pos2(name) | ||||
| 	worldedit.player_notify(name, S("position @1 set to @2", 2, minetest.pos_to_string(pos)), "ok") | ||||
| end | ||||
|  | ||||
| worldedit.register_command("pos1", { | ||||
| 	params = "", | ||||
| 	description = S("Set WorldEdit region position @1 to the player's location", 1), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	func = function(name) | ||||
| 		local player = minetest.get_player_by_name(name) | ||||
| 		if not player then return end | ||||
| 		set_pos1(name, vector.round(player:get_pos())) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("pos2", { | ||||
| 	params = "", | ||||
| 	description = S("Set WorldEdit region position @1 to the player's location", 2), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	func = function(name) | ||||
| 		local player = minetest.get_player_by_name(name) | ||||
| 		if not player then return end | ||||
| 		set_pos2(name, vector.round(player:get_pos())) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("p", { | ||||
| 	params = "set/set1/set2/get", | ||||
| 	description = S("Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region"), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	parse = function(param) | ||||
| 		if param == "set" or param == "set1" or param == "set2" or param == "get" then | ||||
| 			return true, param | ||||
| 		end | ||||
| 		return false, S("unknown subcommand: @1", param) | ||||
| 	end, | ||||
| 	func = function(name, param) | ||||
| 		local msg | ||||
| 		if param == "set" then --set both WorldEdit positions | ||||
| 			worldedit.set_pos[name] = "pos1" | ||||
| 			msg = S("select positions by punching two nodes") | ||||
| 		elseif param == "set1" then --set WorldEdit position 1 | ||||
| 			worldedit.set_pos[name] = "pos1only" | ||||
| 			msg = S("select position @1 by punching a node", 1) | ||||
| 		elseif param == "set2" then --set WorldEdit position 2 | ||||
| 			worldedit.set_pos[name] = "pos2" | ||||
| 			msg = S("select position @1 by punching a node", 2) | ||||
| 		elseif param == "get" then --display current WorldEdit positions | ||||
| 			if worldedit.pos1[name] ~= nil then | ||||
| 				msg = S("position @1: @2", 1, minetest.pos_to_string(worldedit.pos1[name])) | ||||
| 			else | ||||
| 				msg = S("position @1 not set", 1) | ||||
| 			end | ||||
| 			msg = msg .. "\n" | ||||
| 			if worldedit.pos2[name] ~= nil then | ||||
| 				msg = msg .. S("position @1: @2", 2, minetest.pos_to_string(worldedit.pos2[name])) | ||||
| 			else | ||||
| 				msg = msg .. S("position @1 not set", 2) | ||||
| 			end | ||||
| 		end | ||||
| 		if msg then | ||||
| 			worldedit.player_notify(name, msg, "info") | ||||
| 		end | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("fixedpos", { | ||||
| 	params = "set1/set2 <x> <y> <z>", | ||||
| 	description = S("Set a WorldEdit region position to the position at (<x>, <y>, <z>)"), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	parse = function(param) | ||||
| 		local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$") | ||||
| 		if found == nil then | ||||
| 			return false | ||||
| 		end | ||||
| 		return true, flag, vector.new(tonumber(x), tonumber(y), tonumber(z)) | ||||
| 	end, | ||||
| 	func = function(name, flag, pos) | ||||
| 		if flag == "set1" then | ||||
| 			set_pos1(name, pos) | ||||
| 		else --flag == "set2" | ||||
| 			set_pos2(name, pos) | ||||
| 		end | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| minetest.register_on_punchnode(function(pos, node, puncher) | ||||
| 	local name = puncher:get_player_name() | ||||
| 	if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position | ||||
| 		if worldedit.set_pos[name] == "pos1" then --setting position 1 | ||||
| 			set_pos1(name, pos) | ||||
| 			worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation | ||||
| 		elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only | ||||
| 			set_pos1(name, pos) | ||||
| 			worldedit.set_pos[name] = nil --finished setting positions | ||||
| 		elseif worldedit.set_pos[name] == "pos2" then --setting position 2 | ||||
| 			set_pos2(name, pos) | ||||
| 			worldedit.set_pos[name] = nil --finished setting positions | ||||
| 		elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities | ||||
| 			worldedit.prob_pos[name] = pos | ||||
| 			minetest.show_formspec(name, "prob_val_enter", "field[text;;]") | ||||
| 		end | ||||
| 	end | ||||
| end) | ||||
|  | ||||
| worldedit.register_command("volume", { | ||||
| 	params = "", | ||||
| 	description = S("Display the volume of the current WorldEdit region"), | ||||
| 	category = S("Region operations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	func = function(name) | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
|  | ||||
| 		local volume = worldedit.volume(pos1, pos2) | ||||
| 		local abs = math.abs | ||||
| 		worldedit.player_notify(name, S( | ||||
| 			"current region has a volume of @1 nodes (@2*@3*@4)", | ||||
| 			volume, | ||||
| 			abs(pos2.x - pos1.x) + 1, | ||||
| 			abs(pos2.y - pos1.y) + 1, | ||||
| 			abs(pos2.z - pos1.z) + 1 | ||||
| 		), "info") | ||||
| 	end, | ||||
| }) | ||||
| @@ -1,15 +1,30 @@ | ||||
| local S = minetest.get_translator("worldedit_commands") | ||||
|  | ||||
| local safe_region_limit = tonumber(minetest.settings:get("worldedit_safe_region_limit") or "20000") | ||||
|  | ||||
| local safe_region_callback = {} | ||||
|  | ||||
| --`count` is the number of nodes that would possibly be modified | ||||
| --`callback` is a callback to run when the user confirms | ||||
| local function safe_region(name, count, callback) | ||||
| 	if count < 20000 then | ||||
| 	if safe_region_limit <= 0 or count < safe_region_limit then | ||||
| 		return callback() | ||||
| 	end | ||||
|  | ||||
| 	-- save callback to call later | ||||
| 	safe_region_callback[name] = callback | ||||
| 	worldedit.player_notify(name, "WARNING: this operation could affect up to " .. count .. " nodes; type //y to continue or //n to cancel") | ||||
|  | ||||
| 	local count_str = tostring(count) | ||||
| 	-- highlight millions, 1 mln <=> 100x100x100 cube | ||||
| 	if #count_str > 6 then | ||||
| 		count_str = minetest.colorize("#f33", count_str:sub(1, -7)) .. count_str:sub(-6, -1) | ||||
| 	end | ||||
|  | ||||
| 	local yes_cmd = minetest.colorize("#00ffff", "//y") | ||||
| 	local no_cmd = minetest.colorize("#00ffff", "//n") | ||||
| 	local msg = S("WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel", | ||||
| 		count_str, yes_cmd, no_cmd) | ||||
| 	worldedit.player_notify(name, msg, "info") | ||||
| end | ||||
|  | ||||
| local function reset_pending(name) | ||||
| @@ -18,11 +33,11 @@ end | ||||
|  | ||||
| minetest.register_chatcommand("/y", { | ||||
| 	params = "", | ||||
| 	description = "Confirm a pending operation", | ||||
| 	description = S("Confirm a pending operation"), | ||||
| 	func = function(name) | ||||
| 		local callback = safe_region_callback[name] | ||||
| 		if not callback then | ||||
| 			worldedit.player_notify(name, "no operation pending") | ||||
| 			worldedit.player_notify(name, S("no operation pending"), "error") | ||||
| 			return | ||||
| 		end | ||||
|  | ||||
| @@ -33,10 +48,10 @@ minetest.register_chatcommand("/y", { | ||||
|  | ||||
| minetest.register_chatcommand("/n", { | ||||
| 	params = "", | ||||
| 	description = "Abort a pending operation", | ||||
| 	description = S("Abort a pending operation"), | ||||
| 	func = function(name) | ||||
| 		if not safe_region_callback[name] then | ||||
| 			worldedit.player_notify(name, "no operation pending") | ||||
| 			worldedit.player_notify(name, S("no operation pending"), "error") | ||||
| 			return | ||||
| 		end | ||||
|  | ||||
|   | ||||
							
								
								
									
										284
									
								
								worldedit_commands/schematics.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,284 @@ | ||||
| local S = minetest.get_translator("worldedit_commands") | ||||
|  | ||||
| worldedit.prob_pos = {} | ||||
| worldedit.prob_list = {} | ||||
|  | ||||
| local function check_region(name) | ||||
| 	return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| end | ||||
|  | ||||
| local function check_filename(name) | ||||
| 	return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil | ||||
| end | ||||
|  | ||||
| local function open_schematic(name, param) | ||||
| 	-- find the file in the world path | ||||
| 	local testpaths = { | ||||
| 		minetest.get_worldpath() .. "/schems/" .. param, | ||||
| 		minetest.get_worldpath() .. "/schems/" .. param .. ".we", | ||||
| 		minetest.get_worldpath() .. "/schems/" .. param .. ".wem", | ||||
| 	} | ||||
| 	local file, err | ||||
| 	for index, path in ipairs(testpaths) do | ||||
| 		file, err = io.open(path, "rb") | ||||
| 		if not err then | ||||
| 			break | ||||
| 		end | ||||
| 	end | ||||
| 	if err then | ||||
| 		worldedit.player_notify(name, S("Could not open file \"@1\"", param), "error") | ||||
| 		return | ||||
| 	end | ||||
| 	local value = file:read("*a") | ||||
| 	file:close() | ||||
|  | ||||
| 	local version = worldedit.read_header(value) | ||||
| 	if version == nil or version == 0 then | ||||
| 		worldedit.player_notify(name, S("Invalid file format!"), "error") | ||||
| 		return | ||||
| 	elseif version > worldedit.LATEST_SERIALIZATION_VERSION then | ||||
| 		worldedit.player_notify(name, S("Schematic was created with a newer version of WorldEdit."), "error") | ||||
| 		return | ||||
| 	end | ||||
|  | ||||
| 	return value | ||||
| end | ||||
|  | ||||
| local function detect_misaligned_schematic(name, pos1, pos2) | ||||
| 	pos1 = worldedit.sort_pos(pos1, pos2) | ||||
| 	-- Check that allocate/save can position the schematic correctly | ||||
| 	-- The expected behaviour is that the (0,0,0) corner of the schematic stays | ||||
| 	-- at pos1, this only works when the minimum position is actually present | ||||
| 	-- in the schematic. | ||||
| 	local node = minetest.get_node(pos1) | ||||
| 	local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore" | ||||
| 	if not have_node_at_origin then | ||||
| 		worldedit.player_notify(name, | ||||
| 			S("Warning: The schematic contains excessive free space and WILL be ".. | ||||
| 			"misaligned when allocated or loaded. To avoid this, shrink your ".. | ||||
| 			"area to cover exactly the nodes to be saved.") | ||||
| 		) | ||||
| 	end | ||||
| end | ||||
|  | ||||
|  | ||||
| worldedit.register_command("save", { | ||||
| 	params = "<file>", | ||||
| 	description = S("Save the current WorldEdit region to \"(world folder)/schems/<file>.we\""), | ||||
| 	category = S("Schematics"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		if param == "" then | ||||
| 			return false | ||||
| 		end | ||||
| 		if not check_filename(param) then | ||||
| 			return false, S("Disallowed file name: @1", param) | ||||
| 		end | ||||
| 		return true, param | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, param) | ||||
| 		local result, count = worldedit.serialize(worldedit.pos1[name], | ||||
| 				worldedit.pos2[name]) | ||||
| 		detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name]) | ||||
|  | ||||
| 		local path = minetest.get_worldpath() .. "/schems" | ||||
| 		-- Create directory if it does not already exist | ||||
| 		minetest.mkdir(path) | ||||
|  | ||||
| 		local filename = path .. "/" .. param .. ".we" | ||||
| 		local file, err = io.open(filename, "wb") | ||||
| 		if err ~= nil then | ||||
| 			return false, S("Could not save file to \"@1\"", filename) | ||||
| 		end | ||||
| 		file:write(result) | ||||
| 		file:flush() | ||||
| 		file:close() | ||||
|  | ||||
| 		return true, S("@1 nodes saved", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("allocate", { | ||||
| 	params = "<file>", | ||||
| 	description = S("Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region"), | ||||
| 	category = S("Schematics"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = function(param) | ||||
| 		if param == "" then | ||||
| 			return false | ||||
| 		end | ||||
| 		if not check_filename(param) then | ||||
| 			return false, S("Disallowed file name: @1", param) | ||||
| 		end | ||||
| 		return true, param | ||||
| 	end, | ||||
| 	func = function(name, param) | ||||
| 		local pos = worldedit.pos1[name] | ||||
|  | ||||
| 		local value = open_schematic(name, param) | ||||
| 		if not value then | ||||
| 			return false | ||||
| 		end | ||||
|  | ||||
| 		local nodepos1, nodepos2, count = worldedit.allocate(pos, value) | ||||
| 		if not nodepos1 then | ||||
| 			return false, S("Schematic empty, nothing allocated") | ||||
| 		end | ||||
|  | ||||
| 		worldedit.pos1[name] = nodepos1 | ||||
| 		worldedit.pos2[name] = nodepos2 | ||||
| 		worldedit.marker_update(name) | ||||
|  | ||||
| 		return true, S("@1 nodes allocated", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("load", { | ||||
| 	params = "<file>", | ||||
| 	description = S("Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin"), | ||||
| 	category = S("Schematics"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = function(param) | ||||
| 		if param == "" then | ||||
| 			return false | ||||
| 		end | ||||
| 		if not check_filename(param) then | ||||
| 			return false, S("Disallowed file name: @1", param) | ||||
| 		end | ||||
| 		return true, param | ||||
| 	end, | ||||
| 	func = function(name, param) | ||||
| 		local pos = worldedit.pos1[name] | ||||
|  | ||||
| 		local value = open_schematic(name, param) | ||||
| 		if not value then | ||||
| 			return false | ||||
| 		end | ||||
|  | ||||
| 		local count = worldedit.deserialize(pos, value) | ||||
| 		if count == nil then | ||||
| 			return false, S("Loading failed!") | ||||
| 		end | ||||
| 		return true, S("@1 nodes loaded", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
|  | ||||
| worldedit.register_command("mtschemcreate", { | ||||
| 	params = "<file>", | ||||
| 	description = S("Save the current WorldEdit region using the Minetest ".. | ||||
| 		"Schematic format to \"(world folder)/schems/<filename>.mts\""), | ||||
| 	category = S("Schematics"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		if param == "" then | ||||
| 			return false | ||||
| 		end | ||||
| 		if not check_filename(param) then | ||||
| 			return false, S("Disallowed file name: @1", param) | ||||
| 		end | ||||
| 		return true, param | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, param) | ||||
| 		local path = minetest.get_worldpath() .. "/schems" | ||||
| 		-- Create directory if it does not already exist | ||||
| 		minetest.mkdir(path) | ||||
|  | ||||
| 		local filename = path .. "/" .. param .. ".mts" | ||||
| 		local ret = minetest.create_schematic(worldedit.pos1[name], | ||||
| 				worldedit.pos2[name], worldedit.prob_list[name], | ||||
| 				filename) | ||||
| 		worldedit.prob_list[name] = {} | ||||
| 		if ret == nil then | ||||
| 			return false, S("Failed to create Minetest schematic") | ||||
| 		end | ||||
| 		return true, S("Saved Minetest schematic to @1", param) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("mtschemplace", { | ||||
| 	params = "<file>", | ||||
| 	description = S("Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin"), | ||||
| 	category = S("Schematics"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 1, | ||||
| 	parse = function(param) | ||||
| 		local found, _, filename, rotation = param:find("^(.+)%s+([012789]+)$") | ||||
| 		if found == nil then | ||||
| 			filename = param | ||||
| 		elseif rotation ~= "0" and rotation ~= "90" and rotation ~= "180" and rotation ~= "270" then | ||||
| 			return false, S("Invalid rotation: @1", rotation) | ||||
| 		end | ||||
| 		if not check_filename(filename) then | ||||
| 			return false, S("Disallowed file name: @1", filename) | ||||
| 		end | ||||
| 		return true, filename, rotation | ||||
| 	end, | ||||
| 	func = function(name, filename, rotation) | ||||
| 		local pos = worldedit.pos1[name] | ||||
|  | ||||
| 		local path = minetest.get_worldpath() .. "/schems/" .. filename .. ".mts" | ||||
| 		if minetest.place_schematic(pos, path, rotation) == nil then | ||||
| 			return false, S("failed to place Minetest schematic") | ||||
| 		end | ||||
| 		return true, S("placed Minetest schematic @1 at @2", | ||||
| 			filename, minetest.pos_to_string(pos)) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("mtschemprob", { | ||||
| 	params = "start/finish/get", | ||||
| 	description = S("Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry"), | ||||
| 	category = S("Schematics"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	parse = function(param) | ||||
| 		if param ~= "start" and param ~= "finish" and param ~= "get" then | ||||
| 			return false, S("unknown subcommand: @1", param) | ||||
| 		end | ||||
| 		return true, param | ||||
| 	end, | ||||
| 	func = function(name, param) | ||||
| 		if param == "start" then --start probability setting | ||||
| 			worldedit.set_pos[name] = "prob" | ||||
| 			worldedit.prob_list[name] = {} | ||||
| 			worldedit.player_notify(name, S("select Minetest schematic probability values by punching nodes"), "info") | ||||
| 		elseif param == "finish" then --finish probability setting | ||||
| 			worldedit.set_pos[name] = nil | ||||
| 			worldedit.player_notify(name, S("finished Minetest schematic probability selection"), "info") | ||||
| 		elseif param == "get" then --get all nodes that had probabilities set on them | ||||
| 			local text = "" | ||||
| 			local problist = worldedit.prob_list[name] | ||||
| 			if problist == nil then | ||||
| 				return | ||||
| 			end | ||||
| 			for k,v in pairs(problist) do | ||||
| 				local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100 | ||||
| 				text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | " | ||||
| 			end | ||||
| 			worldedit.player_notify(name, S("currently set node probabilities:") .. "\n" .. text, "info") | ||||
| 		end | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 	if formname == "prob_val_enter" then | ||||
| 		local name = player:get_player_name() | ||||
| 		local problist = worldedit.prob_list[name] | ||||
| 		if problist == nil then | ||||
| 			return | ||||
| 		end | ||||
| 		local e = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)} | ||||
| 		if e.pos == nil or e.prob == nil or e.prob < 0 or e.prob > 256 then | ||||
| 			worldedit.player_notify(name, S("invalid node probability given, not saved"), "error") | ||||
| 			return | ||||
| 		end | ||||
| 		problist[#problist+1] = e | ||||
| 	end | ||||
| end) | ||||
|  | ||||
| Before Width: | Height: | Size: 147 B After Width: | Height: | Size: 79 B | 
| Before Width: | Height: | Size: 142 B After Width: | Height: | Size: 100 B | 
| Before Width: | Height: | Size: 157 B After Width: | Height: | Size: 112 B | 
							
								
								
									
										
											BIN
										
									
								
								worldedit_commands/textures/worldedit_wand.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 303 B | 
							
								
								
									
										269
									
								
								worldedit_commands/transform.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,269 @@ | ||||
| local S = minetest.get_translator("worldedit_commands") | ||||
|  | ||||
| local function check_region(name) | ||||
| 	return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) | ||||
| end | ||||
|  | ||||
|  | ||||
| worldedit.register_command("copy", { | ||||
| 	params = "x/y/z/? <amount>", | ||||
| 	description = S("Copy the current WorldEdit region along the given axis by <amount> nodes"), | ||||
| 	category = S("Transformations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$") | ||||
| 		if found == nil then | ||||
| 			return false | ||||
| 		end | ||||
| 		return true, axis, tonumber(amount) | ||||
| 	end, | ||||
| 	nodes_needed = function(name, axis, amount) | ||||
| 		return check_region(name) * 2 | ||||
| 	end, | ||||
| 	func = function(name, axis, amount) | ||||
| 		if axis == "?" then | ||||
| 			local sign | ||||
| 			axis, sign = worldedit.player_axis(name) | ||||
| 			amount = amount * sign | ||||
| 		end | ||||
|  | ||||
| 		local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount) | ||||
| 		return true, S("@1 nodes copied", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("move", { | ||||
| 	params = "x/y/z/? <amount>", | ||||
| 	description = S("Move the current WorldEdit region along the given axis by <amount> nodes"), | ||||
| 	category = S("Transformations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$") | ||||
| 		if found == nil then | ||||
| 			return false | ||||
| 		end | ||||
| 		return true, axis, tonumber(amount) | ||||
| 	end, | ||||
| 	nodes_needed = function(name, axis, amount) | ||||
| 		return check_region(name) * 2 | ||||
| 	end, | ||||
| 	func = function(name, axis, amount) | ||||
| 		if axis == "?" then | ||||
| 			local sign | ||||
| 			axis, sign = worldedit.player_axis(name) | ||||
| 			amount = amount * sign | ||||
| 		end | ||||
|  | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 		local count = worldedit.move(pos1, pos2, axis, amount) | ||||
|  | ||||
| 		pos1[axis] = pos1[axis] + amount | ||||
| 		pos2[axis] = pos2[axis] + amount | ||||
| 		worldedit.marker_update(name) | ||||
| 		return true, S("@1 nodes moved", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("stack", { | ||||
| 	params = "x/y/z/? <count>", | ||||
| 	description = S("Stack the current WorldEdit region along the given axis <count> times"), | ||||
| 	category = S("Transformations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$") | ||||
| 		if found == nil then | ||||
| 			return false | ||||
| 		end | ||||
| 		return true, axis, tonumber(repetitions) | ||||
| 	end, | ||||
| 	nodes_needed = function(name, axis, repetitions) | ||||
| 		return check_region(name) * math.abs(repetitions) | ||||
| 	end, | ||||
| 	func = function(name, axis, repetitions) | ||||
| 		if axis == "?" then | ||||
| 			local sign | ||||
| 			axis, sign = worldedit.player_axis(name) | ||||
| 			repetitions = repetitions * sign | ||||
| 		end | ||||
|  | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 		local count = worldedit.volume(pos1, pos2) * math.abs(repetitions) | ||||
| 		worldedit.stack(pos1, pos2, axis, repetitions, function() | ||||
| 			worldedit.player_notify(name, S("@1 nodes stacked", count), "ok") | ||||
| 		end) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("stack2", { | ||||
| 	params = "<count> <x> <y> <z>", | ||||
| 	description = S("Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>"), | ||||
| 	category = S("Transformations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local repetitions, incs = param:match("(%d+)%s*(.+)") | ||||
| 		if repetitions == nil then | ||||
| 			return false, S("invalid count: @1", param) | ||||
| 		end | ||||
| 		local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)") | ||||
| 		if x == nil then | ||||
| 			return false, S("invalid increments: @1", param) | ||||
| 		end | ||||
|  | ||||
| 		return true, tonumber(repetitions), vector.new(tonumber(x), tonumber(y), tonumber(z)) | ||||
| 	end, | ||||
| 	nodes_needed = function(name, repetitions, offset) | ||||
| 		return check_region(name) * repetitions | ||||
| 	end, | ||||
| 	func = function(name, repetitions, offset) | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 		local count = worldedit.volume(pos1, pos2) * repetitions | ||||
| 		worldedit.stack2(pos1, pos2, offset, repetitions, function() | ||||
| 			worldedit.player_notify(name, S("@1 nodes stacked", count), "ok") | ||||
| 		end) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("stretch", { | ||||
| 	params = "<stretchx> <stretchy> <stretchz>", | ||||
| 	description = S("Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin"), | ||||
| 	category = S("Transformations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$") | ||||
| 		if found == nil then | ||||
| 			return false | ||||
| 		end | ||||
| 		stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz) | ||||
| 		if stretchx == 0 or stretchy == 0 or stretchz == 0 then | ||||
| 			return false, S("invalid scaling factors: @1", param) | ||||
| 		end | ||||
| 		return true, stretchx, stretchy, stretchz | ||||
| 	end, | ||||
| 	nodes_needed = function(name, stretchx, stretchy, stretchz) | ||||
| 		return check_region(name) * stretchx * stretchy * stretchz | ||||
| 	end, | ||||
| 	func = function(name, stretchx, stretchy, stretchz) | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 		local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz) | ||||
|  | ||||
| 		--reset markers to scaled positions | ||||
| 		worldedit.pos1[name] = pos1 | ||||
| 		worldedit.pos2[name] = pos2 | ||||
| 		worldedit.marker_update(name) | ||||
|  | ||||
| 		return true, S("@1 nodes stretched", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("transpose", { | ||||
| 	params = "x/y/z/? x/y/z/?", | ||||
| 	description = S("Transpose the current WorldEdit region along the given axes"), | ||||
| 	category = S("Transformations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$") | ||||
| 		if found == nil then | ||||
| 			return false | ||||
| 		elseif axis1 == axis2 then | ||||
| 			return false, S("invalid usage: axes must be different") | ||||
| 		end | ||||
| 		return true, axis1, axis2 | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, axis1, axis2) | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 		if axis1 == "?" then axis1 = worldedit.player_axis(name) end | ||||
| 		if axis2 == "?" then axis2 = worldedit.player_axis(name) end | ||||
| 		local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2) | ||||
|  | ||||
| 		--reset markers to transposed positions | ||||
| 		worldedit.pos1[name] = pos1 | ||||
| 		worldedit.pos2[name] = pos2 | ||||
| 		worldedit.marker_update(name) | ||||
|  | ||||
| 		return true, S("@1 nodes transposed", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("flip", { | ||||
| 	params = "x/y/z/?", | ||||
| 	description = S("Flip the current WorldEdit region along the given axis"), | ||||
| 	category = S("Transformations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then | ||||
| 			return false | ||||
| 		end | ||||
| 		return true, param | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, param) | ||||
| 		if param == "?" then param = worldedit.player_axis(name) end | ||||
| 		local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param) | ||||
| 		return true, S("@1 nodes flipped", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("rotate", { | ||||
| 	params = "x/y/z/? <angle>", | ||||
| 	description = S("Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)"), | ||||
| 	category = S("Transformations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$") | ||||
| 		if found == nil then | ||||
| 			return false | ||||
| 		end | ||||
| 		angle = tonumber(angle) | ||||
| 		if angle % 90 ~= 0 or angle % 360 == 0 then | ||||
| 			return false, S("invalid usage: angle must be multiple of 90") | ||||
| 		end | ||||
| 		return true, axis, angle | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, axis, angle) | ||||
| 		local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] | ||||
| 		if axis == "?" then axis = worldedit.player_axis(name) end | ||||
| 		local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle) | ||||
|  | ||||
| 		--reset markers to rotated positions | ||||
| 		worldedit.pos1[name] = pos1 | ||||
| 		worldedit.pos2[name] = pos2 | ||||
| 		worldedit.marker_update(name) | ||||
|  | ||||
| 		return true, S("@1 nodes rotated", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| worldedit.register_command("orient", { | ||||
| 	params = "<angle>", | ||||
| 	description = S("Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)"), | ||||
| 	category = S("Transformations"), | ||||
| 	privs = {worldedit=true}, | ||||
| 	require_pos = 2, | ||||
| 	parse = function(param) | ||||
| 		local found, _, angle = param:find("^([+-]?%d+)$") | ||||
| 		if found == nil then | ||||
| 			return false | ||||
| 		end | ||||
| 		angle = tonumber(angle) | ||||
| 		if angle % 90 ~= 0 then | ||||
| 			return false, S("invalid usage: angle must be multiple of 90") | ||||
| 		end | ||||
| 		return true, angle | ||||
| 	end, | ||||
| 	nodes_needed = check_region, | ||||
| 	func = function(name, angle) | ||||
| 		local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle) | ||||
| 		return true, S("@1 nodes oriented", count) | ||||
| 	end, | ||||
| }) | ||||
|  | ||||
| @@ -1,3 +1,5 @@ | ||||
| local S = minetest.get_translator("worldedit_commands") | ||||
|  | ||||
| local function above_or_under(placer, pointed_thing) | ||||
| 	if placer:get_player_control().sneak then | ||||
| 		return pointed_thing.above | ||||
| @@ -9,11 +11,18 @@ end | ||||
| local punched_air_time = {} | ||||
|  | ||||
| minetest.register_tool(":worldedit:wand", { | ||||
| 	description = "WorldEdit Wand tool\nLeft-click to set 1st position, right-click to set 2nd", | ||||
| 	description = S("WorldEdit Wand tool\nLeft-click to set 1st position, right-click to set 2nd"), | ||||
| 	inventory_image = "worldedit_wand.png", | ||||
| 	stack_max = 1, -- there is no need to have more than one | ||||
| 	liquids_pointable = true, -- ground with only water on can be selected as well | ||||
|  | ||||
| 	-- ignore marker cube so the clicking on the position markers works reliably | ||||
| 	pointabilities = { | ||||
| 		objects = { | ||||
| 			["worldedit:region_cube"] = false | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	on_use = function(itemstack, placer, pointed_thing) | ||||
| 		if placer == nil or pointed_thing == nil then return end | ||||
| 		local name = placer:get_player_name() | ||||
| @@ -32,7 +41,7 @@ minetest.register_tool(":worldedit:wand", { | ||||
| 			local entity = pointed_thing.ref:get_luaentity() | ||||
| 			if entity and entity.name == "worldedit:pos2" then | ||||
| 				-- set pos1 = pos2 | ||||
| 				worldedit.pos1[name] = worldedit.pos2[name] | ||||
| 				worldedit.pos1[name] = vector.copy(worldedit.pos2[name]) | ||||
| 				worldedit.mark_pos1(name) | ||||
| 			end | ||||
| 		end | ||||
| @@ -57,7 +66,7 @@ minetest.register_tool(":worldedit:wand", { | ||||
| 		local entity = pointed_thing.ref:get_luaentity() | ||||
| 		if entity and entity.name == "worldedit:pos1" then | ||||
| 			-- set pos2 = pos1 | ||||
| 			worldedit.pos2[name] = worldedit.pos1[name] | ||||
| 			worldedit.pos2[name] = vector.copy(worldedit.pos1[name]) | ||||
| 			worldedit.mark_pos2(name) | ||||
| 		end | ||||
| 		return itemstack -- nothing consumed, nothing changed | ||||
|   | ||||
| @@ -140,7 +140,7 @@ local function execute_worldedit_command(command_name, player_name, params) | ||||
| 	assert(chatcmd, "unknown command: " .. command_name) | ||||
| 	local _, msg = chatcmd.func(player_name, params) | ||||
| 	if msg then | ||||
| 		 worldedit.player_notify(player_name, msg) | ||||
| 		minetest.chat_send_player(player_name, msg) | ||||
| 	end | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -42,7 +42,7 @@ Example: | ||||
| worldedit.register_gui_handler = function(identifier, handler) | ||||
| 	local enabled = true | ||||
| 	minetest.register_on_player_receive_fields(function(player, formname, fields) | ||||
| 		if not enabled then return false end | ||||
| 		if not enabled or formname ~= "" or fields.worldedit_gui then return false end | ||||
| 		enabled = false | ||||
| 		minetest.after(0.2, function() enabled = true end) | ||||
| 		local name = player:get_player_name() | ||||
| @@ -80,6 +80,7 @@ if minetest.global_exists("unified_inventory") then -- unified inventory install | ||||
| 	unified_inventory.register_button("worldedit_gui", { | ||||
| 		type = "image", | ||||
| 		image = "inventory_plus_worldedit_gui.png", | ||||
| 		tooltip = "Edit your World!", | ||||
| 		condition = function(player) | ||||
| 			return minetest.check_player_privs(player:get_player_name(), {worldedit=true}) | ||||
| 		end, | ||||
| @@ -193,7 +194,8 @@ elseif minetest.global_exists("sfinv") then -- sfinv installed | ||||
| 		get = function(self, player, context) | ||||
| 			local can_worldedit = minetest.check_player_privs(player, {worldedit=true}) | ||||
| 			local fs = orig_get(self, player, context) | ||||
| 			return fs .. (can_worldedit and "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]" or "") | ||||
| 			return fs .. (can_worldedit and "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]" .. | ||||
| 				"tooltip[worldedit_gui;Edit your World!]" or "") | ||||
| 		end | ||||
| 	}) | ||||
|  | ||||
| @@ -262,7 +264,7 @@ worldedit.register_gui_handler("worldedit_gui", function(name, fields) | ||||
| 			--ensure player has permission to perform action | ||||
| 			local has_privs, missing_privs = minetest.check_player_privs(name, entry.privs) | ||||
| 			if not has_privs then | ||||
| 				worldedit.player_notify(name, "you are not allowed to use this function (missing privileges: " .. table.concat(missing_privs, ", ") .. ")") | ||||
| 				worldedit.player_notify(name, "you are not allowed to use this function (missing privileges: " .. table.concat(missing_privs, ", ") .. ")", "error") | ||||
| 				return false | ||||
| 			end | ||||
| 			if entry.on_select then | ||||
|   | ||||
| Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB | 
| Before Width: | Height: | Size: 597 B After Width: | Height: | Size: 382 B |