Compare commits

...

141 Commits
1.2 ... master

Author SHA1 Message Date
sfan5 28374f4f27 Run unit tests on MT 5.0 too 2024-05-26 16:08:46 +02:00
sfan5 fb7a37e87c Some minor clean ups 2024-05-26 16:02:25 +02:00
sfan5 57e7d4c488 Add polyfill for vector.copy()
closes #246
You could say I am doing this against better judgement, but this is
really a simple and easy fix for my problem.
2024-05-26 15:42:15 +02:00
sfan5 e20a5a4e09 Teach LuaCheck about vector.* fields 2024-05-26 15:37:06 +02:00
sfan5 f8442ef9fd Highlight big numbers in safe region warning 2024-05-26 15:18:13 +02:00
sfan5 86de970552 Make safe region limit configurable 2024-05-26 15:01:12 +02:00
sfan5 4c8d42bf7b Refuse too large areas in worldedit.keep_loaded()
fixes #234
2024-05-26 14:50:11 +02:00
sfan5 5a00c07c68 Add forgotten file include
fixes: acb3ecefe4
2024-04-29 09:56:01 +02:00
HybridDog cda772b18d
Add a hollowcuboid iteration helper (#190) 2024-04-23 20:35:26 +02:00
sfan5 8c758671bc Optimize //drain a bit 2024-04-22 23:19:21 +02:00
sfan5 456ce8c800 Add tests for (hollow)cube 2024-04-22 19:16:04 +02:00
sfan5 8065e3d804 Allow rotation with //mtschemplace
fixes #153
2024-04-22 18:37:14 +02:00
sfan5 60b6b205ad Add nil checks around get_player_by_name()
in case someone uses the commands from ncurses
2024-04-22 18:10:30 +02:00
sfan5 1d8d9a704f Introduce type parameter for worldedit.player_notify 2024-04-22 17:42:26 +02:00
sfan5 d13ba673fe Remove stray gitignore 2024-04-22 00:06:13 +02:00
sfan5 acb3ecefe4 Split up some files in worldedit mod 2024-04-22 00:01:31 +02:00
sfan5 dc1150fe3d Split up init.lua in worldedit_commands 2024-04-22 00:00:51 +02:00
sfan5 74663869f7 Translate worldedit_brush 2024-04-21 23:00:57 +02:00
sfan5 4b470bdae6 Put commands into categories for //help 2024-04-21 23:00:48 +02:00
sfan5 75d101116d Display aliases in //help output nicer 2024-04-20 20:39:34 +02:00
sfan5 860d4a267d Some fixes to tests 2024-04-20 20:39:34 +02:00
sfan5 372847e774 Add tests for worldedit.lua/luatransform 2024-04-20 20:39:34 +02:00
imre84 883caff58d
Make //lua work with expressions as well (#243) 2024-04-20 20:00:41 +02:00
sfan5 41d53180b1 Colorize command references in chat output 2024-04-20 14:22:46 +02:00
sfan5 602f175cc0 Make use of minetest.load_area 2024-04-20 14:03:37 +02:00
sfan5 e6fac23c53 Use worldedit.keep_loaded consistently 2024-04-20 13:59:04 +02:00
sfan5 5914eab20b Abort a delayed operation if positions change
fixes #236
2024-04-20 13:45:00 +02:00
sfan5 002dc462d6 Make wand tool not point the region marker cube 2024-04-20 13:02:27 +02:00
sfan5 a713efe051 Fix wand tool causing pos1/pos2 aliasing
fixes #245
2024-04-20 13:02:23 +02:00
luk3yx 469c3bf70b
Ignore unknown nodes when loading in saves 2024-04-16 11:35:19 +02:00
fluxionary f75700ed76
Make //clearobjects remove unknown objects (#244) 2024-03-30 00:17:51 +01:00
sfan5 575bfca67a Run tests on older MT too 2024-03-25 18:01:14 +01:00
David Leal eac05e3133
Add tooltip to WorldEdit GUI button (#240) 2024-03-25 17:58:35 +01:00
sfan5 41efbaf210 Migrate tests to new Docker image 2024-03-25 16:48:09 +01:00
YuGiOhJCJ 045c7510bf
Fix the link to the minetest forums 2024-03-01 11:45:54 +01:00
Andrey Andreyevich Bienkowski b90eeb1e68
Fix incorrect translation syntax
Worldedit was causing a crash when I used the "inspector" feature.
2023-12-13 17:34:09 +01:00
sfan5 341014f94a More complete German translation 2023-11-24 14:24:18 +01:00
sfan5 b84aa8508a Add German in-game translation (incomplete) 2023-10-30 17:24:37 +01:00
sfan5 17df0bbf71 Fix an issue with translations and update template 2023-10-30 16:53:12 +01:00
sfan5 36b14413e0 Optimize textures
done with: oxipng -Z --strip all
2023-10-28 11:37:05 +02:00
sfan5 1fc6d93112 Move worldedit_wand.png to correct place 2023-10-28 11:34:42 +02:00
Alexander Chibrikin ccfb6b4d61
Translate `worldedit_commands` (#229) 2023-10-23 20:52:04 +02:00
sfan5 8f60e6f729 Fix clear_objects not working 2023-10-08 18:21:15 +02:00
sfan5 8f86a2120c
Include minimum MT version in modpack.conf 2023-08-06 16:31:36 +02:00
nauta-turbidus b4202ea779
Trim trailing content when obtaining dedicated_server_step
This change trims anything starting with the first space from dedicated_server_step, including single-line comments following the configured value specifically, before using it for calculations. It fixes compatibility-breaking crashes with some mods/games, which change the mentioned value by adding a comment after it. Such a comment is, as far as I know, syntactically valid, and is accepted by the engine it seems.
2023-08-06 14:05:34 +02:00
HybridDog 689ff90a78
Remove unused variables and assignments 2023-06-19 18:24:31 +02:00
sfan5 bf55f52197 Give CI workflows consistent names 2023-06-11 16:09:06 +02:00
HybridDog 79e5e64c44 luacheck: Reduce the number of ignored codes
After the recent changes, luacheck can be configured to be more strict now, for example to check for inconsistent indentation and undefined variables.
2023-06-10 11:38:23 +02:00
HybridDog 375fbf3c68 Remove trailing whitespaces 2023-06-09 19:51:10 +02:00
HybridDog cc3aab00bc Fix tab after space indentations 2023-06-09 19:51:10 +02:00
HybridDog eff01bc8e7
Add code linting with luacheck (#221)
With luacheck mistakes in Lua code can be found, e.g. the use of undefined variables, and the code style can be checked.
workflow by @Panquesito7
2023-06-09 19:48:46 +02:00
HybridDog 099d5047bd Fix undefined variable access in `worldedit.metaload`
`file` in the deprecated `worldedit.metaload` function was undefined, as reported by luacheck.
2023-06-09 19:47:31 +02:00
sfan5 7f7e928dd9 Switch bare vectors to vector.new() 2023-06-09 14:49:58 +02:00
luk3yx 1a9f66f091 Fix back button in some worldedit_gui pages 2023-06-09 13:59:09 +02:00
sfan5 7a5d76a9bc Add comprehensive schematic deserialization unit tests 2023-06-09 13:02:37 +02:00
sfan5 5260f595c6 Log deserialization errors 2023-06-07 11:50:33 +02:00
sfan5 7a645eba05 Improve loading and error handling for schematics 2023-06-07 11:41:10 +02:00
sfan5 9417f2bbf1 Harden //mtschemprob against incorrect input
closes #216
2023-03-22 21:26:53 +01:00
sfan5 abc9efeeb8 Harden deserialize workaround against unexpected input
Otherwise it will stop working entirely soon when the
serialization inside Minetest is reworked.
This allows it to work at least in the cases where the original
bug (LuaJIT register limit) doesn't apply.
2022-06-06 20:39:15 +02:00
sfan5 c223ca4cec Update IRC link in README
closes #207
2021-11-15 00:16:34 +01:00
wsor4035 c8afa95542
Make worldedit_gui error non-fatal
to allow worldedit to be used in worldmods
2021-09-21 20:47:57 +02:00
sfan5 670e421f57 Rename util folder
because mod loading woes, minetest/minetest#11240
2021-09-21 01:34:10 +02:00
sfan5 770601dd5d Add automated tests for WorldEdit API functions that run under CI
uses a real Minetest instance (Docker)
currently covers most basic manipulations
2021-09-20 23:10:04 +02:00
sfan5 2f2f5a7def Use minetest.global_exists for LuaJIT check
closes #199
2021-09-12 19:35:57 +02:00
Nathan Salapat 7f87f1658e
Add param2 button to WorldEdit GUI 2021-07-23 23:34:13 +02:00
sfan5 4378750498 Use minetest.get_objects_in_area when possible 2021-04-30 19:33:27 +02:00
sfan5 2f26fb7645 WorldEdit 1.3 2021-01-31 00:49:58 +01:00
h3ndrik f1b6da3c20
Hide gui button in unified_inventory on missing privilege (#195)
this fixes #151
2020-12-05 14:12:32 +01:00
sfan5 418a30c89e Raise safe region limit to 20000 2020-10-06 13:53:22 +02:00
sfan5 2f535dd053
worldedit: Document inner working of worldedit.keep_loaded 2020-05-25 19:48:54 +02:00
sfan5 867cd6edc7 Improve node name normalization again
so that "desert stone" won't select "desert stone block"
2020-05-12 21:53:01 +02:00
sfan5 4918610c43 Fix rotate in worldedit GUI 2020-05-12 21:29:51 +02:00
sfan5 edd27636a6 Do not allow //mix with no nodes 2020-05-09 22:34:29 +02:00
sfan5 7d0811fd47 Update help texts to be consistent 2020-04-26 16:53:55 +02:00
sfan5 4d08a1cc55 Implement //cubeapply with three side lengths 2020-04-26 16:01:07 +02:00
sfan5 34844b2d38 Set static_save = false for marker entities 2020-04-16 23:50:15 +02:00
sfan5 0f5dcc790d Implement //clearcut to delete trees, plants from generated terrain
closes #165
2020-04-07 03:05:30 +02:00
sfan5 37de177f72 Fix off-by-one in //cubeapply 2020-04-07 00:46:13 +02:00
sfan5 f285a55dd5 Improve brush command error messages 2020-04-07 00:38:11 +02:00
sfan5 9b26034aea Move worldedit.marker_update
Markers are part of the UI (`worldedit_chatcommands`) and do not belong in `worldedit`.
2020-04-07 00:14:31 +02:00
sfan5 963a9f6b96 Implement //cubeapply 2020-04-06 23:32:11 +02:00
sfan5 5e8bfd5166 Improve node name normalization in case of multi-line descriptions 2020-03-29 21:55:47 +02:00
HybridDog 06a3b63578 GUI command execution: Do not ignore chatcommand return values
Additionally, this commit makes the command execution more modular,
so it is e.g. possible to implement something to disable the
many nodes confirmation after setting a checkbox in the formspec.
2020-03-09 15:33:19 +01:00
sfan5 5e47af8773 Make region marker entities withstand TNT explosions
Also fixes a crash when detonating TNT near them though this is
not our fault and the proper fix is pending in minetest_game.
2020-03-06 21:20:15 +01:00
sfan5 da51c688d2 Call minetest.deserialize with safe=true
wtf?
2020-02-06 11:53:14 +01:00
sfan5 ab93871877 Move some deserialization code around 2020-02-06 11:51:44 +01:00
sfan5 116ae25ebd Unbreak //move (closes #188) 2019-12-20 20:29:41 +01:00
sfan5 18444379a1 worldedit_gui: Improve behaviour of all input fields
continuation of 45acf09e4a
2019-12-19 18:20:24 +01:00
sfan5 974dcaa918 Improve nodename normalization in some cases 2019-12-19 16:58:33 +01:00
sfan5 abdecd4239 Fix some broken commands
This happens when you don't test.
2019-12-19 16:45:38 +01:00
sfan5 9c38b9f6ad Allow more commands for brush usage
In fact, allow all commands that are compatible by the brush:
the ones that expect exactly pos1 to be set
2019-12-19 16:44:15 +01:00
sfan5 7833b68c85 worldedit_brush: Remove "safe region" hack 2019-12-19 15:58:26 +01:00
sfan5 03bef18272 Update worldedit_shortcommands for new changes 2019-12-19 15:16:20 +01:00
sfan5 91d02f6f5b Rewrite command registration 2019-12-19 15:13:55 +01:00
sfan5 8feaf8a21d Fix area clearing step of negative //move's
fixes #187
2019-11-13 20:49:25 +01:00
sfan5 5bab991e0b Make object right-click work with wand 2019-11-10 00:40:31 +01:00
ANAND 9bb0ad0442 Fix double-definition of visual_size in entity def 2019-10-14 12:46:58 +02:00
sfan5 63a7069671 Allow easily setting pos1 + 2 to the same node using the wand
Though right-click currently doesn't work due to an engine bug.
2019-09-19 00:06:30 +02:00
sfan5 79097dc6c8 Reset region by double-punching air using the wand 2019-09-18 23:56:21 +02:00
sfan5 3bc7a0f97d worldedit_gui: Add tooltips to node search result 2019-09-18 18:55:55 +02:00
sfan5 45acf09e4a worldedit_gui: Improve behaviour of some input fields
Can't be bothered to do the rest. This stuff should be
rewritten in a modular fashion anyway.
2019-09-18 18:52:27 +02:00
sfan5 adab528f8a Fix nodename normalization with translated descriptions 2019-09-18 18:48:31 +02:00
sfan5 26b6682587 Update API docs 2019-09-17 18:44:13 +02:00
sfan5 b4826aa821 Use faster vmanip copying for //move too 2019-09-17 18:42:11 +02:00
sfan5 a0181ea897 Delete old metadata when moving regions
This matches the behaviour before introduction of the vmanip code.
2019-09-17 18:03:19 +02:00
sfan5 2a4aaae8a2 Always use faster vmanip copying code
Since the code has two arrays of the area before and after,
the copy order is totally irrelevant.
Of course, this doesn't apply to metadata and we still need
to do that backwards.
2019-09-17 01:28:53 +02:00
sfan5 f6298d7b87 Fix schematics with extra headers to parse properly
Currently no schematic format makes use of these, but that
may change so do some future proofing.
2019-09-09 20:52:15 +02:00
sfan5 68f7bcc728 Warn when "misaligned" schematics are created
The underlying issue is the size detection worldedit.allocate does
and can't be fixed or worked around in a simple way.
2019-09-09 01:07:17 +02:00
sfan5 0919f7cab3 Clean up command code for //stack, //stack2 2019-09-08 22:14:07 +02:00
sfan5 de566d2e3e Update API docs 2019-09-08 22:14:07 +02:00
sfan5 cc897150f2 Fix performance of //stack, //stack2 2019-09-08 22:14:07 +02:00
sfan5 f39a8e264d Faster moving using vmanips (in some cases) 2019-09-08 22:14:07 +02:00
sfan5 da5abec273 Faster copying using vmanips 2019-09-08 22:14:07 +02:00
sfan5 ff53a71d58 Remove outdated function check 2019-09-08 18:57:54 +02:00
sfan5 0edcd1a59d Fix crash on loading invalid schematic (fixes #160) 2019-09-06 17:56:11 +02:00
sfan5 a1b6a34e61 Use eye_height property for brush raytracing 2019-09-06 17:45:51 +02:00
sfan5 f589c988b1 Handle invalid //rotate usage gracefully, fixes #150 2019-07-17 02:35:57 +02:00
sfan5 f28d9b8d35 Update README 2019-07-17 02:23:39 +02:00
sfan5 b8453a0e0b Remove deprecated depends.txt, add descriptions to mod.conf 2019-07-17 02:15:04 +02:00
sfan5 0a16f11d93 Replace more deprecated functions 2019-07-17 02:06:29 +02:00
sfan5 b50294d10c Adjust GUI compatibility code 2019-07-17 01:44:15 +02:00
sfan5 324eb556f6 Make compatibility error in worldedit_brush fatal again
This reverts commit a97cccd2a1.
2019-07-17 01:42:28 +02:00
Panquesito7 3ffecdd9c4 Replace deprecated functions with newer ones
These commits create compatibility with MT/MTG 5.0.0+.
However, these commits may/will break with the 0.4-series.
2019-07-17 01:23:40 +02:00
Niwla23 40b49ee9bc Make placeholders not pointable 2019-07-17 01:22:12 +02:00
sfan5 4f2c7b18cc Fix //allocate with 0 nodes 2019-07-17 01:20:40 +02:00
TalkLounge b2e086f9ec Fix //load with 0 nodes (#177) 2019-06-15 16:46:12 +02:00
HybridDog d1cbd420bb serialize: Fix detecting empty metadata (#176) 2019-04-24 22:51:18 +02:00
Pedro Gimeno 0aeee79af6 Implement full facedir and color* in //orient
Thanks to entuland for the Rhotator facedir to matrix and matrix to facedir code, which helped creating the tables.
2019-04-05 00:12:02 +02:00
sfan5 f242f2f722
Change description for //about, fix #170 2018-12-08 21:52:36 +01:00
Isidor Zeuner f8e57559ec replace deprecated function call 2018-11-25 13:46:27 +01:00
sfan5 b2b2b9364d Warn about invalid //stretch usage 2018-11-15 20:49:19 +01:00
Starbeamrainbowlabs b37605943b //mix: Add node weighting support 2018-10-29 00:36:33 +01:00
beyondlimits f7256633c0 Wand: select node under or above depending on pressed sneak key. 2018-08-06 17:43:08 +02:00
sfan5 99fa0ebd75
Document //stretch, remove old documentation for //scale
scale was replaced with stretch in 674d647
2018-07-26 11:52:10 +02:00
Eugen Wesseloh a97cccd2a1 Disable worldedit_brush mod instead of throwing error (#156) 2018-04-10 09:25:08 +02:00
sfan5 3322ef90c4 Fix negative height pyramids (fixes #152) 2018-01-04 01:03:51 +01:00
Alexander Weber b259906fd0 Add support for smart_inventory 2017-11-13 09:58:42 +01:00
sfan5 12270dc094 Correct spelling of Minetest 2017-11-07 14:20:50 +01:00
sfan5 415000e797 Fix a few assignments to undeclared globals 2017-11-07 14:19:57 +01:00
sfan5 7b1deb1b61 Add configurable brush item
texture was supposed to be a brush, i suck at this :(
2017-09-17 23:47:29 +02:00
sfan5 4605596215 Fix assignment to undeclared global "sign" @ worldedit_commands/init.lua:791 2017-09-12 14:58:26 +02:00
63 changed files with 5139 additions and 2594 deletions

15
.github/workflows/check.yml vendored Normal file
View File

@ -0,0 +1,15 @@
name: "Check"
on: [push, pull_request]
jobs:
lint:
name: "Luacheck"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- 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 ./

27
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: "Test"
on: [push, pull_request]
jobs:
test:
name: "Unit Tests ${{ matrix.cfg.image }}"
runs-on: ubuntu-latest
timeout-minutes: 5
strategy:
matrix:
cfg:
- { image: 'registry.gitlab.com/minetest/minetest/server:5.0.1', mtg: false }
- { image: 'registry.gitlab.com/minetest/minetest/server:5.5.1', mtg: false }
- { image: '', mtg: true } # latest mater
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: 'minetest/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
View 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
View 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 minetestserver)
[[ -z "$mtserver" && -x ../../bin/minetestserver ]] && mtserver=../../bin/minetestserver
[ -z "$mtserver" ] && { echo "To run the test outside of Docker, an installation of minetestserver is required." >&2; exit 1; }
fi
mkdir $worldpath
printf '%s\n' mg_name=singlenode '[end_of_params]' >$worldpath/map_meta.txt
printf '%s\n' worldedit_run_tests=true max_forceloaded_blocks=9999 >$confpath
if [ -z "$mtserver" ]; then
chmod -R 777 $tempdir
[ -z "$DOCKER_IMAGE" ] && DOCKER_IMAGE="ghcr.io/minetest/minetest:master"
vol=(
-v "$confpath":/etc/minetest/minetest.conf
-v "$tempdir":/var/lib/minetest/.minetest
-v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit
)
[ -d minetest_game ] && vol+=(
-v "$PWD/minetest_game":/var/lib/minetest/.minetest/games/minetest_game
)
docker run --rm -i "${vol[@]}" "$DOCKER_IMAGE"
else
mkdir $worldpath/worldmods
ln -s "$PWD/worldedit" $worldpath/worldmods/worldedit
$mtserver --config "$confpath" --world "$worldpath" --logfile /dev/null
fi
test -f $worldpath/tests_ok || exit 1
exit 0

View File

@ -29,24 +29,26 @@ Many commands also have shorter names that can be typed faster. For example, if
### `//about`
Get information about the mod.
Get information about the WorldEdit mod.
//about
### `//inspect on/off/1/0/true/false/yes/no/enable/disable/<blank>`
### `//help [all/<cmd>]`
Get help for WorldEdit commands. `all` shows all WorldEdit commands, `<cmd>`
the help text for the given command.
//help
//help all
//help hollowpyramid
### `//inspect [on/off/1/0/true/false/yes/no/enable/disable]`
Enable or disable node inspection.
//inspect on
//inspect off
//inspect 1
//inspect 0
//inspect true
//inspect false
//inspect yes
//inspect no
//inspect enable
//inspect disable
//inspect
### `//reset`
@ -81,18 +83,19 @@ Set WorldEdit region position 2 to the player's location.
### `//p set/set1/set2/get`
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region.
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by
punching nodes, or print the current WorldEdit region.
//p set
//p set1
//p set2
//p get
### `//fixedpos set1 x y z`
### `//fixedpos set1/set2 <x> <y> <z>`
Set a WorldEdit region position to the position at (`<x>`, `<y>`, `<z>`).
Set the WorldEdit region position 1 or 2 to the position (`<x>`, `<y>`, `<z>`).
//fixedpos set1 0 0 0
//fixedpos set1 0 0 0
//fixedpos set1 -30 5 28
//fixedpos set2 1004 -200 432
@ -121,14 +124,19 @@ Set the current WorldEdit region to `<node>`.
Set the param2 value of all nodes in the current WorldEdit region to `<param2>`.
### `//mix <node1> ...`
//param2 8
Fill the current WorldEdit region with a random mix of `<node1>`, `...`.
### `//mix <node1> [count1] <node2> [count2] ...`
Fill the current WorldEdit region with a random mix of `<node1>`, `<node2>`, `...`.
Weightings can be optionally specified via the `[count1]`, `[count2]`, `...` parameters after a node name.
//mix air
//mix cactus stone glass sandstone
//mix Bronze
//mix default:cobble air
//mix stone 3 dirt 2
//mix cobblestone 8 stoneblock 2 stonebrick
### `//replace <search node> <replace node>`
@ -150,13 +158,15 @@ Replace all nodes other than `<search node>` with `<replace node>` in the curren
### `//hollowcube <width> <height> <length> <node>`
Adds a hollow cube with its ground level centered at WorldEdit position 1 with dimensions `<width>` x `<height>` x `<length>`, composed of `<node>`.
Adds a hollow cube with its ground level centered at WorldEdit position 1 with
dimensions `<width>` x `<height>` x `<length>`, composed of `<node>`.
//hollowcube 6 5 6 Diamond Block
### `//cube <width> <height> <length> <node>`
Adds a cube with its ground level centered at WorldEdit position 1 with dimensions `<width>` x `<height>` x `<length>`, composed of `<node>`.
Adds a cube with its ground level centered at WorldEdit position 1 with
dimensions `<width>` x `<height>` x `<length>`, composed of `<node>`.
//cube 6 5 6 Diamond Block
//cube 7 2 1 default:cobble
@ -195,7 +205,8 @@ Add dome centered at WorldEdit position 1 with radius `<radius>`, composed of `<
### `//hollowcylinder x/y/z/? <length> <radius1> [radius2] <node>`
Add hollow cylinder at WorldEdit position 1 along the x/y/z/? axis with length `<length>`, base radius `<radius1>` (and top radius `[radius2]`), composed of `<node>`.
Add hollow cylinder at WorldEdit position 1 along the given axis with length `<length>`,
base radius `<radius1>` (and top radius `[radius2]`), composed of `<node>`.
Despite its name this command allows you to create cones (`radius2` = 0) as well as any shapes inbetween (0 < `radius2` < `radius1`).
Swapping `radius1` and `radius2` will create the same object but upside-down.
@ -211,7 +222,8 @@ Swapping `radius1` and `radius2` will create the same object but upside-down.
### `//cylinder x/y/z/? <length> <radius1> [radius2] <node>`
Add cylinder at WorldEdit position 1 along the x/y/z/? 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>`.
Can also create shapes other than cylinders, e.g. cones (see documentation above).
//cylinder x +5 8 Bronze Block
@ -223,18 +235,18 @@ Can also create shapes other than cylinders, e.g. cones (see documentation above
//cylinder x 6 0 5 Dirt
//cylinder z 20 10 20 default:desert_stone
### `//hollowpyramid x/y/z? <height> <node>`
### `//hollowpyramid x/y/z/? <height> <node>`
Add hollow pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height `<height>`, composed of `<node>`.
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height `<height>` composed of `<node>`.
//hollowpyramid x 8 Diamond Block
//hollowpyramid y -5 glass
//hollowpyramid z 2 mesecons:wire_00000000_off
//hollowpyramid ? 12 mesecons:wire_00000000_off
### `//pyramid x/y/z? <height> <node>`
### `//pyramid x/y/z/? <height> <node>`
Add pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height `<height>`, composed of `<node>`.
Add pyramid centered at WorldEdit position 1 along the given axis with height `<height>` composed of `<node>`.
//pyramid x 8 Diamond Block
//pyramid y -5 glass
@ -243,7 +255,8 @@ Add pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height
### `//spiral <length> <height> <spacer> <node>`
Add spiral centered at WorldEdit position 1 with side length `<length>`, height `<height>`, space between walls `<spacer>`, composed of `<node>`.
Add spiral centered at WorldEdit position 1 with side length `<length>`,
height `<height>`, space between walls `<spacer>`, composed of `<node>`.
//spiral 20 5 3 Diamond Block
//spiral 5 2 1 glass
@ -251,7 +264,7 @@ Add spiral centered at WorldEdit position 1 with side length `<length>`, height
### `//copy x/y/z/? <amount>`
Copy the current WorldEdit region along the x/y/z/? axis by `<amount>` nodes.
Copy the current WorldEdit region along the given axis by `<amount>` nodes.
//copy x 15
//copy y -7
@ -260,7 +273,7 @@ Copy the current WorldEdit region along the x/y/z/? axis by `<amount>` nodes.
### `//move x/y/z/? <amount>`
Move the current WorldEdit positions and region along the x/y/z/? axis by `<amount>` nodes.
Move the current WorldEdit positions and region along the given axis by `<amount>` nodes.
//move x 15
//move y -7
@ -269,7 +282,7 @@ Move the current WorldEdit positions and region along the x/y/z/? axis by `<amou
### `//stack x/y/z/? <count>`
Stack the current WorldEdit region along the x/y/z/? axis `<count>` times.
Stack the current WorldEdit region along the given axis `<count>` times.
//stack x 3
//stack y -1
@ -283,35 +296,34 @@ Stack the current WorldEdit region `<count>` times by offset `<x>`, `<y>`, `<z>`
//stack2 5 3 8 2
//stack2 1 -1 -1 -1
### `//scale <factor>`
### `//stretch <stretchx> <stretchy> <stretchz>`
Scale the current WorldEdit positions and region by a factor of positive integer `<factor>` with position 1 as the origin.
Scale the current WorldEdit positions and region by a factor of
`<stretchx>`, `<stretchy>`, `<stretchz>` along the X, Y, and Z axes,
respectively, with position 1 as the origin.
//scale 2
//scale 1
//scale 10
//stretch 2 2 2
//stretch 1 2 1
//stretch 10 20 1
### `//transpose x/y/z/? x/y/z/?`
Transpose the current WorldEdit positions and region along the x/y/z/? and x/y/z/? axes.
Transpose the current WorldEdit positions and region along given axes.
//transpose x y
//transpose x z
//transpose y z
//transpose ? y
### `//flip x/y/z/?`
Flip the current WorldEdit region along the x/y/z/? axis.
Flip the current WorldEdit region along the given axis.
//flip x
//flip y
//flip z
//flip ?
### `//rotate x/y/z/? <angle>`
Rotate the current WorldEdit positions and region along the x/y/z/? axis by angle `<angle>` (90 degree increment).
Rotate the current WorldEdit positions and region along the given axis by angle `<angle>` (90 degree increment).
//rotate x 90
//rotate y 180
@ -339,6 +351,13 @@ Removes any fluid node within the current WorldEdit region.
//drain
### `//clearcut`
Removes any plant, tree or foilage-like nodes in the selected region.
The idea is to remove anything that isn't part of the terrain, leaving a "natural" empty space ready for building.
//clearcut
### `//hide`
Hide all nodes in the current WorldEdit region non-destructively.
@ -347,7 +366,7 @@ Hide all nodes in the current WorldEdit region non-destructively.
### `//suppress <node>`
Suppress all <node> in the current WorldEdit region non-destructively.
Suppress all `<node>` in the current WorldEdit region non-destructively.
//suppress Diamond Block
//suppress glass
@ -355,7 +374,7 @@ Suppress all <node> in the current WorldEdit region non-destructively.
### `//highlight <node>`
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively.
Highlight `<node>` in the current WorldEdit region by hiding everything else non-destructively.
//highlight Diamond Block
//highlight glass
@ -390,17 +409,18 @@ 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.add_node(pos, {name="default:stone"})
//luatransform if minetest.get_node(pos).name == "air" then minetest.add_node(pos, {name="default:water_source"})
//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
### `//mtschemcreate <file>`
@ -409,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`
@ -429,37 +450,61 @@ Clears all objects within the WorldEdit region.
//clearobjects
### `//shift x/y/z/?/up/down/left/right/front/back [+|-]<amount>`
### `//shift x/y/z/?/up/down/left/right/front/back [+/-]<amount>`
Shifts the selection area by `[+|-]<amount>` without touching its contents. The shifting axis can be absolute (`x/y/z`) or
relative (`up/down/left/right/front/back`).
Shifts the selection area by `[+|-]<amount>` without moving its contents.
The shifting axis can be absolute (`x/y/z`) or relative (`up/down/left/right/front/back`).
//shift left 5
### `//expand [+|-]x/y/z/?/up/down/left/right/front/back <amount> [reverse-amount]`
### `//expand [+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]`
Expands the selection by `<amount>` in the selected absolute or relative axis. If specified, the selection can be expanded in the
opposite direction over the same axis by `[reverse-amount]`.
Expands the selection by `<amount>` in the selected absolute or relative axis.
If specified, the selection can be expanded in the opposite direction over the same axis by `[reverse amount]`.
//expand right 7 5
### `//contract [+|-]x/y/z/?/up/down/left/right/front/back <amount> [reverse-amount]`
### `//contract [+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]`
Contracts the selection by `<amount>` in the selected absolute or relative axis. If specified, the selection can be contracted in the
opposite direction over the same axis by `[reverse-amount]`.
Contracts the selection by `<amount>` in the selected absolute or relative axis.
If specified, the selection can be contracted in the opposite direction over the same axis by `[reverse amount]`.
//expand right 7 5
### `//outset [hv] <amount>`
### `//outset [h/v] <amount>`
Expands the selection in all directions by `<amount>`. If specified, the selection can be expanded horizontally in the x and z axes `[h]`
or vertically in the y axis `[v]`.
Expands the selection in all directions by `<amount>`. If specified,
the selection can be expanded horizontally in the x and z axes using `h`
or vertically in the y axis using `v`.
//outset v 5
### `//inset [hv] <amount>`
### `//inset [h/v] <amount>`
Contracts the selection in all directions by `<amount>`. If specified, the selection can be contracted horizontally in the x and z axes `[h]`
or vertically in the y axis `[v]`.
Contracts the selection in all directions by `<amount>`. If specified,
the selection can be contracted horizontally in the x and z axes using `h`
or vertically in the y axis using `v`.
//outset v 5
//inset h 5
### `//brush none/(<command> [parameters])`
Assigns the given `<command>` to the currently held brush item, it will be ran with the first pointed solid node (as determined via raycast) as
WorldEdit position 1 when using that specific brush item.
Passing `none` instead clears the command assigned to the currently held brush item.
Note that this functionality requires the `worldedit_brush` mod enabled.
//brush cube 8 8 8 Cobblestone
//brush spr 12 glass
//brush none
### `//cubeapply <size>/(<sizex> <sizey> <sizez>) <command> [parameters]`
Selects a cube with side length of `<size>` around the WorldEdit position 1 and runs the given `<command>` on the newly selected region.
If `<sizex>`, `<sizey>` and `<sizez>` are given, they instead specify the length of the cuboid in X, Y, Z direction.
This is mostly useful for brushes since it allows commands such as `//replace` to be ran, but it can also be used standalone.
//cubeapply 10 replaceinverse air default:water_source
//brush cubeapply 15 drain
//brush cubeapply 12 3 12 drain
//brush cubeapply 1 deleteblocks

101
README.md
View File

@ -1,8 +1,8 @@
WorldEdit v1.2
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).
@ -11,47 +11,52 @@ For more information, see the [forum topic](https://forum.minetest.net/viewtopic
Installing
----------
If you are using Windows, consider installing this mod using [MODSTER](https://forum.minetest.net/viewtopic.php?id=6497), a super simple mod installer that will take care of everything for you. If you are using MODSTER, skip directly to step 6 in the instructions below.
There is a nice installation guide over at the [Minetest Wiki](http://wiki.minetest.com/wiki/Installing_mods). Here is a short summary:
1. Download the mod from the [official releases page](https://github.com/Uberi/Minetest-WorldEdit/releases). The download links are labelled "Source Code". If you are using Windows, you will probably want to download the ZIP version.
2. You should have a file named `SOMETHING.zip` or `SOMETHING.tar.gz`.
1. Download the mod from the [official releases page](https://github.com/Uberi/Minetest-WorldEdit/releases). The download links are labelled "Source Code". If you are using Windows, you'll want to download the ZIP version.
2. You should have a file named `Minetest-WorldEdit-x.x.zip`.
3. Extract this file using your archiver of choice. If you are using Windows, open the ZIP file and move the folder inside to a safe place outside of the ZIP file.
4. Make sure that you now have a folder with a file named README.md inside it. If you just have another folder inside this folder, use this nested folder instead.
4. Make sure that you now have a folder with a file named README.md inside it. If you just have another folder inside this folder, use the nested folder instead.
5. Move this folder into the `MINETEST_FOLDER/mods` folder, where `MINETEST_FOLDER` is the folder Minetest is located in.
6. Open Minetest to a world selection screen.
7. Select a world you want to use WorldEdit in by left clicking on it once, and press the **Configure** button.
8. You should have a mod selection screen. Select the one named something like `Minetest-WorldEdit` by left clicking once and press the **Enable MP** button.
7. Select a world you want to use WorldEdit in by left clicking on it once and press the **Configure** button.
8. You should have a mod selection screen. Select the one named something like `Minetest-WorldEdit` by left clicking once and press the **Enable Modpack** button.
9. Press the **Save** button. You can now use WorldEdit in that world. Repeat steps 7 to 9 to enable WorldEdit for other worlds too.
If you are having trouble, try asking for help in the [IRC channel](http://webchat.freenode.net/?channels=#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).
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
-----
WorldEdit works primarily through the WorldEdit GUI and chat commands. Depending on your key bindings, you can invoke chat entry with the "t" key, and open the chat console with the "F10" key.
WorldEdit works primarily through the WorldEdit GUI and chat commands. Depending on your key bindings, you can invoke chat entry with the "T" key and open the chat console with the "F10" key.
WorldEdit has a huge potential for abuse by untrusted players. Therefore, users will not be able to use WorldEdit unless they have the `worldedit` privelege. This is available by default in single player, but in multiplayer the permission must be explicitly given by someone with the right credentials, using the follwoing chat command: `/grant <player name> worldedit`. This privelege can later be removed using the following chat command: `/revoke <player name> worldedit`.
WorldEdit has a huge potential for abuse by untrusted players. Therefore, users will not be able to use WorldEdit unless they have the `worldedit` privilege.
This is available by default in singleplayer, but in multiplayer the permission must be explicitly given by someone with the right credentials,
using the following chat command: `/grant <player name> worldedit`. This privilege can later be removed using the following chat command: `/revoke <player name> worldedit`.
Certain functions/commands such as WorldEdit `//lua` and `//luatransform` chat commands additionally require the `server` privilege. This is because it is extremely dangerous to give access to these commands to untrusted players, since they essentially are able to control the computer the server is running on. Give this privilege only to people you trust with your computer.
Certain functions/commands such as WorldEdit `//lua` and `//luatransform` chat commands additionally require the `server` privilege.
This is because it is extremely dangerous to give access to these commands to untrusted players, since they essentially are able to control the computer the server is running on.
Give this privilege only to people you trust with your computer.
For in-game information about these commands, type `/help <command name>` in the chat. For example, to learn more about the `//copy` command, simply type `/help /copy` to display information relevant to copying a region.
For in-game information about these commands, type `//help <command name>` in the chat. For example, to learn more about the `//copy` command, simply type `//help copy` to display information relevant to copying a region.
Interface
---------
WorldEdit is accessed in-game in two main ways.
The GUI adds a screen to each player's inventory that gives access to various WorldEdit functions. The [tutorial](Tutorial.md) and the [Chat Commands Reference](ChatCommands.md) may be helpful in learning to use it.
The GUI adds a screen to each player's inventory that gives access to various WorldEdit functions. The [tutorial](Tutorial.md) may be helpful in learning to use it.
The chat interface adds many chat commands that perform various WorldEdit powered tasks. It is documented in the [Chat Commands Reference](ChatCommands.md).
Compatibility
-------------
This mod supports Minetest versions 0.4.8 and newer. Older versions of WorldEdit may work with older versions of Minetest, but are not recommended or supported.
This mod supports Minetest versions 5.0 and newer. Older versions of WorldEdit may work with older versions of Minetest, but are not recommended or supported.
WorldEdit works quite well with other mods, and does not have any known mod conflicts.
WorldEdit works quite well with other mods and does not have any known mod conflicts.
WorldEdit GUI requires one of [sfinv](https://github.com/minetest/minetest_game/tree/master/mods/sfinv) (included in minetest_game since 0.4.15), [Unified Inventory](https://forum.minetest.net/viewtopic.php?id=3933) or [Inventory++](https://forum.minetest.net/viewtopic.php?id=6204).
WorldEdit GUI requires one of [sfinv](https://github.com/minetest/minetest_game/tree/master/mods/sfinv) (included in minetest_game),
[Unified Inventory](https://forum.minetest.net/viewtopic.php?t=12767),
[Inventory++](https://forum.minetest.net/viewtopic.php?id=6204) or [Smart Inventory](https://forum.minetest.net/viewtopic.php?t=16597).
If you use any other inventory manager mods, note that they may conflict with the WorldEdit GUI. If this is the case, it may be necessary to disable them.
@ -59,9 +64,9 @@ WorldEdit API
-------------
WorldEdit exposes all significant functionality in a simple Lua interface.
Adding WorldEdit to the file "depends.txt" in your mod gives you access to all of the `worldedit` functions. The API is useful for tasks such as high-performance node manipulation, alternative interfaces, and map creation.
Adding WorldEdit as a dependency to your mod gives you access to all of the `worldedit` functions. The API is useful for tasks such as high-performance node manipulation, alternative interfaces and map creation.
AGPLv3 compatible mods may further include WorldEdit files in their own mods. This may be useful if a modder wishes to completely avoid any dependencies on WorldEdit. Note that it is required to give credit to the authors.
AGPLv3 compatible mods may further include WorldEdit files in their own mods. This can be useful if a modder wishes to completely avoid any dependency on WorldEdit. Note that it is required to give credit to the authors in this case.
This API is documented in the [WorldEdit API Reference](WorldEdit%20API.md).
@ -79,13 +84,16 @@ Nodes
-----
Node names are required for many types of commands that identify or modify specific types of nodes. They can be specified in a number of ways.
First, by description - the tooltip that appears when hovering over the item in an inventory. This is case insensitive and includes values such as "Cobblestone" and "bronze block". Note that certain commands (namely, `//replace` and `//replaceinverse`) do not support descriptions that contain spaces in the `<searchnode>` field.
First, by description - the tooltip that appears when hovering over the item in an inventory. This is case insensitive and includes values such as "Cobblestone" and "bronze block".
Note that certain commands (namely, `//replace` and `//replaceinverse`) do not support descriptions that contain spaces in the `<searchnode>` field.
Second, by name - the node name that is defined by code, but without the mod name prefix. This is case sensitive and includes values such as "piston_normal_off" and "cactus". Nodes defined in the `default` mod always take precedence over other nodes when searching for the correct one, and if there are multiple possible nodes (such as "a:celery" and "b:celery"), one is chosen in no particular order.
Second, by name - the node name that is defined by code, but without the mod name prefix. This is case sensitive and includes values such as "piston_normal_off" and "cactus".
If there are multiple possible nodes (such as "a:celery" and "b:celery"), one is chosen in no particular order.
Finally, by full name - the unambiguous identifier of the node, prefixes and all. This is case sensitive and includes values such as "default:stone" and "mesecons:wire_00000000_off".
The node name "air" can be used anywhere a normal node name can, and acts as a blank node. This is useful for clearing or removing nodes. For example, `//set air` would remove all the nodes in the current WorldEdit region. Similarly, `//sphere 10 air`, when WorldEdit position 1 underground, would dig a large sphere out of the ground.
The node name "air" can be used anywhere a normal node name can and acts as a blank node. This is useful for clearing or removing nodes.
For example, `//set air` would remove all the nodes in the current WorldEdit region. Similarly, `//sphere 10 air`, when WorldEdit position 1 underground, would dig a large sphere out of the ground.
Regions
-------
@ -93,11 +101,11 @@ Most WorldEdit commands operate on regions. Regions are a set of two positions t
Each positions together define two opposing corners of the cube. With two opposing corners it is possible to determine both the location and dimensions of the region.
Regions are not saved between server restarts. They start off as empty regions, and cannot be used with most WorldEdit commands until they are set to valid values.
Regions are not saved between server restarts. They start off as empty regions and cannot be used with most WorldEdit commands until they are set to valid values.
Markers
-------
Entities are used to mark the location of the WorldEdit regions. They appear as boxes containing the number 1 or 2, and represent position 1 and 2 of the WorldEdit region, respectively.
Entities are used to mark the location of the WorldEdit regions. They appear as boxes containing the number 1 or 2 and represent the first and second position of the WorldEdit region, respectively.
To remove the entities, simply punch them. This does not reset the positions themselves.
@ -105,9 +113,11 @@ Schematics
----------
WorldEdit supports two different types of schematics.
The first is the WorldEdit Schematic format, with the file extension ".we", and in some older versions, ".wem". There have been several previous versions of the WorldEdit Schematic format, but WorldEdit is capable of loading any past versions, and will always support them - there is no need to worry about schematics becoming obselete.
The first is the WorldEdit Schematic format, with the file extension ".we", and in some older versions, ".wem".
There have been several previous versions of the WorldEdit Schematic format, but WorldEdit is capable of loading any past versions, and will always support them - there is no need to worry about schematics becoming obsolete.
As of version 5, WorldEdit schematics include a header. The header is seperated from the content by a colon (`:`). It contains fields seperated by commas (`,`). Currently only one field is used, which contains the version in ASCII decimal.
As of version 5, WorldEdit schematics include a header. The header is seperated from the content by a colon (`:`). It may contain fields seperated by commas (`,`).
Currently only one field is used, which contains the version as an ASCII decimal.
The current version of the WorldEdit Schematic format is essentially an array of node data tables in Lua 5.1 table syntax preceded by a header.
Specifically it looks like this:
@ -130,32 +140,53 @@ The ordering of the values and minor aspects of the syntax, such as trailing com
The WorldEdit Schematic format is accessed via the WorldEdit API, or WorldEdit serialization chat commands such as `//serialize` and `//deserialize`.
The second is the Minetest Schematic format (MTS). The details of this format may be found in the Minetest documentation and are out of the scope of this document. Access to this format is done via specialized MTS commands such as `//mtschemcreate` and `//mtschemplace`.
The second is the Minetest Schematic format (MTS). The details of this format may be found in the Minetest documentation and are out of the scope of this document.
Access to this format is done via specialized MTS commands such as `//mtschemcreate` and `//mtschemplace`.
Authors
-------
WorldEdit would not be possible without the contributions of many developers and designers. Below, they are listed alphabetically:
cheapie
Alexander Weber
ANAND
beyondlimits
Carter Kolwey
cornernote
cyisfor
danierukun
Cy
Daniel Sosa
electricface
est31
Eugen Wesseloh
h3ndrik
HybridDog
Isidor Zeuner
Jean-Patrick Guerrero
Joseph Pickard
kaeza
khonkhortisan
pickardjoe
kilbith
KodexKy
Kyle
MT-Modder
Niwla23
Panquesito7
Pedro Gimeno
Rui
Sebastien Ponce
sfan5
ShadowNinja
shivajiva101
spillz
Starbeamrainbowlabs
TalkLounge
tenplus1
Uberi/Temperest
Wuzzy
License
-------
Copyright 2013 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote).
Copyright (c) 2012 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote).
This mod is licensed under the [GNU Affero General Public License](http://www.gnu.org/licenses/agpl-3.0.html).
This mod is licensed under the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.html).
Basically, this means everyone is free to use, modify, and distribute the files, as long as these modifications are also licensed the same way.
Most importantly, the Affero variant of the GPL requires you to publish your modifications in source form, even if the mod is run only on the server, and not distributed.

View File

@ -1,13 +1,11 @@
WorldEdit Tutorial
==================
This is a step-by-step tutorial outlining the basic usage of WorldEdit. For more information, see the [README](README.md).
This is a step-by-step tutorial outlining the basic usage of WorldEdit.
Let's start with a few assumptions:
* You have a compatible version of Minetest working.
* See the [README](README.md) for compatibility information.
* You have a compatible version of Minetest working, that is 5.0 or later.
* You have WorldEdit installed as a mod.
* If using Windows, [MODSTER](https://forum.minetest.net/viewtopic.php?pid=101463) makes installing mods totally painless.
* Simply download the file, extract the archive, and move it to the correct mod folder for Minetest.
* See the installation instructions in [README](README.md) if you need more details.
* You are familiar with the basics of the game.
@ -59,7 +57,7 @@ Look at the place between the two markers: it is now filled with MESE blocks!
The `//set <node>` command fills the region with whatever node you want. It is a region-oriented command, which means it works inside the WorldEdit region only.
Now, try a few different variations, such as `//set torch`, `//set cobble`, and `//set water`.
Now, try a few different variations, such as `//set torch`, `//set cobble`, and `//set water source`.
### WorldEdit GUI
@ -75,7 +73,7 @@ Look at the place between the two markers: it is now filled with MESE blocks!
The "Set Nodes" function fills the region with whatever node you want. It is a region-oriented command, which means it works inside the WorldEdit region only.
Now, try a few different variations on the node name, such as "torch", "cobble", and "water".
Now, try a few different variations on the node name, such as "torch", "cobble", and "water source".
Step 3: Position commands
-------------------------
@ -117,4 +115,4 @@ A very useful command to check out is the `//save <schematic>` command, which ca
This only scratches the surface of what WorldEdit is capable of. Most of the functions in the WorldEdit GUI correspond to chat commands, and so the [Chat Commands Reference](ChatCommands.md) may be useful if you get stuck.
It is helpful to explore the various buttons in the interface and check out what they do. Learning the chat command interface is also useful if you use WorldEdit intensively - an experienced chat command user can usually work faster than an experienced WorldEdit GUI user.
It is helpful to explore the various buttons in the interface and check out what they do. Learning the chat command interface is also useful if you use WorldEdit intensively - an experienced chat command user can usually work faster than an experienced WorldEdit GUI user.

View File

@ -24,6 +24,7 @@ Contained in manipulations.lua, this module allows several node operations to be
### count = worldedit.set(pos1, pos2, node_name)
Sets a region defined by positions `pos1` and `pos2` to `node_name`. To clear a region, use "air" as the value of `node_name`.
If `node_name` is a list of nodes, each set node is randomly picked from it.
Returns the number of nodes set.
@ -54,6 +55,7 @@ Returns the number of nodes copied.
### count = worldedit.copy2(pos1, pos2, off)
Copies the region defined by positions `pos1` and `pos2` by the offset vector `off`.
Note that the offset needs to be big enough that there is no overlap.
Returns the number of nodes copied.
@ -72,6 +74,7 @@ Returns the number of nodes stacked.
### count = worldedit.stack2(pos1, pos2, direction, amount)
Duplicates the region defined by positions `pos1` and `pos2` `amount` times with offset vector `direction`.
Note that the offset vector needs to be big enough that there is no overlap.
Returns the number of nodes stacked.
@ -193,7 +196,7 @@ Returns the number of nodes restored.
Serialization
-------------
Contained in serialization.lua, this module allows regions of nodes to be serialized and deserialized to formats suitable for use outside MineTest.
Contained in serialization.lua, this module allows regions of nodes to be serialized and deserialized to formats suitable for use outside Minetest.
### version, extra_fields, content = worldedit.read_header(value)
@ -224,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)

3
modpack.conf Normal file
View File

@ -0,0 +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

View File

4
settingtypes.txt Normal file
View File

@ -0,0 +1,4 @@
# For operations that potentially affect more than the specified amount of nodes
# WorldEdit will require additional confirmation via //y before proceeding.
# Set to 0 to disable the confirmation in all cases.
worldedit_safe_region_limit (Limit for safe region warning) int 20000

View File

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

View File

@ -1,12 +1,21 @@
--- Common functions [INTERNAL]. All of these functions are internal!
-- @module worldedit.common
-- Polyfill for vector.copy (added in 5.5.0)
if not vector.copy then
local vnew = vector.new
vector.copy = function(v)
return vnew(v.x, v.y, v.z)
end
end
--- Copies and modifies positions `pos1` and `pos2` so that each component of
-- `pos1` is less than or equal to the corresponding component of `pos2`.
-- Returns the new positions.
function worldedit.sort_pos(pos1, pos2)
pos1 = {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,9 +54,22 @@ function worldedit.get_axis_others(axis)
end
-- Create a vmanip and read the area from map, this causes all
-- MapBlocks to be loaded into memory synchronously.
-- This doesn't actually *keep* them loaded, unlike the name implies.
function worldedit.keep_loaded(pos1, pos2)
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
-- rough estimate, a MapNode is 4 bytes in the engine
if worldedit.volume(pos1, pos2) > 268400000 then
print("[WorldEdit] Requested to load an area bigger than 1GB, refusing. The subsequent operation may fail.")
return
end
if minetest.load_area then
-- same effect but without unnecessary data copying
minetest.load_area(pos1, pos2)
else
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
end
end
@ -62,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
@ -107,8 +129,79 @@ end
function mh.finish(manip, data)
-- Update map
manip:set_data(data)
if data ~= nil then
manip:set_data(data)
end
manip:write_to_map()
manip:update_map()
end
-- returns an iterator that returns voxelarea indices for a hollow cuboid
function mh.iter_hollowcuboid(area, minx, miny, minz, maxx, maxy, maxz)
local i = area:index(minx, miny, minz) - 1
local xrange = maxx - minx + 1
local nextaction = i + 1 + xrange
local do_hole = false
local y = 0
local ydiff = maxy - miny
local ystride = area.ystride
local ymultistride = ydiff * ystride
local z = 0
local zdiff = maxz - minz
local zstride = area.zstride
local zcorner = true
return function()
-- continue i until it needs to jump ystride
i = i + 1
if i ~= nextaction then
return i
end
-- add the x offset if y (and z) are not 0 or maxy (or maxz)
if do_hole then
do_hole = false
i = i + xrange - 2
nextaction = i + 1
return i
end
-- continue y until maxy is exceeded
y = y+1
if y ~= ydiff + 1 then
i = i + ystride - xrange
if zcorner
or y == ydiff then
nextaction = i + xrange
else
nextaction = i + 1
do_hole = true
end
return i
end
-- continue z until maxz is exceeded
z = z+1
if z == zdiff + 1 then
-- hollowcuboid finished, return nil
return
end
-- set i to index(minx, miny, minz + z) - 1
i = i + zstride - (ymultistride + xrange)
zcorner = z == zdiff
-- y is 0, so traverse the xs
y = 0
nextaction = i + xrange
return i
end
end
function mh.iterp_hollowcuboid(area, minp, maxp)
return mh.iter_hollowcuboid(area, minp.x, minp.y, minp.z,
maxp.x, maxp.y, maxp.z)
end

View File

@ -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

View File

@ -2,16 +2,16 @@
worldedit.cuboid_volumetric_expand = function(name, amount)
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
return false, "Undefined cuboid"
end
local delta1 = vector.new()
local delta2 = vector.new()
local delta_dir1
local delta_dir2
delta1 = vector.add(delta1, amount)
delta2 = vector.add(delta2, amount)
delta_dir1, delta_dir2 = worldedit.get_expansion_directions(pos1, pos2)
@ -19,7 +19,7 @@ worldedit.cuboid_volumetric_expand = function(name, amount)
delta2 = vector.multiply(delta2, delta_dir2)
worldedit.pos1[name] = vector.add(pos1, delta1)
worldedit.pos2[name] = vector.add(pos2, delta2)
return true
end
@ -28,18 +28,18 @@ end
worldedit.cuboid_linear_expand = function(name, axis, direction, amount)
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
return false, "undefined cuboid"
end
if direction ~= 1 and direction ~= -1 then
return false, "invalid marker"
end
local marker = worldedit.marker_get_closest_to_axis(name, axis, direction)
local deltavect = vector.new()
if axis == 'x' then
deltavect.x = amount * direction
elseif axis == 'y' then
@ -49,7 +49,7 @@ worldedit.cuboid_linear_expand = function(name, axis, direction, amount)
else
return false, "invalid axis"
end
worldedit.marker_move(name, marker, deltavect)
return true
end
@ -59,11 +59,13 @@ end
worldedit.cuboid_shift = function(name, axis, amount)
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
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
@ -76,7 +78,7 @@ worldedit.cuboid_shift = function(name, axis, amount)
else
return false, "invalid axis"
end
return true
end
@ -86,7 +88,7 @@ worldedit.marker_move = function(name, marker, deltavector)
if marker ~= 1 and marker ~= 2 then
return false
end
if marker == 1 then
local pos = worldedit.pos1[name]
worldedit.pos1[name] = vector.add(deltavector, pos)
@ -94,23 +96,8 @@ worldedit.marker_move = function(name, marker, deltavector)
local pos = worldedit.pos2[name]
worldedit.pos2[name] = vector.add(deltavector, pos)
end
return true
end
-- Updates the location ingame of the markers
worldedit.marker_update = function(name, marker)
if marker == nil then
worldedit.mark_pos1(name)
worldedit.mark_pos2(name)
elseif marker == 1 then
worldedit.mark_pos1(name)
elseif marker == 2 then
worldedit.mark_pos2(name)
else
minetest.debug(
"worldedit: Invalid execution of function update_markers")
end
return true
end
@ -149,10 +136,11 @@ 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):getpos()
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])
if dist1 < dist2 then
return 1
else
@ -165,7 +153,7 @@ end
worldedit.marker_get_closest_to_axis = function(name, axis, direction)
local pos1 = vector.new()
local pos2 = vector.new()
if direction ~= 1 and direction ~= -1 then
return nil
end
@ -200,20 +188,20 @@ worldedit.marker_get_closest_to_axis = function(name, axis, direction)
end
-- Translates up, down, left, right, front, back to their corresponding axes and
-- Translates up, down, left, right, front, back to their corresponding axes and
-- directions according to faced direction
worldedit.translate_direction = function(name, direction)
local axis, dir = worldedit.player_axis(name)
local resaxis, resdir
if direction == "up" then
return 'y', 1
end
if direction == "down" then
return 'y', -1
end
if direction == "front" then
if axis == "y" then
resaxis = nil
@ -223,7 +211,7 @@ worldedit.translate_direction = function(name, direction)
resdir = dir
end
end
if direction == "back" then
if axis == "y" then
resaxis = nil
@ -233,7 +221,7 @@ worldedit.translate_direction = function(name, direction)
resdir = -dir
end
end
if direction == "left" then
if axis == 'x' then
resaxis = 'z'
@ -243,7 +231,7 @@ worldedit.translate_direction = function(name, direction)
resdir = -dir
end
end
if direction == "right" then
if axis == 'x' then
resaxis = 'z'
@ -253,6 +241,6 @@ worldedit.translate_direction = function(name, direction)
resdir = dir
end
end
return resaxis, resdir
end
end

View File

@ -1,7 +1,7 @@
--- Worldedit.
--- WorldEdit mod for the Minetest engine
-- @module worldedit
-- @release 1.2
-- @copyright 2013 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote).
-- @release 1.3
-- @copyright 2012 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote)
-- @license GNU Affero General Public License version 3 (AGPLv3)
-- @author sfan5
-- @author Anthony Zang (Uberi/Temperest)
@ -11,18 +11,10 @@
worldedit = {}
local ver = {major=1, minor=2}
local ver = {major=1, minor=3}
worldedit.version = ver
worldedit.version_string = string.format("%d.%d", ver.major, ver.minor)
if not minetest.get_voxel_manip then
local err_msg = "This version of WorldEdit requires Minetest 0.4.8 or later! You have an old version."
minetest.log("error", string.rep("#", 128))
minetest.log("error", err_msg)
minetest.log("error", string.rep("#", 128))
error(err_msg)
end
local path = minetest.get_modpath(minetest.get_current_modname())
local function load_module(path)
@ -35,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")
@ -42,7 +35,11 @@ load_module(path .. "/compatibility.lua")
load_module(path .. "/cuboid.lua")
if minetest.setting_getbool("log_mods") then
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

View File

@ -76,9 +76,6 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
local count = 0
--- TODO: This could be shortened by checking `inverse` in the loop,
-- but that would have a speed penalty. Is the penalty big enough
-- to matter?
if not inverse then
for i in area:iterp(pos1, pos2) do
if data[i] == search_id then
@ -101,31 +98,6 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
end
--- Duplicates a region `amount` times with offset vector `direction`.
-- Stacking is spread across server steps, one copy per step.
-- @return The number of nodes stacked.
function worldedit.stack2(pos1, pos2, direction, amount, finished)
local i = 0
local translated = {x=0, y=0, z=0}
local function next_one()
if i < amount then
i = i + 1
translated.x = translated.x + direction.x
translated.y = translated.y + direction.y
translated.z = translated.z + direction.z
worldedit.copy2(pos1, pos2, translated)
minetest.after(0, next_one)
else
if finished then
finished()
end
end
end
next_one()
return worldedit.volume(pos1, pos2) * amount
end
--- Copies a region along `axis` by `amount` nodes.
-- @param pos1
-- @param pos2
@ -135,464 +107,162 @@ end
function worldedit.copy(pos1, pos2, axis, amount)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
-- Decide if we need to copy stuff backwards (only applies to metadata)
local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1)
local get_node, get_meta, set_node = minetest.get_node,
minetest.get_meta, minetest.set_node
-- Copy things backwards when negative to avoid corruption.
-- FIXME: Lots of code duplication here.
if amount < 0 then
local pos = {}
pos.x = pos1.x
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) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get meta of current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Copy node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
else
local pos = {}
pos.x = pos2.x
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) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get meta of current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Copy node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
end
return worldedit.volume(pos1, pos2)
local off = vector.new()
off[axis] = amount
return worldedit.copy2(pos1, pos2, off, backwards)
end
--- Copies a region by offset vector `off`.
-- @param pos1
-- @param pos2
-- @param off
-- @param meta_backwards (not officially part of API)
-- @return The number of nodes copied.
function worldedit.copy2(pos1, pos2, off)
function worldedit.copy2(pos1, pos2, off, meta_backwards)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local src_manip, src_area = mh.init(pos1, pos2)
local src_stride = vector.new(1, src_area.ystride, src_area.zstride)
local src_offset = vector.subtract(pos1, src_area.MinEdge)
local get_node, get_meta, set_node = minetest.get_node,
minetest.get_meta, minetest.set_node
local pos = {}
pos.x = pos2.x
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) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get meta of current node
local newpos = vector.add(pos, off) -- Calculate new position
set_node(newpos, node) -- Copy node to new position
get_meta(newpos):from_table(meta) -- Set metadata of new node
pos.z = pos.z - 1
local dpos1 = vector.add(pos1, off)
local dpos2 = vector.add(pos2, off)
local dim = vector.add(vector.subtract(pos2, pos1), 1)
local dst_manip, dst_area = mh.init(dpos1, dpos2)
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)
for z = 0, dim.z-1 do
local src_index_z = (src_offset.z + z) * src_stride.z + 1 -- +1 for 1-based indexing
local dst_index_z = (dst_offset.z + z) * dst_stride.z + 1
for y = 0, dim.y-1 do
local src_index_y = src_index_z + (src_offset.y + y) * src_stride.y
local dst_index_y = dst_index_z + (dst_offset.y + y) * dst_stride.y
-- Copy entire row at once
local src_index_x = src_index_y + src_offset.x
local dst_index_x = dst_index_y + dst_offset.x
for x = 0, dim.x-1 do
dst_data[dst_index_x + x] = src_data[src_index_x + x]
end
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
-- Copy node data
local src_data = src_manip:get_data()
local dst_data = dst_manip:get_data()
do_copy(src_data, dst_data)
dst_manip:set_data(dst_data)
-- Copy param1
src_manip:get_light_data(src_data)
dst_manip:get_light_data(dst_data)
do_copy(src_data, dst_data)
dst_manip:set_light_data(dst_data)
-- Copy param2
src_manip:get_param2_data(src_data)
dst_manip:get_param2_data(dst_data)
do_copy(src_data, dst_data)
dst_manip:set_param2_data(dst_data)
mh.finish(dst_manip)
-- Copy metadata
local get_meta = minetest.get_meta
if meta_backwards then
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 = 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)
end
end
end
else
for z = 0, dim.z-1 do
for y = 0, dim.y-1 do
for x = 0, dim.x-1 do
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)
end
end
end
end
return worldedit.volume(pos1, pos2)
end
--- Deletes all node metadata in the region
-- @param pos1
-- @param pos2
-- @return The number of nodes that had their meta deleted.
function worldedit.delete_meta(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)
local get_meta = minetest.get_meta
for _, pos in ipairs(meta_positions) do
get_meta(pos):from_table(nil)
end
return #meta_positions
end
--- Moves a region along `axis` by `amount` nodes.
-- @return The number of nodes moved.
function worldedit.move(pos1, pos2, axis, amount)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local dim = vector.add(vector.subtract(pos2, pos1), 1)
local overlap = math.abs(amount) < dim[axis]
-- Decide if we need to copy metadata backwards
local backwards = overlap and amount > 0
--- TODO: Move slice by slice using schematic method in the move axis
-- and transfer metadata in separate loop (and if the amount is
-- greater than the length in the axis, copy whole thing at a time and
-- erase original after, using schematic method).
local get_node, get_meta, set_node, remove_node = minetest.get_node,
minetest.get_meta, minetest.set_node, minetest.remove_node
-- Copy things backwards when negative to avoid corruption.
--- FIXME: Lots of code duplication here.
if amount < 0 then
local pos = {}
pos.x = pos1.x
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) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get metadata of current node
remove_node(pos) -- Remove current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Move node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
local function nuke_area(my_off, my_dim)
if my_dim.x == 0 or my_dim.y == 0 or my_dim.z == 0 then
return
end
local my_pos1 = vector.add(pos1, my_off)
local my_pos2 = vector.subtract(vector.add(my_pos1, my_dim), 1)
worldedit.set(my_pos1, my_pos2, "air")
worldedit.delete_meta(my_pos1, my_pos2)
end
-- Copy stuff to new location
local off = vector.new()
off[axis] = amount
worldedit.copy2(pos1, pos2, off, backwards)
-- Nuke old area
if not overlap then
nuke_area(vector.new(), dim)
else
local pos = {}
pos.x = pos2.x
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) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get metadata of current node
remove_node(pos) -- Remove current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Move node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
-- 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(vector.new(), leftover)
else
local top = vector.new() -- offset of the leftover slice from pos1
top[axis] = dim[axis] - math.abs(amount)
nuke_area(top, leftover)
end
end
return worldedit.volume(pos1, pos2)
end
--- Duplicates a region along `axis` `amount` times.
-- Stacking is spread across server steps, one copy per step.
-- @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)
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 amount = 0
local copy = worldedit.copy
local i = 1
function next_one()
if i <= count then
i = i + 1
amount = amount + length
copy(pos1, pos2, axis, amount)
minetest.after(0, next_one)
end
end
next_one()
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.
-- TODO: Support 6D facedir rotation along arbitrary axis.
function worldedit.orient(pos1, pos2, angle)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local registered_nodes = minetest.registered_nodes
local wallmounted = {
[90] = {[0]=0, 1, 5, 4, 2, 3},
[180] = {[0]=0, 1, 3, 2, 5, 4},
[270] = {[0]=0, 1, 4, 5, 3, 2}
}
local facedir = {
[90] = {[0]=1, 2, 3, 0},
[180] = {[0]=2, 3, 0, 1},
[270] = {[0]=3, 0, 1, 2}
}
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 set_node, get_node, get_meta, swap_node = minetest.set_node,
minetest.get_node, minetest.get_meta, 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
if def.paramtype2 == "wallmounted" then
node.param2 = wallmounted_substitution[node.param2]
local meta = get_meta(pos):to_table()
set_node(pos, node)
get_meta(pos):from_table(meta)
count = count + 1
elseif def.paramtype2 == "facedir" then
node.param2 = facedir_substitution[node.param2]
local meta = get_meta(pos):to_table()
set_node(pos, node)
get_meta(pos):from_table(meta)
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.
function worldedit.fixlight(pos1, pos2)
@ -613,31 +283,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
local pos = obj:getpos()
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
(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 >= 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
@ -646,4 +336,3 @@ function worldedit.clear_objects(pos1, pos2)
end
return count
end

2
worldedit/mod.conf Normal file
View File

@ -0,0 +1,2 @@
name = worldedit
description = WorldEdit main functionality & API

View File

@ -14,31 +14,26 @@ local mh = worldedit.manip_helpers
-- @return The number of nodes added.
function worldedit.cube(pos, width, height, length, node_name, hollow)
-- Set up voxel manipulator
local basepos = vector.subtract(pos, {x=math.floor(width/2), y=0, z=math.floor(length/2)})
local manip, area = mh.init(basepos, vector.add(basepos, {x=width, y=height, z=length}))
local basepos = vector.subtract(pos,
vector.new(math.floor(width / 2), 0, math.floor(length / 2)))
local endpos = vector.add(basepos,
vector.new(width - 1, height - 1, length - 1))
local manip, area = mh.init(basepos, endpos)
local data = mh.get_empty_data(area)
-- Add cube
local node_id = minetest.get_content_id(node_name)
local stride = {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
count = count + 1
end
end
end
for vi in iterfunc do
data[vi] = node_id
count = count + 1
end
mh.finish(manip, data)
@ -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
@ -208,12 +199,13 @@ function worldedit.pyramid(pos, axis, height, node_name, hollow)
local other1, other2 = worldedit.get_axis_others(axis)
-- Set up voxel manipulator
local manip, area = mh.init_axis_radius(pos, axis,
height >= 0 and height or -height)
-- FIXME: passing negative <radius> causes mis-sorted pos to be passed
-- into mh.init() which is technically not allowed but works
local manip, area = mh.init_axis_radius(pos, axis, height)
local data = mh.get_empty_data(area)
-- Handle inverted pyramids
local start_axis, end_axis, step
local step
if height > 0 then
height = height - 1
step = 1
@ -224,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
@ -241,8 +229,8 @@ function worldedit.pyramid(pos, axis, height, node_name, hollow)
for index3 = -size, size do
local i = new_index2 + (index3 + offset[other2]) * stride[other2]
if (not hollow or size - math.abs(index2) < 2 or size - math.abs(index3) < 2) then
data[i] = node_id
count = count + 1
data[i] = node_id
count = count + 1
end
end
end
@ -270,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

View File

@ -24,7 +24,7 @@ Serialization version history:
-- @return Extra header fields as a list of strings, or nil if not supported.
-- @return Content (data after header).
function worldedit.read_header(value)
if value:find("^[0-9]+[%-:]") then
if value:find("^[0-9]+[,:]") then
local header_end = value:find(":", 1, true)
local header = value:sub(1, header_end - 1):split(",")
local version = tonumber(header[1])
@ -56,10 +56,19 @@ function worldedit.serialize(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0}
local get_node, get_meta, hash_node_position =
minetest.get_node, minetest.get_meta, minetest.hash_node_position
-- Find the positions which have metadata
local has_meta = {}
local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)
for i = 1, #meta_positions do
has_meta[hash_node_position(meta_positions[i])] = true
end
local pos = vector.new(pos1.x, 0, 0)
local count = 0
local result = {}
local get_node, get_meta = minetest.get_node, minetest.get_meta
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
@ -68,20 +77,19 @@ function worldedit.serialize(pos1, pos2)
local node = get_node(pos)
if node.name ~= "air" and node.name ~= "ignore" then
count = count + 1
local meta = get_meta(pos):to_table()
local meta_empty = true
-- Convert metadata item stacks to item strings
for name, inventory in pairs(meta.inventory) do
for index, stack in ipairs(inventory) do
meta_empty = false
inventory[index] = stack.to_string and stack:to_string() or stack
end
end
for k in pairs(meta) do
if k ~= "inventory" then
meta_empty = false
break
local meta
if has_meta[hash_node_position(pos)] then
meta = get_meta(pos):to_table()
-- Convert metadata item stacks to item strings
for _, invlist in pairs(meta.inventory) do
for index = 1, #invlist do
local itemstack = invlist[index]
if itemstack.to_string then
invlist[index] = itemstack:to_string()
end
end
end
end
@ -92,7 +100,7 @@ function worldedit.serialize(pos1, pos2)
name = node.name,
param1 = node.param1 ~= 0 and node.param1 or nil,
param2 = node.param2 ~= 0 and node.param2 or nil,
meta = not meta_empty and meta or nil,
meta = meta,
}
end
pos.z = pos.z + 1
@ -106,16 +114,56 @@ function worldedit.serialize(pos1, pos2)
return LATEST_SERIALIZATION_HEADER .. result, count
end
local function deserialize_workaround(content)
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
nodes = {}
content = content:gsub("^%s*return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data
-- remove string contents strings while preserving their length
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*,%s*{", startpos)
if not startpos then
break
end
local current = content:sub(startpos1, startpos)
entry, err = minetest.deserialize("return " .. current, true)
if not entry then
break
end
table.insert(nodes, entry)
startpos, startpos1 = endpos, endpos
end
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.
-- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile)
-- by ChillCode, available under the MIT license.
-- @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)
local tables = minetest.deserialize(content, true)
if not tables then return nil end
-- Transform the node table into an array of nodes
@ -153,28 +201,7 @@ local function load_schematic(value)
})
end
elseif version == 4 or version == 5 then -- Nested table format
if not jit then
-- This is broken for larger tables in the current version of LuaJIT
nodes = minetest.deserialize(content)
else
-- XXX: This is a filthy hack that works surprisingly well - in LuaJIT, `minetest.deserialize` will fail due to the register limit
nodes = {}
content = content:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data
local escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)
local startpos, startpos1, endpos = 1, 1
while true do -- go through each individual node entry (except the last)
startpos, endpos = escaped:find("},%s*{", startpos)
if not startpos then
break
end
local current = content:sub(startpos1, startpos)
local entry = minetest.deserialize("return " .. current)
table.insert(nodes, entry)
startpos, startpos1 = endpos, endpos
end
local entry = minetest.deserialize("return " .. content:sub(startpos1)) -- process the last entry
table.insert(nodes, entry)
end
nodes = deserialize_workaround(content)
else
return nil
end
@ -188,7 +215,7 @@ end
-- @return The number of nodes.
function worldedit.allocate(origin_pos, value)
local nodes = load_schematic(value)
if not nodes then return nil end
if not nodes or #nodes == 0 then return nil end
return worldedit.allocate_with_nodes(origin_pos, nodes)
end
@ -208,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
@ -219,19 +244,22 @@ end
function worldedit.deserialize(origin_pos, value)
local nodes = load_schematic(value)
if not nodes then return nil end
if #nodes == 0 then return #nodes end
local pos1, pos2 = worldedit.allocate_with_nodes(origin_pos, nodes)
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
entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
-- Entry acts as both position and node
add_node(entry, entry)
if entry.meta then
get_meta(entry):from_table(entry.meta)
if registered_nodes[entry.name] then
entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
-- Entry acts as both position and node
add_node(entry, entry)
if entry.meta then
get_meta(entry):from_table(entry.meta)
end
end
end
return #nodes

382
worldedit/test/init.lua Normal file
View 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)

View File

@ -0,0 +1,121 @@
---------------------
local vec = vector.new
local vecw = function(axis, n, base)
local ret = vec(base)
ret[axis] = n
return ret
end
local air = "air"
---------------------
worldedit.register_test("Generic node manipulations")
worldedit.register_test("worldedit.set", function()
local pos1, pos2 = area.get(10)
local m = area.margin(1)
worldedit.set(pos1, pos2, testnode1)
check.filled(pos1, pos2, testnode1)
check.filled2(m, air)
end)
worldedit.register_test("worldedit.set mix", function()
local pos1, pos2 = area.get(10)
local m = area.margin(1)
worldedit.set(pos1, pos2, {testnode1, testnode2})
check.filled(pos1, pos2, {testnode1, testnode2})
check.filled2(m, air)
end)
worldedit.register_test("worldedit.replace", function()
local pos1, pos2 = area.get(10)
local half1, half2 = area.split(pos1, pos2)
worldedit.set(pos1, half1, testnode1)
worldedit.set(half2, pos2, testnode2)
worldedit.replace(pos1, pos2, testnode1, testnode3)
check.not_filled(pos1, pos2, testnode1)
check.filled(pos1, half1, testnode3)
check.filled(half2, pos2, testnode2)
end)
worldedit.register_test("worldedit.replace inverse", function()
local pos1, pos2 = area.get(10)
local half1, half2 = area.split(pos1, pos2)
worldedit.set(pos1, half1, testnode1)
worldedit.set(half2, pos2, testnode2)
worldedit.replace(pos1, pos2, testnode1, testnode3, true)
check.filled(pos1, half1, testnode1)
check.filled(half2, pos2, testnode3)
end)
-- FIXME?: this one looks overcomplicated
worldedit.register_test("worldedit.copy", function()
local pos1, pos2 = area.get(4)
local axis, n = area.dir(2)
local m = area.margin(1)
local b = pos1[axis]
-- create one slice with testnode1, one with testnode2
worldedit.set(pos1, vecw(axis, b + 1, pos2), testnode1)
worldedit.set(vecw(axis, b + 2, pos1), pos2, testnode2)
worldedit.copy(pos1, pos2, axis, n)
-- should have three slices now
check.filled(pos1, vecw(axis, b + 1, pos2), testnode1)
check.filled(vecw(axis, b + 2, pos1), pos2, testnode1)
check.filled(vecw(axis, b + 4, pos1), vector.add(pos2, vecw(axis, n)), testnode2)
check.filled2(m, air)
end)
worldedit.register_test("worldedit.copy2", function()
local pos1, pos2 = area.get(6)
local m1 = area.margin(1)
local pos1_, pos2_ = area.get(6)
local m2 = area.margin(1)
local pattern = {testnode1, testnode2, testnode3, testnode1, testnode2}
place_pattern(pos1, pos2, pattern)
worldedit.copy2(pos1, pos2, vector.subtract(pos1_, pos1))
check.pattern(pos1, pos2, pattern)
check.pattern(pos1_, pos2_, pattern)
check.filled2(m1, air)
check.filled2(m2, air)
end)
worldedit.register_test("worldedit.move (overlap)", function()
local pos1, pos2 = area.get(7)
local axis, n = area.dir(2)
local m = area.margin(1)
local pattern = {testnode2, testnode1, testnode2, testnode3, testnode3}
place_pattern(pos1, pos2, pattern)
worldedit.move(pos1, pos2, axis, n)
check.filled(pos1, vecw(axis, pos1[axis] + n - 1, pos2), air)
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
check.filled2(m, air)
end)
worldedit.register_test("worldedit.move", function()
local pos1, pos2 = area.get(10)
local axis, n = area.dir(10)
local m = area.margin(1)
local pattern = {testnode1, testnode3, testnode3, testnode2}
place_pattern(pos1, pos2, pattern)
worldedit.move(pos1, pos2, axis, n)
check.filled(pos1, pos2, air)
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
check.filled2(m, air)
end)
-- TODO: the rest (also testing param2 + metadata)

View File

@ -0,0 +1,59 @@
---------------------
local vec = vector.new
local vecw = function(axis, n, base)
local ret = vec(base)
ret[axis] = n
return ret
end
local air = "air"
---------------------
worldedit.register_test("Primitives")
worldedit.register_test("worldedit.cube", function()
local pos1, pos2 = area.get(6, 5, 4)
local m = area.margin(1)
local center = vec(pos1.x + 3, pos1.y, pos1.z + 2)
worldedit.cube(center, 6, 5, 4, testnode2)
check.filled(pos1, pos2, testnode2)
check.filled2(m, air)
end)
worldedit.register_test("worldedit.cube hollow small", function()
for n = 1, 2 do
local pos1, pos2 = area.get(n)
local m = area.margin(1)
local center = vec(pos1.x + math.floor(n/2), pos1.y, pos1.z + math.floor(n/2))
worldedit.cube(center, n, n, n, testnode1, true)
check.filled(pos1, pos2, testnode1) -- filled entirely
check.filled2(m, air)
end
end)
worldedit.register_test("worldedit.cube hollow", function()
local pos1, pos2 = area.get(6, 5, 4)
local m = area.margin(1)
local center = vec(pos1.x + 3, pos1.y, pos1.z + 2)
worldedit.cube(center, 6, 5, 4, testnode1, true)
check.filled(vector.add(pos1, vec(1,1,1)), vector.subtract(pos2, vec(1,1,1)), air)
check.filled2({
{ vecw("x", pos2.x, pos1), pos2 },
{ vecw("y", pos2.y, pos1), pos2 },
{ vecw("z", pos2.z, pos1), pos2 },
{ pos1, vecw("x", pos1.x, pos2) },
{ pos1, vecw("y", pos1.y, pos2) },
{ pos1, vecw("z", pos1.z, pos2) },
}, testnode1)
check.filled2(m, air)
end)

View File

@ -0,0 +1,162 @@
---------------------
local vec = vector.new
local air = "air"
---------------------
local function output_weird(numbers, body)
local s = {"return {"}
for _, parts in ipairs(numbers) do
s[#s+1] = "{"
for _, n in ipairs(parts) do
s[#s+1] = string.format(" {%d},", n)
end
s[#s+1] = "},"
end
return table.concat(s, "\n") .. table.concat(body, "\n") .. "}"
end
local fmt1p = '{\n ["x"]=%d,\n ["y"]=%d,\n ["z"]=%d,\n},'
local fmt1n = '{\n ["name"]="%s",\n},'
local fmt4 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["meta"] = { ["fields"] = { }, ["inventory"] = { } }, ["param2"] = 0, ["param1"] = 0, ["name"] = "%s" }'
local fmt5 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["name"] = "%s" }'
local fmt51 = '{[r2]=0,x=%d,y=%d,z=%d,name=r%d}'
local fmt52 = '{x=%d,y=%d,z=%d,name=_[%d]}'
local test_data = {
-- used by WorldEdit 0.2 (first public release)
{
name = "v1", ver = 1,
gen = function(pat)
local numbers = {
{2, 3, 4, 5, 6},
{7, 8}, {9, 10}, {11, 12},
{13, 14}, {15, 16}
}
return output_weird(numbers, {
fmt1p:format(0, 0, 0),
fmt1n:format(pat[1]),
fmt1p:format(0, 1, 0),
fmt1n:format(pat[3]),
fmt1p:format(1, 1, 0),
fmt1n:format(pat[1]),
fmt1p:format(1, 0, 1),
fmt1n:format(pat[3]),
fmt1p:format(0, 1, 1),
fmt1n:format(pat[1]),
})
end
},
-- v2: missing because I couldn't find any code in my archives that actually wrote this format
{
name = "v3", ver = 3,
gen = function(pat)
assert(pat[2] == air)
return table.concat({
"0 0 0 " .. pat[1] .. " 0 0",
"0 1 0 " .. pat[3] .. " 0 0",
"1 1 0 " .. pat[1] .. " 0 0",
"1 0 1 " .. pat[3] .. " 0 0",
"0 1 1 " .. pat[1] .. " 0 0",
}, "\n")
end
},
{
name = "v4", ver = 4,
gen = function(pat)
return table.concat({
"return { " .. fmt4:format(0, 0, 0, pat[1]),
fmt4:format(0, 1, 0, pat[3]),
fmt4:format(1, 1, 0, pat[1]),
fmt4:format(1, 0, 1, pat[3]),
fmt4:format(0, 1, 1, pat[1]) .. " }",
}, ", ")
end
},
-- like v4 but no meta and param (if empty)
{
name = "v5 (pre-5.6)", ver = 5,
gen = function(pat)
return table.concat({
"5:return { " .. fmt5:format(0, 0, 0, pat[1]),
fmt5:format(0, 1, 0, pat[3]),
fmt5:format(1, 1, 0, pat[1]),
fmt5:format(1, 0, 1, pat[3]),
fmt5:format(0, 1, 1, pat[1]) .. " }",
}, ", ")
end
},
-- reworked engine serialization in 5.6
{
name = "v5 (5.6)", ver = 5,
gen = function(pat)
return table.concat({
'5:r1="' .. pat[1] .. '";r2="param1";r3="' .. pat[3] .. '";return {'
.. fmt51:format(0, 0, 0, 1),
fmt51:format(0, 1, 0, 3),
fmt51:format(1, 1, 0, 1),
fmt51:format(1, 0, 1, 3),
fmt51:format(0, 1, 1, 1) .. "}",
}, ",")
end
},
-- small changes on engine side again
{
name = "v5 (post-5.7)", ver = 5,
gen = function(pat)
return table.concat({
'5:local _={};_[1]="' .. pat[1] .. '";_[3]="' .. pat[3] .. '";return {'
.. fmt52:format(0, 0, 0, 1),
fmt52:format(0, 1, 0, 3),
fmt52:format(1, 1, 0, 1),
fmt52:format(1, 0, 1, 3),
fmt52:format(0, 1, 1, 1) .. "}",
}, ",")
end
},
}
worldedit.register_test("Schematics")
worldedit.register_test("worldedit.read_header", function()
local value = '5,foo,BAR,-1,234:the content'
local version, header, content = worldedit.read_header(value)
assert(version == 5)
assert(#header == 4)
assert(header[1] == "foo" and header[2] == "BAR")
assert(header[3] == "-1" and header[4] == "234")
assert(content == "the content")
end)
worldedit.register_test("worldedit.allocate", function()
local value = '3:-1 0 0 dummy 0 0\n0 0 4 dummy 0 0\n0 1 0 dummy 0 0'
local pos1, pos2, count = worldedit.allocate(vec(1, 1, 1), value)
assert(vector.equals(pos1, vec(0, 1, 1)))
assert(vector.equals(pos2, vec(1, 2, 5)))
assert(count == 3)
end)
for _, e in ipairs(test_data) do
worldedit.register_test("worldedit.deserialize " .. e.name, function()
local pos1, pos2 = area.get(2)
local m = area.margin(1)
local pat = {testnode3, air, testnode2}
local value = e.gen(pat)
assert(type(value) == "string")
local version = worldedit.read_header(value)
assert(version == e.ver, "version: got " .. tostring(version) .. " expected " .. e.ver)
local count = worldedit.deserialize(pos1, value)
assert(count ~= nil and count > 0)
check.pattern(pos1, pos2, pat)
check.filled2(m, air)
end)
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

View File

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

View File

@ -6,6 +6,7 @@ minetest.register_node("worldedit:placeholder", {
paramtype = "light",
sunlight_propagates = true,
diggable = false,
pointable = false,
walkable = false,
groups = {not_in_creative_inventory=1},
})
@ -18,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
@ -78,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

122
worldedit_brush/init.lua Normal file
View File

@ -0,0 +1,122 @@
local S = minetest.get_translator("worldedit_brush")
local BRUSH_MAX_DIST = 150
local brush_on_use = function(itemstack, placer)
local meta = itemstack:get_meta()
local name = placer:get_player_name()
local cmd = meta:get_string("command")
if cmd == "" then
worldedit.player_notify(name,
S("This brush is not bound, use @1 to bind a command to it.",
minetest.colorize("#00ffff", "//brush")), "info")
return false
end
local cmddef = worldedit.registered_commands[cmd]
if cmddef == nil then return false end -- shouldn't happen as //brush checks this
local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs)
if not has_privs then
worldedit.player_notify(name,
S("Missing privileges: @1", table.concat(missing_privs, ", ")), "error")
return false
end
local raybegin = vector.add(placer:get_pos(),
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, S("Too far away."), "error")
return false
end
assert(pointed_thing.type == "node")
worldedit.pos1[name] = pointed_thing.under
worldedit.pos2[name] = nil
worldedit.marker_update(name)
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))
worldedit.player_notify = player_notify_old
return true
end
minetest.register_tool(":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,
on_use = function(itemstack, placer, pointed_thing)
brush_on_use(itemstack, placer)
return itemstack -- nothing consumed, nothing changed
end,
})
worldedit.register_command("brush", {
privs = {worldedit=true},
params = "none/<cmd> [parameters]",
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
params = ""
found, _, cmd = param:find("^(.+)$")
end
if not found then
return false
end
return true, cmd, params
end,
func = function(name, cmd, params)
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
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, S("Brush assignment cleared."), "ok")
else
local cmddef = worldedit.registered_commands[cmd]
if cmddef == nil or cmddef.require_pos ~= 1 then
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 S("invalid usage")
return false, S("Error with command: @1", err)
end
meta:set_string("command", cmd)
meta:set_string("params", params)
local fullcmd = minetest.colorize("#00ffff", "//"..cmd) .. " " .. params
meta:set_string("description",
minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd)
worldedit.player_notify(name, S("Brush assigned to command: @1", fullcmd), "ok")
end
player:set_wielded_item(itemstack)
end,
})

View File

@ -0,0 +1,12 @@
# textdomain: worldedit_brush
This brush is not bound, use @1 to bind a command to it.=
Missing privileges: @1=
Too far away.=
WorldEdit Brush=
Assign command to WorldEdit brush item or clear assignment using 'none'=
Not holding brush item.=
Brush assignment cleared.=
@1 cannot be used with brushes=
invalid usage=
Error with command: @1=
Brush assigned to command: @1=

View File

@ -0,0 +1,12 @@
# textdomain: worldedit_brush
This brush is not bound, use @1 to bind a command to it.=Dieser Pinsel ist nicht konfiguriert, nutzen Sie @1 um ein 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

3
worldedit_brush/mod.conf Normal file
View File

@ -0,0 +1,3 @@
name = worldedit_brush
description = WorldEdit brush
depends = worldedit, worldedit_commands

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

View File

@ -1 +0,0 @@
*~

View File

@ -0,0 +1,59 @@
local S = minetest.get_translator("worldedit_commands")
local function check_region(name)
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
end
worldedit.register_command("lua", {
params = "<code>",
description = S("Executes <code> as a Lua chunk in the global namespace"),
category = S("Code"),
privs = {worldedit=true, server=true},
parse = function(param)
if param == "" then
return false
end
return true, param
end,
func = function(name, param)
-- shorthand like in the Lua interpreter
if param:sub(1, 1) == "=" then
param = "return " .. param:sub(2)
end
local err, ret = worldedit.lua(param, name)
if err == nil then
minetest.log("action", name .. " executed " .. param)
if ret ~= "nil" then
worldedit.player_notify(name, "code successfully executed, returned " .. ret, "info")
else
worldedit.player_notify(name, "code successfully executed", "ok")
end
else
minetest.log("action", name .. " tried to execute " .. param)
worldedit.player_notify(name, "code error: " .. err, "error")
end
end,
})
worldedit.register_command("luatransform", {
params = "<code>",
description = S("Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region"),
category = S("Code"),
privs = {worldedit=true, server=true},
require_pos = 2,
parse = function(param)
return true, param
end,
nodes_needed = check_region,
func = function(name, param)
local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)
if err then
worldedit.player_notify(name, "code error: " .. err, "error")
minetest.log("action", name.." tried to execute luatransform "..param)
else
worldedit.player_notify(name, "code successfully executed", "ok")
minetest.log("action", name.." executed luatransform "..param)
end
end,
})

View File

@ -1,28 +1,25 @@
minetest.register_chatcommand("/outset", {
params = "[h|v] <amount>",
description = "outset the selection",
local S = minetest.get_translator("worldedit_commands")
worldedit.register_command("outset", {
params = "[h/v] <amount>",
description = S("Outset the selected region."),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name, param)
require_pos = 2,
parse = function(param)
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
if find == nil then
return false, "invalid usage: " .. param
return false
end
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
return false,
"Undefined region. Region must be defined beforehand."
end
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)
end,
func = function(name, dir, amount)
if dir == "" or dir == "hv" or dir == "vh" then
assert(worldedit.cuboid_volumetric_expand(name, amount))
elseif dir == "h" then
@ -34,41 +31,33 @@ minetest.register_chatcommand("/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,
}
)
})
minetest.register_chatcommand("/inset", {
params = "[h|v] <amount>",
description = "inset the selection",
worldedit.register_command("inset", {
params = "[h/v] <amount>",
description = S("Inset the selected region."),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name, param)
require_pos = 2,
parse = function(param)
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
if find == nil then
return false, "invalid usage: " .. param
return false
end
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
return false,
"Undefined region. Region must be defined beforehand."
if dir:find("[^hv]") ~= nil then
return false, S("Invalid direction: @1", dir)
end
local hv_test = dir:find("[^hv]+")
if hv_test ~= nil then
return false, "Invalid direction."
end
return true, dir, tonumber(amount)
end,
func = function(name, dir, amount)
if dir == "" or dir == "vh" or dir == "hv" then
assert(worldedit.cuboid_volumetric_expand(name, -amount))
elseif dir == "h" then
@ -80,36 +69,30 @@ minetest.register_chatcommand("/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,
}
)
})
minetest.register_chatcommand("/shift", {
params = "[x|y|z|?|up|down|left|right|front|back] [+|-]<amount>",
description = "Moves the selection region. Does not move contents.",
worldedit.register_command("shift", {
params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>",
description = S("Shifts the selection area without moving its contents"),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name, param)
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
require_pos = 2,
parse = function(param)
local find, _, direction, amount = param:find("([%?%l]+)%s*([+-]?%d+)")
if find == nil then
worldedit.player_notify(name, "invalid usage: " .. param)
return
return false
end
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name,
"Undefined region. Region must be defined beforehand.")
return
end
return true, direction, tonumber(amount)
end,
func = function(name, direction, amount)
local axis, dir
if direction == "x" or direction == "y" or direction == "z" then
axis, dir = direction, 1
@ -118,123 +101,173 @@ minetest.register_chatcommand("/shift", {
else
axis, dir = worldedit.translate_direction(name, direction)
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,
}
)
})
minetest.register_chatcommand("/expand", {
params = "[+|-]<x|y|z|?|up|down|left|right|front|back> <amount> [reverse-amount]",
description = "expand the selection in one or two directions at once",
worldedit.register_command("expand", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = S("Expands the selection in the selected absolute or relative axis"),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name, param)
local find, _, sign, direction, amount,
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)")
if find == nil then
worldedit.player_notify(name, "invalid use: " .. param)
return
end
if worldedit.pos1[name] == nil or worldedit.pos2[name] == nil then
worldedit.player_notify(name,
"Undefined region. Region must be defined beforehand.")
return
end
local absolute = direction:find("[xyz?]")
local dir, axis
if rev_amount == "" then
rev_amount = 0
end
if absolute == nil then
axis, dir = worldedit.translate_direction(name, direction)
if axis == nil or dir == nil then
return false, "Invalid if looking straight up or down"
require_pos = 2,
parse = function(param)
local find, _, sign, direction, amount,
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)")
if find == nil then
return false
end
else
if direction == "?" then
axis, dir = worldedit.player_axis(name)
if rev_amount == "" then
rev_amount = "0"
end
return true, sign, direction, tonumber(amount), tonumber(rev_amount)
end,
func = function(name, sign, direction, amount, rev_amount)
local absolute = direction:find("[xyz?]")
local dir, axis
if absolute == nil then
axis, dir = worldedit.translate_direction(name, direction)
if axis == nil or dir == nil then
return false, S("Invalid if looking straight up or down")
end
else
axis = direction
dir = 1
if direction == "?" then
axis, dir = worldedit.player_axis(name)
else
axis = direction
dir = 1
end
end
end
if sign == "-" then
dir = -dir
end
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"
end,
}
)
if sign == "-" then
dir = -dir
end
worldedit.cuboid_linear_expand(name, axis, dir, amount)
worldedit.cuboid_linear_expand(name, axis, -dir, rev_amount)
worldedit.marker_update(name)
return true, S("Region expanded by @1 nodes", amount + rev_amount)
end,
})
minetest.register_chatcommand("/contract", {
params = "[+|-]<x|y|z|?|up|down|left|right|front|back> <amount> [reverse-amount]",
description = "contract the selection in one or two directions at once",
worldedit.register_command("contract", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = S("Contracts the selection in the selected absolute or relative axis"),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name, param)
local find, _, sign, direction, amount,
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)")
if find == nil then
worldedit.player_notify(name, "invalid use: " .. param)
return
end
if worldedit.pos1[name] == nil or worldedit.pos2[name] == nil then
worldedit.player_notify(name,
"Undefined region. Region must be defined beforehand.")
return
end
local absolute = direction:find("[xyz?]")
local dir, axis
if rev_amount == "" then
rev_amount = 0
end
if absolute == nil then
axis, dir = worldedit.translate_direction(name, direction)
if axis == nil or dir == nil then
return false, "Invalid if looking straight up or down"
require_pos = 2,
parse = function(param)
local find, _, sign, direction, amount,
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)")
if find == nil then
return false
end
else
if direction == "?" then
axis, dir = worldedit.player_axis(name)
if rev_amount == "" then
rev_amount = "0"
end
return true, sign, direction, tonumber(amount), tonumber(rev_amount)
end,
func = function(name, sign, direction, amount, rev_amount)
local absolute = direction:find("[xyz?]")
local dir, axis
if absolute == nil then
axis, dir = worldedit.translate_direction(name, direction)
if axis == nil or dir == nil then
return false, S("Invalid if looking straight up or down")
end
else
axis = direction
dir = 1
if direction == "?" then
axis, dir = worldedit.player_axis(name)
else
axis = direction
dir = 1
end
end
end
if sign == "-" then
dir = -dir
end
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"
end,
}
)
if sign == "-" then
dir = -dir
end
worldedit.cuboid_linear_expand(name, axis, dir, -amount)
worldedit.cuboid_linear_expand(name, axis, -dir, -rev_amount)
worldedit.marker_update(name)
return true, S("Region contracted by @1 nodes", amount + rev_amount)
end,
})
worldedit.register_command("cubeapply", {
params = "<size>/(<sizex> <sizey> <sizez>) <command> [parameters]",
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)
local found, _, sidex, sidey, sidez, cmd, args =
param:find("^(%d+)%s+(%d+)%s+(%d+)%s+([^%s]+)%s*(.*)$")
if found == nil then
found, _, sidex, cmd, args = param:find("^(%d+)%s+([^%s]+)%s*(.*)$")
if found == nil then
return false
end
sidey = sidex
sidez = sidex
end
sidex = tonumber(sidex)
sidey = tonumber(sidey)
sidez = tonumber(sidez)
if sidex < 1 or sidey < 1 or sidez < 1 then
return false
end
local cmddef = worldedit.registered_commands[cmd]
if cmddef == nil or cmddef.require_pos ~= 2 then
return false, S("invalid usage: @1 cannot be used with cubeapply",
minetest.colorize("#00ffff", "//"..cmd))
end
-- run parsing of target command
local parsed = {cmddef.parse(args)}
if not table.remove(parsed, 1) then
return false, parsed[1]
end
return true, sidex, sidey, sidez, cmd, parsed
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
return false, S("Missing privileges: @1", table.concat(missing_privs, ", "))
end
-- update region to be the cuboid the user wanted
local half = vector.divide(vector.new(sidex, sidey, sidez), 2)
local sizea, sizeb = vector.apply(half, math.floor), vector.apply(half, math.ceil)
local center = worldedit.pos1[name]
worldedit.pos1[name] = vector.subtract(center, sizea)
worldedit.pos2[name] = vector.add(center, vector.subtract(sizeb, 1))
worldedit.marker_update(name)
-- actually run target command
return cmddef.func(name, unpack(parsed))
end,
})

View File

@ -1 +0,0 @@
worldedit

File diff suppressed because it is too large Load Diff

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

View 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 gerade Blickrichtung nach unten oder oben
Region shifted by @1 nodes=Gebiet um @1 Blöcke verschoben
Expands the selection in the selected absolute or relative axis=Erweitert das Gebiet in Richtung einer absoluten oder relativen Achse
Region expanded by @1 nodes=Gebiet um @1 Blöcke erweitert
Contracts the selection in the selected absolute or relative axis=Schrumpft das Gebiet in Richtung einer absoluten oder relativen Achse
Region contracted by @1 nodes=Gebiet um @1 Blöcke geschrumpft
Select a cube with side length <size> around position 1 and run <command> on region=Wähle einen Würfel mit Seitenlänge <size> um Position 1 und führe <command> auf diesem Gebiet aus
invalid usage: @1 cannot be used with cubeapply=Ungültige Verwendung: @1 kann nicht mit cubeapply verwendet werden
Missing privileges: @1=Fehlende Privilegien: @1
Region operations=Gebietsoperationen
Can use WorldEdit commands=Kann WorldEdit-Befehle benutzen
no region selected=Kein Gebiet ausgewählt
no position 1 selected=Keine Position 1 ausgewählt
invalid usage=Ungültige Verwendung
ERROR: the operation was cancelled because the region has changed.=FEHLER: Der Vorgang wurde abgebrochen, weil das Gebiet verändert wurde.
Could not open file "@1"=Konnte die Datei „@1“ nicht öffnen
Invalid file format!=Ungültiges Dateiformat!
Schematic was created with a newer version of WorldEdit.=Schematic wurde mit einer neueren Version von WorldEdit erstellt.
Get information about the WorldEdit mod=Informationen über den WorldEdit-Mod erhalten.
WorldEdit @1 is available on this server. Type @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 Gebietsposition 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 Spieler 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=
unknown subcommand: @1=Unbekannter Unterbefehl: @1
select positions by punching two nodes=Wählen Sie die Position durch Hauen zweier Blöcke
select position @1 by punching a node=Wählen Sie Position @1 durch Hauen eines Blocks
position @1: @2=Position @1: @2
position @1 not set=Position @1 ist nicht gesetzt
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=Position des WorldEdit-Gebiets auf (<x>, <y>, <z>) setzen
Display the volume of the current WorldEdit region=Volumen des aktuellen WorldEdit-Gebiets anzeigen
current region has a volume of @1 nodes (@2*@3*@4)=Das aktuelle Gebiet hat ein Volumen von @1 Blöcken (@2*@3*@4)
Remove all MapBlocks (16x16x16) containing the selected area from the map=Alle Kartenblöcke (16x16x16) entfernen, die das Gebiet enthalten.
Node manipulation=Block-Manipulation
Area deleted.=Gebiet gelöscht.
There was an error during deletion of the area.=Während des Löschens des Gebiets trat ein Fehler aus.
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>.=
Shapes=Formen
@1 nodes added=@1 Blöcke hinzugefügt
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=
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=
invalid scaling factors: @1=Ungültige Skalierungsfaktoren: @1
@1 nodes stretched=@1 Blöcke gestreckt
Transpose the current WorldEdit region along the given axes=
invalid usage: axes must be different=Ungültige Verwendung: Achsen müssen verschieden sein
@1 nodes transposed=
Flip the current WorldEdit region along the given axis=
@1 nodes flipped=
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=
invalid usage: angle must be multiple of 90=Ungültige Verwendung: Winkel muss ein Vielfaches von 90 sein
@1 nodes rotated=@1 Blöcke gedreht
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=
@1 nodes oriented=
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 zerstörungsfrei verstecken
@1 nodes hidden=@1 Blöcke versteckt
Suppress all <node> in the current WorldEdit region non-destructively=
@1 nodes suppressed=
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Alle <node> Blöcke im WorldEdit-Gebiet hervorheben, indem alle anderen zerstörungsfrei versteckt werden
@1 nodes highlighted=@1 Blöcke hervorgehoben
Restores nodes hidden with WorldEdit in the current WorldEdit region=Versteckte Blöcke im WorldEdit-Gebiet wiederherstellen.
@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=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"=Das aktuelle WorldEdit-Gebiet als Minetest-Schematic in "(Weltordner)/schems/<filename>.mts" speichern
Failed to create Minetest schematic=Konnte Minetest-Schematic nicht erstellen
Saved Minetest schematic to @1=Minetest-Schematic in @1 gespeichert
Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=Blöcke aus "(Weltordner)/schems/<file>.mts" mit Position 1 als Ausgangspunkt laden
failed to place Minetest schematic=Konnte Minetest-Schematic nicht platzieren
placed Minetest schematic @1 at @2=Minetest-Schematic @1 bei @2 platziert
Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=
select Minetest schematic probability values by punching nodes=
finished Minetest schematic probability selection=
currently set node probabilities:=
invalid node probability given, not saved=
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

View File

@ -0,0 +1,155 @@
# textdomain: worldedit_commands
### init.lua ###
Can use WorldEdit commands=Возможность редактировать мир с помощью команд WorldEdit
no region selected=не выделен регион
no position 1 selected=не установлена позиция региона 1
invalid usage=не верное использование команды
Could not open file "@1"=Не удаётся открыть файл "@1"
Invalid file format!=Не верный формат файла!
Schematic was created with a newer version of WorldEdit.=Схема была создана с использованием более новой версии WorldEdit.
Get information about the WorldEdit mod=Вывести информацию о WorldEdit
WorldEdit @1 is available on this server. Type @2 to get a list of commands, or get more information at @3=WorldEdit @1 доступен на этом сервере. Наберите команду @2 чтобы увидеть список команд, больше информации по ссылке @3
Get help for WorldEdit commands=Вывести информацию об использовании команд WorldEdit
You are not allowed to use any WorldEdit commands.=У вас нет привилегий, чтобы использовать команды WorldEdit.
Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=Доступные команды: @1@nИспользуйте '@2' для получения информации по команде или '@3' для вывода подсказок по всем командам.
Available commands:@n=Доступные команды:@n
Command not available: =Команда не найдена:
Enable or disable node inspection=Включить/отключить инспекцию блоков
inspector: inspection enabled for @1, currently facing the @2 axis=inspector: инспекция включена для @1, текущий взор в направлении оси @2
inspector: inspection disabled=inspector: инспекция отключена
inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=inspector: @1 в @2 (param1@=@3, param2@=@4, received light@=@5) ударен по поверхности @6
Reset the region so that it is empty=Сбросить выделение области
region reset=регион сброшен
Show markers at the region positions=Отобразить маркеры выделенной области
region marked=маркеры отображены
Hide markers if currently shown=Скрыть маркеры выделенной области
region unmarked=маркеры скрыты
Set WorldEdit region position @1 to the player's location=Установить маркер @1 для WorldEdit-региона в месте нахождения игрока
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region=Выделить WorldEdit-регион или установить маркеры для WorldEdit-региона, либо отобразить уже выбранную область
unknown subcommand: @1=неизвестная подкоманда: @1
select positions by punching two nodes=выберите позиции, ударив по блокам
select position @1 by punching a node=выберите позицию @1, ударив по блоку
position @1: @2=позиция @1: @2
position @1 not set=позиция @1 не установлена
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=Установить маркер для WorldEdit в позиции (<x>, <y>, <z>)
position @1 set to @2=позиция @1 установлена в @2
Display the volume of the current WorldEdit region=Вывести информацию об объёме текущей выделенной области WorldEdit (кол-во нод, размеры)
current region has a volume of @1 nodes (@2*@3*@4)=текущий регион имеет объем @1 нод (@2*@3*@4)
Remove all MapBlocks (16x16x16) containing the selected area from the map=Удалить MapBlocks (16x16x16), содержащие выбранную область
Area deleted.=Область удалена.
There was an error during deletion of the area.=Что-то пошло не так при удалении области.
Set the current WorldEdit region to <node>=Заполнить выбранный WorldEdit-регион указанным типом блоков
invalid node name: @1=неверное название блока: @1
@1 nodes set=@1 блок(а/ов) установленно
Set param2 of all nodes in the current WorldEdit region to <param2>=Проставить param2 для всех блоков в текущем WorldEdit-регионе
Param2 is out of range (must be between 0 and 255 inclusive!)=Значение param2 должно быть от 0 до 255 (включительно!)
@1 nodes altered=изменено @1 блок(а/ов)
Fill the current WorldEdit region with a random mix of <node1>, ...=Заполнить выбранный WorldEdit-регион смесью указанных типов блоков
invalid search node name: @1=неверное название блока-поиска: @1
invalid replace node name: @1=неверное название блока-замены: @1
Replace all instances of <search node> with <replace node> in the current WorldEdit region=Заменить все блоки <search node> на <replace node> в выбранной WorldEdit-области
@1 nodes replaced=заменено @1 нод(а/ы)
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=Заменить все блоки, кроме <search node>, на <replace node> в выбранной WorldEdit-области
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Установить полый куб с центром нижней грани в позиции 1 и указанными размерами, состоящий из блоков <node>.
@1 nodes added=добавлен(о) @1 блок(а/ов)
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Установить куб с центром нижней грани в позиции 1 и указанными размерами, состоящий из блоков <node>.
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить полую сферу с центром в WorldEdit-позиции 1 радиусом <radius>, состоящую из блоков <node>
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить сферу с центром в WorldEdit-позиции 1 радиусом <radius>, состоящую из блоков <node>
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить полый купол с центром в WorldEdit-позиции 1 радиусом <radius>, состоящий из блоков <node>
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить купол с центром в WorldEdit-позиции 1 радиусом <radius>, состоящий из блоков <node>
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Установить полый цилиндр вдоль указанной оси, с центром в позиции 1, высотой/длинной <length>, с радиусом основания <radius> (и радиусом вершины [radius 2]), состоящий из блоков <node>
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Установить цилиндр вдоль указанной оси, с центром в позиции 1, высотой/длинной <length>, с радиусом основания <radius> (и радиусом вершины [radius 2]), состоящий из блоков <node>
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Установить полую пирамиду вдоль указанной оси, с центром в WorldEdit-позиции 1 высотой <height>, состоящую из блоков <node>
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Установить пирамиду вдоль указанной оси, с центром в WorldEdit-позиции 1 высотой <height>, состоящую из блоков <node>
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=Установить спираль с центром в WorldEdit-позиции 1 шириной <length>, высотой <height> и с расстоянием между витками <space>, состоящую из блоков <node>
Copy the current WorldEdit region along the given axis by <amount> nodes=Копировать текущий WorldEdit-регион со смещением вдоль указанной оси (x/y/z) на <amount> блоков
@1 nodes copied=скопировано @1 нод(а/ы)
Move the current WorldEdit region along the given axis by <amount> nodes=Переместить текущий WorldEdit-регион вдоль указанной оси (x/y/z) на <amount> блоков
@1 nodes moved=перемещено @1 нод(а/ы)
Stack the current WorldEdit region along the given axis <count> times=Размножить текущий WorldEdit-регион вдоль указанной оси <count> раз
@1 nodes stacked=размножено @1 нод(а/ы)
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Размножить текущий WorldEdit-регион <count> раз с шагом <x>, <y>, <z> по соответствующим осям
invalid count: @1=неверное количество: @1
invalid increments: @1=неверные приращения(шаг)
Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=Масштабировать текущий WorldEdit-регион с коэффициентами <stretchx>, <stretchy>, <stretchz> вдоль осей X, Y и Z, используя WorldEdit-позицию 1 в качестве точки отсчёта
invalid scaling factors: @1=неверные коэффициенты масштабирования: @1
@1 nodes stretched=масштабировано @1 нод(а/ы)
Transpose the current WorldEdit region along the given axes=Транспонировать текущий WorldEdit-регион по заданным осям.
invalid usage: axes must be different=недопустимое использование: оси должны быть разными
@1 nodes transposed=транспонировано @1 нод(а/ы)
Flip the current WorldEdit region along the given axis=Перевернуть/Отразить текущий WorldEdit-регион вдоль указанной оси
@1 nodes flipped=отражено @1 нод(а/ы)
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=Повернуть текущий WorldEdit-регион вокруг оси <axis> на угол <angle> (шаг - 90 градусов)
invalid usage: angle must be multiple of 90=недопустимое использование: угол должен быть кратен 90
@1 nodes rotated=повёрнуто @1 нод(а/ы)
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=Повернуть блоки в текущем WorldEdit-регионе вокруг оси Y на угол <angle> (шаг - 90 градусов)
@1 nodes oriented=повёрнуто @1 нод(а/ы)
Fix the lighting in the current WorldEdit region=Исправить освещение в текущем WorldEdit-регионе
@1 nodes updated=обновлено @1 нод(а/ы)
Remove any fluid node within the current WorldEdit region=Удалить любые жидкости в текущем WorldEdit-регионе
Remove any plant, tree or foliage-like nodes in the selected region=Удалить любые растения, деревья или листье-подобные ноды в текущем WorldEdit-регионе
@1 nodes removed=удалено @1 нод(а/ы)
Hide all nodes in the current WorldEdit region non-destructively=Скрыть узлы текущего WorldEdit-региона, не удаляя их
@1 nodes hidden=скрыто @1 нод(а/ы)
Suppress all <node> in the current WorldEdit region non-destructively=Скрыть все блоки <node> в текущем WorldEdit-регионе, не удаляя их
@1 nodes suppressed=скрыто @1 нод(а/ы)
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Скрыть все блоки, кроме <node>, в текущем WorldEdit-регионе, не удаляя их
@1 nodes highlighted="подсвечено" @1 нод(а/ы)
Restores nodes hidden with WorldEdit in the current WorldEdit region=Восстановить скрытые WorldEdit'ом узлы в текущем WorldEdit-регионе
@1 nodes restored=восстановлено @1 нод(а/ы)
Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=Предупреждение: Схема содержит слишком много свободного места и будет смещена при размещении или загрузке. Чтобы избежать этого, уменьшите область так, чтобы она охватывала именно те узлы, которые необходимо сохранить.
Save the current WorldEdit region to "(world folder)/schems/<file>.we"=Сохранить текущий WorldEdit-регион в файл "(world folder)/schems/<file>.we"
Disallowed file name: @1=Недопустимое имя файла: @1
Could not save file to "@1"=Не удалось сохранить файл в "@1"
@1 nodes saved=сохранено @1 нод(а/ы)
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=Выделить область, определённую узлами из "(world folder)/schems/<file>.we", как текущий WorldEdit-регион
Schematic empty, nothing allocated=Схема пуста, ничего не выделено
@1 nodes allocated=выделено @1 нод(а/ы)
Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=Загрузить регион из "(world folder)/schems/<file>[.we[m]]" с WorldEdit-позицией 1 в качестве точки отсчёта
Loading failed!=Не удалось загрузить!
@1 nodes loaded=загружено @1 нод(а/ы)
Executes <code> as a Lua chunk in the global namespace=Выполнить <code> как Lua-код в глобальном пространстве имён
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=Выполнить <code> как Lua-код в глобальном пространстве имён, с доступом к переменным позиций для каждого блока в текущем WordEdit-регионе
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=Сохранить текущий WorldEdit-регион с использованием сжатия в формат Minetest Schematic в файл "(world folder)/schems/<filename>.mts"
Failed to create Minetest schematic=Не удалось создать Minetest-схему
Saved Minetest schematic to @1=Minetest-схема сохранена в @1
Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=Загрузить блоки из "(world folder)/schems/<file>.mts" с WorldEdit-позицией 1 в качестве точки отсчёта
failed to place Minetest schematic=не удалось загрузить Minetest-схему
placed Minetest schematic @1 at @2=Minetest-схема @1 загружена в @2
Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=Начать запись вероятностей для Minetest Schematic, ударяя по блокам, закончить запись вероятностей или вывести уже записанные вероятности
select Minetest schematic probability values by punching nodes=выберите значения вероятностей для Minetest-схемы, ударя по нодам
finished Minetest schematic probability selection=выбор вероятностей для Minetest-схемы завершен
currently set node probabilities:=заданные вероятности нод на данный момент:
invalid node probability given, not saved=недопустимая вероятность ноды, не сохранена
Clears all objects within the WorldEdit region=Очистить все объекты в текущем WorldEdit-регионе
@1 objects cleared=очищено @1 объектов
### safe.lua ###
WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=ПРЕДУПРЕЖДЕНИЕ: эта операция может затронуть до @1 нод; введите @2 для продолжения или @3 для отмены
Confirm a pending operation=Подтвердить отложенную операцию
no operation pending=нет ожидающей операции
Abort a pending operation=Отклонить отложенную операцию
### cuboid.lua ###
Outset the selected region.=Расширить выделение региона.
Invalid direction: @1=Недопустимое направление: @1
Invalid number of arguments=Недопустимое количество аргументов
Region outset by @1 nodes=Регион расширен на @1 нод(у/ы)
Inset the selected region.=Сузить выделение региона.
Region inset by @1 nodes=Регион сужен на @1 нод(у/ы)
Shifts the selection area without moving its contents=Сдвинуть выделение региона без перемещения его содержимого
Invalid if looking straight up or down=Недопустимо, если смотреть прямо вверх или вниз
Region shifted by @1 nodes=Регион сдвинут на @1 нод(у/ы)
Expands the selection in the selected absolute or relative axis=Увеличить выделение региона по выбранной абсолютной или относительной оси
Region expanded by @1 nodes=Регион увеличен на @1 нод(у/ы)
Contracts the selection in the selected absolute or relative axis=Уменьшить выделение региона по выбранной абсолютной или относительной оси
Region contracted by @1 nodes=Регион уменьшен на @1 нод(у/ы)
Select a cube with side length <size> around position 1 and run <command> on region=Выделить куб с длиной стороны <size> вокруг позиции 1 и запустите <команду> в области
invalid usage: @1 cannot be used with cubeapply=недопустимое использование: @1 не может быть применено в cubeapply
Missing privileges: @1=Отсутствуют привилегии: @1
### wand.lua ###
WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=Инструмент WorldEdit Wand@nЛевая кнопка мыши, чтобы установить 1-ю позицию, правая кнопка мыши, чтобы установить 2-ю

View File

@ -0,0 +1,366 @@
local S = minetest.get_translator("worldedit_commands")
local function check_region(name)
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
end
worldedit.register_command("deleteblocks", {
params = "",
description = S("Remove all MapBlocks (16x16x16) containing the selected area from the map"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local success = minetest.delete_area(pos1, pos2)
if success then
return true, S("Area deleted.")
else
return false, S("There was an error during deletion of the area.")
end
end,
})
worldedit.register_command("clearobjects", {
params = "",
description = S("Clears all objects within the WorldEdit region"),
category = S("Node manipulation"), -- not really, but it doesn't fit anywhere else
privs = {worldedit=true},
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])
return true, S("@1 objects cleared", count)
end,
})
worldedit.register_command("set", {
params = "<node>",
description = S("Set the current WorldEdit region to <node>"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local node = worldedit.normalize_nodename(param)
if not node then
return false, S("invalid node name: @1", param)
end
return true, node
end,
nodes_needed = check_region,
func = function(name, node)
local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node)
return true, S("@1 nodes set", count)
end,
})
worldedit.register_command("param2", {
params = "<param2>",
description = S("Set param2 of all nodes in the current WorldEdit region to <param2>"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local param2 = tonumber(param)
if not param2 then
return false
elseif param2 < 0 or param2 > 255 then
return false, S("Param2 is out of range (must be between 0 and 255 inclusive!)")
end
return true, param2
end,
nodes_needed = check_region,
func = function(name, param2)
local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2)
return true, S("@1 nodes altered", count)
end,
})
worldedit.register_command("mix", {
params = "<node1> [count1] <node2> [count2] ...",
description = S("Fill the current WorldEdit region with a random mix of <node1>, ..."),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local nodes = {}
for nodename in param:gmatch("[^%s]+") do
if tonumber(nodename) ~= nil and #nodes > 0 then
local last_node = nodes[#nodes]
for i = 1, tonumber(nodename) do
nodes[#nodes + 1] = last_node
end
else
local node = worldedit.normalize_nodename(nodename)
if not node then
return false, S("invalid node name: @1", nodename)
end
nodes[#nodes + 1] = node
end
end
if #nodes == 0 then
return false
end
return true, nodes
end,
nodes_needed = check_region,
func = function(name, nodes)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count = worldedit.set(pos1, pos2, nodes)
return true, S("@1 nodes set", count)
end,
})
local check_replace = function(param)
local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$")
if found == nil then
return false
end
local newsearchnode = worldedit.normalize_nodename(searchnode)
if not newsearchnode then
return false, S("invalid search node name: @1", searchnode)
end
local newreplacenode = worldedit.normalize_nodename(replacenode)
if not newreplacenode then
return false, S("invalid replace node name: @1", replacenode)
end
return true, newsearchnode, newreplacenode
end
worldedit.register_command("replace", {
params = "<search node> <replace node>",
description = S("Replace all instances of <search node> with <replace node> in the current WorldEdit region"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
parse = check_replace,
nodes_needed = check_region,
func = function(name, search_node, replace_node)
local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
search_node, replace_node)
return true, S("@1 nodes replaced", count)
end,
})
worldedit.register_command("replaceinverse", {
params = "<search node> <replace node>",
description = S("Replace all nodes other than <search node> with <replace node> in the current WorldEdit region"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
parse = check_replace,
nodes_needed = check_region,
func = function(name, search_node, replace_node)
local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
search_node, replace_node, true)
return true, S("@1 nodes replaced", count)
end,
})
worldedit.register_command("fixlight", {
params = "",
description = S("Fix the lighting in the current WorldEdit region"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])
return true, S("@1 nodes updated", count)
end,
})
local drain_cache
local function drain(pos1, pos2)
if drain_cache == nil then
drain_cache = {}
for name, d in pairs(minetest.registered_nodes) do
if d.drawtype == "liquid" or d.drawtype == "flowingliquid" then
drain_cache[name] = true
end
end
end
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local count = 0
local get_node, remove_node = minetest.get_node, minetest.remove_node
for x = pos1.x, pos2.x do
for y = pos1.y, pos2.y do
for z = pos1.z, pos2.z do
local p = vector.new(x, y, z)
local n = get_node(p).name
if drain_cache[n] then
remove_node(p)
count = count + 1
end
end
end
end
return count
end
worldedit.register_command("drain", {
params = "",
description = S("Remove any fluid node within the current WorldEdit region"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local count = drain(worldedit.pos1[name], worldedit.pos2[name])
return true, S("@1 nodes updated", count)
end,
})
local clearcut_cache
local function clearcut(pos1, pos2)
-- decide which nodes we consider plants
if clearcut_cache == nil then
clearcut_cache = {}
for name, def in pairs(minetest.registered_nodes) do
local groups = def.groups or {}
if (
-- the groups say so
groups.flower or groups.grass or groups.flora or groups.plant or
groups.leaves or groups.tree or groups.leafdecay or groups.sapling or
-- drawtype heuristic
(def.is_ground_content and def.buildable_to and
(def.sunlight_propagates or not def.walkable)
and def.drawtype == "plantlike") or
-- if it's flammable, it probably needs to go too
(def.is_ground_content and not def.walkable and groups.flammable)
) then
clearcut_cache[name] = true
end
end
end
local plants = clearcut_cache
local count = 0
local prev, any
local get_node, remove_node = minetest.get_node, minetest.remove_node
for x = pos1.x, pos2.x do
for z = pos1.z, pos2.z do
prev = false
any = false
-- first pass: remove floating nodes that would be left over
for y = pos1.y, pos2.y do
local pos = vector.new(x, y, z)
local n = get_node(pos).name
if plants[n] then
prev = true
any = true
elseif prev then
local def = minetest.registered_nodes[n] or {}
local groups = def.groups or {}
if groups.attached_node or (def.buildable_to and groups.falling_node) then
remove_node(pos)
count = count + 1
else
prev = false
end
end
end
-- second pass: remove plants, top-to-bottom to avoid item drops
if any then
for y = pos2.y, pos1.y, -1 do
local pos = vector.new(x, y, z)
local n = get_node(pos).name
if plants[n] then
remove_node(pos)
count = count + 1
end
end
end
end
end
return count
end
worldedit.register_command("clearcut", {
params = "",
description = S("Remove any plant, tree or foliage-like nodes in the selected region"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
local count = clearcut(pos1, pos2)
return true, S("@1 nodes removed", count)
end,
})
worldedit.register_command("hide", {
params = "",
description = S("Hide all nodes in the current WorldEdit region non-destructively"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])
return true, S("@1 nodes hidden", count)
end,
})
worldedit.register_command("suppress", {
params = "<node>",
description = S("Suppress all <node> in the current WorldEdit region non-destructively"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local node = worldedit.normalize_nodename(param)
if not node then
return false, S("invalid node name: @1", param)
end
return true, node
end,
nodes_needed = check_region,
func = function(name, node)
local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)
return true, S("@1 nodes suppressed", count)
end,
})
worldedit.register_command("highlight", {
params = "<node>",
description = S("Highlight <node> in the current WorldEdit region by hiding everything else non-destructively"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local node = worldedit.normalize_nodename(param)
if not node then
return false, S("invalid node name: @1", param)
end
return true, node
end,
nodes_needed = check_region,
func = function(name, node)
local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node)
return true, S("@1 nodes highlighted", count)
end,
})
worldedit.register_command("restore", {
params = "",
description = S("Restores nodes hidden with WorldEdit in the current WorldEdit region"),
category = S("Node manipulation"),
privs = {worldedit=true},
require_pos = 2,
nodes_needed = check_region,
func = function(name)
local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])
return true, S("@1 nodes restored", count)
end,
})

View File

@ -2,57 +2,56 @@ worldedit.marker1 = {}
worldedit.marker2 = {}
worldedit.marker_region = {}
--marks worldedit region position 1
worldedit.mark_pos1 = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local init_sentinel = "new" .. tostring(math.random(99999))
--marks worldedit region position 1
worldedit.mark_pos1 = function(name, region_too)
local pos1 = worldedit.pos1[name]
if pos1 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos1)
end
if worldedit.marker1[name] ~= nil then --marker already exists
worldedit.marker1[name]:remove() --remove marker
worldedit.marker1[name] = nil
end
if pos1 ~= nil then
worldedit.keep_loaded(pos1, pos1)
--add marker
worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1")
worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel)
if worldedit.marker1[name] ~= nil then
worldedit.marker1[name]:get_luaentity().player_name = name
end
end
worldedit.mark_region(name)
if region_too == nil or region_too then
worldedit.mark_region(name)
end
end
--marks worldedit region position 2
worldedit.mark_pos2 = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
worldedit.mark_pos2 = function(name, region_too)
local pos2 = worldedit.pos2[name]
if pos2 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos2, pos2)
end
if worldedit.marker2[name] ~= nil then --marker already exists
worldedit.marker2[name]:remove() --remove marker
worldedit.marker2[name] = nil
end
if pos2 ~= nil then
worldedit.keep_loaded(pos2, pos2)
--add marker
worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2")
worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel)
if worldedit.marker2[name] ~= nil then
worldedit.marker2[name]:get_luaentity().player_name = name
end
end
worldedit.mark_region(name)
if region_too == nil or region_too then
worldedit.mark_region(name)
end
end
worldedit.mark_region = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if worldedit.marker_region[name] ~= nil then --marker already exists
--wip: make the area stay loaded somehow
for _, entity in ipairs(worldedit.marker_region[name]) do
entity:remove()
end
@ -64,7 +63,7 @@ worldedit.mark_region = function(name)
local vec = vector.subtract(pos2, pos1)
local maxside = math.max(vec.x, math.max(vec.y, vec.z))
local limit = tonumber(minetest.setting_get("active_object_send_range_blocks")) * 16
local limit = tonumber(minetest.settings:get("active_object_send_range_blocks")) * 16
if maxside > limit * 1.5 then
-- The client likely won't be able to see the plane markers as intended anyway,
-- thus don't place them and also don't load the area into memory
@ -74,15 +73,15 @@ 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 marker = minetest.add_entity({x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}, "worldedit:region_cube")
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({
visual_size={x=sizex * 2, y=sizey * 2},
@ -95,13 +94,14 @@ worldedit.mark_region = function(name)
--YZ plane markers
for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
local marker = minetest.add_entity({x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}, "worldedit:region_cube")
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({
visual_size={x=sizez * 2, y=sizey * 2},
collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez},
})
marker:setyaw(math.pi / 2)
marker:set_yaw(math.pi / 2)
marker:get_luaentity().player_name = name
table.insert(markers, marker)
end
@ -111,6 +111,13 @@ worldedit.mark_region = function(name)
end
end
--convenience function that calls everything
worldedit.marker_update = function(name)
worldedit.mark_pos1(name, false)
worldedit.mark_pos2(name, false)
worldedit.mark_region(name)
end
minetest.register_entity(":worldedit:pos1", {
initial_properties = {
visual = "cube",
@ -120,9 +127,11 @@ minetest.register_entity(":worldedit:pos1", {
"worldedit_pos1.png", "worldedit_pos1.png"},
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
physical = false,
static_save = false,
},
on_step = function(self, dtime)
if worldedit.marker1[self.player_name] == nil then
on_activate = function(self, staticdata, dtime_s)
if staticdata ~= init_sentinel then
-- we were loaded from before static_save = false was added
self.object:remove()
end
end,
@ -130,6 +139,9 @@ minetest.register_entity(":worldedit:pos1", {
self.object:remove()
worldedit.marker1[self.player_name] = nil
end,
on_blast = function(self, damage)
return false, false, {} -- don't damage or knockback
end,
})
minetest.register_entity(":worldedit:pos2", {
@ -141,9 +153,11 @@ minetest.register_entity(":worldedit:pos2", {
"worldedit_pos2.png", "worldedit_pos2.png"},
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
physical = false,
static_save = false,
},
on_step = function(self, dtime)
if worldedit.marker2[self.player_name] == nil then
on_activate = function(self, staticdata, dtime_s)
if staticdata ~= init_sentinel then
-- we were loaded from before static_save = false was added
self.object:remove()
end
end,
@ -151,20 +165,23 @@ minetest.register_entity(":worldedit:pos2", {
self.object:remove()
worldedit.marker2[self.player_name] = nil
end,
on_blast = function(self, damage)
return false, false, {} -- don't damage or knockback
end,
})
minetest.register_entity(":worldedit:region_cube", {
initial_properties = {
visual = "upright_sprite",
visual_size = {x=1.1, y=1.1},
textures = {"worldedit_cube.png"},
visual_size = {x=10, y=10},
physical = false,
static_save = false,
},
on_step = function(self, dtime)
if worldedit.marker_region[self.player_name] == nil then
on_activate = function(self, staticdata, dtime_s)
if staticdata ~= init_sentinel then
-- we were loaded from before static_save = false was added
self.object:remove()
return
end
end,
on_punch = function(self, hitter)
@ -177,5 +194,8 @@ minetest.register_entity(":worldedit:region_cube", {
end
worldedit.marker_region[self.player_name] = nil
end,
on_blast = function(self, damage)
return false, false, {} -- don't damage or knockback
end,
})

View File

@ -0,0 +1,3 @@
name = worldedit_commands
description = WorldEdit chat commands
depends = worldedit

View File

@ -0,0 +1,85 @@
-- Strips any kind of escape codes (translation, colors) from a string
-- https://github.com/minetest/minetest/blob/53dd7819277c53954d1298dfffa5287c306db8d0/src/util/string.cpp#L777
local function strip_escapes(input)
local s = function(idx) return input:sub(idx, idx) end
local out = ""
local i = 1
while i <= #input do
if s(i) == "\027" then -- escape sequence
i = i + 1
if s(i) == "(" then -- enclosed
i = i + 1
while i <= #input and s(i) ~= ")" do
if s(i) == "\\" then
i = i + 2
else
i = i + 1
end
end
end
else
out = out .. s(i)
end
i = i + 1
end
--print(("%q -> %q"):format(input, out))
return out
end
local function string_endswith(full, part)
return full:find(part, 1, true) == #full - #part + 1
end
local description_cache = nil
-- normalizes node "description" `nodename`, returning a string (or nil)
worldedit.normalize_nodename = function(nodename)
nodename = nodename:gsub("^%s*(.-)%s*$", "%1") -- strip spaces
if nodename == "" then return nil end
local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases
if minetest.registered_nodes[fullname] or fullname == "air" then -- full name
return fullname
end
nodename = nodename:lower()
for key, _ in pairs(minetest.registered_nodes) do
if string_endswith(key:lower(), ":" .. nodename) then -- matches name (w/o mod part)
return key
end
end
if description_cache == nil then
-- cache stripped descriptions
description_cache = {}
for key, value in pairs(minetest.registered_nodes) do
local desc = strip_escapes(value.description):gsub("\n.*", "", 1):lower()
if desc ~= "" then
description_cache[key] = desc
end
end
end
for key, desc in pairs(description_cache) do
if desc == nodename then -- matches description
return key
end
end
for key, desc in pairs(description_cache) do
if desc == nodename .. " block" then
-- fuzzy description match (e.g. "Steel" == "Steel Block")
return key
end
end
local match = nil
for key, value in pairs(description_cache) do
if value:find(nodename, 1, true) ~= nil then
if match ~= nil then
return nil
end
match = key -- substring description match (only if no ambiguities)
end
end
return match
end

View File

@ -0,0 +1,276 @@
local S = minetest.get_translator("worldedit_commands")
local check_cube = function(param)
local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
if found == nil then
return false
end
local node = worldedit.normalize_nodename(nodename)
if not node then
return false, S("invalid node name: @1", nodename)
end
return true, tonumber(w), tonumber(h), tonumber(l), node
end
worldedit.register_command("hollowcube", {
params = "<width> <height> <length> <node>",
description = S("Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>."),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_cube,
nodes_needed = function(name, w, h, l, node)
return w * h * l
end,
func = function(name, w, h, l, node)
local count = worldedit.cube(worldedit.pos1[name], w, h, l, node, true)
return true, S("@1 nodes added", count)
end,
})
worldedit.register_command("cube", {
params = "<width> <height> <length> <node>",
description = S("Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>."),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_cube,
nodes_needed = function(name, w, h, l, node)
return w * h * l
end,
func = function(name, w, h, l, node)
local count = worldedit.cube(worldedit.pos1[name], w, h, l, node)
return true, S("@1 nodes added", count)
end,
})
local check_sphere = function(param)
local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
if found == nil then
return false
end
local node = worldedit.normalize_nodename(nodename)
if not node then
return false, S("invalid node name: @1", nodename)
end
return true, tonumber(radius), node
end
worldedit.register_command("hollowsphere", {
params = "<radius> <node>",
description = S("Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_sphere,
nodes_needed = function(name, radius, node)
return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
end,
func = function(name, radius, node)
local count = worldedit.sphere(worldedit.pos1[name], radius, node, true)
return true, S("@1 nodes added", count)
end,
})
worldedit.register_command("sphere", {
params = "<radius> <node>",
description = S("Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_sphere,
nodes_needed = function(name, radius, node)
return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
end,
func = function(name, radius, node)
local count = worldedit.sphere(worldedit.pos1[name], radius, node)
return true, S("@1 nodes added", count)
end,
})
local check_dome = function(param)
local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
if found == nil then
return false
end
local node = worldedit.normalize_nodename(nodename)
if not node then
return false, S("invalid node name: @1", nodename)
end
return true, tonumber(radius), node
end
worldedit.register_command("hollowdome", {
params = "<radius> <node>",
description = S("Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_dome,
nodes_needed = function(name, radius, node)
return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
end,
func = function(name, radius, node)
local count = worldedit.dome(worldedit.pos1[name], radius, node, true)
return true, S("@1 nodes added", count)
end,
})
worldedit.register_command("dome", {
params = "<radius> <node>",
description = S("Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_dome,
nodes_needed = function(name, radius, node)
return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
end,
func = function(name, radius, node)
local count = worldedit.dome(worldedit.pos1[name], radius, node)
return true, S("@1 nodes added", count)
end,
})
local check_cylinder = function(param)
-- two radii
local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
if found == nil then
-- single radius
found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")
radius2 = radius1
end
if found == nil then
return false
end
local node = worldedit.normalize_nodename(nodename)
if not node then
return false, S("invalid node name: @1", nodename)
end
return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node
end
worldedit.register_command("hollowcylinder", {
params = "x/y/z/? <length> <radius1> [radius2] <node>",
description = S("Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>"),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_cylinder,
nodes_needed = function(name, axis, length, radius1, radius2, node)
local radius = math.max(radius1, radius2)
return math.ceil(math.pi * (radius ^ 2) * length)
end,
func = function(name, axis, length, radius1, radius2, node)
if axis == "?" then
local sign
axis, sign = worldedit.player_axis(name)
length = length * sign
end
local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node, true)
return true, S("@1 nodes added", count)
end,
})
worldedit.register_command("cylinder", {
params = "x/y/z/? <length> <radius1> [radius2] <node>",
description = S("Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>"),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_cylinder,
nodes_needed = function(name, axis, length, radius1, radius2, node)
local radius = math.max(radius1, radius2)
return math.ceil(math.pi * (radius ^ 2) * length)
end,
func = function(name, axis, length, radius1, radius2, node)
if axis == "?" then
local sign
axis, sign = worldedit.player_axis(name)
length = length * sign
end
local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node)
return true, S("@1 nodes added", count)
end,
})
local check_pyramid = function(param)
local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")
if found == nil then
return false
end
local node = worldedit.normalize_nodename(nodename)
if not node then
return false, S("invalid node name: @1", nodename)
end
return true, axis, tonumber(height), node
end
worldedit.register_command("hollowpyramid", {
params = "x/y/z/? <height> <node>",
description = S("Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>"),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_pyramid,
nodes_needed = function(name, axis, height, node)
return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
end,
func = function(name, axis, height, node)
if axis == "?" then
local sign
axis, sign = worldedit.player_axis(name)
height = height * sign
end
local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true)
return true, S("@1 nodes added", count)
end,
})
worldedit.register_command("pyramid", {
params = "x/y/z/? <height> <node>",
description = S("Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>"),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = check_pyramid,
nodes_needed = function(name, axis, height, node)
return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
end,
func = function(name, axis, height, node)
if axis == "?" then
local sign
axis, sign = worldedit.player_axis(name)
height = height * sign
end
local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node)
return true, S("@1 nodes added", count)
end,
})
worldedit.register_command("spiral", {
params = "<length> <height> <space> <node>",
description = S("Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>"),
category = S("Shapes"),
privs = {worldedit=true},
require_pos = 1,
parse = function(param)
local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
if found == nil then
return false
end
local node = worldedit.normalize_nodename(nodename)
if not node then
return false, S("invalid node name: @1", nodename)
end
return true, tonumber(length), tonumber(height), tonumber(space), node
end,
nodes_needed = function(name, length, height, space, node)
return (length + space) * height -- TODO: this is not the upper bound
end,
func = function(name, length, height, space, node)
local count = worldedit.spiral(worldedit.pos1[name], length, height, space, node)
return true, S("@1 nodes added", count)
end,
})

View File

@ -0,0 +1,234 @@
local S = minetest.get_translator("worldedit_commands")
worldedit.set_pos = {}
worldedit.inspect = {}
worldedit.register_command("inspect", {
params = "[on/off/1/0/true/false/yes/no/enable/disable]",
description = S("Enable or disable node inspection"),
privs = {worldedit=true},
parse = function(param)
if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then
return true, true
elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then
return true, false
end
return false
end,
func = function(name, enable)
if enable then
worldedit.inspect[name] = true
local axis, sign = worldedit.player_axis(name)
worldedit.player_notify(name, S(
"inspector: inspection enabled for @1, currently facing the @2 axis",
name,
axis .. (sign > 0 and "+" or "-")
), "info")
else
worldedit.inspect[name] = nil
worldedit.player_notify(name, S("inspector: inspection disabled"), "info")
end
end,
})
local VEC_6DIRS = {
vector.new( 1, 0, 0),
vector.new(-1, 0, 0),
vector.new( 0, 1, 0),
vector.new( 0,-1, 0),
vector.new( 0, 0, 1),
vector.new( 0, 0,-1),
}
local function get_node_rlight(pos)
local ret = 0
for _, v in ipairs(VEC_6DIRS) do
ret = math.max(ret, minetest.get_node_light(vector.add(pos, v)))
end
return ret
end
minetest.register_on_punchnode(function(pos, node, puncher)
local name = puncher:get_player_name()
if worldedit.inspect[name] then
local axis, sign = worldedit.player_axis(name)
local message = S(
"inspector: @1 at @2 (param1=@3, param2=@4, received light=@5) punched facing the @6 axis",
node.name,
minetest.pos_to_string(pos),
node.param1,
node.param2,
get_node_rlight(pos),
axis .. (sign > 0 and "+" or "-")
)
worldedit.player_notify(name, message, "info")
end
end)
worldedit.register_command("mark", {
params = "",
description = S("Show markers at the region positions"),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name)
worldedit.marker_update(name)
return true, S("region marked")
end,
})
worldedit.register_command("unmark", {
params = "",
description = S("Hide markers if currently shown"),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
worldedit.pos1[name] = nil
worldedit.pos2[name] = nil
worldedit.marker_update(name)
worldedit.pos1[name] = pos1
worldedit.pos2[name] = pos2
return true, S("region unmarked")
end,
})
local function set_pos1(name, pos)
assert(pos)
worldedit.pos1[name] = pos
worldedit.mark_pos1(name)
worldedit.player_notify(name, S("position @1 set to @2", 1, minetest.pos_to_string(pos)), "ok")
end
local function set_pos2(name, pos)
assert(pos)
worldedit.pos2[name] = pos
worldedit.mark_pos2(name)
worldedit.player_notify(name, S("position @1 set to @2", 2, minetest.pos_to_string(pos)), "ok")
end
worldedit.register_command("pos1", {
params = "",
description = S("Set WorldEdit region position @1 to the player's location", 1),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name)
local player = minetest.get_player_by_name(name)
if not player then return end
set_pos1(name, vector.round(player:get_pos()))
end,
})
worldedit.register_command("pos2", {
params = "",
description = S("Set WorldEdit region position @1 to the player's location", 2),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name)
local player = minetest.get_player_by_name(name)
if not player then return end
set_pos2(name, vector.round(player:get_pos()))
end,
})
worldedit.register_command("p", {
params = "set/set1/set2/get",
description = S("Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region"),
category = S("Region operations"),
privs = {worldedit=true},
parse = function(param)
if param == "set" or param == "set1" or param == "set2" or param == "get" then
return true, param
end
return false, S("unknown subcommand: @1", param)
end,
func = function(name, param)
local msg
if param == "set" then --set both WorldEdit positions
worldedit.set_pos[name] = "pos1"
msg = S("select positions by punching two nodes")
elseif param == "set1" then --set WorldEdit position 1
worldedit.set_pos[name] = "pos1only"
msg = S("select position @1 by punching a node", 1)
elseif param == "set2" then --set WorldEdit position 2
worldedit.set_pos[name] = "pos2"
msg = S("select position @1 by punching a node", 2)
elseif param == "get" then --display current WorldEdit positions
if worldedit.pos1[name] ~= nil then
msg = S("position @1: @2", 1, minetest.pos_to_string(worldedit.pos1[name]))
else
msg = S("position @1 not set", 1)
end
msg = msg .. "\n"
if worldedit.pos2[name] ~= nil then
msg = msg .. S("position @1: @2", 2, minetest.pos_to_string(worldedit.pos2[name]))
else
msg = msg .. S("position @1 not set", 2)
end
end
if msg then
worldedit.player_notify(name, msg, "info")
end
end,
})
worldedit.register_command("fixedpos", {
params = "set1/set2 <x> <y> <z>",
description = S("Set a WorldEdit region position to the position at (<x>, <y>, <z>)"),
category = S("Region operations"),
privs = {worldedit=true},
parse = function(param)
local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$")
if found == nil then
return false
end
return true, flag, vector.new(tonumber(x), tonumber(y), tonumber(z))
end,
func = function(name, flag, pos)
if flag == "set1" then
set_pos1(name, pos)
else --flag == "set2"
set_pos2(name, pos)
end
end,
})
minetest.register_on_punchnode(function(pos, node, puncher)
local name = puncher:get_player_name()
if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position
if worldedit.set_pos[name] == "pos1" then --setting position 1
set_pos1(name, pos)
worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation
elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only
set_pos1(name, pos)
worldedit.set_pos[name] = nil --finished setting positions
elseif worldedit.set_pos[name] == "pos2" then --setting position 2
set_pos2(name, pos)
worldedit.set_pos[name] = nil --finished setting positions
elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities
worldedit.prob_pos[name] = pos
minetest.show_formspec(name, "prob_val_enter", "field[text;;]")
end
end
end)
worldedit.register_command("volume", {
params = "",
description = S("Display the volume of the current WorldEdit region"),
category = S("Region operations"),
privs = {worldedit=true},
require_pos = 2,
func = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local volume = worldedit.volume(pos1, pos2)
local abs = math.abs
worldedit.player_notify(name, S(
"current region has a volume of @1 nodes (@2*@3*@4)",
volume,
abs(pos2.x - pos1.x) + 1,
abs(pos2.y - pos1.y) + 1,
abs(pos2.z - pos1.z) + 1
), "info")
end,
})

View File

@ -1,65 +1,63 @@
local S = minetest.get_translator("worldedit_commands")
local safe_region_limit = tonumber(minetest.settings:get("worldedit_safe_region_limit") or "20000")
local safe_region_callback = {}
local safe_region_param = {}
local function check_region(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] --obtain positions
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name, "no region selected")
return nil
end
return worldedit.volume(pos1, pos2)
end
--`count` is the number of nodes that would possibly be modified
--`callback` is a callback to run when the user confirms
--`nodes_needed` is a function accepting `param`, `pos1`, and `pos2` to calculate the number of nodes needed
local function safe_region(callback, nodes_needed)
--default node volume calculation
nodes_needed = nodes_needed or check_region
return function(name, param)
--check if the operation applies to a safe number of nodes
local count = nodes_needed(name, param)
if count == nil then return end --invalid command
if count < 10000 then
return callback(name, param)
end
--save callback to call later
safe_region_callback[name], safe_region_param[name] = callback, param
worldedit.player_notify(name, "WARNING: this operation could affect up to " .. count .. " nodes; type //y to continue or //n to cancel")
local function safe_region(name, count, callback)
if safe_region_limit <= 0 or count < safe_region_limit then
return callback()
end
-- save callback to call later
safe_region_callback[name] = callback
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)
safe_region_callback[name], safe_region_param[name] = nil, nil
safe_region_callback[name] = nil
end
minetest.register_chatcommand("/y", {
params = "",
description = "Confirm a pending operation",
description = S("Confirm a pending operation"),
func = function(name)
local callback, param = safe_region_callback[name], safe_region_param[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
safe_region_callback[name], safe_region_param[name] = nil, nil --reset pending operation
callback(name, param)
reset_pending(name)
callback(name)
end,
})
minetest.register_chatcommand("/n", {
params = "",
description = "Confirm 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
safe_region_callback[name], safe_region_param[name] = nil, nil
reset_pending(name)
end,
})
return safe_region, check_region, reset_pending
return safe_region, reset_pending

View File

@ -0,0 +1,284 @@
local S = minetest.get_translator("worldedit_commands")
worldedit.prob_pos = {}
worldedit.prob_list = {}
local function check_region(name)
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
end
local function check_filename(name)
return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil
end
local function open_schematic(name, param)
-- find the file in the world path
local testpaths = {
minetest.get_worldpath() .. "/schems/" .. param,
minetest.get_worldpath() .. "/schems/" .. param .. ".we",
minetest.get_worldpath() .. "/schems/" .. param .. ".wem",
}
local file, err
for index, path in ipairs(testpaths) do
file, err = io.open(path, "rb")
if not err then
break
end
end
if err then
worldedit.player_notify(name, S("Could not open file \"@1\"", param), "error")
return
end
local value = file:read("*a")
file:close()
local version = worldedit.read_header(value)
if version == nil or version == 0 then
worldedit.player_notify(name, S("Invalid file format!"), "error")
return
elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
worldedit.player_notify(name, S("Schematic was created with a newer version of WorldEdit."), "error")
return
end
return value
end
local function detect_misaligned_schematic(name, pos1, pos2)
pos1 = worldedit.sort_pos(pos1, pos2)
-- Check that allocate/save can position the schematic correctly
-- The expected behaviour is that the (0,0,0) corner of the schematic stays
-- at pos1, this only works when the minimum position is actually present
-- in the schematic.
local node = minetest.get_node(pos1)
local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"
if not have_node_at_origin then
worldedit.player_notify(name,
S("Warning: The schematic contains excessive free space and WILL be "..
"misaligned when allocated or loaded. To avoid this, shrink your "..
"area to cover exactly the nodes to be saved.")
)
end
end
worldedit.register_command("save", {
params = "<file>",
description = S("Save the current WorldEdit region to \"(world folder)/schems/<file>.we\""),
category = S("Schematics"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
if param == "" then
return false
end
if not check_filename(param) then
return false, S("Disallowed file name: @1", param)
end
return true, param
end,
nodes_needed = check_region,
func = function(name, param)
local result, count = worldedit.serialize(worldedit.pos1[name],
worldedit.pos2[name])
detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])
local path = minetest.get_worldpath() .. "/schems"
-- Create directory if it does not already exist
minetest.mkdir(path)
local filename = path .. "/" .. param .. ".we"
local file, err = io.open(filename, "wb")
if err ~= nil then
return false, S("Could not save file to \"@1\"", filename)
end
file:write(result)
file:flush()
file:close()
return true, S("@1 nodes saved", count)
end,
})
worldedit.register_command("allocate", {
params = "<file>",
description = S("Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region"),
category = S("Schematics"),
privs = {worldedit=true},
require_pos = 1,
parse = function(param)
if param == "" then
return false
end
if not check_filename(param) then
return false, S("Disallowed file name: @1", param)
end
return true, param
end,
func = function(name, param)
local pos = worldedit.pos1[name]
local value = open_schematic(name, param)
if not value then
return false
end
local nodepos1, nodepos2, count = worldedit.allocate(pos, value)
if not nodepos1 then
return false, S("Schematic empty, nothing allocated")
end
worldedit.pos1[name] = nodepos1
worldedit.pos2[name] = nodepos2
worldedit.marker_update(name)
return true, S("@1 nodes allocated", count)
end,
})
worldedit.register_command("load", {
params = "<file>",
description = S("Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin"),
category = S("Schematics"),
privs = {worldedit=true},
require_pos = 1,
parse = function(param)
if param == "" then
return false
end
if not check_filename(param) then
return false, S("Disallowed file name: @1", param)
end
return true, param
end,
func = function(name, param)
local pos = worldedit.pos1[name]
local value = open_schematic(name, param)
if not value then
return false
end
local count = worldedit.deserialize(pos, value)
if count == nil then
return false, S("Loading failed!")
end
return true, S("@1 nodes loaded", count)
end,
})
worldedit.register_command("mtschemcreate", {
params = "<file>",
description = S("Save the current WorldEdit region using the Minetest "..
"Schematic format to \"(world folder)/schems/<filename>.mts\""),
category = S("Schematics"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
if param == "" then
return false
end
if not check_filename(param) then
return false, S("Disallowed file name: @1", param)
end
return true, param
end,
nodes_needed = check_region,
func = function(name, param)
local path = minetest.get_worldpath() .. "/schems"
-- Create directory if it does not already exist
minetest.mkdir(path)
local filename = path .. "/" .. param .. ".mts"
local ret = minetest.create_schematic(worldedit.pos1[name],
worldedit.pos2[name], worldedit.prob_list[name],
filename)
worldedit.prob_list[name] = {}
if ret == nil then
return false, S("Failed to create Minetest schematic")
end
return true, S("Saved Minetest schematic to @1", param)
end,
})
worldedit.register_command("mtschemplace", {
params = "<file>",
description = S("Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin"),
category = S("Schematics"),
privs = {worldedit=true},
require_pos = 1,
parse = function(param)
local found, _, filename, rotation = param:find("^(.+)%s+([012789]+)$")
if found == nil then
filename = param
elseif rotation ~= "0" and rotation ~= "90" and rotation ~= "180" and rotation ~= "270" then
return false, S("Invalid rotation: @1", rotation)
end
if not check_filename(filename) then
return false, S("Disallowed file name: @1", filename)
end
return true, filename, rotation
end,
func = function(name, filename, rotation)
local pos = worldedit.pos1[name]
local path = minetest.get_worldpath() .. "/schems/" .. filename .. ".mts"
if minetest.place_schematic(pos, path, rotation) == nil then
return false, S("failed to place Minetest schematic")
end
return true, S("placed Minetest schematic @1 at @2",
filename, minetest.pos_to_string(pos))
end,
})
worldedit.register_command("mtschemprob", {
params = "start/finish/get",
description = S("Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry"),
category = S("Schematics"),
privs = {worldedit=true},
parse = function(param)
if param ~= "start" and param ~= "finish" and param ~= "get" then
return false, S("unknown subcommand: @1", param)
end
return true, param
end,
func = function(name, param)
if param == "start" then --start probability setting
worldedit.set_pos[name] = "prob"
worldedit.prob_list[name] = {}
worldedit.player_notify(name, S("select Minetest schematic probability values by punching nodes"), "info")
elseif param == "finish" then --finish probability setting
worldedit.set_pos[name] = nil
worldedit.player_notify(name, S("finished Minetest schematic probability selection"), "info")
elseif param == "get" then --get all nodes that had probabilities set on them
local text = ""
local problist = worldedit.prob_list[name]
if problist == nil then
return
end
for k,v in pairs(problist) do
local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100
text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "
end
worldedit.player_notify(name, S("currently set node probabilities:") .. "\n" .. text, "info")
end
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "prob_val_enter" then
local name = player:get_player_name()
local problist = worldedit.prob_list[name]
if problist == nil then
return
end
local e = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}
if e.pos == nil or e.prob == nil or e.prob < 0 or e.prob > 256 then
worldedit.player_notify(name, S("invalid node probability given, not saved"), "error")
return
end
problist[#problist+1] = e
end
end)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 B

After

Width:  |  Height:  |  Size: 112 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

View File

@ -0,0 +1,269 @@
local S = minetest.get_translator("worldedit_commands")
local function check_region(name)
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
end
worldedit.register_command("copy", {
params = "x/y/z/? <amount>",
description = S("Copy the current WorldEdit region along the given axis by <amount> nodes"),
category = S("Transformations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
if found == nil then
return false
end
return true, axis, tonumber(amount)
end,
nodes_needed = function(name, axis, amount)
return check_region(name) * 2
end,
func = function(name, axis, amount)
if axis == "?" then
local sign
axis, sign = worldedit.player_axis(name)
amount = amount * sign
end
local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)
return true, S("@1 nodes copied", count)
end,
})
worldedit.register_command("move", {
params = "x/y/z/? <amount>",
description = S("Move the current WorldEdit region along the given axis by <amount> nodes"),
category = S("Transformations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
if found == nil then
return false
end
return true, axis, tonumber(amount)
end,
nodes_needed = function(name, axis, amount)
return check_region(name) * 2
end,
func = function(name, axis, amount)
if axis == "?" then
local sign
axis, sign = worldedit.player_axis(name)
amount = amount * sign
end
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count = worldedit.move(pos1, pos2, axis, amount)
pos1[axis] = pos1[axis] + amount
pos2[axis] = pos2[axis] + amount
worldedit.marker_update(name)
return true, S("@1 nodes moved", count)
end,
})
worldedit.register_command("stack", {
params = "x/y/z/? <count>",
description = S("Stack the current WorldEdit region along the given axis <count> times"),
category = S("Transformations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")
if found == nil then
return false
end
return true, axis, tonumber(repetitions)
end,
nodes_needed = function(name, axis, repetitions)
return check_region(name) * math.abs(repetitions)
end,
func = function(name, axis, repetitions)
if axis == "?" then
local sign
axis, sign = worldedit.player_axis(name)
repetitions = repetitions * sign
end
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)
worldedit.stack(pos1, pos2, axis, repetitions, function()
worldedit.player_notify(name, S("@1 nodes stacked", count), "ok")
end)
end,
})
worldedit.register_command("stack2", {
params = "<count> <x> <y> <z>",
description = S("Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>"),
category = S("Transformations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local repetitions, incs = param:match("(%d+)%s*(.+)")
if repetitions == nil then
return false, S("invalid count: @1", param)
end
local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")
if x == nil then
return false, S("invalid increments: @1", param)
end
return true, tonumber(repetitions), vector.new(tonumber(x), tonumber(y), tonumber(z))
end,
nodes_needed = function(name, repetitions, offset)
return check_region(name) * repetitions
end,
func = function(name, repetitions, offset)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count = worldedit.volume(pos1, pos2) * repetitions
worldedit.stack2(pos1, pos2, offset, repetitions, function()
worldedit.player_notify(name, S("@1 nodes stacked", count), "ok")
end)
end,
})
worldedit.register_command("stretch", {
params = "<stretchx> <stretchy> <stretchz>",
description = S("Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin"),
category = S("Transformations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")
if found == nil then
return false
end
stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)
if stretchx == 0 or stretchy == 0 or stretchz == 0 then
return false, S("invalid scaling factors: @1", param)
end
return true, stretchx, stretchy, stretchz
end,
nodes_needed = function(name, stretchx, stretchy, stretchz)
return check_region(name) * stretchx * stretchy * stretchz
end,
func = function(name, stretchx, stretchy, stretchz)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
--reset markers to scaled positions
worldedit.pos1[name] = pos1
worldedit.pos2[name] = pos2
worldedit.marker_update(name)
return true, S("@1 nodes stretched", count)
end,
})
worldedit.register_command("transpose", {
params = "x/y/z/? x/y/z/?",
description = S("Transpose the current WorldEdit region along the given axes"),
category = S("Transformations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")
if found == nil then
return false
elseif axis1 == axis2 then
return false, S("invalid usage: axes must be different")
end
return true, axis1, axis2
end,
nodes_needed = check_region,
func = function(name, axis1, axis2)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if axis1 == "?" then axis1 = worldedit.player_axis(name) end
if axis2 == "?" then axis2 = worldedit.player_axis(name) end
local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
--reset markers to transposed positions
worldedit.pos1[name] = pos1
worldedit.pos2[name] = pos2
worldedit.marker_update(name)
return true, S("@1 nodes transposed", count)
end,
})
worldedit.register_command("flip", {
params = "x/y/z/?",
description = S("Flip the current WorldEdit region along the given axis"),
category = S("Transformations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then
return false
end
return true, param
end,
nodes_needed = check_region,
func = function(name, param)
if param == "?" then param = worldedit.player_axis(name) end
local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)
return true, S("@1 nodes flipped", count)
end,
})
worldedit.register_command("rotate", {
params = "x/y/z/? <angle>",
description = S("Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)"),
category = S("Transformations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")
if found == nil then
return false
end
angle = tonumber(angle)
if angle % 90 ~= 0 or angle % 360 == 0 then
return false, S("invalid usage: angle must be multiple of 90")
end
return true, axis, angle
end,
nodes_needed = check_region,
func = function(name, axis, angle)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if axis == "?" then axis = worldedit.player_axis(name) end
local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)
--reset markers to rotated positions
worldedit.pos1[name] = pos1
worldedit.pos2[name] = pos2
worldedit.marker_update(name)
return true, S("@1 nodes rotated", count)
end,
})
worldedit.register_command("orient", {
params = "<angle>",
description = S("Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)"),
category = S("Transformations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local found, _, angle = param:find("^([+-]?%d+)$")
if found == nil then
return false
end
angle = tonumber(angle)
if angle % 90 ~= 0 then
return false, S("invalid usage: angle must be multiple of 90")
end
return true, angle
end,
nodes_needed = check_region,
func = function(name, angle)
local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)
return true, S("@1 nodes oriented", count)
end,
})

View File

@ -1,22 +1,72 @@
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
else
return pointed_thing.under
end
end
local punched_air_time = {}
minetest.register_tool(":worldedit:wand", {
description = "WorldEdit Wand tool, Left-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 and pointed_thing ~= nil and pointed_thing.type == "node" then
local name = placer:get_player_name()
worldedit.pos1[name] = pointed_thing.under
if placer == nil or pointed_thing == nil then return end
local name = placer:get_player_name()
if pointed_thing.type == "node" then
-- set and mark pos1
worldedit.pos1[name] = above_or_under(placer, pointed_thing)
worldedit.mark_pos1(name)
elseif pointed_thing.type == "nothing" then
local now = minetest.get_us_time()
if now - (punched_air_time[name] or 0) < 1000 * 1000 then
-- reset markers
worldedit.registered_commands["reset"].func(name)
end
punched_air_time[name] = now
elseif pointed_thing.type == "object" then
local entity = pointed_thing.ref:get_luaentity()
if entity and entity.name == "worldedit:pos2" then
-- set pos1 = pos2
worldedit.pos1[name] = vector.copy(worldedit.pos2[name])
worldedit.mark_pos1(name)
end
end
end,
on_place = function(itemstack, placer, pointed_thing)
if placer == nil or (pointed_thing or {}).type ~= "node" then
return itemstack
end
local name = placer:get_player_name()
-- set and mark pos2
worldedit.pos2[name] = above_or_under(placer, pointed_thing)
worldedit.mark_pos2(name)
return itemstack -- nothing consumed, nothing changed
end,
on_place = function(itemstack, placer, pointed_thing) -- Left Click
if placer ~= nil and pointed_thing ~= nil and pointed_thing.type == "node" then
local name = placer:get_player_name()
worldedit.pos2[name] = pointed_thing.under
on_secondary_use = function(itemstack, user, pointed_thing)
if user == nil or (pointed_thing or {}).type ~= "object" then
return itemstack
end
local name = user:get_player_name()
local entity = pointed_thing.ref:get_luaentity()
if entity and entity.name == "worldedit:pos1" then
-- set pos2 = pos1
worldedit.pos2[name] = vector.copy(worldedit.pos1[name])
worldedit.mark_pos2(name)
end
return itemstack -- nothing consumed, nothing changed

View File

@ -1,6 +0,0 @@
worldedit
worldedit_commands
unified_inventory?
inventory_plus?
sfinv?
creative?

View File

@ -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", "?"}
@ -52,13 +54,18 @@ end
-- display node (or unknown_node image otherwise) at specified pos in formspec
local formspec_node = function(pos, nodename)
return nodename and string.format("item_image[%s;1,1;%s]", pos, nodename)
or string.format("image[%s;1,1;worldedit_gui_unknown.png]", pos)
local ndef = nodename and minetest.registered_nodes[nodename]
if nodename and ndef then
return string.format("item_image[%s;1,1;%s]", pos, nodename) ..
string.format("tooltip[%s;1,1;%s]", pos, minetest.formspec_escape(ndef.description))
else
return string.format("image[%s;1,1;worldedit_gui_unknown.png]", pos)
end
end
-- two further priv helpers
local function we_privs(command)
return minetest.chatcommands["/" .. command].privs
return worldedit.registered_commands[command].privs
end
local function combine_we_privs(list)
@ -69,11 +76,79 @@ local function combine_we_privs(list)
return combine_privs(unpack(args))
end
-- functions that handle value changing & page reshowing (without submitting)
local function copy_changes(name, fields, def)
for field, into in pairs(def) do
if into ~= true and fields[field] then
local value = tostring(fields[field])
if into == gui_axis1 or into == gui_axis2 then
into[name] = axis_indices[value]
elseif into == gui_angle then
into[name] = angle_indices[value]
else
into[name] = value
end
end
end
end
local function handle_changes(name, identifier, fields, def)
local any = false
for field, into in pairs(def) do
if fields.key_enter_field == field then
any = true
end
-- first condition: buttons (value not saved)
-- others: dropdowns which will be sent when their value changes
if into == true or into == gui_axis1 or into == gui_axis2 or into == gui_angle then
if fields[field] then
any = true
end
end
end
if not any then
return false
end
any = false
for field, into in pairs(def) do
if into ~= true and fields[field] then
local value = tostring(fields[field])
if into == gui_axis1 or into == gui_axis2 then
into[name] = axis_indices[value]
elseif into == gui_angle then
into[name] = angle_indices[value]
else
into[name] = value
end
if into == gui_nodename1 or into == gui_nodename2 then
any = true
end
end
end
-- Only nodename fields change based on the value, so only re-show the page if necessary
if any then
worldedit.show_page(name, identifier)
end
return true
end
-- This has the same behaviour as the player invoking the chat command
local function execute_worldedit_command(command_name, player_name, params)
local chatcmd = minetest.registered_chatcommands["/" .. command_name]
assert(chatcmd, "unknown command: " .. command_name)
local _, msg = chatcmd.func(player_name, params)
if msg then
minetest.chat_send_player(player_name, msg)
end
end
worldedit.register_gui_function("worldedit_gui_about", {
name = "About",
privs = {interact=true},
on_select = function(name)
minetest.chatcommands["/about"].func(name, "")
execute_worldedit_command("about", name, "")
end,
})
@ -81,7 +156,8 @@ worldedit.register_gui_function("worldedit_gui_inspect", {
name = "Toggle Inspect",
privs = we_privs("inspect"),
on_select = function(name)
minetest.chatcommands["/inspect"].func(name, worldedit.inspect[name] and "disable" or "enable")
execute_worldedit_command("inspect", name,
worldedit.inspect[name] and "disable" or "enable")
end,
})
@ -115,47 +191,47 @@ worldedit.register_gui_function("worldedit_gui_region", {
worldedit.register_gui_handler("worldedit_gui_region", function(name, fields)
if fields.worldedit_gui_p_get then
minetest.chatcommands["/p"].func(name, "get")
execute_worldedit_command("p", name, "get")
return true
elseif fields.worldedit_gui_p_set1 then
minetest.chatcommands["/p"].func(name, "set1")
execute_worldedit_command("p", name, "set1")
return true
elseif fields.worldedit_gui_p_set2 then
minetest.chatcommands["/p"].func(name, "set2")
execute_worldedit_command("p", name, "set2")
return true
elseif fields.worldedit_gui_pos1 then
minetest.chatcommands["/pos1"].func(name, "")
execute_worldedit_command("pos1", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_pos2 then
minetest.chatcommands["/pos2"].func(name, "")
execute_worldedit_command("pos2", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_reset then
minetest.chatcommands["/reset"].func(name, "")
execute_worldedit_command("reset", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_mark then
minetest.chatcommands["/mark"].func(name, "")
execute_worldedit_command("mark", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_unmark then
minetest.chatcommands["/unmark"].func(name, "")
execute_worldedit_command("unmark", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_volume then
minetest.chatcommands["/volume"].func(name, "")
execute_worldedit_command("volume", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_fixedpos_pos1_submit then
minetest.chatcommands["/fixedpos"].func(name, string.format("set1 %s %s %s",
execute_worldedit_command("fixedpos", name, ("set1 %s %s %s"):format(
tostring(fields.worldedit_gui_fixedpos_pos1x),
tostring(fields.worldedit_gui_fixedpos_pos1y),
tostring(fields.worldedit_gui_fixedpos_pos1z)))
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_fixedpos_pos2_submit then
minetest.chatcommands["/fixedpos"].func(name, string.format("set2 %s %s %s",
execute_worldedit_command("fixedpos", name, ("set2 %s %s %s"):format(
tostring(fields.worldedit_gui_fixedpos_pos2x),
tostring(fields.worldedit_gui_fixedpos_pos2y),
tostring(fields.worldedit_gui_fixedpos_pos2z)))
@ -173,6 +249,7 @@ worldedit.register_gui_function("worldedit_gui_set", {
local nodename = worldedit.normalize_nodename(node)
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_set") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_set_node;Name;%s]", minetest.formspec_escape(node)) ..
"field_close_on_enter[worldedit_gui_set_node;false]" ..
"button[4,1.18;1.5,0.8;worldedit_gui_set_search;Search]" ..
formspec_node("5.5,1.1", nodename) ..
"button_exit[0,2.5;3,0.8;worldedit_gui_set_submit;Set Nodes]"
@ -180,18 +257,22 @@ worldedit.register_gui_function("worldedit_gui_set", {
})
worldedit.register_gui_handler("worldedit_gui_set", function(name, fields)
if fields.worldedit_gui_set_search or fields.worldedit_gui_set_submit then
gui_nodename1[name] = tostring(fields.worldedit_gui_set_node)
local cg = {
worldedit_gui_set_search = true,
worldedit_gui_set_node = gui_nodename1,
}
local ret = handle_changes(name, "worldedit_gui_set", fields, cg)
if fields.worldedit_gui_set_submit then
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_set")
if fields.worldedit_gui_set_submit then
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
minetest.chatcommands["/set"].func(name, n)
end
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
execute_worldedit_command("set", name, n)
end
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_replace", {
@ -202,9 +283,11 @@ worldedit.register_gui_function("worldedit_gui_replace", {
local search_nodename, replace_nodename = worldedit.normalize_nodename(search), worldedit.normalize_nodename(replace)
return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_replace") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_replace_search;Name;%s]", minetest.formspec_escape(search)) ..
"field_close_on_enter[worldedit_gui_replace_search;false]" ..
"button[4,1.18;1.5,0.8;worldedit_gui_replace_search_search;Search]" ..
formspec_node("5.5,1.1", search_nodename) ..
string.format("field[0.5,2.5;4,0.8;worldedit_gui_replace_replace;Name;%s]", minetest.formspec_escape(replace)) ..
"field_close_on_enter[worldedit_gui_replace_replace;false]" ..
"button[4,2.18;1.5,0.8;worldedit_gui_replace_replace_search;Search]" ..
formspec_node("5.5,2.1", replace_nodename) ..
"button_exit[0,3.5;3,0.8;worldedit_gui_replace_submit;Replace Nodes]" ..
@ -213,28 +296,29 @@ worldedit.register_gui_function("worldedit_gui_replace", {
})
worldedit.register_gui_handler("worldedit_gui_replace", function(name, fields)
if fields.worldedit_gui_replace_search_search or fields.worldedit_gui_replace_replace_search
or fields.worldedit_gui_replace_submit or fields.worldedit_gui_replace_submit_inverse then
gui_nodename1[name] = tostring(fields.worldedit_gui_replace_search)
gui_nodename2[name] = tostring(fields.worldedit_gui_replace_replace)
local cg = {
worldedit_gui_replace_search_search = true,
worldedit_gui_replace_replace_search = true,
worldedit_gui_replace_search = gui_nodename1,
worldedit_gui_replace_replace = gui_nodename2,
}
local ret = handle_changes(name, "worldedit_gui_replace", fields, cg)
if fields.worldedit_gui_replace_submit or fields.worldedit_gui_replace_submit_inverse then
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_replace")
local submit = nil
if fields.worldedit_gui_replace_submit then
submit = "replace"
elseif fields.worldedit_gui_replace_submit_inverse then
local submit = "replace"
if fields.worldedit_gui_replace_submit_inverse then
submit = "replaceinverse"
end
if submit then
local n1 = worldedit.normalize_nodename(gui_nodename1[name])
local n2 = worldedit.normalize_nodename(gui_nodename2[name])
if n1 and n2 then
minetest.chatcommands["/"..submit].func(name, string.format("%s %s", n1, n2))
end
local n1 = worldedit.normalize_nodename(gui_nodename1[name])
local n2 = worldedit.normalize_nodename(gui_nodename2[name])
if n1 and n2 then
execute_worldedit_command(submit, name, n1 .. " " .. n2)
end
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_sphere_dome", {
@ -245,9 +329,11 @@ worldedit.register_gui_function("worldedit_gui_sphere_dome", {
local nodename = worldedit.normalize_nodename(node)
return "size[6.5,5]" .. worldedit.get_formspec_header("worldedit_gui_sphere_dome") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_sphere_dome_node;Name;%s]", minetest.formspec_escape(node)) ..
"field_close_on_enter[worldedit_gui_sphere_dome_node;false]" ..
"button[4,1.18;1.5,0.8;worldedit_gui_sphere_dome_search;Search]" ..
formspec_node("5.5,1.1", nodename) ..
string.format("field[0.5,2.5;4,0.8;worldedit_gui_sphere_dome_radius;Radius;%s]", minetest.formspec_escape(radius)) ..
"field_close_on_enter[worldedit_gui_sphere_dome_radius;false]" ..
"button_exit[0,3.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow;Hollow Sphere]" ..
"button_exit[3.5,3.5;3,0.8;worldedit_gui_sphere_dome_submit_solid;Solid Sphere]" ..
"button_exit[0,4.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow_dome;Hollow Dome]" ..
@ -256,32 +342,33 @@ worldedit.register_gui_function("worldedit_gui_sphere_dome", {
})
worldedit.register_gui_handler("worldedit_gui_sphere_dome", function(name, fields)
if fields.worldedit_gui_sphere_dome_search
or fields.worldedit_gui_sphere_dome_submit_hollow or fields.worldedit_gui_sphere_dome_submit_solid
local cg = {
worldedit_gui_sphere_dome_search = true,
worldedit_gui_sphere_dome_node = gui_nodename1,
worldedit_gui_sphere_dome_radius = gui_distance2,
}
local ret = handle_changes(name, "worldedit_gui_sphere_dome", fields, cg)
if fields.worldedit_gui_sphere_dome_submit_hollow or fields.worldedit_gui_sphere_dome_submit_solid
or fields.worldedit_gui_sphere_dome_submit_hollow_dome or fields.worldedit_gui_sphere_dome_submit_solid_dome then
gui_nodename1[name] = tostring(fields.worldedit_gui_sphere_dome_node)
gui_distance2[name] = tostring(fields.worldedit_gui_sphere_dome_radius)
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_sphere_dome")
local submit = nil
if fields.worldedit_gui_sphere_dome_submit_hollow then
submit = "hollowsphere"
elseif fields.worldedit_gui_sphere_dome_submit_solid then
local submit = "hollowsphere"
if fields.worldedit_gui_sphere_dome_submit_solid then
submit = "sphere"
elseif fields.worldedit_gui_sphere_dome_submit_hollow_dome then
submit = "hollowdome"
elseif fields.worldedit_gui_sphere_dome_submit_solid_dome then
submit = "dome"
end
if submit then
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
minetest.chatcommands["/"..submit].func(name, string.format("%s %s", gui_distance2[name], n))
end
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
execute_worldedit_command(submit, name,
gui_distance2[name] .. " " .. n)
end
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_cylinder", {
@ -293,12 +380,16 @@ worldedit.register_gui_function("worldedit_gui_cylinder", {
local nodename = worldedit.normalize_nodename(node)
return "size[6.5,6]" .. worldedit.get_formspec_header("worldedit_gui_cylinder") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_cylinder_node;Name;%s]", minetest.formspec_escape(node)) ..
"field_close_on_enter[worldedit_gui_cylinder_node;false]" ..
"button[4,1.18;1.5,0.8;worldedit_gui_cylinder_search;Search]" ..
formspec_node("5.5,1.1", nodename) ..
string.format("field[0.5,2.5;4,0.8;worldedit_gui_cylinder_length;Length;%s]", minetest.formspec_escape(length)) ..
string.format("dropdown[4,2.18;2.5;worldedit_gui_cylinder_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
string.format("field[0.5,3.5;2,0.8;worldedit_gui_cylinder_radius1;Base Radius;%s]", minetest.formspec_escape(radius1)) ..
string.format("field[2.5,3.5;2,0.8;worldedit_gui_cylinder_radius2;Top Radius;%s]", minetest.formspec_escape(radius2)) ..
"field_close_on_enter[worldedit_gui_cylinder_length;false]" ..
"field_close_on_enter[worldedit_gui_cylinder_radius1;false]" ..
"field_close_on_enter[worldedit_gui_cylinder_radius2;false]" ..
"label[0.25,4;Equal base and top radius creates a cylinder,\n"..
"zero top radius creates a cone.\nConsult documentation for more information.]"..
"button_exit[0,5.5;3,0.8;worldedit_gui_cylinder_submit_hollow;Hollow Cylinder]" ..
@ -307,36 +398,31 @@ worldedit.register_gui_function("worldedit_gui_cylinder", {
})
worldedit.register_gui_handler("worldedit_gui_cylinder", function(name, fields)
if fields.worldedit_gui_cylinder_search
or fields.worldedit_gui_cylinder_submit_hollow or fields.worldedit_gui_cylinder_submit_solid then
gui_nodename1[name] = tostring(fields.worldedit_gui_cylinder_node)
gui_axis1[name] = axis_indices[fields.worldedit_gui_cylinder_axis]
gui_distance1[name] = tostring(fields.worldedit_gui_cylinder_length)
gui_distance2[name] = tostring(fields.worldedit_gui_cylinder_radius1)
gui_distance3[name] = tostring(fields.worldedit_gui_cylinder_radius2)
local cg = {
worldedit_gui_cylinder_search = true,
worldedit_gui_cylinder_node = gui_nodename1,
worldedit_gui_cylinder_axis = gui_axis1,
worldedit_gui_cylinder_length = gui_distance1,
worldedit_gui_cylinder_radius1 = gui_distance2,
worldedit_gui_cylinder_radius2 = gui_distance3,
}
local ret = handle_changes(name, "worldedit_gui_cylinder", fields, cg)
if fields.worldedit_gui_cylinder_submit_hollow or fields.worldedit_gui_cylinder_submit_solid then
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_cylinder")
local submit = nil
if fields.worldedit_gui_cylinder_submit_hollow then
submit = "hollowcylinder"
elseif fields.worldedit_gui_cylinder_submit_solid then
local submit = "hollowcylinder"
if fields.worldedit_gui_cylinder_submit_solid then
submit = "cylinder"
end
if submit then
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
local args = string.format("%s %s %s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_distance2[name], gui_distance3[name], n)
minetest.chatcommands["/"..submit].func(name, args)
end
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
local args = string.format("%s %s %s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_distance2[name], gui_distance3[name], n)
execute_worldedit_command(submit, name, args)
end
return true
end
if fields.worldedit_gui_cylinder_axis then
gui_axis1[name] = axis_indices[fields.worldedit_gui_cylinder_axis]
worldedit.show_page(name, "worldedit_gui_cylinder")
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_pyramid", {
@ -347,42 +433,42 @@ worldedit.register_gui_function("worldedit_gui_pyramid", {
local nodename = worldedit.normalize_nodename(node)
return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_pyramid") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_pyramid_node;Name;%s]", minetest.formspec_escape(node)) ..
"field_close_on_enter[worldedit_gui_pyramid_node;false]" ..
"button[4,1.18;1.5,0.8;worldedit_gui_pyramid_search;Search]" ..
formspec_node("5.5,1.1", nodename) ..
string.format("field[0.5,2.5;4,0.8;worldedit_gui_pyramid_length;Length;%s]", minetest.formspec_escape(length)) ..
string.format("dropdown[4,2.18;2.5;worldedit_gui_pyramid_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
"field_close_on_enter[worldedit_gui_pyramid_length;false]" ..
"button_exit[0,3.5;3,0.8;worldedit_gui_pyramid_submit_hollow;Hollow Pyramid]" ..
"button_exit[3.5,3.5;3,0.8;worldedit_gui_pyramid_submit_solid;Solid Pyramid]"
end,
})
worldedit.register_gui_handler("worldedit_gui_pyramid", function(name, fields)
if fields.worldedit_gui_pyramid_search or fields.worldedit_gui_pyramid_submit_solid or fields.worldedit_gui_pyramid_submit_hollow or fields.worldedit_gui_pyramid_axis then
gui_nodename1[name] = tostring(fields.worldedit_gui_pyramid_node)
gui_axis1[name] = axis_indices[fields.worldedit_gui_pyramid_axis]
gui_distance1[name] = tostring(fields.worldedit_gui_pyramid_length)
local cg = {
worldedit_gui_pyramid_search = true,
worldedit_gui_pyramid_node = gui_nodename1,
worldedit_gui_pyramid_axis = gui_axis1,
worldedit_gui_pyramid_length = gui_distance1,
}
local ret = handle_changes(name, "worldedit_gui_pyramid", fields, cg)
if fields.worldedit_gui_pyramid_submit_solid or fields.worldedit_gui_pyramid_submit_hollow then
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_pyramid")
local submit = nil
if fields.worldedit_gui_pyramid_submit_solid then
submit = "pyramid"
elseif fields.worldedit_gui_pyramid_submit_hollow then
local submit = "pyramid"
if fields.worldedit_gui_pyramid_submit_hollow then
submit = "hollowpyramid"
end
if submit then
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
minetest.chatcommands["/"..submit].func(name, string.format("%s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], n))
end
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
execute_worldedit_command(submit, name,
string.format("%s %s %s", axis_values[gui_axis1[name]],
gui_distance1[name], n))
end
return true
end
if fields.worldedit_gui_pyramid_axis then
gui_axis1[name] = axis_indices[fields.worldedit_gui_pyramid_axis]
worldedit.show_page(name, "worldedit_gui_pyramid")
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_spiral", {
@ -393,31 +479,41 @@ worldedit.register_gui_function("worldedit_gui_spiral", {
local nodename = worldedit.normalize_nodename(node)
return "size[6.5,6]" .. worldedit.get_formspec_header("worldedit_gui_spiral") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_spiral_node;Name;%s]", minetest.formspec_escape(node)) ..
"field_close_on_enter[worldedit_gui_spiral_node;false]" ..
"button[4,1.18;1.5,0.8;worldedit_gui_spiral_search;Search]" ..
formspec_node("5.5,1.1", nodename) ..
string.format("field[0.5,2.5;4,0.8;worldedit_gui_spiral_length;Side Length;%s]", minetest.formspec_escape(length)) ..
string.format("field[0.5,3.5;4,0.8;worldedit_gui_spiral_height;Height;%s]", minetest.formspec_escape(height)) ..
string.format("field[0.5,4.5;4,0.8;worldedit_gui_spiral_space;Wall Spacing;%s]", minetest.formspec_escape(space)) ..
"field_close_on_enter[worldedit_gui_spiral_length;false]" ..
"field_close_on_enter[worldedit_gui_spiral_height;false]" ..
"field_close_on_enter[worldedit_gui_spiral_space;false]" ..
"button_exit[0,5.5;3,0.8;worldedit_gui_spiral_submit;Spiral]"
end,
})
worldedit.register_gui_handler("worldedit_gui_spiral", function(name, fields)
if fields.worldedit_gui_spiral_search or fields.worldedit_gui_spiral_submit then
gui_nodename1[name] = fields.worldedit_gui_spiral_node
gui_distance1[name] = tostring(fields.worldedit_gui_spiral_length)
gui_distance2[name] = tostring(fields.worldedit_gui_spiral_height)
gui_distance3[name] = tostring(fields.worldedit_gui_spiral_space)
local cg = {
worldedit_gui_spiral_search = true,
worldedit_gui_spiral_node = gui_nodename1,
worldedit_gui_spiral_length = gui_distance1,
worldedit_gui_spiral_height = gui_distance2,
worldedit_gui_spiral_space = gui_distance3,
}
local ret = handle_changes(name, "worldedit_gui_spiral", fields, cg)
if fields.worldedit_gui_spiral_submit then
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_spiral")
if fields.worldedit_gui_spiral_submit then
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
minetest.chatcommands["/spiral"].func(name, string.format("%s %s %s %s", gui_distance1[name], gui_distance2[name], gui_distance3[name], n))
end
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
execute_worldedit_command("spiral", name,
string.format("%s %s %s %s", gui_distance1[name],
gui_distance2[name], gui_distance3[name], n))
end
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_copy_move", {
@ -429,29 +525,31 @@ worldedit.register_gui_function("worldedit_gui_copy_move", {
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_copy_move") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_copy_move_amount;Amount;%s]", minetest.formspec_escape(amount)) ..
string.format("dropdown[4,1.18;2.5;worldedit_gui_copy_move_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
"field_close_on_enter[worldedit_gui_copy_move_amount;false]" ..
"button_exit[0,2.5;3,0.8;worldedit_gui_copy_move_copy;Copy Region]" ..
"button_exit[3.5,2.5;3,0.8;worldedit_gui_copy_move_move;Move Region]"
end,
})
worldedit.register_gui_handler("worldedit_gui_copy_move", function(name, fields)
local cg = {
worldedit_gui_copy_move_amount = gui_distance1,
worldedit_gui_copy_move_axis = gui_axis1,
}
local ret = handle_changes(name, "worldedit_gui_spiral", fields, cg)
if fields.worldedit_gui_copy_move_copy or fields.worldedit_gui_copy_move_move then
gui_axis1[name] = axis_indices[fields.worldedit_gui_copy_move_axis] or 4
gui_distance1[name] = tostring(fields.worldedit_gui_copy_move_amount)
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_copy_move")
if fields.worldedit_gui_copy_move_copy then
minetest.chatcommands["/copy"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], gui_distance1[name]))
else --fields.worldedit_gui_copy_move_move
minetest.chatcommands["/move"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], gui_distance1[name]))
local submit = "copy"
if fields.worldedit_gui_copy_move_move then
submit = "move"
end
execute_worldedit_command(submit, name,
axis_values[gui_axis1[name]] .. " " .. gui_distance1[name])
return true
end
if fields.worldedit_gui_copy_move_axis then
gui_axis1[name] = axis_indices[fields.worldedit_gui_copy_move_axis] or 4
worldedit.show_page(name, "worldedit_gui_copy_move")
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_stack", {
@ -462,24 +560,26 @@ worldedit.register_gui_function("worldedit_gui_stack", {
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_stack") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_stack_count;Count;%s]", minetest.formspec_escape(count)) ..
string.format("dropdown[4,1.18;2.5;worldedit_gui_stack_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
"field_close_on_enter[worldedit_gui_stack_count;false]" ..
"button_exit[0,2.5;3,0.8;worldedit_gui_stack_submit;Stack]"
end,
})
worldedit.register_gui_handler("worldedit_gui_stack", function(name, fields)
local cg = {
worldedit_gui_stack_axis = gui_axis1,
worldedit_gui_stack_count = gui_count1,
}
local ret = handle_changes(name, "worldedit_gui_stack", fields, cg)
if fields.worldedit_gui_stack_submit then
gui_axis1[name] = axis_indices[fields.worldedit_gui_stack_axis]
gui_count1[name] = tostring(fields.worldedit_gui_stack_count)
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_stack")
minetest.chatcommands["/stack"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], gui_count1[name]))
execute_worldedit_command("stack", name,
axis_values[gui_axis1[name]] .. " " .. gui_count1[name])
return true
end
if fields.worldedit_gui_stack_axis then
gui_axis1[name] = axis_indices[fields.worldedit_gui_stack_axis]
worldedit.show_page(name, "worldedit_gui_stack")
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_stretch", {
@ -491,20 +591,29 @@ worldedit.register_gui_function("worldedit_gui_stretch", {
string.format("field[0.5,1.5;4,0.8;worldedit_gui_stretch_x;Stretch X;%s]", minetest.formspec_escape(stretchx)) ..
string.format("field[0.5,2.5;4,0.8;worldedit_gui_stretch_y;Stretch Y;%s]", minetest.formspec_escape(stretchy)) ..
string.format("field[0.5,3.5;4,0.8;worldedit_gui_stretch_z;Stretch Z;%s]", minetest.formspec_escape(stretchz)) ..
"field_close_on_enter[worldedit_gui_stretch_x;false]" ..
"field_close_on_enter[worldedit_gui_stretch_y;false]" ..
"field_close_on_enter[worldedit_gui_stretch_z;false]" ..
"button_exit[0,4.5;3,0.8;worldedit_gui_stretch_submit;Stretch]"
end,
})
worldedit.register_gui_handler("worldedit_gui_stretch", function(name, fields)
local cg = {
worldedit_gui_stretch_x = gui_count1,
worldedit_gui_stretch_y = gui_count2,
worldedit_gui_stretch_z = gui_count3,
}
local ret = handle_changes(name, "worldedit_gui_stretch", fields, cg)
if fields.worldedit_gui_stretch_submit then
gui_count1[name] = tostring(fields.worldedit_gui_stretch_x)
gui_count2[name] = tostring(fields.worldedit_gui_stretch_y)
gui_count3[name] = tostring(fields.worldedit_gui_stretch_z)
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_stretch")
minetest.chatcommands["/stretch"].func(name, string.format("%s %s %s", gui_count1[name], gui_count2[name], gui_count3[name]))
execute_worldedit_command("stretch", name, string.format("%s %s %s",
gui_count1[name], gui_count2[name], gui_count3[name]))
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_transpose", {
@ -520,23 +629,19 @@ worldedit.register_gui_function("worldedit_gui_transpose", {
})
worldedit.register_gui_handler("worldedit_gui_transpose", function(name, fields)
local cg = {
worldedit_gui_transpose_axis1 = gui_axis1,
worldedit_gui_transpose_axis2 = gui_axis2,
}
local ret = handle_changes(name, "worldedit_gui_transpose", fields, cg)
if fields.worldedit_gui_transpose_submit then
gui_axis1[name] = axis_indices[fields.worldedit_gui_transpose_axis1]
worldedit.show_page(name, "worldedit_gui_transpose")
minetest.chatcommands["/transpose"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], axis_values[gui_axis2[name]]))
copy_changes(name, fields, cg)
execute_worldedit_command("transpose", name,
axis_values[gui_axis1[name]] .. " " .. axis_values[gui_axis2[name]])
return true
end
if fields.worldedit_gui_transpose_axis1 then
gui_axis1[name] = axis_indices[fields.worldedit_gui_transpose_axis1]
worldedit.show_page(name, "worldedit_gui_transpose")
return true
end
if fields.worldedit_gui_transpose_axis2 then
gui_axis2[name] = axis_indices[fields.worldedit_gui_transpose_axis2]
worldedit.show_page(name, "worldedit_gui_transpose")
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_flip", {
@ -551,18 +656,18 @@ worldedit.register_gui_function("worldedit_gui_flip", {
})
worldedit.register_gui_handler("worldedit_gui_flip", function(name, fields)
local cg = {
worldedit_gui_flip_axis = gui_axis1
}
local ret = handle_changes(name, "worldedit_gui_flip", fields, cg)
if fields.worldedit_gui_flip_submit then
gui_axis1[name] = axis_indices[fields.worldedit_gui_flip_axis]
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_flip")
minetest.chatcommands["/flip"].func(name, axis_values[gui_axis1[name]])
execute_worldedit_command("flip", name, axis_values[gui_axis1[name]])
return true
end
if fields.worldedit_gui_flip_axis then
gui_axis1[name] = axis_indices[fields.worldedit_gui_flip_axis]
worldedit.show_page(name, "worldedit_gui_flip")
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_rotate", {
@ -578,24 +683,20 @@ worldedit.register_gui_function("worldedit_gui_rotate", {
})
worldedit.register_gui_handler("worldedit_gui_rotate", function(name, fields)
local cg = {
worldedit_gui_rotate_axis = gui_axis1,
worldedit_gui_rotate_angle = gui_angle,
}
local ret = handle_changes(name, "worldedit_gui_rotate", fields, cg)
if fields.worldedit_gui_rotate_submit then
gui_axis1[name] = axis_indices[fields.worldedit_gui_rotate_axis]
gui_angle[name] = angle_indices[fields.worldedit_gui_rotate_angle]
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_rotate")
minetest.chatcommands["/rotate"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], angle_values[gui_angle[name]]))
execute_worldedit_command("rotate", name,
axis_values[gui_axis1[name]] .. " " .. angle_values[gui_angle[name]])
return true
end
if fields.worldedit_gui_rotate_axis then
gui_axis1[name] = axis_indices[fields.worldedit_gui_rotate_axis]
worldedit.show_page(name, "worldedit_gui_rotate")
return true
end
if fields.worldedit_gui_rotate_angle then
gui_angle[name] = angle_indices[fields.worldedit_gui_rotate_angle]
worldedit.show_page(name, "worldedit_gui_rotate")
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_orient", {
@ -610,25 +711,26 @@ worldedit.register_gui_function("worldedit_gui_orient", {
})
worldedit.register_gui_handler("worldedit_gui_orient", function(name, fields)
local cg = {
worldedit_gui_orient_angle = gui_angle,
}
local ret = handle_changes(name, "worldedit_gui_orient", fields, cg)
if fields.worldedit_gui_orient_submit then
gui_angle[name] = angle_indices[fields.worldedit_gui_orient_angle]
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_orient")
minetest.chatcommands["/orient"].func(name, tostring(angle_values[gui_angle[name]]))
execute_worldedit_command("orient", name,
tostring(angle_values[gui_angle[name]]))
return true
end
if fields.worldedit_gui_orient_angle then
gui_angle[name] = angle_indices[fields.worldedit_gui_orient_angle]
worldedit.show_page(name, "worldedit_gui_orient")
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_fixlight", {
name = "Fix Lighting",
privs = we_privs("fixlight"),
on_select = function(name)
minetest.chatcommands["/fixlight"].func(name, "")
execute_worldedit_command("fixlight", name, "")
end,
})
@ -636,7 +738,7 @@ worldedit.register_gui_function("worldedit_gui_hide", {
name = "Hide Region",
privs = we_privs("hide"),
on_select = function(name)
minetest.chatcommands["/hide"].func(name, "")
execute_worldedit_command("hide", name, "")
end,
})
@ -648,6 +750,7 @@ worldedit.register_gui_function("worldedit_gui_suppress", {
local nodename = worldedit.normalize_nodename(node)
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_suppress") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_suppress_node;Name;%s]", minetest.formspec_escape(node)) ..
"field_close_on_enter[worldedit_gui_suppress_node;false]" ..
"button[4,1.18;1.5,0.8;worldedit_gui_suppress_search;Search]" ..
formspec_node("5.5,1.1", nodename) ..
"button_exit[0,2.5;3,0.8;worldedit_gui_suppress_submit;Suppress Nodes]"
@ -655,18 +758,22 @@ worldedit.register_gui_function("worldedit_gui_suppress", {
})
worldedit.register_gui_handler("worldedit_gui_suppress", function(name, fields)
if fields.worldedit_gui_suppress_search or fields.worldedit_gui_suppress_submit then
gui_nodename1[name] = tostring(fields.worldedit_gui_suppress_node)
local cg = {
worldedit_gui_suppress_search = true,
worldedit_gui_suppress_node = gui_nodename1,
}
local ret = handle_changes(name, "worldedit_gui_suppress", fields, cg)
if fields.worldedit_gui_suppress_submit then
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_suppress")
if fields.worldedit_gui_suppress_submit then
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
minetest.chatcommands["/suppress"].func(name, n)
end
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
execute_worldedit_command("suppress", name, n)
end
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_highlight", {
@ -677,6 +784,7 @@ worldedit.register_gui_function("worldedit_gui_highlight", {
local nodename = worldedit.normalize_nodename(node)
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_highlight") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_highlight_node;Name;%s]", minetest.formspec_escape(node)) ..
"field_close_on_enter[worldedit_gui_highlight_node;false]" ..
"button[4,1.18;1.5,0.8;worldedit_gui_highlight_search;Search]" ..
formspec_node("5.5,1.1", nodename) ..
"button_exit[0,2.5;3,0.8;worldedit_gui_highlight_submit;Highlight Nodes]"
@ -684,25 +792,29 @@ worldedit.register_gui_function("worldedit_gui_highlight", {
})
worldedit.register_gui_handler("worldedit_gui_highlight", function(name, fields)
if fields.worldedit_gui_highlight_search or fields.worldedit_gui_highlight_submit then
gui_nodename1[name] = tostring(fields.worldedit_gui_highlight_node)
local cg = {
worldedit_gui_highlight_search = true,
worldedit_gui_highlight_node = gui_nodename1,
}
local ret = handle_changes(name, "worldedit_gui_highlight", fields, cg)
if fields.worldedit_gui_highlight_submit then
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_highlight")
if fields.worldedit_gui_highlight_submit then
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
minetest.chatcommands["/highlight"].func(name, n)
end
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
execute_worldedit_command("highlight", name, n)
end
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_restore", {
name = "Restore Region",
privs = we_privs("restore"),
on_select = function(name)
minetest.chatcommands["/restore"].func(name, "")
execute_worldedit_command("restore", name, "")
end,
})
@ -713,6 +825,7 @@ worldedit.register_gui_function("worldedit_gui_save_load", {
local filename = gui_filename[name]
return "size[6,4]" .. worldedit.get_formspec_header("worldedit_gui_save_load") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_save_filename;Filename;%s]", minetest.formspec_escape(filename)) ..
"field_close_on_enter[worldedit_gui_save_filename;false]" ..
"button_exit[0,2.5;3,0.8;worldedit_gui_save_load_submit_save;Save]" ..
"button_exit[3,2.5;3,0.8;worldedit_gui_save_load_submit_allocate;Allocate]" ..
"button_exit[0,3.5;3,0.8;worldedit_gui_save_load_submit_load;Load]"
@ -723,12 +836,13 @@ worldedit.register_gui_handler("worldedit_gui_save_load", function(name, fields)
if fields.worldedit_gui_save_load_submit_save or fields.worldedit_gui_save_load_submit_allocate or fields.worldedit_gui_save_load_submit_load then
gui_filename[name] = tostring(fields.worldedit_gui_save_filename)
worldedit.show_page(name, "worldedit_gui_save_load")
if fields.worldedit_gui_save_load_submit_save then
minetest.chatcommands["/save"].func(name, gui_filename[name])
execute_worldedit_command("save", name, gui_filename[name])
elseif fields.worldedit_gui_save_load_submit_allocate then
minetest.chatcommands["/allocate"].func(name, gui_filename[name])
execute_worldedit_command("allocate", name, gui_filename[name])
else --fields.worldedit_gui_save_load_submit_load
minetest.chatcommands["/load"].func(name, gui_filename[name])
execute_worldedit_command("load", name, gui_filename[name])
end
return true
end
@ -736,7 +850,7 @@ worldedit.register_gui_handler("worldedit_gui_save_load", function(name, fields)
end)
worldedit.register_gui_function("worldedit_gui_cube", {
name = "Cuboid", -- technically the command is misnamed, I know...
name = "Cube",
privs = combine_we_privs({"hollowcube", "cube"}),
get_formspec = function(name)
local width, height, length = gui_distance1[name], gui_distance2[name], gui_distance3[name]
@ -744,47 +858,79 @@ worldedit.register_gui_function("worldedit_gui_cube", {
local nodename = worldedit.normalize_nodename(node)
return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_cube") ..
string.format("field[0.5,1.5;4,0.8;worldedit_gui_cube_node;Name;%s]", minetest.formspec_escape(node)) ..
"field_close_on_enter[worldedit_gui_cube_node;false]" ..
"button[4,1.18;1.5,0.8;worldedit_gui_cube_search;Search]" ..
formspec_node("5.5,1.1", nodename) ..
string.format("field[0.5,2.5;1,0.8;worldedit_gui_cube_width;Width;%s]", minetest.formspec_escape(width)) ..
string.format("field[1.5,2.5;1,0.8;worldedit_gui_cube_height;Height;%s]", minetest.formspec_escape(height)) ..
string.format("field[2.5,2.5;1,0.8;worldedit_gui_cube_length;Length;%s]", minetest.formspec_escape(length)) ..
"field_close_on_enter[worldedit_gui_cube_width;false]" ..
"field_close_on_enter[worldedit_gui_cube_height;false]" ..
"field_close_on_enter[worldedit_gui_cube_length;false]" ..
"button_exit[0,3.5;3,0.8;worldedit_gui_cube_submit_hollow;Hollow Cuboid]" ..
"button_exit[3.5,3.5;3,0.8;worldedit_gui_cube_submit_solid;Solid Cuboid]"
end,
})
worldedit.register_gui_handler("worldedit_gui_cube", function(name, fields)
if fields.worldedit_gui_cube_search
or fields.worldedit_gui_cube_submit_hollow or fields.worldedit_gui_cube_submit_solid then
gui_nodename1[name] = tostring(fields.worldedit_gui_cube_node)
gui_distance1[name] = tostring(fields.worldedit_gui_cube_width)
gui_distance2[name] = tostring(fields.worldedit_gui_cube_height)
gui_distance3[name] = tostring(fields.worldedit_gui_cube_length)
local cg = {
worldedit_gui_cube_search = true,
worldedit_gui_cube_node = gui_nodename1,
worldedit_gui_cube_width = gui_distance1,
worldedit_gui_cube_height = gui_distance2,
worldedit_gui_cube_length = gui_distance3,
}
local ret = handle_changes(name, "worldedit_gui_cube", fields, cg)
if fields.worldedit_gui_cube_submit_hollow or fields.worldedit_gui_cube_submit_solid then
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_cube")
local submit = nil
if fields.worldedit_gui_cube_submit_hollow then
submit = "hollowcube"
elseif fields.worldedit_gui_cube_submit_solid then
local submit = "hollowcube"
if fields.worldedit_gui_cube_submit_solid then
submit = "cube"
end
if submit then
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
local args = string.format("%s %s %s %s", gui_distance1[name], gui_distance2[name], gui_distance3[name], n)
minetest.chatcommands["/"..submit].func(name, args)
end
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
local args = string.format("%s %s %s %s", gui_distance1[name], gui_distance2[name], gui_distance3[name], n)
execute_worldedit_command(submit, name, args)
end
return true
end
return false
return ret
end)
worldedit.register_gui_function("worldedit_gui_clearobjects", {
name = "Clear Objects",
privs = we_privs("clearobjects"),
on_select = function(name)
minetest.chatcommands["/clearobjects"].func(name, "")
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)

View File

@ -4,10 +4,10 @@ worldedit = worldedit or {}
Example:
worldedit.register_gui_function("worldedit_gui_hollow_cylinder", {
name = "Make Hollow Cylinder",
privs = {worldedit=true},
get_formspec = function(name) return "some formspec here" end,
on_select = function(name) print(name .. " clicked the button!") end,
name = "Make Hollow Cylinder",
privs = {worldedit=true},
get_formspec = function(name) return "some formspec here" end,
on_select = function(name) print(name .. " clicked the button!") end,
})
Use `nil` for the `options` parameter to unregister the function associated with the given identifier.
@ -35,14 +35,14 @@ end
Example:
worldedit.register_gui_handler("worldedit_gui_hollow_cylinder", function(name, fields)
print(minetest.serialize(fields))
print(minetest.serialize(fields))
end)
]]
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()
@ -70,7 +70,7 @@ local get_formspec = function(name, identifier)
end
--implement worldedit.show_page(name, page) in different ways depending on the available APIs
if rawget(_G, "unified_inventory") then --unified inventory installed
if minetest.global_exists("unified_inventory") then -- unified inventory installed
local old_func = worldedit.register_gui_function
worldedit.register_gui_function = function(identifier, options)
old_func(identifier, options)
@ -80,6 +80,10 @@ if rawget(_G, "unified_inventory") then --unified inventory installed
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,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
@ -103,7 +107,7 @@ if rawget(_G, "unified_inventory") then --unified inventory installed
player:set_inventory_formspec(get_formspec(name, page))
end
end
elseif rawget(_G, "inventory_plus") then --inventory++ installed
elseif minetest.global_exists("inventory_plus") then -- inventory++ installed
minetest.register_on_joinplayer(function(player)
local can_worldedit = minetest.check_player_privs(player:get_player_name(), {worldedit=true})
if can_worldedit then
@ -134,32 +138,74 @@ elseif rawget(_G, "inventory_plus") then --inventory++ installed
inventory_plus.set_inventory_formspec(player, get_formspec(name, page))
end
end
elseif rawget(_G, "sfinv") then --sfinv installed (part of minetest_game since 0.4.15)
elseif minetest.global_exists("smart_inventory") then -- smart_inventory installed
-- redefinition: Update the code element on inventory page to show the we-page
function worldedit.show_page(name, page)
local state = smart_inventory.get_page_state("worldedit_gui", name)
if state then
state:get("code"):set_we_formspec(page)
state.location.rootState:show() -- update inventory page
end
end
-- smart_inventory page callback. Contains just a "custom code" element
local function smart_worldedit_gui_callback(state)
local codebox = state:element("code", { name = "code", code = "" })
function codebox:set_we_formspec(we_page)
local new_formspec = get_formspec(state.location.rootState.location.player, we_page)
new_formspec = new_formspec:gsub('button_exit','button') --no inventory closing
self.data.code = "container[1,1]".. new_formspec .. "container_end[]"
end
codebox:set_we_formspec("worldedit_gui")
-- process input (the back button)
state:onInput(function(state, fields, player)
if fields.worldedit_gui then --main page
state:get("code"):set_we_formspec("worldedit_gui")
elseif fields.worldedit_gui_exit then --return to original page
state:get("code"):set_we_formspec("worldedit_gui")
state.location.parentState:get("crafting_button"):submit() -- switch to the crafting tab
end
end)
end
-- all handler should return false to force inventory UI update
local orig_register_gui_handler = worldedit.register_gui_handler
worldedit.register_gui_handler = function(identifier, handler)
local wrapper = function(...)
handler(...)
return false
end
orig_register_gui_handler(identifier, wrapper)
end
-- register the inventory button
smart_inventory.register_page({
name = "worldedit_gui",
tooltip = "Edit your World!",
icon = "inventory_plus_worldedit_gui.png",
smartfs_callback = smart_worldedit_gui_callback,
sequence = 99
})
elseif minetest.global_exists("sfinv") then -- sfinv installed
assert(sfinv.enabled)
local orig_get = sfinv.pages["sfinv:crafting"].get
sfinv.override_page("sfinv:crafting", {
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
})
--compatibility with pre-0.4.16 sfinv
local set_page = sfinv.set_page or function(player, name)
--assumptions: src pg has no leave callback, dst pg has no enter callback
local ctx = {page=name}
sfinv.contexts[player:get_player_name()] = ctx
sfinv.set_player_inventory_formspec(player, ctx)
end
--show the form when the button is pressed and hide it when done
minetest.register_on_player_receive_fields(function(player, formname, fields)
if fields.worldedit_gui then --main page
worldedit.show_page(player:get_player_name(), "worldedit_gui")
return true
elseif fields.worldedit_gui_exit then --return to original page
set_page(player, "sfinv:crafting")
sfinv.set_page(player, "sfinv:crafting")
return true
end
return false
@ -172,12 +218,12 @@ elseif rawget(_G, "sfinv") then --sfinv installed (part of minetest_game since 0
end
end
else
error(
"worldedit_gui requires a supported \"gui management\" mod to be installed\n"..
"To use the GUI you need to either\n"..
"* Use minetest_game (at least 0.4.15) or a subgame with compatible sfinv\n"..
"* Install Unified Inventory or Inventory++\n"..
"If you do not want to use worldedit_gui, disable it by editing world.mt or from the Main Menu"
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"..
"* install Unified Inventory, Inventory++ or Smart Inventory\n"..
"If you don't want to use worldedit_gui, disable it by editing world.mt or from the main menu."
)
end
@ -218,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

4
worldedit_gui/mod.conf Normal file
View File

@ -0,0 +1,4 @@
name = worldedit_gui
description = WorldEdit GUI
depends = worldedit, worldedit_commands
optional_depends = unified_inventory, inventory_plus, sfinv, creative, smart_inventory

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 B

After

Width:  |  Height:  |  Size: 382 B

View File

@ -1 +0,0 @@
worldedit_commands

View File

@ -1,52 +1,52 @@
--provides shorter names for the commands in `worldedit_commands`
--returns true if command could not be aliased, false otherwise
worldedit.alias_chatcommand = function(alias, original_command)
if not minetest.chatcommands[original_command] then
minetest.log("error", "worldedit_shortcommands: original command " .. original_command .. " does not exist")
return true
worldedit.alias_command = function(alias, original)
if not worldedit.registered_commands[original] then
minetest.log("error", "worldedit_shortcommands: original " .. original .. " does not exist")
return
end
if minetest.chatcommands[alias] then
if minetest.chatcommands["/" .. alias] then
minetest.log("error", "worldedit_shortcommands: alias " .. alias .. " already exists")
return true
return
end
minetest.register_chatcommand(alias, minetest.chatcommands[original_command])
return false
minetest.register_chatcommand("/" .. alias, minetest.chatcommands["/" .. original])
worldedit.registered_commands[alias] = worldedit.registered_commands[original]
end
worldedit.alias_chatcommand("/i", "/inspect")
worldedit.alias_chatcommand("/rst", "/reset")
worldedit.alias_chatcommand("/mk", "/mark")
worldedit.alias_chatcommand("/umk", "/unmark")
worldedit.alias_chatcommand("/1", "/pos1")
worldedit.alias_chatcommand("/2", "/pos2")
worldedit.alias_chatcommand("/fp", "/fixedpos")
worldedit.alias_chatcommand("/v", "/volume")
worldedit.alias_chatcommand("/s", "/set")
worldedit.alias_chatcommand("/r", "/replace")
worldedit.alias_chatcommand("/ri", "/replaceinverse")
worldedit.alias_chatcommand("/hcube", "/hollowcube")
worldedit.alias_chatcommand("/hspr", "/hollowsphere")
worldedit.alias_chatcommand("/spr", "/sphere")
worldedit.alias_chatcommand("/hdo", "/hollowdome")
worldedit.alias_chatcommand("/do", "/dome")
worldedit.alias_chatcommand("/hcyl", "/hollowcylinder")
worldedit.alias_chatcommand("/cyl", "/cylinder")
worldedit.alias_chatcommand("/hpyr", "/hollowpyramid")
worldedit.alias_chatcommand("/pyr", "/pyramid")
worldedit.alias_chatcommand("/spl", "/spiral")
worldedit.alias_chatcommand("/m", "/move")
worldedit.alias_chatcommand("/c", "/copy")
worldedit.alias_chatcommand("/stk", "/stack")
worldedit.alias_chatcommand("/sch", "/stretch")
worldedit.alias_chatcommand("/tps", "/transpose")
worldedit.alias_chatcommand("/fl", "/flip")
worldedit.alias_chatcommand("/rot", "/rotate")
worldedit.alias_chatcommand("/ort", "/orient")
worldedit.alias_chatcommand("/hi", "/hide")
worldedit.alias_chatcommand("/sup", "/suppress")
worldedit.alias_chatcommand("/hlt", "/highlight")
worldedit.alias_chatcommand("/rsr", "/restore")
worldedit.alias_chatcommand("/l", "/lua")
worldedit.alias_chatcommand("/lt", "/luatransform")
worldedit.alias_chatcommand("/clro", "/clearobjects")
worldedit.alias_command("i", "inspect")
worldedit.alias_command("rst", "reset")
worldedit.alias_command("mk", "mark")
worldedit.alias_command("umk", "unmark")
worldedit.alias_command("1", "pos1")
worldedit.alias_command("2", "pos2")
worldedit.alias_command("fp", "fixedpos")
worldedit.alias_command("v", "volume")
worldedit.alias_command("s", "set")
worldedit.alias_command("r", "replace")
worldedit.alias_command("ri", "replaceinverse")
worldedit.alias_command("hcube", "hollowcube")
worldedit.alias_command("hspr", "hollowsphere")
worldedit.alias_command("spr", "sphere")
worldedit.alias_command("hdo", "hollowdome")
worldedit.alias_command("do", "dome")
worldedit.alias_command("hcyl", "hollowcylinder")
worldedit.alias_command("cyl", "cylinder")
worldedit.alias_command("hpyr", "hollowpyramid")
worldedit.alias_command("pyr", "pyramid")
worldedit.alias_command("spl", "spiral")
worldedit.alias_command("m", "move")
worldedit.alias_command("c", "copy")
worldedit.alias_command("stk", "stack")
worldedit.alias_command("sch", "stretch")
worldedit.alias_command("tps", "transpose")
worldedit.alias_command("fl", "flip")
worldedit.alias_command("rot", "rotate")
worldedit.alias_command("ort", "orient")
worldedit.alias_command("hi", "hide")
worldedit.alias_command("sup", "suppress")
worldedit.alias_command("hlt", "highlight")
worldedit.alias_command("rsr", "restore")
worldedit.alias_command("l", "lua")
worldedit.alias_command("lt", "luatransform")
worldedit.alias_command("clro", "clearobjects")

View File

@ -0,0 +1,3 @@
name = worldedit_shortcommands
description = WorldEdit command short aliases
depends = worldedit_commands