1
0
mirror of https://github.com/Uberi/Minetest-WorldEdit.git synced 2025-07-20 16:40:27 +02:00

41 Commits

Author SHA1 Message Date
bf154b12c7 Use new 5.13.0 explicit VoxelManip free 2025-05-30 18:04:55 +02:00
03d4e45e59 Fix CI tests for new name and repo 2025-05-30 15:40:06 +02:00
6e316105e0 fixes #252: Missing placeholder in Russian translation. 2025-04-05 18:40:22 +02:00
ceb2c2cded Update German translation (#253) 2024-10-01 13:32:27 +02:00
ae77f73650 CI: replace deprecation action 2024-08-16 17:17:41 +02:00
28374f4f27 Run unit tests on MT 5.0 too 2024-05-26 16:08:46 +02:00
fb7a37e87c Some minor clean ups 2024-05-26 16:02:25 +02:00
57e7d4c488 Add polyfill for vector.copy()
closes #246
You could say I am doing this against better judgement, but this is
really a simple and easy fix for my problem.
2024-05-26 15:42:15 +02:00
e20a5a4e09 Teach LuaCheck about vector.* fields 2024-05-26 15:37:06 +02:00
f8442ef9fd Highlight big numbers in safe region warning 2024-05-26 15:18:13 +02:00
86de970552 Make safe region limit configurable 2024-05-26 15:01:12 +02:00
4c8d42bf7b Refuse too large areas in worldedit.keep_loaded()
fixes #234
2024-05-26 14:50:11 +02:00
5a00c07c68 Add forgotten file include
fixes: acb3ecefe4
2024-04-29 09:56:01 +02:00
cda772b18d Add a hollowcuboid iteration helper (#190) 2024-04-23 20:35:26 +02:00
8c758671bc Optimize //drain a bit 2024-04-22 23:19:21 +02:00
456ce8c800 Add tests for (hollow)cube 2024-04-22 19:16:04 +02:00
8065e3d804 Allow rotation with //mtschemplace
fixes #153
2024-04-22 18:37:14 +02:00
60b6b205ad Add nil checks around get_player_by_name()
in case someone uses the commands from ncurses
2024-04-22 18:10:30 +02:00
1d8d9a704f Introduce type parameter for worldedit.player_notify 2024-04-22 17:42:26 +02:00
d13ba673fe Remove stray gitignore 2024-04-22 00:06:13 +02:00
acb3ecefe4 Split up some files in worldedit mod 2024-04-22 00:01:31 +02:00
dc1150fe3d Split up init.lua in worldedit_commands 2024-04-22 00:00:51 +02:00
74663869f7 Translate worldedit_brush 2024-04-21 23:00:57 +02:00
4b470bdae6 Put commands into categories for //help 2024-04-21 23:00:48 +02:00
75d101116d Display aliases in //help output nicer 2024-04-20 20:39:34 +02:00
860d4a267d Some fixes to tests 2024-04-20 20:39:34 +02:00
372847e774 Add tests for worldedit.lua/luatransform 2024-04-20 20:39:34 +02:00
883caff58d Make //lua work with expressions as well (#243) 2024-04-20 20:00:41 +02:00
41d53180b1 Colorize command references in chat output 2024-04-20 14:22:46 +02:00
602f175cc0 Make use of minetest.load_area 2024-04-20 14:03:37 +02:00
e6fac23c53 Use worldedit.keep_loaded consistently 2024-04-20 13:59:04 +02:00
5914eab20b Abort a delayed operation if positions change
fixes #236
2024-04-20 13:45:00 +02:00
002dc462d6 Make wand tool not point the region marker cube 2024-04-20 13:02:27 +02:00
a713efe051 Fix wand tool causing pos1/pos2 aliasing
fixes #245
2024-04-20 13:02:23 +02:00
469c3bf70b Ignore unknown nodes when loading in saves 2024-04-16 11:35:19 +02:00
f75700ed76 Make //clearobjects remove unknown objects (#244) 2024-03-30 00:17:51 +01:00
575bfca67a Run tests on older MT too 2024-03-25 18:01:14 +01:00
eac05e3133 Add tooltip to WorldEdit GUI button (#240) 2024-03-25 17:58:35 +01:00
41efbaf210 Migrate tests to new Docker image 2024-03-25 16:48:09 +01:00
045c7510bf Fix the link to the minetest forums 2024-03-01 11:45:54 +01:00
b90eeb1e68 Fix incorrect translation syntax
Worldedit was causing a crash when I used the "inspector" feature.
2023-12-13 17:34:09 +01:00
41 changed files with 2948 additions and 2393 deletions

View File

@ -6,7 +6,7 @@ jobs:
name: "Luacheck" name: "Luacheck"
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: apt - name: apt
run: sudo apt-get install -y luarocks run: sudo apt-get install -y luarocks
- name: luacheck install - name: luacheck install

View File

@ -3,9 +3,26 @@ on: [push, pull_request]
jobs: jobs:
test: test:
name: "Unittests" name: "Unit Tests ${{ matrix.cfg.image }}"
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
matrix:
cfg:
- { image: 'registry.gitlab.com/minetest/minetest/server:5.0.1', mtg: false }
- { image: 'registry.gitlab.com/minetest/minetest/server:5.5.1', mtg: false }
- { image: 'ghcr.io/minetest/minetest:5.10.0', mtg: true }
- { image: 'ghcr.io/luanti-org/luanti:master', mtg: true } # latest git
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: 'luanti-org/minetest_game'
path: minetest_game
if: ${{ matrix.cfg.mtg }}
- name: Run tests - name: Run tests
run: MINETEST_VER=latest ./.util/run_tests.sh run: ./.util/run_tests.sh --docker
env:
DOCKER_IMAGE: "${{ matrix.cfg.image }}"

View File

@ -1,7 +1,28 @@
read_globals = {"minetest", "vector", "VoxelArea", "ItemStack", read_globals = {
"table", "minetest", "VoxelArea", "ItemStack",
"unified_inventory", "sfinv", "smart_inventory", "inventory_plus" "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"} globals = {"worldedit"}
-- Ignore these errors until someone decides to fix them -- Ignore these errors until someone decides to fix them
ignore = {"212", "213", "411", "412", "421", "422", "431", "432", "631"} 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"},
}

View File

@ -1,29 +1,41 @@
#!/bin/bash #!/bin/bash
tempdir=/tmp/mt tempdir=$(mktemp -d)
confpath=$tempdir/minetest.conf confpath=$tempdir/minetest.conf
worldpath=$tempdir/world worldpath=$tempdir/world
trap 'rm -rf "$tempdir"' EXIT
use_docker=y [ -f worldedit/mod.conf ] || { echo "Must be run in modpack root folder." >&2; exit 1; }
[ -x ../../bin/minetestserver ] && use_docker=
rm -rf $tempdir mtserver=
mkdir -p $worldpath if [ "$1" == "--docker" ]; then
# the docker image doesn't have devtest command -v docker >/dev/null || { echo "Docker is not installed." >&2; exit 1; }
[ -n "$use_docker" ] || printf '%s\n' gameid=devtest >$worldpath/world.mt [ -d minetest_game ] || echo "A source checkout of minetest_game was not found. This can fail if your docker image does not ship a game." >&2;
else
mtserver=$(command -v luantiserver)
[[ -z "$mtserver" && -x ../../bin/luantiserver ]] && mtserver=../../bin/luantiserver
[ -z "$mtserver" ] && { echo "To run the test outside of Docker, an installation of luantiserver is required." >&2; exit 1; }
fi
mkdir $worldpath
printf '%s\n' mg_name=singlenode '[end_of_params]' >$worldpath/map_meta.txt printf '%s\n' mg_name=singlenode '[end_of_params]' >$worldpath/map_meta.txt
printf '%s\n' worldedit_run_tests=true max_forceloaded_blocks=9999 >$confpath printf '%s\n' worldedit_run_tests=true max_forceloaded_blocks=9999 >$confpath
if [ -n "$use_docker" ]; then if [ -z "$mtserver" ]; then
chmod -R 777 $tempdir chmod -R 777 $tempdir
docker run --rm -i \ [ -n "$DOCKER_IMAGE" ] || { echo "Missing DOCKER_IMAGE env variable" >&2; exit 1; }
-v $confpath:/etc/minetest/minetest.conf \ vol=(
-v $tempdir:/var/lib/minetest/.minetest \ -v "$confpath":/etc/minetest/minetest.conf
-v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit \ -v "$tempdir":/var/lib/minetest/.minetest
registry.gitlab.com/minetest/minetest/server:${MINETEST_VER} -v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit
)
[ -d minetest_game ] && vol+=(
-v "$PWD/minetest_game":/var/lib/minetest/.minetest/games/minetest_game
)
docker run --rm -i "${vol[@]}" "$DOCKER_IMAGE"
else else
mkdir $worldpath/worldmods mkdir $worldpath/worldmods
ln -s "$PWD/worldedit" $worldpath/worldmods/worldedit ln -s "$PWD/worldedit" $worldpath/worldmods/worldedit
../../bin/minetestserver --config $confpath --world $worldpath --logfile /dev/null $mtserver --config "$confpath" --world "$worldpath" --logfile /dev/null
fi fi
test -f $worldpath/tests_ok || exit 1 test -f $worldpath/tests_ok || exit 1

View File

@ -409,14 +409,15 @@ Load nodes from "(world folder)/schems/`<file>`.we" with position 1 of the curre
### `//lua <code>` ### `//lua <code>`
Executes `<code>` as a Lua chunk in the global namespace. Executes `<code>` as a Lua chunk in the global namespace with the variables `name`, `player` and `pos` (= player position) available.
//lua worldedit.pos1["singleplayer"] = {x=0, y=0, z=0} //lua worldedit.pos1[name] = vector.new(0, 0, 0)
//lua worldedit.rotate(worldedit.pos1["singleplayer"], worldedit.pos2["singleplayer"], "y", 90) //lua worldedit.rotate(worldedit.pos1["jones"], worldedit.pos2["jones"], "y", 90)
//lua player:set_pos(worldedit.pos2[name])
### `//luatransform <code>` ### `//luatransform <code>`
Executes `<code>` as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region. Executes `<code>` as a Lua chunk in the global namespace with the variable `pos` available, for each node in the current WorldEdit region.
//luatransform minetest.swap_node(pos, {name="default:stone"}) //luatransform minetest.swap_node(pos, {name="default:stone"})
//luatransform if minetest.get_node(pos).name == "air" then minetest.add_node(pos, {name="default:water_source"}) end //luatransform if minetest.get_node(pos).name == "air" then minetest.add_node(pos, {name="default:water_source"}) end
@ -428,11 +429,12 @@ Save the current WorldEdit region using the Minetest Schematic format to "(world
//mtschemcreate some random filename //mtschemcreate some random filename
//mtschemcreate huge_base //mtschemcreate huge_base
### `//mtschemplace <file>` ### `//mtschemplace <file> [rotation]`
Load nodes from "(world folder)/schems/`<file>`.mts" with position 1 of the current WorldEdit region as the origin. Load nodes from "(world folder)/schems/`<file>`.mts" with position 1 of the current WorldEdit region as the origin.
Valid values for `[rotation]` are 0, 90, 180 and 270.
//mtschemplace some random filename //mtschemplace a_tree 270
//mtschemplace huge_base //mtschemplace huge_base
### `//mtschemprob start/finish/get` ### `//mtschemprob start/finish/get`

View File

@ -2,7 +2,7 @@ WorldEdit v1.3
============== ==============
The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more. The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more.
For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) at the Minetest forums. For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?t=572) at the Minetest forums.
# New users should see the [tutorial](Tutorial.md). # New users should see the [tutorial](Tutorial.md).

View File

@ -227,11 +227,19 @@ Code
---- ----
Contained in code.lua, this module allows arbitrary Lua code to be used with WorldEdit. Contained in code.lua, this module allows arbitrary Lua code to be used with WorldEdit.
### error = worldedit.lua(code) ### error = worldedit.lua(code, name)
Executes `code` as a Lua chunk in the global namespace. the given code gets encapsulated into a function with parameters `name`, `player`, `pos`
where
* `name` is a playername or `nil`
* `player` is the player object of the above player if applicable, otherwise `nil`
* `pos` is the position of the aforementioned player (if applicable, otherwise `nil`) rounded to integers
Returns an error if the code fails or nil otherwise. the resulting function is then executed as a Lua chunk in the global namespace.
The return is
* a string in case of an error
* a tuple of `nil` and return of the function converted to a string in case of success
### error = worldedit.luatransform(pos1, pos2, code) ### error = worldedit.luatransform(pos1, pos2, code)

4
settingtypes.txt Normal file
View 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

View File

@ -2,17 +2,29 @@
-- @module worldedit.code -- @module worldedit.code
--- Executes `code` as a Lua chunk in the global namespace. --- Executes `code` as a Lua chunk in the global namespace.
-- @return An error message if the code fails, or nil on success. -- the code will be encapsulated into a function with parameters
function worldedit.lua(code) -- * name (the name of the player issuing the //lua command)
local func, err = loadstring(code) -- * player (the player object of the player)
if not func then -- Syntax error -- * pos (the position of the player rounded to integers)
-- @return string in case of error, tuple of nil, return of code as string in case of success
function worldedit.lua(code, name)
local factory, err = loadstring("return function(name, player, pos)\n" .. code .. "\nend")
if not factory then -- Syntax error
return err return err
end end
local good, err = pcall(func) local func = factory()
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 if not good then -- Runtime error
return err return tostring(err)
end end
return nil return nil, dump(err)
end end
@ -39,7 +51,7 @@ function worldedit.luatransform(pos1, pos2, code)
while pos.z <= pos2.z do while pos.z <= pos2.z do
local good, err = pcall(func, pos) local good, err = pcall(func, pos)
if not good then -- Runtime error if not good then -- Runtime error
return err return tostring(err)
end end
pos.z = pos.z + 1 pos.z = pos.z + 1
end end

View File

@ -1,12 +1,21 @@
--- Common functions [INTERNAL]. All of these functions are internal! --- Common functions [INTERNAL]. All of these functions are internal!
-- @module worldedit.common -- @module worldedit.common
-- Polyfill for vector.copy (added in 5.5.0)
if not vector.copy then
local vnew = vector.new
vector.copy = function(v)
return vnew(v.x, v.y, v.z)
end
end
--- Copies and modifies positions `pos1` and `pos2` so that each component of --- Copies and modifies positions `pos1` and `pos2` so that each component of
-- `pos1` is less than or equal to the corresponding component of `pos2`. -- `pos1` is less than or equal to the corresponding component of `pos2`.
-- Returns the new positions. -- Returns the new positions.
function worldedit.sort_pos(pos1, pos2) function worldedit.sort_pos(pos1, pos2)
pos1 = vector.new(pos1.x, pos1.y, pos1.z) pos1 = vector.copy(pos1)
pos2 = vector.new(pos2.x, pos2.y, pos2.z) pos2 = vector.copy(pos2)
if pos1.x > pos2.x then if pos1.x > pos2.x then
pos2.x, pos1.x = pos1.x, pos2.x pos2.x, pos1.x = pos1.x, pos2.x
end end
@ -45,13 +54,23 @@ function worldedit.get_axis_others(axis)
end end
function worldedit.keep_loaded(pos1, pos2) -- Create a vmanip and read the area from map, this causes all
-- Create a vmanip and read the area from map, this -- MapBlocks to be loaded into memory synchronously.
-- causes all MapBlocks to be loaded into memory.
-- This doesn't actually *keep* them loaded, unlike the name implies. -- 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() local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2) manip:read_from_map(pos1, pos2)
end end
end
local mh = {} local mh = {}
@ -65,7 +84,7 @@ function mh.get_empty_data(area)
-- only partially modified aren't overwriten. -- only partially modified aren't overwriten.
local data = {} local data = {}
local c_ignore = minetest.get_content_id("ignore") local c_ignore = minetest.get_content_id("ignore")
for i = 1, worldedit.volume(area.MinEdge, area.MaxEdge) do for i = 1, area:getVolume() do
data[i] = c_ignore data[i] = c_ignore
end end
return data return data
@ -114,6 +133,77 @@ function mh.finish(manip, data)
manip:set_data(data) manip:set_data(data)
end end
manip:write_to_map() manip:write_to_map()
manip:update_map() if manip.close ~= nil then
manip:close() -- explicitly free memory
end
end end
-- returns an iterator that returns voxelarea indices for a hollow cuboid
function mh.iter_hollowcuboid(area, minx, miny, minz, maxx, maxy, maxz)
local i = area:index(minx, miny, minz) - 1
local xrange = maxx - minx + 1
local nextaction = i + 1 + xrange
local do_hole = false
local y = 0
local ydiff = maxy - miny
local ystride = area.ystride
local ymultistride = ydiff * ystride
local z = 0
local zdiff = maxz - minz
local zstride = area.zstride
local zcorner = true
return function()
-- continue i until it needs to jump ystride
i = i + 1
if i ~= nextaction then
return i
end
-- add the x offset if y (and z) are not 0 or maxy (or maxz)
if do_hole then
do_hole = false
i = i + xrange - 2
nextaction = i + 1
return i
end
-- continue y until maxy is exceeded
y = y+1
if y ~= ydiff + 1 then
i = i + ystride - xrange
if zcorner
or y == ydiff then
nextaction = i + xrange
else
nextaction = i + 1
do_hole = true
end
return i
end
-- continue z until maxz is exceeded
z = z+1
if z == zdiff + 1 then
-- hollowcuboid finished, return nil
return
end
-- set i to index(minx, miny, minz + z) - 1
i = i + zstride - (ymultistride + xrange)
zcorner = z == zdiff
-- y is 0, so traverse the xs
y = 0
nextaction = i + xrange
return i
end
end
function mh.iterp_hollowcuboid(area, minp, maxp)
return mh.iter_hollowcuboid(area, minp.x, minp.y, minp.z,
maxp.x, maxp.y, maxp.z)
end

View File

@ -64,6 +64,8 @@ worldedit.cuboid_shift = function(name, axis, amount)
return false, "undefined cuboid" return false, "undefined cuboid"
end end
assert(not rawequal(pos1, pos2)) -- vectors must not alias
if axis == 'x' then if axis == 'x' then
worldedit.pos1[name].x = pos1.x + amount worldedit.pos1[name].x = pos1.x + amount
worldedit.pos2[name].x = pos2.x + amount worldedit.pos2[name].x = pos2.x + amount
@ -134,7 +136,8 @@ end
-- Return the marker that is closest to the player -- Return the marker that is closest to the player
worldedit.marker_get_closest_to_player = function(name) worldedit.marker_get_closest_to_player = function(name)
local playerpos = minetest.get_player_by_name(name):get_pos() local player = assert(minetest.get_player_by_name(name))
local playerpos = player:get_pos()
local dist1 = vector.distance(playerpos, worldedit.pos1[name]) local dist1 = vector.distance(playerpos, worldedit.pos1[name])
local dist2 = vector.distance(playerpos, worldedit.pos2[name]) local dist2 = vector.distance(playerpos, worldedit.pos2[name])

View File

@ -27,6 +27,7 @@ end
dofile(path .. "/common.lua") dofile(path .. "/common.lua")
load_module(path .. "/manipulations.lua") load_module(path .. "/manipulations.lua")
load_module(path .. "/primitives.lua") load_module(path .. "/primitives.lua")
load_module(path .. "/transformations.lua")
load_module(path .. "/visualization.lua") load_module(path .. "/visualization.lua")
load_module(path .. "/serialization.lua") load_module(path .. "/serialization.lua")
load_module(path .. "/code.lua") load_module(path .. "/code.lua")
@ -39,6 +40,6 @@ if minetest.settings:get_bool("log_mods") then
end end
if minetest.settings:get_bool("worldedit_run_tests") then if minetest.settings:get_bool("worldedit_run_tests") then
dofile(path .. "/test.lua") dofile(path .. "/test/init.lua")
minetest.after(0, worldedit.run_tests) minetest.after(0, worldedit.run_tests)
end end

View File

@ -56,8 +56,7 @@ function worldedit.set_param2(pos1, pos2, param2)
-- Update map -- Update map
manip:set_param2_data(param2_data) manip:set_param2_data(param2_data)
manip:write_to_map() mh.finish(manip)
manip:update_map()
return worldedit.volume(pos1, pos2) return worldedit.volume(pos1, pos2)
end end
@ -98,51 +97,6 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
end end
local function deferred_execution(next_one, finished)
-- Allocate 100% of server step for execution (might lag a little)
local allocated_usecs =
tonumber(minetest.settings:get("dedicated_server_step"):split(" ")[1]) * 1000000
local function f()
local deadline = minetest.get_us_time() + allocated_usecs
repeat
local is_done = next_one()
if is_done then
if finished then
finished()
end
return
end
until minetest.get_us_time() >= deadline
minetest.after(0, f)
end
f()
end
--- Duplicates a region `amount` times with offset vector `direction`.
-- Stacking is spread across server steps.
-- @return The number of nodes stacked.
function worldedit.stack2(pos1, pos2, direction, amount, finished)
-- Protect arguments from external changes during execution
pos1 = table.copy(pos1)
pos2 = table.copy(pos2)
direction = table.copy(direction)
local i = 0
local translated = vector.new()
local function step()
translated.x = translated.x + direction.x
translated.y = translated.y + direction.y
translated.z = translated.z + direction.z
worldedit.copy2(pos1, pos2, translated)
i = i + 1
return i >= amount
end
deferred_execution(step, finished)
return worldedit.volume(pos1, pos2) * amount
end
--- Copies a region along `axis` by `amount` nodes. --- Copies a region along `axis` by `amount` nodes.
-- @param pos1 -- @param pos1
-- @param pos2 -- @param pos2
@ -307,316 +261,6 @@ function worldedit.move(pos1, pos2, axis, amount)
return worldedit.volume(pos1, pos2) return worldedit.volume(pos1, pos2)
end end
--- Duplicates a region along `axis` `amount` times.
-- Stacking is spread across server steps.
-- @param pos1
-- @param pos2
-- @param axis Axis direction, "x", "y", or "z".
-- @param count
-- @return The number of nodes stacked.
function worldedit.stack(pos1, pos2, axis, count, finished)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local length = pos2[axis] - pos1[axis] + 1
if count < 0 then
count = -count
length = -length
end
local i, distance = 0, 0
local function step()
distance = distance + length
worldedit.copy(pos1, pos2, axis, distance)
i = i + 1
return i >= count
end
deferred_execution(step, finished)
return worldedit.volume(pos1, pos2) * count
end
--- Stretches a region by a factor of positive integers along the X, Y, and Z
-- axes, respectively, with `pos1` as the origin.
-- @param pos1
-- @param pos2
-- @param stretch_x Amount to stretch along X axis.
-- @param stretch_y Amount to stretch along Y axis.
-- @param stretch_z Amount to stretch along Z axis.
-- @return The number of nodes scaled.
-- @return The new scaled position 1.
-- @return The new scaled position 2.
function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
-- Prepare schematic of large node
local get_node, get_meta, place_schematic = minetest.get_node,
minetest.get_meta, minetest.place_schematic
local placeholder_node = {name="", param1=255, param2=0}
local nodes = {}
for i = 1, stretch_x * stretch_y * stretch_z do
nodes[i] = placeholder_node
end
local schematic = {size=vector.new(stretch_x, stretch_y, stretch_z), data=nodes}
local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1
local new_pos2 = {
x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,
y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,
z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,
}
worldedit.keep_loaded(pos1, new_pos2)
local pos = vector.new(pos2.x, 0, 0)
local big_pos = vector.new()
while pos.x >= pos1.x do
pos.y = pos2.y
while pos.y >= pos1.y do
pos.z = pos2.z
while pos.z >= pos1.z do
local node = get_node(pos) -- Get current node
local meta = get_meta(pos):to_table() -- Get meta of current node
-- Calculate far corner of the big node
local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x
local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y
local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z
-- Create large node
placeholder_node.name = node.name
placeholder_node.param2 = node.param2
big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z
place_schematic(big_pos, schematic)
-- Fill in large node meta
if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
-- Node has meta fields
for x = 0, size_x do
for y = 0, size_y do
for z = 0, size_z do
big_pos.x = pos_x + x
big_pos.y = pos_y + y
big_pos.z = pos_z + z
-- Set metadata of new node
get_meta(big_pos):from_table(meta)
end
end
end
end
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2
end
--- Transposes a region between two axes.
-- @return The number of nodes transposed.
-- @return The new transposed position 1.
-- @return The new transposed position 2.
function worldedit.transpose(pos1, pos2, axis1, axis2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local compare
local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]
if extent1 > extent2 then
compare = function(extent1, extent2)
return extent1 > extent2
end
else
compare = function(extent1, extent2)
return extent1 < extent2
end
end
-- Calculate the new position 2 after transposition
local new_pos2 = vector.new(pos2)
new_pos2[axis1] = pos1[axis1] + extent2
new_pos2[axis2] = pos1[axis2] + extent1
local upper_bound = vector.new(pos2)
if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end
if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end
worldedit.keep_loaded(pos1, upper_bound)
local pos = vector.new(pos1.x, 0, 0)
local get_node, get_meta, set_node = minetest.get_node,
minetest.get_meta, minetest.set_node
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
if compare(extent1, extent2) then -- Transpose only if below the diagonal
local node1 = get_node(pos)
local meta1 = get_meta(pos):to_table()
local value1, value2 = pos[axis1], pos[axis2] -- Save position values
pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents
local node2 = get_node(pos)
local meta2 = get_meta(pos):to_table()
set_node(pos, node1)
get_meta(pos):from_table(meta1)
pos[axis1], pos[axis2] = value1, value2 -- Restore position values
set_node(pos, node2)
get_meta(pos):from_table(meta2)
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return worldedit.volume(pos1, pos2), pos1, new_pos2
end
--- Flips a region along `axis`.
-- @return The number of nodes flipped.
function worldedit.flip(pos1, pos2, axis)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
--- TODO: Flip the region slice by slice along the flip axis using schematic method.
local pos = vector.new(pos1.x, 0, 0)
local start = pos1[axis] + pos2[axis]
pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
local get_node, get_meta, set_node = minetest.get_node,
minetest.get_meta, minetest.set_node
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local node1 = get_node(pos)
local meta1 = get_meta(pos):to_table()
local value = pos[axis] -- Save position
pos[axis] = start - value -- Shift position
local node2 = get_node(pos)
local meta2 = get_meta(pos):to_table()
set_node(pos, node1)
get_meta(pos):from_table(meta1)
pos[axis] = value -- Restore position
set_node(pos, node2)
get_meta(pos):from_table(meta2)
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return worldedit.volume(pos1, pos2)
end
--- Rotates a region clockwise around an axis.
-- @param pos1
-- @param pos2
-- @param axis Axis ("x", "y", or "z").
-- @param angle Angle in degrees (90 degree increments only).
-- @return The number of nodes rotated.
-- @return The new first position.
-- @return The new second position.
function worldedit.rotate(pos1, pos2, axis, angle)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local other1, other2 = worldedit.get_axis_others(axis)
angle = angle % 360
local count
if angle == 90 then
worldedit.flip(pos1, pos2, other1)
count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
elseif angle == 180 then
worldedit.flip(pos1, pos2, other1)
count = worldedit.flip(pos1, pos2, other2)
elseif angle == 270 then
worldedit.flip(pos1, pos2, other2)
count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
else
error("Only 90 degree increments are supported!")
end
return count, pos1, pos2
end
--- Rotates all oriented nodes in a region clockwise around the Y axis.
-- @param pos1
-- @param pos2
-- @param angle Angle in degrees (90 degree increments only).
-- @return The number of nodes oriented.
function worldedit.orient(pos1, pos2, angle)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local registered_nodes = minetest.registered_nodes
local wallmounted = {
[90] = {0, 1, 5, 4, 2, 3, 0, 0},
[180] = {0, 1, 3, 2, 5, 4, 0, 0},
[270] = {0, 1, 4, 5, 3, 2, 0, 0}
}
local facedir = {
[90] = { 1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16,
9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22},
[180] = { 2, 3, 0, 1, 10, 11, 8, 9, 6, 7, 4, 5,
18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21},
[270] = { 3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14,
7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20}
}
angle = angle % 360
if angle == 0 then
return 0
end
if angle % 90 ~= 0 then
error("Only 90 degree increments are supported!")
end
local wallmounted_substitution = wallmounted[angle]
local facedir_substitution = facedir[angle]
worldedit.keep_loaded(pos1, pos2)
local count = 0
local get_node, swap_node = minetest.get_node, minetest.swap_node
local pos = vector.new(pos1.x, 0, 0)
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local node = get_node(pos)
local def = registered_nodes[node.name]
if def then
local paramtype2 = def.paramtype2
if paramtype2 == "wallmounted" or
paramtype2 == "colorwallmounted" then
local orient = node.param2 % 8
node.param2 = node.param2 - orient +
wallmounted_substitution[orient + 1]
swap_node(pos, node)
count = count + 1
elseif paramtype2 == "facedir" or
paramtype2 == "colorfacedir" then
local orient = node.param2 % 32
node.param2 = node.param2 - orient +
facedir_substitution[orient + 1]
swap_node(pos, node)
count = count + 1
end
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return count
end
--- Attempts to fix the lighting in a region. --- Attempts to fix the lighting in a region.
-- @return The number of nodes updated. -- @return The number of nodes updated.
@ -624,8 +268,10 @@ function worldedit.fixlight(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local vmanip = minetest.get_voxel_manip(pos1, pos2) local vmanip = minetest.get_voxel_manip(pos1, pos2)
vmanip:write_to_map() vmanip:write_to_map() -- this updates the lighting
vmanip:update_map() -- this updates the lighting if vmanip.close ~= nil then
vmanip:close()
end
return worldedit.volume(pos1, pos2) return worldedit.volume(pos1, pos2)
end end
@ -644,7 +290,7 @@ function worldedit.clear_objects(pos1, pos2)
return false return false
end end
local entity = obj:get_luaentity() local entity = obj:get_luaentity()
return not entity or not entity.name:find("^worldedit:") return not (entity and entity.name:find("^worldedit:"))
end end
-- Offset positions to include full nodes (positions are in the center of nodes) -- Offset positions to include full nodes (positions are in the center of nodes)
@ -691,4 +337,3 @@ function worldedit.clear_objects(pos1, pos2)
end end
return count return count
end end

View File

@ -14,32 +14,27 @@ local mh = worldedit.manip_helpers
-- @return The number of nodes added. -- @return The number of nodes added.
function worldedit.cube(pos, width, height, length, node_name, hollow) function worldedit.cube(pos, width, height, length, node_name, hollow)
-- Set up voxel manipulator -- Set up voxel manipulator
local basepos = vector.subtract(pos, {x=math.floor(width/2), y=0, z=math.floor(length/2)}) local basepos = vector.subtract(pos,
local manip, area = mh.init(basepos, vector.add(basepos, {x=width, y=height, z=length})) vector.new(math.floor(width / 2), 0, math.floor(length / 2)))
local endpos = vector.add(basepos,
vector.new(width - 1, height - 1, length - 1))
local manip, area = mh.init(basepos, endpos)
local data = mh.get_empty_data(area) local data = mh.get_empty_data(area)
-- Add cube -- Add cube
local node_id = minetest.get_content_id(node_name) local node_id = minetest.get_content_id(node_name)
local stride = vector.new(1, area.ystride, area.zstride)
local offset = vector.subtract(basepos, area.MinEdge)
local count = 0 local count = 0
local iterfunc
if hollow then
iterfunc = mh.iterp_hollowcuboid(area, basepos, endpos)
else
iterfunc = area:iterp(basepos, endpos)
end
for z = 0, length-1 do for vi in iterfunc do
local index_z = (offset.z + z) * stride.z + 1 -- +1 for 1-based indexing data[vi] = node_id
for y = 0, height-1 do
local index_y = index_z + (offset.y + y) * stride.y
for x = 0, width-1 do
local is_wall = z == 0 or z == length-1
or y == 0 or y == height-1
or x == 0 or x == width-1
if not hollow or is_wall then
local i = index_y + (offset.x + x)
data[i] = node_id
count = count + 1 count = count + 1
end end
end
end
end
mh.finish(manip, data) mh.finish(manip, data)
return count return count
@ -163,11 +158,7 @@ function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, holl
-- Add desired shape (anything inbetween cylinder & cone) -- Add desired shape (anything inbetween cylinder & cone)
local node_id = minetest.get_content_id(node_name) local node_id = minetest.get_content_id(node_name)
local stride = vector.new(1, area.ystride, area.zstride) local stride = vector.new(1, area.ystride, area.zstride)
local offset = { local offset = vector.subtract(current_pos, area.MinEdge)
x = current_pos.x - area.MinEdge.x,
y = current_pos.y - area.MinEdge.y,
z = current_pos.z - area.MinEdge.z,
}
local count = 0 local count = 0
for i = 0, length - 1 do for i = 0, length - 1 do
-- Calulate radius for this "height" in the cylinder -- Calulate radius for this "height" in the cylinder
@ -226,11 +217,7 @@ function worldedit.pyramid(pos, axis, height, node_name, hollow)
-- Add pyramid -- Add pyramid
local node_id = minetest.get_content_id(node_name) local node_id = minetest.get_content_id(node_name)
local stride = vector.new(1, area.ystride, area.zstride) local stride = vector.new(1, area.ystride, area.zstride)
local offset = { local offset = vector.subtract(pos, area.MinEdge)
x = pos.x - area.MinEdge.x,
y = pos.y - area.MinEdge.y,
z = pos.z - area.MinEdge.z,
}
local size = math.abs(height * step) local size = math.abs(height * step)
local count = 0 local count = 0
-- For each level of the pyramid -- For each level of the pyramid
@ -272,8 +259,8 @@ function worldedit.spiral(pos, length, height, spacer, node_name)
-- Set up variables -- Set up variables
local node_id = minetest.get_content_id(node_name) local node_id = minetest.get_content_id(node_name)
local stride = vector.new(1, area.ystride, area.zstride) local stride = vector.new(1, area.ystride, area.zstride)
local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z local offset = vector.subtract(pos, area.MinEdge)
local i = offset_z * stride.z + offset_y * stride.y + offset_x + 1 local i = offset.z * stride.z + offset.y * stride.y + offset.x + 1
-- Add first column -- Add first column
local count = height local count = height

View File

@ -251,7 +251,9 @@ function worldedit.deserialize(origin_pos, value)
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
local add_node, get_meta = minetest.add_node, minetest.get_meta local add_node, get_meta = minetest.add_node, minetest.get_meta
local registered_nodes = minetest.registered_nodes
for i, entry in ipairs(nodes) do for i, entry in ipairs(nodes) do
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.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
-- Entry acts as both position and node -- Entry acts as both position and node
add_node(entry, entry) add_node(entry, entry)
@ -259,6 +261,7 @@ function worldedit.deserialize(origin_pos, value)
get_meta(entry):from_table(entry.meta) get_meta(entry):from_table(entry.meta)
end end
end end
end
return #nodes return #nodes
end end

View File

@ -1,7 +1,8 @@
-- TODO: don't shit individual variables into the globals
--------------------- ---------------------
-- Helpers -- Helpers
--------------------- ---------------------
local vec = vector.new local vec = vector.new
local vecw = function(axis, n, base) local vecw = function(axis, n, base)
local ret = vec(base) local ret = vec(base)
@ -16,9 +17,9 @@ local set_node = minetest.set_node
-- Nodes -- Nodes
--------------------- ---------------------
local air = "air" local air = "air"
local testnode1 rawset(_G, "testnode1", "")
local testnode2 rawset(_G, "testnode2", "")
local testnode3 rawset(_G, "testnode3", "")
-- Loads nodenames to use for tests -- Loads nodenames to use for tests
local function init_nodes() local function init_nodes()
testnode1 = minetest.registered_aliases["mapgen_stone"] testnode1 = minetest.registered_aliases["mapgen_stone"]
@ -27,7 +28,7 @@ local function init_nodes()
assert(testnode1 and testnode2 and testnode3) assert(testnode1 and testnode2 and testnode3)
end end
-- Writes repeating pattern into given area -- Writes repeating pattern into given area
local function place_pattern(pos1, pos2, pattern) rawset(_G, "place_pattern", function(pos1, pos2, pattern)
local pos = vec() local pos = vec()
local node = {name=""} local node = {name=""}
local i = 1 local i = 1
@ -43,14 +44,14 @@ local function place_pattern(pos1, pos2, pattern)
end end
end end
end end
end end)
--------------------- ---------------------
-- Area management -- Area management
--------------------- ---------------------
assert(minetest.get_mapgen_setting("mg_name") == "singlenode") assert(minetest.get_mapgen_setting("mg_name") == "singlenode")
local area = {} rawset(_G, "area", {})
do do
local areamin, areamax local areamin, areamax
local off local off
@ -71,6 +72,9 @@ do
end end
-- Reset area contents and state -- Reset area contents and state
area.clear = function() area.clear = function()
if off and vector.equals(off, vec(0, 0, 0)) then
return
end
local vmanip = minetest.get_voxel_manip(areamin, areamax) local vmanip = minetest.get_voxel_manip(areamin, areamax)
local vpos1, vpos2 = vmanip:get_emerged_area() local vpos1, vpos2 = vmanip:get_emerged_area()
local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1) local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1)
@ -88,9 +92,9 @@ do
area.get = function(sizex, sizey, sizez) area.get = function(sizex, sizey, sizez)
local size local size
if sizey == nil and sizez == nil then if sizey == nil and sizez == nil then
size = vector.new(sizex, sizex, sizex) size = vec(sizex, sizex, sizex)
else else
size = vector.new(sizex, sizey, sizez) size = vec(sizex, sizey, sizez)
end end
local pos1 = vector.add(areamin, off) local pos1 = vector.add(areamin, off)
local pos2 = vector.subtract(vector.add(pos1, size), 1) local pos2 = vector.subtract(vector.add(pos1, size), 1)
@ -148,7 +152,7 @@ end
--------------------- ---------------------
-- Checks -- Checks
--------------------- ---------------------
local check = {} rawset(_G, "check", {})
-- Check that all nodes in [pos1, pos2] are the node(s) specified -- Check that all nodes in [pos1, pos2] are the node(s) specified
check.filled = function(pos1, pos2, nodes) check.filled = function(pos1, pos2, nodes)
if type(nodes) == "string" then if type(nodes) == "string" then
@ -215,7 +219,7 @@ end
-- The actual tests -- The actual tests
--------------------- ---------------------
local tests = {} local tests = {}
local function register_test(name, func, opts) worldedit.register_test = function(name, func, opts)
assert(type(name) == "string") assert(type(name) == "string")
assert(func == nil or type(func) == "function") assert(func == nil or type(func) == "function")
if not opts then if not opts then
@ -227,6 +231,7 @@ local function register_test(name, func, opts)
opts.func = func opts.func = func
table.insert(tests, opts) table.insert(tests, opts)
end end
local register_test = worldedit.register_test
-- How this works: -- How this works:
-- register_test registers a test with a name and function -- register_test registers a test with a name and function
-- The function should return if the test passes or otherwise cause a Lua error -- The function should return if the test passes or otherwise cause a Lua error
@ -241,8 +246,8 @@ end
register_test("Internal self-test") register_test("Internal self-test")
register_test("is area loaded?", function() register_test("is area loaded?", function()
local pos1, _ = area.get(1) local pos1, _ = area.get(1)
assert(get_node(pos1).name == "air") assert(get_node(pos1).name == air)
end, {dry=true}) end)
register_test("area.split", function() register_test("area.split", function()
for i = 2, 6 do for i = 2, 6 do
@ -255,7 +260,7 @@ register_test("area.split", function()
assert((half1.z - pos1.z) == (pos2.z - half2.z)) -- divided equally assert((half1.z - pos1.z) == (pos2.z - half2.z)) -- divided equally
end end
end end
end, {dry=true}) end)
register_test("check.filled", function() register_test("check.filled", function()
local pos1, pos2 = area.get(1, 2, 1) local pos1, pos2 = area.get(1, 2, 1)
@ -276,271 +281,48 @@ register_test("pattern", function()
end) end)
register_test("Generic node manipulations") for _, name in ipairs({
register_test("worldedit.set", function() "manipulations", "primitives", "schematic"
local pos1, pos2 = area.get(10) }) do
local m = area.margin(1) dofile(minetest.get_modpath("worldedit") .. "/test/" .. name .. ".lua")
worldedit.set(pos1, pos2, testnode1)
check.filled(pos1, pos2, testnode1)
check.filled2(m, air)
end)
register_test("worldedit.set mix", function()
local pos1, pos2 = area.get(10)
local m = area.margin(1)
worldedit.set(pos1, pos2, {testnode1, testnode2})
check.filled(pos1, pos2, {testnode1, testnode2})
check.filled2(m, air)
end)
register_test("worldedit.replace", function()
local pos1, pos2 = area.get(10)
local half1, half2 = area.split(pos1, pos2)
worldedit.set(pos1, half1, testnode1)
worldedit.set(half2, pos2, testnode2)
worldedit.replace(pos1, pos2, testnode1, testnode3)
check.not_filled(pos1, pos2, testnode1)
check.filled(pos1, half1, testnode3)
check.filled(half2, pos2, testnode2)
end)
register_test("worldedit.replace inverse", function()
local pos1, pos2 = area.get(10)
local half1, half2 = area.split(pos1, pos2)
worldedit.set(pos1, half1, testnode1)
worldedit.set(half2, pos2, testnode2)
worldedit.replace(pos1, pos2, testnode1, testnode3, true)
check.filled(pos1, half1, testnode1)
check.filled(half2, pos2, testnode3)
end)
-- FIXME?: this one looks overcomplicated
register_test("worldedit.copy", function()
local pos1, pos2 = area.get(4)
local axis, n = area.dir(2)
local m = area.margin(1)
local b = pos1[axis]
-- create one slice with testnode1, one with testnode2
worldedit.set(pos1, vecw(axis, b + 1, pos2), testnode1)
worldedit.set(vecw(axis, b + 2, pos1), pos2, testnode2)
worldedit.copy(pos1, pos2, axis, n)
-- should have three slices now
check.filled(pos1, vecw(axis, b + 1, pos2), testnode1)
check.filled(vecw(axis, b + 2, pos1), pos2, testnode1)
check.filled(vecw(axis, b + 4, pos1), vector.add(pos2, vecw(axis, n)), testnode2)
check.filled2(m, "air")
end)
register_test("worldedit.copy2", function()
local pos1, pos2 = area.get(6)
local m1 = area.margin(1)
local pos1_, pos2_ = area.get(6)
local m2 = area.margin(1)
local pattern = {testnode1, testnode2, testnode3, testnode1, testnode2}
place_pattern(pos1, pos2, pattern)
worldedit.copy2(pos1, pos2, vector.subtract(pos1_, pos1))
check.pattern(pos1, pos2, pattern)
check.pattern(pos1_, pos2_, pattern)
check.filled2(m1, "air")
check.filled2(m2, "air")
end)
register_test("worldedit.move (overlap)", function()
local pos1, pos2 = area.get(7)
local axis, n = area.dir(2)
local m = area.margin(1)
local pattern = {testnode2, testnode1, testnode2, testnode3, testnode3}
place_pattern(pos1, pos2, pattern)
worldedit.move(pos1, pos2, axis, n)
check.filled(pos1, vecw(axis, pos1[axis] + n - 1, pos2), "air")
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
check.filled2(m, "air")
end)
register_test("worldedit.move", function()
local pos1, pos2 = area.get(10)
local axis, n = area.dir(10)
local m = area.margin(1)
local pattern = {testnode1, testnode3, testnode3, testnode2}
place_pattern(pos1, pos2, pattern)
worldedit.move(pos1, pos2, axis, n)
check.filled(pos1, pos2, "air")
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
check.filled2(m, "air")
end)
-- TODO: the rest (also testing param2 + metadata)
register_test("Schematics")
register_test("worldedit.read_header", function()
local value = '5,foo,BAR,-1,234:the content'
local version, header, content = worldedit.read_header(value)
assert(version == 5)
assert(#header == 4)
assert(header[1] == "foo" and header[2] == "BAR")
assert(header[3] == "-1" and header[4] == "234")
assert(content == "the content")
end)
register_test("worldedit.allocate", function()
local value = '3:-1 0 0 dummy 0 0\n0 0 4 dummy 0 0\n0 1 0 dummy 0 0'
local pos1, pos2, count = worldedit.allocate(vec(1, 1, 1), value)
assert(vector.equals(pos1, vec(0, 1, 1)))
assert(vector.equals(pos2, vec(1, 2, 5)))
assert(count == 3)
end)
do
local function output_weird(numbers, body)
local s = {"return {"}
for _, parts in ipairs(numbers) do
s[#s+1] = "{"
for _, n in ipairs(parts) do
s[#s+1] = string.format(" {%d},", n)
end 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
{ register_test("Code")
name = "v3", ver = 3, register_test("worldedit.lua", function()
gen = function(pat) -- syntax error
assert(pat[2] == "air") local err, ret = worldedit.lua("?")
return table.concat({ assert(ret == nil)
"0 0 0 " .. pat[1] .. " 0 0", assert(err:find("unexpected symbol"))
"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
},
{ -- runtime error
name = "v4", ver = 4, local err, ret = worldedit.lua("error(1234)")
gen = function(pat) assert(ret == nil)
return table.concat({ assert(err:find("1234"))
"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) -- normal operation
{ local err, ret = worldedit.lua("return name..tostring(player == nil)..tostring(pos == nil)", "nobody")
name = "v5 (pre-5.6)", ver = 5, assert(err == nil)
gen = function(pat) assert(ret == "\"nobodytruetrue\"")
return table.concat({ end)
"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 register_test("worldedit.luatransform", function()
{
name = "v5 (5.6)", ver = 5,
gen = function(pat)
return table.concat({
'5:r1="' .. pat[1] .. '";r2="param1";r3="' .. pat[3] .. '";return {'
.. fmt51:format(0, 0, 0, 1),
fmt51:format(0, 1, 0, 3),
fmt51:format(1, 1, 0, 1),
fmt51:format(1, 0, 1, 3),
fmt51:format(0, 1, 1, 1) .. "}",
}, ",")
end
},
-- small changes on engine side again
{
name = "v5 (post-5.7)", ver = 5,
gen = function(pat)
return table.concat({
'5:local _={};_[1]="' .. pat[1] .. '";_[3]="' .. pat[3] .. '";return {'
.. fmt52:format(0, 0, 0, 1),
fmt52:format(0, 1, 0, 3),
fmt52:format(1, 1, 0, 1),
fmt52:format(1, 0, 1, 3),
fmt52:format(0, 1, 1, 1) .. "}",
}, ",")
end
},
}
for _, e in ipairs(test_data) do
register_test("worldedit.deserialize " .. e.name, function()
local pos1, pos2 = area.get(2) local pos1, pos2 = area.get(2)
local m = area.margin(1)
local pat = {testnode3, "air", testnode2} -- syntax error
local value = e.gen(pat) local err = worldedit.luatransform(pos1, pos2, "?")
assert(type(value) == "string") assert(err:find("unexpected symbol"))
local version = worldedit.read_header(value) -- runtime error
assert(version == e.ver, "version: got " .. tostring(version) .. " expected " .. e.ver) local err = worldedit.luatransform(pos1, pos2, "error(2345)")
local count = worldedit.deserialize(pos1, value) assert(err:find("2345"))
assert(count ~= nil and count > 0)
check.pattern(pos1, pos2, pat) -- normal operation
check.filled2(m, "air") local err = worldedit.luatransform(pos1, pos2,
"minetest.swap_node(pos, {name=" .. ("%q"):format(testnode1) .. "})")
assert(err == nil)
check.filled(pos1, pos1, testnode1)
end) end)
end
end
--------------------- ---------------------
-- Main function -- Main function
@ -560,7 +342,7 @@ worldedit.run_tests = function()
for x = 0, math.floor(wanted.x/16) do for x = 0, math.floor(wanted.x/16) do
for y = 0, math.floor(wanted.y/16) do for y = 0, math.floor(wanted.y/16) do
for z = 0, math.floor(wanted.z/16) do for z = 0, math.floor(wanted.z/16) do
assert(minetest.forceload_block(vector.new(x*16, y*16, z*16), true)) assert(minetest.forceload_block(vec(x*16, y*16, z*16), true, -1))
end end
end end
end end
@ -572,9 +354,7 @@ worldedit.run_tests = function()
local s = "---- " .. test.name .. " " local s = "---- " .. test.name .. " "
print(s .. string.rep("-", 60 - #s)) print(s .. string.rep("-", 60 - #s))
else else
if not test.dry then
area.clear() area.clear()
end
local ok, err = pcall(test.func) local ok, err = pcall(test.func)
print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL")) print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL"))
if not ok then if not ok then

View 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)

View 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)

View 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

View 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

View File

@ -1,8 +1,4 @@
if minetest.raycast == nil then local S = minetest.get_translator("worldedit_brush")
error(
"worldedit_brush requires at least Minetest 5.0"
)
end
local BRUSH_MAX_DIST = 150 local BRUSH_MAX_DIST = 150
local brush_on_use = function(itemstack, placer) local brush_on_use = function(itemstack, placer)
@ -12,7 +8,8 @@ local brush_on_use = function(itemstack, placer)
local cmd = meta:get_string("command") local cmd = meta:get_string("command")
if cmd == "" then if cmd == "" then
worldedit.player_notify(name, worldedit.player_notify(name,
"This brush is not bound, use //brush to bind a command to it.") S("This brush is not bound, use @1 to bind a command to it.",
minetest.colorize("#00ffff", "//brush")), "info")
return false return false
end end
@ -22,7 +19,7 @@ local brush_on_use = function(itemstack, placer)
local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs) local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs)
if not has_privs then if not has_privs then
worldedit.player_notify(name, worldedit.player_notify(name,
"Missing privileges: " .. table.concat(missing_privs, ", ")) S("Missing privileges: @1", table.concat(missing_privs, ", ")), "error")
return false return false
end end
@ -32,7 +29,7 @@ local brush_on_use = function(itemstack, placer)
local ray = minetest.raycast(raybegin, rayend, false, true) local ray = minetest.raycast(raybegin, rayend, false, true)
local pointed_thing = ray:next() local pointed_thing = ray:next()
if pointed_thing == nil then if pointed_thing == nil then
worldedit.player_notify(name, "Too far away.") worldedit.player_notify(name, S("Too far away."), "error")
return false return false
end end
@ -41,17 +38,17 @@ local brush_on_use = function(itemstack, placer)
worldedit.pos2[name] = nil worldedit.pos2[name] = nil
worldedit.marker_update(name) worldedit.marker_update(name)
-- this isn't really clean...
local player_notify_old = worldedit.player_notify
worldedit.player_notify = function(name, msg)
if string.match(msg, "^%d") then return end -- discard "1234 nodes added."
return player_notify_old(name, msg)
end
assert(cmddef.require_pos < 2) assert(cmddef.require_pos < 2)
local parsed = {cmddef.parse(meta:get_string("params"))} local parsed = {cmddef.parse(meta:get_string("params"))}
if not table.remove(parsed, 1) then return false end -- shouldn't happen if not table.remove(parsed, 1) then return false end -- shouldn't happen
-- discard success messages
local player_notify_old = worldedit.player_notify
worldedit.player_notify = function(name, msg, typ)
if typ == "ok" then return end
return player_notify_old(name, msg, typ)
end
minetest.log("action", string.format("%s uses WorldEdit brush (//%s) at %s", minetest.log("action", string.format("%s uses WorldEdit brush (//%s) at %s",
name, cmd, minetest.pos_to_string(pointed_thing.under))) name, cmd, minetest.pos_to_string(pointed_thing.under)))
cmddef.func(name, unpack(parsed)) cmddef.func(name, unpack(parsed))
@ -61,7 +58,7 @@ local brush_on_use = function(itemstack, placer)
end end
minetest.register_tool(":worldedit:brush", { minetest.register_tool(":worldedit:brush", {
description = "WorldEdit Brush", description = S("WorldEdit Brush"),
inventory_image = "worldedit_brush.png", inventory_image = "worldedit_brush.png",
stack_max = 1, -- no need to stack these (metadata prevents this anyway) stack_max = 1, -- no need to stack these (metadata prevents this anyway)
range = 0, range = 0,
@ -74,7 +71,7 @@ minetest.register_tool(":worldedit:brush", {
worldedit.register_command("brush", { worldedit.register_command("brush", {
privs = {worldedit=true}, privs = {worldedit=true},
params = "none/<cmd> [parameters]", params = "none/<cmd> [parameters]",
description = "Assign command to WorldEdit brush item", description = S("Assign command to WorldEdit brush item or clear assignment using 'none'"),
parse = function(param) parse = function(param)
local found, _, cmd, params = param:find("^([^%s]+)%s+(.+)$") local found, _, cmd, params = param:find("^([^%s]+)%s+(.+)$")
if not found then if not found then
@ -87,39 +84,39 @@ worldedit.register_command("brush", {
return true, cmd, params return true, cmd, params
end, end,
func = function(name, cmd, params) func = function(name, cmd, params)
local itemstack = minetest.get_player_by_name(name):get_wielded_item() local player = minetest.get_player_by_name(name)
if not player then return end
local itemstack = player:get_wielded_item()
if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then
worldedit.player_notify(name, "Not holding brush item.") return false, S("Not holding brush item.")
return
end end
cmd = cmd:lower() cmd = cmd:lower()
local meta = itemstack:get_meta() local meta = itemstack:get_meta()
if cmd == "none" then if cmd == "none" then
meta:from_table(nil) meta:from_table(nil)
worldedit.player_notify(name, "Brush assignment cleared.") worldedit.player_notify(name, S("Brush assignment cleared."), "ok")
else else
local cmddef = worldedit.registered_commands[cmd] local cmddef = worldedit.registered_commands[cmd]
if cmddef == nil or cmddef.require_pos ~= 1 then if cmddef == nil or cmddef.require_pos ~= 1 then
worldedit.player_notify(name, "//" .. cmd .. " cannot be used with brushes") return false, S("@1 cannot be used with brushes",
return minetest.colorize("#00ffff", "//"..cmd))
end end
-- Try parsing command params so we can give the user feedback -- Try parsing command params so we can give the user feedback
local ok, err = cmddef.parse(params) local ok, err = cmddef.parse(params)
if not ok then if not ok then
err = err or "invalid usage" err = err or S("invalid usage")
worldedit.player_notify(name, "Error with brush command: " .. err) return false, S("Error with command: @1", err)
return
end end
meta:set_string("command", cmd) meta:set_string("command", cmd)
meta:set_string("params", params) meta:set_string("params", params)
local fullcmd = "//" .. cmd .. " " .. params local fullcmd = minetest.colorize("#00ffff", "//"..cmd) .. " " .. params
meta:set_string("description", meta:set_string("description",
minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd) minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd)
worldedit.player_notify(name, "Brush assigned to command: " .. fullcmd) worldedit.player_notify(name, S("Brush assigned to command: @1", fullcmd), "ok")
end end
minetest.get_player_by_name(name):set_wielded_item(itemstack) player:set_wielded_item(itemstack)
end, end,
}) })

View 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=

View 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

View File

@ -1 +0,0 @@
*~

View 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,
})

View File

@ -3,6 +3,7 @@ local S = minetest.get_translator("worldedit_commands")
worldedit.register_command("outset", { worldedit.register_command("outset", {
params = "[h/v] <amount>", params = "[h/v] <amount>",
description = S("Outset the selected region."), description = S("Outset the selected region."),
category = S("Region operations"),
privs = {worldedit=true}, privs = {worldedit=true},
require_pos = 2, require_pos = 2,
parse = function(param) parse = function(param)
@ -42,6 +43,7 @@ worldedit.register_command("outset", {
worldedit.register_command("inset", { worldedit.register_command("inset", {
params = "[h/v] <amount>", params = "[h/v] <amount>",
description = S("Inset the selected region."), description = S("Inset the selected region."),
category = S("Region operations"),
privs = {worldedit=true}, privs = {worldedit=true},
require_pos = 2, require_pos = 2,
parse = function(param) parse = function(param)
@ -79,6 +81,7 @@ worldedit.register_command("inset", {
worldedit.register_command("shift", { worldedit.register_command("shift", {
params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>", params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>",
description = S("Shifts the selection area without moving its contents"), description = S("Shifts the selection area without moving its contents"),
category = S("Region operations"),
privs = {worldedit=true}, privs = {worldedit=true},
require_pos = 2, require_pos = 2,
parse = function(param) parse = function(param)
@ -114,6 +117,7 @@ worldedit.register_command("shift", {
worldedit.register_command("expand", { worldedit.register_command("expand", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]", params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = S("Expands the selection in the selected absolute or relative axis"), description = S("Expands the selection in the selected absolute or relative axis"),
category = S("Region operations"),
privs = {worldedit=true}, privs = {worldedit=true},
require_pos = 2, require_pos = 2,
parse = function(param) parse = function(param)
@ -163,6 +167,7 @@ worldedit.register_command("expand", {
worldedit.register_command("contract", { worldedit.register_command("contract", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]", params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = S("Contracts the selection in the selected absolute or relative axis"), description = S("Contracts the selection in the selected absolute or relative axis"),
category = S("Region operations"),
privs = {worldedit=true}, privs = {worldedit=true},
require_pos = 2, require_pos = 2,
parse = function(param) parse = function(param)
@ -232,7 +237,8 @@ worldedit.register_command("cubeapply", {
end end
local cmddef = worldedit.registered_commands[cmd] local cmddef = worldedit.registered_commands[cmd]
if cmddef == nil or cmddef.require_pos ~= 2 then if cmddef == nil or cmddef.require_pos ~= 2 then
return false, S("invalid usage: //@1 cannot be used with cubeapply", cmd) return false, S("invalid usage: @1 cannot be used with cubeapply",
minetest.colorize("#00ffff", "//"..cmd))
end end
-- run parsing of target command -- run parsing of target command
local parsed = {cmddef.parse(args)} local parsed = {cmddef.parse(args)}
@ -243,14 +249,14 @@ worldedit.register_command("cubeapply", {
end, end,
nodes_needed = function(name, sidex, sidey, sidez, cmd, parsed) nodes_needed = function(name, sidex, sidey, sidez, cmd, parsed)
-- its not possible to defer to the target command at this point -- its not possible to defer to the target command at this point
-- FIXME: why not?
return sidex * sidey * sidez return sidex * sidey * sidez
end, end,
func = function(name, sidex, sidey, sidez, cmd, parsed) func = function(name, sidex, sidey, sidez, cmd, parsed)
local cmddef = assert(worldedit.registered_commands[cmd]) local cmddef = assert(worldedit.registered_commands[cmd])
local success, missing_privs = minetest.check_player_privs(name, cmddef.privs) local success, missing_privs = minetest.check_player_privs(name, cmddef.privs)
if not success then if not success then
worldedit.player_notify(name, S("Missing privileges: @1", table.concat(missing_privs, ", "))) return false, S("Missing privileges: @1", table.concat(missing_privs, ", "))
return
end end
-- update region to be the cuboid the user wanted -- update region to be the cuboid the user wanted

File diff suppressed because it is too large Load Diff

View File

@ -13,20 +13,23 @@ Region expanded by @1 nodes=
Contracts the selection in the selected absolute or relative axis= Contracts the selection in the selected absolute or relative axis=
Region contracted by @1 nodes= Region contracted by @1 nodes=
Select a cube with side length <size> around position 1 and run <command> on region= Select a cube with side length <size> around position 1 and run <command> on region=
invalid usage: //@1 cannot be used with cubeapply= invalid usage: @1 cannot be used with cubeapply=
Missing privileges: @1= Missing privileges: @1=
Region operations=
Can use WorldEdit commands= Can use WorldEdit commands=
no region selected= no region selected=
no position 1 selected= no position 1 selected=
invalid usage= invalid usage=
ERROR: the operation was cancelled because the region has changed.=
Could not open file "@1"= Could not open file "@1"=
Invalid file format!= Invalid file format!=
Schematic was created with a newer version of WorldEdit.= Schematic was created with a newer version of WorldEdit.=
Get information about the WorldEdit mod= Get information about the WorldEdit mod=
WorldEdit @1 is available on this server. Type //help to get a list of commands, or get more information at @2= 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= Get help for WorldEdit commands=
alias to @1=
You are not allowed to use any WorldEdit commands.= You are not allowed to use any WorldEdit commands.=
Available commands: @1@nUse '//help <cmd>' to get more information, or '//help all' to list everything.= Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=
Available commands:@n= Available commands:@n=
Command not available: = Command not available: =
Enable or disable node inspection= Enable or disable node inspection=
@ -51,6 +54,7 @@ Set a WorldEdit region position to the position at (<x>, <y>, <z>)=
Display the volume of the current WorldEdit region= Display the volume of the current WorldEdit region=
current region has a volume of @1 nodes (@2*@3*@4)= current region has a volume of @1 nodes (@2*@3*@4)=
Remove all MapBlocks (16x16x16) containing the selected area from the map= Remove all MapBlocks (16x16x16) containing the selected area from the map=
Node manipulation=
Area deleted.= Area deleted.=
There was an error during deletion of the area.= There was an error during deletion of the area.=
Set the current WorldEdit region to <node>= Set the current WorldEdit region to <node>=
@ -66,6 +70,7 @@ Replace all instances of <search node> with <replace node> in the current WorldE
@1 nodes replaced= @1 nodes replaced=
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region= 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>.= 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= @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 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 hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
@ -78,6 +83,7 @@ Add hollow pyramid centered at WorldEdit position 1 along the given axis with he
Add 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>= 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= Copy the current WorldEdit region along the given axis by <amount> nodes=
Transformations=
@1 nodes copied= @1 nodes copied=
Move the current WorldEdit region along the given axis by <amount> nodes= Move the current WorldEdit region along the given axis by <amount> nodes=
@1 nodes moved= @1 nodes moved=
@ -114,6 +120,7 @@ Restores nodes hidden with WorldEdit in the current WorldEdit region=
@1 nodes restored= @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.= 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"= Save the current WorldEdit region to "(world folder)/schems/<file>.we"=
Schematics=
Disallowed file name: @1= Disallowed file name: @1=
Could not save file to "@1"= Could not save file to "@1"=
@1 nodes saved= @1 nodes saved=
@ -124,6 +131,7 @@ Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the cu
Loading failed!= Loading failed!=
@1 nodes loaded= @1 nodes loaded=
Executes <code> as a Lua chunk in the global namespace= 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= 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"= Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=
Failed to create Minetest schematic= Failed to create Minetest schematic=
@ -138,7 +146,7 @@ currently set node probabilities:=
invalid node probability given, not saved= invalid node probability given, not saved=
Clears all objects within the WorldEdit region= Clears all objects within the WorldEdit region=
@1 objects cleared= @1 objects cleared=
WARNING: this operation could affect up to @1 nodes; type //y to continue or //n to cancel= WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=
Confirm a pending operation= Confirm a pending operation=
no operation pending= no operation pending=
Abort a pending operation= Abort a pending operation=

View File

@ -17,140 +17,148 @@ Region outset by @1 nodes=Gebiet um @1 Blöcke vergrößert
Inset the selected region.=Ausgewähltes Gebiet verkleinern. Inset the selected region.=Ausgewähltes Gebiet verkleinern.
Region inset by @1 nodes=Gebiet um @1 Blöcke verkleinert 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 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 gerade Blickrichtung nach unten oder oben 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 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 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 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 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 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=Wähle einen Würfel mit Seitenlänge <size> um Position 1 und führe <command> auf diesem Gebiet aus 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 invalid usage: @1 cannot be used with cubeapply=Ungültige Verwendung: @1 kann nicht mit cubeapply verwendet werden
Missing privileges: @1=Fehlende Privilegien: @1 Missing privileges: @1=Fehlende Privilegien: @1
Region operations=Gebietsoperationen
Can use WorldEdit commands=Kann WorldEdit-Befehle benutzen Can use WorldEdit commands=Kann WorldEdit-Befehle benutzen
no region selected=Kein Gebiet ausgewählt no region selected=Kein Gebiet ausgewählt
no position 1 selected=Keine Position 1 ausgewählt no position 1 selected=Keine Position 1 ausgewählt
invalid usage=Ungültige Verwendung 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 Could not open file "@1"=Konnte die Datei „@1“ nicht öffnen
Invalid file format!=Ungültiges Dateiformat! Invalid file format!=Ungültiges Dateiformat!
Schematic was created with a newer version of WorldEdit.=Schematic wurde mit einer neueren Version von WorldEdit erstellt. 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 den WorldEdit-Mod erhalten. Get information about the WorldEdit mod=Informationen über die WorldEdit-Mod erhalten
WorldEdit @1 is available on this server. Type //help to get a list of commands, or get more information at @2=WorldEdit @1 ist auf diesem Server verfügbar. Nutzen Sie //help, um eine Liste der Befehle zu erhalten, oder finden Sie weitere Informationen unter @2 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 Get help for WorldEdit commands=Hilfe für WorldEdit-Befehle erhalten
You are not allowed to use any WorldEdit commands.=Ihnen ist nicht erlaubt WorldEdit-Befehle zu nutzen. alias to @1=Alias für @1
Available commands: @1@nUse '//help <cmd>' to get more information, or '//help all' to list everything.=Verfügbare Befehle: @1@n„//help <Befehl>“ benutzen, um mehr Informationen zu erhalten, oder „//help all“, um alles aufzulisten. 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 Available commands:@n=Verfügbare Befehle:@n
Command not available: =Befehl nicht verfügbar: Command not available: =Befehl nicht verfügbar:
Enable or disable node inspection=Inspizieren von Blöcken ein- oder ausschalten 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 enabled for @1, currently facing the @2 axis=Inspektur: für @1 aktiviert, aktuell der @2-Achse zugewandt
inspector: inspection disabled=Inspektur: deaktiviert 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 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 Reset the region so that it is empty=Gebiet zurücksetzen, sodass es leer ist
region reset=Gebiet zurückgesetzt region reset=Gebiet zurückgesetzt
Show markers at the region positions=Markierungen bei den Gebietsposition anzeigen Show markers at the region positions=Markierungen bei den Gebietspositionen anzeigen
region marked=Gebiet markiert region marked=Gebiet markiert
Hide markers if currently shown=Markierungen verstecken falls derzeit sichtbar Hide markers if currently shown=Markierungen verstecken, falls derzeit sichtbar
region unmarked=Gebiet nicht mehr markiert 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 Spieler setzen 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 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= 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 unknown subcommand: @1=Unbekannter Unterbefehl: @1
select positions by punching two nodes=Wählen Sie die Position durch Hauen zweier Blöcke 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 select position @1 by punching a node=Wählen Sie Position @1 durch Hauen eines Blocks
position @1: @2=Position @1: @2 position @1: @2=Position @1: @2
position @1 not set=Position @1 ist nicht gesetzt position @1 not set=Position @1 ist nicht gesetzt
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=Position des WorldEdit-Gebiets auf (<x>, <y>, <z>) setzen 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 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) 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 (16x16x16) entfernen, die das Gebiet enthalten. 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. Area deleted.=Gebiet gelöscht.
There was an error during deletion of the area.=Während des Löschens des Gebiets trat ein Fehler aus. 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 Set the current WorldEdit region to <node>=Das aktuelle WorldEdit-Gebiet mit <node> füllen
invalid node name: @1=Ungültiger Blockname: @1 invalid node name: @1=Ungültiger Blockname: @1
@1 nodes set=@1 Blöcke gesetzt @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 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 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 @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 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 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 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 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 @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. 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>.= 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 @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>.= 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>= 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>= 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>= 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>= 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>= 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>= 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>= 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>= 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>= 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 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 @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 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 @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 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 @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 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 count: @1=Ungültige Anzahl: @1
invalid increments: @1=Ungültige Schrittweite: @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= 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 invalid scaling factors: @1=Ungültige Skalierungsfaktoren: @1
@1 nodes stretched=@1 Blöcke gestreckt @1 nodes stretched=@1 Blöcke gestreckt
Transpose the current WorldEdit region along the given axes= 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 invalid usage: axes must be different=Ungültige Verwendung: Achsen müssen verschieden sein
@1 nodes transposed= @1 nodes transposed=@1 Blöcke transponiert
Flip the current WorldEdit region along the given axis= Flip the current WorldEdit region along the given axis=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse spiegeln
@1 nodes flipped= @1 nodes flipped=@1 Blöcke gespiegelt
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)= 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 invalid usage: angle must be multiple of 90=Ungültige Verwendung: Winkel muss ein Vielfaches von 90 sein
@1 nodes rotated=@1 Blöcke gedreht @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)= 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 nodes oriented=@1 Blöcke ausgerichtet
Fix the lighting in the current WorldEdit region=Belichtung im aktuellen WorldEdit-Gebiet korrigieren Fix the lighting in the current WorldEdit region=Belichtung im aktuellen WorldEdit-Gebiet korrigieren
@1 nodes updated=@1 Blöcke verändert @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 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 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 @1 nodes removed=@1 Blöcke entfernt
Hide all nodes in the current WorldEdit region non-destructively=Alle Blöcke im WorldEdit-Gebiet zerstörungsfrei verstecken 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 @1 nodes hidden=@1 Blöcke versteckt
Suppress all <node> in the current WorldEdit region non-destructively= 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 nodes suppressed=@1 Blöcke versteckt
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Alle <node> Blöcke im WorldEdit-Gebiet hervorheben, indem alle anderen zerstörungsfrei versteckt werden 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 @1 nodes highlighted=@1 Blöcke hervorgehoben
Restores nodes hidden with WorldEdit in the current WorldEdit region=Versteckte Blöcke im WorldEdit-Gebiet wiederherstellen. 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 @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. 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 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 Disallowed file name: @1=Nicht erlaubter Dateiname: @1
Could not save file to "@1"=Konnte die Datei nicht unter "@1" speichern Could not save file to "@1"=Konnte die Datei nicht unter @1 speichern
@1 nodes saved=@1 Blöcke gespeichert @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 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 Schematic empty, nothing allocated=Schematic leer, nichts reserviert
@1 nodes allocated=@1 Blöcke 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 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! Loading failed!=Laden fehlgeschlagen!
@1 nodes loaded=@1 Blöcke geladen @1 nodes loaded=@1 Blöcke geladen
Executes <code> as a Lua chunk in the global namespace= Executes <code> as a Lua chunk in the global namespace=<code> als einen Lua-Chunk im globalen Namensraum ausführen
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region= Code=Code
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 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 Failed to create Minetest schematic=Konnte Minetest-Schematic nicht erstellen
Saved Minetest schematic to @1=Minetest-Schematic in @1 gespeichert 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 als Ausgangspunkt laden 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 failed to place Minetest schematic=Konnte Minetest-Schematic nicht platzieren
placed Minetest schematic @1 at @2=Minetest-Schematic @1 bei @2 platziert 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= 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= select Minetest schematic probability values by punching nodes=Minetest-Schematic-Wahrscheinlichkeitswerte mit Hauen eingeben
finished Minetest schematic probability selection= finished Minetest schematic probability selection=Minetest-Schematic-Wahrscheinlichkeitsauswahl abgeschlossen
currently set node probabilities:= currently set node probabilities:=Momentan gesetzte Block-Wahrscheinlichkeiten:
invalid node probability given, not saved= 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 Clears all objects within the WorldEdit region=Löscht alle Objekte innerhalb des WorldEdit-Gebiets
@1 objects cleared=@1 Objekte gelöscht @1 objects cleared=@1 Objekte gelöscht
WARNING: this operation could affect up to @1 nodes; type //y to continue or //n to cancel=WARNUNG: Dieser Vorgang könnte bis zu @1 Blöcke betreffen; //y zum Fortfahren oder //n zum Abbrechen 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 Confirm a pending operation=Einen ausstehenden Vorgang bestätigen
no operation pending=Kein Vorgang ausstehend no operation pending=Kein Vorgang ausstehend
Abort a pending operation=Einen ausstehenden Vorgang abbrechen 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 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

View File

@ -11,10 +11,10 @@ Invalid file format!=Не верный формат файла!
Schematic was created with a newer version of WorldEdit.=Схема была создана с использованием более новой версии WorldEdit. Schematic was created with a newer version of WorldEdit.=Схема была создана с использованием более новой версии WorldEdit.
Get information about the WorldEdit mod=Вывести информацию о WorldEdit Get information about the WorldEdit mod=Вывести информацию о WorldEdit
WorldEdit @1 is available on this server. Type //help to get a list of commands, or get more information at @2=WorldEdit @1 доступен на этом сервере. Наберите команду //help чтобы увидеть список команд, больше информации по ссылке @2 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 Get help for WorldEdit commands=Вывести информацию об использовании команд WorldEdit
You are not allowed to use any WorldEdit commands.=У вас нет привилегий, чтобы использовать команды WorldEdit. You are not allowed to use any WorldEdit commands.=У вас нет привилегий, чтобы использовать команды WorldEdit.
Available commands: @1@nUse '//help <cmd>' to get more information, or '//help all' to list everything.=Доступные команды: @1@nИспользуйте '//help <cmd>' для получения информации по команде или '//help all' для вывода подсказок по всем командам. Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=Доступные команды: @1@nИспользуйте '@2' для получения информации по команде или '@3' для вывода подсказок по всем командам.
Available commands:@n=Доступные команды:@n Available commands:@n=Доступные команды:@n
Command not available: =Команда не найдена: Command not available: =Команда не найдена:
Enable or disable node inspection=Включить/отключить инспекцию блоков Enable or disable node inspection=Включить/отключить инспекцию блоков
@ -73,7 +73,7 @@ Stack the current WorldEdit region along the given axis <count> times=Размн
@1 nodes stacked=размножено @1 нод(а/ы) @1 nodes stacked=размножено @1 нод(а/ы)
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Размножить текущий WorldEdit-регион <count> раз с шагом <x>, <y>, <z> по соответствующим осям Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Размножить текущий WorldEdit-регион <count> раз с шагом <x>, <y>, <z> по соответствующим осям
invalid count: @1=неверное количество: @1 invalid count: @1=неверное количество: @1
invalid increments: @1=неверные приращения(шаг) invalid increments: @1=неверные приращения(шаг): @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 в качестве точки отсчёта 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 invalid scaling factors: @1=неверные коэффициенты масштабирования: @1
@1 nodes stretched=масштабировано @1 нод(а/ы) @1 nodes stretched=масштабировано @1 нод(а/ы)
@ -128,7 +128,7 @@ Clears all objects within the WorldEdit region=Очистить все объе
@1 objects cleared=очищено @1 объектов @1 objects cleared=очищено @1 объектов
### safe.lua ### ### safe.lua ###
WARNING: this operation could affect up to @1 nodes; type //y to continue or //n to cancel=ПРЕДУПРЕЖДЕНИЕ: эта операция может затронуть до @1 нод; введите //y для продолжения или //n для отмены WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=ПРЕДУПРЕЖДЕНИЕ: эта операция может затронуть до @1 нод; введите @2 для продолжения или @3 для отмены
Confirm a pending operation=Подтвердить отложенную операцию Confirm a pending operation=Подтвердить отложенную операцию
no operation pending=нет ожидающей операции no operation pending=нет ожидающей операции
Abort a pending operation=Отклонить отложенную операцию Abort a pending operation=Отклонить отложенную операцию
@ -148,7 +148,7 @@ Region expanded by @1 nodes=Регион увеличен на @1 нод(у/ы)
Contracts the selection in the selected absolute or relative axis=Уменьшить выделение региона по выбранной абсолютной или относительной оси Contracts the selection in the selected absolute or relative axis=Уменьшить выделение региона по выбранной абсолютной или относительной оси
Region contracted by @1 nodes=Регион уменьшен на @1 нод(у/ы) Region contracted by @1 nodes=Регион уменьшен на @1 нод(у/ы)
Select a cube with side length <size> around position 1 and run <command> on region=Выделить куб с длиной стороны <size> вокруг позиции 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 invalid usage: @1 cannot be used with cubeapply=недопустимое использование: @1 не может быть применено в cubeapply
Missing privileges: @1=Отсутствуют привилегии: @1 Missing privileges: @1=Отсутствуют привилегии: @1
### wand.lua ### ### wand.lua ###

View 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,
})

View File

@ -13,9 +13,7 @@ worldedit.mark_pos1 = function(name, region_too)
worldedit.marker1[name] = nil worldedit.marker1[name] = nil
end end
if pos1 ~= nil then if pos1 ~= nil then
--make area stay loaded worldedit.keep_loaded(pos1, pos1)
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos1)
--add marker --add marker
worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel) worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel)
@ -37,9 +35,7 @@ worldedit.mark_pos2 = function(name, region_too)
worldedit.marker2[name] = nil worldedit.marker2[name] = nil
end end
if pos2 ~= nil then if pos2 ~= nil then
--make area stay loaded worldedit.keep_loaded(pos2, pos2)
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos2, pos2)
--add marker --add marker
worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel) worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel)
@ -77,15 +73,14 @@ worldedit.mark_region = function(name)
local thickness = 0.2 local thickness = 0.2
local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2 local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
--make area stay loaded -- TODO maybe we could skip this actually?
local manip = minetest.get_voxel_manip() worldedit.keep_loaded(pos1, pos2)
manip:read_from_map(pos1, pos2)
local markers = {} local markers = {}
--XY plane markers --XY plane markers
for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
local entpos = {x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z} local entpos = vector.new(pos1.x + sizex - 0.5, pos1.y + sizey - 0.5, z)
local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel) local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel)
if marker ~= nil then if marker ~= nil then
marker:set_properties({ marker:set_properties({
@ -99,7 +94,7 @@ worldedit.mark_region = function(name)
--YZ plane markers --YZ plane markers
for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
local entpos = {x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5} local entpos = vector.new(x, pos1.y + sizey - 0.5, pos1.z + sizez - 0.5)
local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel) local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel)
if marker ~= nil then if marker ~= nil then
marker:set_properties({ marker:set_properties({

View 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

View 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,
})

View 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,
})

View File

@ -1,17 +1,30 @@
local S = minetest.get_translator("worldedit_commands") local S = minetest.get_translator("worldedit_commands")
local safe_region_limit = tonumber(minetest.settings:get("worldedit_safe_region_limit") or "20000")
local safe_region_callback = {} local safe_region_callback = {}
--`count` is the number of nodes that would possibly be modified --`count` is the number of nodes that would possibly be modified
--`callback` is a callback to run when the user confirms --`callback` is a callback to run when the user confirms
local function safe_region(name, count, callback) local function safe_region(name, count, callback)
if count < 20000 then if safe_region_limit <= 0 or count < safe_region_limit then
return callback() return callback()
end end
-- save callback to call later -- save callback to call later
safe_region_callback[name] = callback safe_region_callback[name] = callback
worldedit.player_notify(name, S("WARNING: this operation could affect up to @1 nodes; type //y to continue or //n to cancel", count))
local count_str = tostring(count)
-- highlight millions, 1 mln <=> 100x100x100 cube
if #count_str > 6 then
count_str = minetest.colorize("#f33", count_str:sub(1, -7)) .. count_str:sub(-6, -1)
end
local yes_cmd = minetest.colorize("#00ffff", "//y")
local no_cmd = minetest.colorize("#00ffff", "//n")
local msg = S("WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel",
count_str, yes_cmd, no_cmd)
worldedit.player_notify(name, msg, "info")
end end
local function reset_pending(name) local function reset_pending(name)
@ -24,7 +37,7 @@ minetest.register_chatcommand("/y", {
func = function(name) func = function(name)
local callback = safe_region_callback[name] local callback = safe_region_callback[name]
if not callback then if not callback then
worldedit.player_notify(name, S("no operation pending")) worldedit.player_notify(name, S("no operation pending"), "error")
return return
end end
@ -38,7 +51,7 @@ minetest.register_chatcommand("/n", {
description = S("Abort a pending operation"), description = S("Abort a pending operation"),
func = function(name) func = function(name)
if not safe_region_callback[name] then if not safe_region_callback[name] then
worldedit.player_notify(name, S("no operation pending")) worldedit.player_notify(name, S("no operation pending"), "error")
return return
end end

View 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)

View 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,
})

View File

@ -16,6 +16,13 @@ minetest.register_tool(":worldedit:wand", {
stack_max = 1, -- there is no need to have more than one stack_max = 1, -- there is no need to have more than one
liquids_pointable = true, -- ground with only water on can be selected as well liquids_pointable = true, -- ground with only water on can be selected as well
-- ignore marker cube so the clicking on the position markers works reliably
pointabilities = {
objects = {
["worldedit:region_cube"] = false
}
},
on_use = function(itemstack, placer, pointed_thing) on_use = function(itemstack, placer, pointed_thing)
if placer == nil or pointed_thing == nil then return end if placer == nil or pointed_thing == nil then return end
local name = placer:get_player_name() local name = placer:get_player_name()
@ -34,7 +41,7 @@ minetest.register_tool(":worldedit:wand", {
local entity = pointed_thing.ref:get_luaentity() local entity = pointed_thing.ref:get_luaentity()
if entity and entity.name == "worldedit:pos2" then if entity and entity.name == "worldedit:pos2" then
-- set pos1 = pos2 -- set pos1 = pos2
worldedit.pos1[name] = worldedit.pos2[name] worldedit.pos1[name] = vector.copy(worldedit.pos2[name])
worldedit.mark_pos1(name) worldedit.mark_pos1(name)
end end
end end
@ -59,7 +66,7 @@ minetest.register_tool(":worldedit:wand", {
local entity = pointed_thing.ref:get_luaentity() local entity = pointed_thing.ref:get_luaentity()
if entity and entity.name == "worldedit:pos1" then if entity and entity.name == "worldedit:pos1" then
-- set pos2 = pos1 -- set pos2 = pos1
worldedit.pos2[name] = worldedit.pos1[name] worldedit.pos2[name] = vector.copy(worldedit.pos1[name])
worldedit.mark_pos2(name) worldedit.mark_pos2(name)
end end
return itemstack -- nothing consumed, nothing changed return itemstack -- nothing consumed, nothing changed

View File

@ -140,7 +140,7 @@ local function execute_worldedit_command(command_name, player_name, params)
assert(chatcmd, "unknown command: " .. command_name) assert(chatcmd, "unknown command: " .. command_name)
local _, msg = chatcmd.func(player_name, params) local _, msg = chatcmd.func(player_name, params)
if msg then if msg then
worldedit.player_notify(player_name, msg) minetest.chat_send_player(player_name, msg)
end end
end end

View File

@ -80,6 +80,7 @@ if minetest.global_exists("unified_inventory") then -- unified inventory install
unified_inventory.register_button("worldedit_gui", { unified_inventory.register_button("worldedit_gui", {
type = "image", type = "image",
image = "inventory_plus_worldedit_gui.png", image = "inventory_plus_worldedit_gui.png",
tooltip = "Edit your World!",
condition = function(player) condition = function(player)
return minetest.check_player_privs(player:get_player_name(), {worldedit=true}) return minetest.check_player_privs(player:get_player_name(), {worldedit=true})
end, end,
@ -193,7 +194,8 @@ elseif minetest.global_exists("sfinv") then -- sfinv installed
get = function(self, player, context) get = function(self, player, context)
local can_worldedit = minetest.check_player_privs(player, {worldedit=true}) local can_worldedit = minetest.check_player_privs(player, {worldedit=true})
local fs = orig_get(self, player, context) local fs = orig_get(self, player, context)
return fs .. (can_worldedit and "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]" or "") return fs .. (can_worldedit and "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]" ..
"tooltip[worldedit_gui;Edit your World!]" or "")
end end
}) })
@ -262,7 +264,7 @@ worldedit.register_gui_handler("worldedit_gui", function(name, fields)
--ensure player has permission to perform action --ensure player has permission to perform action
local has_privs, missing_privs = minetest.check_player_privs(name, entry.privs) local has_privs, missing_privs = minetest.check_player_privs(name, entry.privs)
if not has_privs then if not has_privs then
worldedit.player_notify(name, "you are not allowed to use this function (missing privileges: " .. table.concat(missing_privs, ", ") .. ")") worldedit.player_notify(name, "you are not allowed to use this function (missing privileges: " .. table.concat(missing_privs, ", ") .. ")", "error")
return false return false
end end
if entry.on_select then if entry.on_select then