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