1
0
mirror of https://github.com/Uberi/Minetest-WorldEdit.git synced 2025-07-19 16:10:25 +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"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: apt
run: sudo apt-get install -y luarocks
- name: luacheck install

View File

@ -3,9 +3,26 @@ on: [push, pull_request]
jobs:
test:
name: "Unittests"
name: "Unit Tests ${{ matrix.cfg.image }}"
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
matrix:
cfg:
- { image: 'registry.gitlab.com/minetest/minetest/server:5.0.1', mtg: false }
- { image: 'registry.gitlab.com/minetest/minetest/server:5.5.1', mtg: false }
- { image: 'ghcr.io/minetest/minetest:5.10.0', mtg: true }
- { image: 'ghcr.io/luanti-org/luanti:master', mtg: true } # latest git
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
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",
"table",
"unified_inventory", "sfinv", "smart_inventory", "inventory_plus"
read_globals = {
"minetest", "VoxelArea", "ItemStack",
"unified_inventory", "sfinv", "smart_inventory", "inventory_plus",
"dump",
table = {fields = {"copy", "indexof", "insert_all"}},
vector = {fields = {
-- as of 5.0
"new", "direction", "distance", "length", "normalize", "floor", "round",
"apply", "equals", "sort", "add", "subtract", "multiply", "divide",
-- polyfilled
"copy"
}},
}
globals = {"worldedit"}
-- Ignore these errors until someone decides to fix them
ignore = {"212", "213", "411", "412", "421", "422", "431", "432", "631"}
files["worldedit/common.lua"] = {
globals = {"vector"},
}
files["worldedit/test"] = {
read_globals = {"testnode1", "testnode2", "testnode3", "area", "check", "place_pattern"},
}
files["worldedit/test/init.lua"] = {
globals = {"testnode1", "testnode2", "testnode3", "area", "check", "place_pattern"},
}

View File

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

View File

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

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

View File

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

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

View File

@ -1,12 +1,21 @@
--- Common functions [INTERNAL]. All of these functions are internal!
-- @module worldedit.common
-- Polyfill for vector.copy (added in 5.5.0)
if not vector.copy then
local vnew = vector.new
vector.copy = function(v)
return vnew(v.x, v.y, v.z)
end
end
--- Copies and modifies positions `pos1` and `pos2` so that each component of
-- `pos1` is less than or equal to the corresponding component of `pos2`.
-- Returns the new positions.
function worldedit.sort_pos(pos1, pos2)
pos1 = vector.new(pos1.x, pos1.y, pos1.z)
pos2 = vector.new(pos2.x, pos2.y, pos2.z)
pos1 = vector.copy(pos1)
pos2 = vector.copy(pos2)
if pos1.x > pos2.x then
pos2.x, pos1.x = pos1.x, pos2.x
end
@ -45,12 +54,22 @@ function worldedit.get_axis_others(axis)
end
-- Create a vmanip and read the area from map, this causes all
-- MapBlocks to be loaded into memory synchronously.
-- This doesn't actually *keep* them loaded, unlike the name implies.
function worldedit.keep_loaded(pos1, pos2)
-- Create a vmanip and read the area from map, this
-- causes all MapBlocks to be loaded into memory.
-- This doesn't actually *keep* them loaded, unlike the name implies.
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
-- rough estimate, a MapNode is 4 bytes in the engine
if worldedit.volume(pos1, pos2) > 268400000 then
print("[WorldEdit] Requested to load an area bigger than 1GB, refusing. The subsequent operation may fail.")
return
end
if minetest.load_area then
-- same effect but without unnecessary data copying
minetest.load_area(pos1, pos2)
else
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
end
end
@ -65,7 +84,7 @@ function mh.get_empty_data(area)
-- only partially modified aren't overwriten.
local data = {}
local c_ignore = minetest.get_content_id("ignore")
for i = 1, worldedit.volume(area.MinEdge, area.MaxEdge) do
for i = 1, area:getVolume() do
data[i] = c_ignore
end
return data
@ -114,6 +133,77 @@ function mh.finish(manip, data)
manip:set_data(data)
end
manip:write_to_map()
manip:update_map()
if manip.close ~= nil then
manip:close() -- explicitly free memory
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"
end
assert(not rawequal(pos1, pos2)) -- vectors must not alias
if axis == 'x' then
worldedit.pos1[name].x = pos1.x + amount
worldedit.pos2[name].x = pos2.x + amount
@ -134,7 +136,8 @@ end
-- Return the marker that is closest to the player
worldedit.marker_get_closest_to_player = function(name)
local playerpos = minetest.get_player_by_name(name):get_pos()
local player = assert(minetest.get_player_by_name(name))
local playerpos = player:get_pos()
local dist1 = vector.distance(playerpos, worldedit.pos1[name])
local dist2 = vector.distance(playerpos, worldedit.pos2[name])

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
-- TODO: don't shit individual variables into the globals
---------------------
-- Helpers
---------------------
local vec = vector.new
local vecw = function(axis, n, base)
local ret = vec(base)
@ -16,9 +17,9 @@ local set_node = minetest.set_node
-- Nodes
---------------------
local air = "air"
local testnode1
local testnode2
local testnode3
rawset(_G, "testnode1", "")
rawset(_G, "testnode2", "")
rawset(_G, "testnode3", "")
-- Loads nodenames to use for tests
local function init_nodes()
testnode1 = minetest.registered_aliases["mapgen_stone"]
@ -27,7 +28,7 @@ local function init_nodes()
assert(testnode1 and testnode2 and testnode3)
end
-- Writes repeating pattern into given area
local function place_pattern(pos1, pos2, pattern)
rawset(_G, "place_pattern", function(pos1, pos2, pattern)
local pos = vec()
local node = {name=""}
local i = 1
@ -43,14 +44,14 @@ local function place_pattern(pos1, pos2, pattern)
end
end
end
end
end)
---------------------
-- Area management
---------------------
assert(minetest.get_mapgen_setting("mg_name") == "singlenode")
local area = {}
rawset(_G, "area", {})
do
local areamin, areamax
local off
@ -71,6 +72,9 @@ do
end
-- Reset area contents and state
area.clear = function()
if off and vector.equals(off, vec(0, 0, 0)) then
return
end
local vmanip = minetest.get_voxel_manip(areamin, areamax)
local vpos1, vpos2 = vmanip:get_emerged_area()
local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1)
@ -88,9 +92,9 @@ do
area.get = function(sizex, sizey, sizez)
local size
if sizey == nil and sizez == nil then
size = vector.new(sizex, sizex, sizex)
size = vec(sizex, sizex, sizex)
else
size = vector.new(sizex, sizey, sizez)
size = vec(sizex, sizey, sizez)
end
local pos1 = vector.add(areamin, off)
local pos2 = vector.subtract(vector.add(pos1, size), 1)
@ -148,7 +152,7 @@ end
---------------------
-- Checks
---------------------
local check = {}
rawset(_G, "check", {})
-- Check that all nodes in [pos1, pos2] are the node(s) specified
check.filled = function(pos1, pos2, nodes)
if type(nodes) == "string" then
@ -215,7 +219,7 @@ end
-- The actual tests
---------------------
local tests = {}
local function register_test(name, func, opts)
worldedit.register_test = function(name, func, opts)
assert(type(name) == "string")
assert(func == nil or type(func) == "function")
if not opts then
@ -227,6 +231,7 @@ local function register_test(name, func, opts)
opts.func = func
table.insert(tests, opts)
end
local register_test = worldedit.register_test
-- How this works:
-- register_test registers a test with a name and function
-- The function should return if the test passes or otherwise cause a Lua error
@ -241,8 +246,8 @@ end
register_test("Internal self-test")
register_test("is area loaded?", function()
local pos1, _ = area.get(1)
assert(get_node(pos1).name == "air")
end, {dry=true})
assert(get_node(pos1).name == air)
end)
register_test("area.split", function()
for i = 2, 6 do
@ -255,7 +260,7 @@ register_test("area.split", function()
assert((half1.z - pos1.z) == (pos2.z - half2.z)) -- divided equally
end
end
end, {dry=true})
end)
register_test("check.filled", function()
local pos1, pos2 = area.get(1, 2, 1)
@ -276,272 +281,49 @@ register_test("pattern", function()
end)
register_test("Generic node manipulations")
register_test("worldedit.set", function()
local pos1, pos2 = area.get(10)
local m = area.margin(1)
worldedit.set(pos1, pos2, testnode1)
check.filled(pos1, pos2, testnode1)
check.filled2(m, air)
end)
register_test("worldedit.set mix", function()
local pos1, pos2 = area.get(10)
local m = area.margin(1)
worldedit.set(pos1, pos2, {testnode1, testnode2})
check.filled(pos1, pos2, {testnode1, testnode2})
check.filled2(m, air)
end)
register_test("worldedit.replace", function()
local pos1, pos2 = area.get(10)
local half1, half2 = area.split(pos1, pos2)
worldedit.set(pos1, half1, testnode1)
worldedit.set(half2, pos2, testnode2)
worldedit.replace(pos1, pos2, testnode1, testnode3)
check.not_filled(pos1, pos2, testnode1)
check.filled(pos1, half1, testnode3)
check.filled(half2, pos2, testnode2)
end)
register_test("worldedit.replace inverse", function()
local pos1, pos2 = area.get(10)
local half1, half2 = area.split(pos1, pos2)
worldedit.set(pos1, half1, testnode1)
worldedit.set(half2, pos2, testnode2)
worldedit.replace(pos1, pos2, testnode1, testnode3, true)
check.filled(pos1, half1, testnode1)
check.filled(half2, pos2, testnode3)
end)
-- FIXME?: this one looks overcomplicated
register_test("worldedit.copy", function()
local pos1, pos2 = area.get(4)
local axis, n = area.dir(2)
local m = area.margin(1)
local b = pos1[axis]
-- create one slice with testnode1, one with testnode2
worldedit.set(pos1, vecw(axis, b + 1, pos2), testnode1)
worldedit.set(vecw(axis, b + 2, pos1), pos2, testnode2)
worldedit.copy(pos1, pos2, axis, n)
-- should have three slices now
check.filled(pos1, vecw(axis, b + 1, pos2), testnode1)
check.filled(vecw(axis, b + 2, pos1), pos2, testnode1)
check.filled(vecw(axis, b + 4, pos1), vector.add(pos2, vecw(axis, n)), testnode2)
check.filled2(m, "air")
end)
register_test("worldedit.copy2", function()
local pos1, pos2 = area.get(6)
local m1 = area.margin(1)
local pos1_, pos2_ = area.get(6)
local m2 = area.margin(1)
local pattern = {testnode1, testnode2, testnode3, testnode1, testnode2}
place_pattern(pos1, pos2, pattern)
worldedit.copy2(pos1, pos2, vector.subtract(pos1_, pos1))
check.pattern(pos1, pos2, pattern)
check.pattern(pos1_, pos2_, pattern)
check.filled2(m1, "air")
check.filled2(m2, "air")
end)
register_test("worldedit.move (overlap)", function()
local pos1, pos2 = area.get(7)
local axis, n = area.dir(2)
local m = area.margin(1)
local pattern = {testnode2, testnode1, testnode2, testnode3, testnode3}
place_pattern(pos1, pos2, pattern)
worldedit.move(pos1, pos2, axis, n)
check.filled(pos1, vecw(axis, pos1[axis] + n - 1, pos2), "air")
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
check.filled2(m, "air")
end)
register_test("worldedit.move", function()
local pos1, pos2 = area.get(10)
local axis, n = area.dir(10)
local m = area.margin(1)
local pattern = {testnode1, testnode3, testnode3, testnode2}
place_pattern(pos1, pos2, pattern)
worldedit.move(pos1, pos2, axis, n)
check.filled(pos1, pos2, "air")
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
check.filled2(m, "air")
end)
-- TODO: the rest (also testing param2 + metadata)
register_test("Schematics")
register_test("worldedit.read_header", function()
local value = '5,foo,BAR,-1,234:the content'
local version, header, content = worldedit.read_header(value)
assert(version == 5)
assert(#header == 4)
assert(header[1] == "foo" and header[2] == "BAR")
assert(header[3] == "-1" and header[4] == "234")
assert(content == "the content")
end)
register_test("worldedit.allocate", function()
local value = '3:-1 0 0 dummy 0 0\n0 0 4 dummy 0 0\n0 1 0 dummy 0 0'
local pos1, pos2, count = worldedit.allocate(vec(1, 1, 1), value)
assert(vector.equals(pos1, vec(0, 1, 1)))
assert(vector.equals(pos2, vec(1, 2, 5)))
assert(count == 3)
end)
do
local function output_weird(numbers, body)
local s = {"return {"}
for _, parts in ipairs(numbers) do
s[#s+1] = "{"
for _, n in ipairs(parts) do
s[#s+1] = string.format(" {%d},", n)
end
s[#s+1] = "},"
end
return table.concat(s, "\n") .. table.concat(body, "\n") .. "}"
end
local fmt1p = '{\n ["x"]=%d,\n ["y"]=%d,\n ["z"]=%d,\n},'
local fmt1n = '{\n ["name"]="%s",\n},'
local fmt4 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["meta"] = { ["fields"] = { }, ["inventory"] = { } }, ["param2"] = 0, ["param1"] = 0, ["name"] = "%s" }'
local fmt5 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["name"] = "%s" }'
local fmt51 = '{[r2]=0,x=%d,y=%d,z=%d,name=r%d}'
local fmt52 = '{x=%d,y=%d,z=%d,name=_[%d]}'
local test_data = {
-- used by WorldEdit 0.2 (first public release)
{
name = "v1", ver = 1,
gen = function(pat)
local numbers = {
{2, 3, 4, 5, 6},
{7, 8}, {9, 10}, {11, 12},
{13, 14}, {15, 16}
}
return output_weird(numbers, {
fmt1p:format(0, 0, 0),
fmt1n:format(pat[1]),
fmt1p:format(0, 1, 0),
fmt1n:format(pat[3]),
fmt1p:format(1, 1, 0),
fmt1n:format(pat[1]),
fmt1p:format(1, 0, 1),
fmt1n:format(pat[3]),
fmt1p:format(0, 1, 1),
fmt1n:format(pat[1]),
})
end
},
-- v2: missing because I couldn't find any code in my archives that actually wrote this format
{
name = "v3", ver = 3,
gen = function(pat)
assert(pat[2] == "air")
return table.concat({
"0 0 0 " .. pat[1] .. " 0 0",
"0 1 0 " .. pat[3] .. " 0 0",
"1 1 0 " .. pat[1] .. " 0 0",
"1 0 1 " .. pat[3] .. " 0 0",
"0 1 1 " .. pat[1] .. " 0 0",
}, "\n")
end
},
{
name = "v4", ver = 4,
gen = function(pat)
return table.concat({
"return { " .. fmt4:format(0, 0, 0, pat[1]),
fmt4:format(0, 1, 0, pat[3]),
fmt4:format(1, 1, 0, pat[1]),
fmt4:format(1, 0, 1, pat[3]),
fmt4:format(0, 1, 1, pat[1]) .. " }",
}, ", ")
end
},
-- like v4 but no meta and param (if empty)
{
name = "v5 (pre-5.6)", ver = 5,
gen = function(pat)
return table.concat({
"5:return { " .. fmt5:format(0, 0, 0, pat[1]),
fmt5:format(0, 1, 0, pat[3]),
fmt5:format(1, 1, 0, pat[1]),
fmt5:format(1, 0, 1, pat[3]),
fmt5:format(0, 1, 1, pat[1]) .. " }",
}, ", ")
end
},
-- reworked engine serialization in 5.6
{
name = "v5 (5.6)", ver = 5,
gen = function(pat)
return table.concat({
'5:r1="' .. pat[1] .. '";r2="param1";r3="' .. pat[3] .. '";return {'
.. fmt51:format(0, 0, 0, 1),
fmt51:format(0, 1, 0, 3),
fmt51:format(1, 1, 0, 1),
fmt51:format(1, 0, 1, 3),
fmt51:format(0, 1, 1, 1) .. "}",
}, ",")
end
},
-- small changes on engine side again
{
name = "v5 (post-5.7)", ver = 5,
gen = function(pat)
return table.concat({
'5:local _={};_[1]="' .. pat[1] .. '";_[3]="' .. pat[3] .. '";return {'
.. fmt52:format(0, 0, 0, 1),
fmt52:format(0, 1, 0, 3),
fmt52:format(1, 1, 0, 1),
fmt52:format(1, 0, 1, 3),
fmt52:format(0, 1, 1, 1) .. "}",
}, ",")
end
},
}
for _, e in ipairs(test_data) do
register_test("worldedit.deserialize " .. e.name, function()
local pos1, pos2 = area.get(2)
local m = area.margin(1)
local pat = {testnode3, "air", testnode2}
local value = e.gen(pat)
assert(type(value) == "string")
local version = worldedit.read_header(value)
assert(version == e.ver, "version: got " .. tostring(version) .. " expected " .. e.ver)
local count = worldedit.deserialize(pos1, value)
assert(count ~= nil and count > 0)
check.pattern(pos1, pos2, pat)
check.filled2(m, "air")
end)
end
for _, name in ipairs({
"manipulations", "primitives", "schematic"
}) do
dofile(minetest.get_modpath("worldedit") .. "/test/" .. name .. ".lua")
end
register_test("Code")
register_test("worldedit.lua", function()
-- syntax error
local err, ret = worldedit.lua("?")
assert(ret == nil)
assert(err:find("unexpected symbol"))
-- runtime error
local err, ret = worldedit.lua("error(1234)")
assert(ret == nil)
assert(err:find("1234"))
-- normal operation
local err, ret = worldedit.lua("return name..tostring(player == nil)..tostring(pos == nil)", "nobody")
assert(err == nil)
assert(ret == "\"nobodytruetrue\"")
end)
register_test("worldedit.luatransform", function()
local pos1, pos2 = area.get(2)
-- syntax error
local err = worldedit.luatransform(pos1, pos2, "?")
assert(err:find("unexpected symbol"))
-- runtime error
local err = worldedit.luatransform(pos1, pos2, "error(2345)")
assert(err:find("2345"))
-- normal operation
local err = worldedit.luatransform(pos1, pos2,
"minetest.swap_node(pos, {name=" .. ("%q"):format(testnode1) .. "})")
assert(err == nil)
check.filled(pos1, pos1, testnode1)
end)
---------------------
-- Main function
---------------------
@ -560,7 +342,7 @@ worldedit.run_tests = function()
for x = 0, math.floor(wanted.x/16) do
for y = 0, math.floor(wanted.y/16) do
for z = 0, math.floor(wanted.z/16) do
assert(minetest.forceload_block(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
@ -572,9 +354,7 @@ worldedit.run_tests = function()
local s = "---- " .. test.name .. " "
print(s .. string.rep("-", 60 - #s))
else
if not test.dry then
area.clear()
end
area.clear()
local ok, err = pcall(test.func)
print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL"))
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
error(
"worldedit_brush requires at least Minetest 5.0"
)
end
local S = minetest.get_translator("worldedit_brush")
local BRUSH_MAX_DIST = 150
local brush_on_use = function(itemstack, placer)
@ -12,7 +8,8 @@ local brush_on_use = function(itemstack, placer)
local cmd = meta:get_string("command")
if cmd == "" then
worldedit.player_notify(name,
"This brush is not bound, use //brush to bind a command to it.")
S("This brush is not bound, use @1 to bind a command to it.",
minetest.colorize("#00ffff", "//brush")), "info")
return false
end
@ -22,7 +19,7 @@ local brush_on_use = function(itemstack, placer)
local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs)
if not has_privs then
worldedit.player_notify(name,
"Missing privileges: " .. table.concat(missing_privs, ", "))
S("Missing privileges: @1", table.concat(missing_privs, ", ")), "error")
return false
end
@ -32,7 +29,7 @@ local brush_on_use = function(itemstack, placer)
local ray = minetest.raycast(raybegin, rayend, false, true)
local pointed_thing = ray:next()
if pointed_thing == nil then
worldedit.player_notify(name, "Too far away.")
worldedit.player_notify(name, S("Too far away."), "error")
return false
end
@ -41,17 +38,17 @@ local brush_on_use = function(itemstack, placer)
worldedit.pos2[name] = nil
worldedit.marker_update(name)
-- this isn't really clean...
local player_notify_old = worldedit.player_notify
worldedit.player_notify = function(name, msg)
if string.match(msg, "^%d") then return end -- discard "1234 nodes added."
return player_notify_old(name, msg)
end
assert(cmddef.require_pos < 2)
local parsed = {cmddef.parse(meta:get_string("params"))}
if not table.remove(parsed, 1) then return false end -- shouldn't happen
-- discard success messages
local player_notify_old = worldedit.player_notify
worldedit.player_notify = function(name, msg, typ)
if typ == "ok" then return end
return player_notify_old(name, msg, typ)
end
minetest.log("action", string.format("%s uses WorldEdit brush (//%s) at %s",
name, cmd, minetest.pos_to_string(pointed_thing.under)))
cmddef.func(name, unpack(parsed))
@ -61,7 +58,7 @@ local brush_on_use = function(itemstack, placer)
end
minetest.register_tool(":worldedit:brush", {
description = "WorldEdit Brush",
description = S("WorldEdit Brush"),
inventory_image = "worldedit_brush.png",
stack_max = 1, -- no need to stack these (metadata prevents this anyway)
range = 0,
@ -74,7 +71,7 @@ minetest.register_tool(":worldedit:brush", {
worldedit.register_command("brush", {
privs = {worldedit=true},
params = "none/<cmd> [parameters]",
description = "Assign command to WorldEdit brush item",
description = S("Assign command to WorldEdit brush item or clear assignment using 'none'"),
parse = function(param)
local found, _, cmd, params = param:find("^([^%s]+)%s+(.+)$")
if not found then
@ -87,39 +84,39 @@ worldedit.register_command("brush", {
return true, cmd, params
end,
func = function(name, cmd, params)
local itemstack = minetest.get_player_by_name(name):get_wielded_item()
local player = minetest.get_player_by_name(name)
if not player then return end
local itemstack = player:get_wielded_item()
if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then
worldedit.player_notify(name, "Not holding brush item.")
return
return false, S("Not holding brush item.")
end
cmd = cmd:lower()
local meta = itemstack:get_meta()
if cmd == "none" then
meta:from_table(nil)
worldedit.player_notify(name, "Brush assignment cleared.")
worldedit.player_notify(name, S("Brush assignment cleared."), "ok")
else
local cmddef = worldedit.registered_commands[cmd]
if cmddef == nil or cmddef.require_pos ~= 1 then
worldedit.player_notify(name, "//" .. cmd .. " cannot be used with brushes")
return
return false, S("@1 cannot be used with brushes",
minetest.colorize("#00ffff", "//"..cmd))
end
-- Try parsing command params so we can give the user feedback
local ok, err = cmddef.parse(params)
if not ok then
err = err or "invalid usage"
worldedit.player_notify(name, "Error with brush command: " .. err)
return
err = err or S("invalid usage")
return false, S("Error with command: @1", err)
end
meta:set_string("command", cmd)
meta:set_string("params", params)
local fullcmd = "//" .. cmd .. " " .. params
local fullcmd = minetest.colorize("#00ffff", "//"..cmd) .. " " .. params
meta:set_string("description",
minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd)
worldedit.player_notify(name, "Brush assigned to command: " .. fullcmd)
worldedit.player_notify(name, S("Brush assigned to command: @1", fullcmd), "ok")
end
minetest.get_player_by_name(name):set_wielded_item(itemstack)
player:set_wielded_item(itemstack)
end,
})

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

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=
Region contracted by @1 nodes=
Select a cube with side length <size> around position 1 and run <command> on region=
invalid usage: //@1 cannot be used with cubeapply=
invalid usage: @1 cannot be used with cubeapply=
Missing privileges: @1=
Region operations=
Can use WorldEdit commands=
no region selected=
no position 1 selected=
invalid usage=
ERROR: the operation was cancelled because the region has changed.=
Could not open file "@1"=
Invalid file format!=
Schematic was created with a newer version of WorldEdit.=
Get information about the WorldEdit mod=
WorldEdit @1 is available on this server. Type //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=
alias to @1=
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=
Command not available: =
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=
current region has a volume of @1 nodes (@2*@3*@4)=
Remove all MapBlocks (16x16x16) containing the selected area from the map=
Node manipulation=
Area deleted.=
There was an error during deletion of the area.=
Set the current WorldEdit region to <node>=
@ -66,6 +70,7 @@ Replace all instances of <search node> with <replace node> in the current WorldE
@1 nodes replaced=
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
Shapes=
@1 nodes added=
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
@ -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 spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=
Copy the current WorldEdit region along the given axis by <amount> nodes=
Transformations=
@1 nodes copied=
Move the current WorldEdit region along the given axis by <amount> nodes=
@1 nodes moved=
@ -114,6 +120,7 @@ Restores nodes hidden with WorldEdit in the current WorldEdit region=
@1 nodes restored=
Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=
Save the current WorldEdit region to "(world folder)/schems/<file>.we"=
Schematics=
Disallowed file name: @1=
Could not save file to "@1"=
@1 nodes saved=
@ -124,6 +131,7 @@ Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the cu
Loading failed!=
@1 nodes loaded=
Executes <code> as a Lua chunk in the global namespace=
Code=
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=
Failed to create Minetest schematic=
@ -138,7 +146,7 @@ currently set node probabilities:=
invalid node probability given, not saved=
Clears all objects within the WorldEdit region=
@1 objects cleared=
WARNING: this operation could affect up to @1 nodes; type //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=
no operation pending=
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.
Region inset by @1 nodes=Gebiet um @1 Blöcke verkleinert
Shifts the selection area without moving its contents=Verschiebt das ausgewählte Gebiet ohne dessen Inhalt
Invalid if looking straight up or down=Nicht zulässig bei 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
Expands the selection in the selected absolute or relative axis=Erweitert das Gebiet in Richtung einer absoluten oder relativen Achse
Region expanded by @1 nodes=Gebiet um @1 Blöcke erweitert
Contracts the selection in the selected absolute or relative axis=Schrumpft das Gebiet in Richtung einer absoluten oder relativen Achse
Region contracted by @1 nodes=Gebiet um @1 Blöcke geschrumpft
Select a cube with side length <size> around position 1 and run <command> on region=Wähle einen Würfel mit Seitenlänge <size> um Position 1 und führe <command> auf diesem Gebiet aus
invalid usage: //@1 cannot be used with cubeapply=Ungültige Verwendung: //@1 kann nicht mit cubeapply verwendet werden
Select a cube with side length <size> around position 1 and run <command> on region=Einen Würfel mit Seitenlänge <size> um Position 1 auswählen und <command> auf diesem Gebiet ausführen
invalid usage: @1 cannot be used with cubeapply=Ungültige Verwendung: @1 kann nicht mit cubeapply verwendet werden
Missing privileges: @1=Fehlende Privilegien: @1
Region operations=Gebietsoperationen
Can use WorldEdit commands=Kann WorldEdit-Befehle benutzen
no region selected=Kein Gebiet ausgewählt
no position 1 selected=Keine Position 1 ausgewählt
invalid usage=Ungültige Verwendung
ERROR: the operation was cancelled because the region has changed.=FEHLER: Der Vorgang wurde abgebrochen, weil das Gebiet verändert wurde.
Could not open file "@1"=Konnte die Datei „@1“ nicht öffnen
Invalid file format!=Ungültiges Dateiformat!
Schematic was created with a newer version of WorldEdit.=Schematic wurde mit einer neueren Version von WorldEdit erstellt.
Get information about the WorldEdit mod=Informationen über den 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
Get information about the WorldEdit mod=Informationen über die WorldEdit-Mod erhalten
WorldEdit @1 is available on this server. Type @2 to get a list of commands, or find more information at @3=WorldEdit @1 ist auf diesem Server verfügbar. Nutzen Sie @2, um eine Liste der Befehle zu erhalten, oder finden Sie weitere Informationen unter @3
Get help for WorldEdit commands=Hilfe für WorldEdit-Befehle erhalten
You are not allowed to use any WorldEdit commands.=Ihnen ist nicht erlaubt WorldEdit-Befehle zu nutzen.
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.
alias to @1=Alias für @1
You are not allowed to use any WorldEdit commands.=Ihnen ist nicht erlaubt, WorldEdit-Befehle zu nutzen.
Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=Verfügbare Befehle: @1@n„@2“ benutzen, um mehr Informationen zu erhalten, oder „@3“, um alles aufzulisten.
Available commands:@n=Verfügbare Befehle:@n
Command not available: =Befehl nicht verfügbar:
Enable or disable node inspection=Inspizieren von Blöcken ein- oder ausschalten
inspector: inspection enabled for @1, currently facing the @2 axis=Inspektur: für @1 aktiviert, aktuell der @2-Achse zugewandt
inspector: inspection disabled=Inspektur: deaktiviert
inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=Inspektur: @1 bei @2 (param1@=@3, param2@=@4, einfallendes Licht@=@5), der @6-Achse zugewandt, gehauen
inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=Inspektur: @1 bei @2 (param1@=@3, param2@=@4, einfallendes Licht@=@5), der @6-Achse zugewandt, gehauen
Reset the region so that it is empty=Gebiet zurücksetzen, sodass es leer ist
region reset=Gebiet zurückgesetzt
Show markers at the region positions=Markierungen bei den Gebietsposition anzeigen
Show markers at the region positions=Markierungen bei den Gebietspositionen anzeigen
region marked=Gebiet markiert
Hide markers if currently shown=Markierungen verstecken falls derzeit sichtbar
Hide markers if currently shown=Markierungen verstecken, falls derzeit sichtbar
region unmarked=Gebiet nicht mehr markiert
Set WorldEdit region position @1 to the player's location=Position @1 des WorldEdit-Gebiets auf die Position des 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
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
select positions by punching two nodes=Wählen Sie die Position durch Hauen zweier Blöcke
select position @1 by punching a node=Wählen Sie Position @1 durch Hauen eines Blocks
position @1: @2=Position @1: @2
position @1 not set=Position @1 ist nicht gesetzt
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=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
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.
current region has a volume of @1 nodes (@2*@3*@4)=Das aktuelle Gebiet hat ein Volumen von @1 Blöcken (@2×@3×@4)
Remove all MapBlocks (16x16x16) containing the selected area from the map=Alle Kartenblöcke (16×16×16) entfernen, die das gewählte Gebiet enthalten
Node manipulation=Block-Manipulation
Area deleted.=Gebiet gelöscht.
There was an error during deletion of the area.=Während des Löschens des Gebiets trat ein Fehler 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
invalid node name: @1=Ungültiger Blockname: @1
@1 nodes set=@1 Blöcke gesetzt
Set param2 of all nodes in the current WorldEdit region to <param2>=Den param2 aller Blocke im aktuellen WorldEdit-Gebiet auf <param2> setzen
Param2 is out of range (must be between 0 and 255 inclusive!)=Param2 muss zwischen 0 und 255 (inklusiv) sein
@1 nodes altered=@1 Blöcke verändert
Fill the current WorldEdit region with a random mix of <node1>, ...=Das aktuelle WorldEdit-Gebiet mit einer zufälligen Mischung aus <node1>, ... füllen
Fill the current WorldEdit region with a random mix of <node1>, ...=Das aktuelle WorldEdit-Gebiet mit einer zufälligen Mischung aus <node1>, füllen
invalid search node name: @1=Ungültiger Blockname für die Suche: @1
invalid replace node name: @1=Ungültiger Blockname für das Ersetzen: @1
Replace all instances of <search node> with <replace node> in the current WorldEdit region=Alle Vorkommen von <search node> im aktuellen WorldEdit-Gebiet mit <replace node> ersetzen
@1 nodes replaced=@1 Blöcke ersetzt
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=Alle Blöcke außer <search node> im aktuellen WorldEdit-Gebiet mit <replace node> ersetzen.
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=Alle Blöcke außer <search node> im aktuellen WorldEdit-Gebiet mit <replace node> ersetzen
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Einen hohlen Würfel mit der Unterseite zentriert auf WorldEdit-Position 1 setzen, mit den Abmessungen <width>×<height>×<length>, bestehend aus <node>.
Shapes=Formen
@1 nodes added=@1 Blöcke hinzugefügt
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Einen Würfel mit der Unterseite zentriert auf WorldEdit-Position 1 setzen, mit den Abmessungen <width>×<height>×<length>, bestehend aus <node>.
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine hohle Kugel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node>
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine Kugel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node>
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine hohle Kuppel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node>
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Eine Kuppel zentriert auf WorldEdit-Position 1 setzen mit Radius <radius>, bestehend aus <node>
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Einen hohlen Zylinder zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <length>, Bodenradius <radius1> (und oberen Radius [radius2]) setzen, bestehend aus <node>
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Einen Zylinder zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <length>, Bodenradius <radius1> (und oberen Radius [radius2]) setzen, bestehend aus <node>
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Eine hohle Pyramide zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <height> setzen, bestehend aus <node>
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Eine Pyramide zentriert auf WorldEdit-Position 1 entlang der gegebenen Achse mit Länge <height> setzen, bestehend aus <node>
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=Eine Spirale zentriert auf WorldEdit-Position 1 mit Seitenlänge <length>, Höhe <height>, Wandabstand <space> setzen, bestehend aus <node>
Copy the current WorldEdit region along the given axis by <amount> nodes=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse um <amount> Blöcke kopieren
Transformations=Transformationen
@1 nodes copied=@1 Blöcke kopiert
Move the current WorldEdit region along the given axis by <amount> nodes=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse um <amount> Blöcke verschieben
@1 nodes moved=@1 Blöcke verschoben
Stack the current WorldEdit region along the given axis <count> times=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse <count>-mal stapeln
Stack the current WorldEdit region along the given axis <count> times=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse <count> mal stapeln
@1 nodes stacked=@1 Blöcke gestapelt
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Das aktuelle WorldEdit-Gebiet <count>-mal um <x>, <y>, <z> versetzt stapeln
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Das aktuelle WorldEdit-Gebiet <count> mal um <x>, <y>, <z> versetzt stapeln
invalid count: @1=Ungültige Anzahl: @1
invalid increments: @1=Ungültige Schrittweite: @1
Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=
Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=Die aktuellen WorldEdit-Positionen und -Region um einen Faktor von <stretchx>, <stretchy>, <stretchz> entlang der X-, Y- und Z-Achsen skalieren, mit Position 1 als Ausgangspunkt
invalid scaling factors: @1=Ungültige Skalierungsfaktoren: @1
@1 nodes stretched=@1 Blöcke gestreckt
Transpose the current WorldEdit region along the given axes=
Transpose the current WorldEdit region along the given axes=Das aktuelle WorldEdit-Gebiet entlang den gegebenen Achsen transponieren
invalid usage: axes must be different=Ungültige Verwendung: Achsen müssen verschieden sein
@1 nodes transposed=
Flip the current WorldEdit region along the given axis=
@1 nodes flipped=
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=
@1 nodes transposed=@1 Blöcke transponiert
Flip the current WorldEdit region along the given axis=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse spiegeln
@1 nodes flipped=@1 Blöcke gespiegelt
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=Das aktuelle WorldEdit-Gebiet um der gegebenen Achse um den Winkel <angle> rotieren (90-Grad-Schritte)
invalid usage: angle must be multiple of 90=Ungültige Verwendung: Winkel muss ein Vielfaches von 90 sein
@1 nodes rotated=@1 Blöcke gedreht
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=
@1 nodes oriented=
@1 nodes rotated=@1 Blöcke rotiert
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=Ausgerichtete Blöcke im aktuellen WorldEdit-Gebiet um die Y-Achse mit dem Winkel <angle> rotieren (90-Grad-Schritte)
@1 nodes oriented=@1 Blöcke ausgerichtet
Fix the lighting in the current WorldEdit region=Belichtung im aktuellen WorldEdit-Gebiet korrigieren
@1 nodes updated=@1 Blöcke verändert
Remove any fluid node within the current WorldEdit region=Alle flüssigen Blöcke im WorldEdit-Gebiet entfernen
Remove any plant, tree or foliage-like nodes in the selected region=Alle Pflanzen, Baum oder Laub-ähnliche Blöcke im WorldEdit-Gebiet entfernen
Remove any plant, tree or foliage-like nodes in the selected region=Alle pflanzen-, baum- oder laubähnliche Blöcke im WorldEdit-Gebiet entfernen
@1 nodes removed=@1 Blöcke entfernt
Hide all nodes in the current WorldEdit region non-destructively=Alle Blöcke im WorldEdit-Gebiet 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
Suppress all <node> in the current WorldEdit region non-destructively=
@1 nodes suppressed=
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Alle <node> Blöcke im WorldEdit-Gebiet hervorheben, indem alle anderen zerstörungsfrei versteckt werden
Suppress all <node> in the current WorldEdit region non-destructively=Alle Blöcke vom Typ <node> in der aktuellen WorldEdit-Region nicht-destruktiv verstecken
@1 nodes suppressed=@1 Blöcke versteckt
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Alle Blöcke vom Typ <node> im WorldEdit-Gebiet hervorheben, indem alle anderen nicht-destruktiv versteckt werden
@1 nodes highlighted=@1 Blöcke hervorgehoben
Restores nodes hidden with WorldEdit in the current WorldEdit region=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
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
Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=Warnung: Das Schematic hat überschüssigen freien Platz und WIRD nach dem Laden falsch ausgerichtet sein. Um dies zu vermeiden, verkleinern Sie das Gebiet, sodass es die zu speichernden Blöcke exakt umfasst.
Save the current WorldEdit region to "(world folder)/schems/<file>.we"=Das aktuelle WorldEdit-Gebiet in (Weltordner)/schems/<file>.we speichern
Schematics=Schematic
Disallowed file name: @1=Nicht erlaubter Dateiname: @1
Could not save file to "@1"=Konnte die Datei nicht unter "@1" speichern
Could not save file to "@1"=Konnte die Datei nicht unter @1 speichern
@1 nodes saved=@1 Blöcke gespeichert
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=Das aktuelle WorldEdit-Gebiet anhand der Blöcke in "(Weltordner)/schems/<file>.we" setzen
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=Das aktuelle WorldEdit-Gebiet anhand der Blöcke in (Weltordner)/schems/<file>.we setzen
Schematic empty, nothing allocated=Schematic leer, nichts reserviert
@1 nodes allocated=@1 Blöcke reserviert
Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=Blöcke aus "(Weltordner)/schems/<file>[.we[m]]" mit Position 1 als Ausgangspunkt laden
Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=Blöcke aus (Weltordner)/schems/<file>[.we[m]] mit Position 1 als Ausgangspunkt laden
Loading failed!=Laden fehlgeschlagen!
@1 nodes loaded=@1 Blöcke geladen
Executes <code> as a Lua chunk in the global namespace=
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"=Das aktuelle WorldEdit-Gebiet als Minetest-Schematic in "(Weltordner)/schems/<filename>.mts" speichern
Executes <code> as a Lua chunk in the global namespace=<code> als einen Lua-Chunk im globalen Namensraum ausführen
Code=Code
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=Führt <code> als einen Lua-Chunk im globalen Namensraum aus, wobei die Variable pos verfügbar ist, für jeden Block im aktuellen WorldEdit-Gebiet
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=Das aktuelle WorldEdit-Gebiet als Minetest-Schematic in „(Weltordner)/schems/<filename>.mts“ speichern
Failed to create Minetest schematic=Konnte Minetest-Schematic nicht erstellen
Saved Minetest schematic to @1=Minetest-Schematic in @1 gespeichert
Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=Blöcke aus "(Weltordner)/schems/<file>.mts" mit Position 1 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
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=
select Minetest schematic probability values by punching nodes=
finished Minetest schematic probability selection=
currently set node probabilities:=
invalid node probability given, not saved=
Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=Startet die Eingabe von Block-Wahrscheinlichkeiten für Minetest-Schematics, holt die Blöcke, welche Wahrscheinlichkeiten gesetzt haben, oder beendet die Eingabe von Block-Wahrscheinlichkeiten
select Minetest schematic probability values by punching nodes=Minetest-Schematic-Wahrscheinlichkeitswerte mit Hauen eingeben
finished Minetest schematic probability selection=Minetest-Schematic-Wahrscheinlichkeitsauswahl abgeschlossen
currently set node probabilities:=Momentan gesetzte Block-Wahrscheinlichkeiten:
invalid node probability given, not saved=Ungültige Block-Wahrscheinlichkeit übergeben, nicht gespeichert
Clears all objects within the WorldEdit region=Löscht alle Objekte innerhalb des WorldEdit-Gebiets
@1 objects cleared=@1 Objekte gelöscht
WARNING: this operation could affect up to @1 nodes; type //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
no operation pending=Kein Vorgang ausstehend
Abort a pending operation=Einen ausstehenden Vorgang abbrechen
WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=WorldEdit Zauberstab@nSetzen der 1. Position mit Linksklick, der 2. mit Rechtsklick
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.
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
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
Command not available: =Команда не найдена:
Enable or disable node inspection=Включить/отключить инспекцию блоков
@ -73,7 +73,7 @@ Stack the current WorldEdit region along the given axis <count> times=Размн
@1 nodes stacked=размножено @1 нод(а/ы)
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Размножить текущий WorldEdit-регион <count> раз с шагом <x>, <y>, <z> по соответствующим осям
invalid count: @1=неверное количество: @1
invalid increments: @1=неверные приращения(шаг)
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 в качестве точки отсчёта
invalid scaling factors: @1=неверные коэффициенты масштабирования: @1
@1 nodes stretched=масштабировано @1 нод(а/ы)
@ -128,7 +128,7 @@ Clears all objects within the WorldEdit region=Очистить все объе
@1 objects cleared=очищено @1 объектов
### 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=Подтвердить отложенную операцию
no operation pending=нет ожидающей операции
Abort a pending operation=Отклонить отложенную операцию
@ -148,7 +148,7 @@ Region expanded by @1 nodes=Регион увеличен на @1 нод(у/ы)
Contracts the selection in the selected absolute or relative axis=Уменьшить выделение региона по выбранной абсолютной или относительной оси
Region contracted by @1 nodes=Регион уменьшен на @1 нод(у/ы)
Select a cube with side length <size> around position 1 and run <command> on region=Выделить куб с длиной стороны <size> вокруг позиции 1 и запустите <команду> в области
invalid usage: //@1 cannot be used with cubeapply=недопустимое использование: //@1 не может быть применено в cubeapply
invalid usage: @1 cannot be used with cubeapply=недопустимое использование: @1 не может быть применено в cubeapply
Missing privileges: @1=Отсутствуют привилегии: @1
### 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
end
if pos1 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos1)
worldedit.keep_loaded(pos1, pos1)
--add marker
worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel)
@ -37,9 +35,7 @@ worldedit.mark_pos2 = function(name, region_too)
worldedit.marker2[name] = nil
end
if pos2 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos2, pos2)
worldedit.keep_loaded(pos2, pos2)
--add marker
worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel)
@ -77,15 +73,14 @@ worldedit.mark_region = function(name)
local thickness = 0.2
local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
-- TODO maybe we could skip this actually?
worldedit.keep_loaded(pos1, pos2)
local markers = {}
--XY plane markers
for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
local entpos = {x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}
local entpos = vector.new(pos1.x + sizex - 0.5, pos1.y + sizey - 0.5, z)
local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel)
if marker ~= nil then
marker:set_properties({
@ -99,7 +94,7 @@ worldedit.mark_region = function(name)
--YZ plane markers
for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
local entpos = {x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}
local entpos = vector.new(x, pos1.y + sizey - 0.5, pos1.z + sizez - 0.5)
local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel)
if marker ~= nil then
marker:set_properties({

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 safe_region_limit = tonumber(minetest.settings:get("worldedit_safe_region_limit") or "20000")
local safe_region_callback = {}
--`count` is the number of nodes that would possibly be modified
--`callback` is a callback to run when the user confirms
local function safe_region(name, count, callback)
if count < 20000 then
if safe_region_limit <= 0 or count < safe_region_limit then
return callback()
end
--save callback to call later
-- save callback to call later
safe_region_callback[name] = callback
worldedit.player_notify(name, 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
local function reset_pending(name)
@ -24,7 +37,7 @@ minetest.register_chatcommand("/y", {
func = function(name)
local callback = safe_region_callback[name]
if not callback then
worldedit.player_notify(name, S("no operation pending"))
worldedit.player_notify(name, S("no operation pending"), "error")
return
end
@ -38,7 +51,7 @@ minetest.register_chatcommand("/n", {
description = S("Abort a pending operation"),
func = function(name)
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
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
liquids_pointable = true, -- ground with only water on can be selected as well
-- ignore marker cube so the clicking on the position markers works reliably
pointabilities = {
objects = {
["worldedit:region_cube"] = false
}
},
on_use = function(itemstack, placer, pointed_thing)
if placer == nil or pointed_thing == nil then return end
local name = placer:get_player_name()
@ -34,7 +41,7 @@ minetest.register_tool(":worldedit:wand", {
local entity = pointed_thing.ref:get_luaentity()
if entity and entity.name == "worldedit:pos2" then
-- set pos1 = pos2
worldedit.pos1[name] = worldedit.pos2[name]
worldedit.pos1[name] = vector.copy(worldedit.pos2[name])
worldedit.mark_pos1(name)
end
end
@ -59,7 +66,7 @@ minetest.register_tool(":worldedit:wand", {
local entity = pointed_thing.ref:get_luaentity()
if entity and entity.name == "worldedit:pos1" then
-- set pos2 = pos1
worldedit.pos2[name] = worldedit.pos1[name]
worldedit.pos2[name] = vector.copy(worldedit.pos1[name])
worldedit.mark_pos2(name)
end
return itemstack -- nothing consumed, nothing changed

View File

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

View File

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