Compare commits

...

413 Commits
0.5 ... master

Author SHA1 Message Date
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
sfan5 59dc2d34d9 WorldEdit 1.2 2017-09-12 14:56:01 +02:00
sfan5 283f47f10d Fix invalid node error message of //set and //mix 2017-09-12 14:56:01 +02:00
sfan5 35ad2f031d Remove work in progress/unused components
infinity: unused and not public, superseded by minetest.raycast()
limited: wip and disabled entirely anyway
2017-09-12 14:13:18 +02:00
sfan5 d572d769ce Optimize gui icon texture 2017-09-12 13:30:11 +02:00
sfan5 5bbb6b3909 Support //cube in the GUI 2017-09-12 13:25:33 +02:00
sfan5 d6a9b517b5 Remove Lua functions from WorldEdit GUI 2017-09-12 13:04:25 +02:00
Wuzzy 3c61759bae Allow to bulk-set param2 of regions (#144) 2017-09-05 14:40:46 +02:00
sfan5 2f4eb19a3a Fix one-node high cylinders
closes #146
2017-09-03 20:12:38 +02:00
sfan5 2cb3fcde66 Improve node name normalization 2017-08-31 20:38:38 +02:00
sfan5 e680d8087e Fix small logic error in worldedit.cylinder 2017-08-31 20:10:08 +02:00
sfan5 870873ad15 Add //hollowcube and //cube
closes #143
2017-08-31 19:08:10 +02:00
sfan5 e356f4521c WorldEdit GUI support for new //cylinder functionality 2017-08-31 16:46:57 +02:00
Kyle bf8e2a8233 Dual-based cylinder 2017-08-31 16:25:22 +02:00
sfan5 6ceb56c3dc Disable worldedit_gui fallback code (closes #141) 2017-08-20 12:21:41 +02:00
sfan5 1fabe60d77 Make sfinv gui code compatible with sfinv as included in MTG 0.4.15
fixes #142
2017-08-20 12:13:56 +02:00
shivajiva101 acc9188828 Add logging for //lua and //luatransform 2017-05-21 17:31:59 +02:00
sfan5 3240167b13 Forgot this one during refactor (a31f955fb1) 2017-05-13 11:53:52 +02:00
sfan5 66b1fa032d Remove formspec tester from worldedit_gui 2017-05-13 11:52:45 +02:00
sfan5 a31f955fb1 Refactor priv determination in worldedit_gui 2017-05-13 11:51:36 +02:00
sfan5 38e9b42caf Do not allow any worldedit_gui commands without privs 2017-05-13 11:51:36 +02:00
sfan5 ea465f8fe4 Remove useless privilege checks
This is covered by the privs = {...},
2017-05-13 11:51:36 +02:00
tenplus1 0ce45a5900 Fix worldedit_gui_lua privs
This fixes the worldedit_gui_lua feature so that it requires certain privs to run.
2017-05-13 11:17:45 +02:00
sfan5 56f77a2f27 Remove unused toolcaps of the wand tool 2017-03-11 12:50:27 +01:00
sfan5 610cd9981d Use sfinv if possible, fixes #124 with recent minetest versions 2017-03-11 12:47:30 +01:00
sfan5 c0f3bb6958 GUI: Use nicer picture to indicate unknown nodes (-> no search results) 2017-03-11 12:24:47 +01:00
sfan5 e49f717000 GUI: Always pass normalized node names to commands 2017-03-11 12:03:06 +01:00
MT-Modder 6e7b9a60be Update wand definition (#129)
Fixes "maxwear" deprecated warning
2017-02-11 00:04:04 +01:00
sfan5 bcac45a476 Fix "safe region" functionality errornously rejecting pos1-only commands (e.g. //sphere) 2017-01-31 19:52:54 +01:00
sfan5 426f3b949f Show light level a node receives with //inspect, fixes #128 2017-01-26 22:27:30 +01:00
sfan5 78e4ba828e Show light level with //inspect 2017-01-03 17:11:38 +01:00
sfan5 92fe95fab7 Make //fixlight work again
The dig-air-nodes method seems to have stopped working a few Minetest version ago
2017-01-02 19:03:21 +01:00
sfan5 3a7fb5bf1e Add //help command (fixes #123) 2017-01-01 22:27:22 +01:00
sfan5 aa0e46d4e2 Handle failure of minetest.add_entity()
(see minetest/minetest#4923)
2016-12-21 14:26:24 +01:00
sfan5 83288c969e Don't mark or load areas into memory when they are over a certain size, fixes #97 2016-12-10 22:02:40 +01:00
sfan5 f9311b2b15 Move wand.lua to worldedit_commands 2016-12-10 21:54:51 +01:00
sfan5 15f0cea72e Disable wand tool on entities 2016-12-10 21:39:44 +01:00
sfan5 6e2e2385e9 Merge remote-tracking branch 'tmp/hollowpyramid' 2016-10-13 17:51:01 +02:00
sfan5 152707a322 Fix 'Run Lua' button in WorldEdit GUI (fixes #121) 2016-10-12 20:10:36 +02:00
sfan5 d040d324e8 Fix //spiral requiring a selected region despite using only pos1 2016-10-12 19:45:38 +02:00
sfan5 df6b54d2f5 Fix blowing up TNT near worldedit markers 2016-10-12 19:38:41 +02:00
sfan5 5abe1cee3d Fix exiting mtschemprob entry dialog with ESC 2016-08-05 16:41:37 +02:00
sfan5 5afea424ba Move cuboidapi.lua to correct place 2016-07-04 19:57:48 +02:00
sfan5 2aed498849 Add completion chat message to //expand and //contract 2016-07-04 19:53:12 +02:00
sfan5 0b68b2aec6 Fix //shift with absolute axis (x/y/z) 2016-07-04 19:51:28 +02:00
sfan5 f72abdb766 Merge remote-tracking branch 'tmp/cuboidmanip' 2016-07-04 19:49:23 +02:00
Daniel Sosa e18525d8c9 Modify outset and inset to accept both hv and vh combinations. Update documentation accordingly. 2016-07-03 21:44:02 -05:00
Daniel Sosa 78890dde07 Clean some documentation issues 2016-07-03 21:44:02 -05:00
Daniel Sosa b922097e7a Update command documentation 2016-07-03 21:44:02 -05:00
Daniel Sosa 48f3f59fc3 Implement some safety mechanisms 2016-07-03 21:44:02 -05:00
Daniel Sosa f8f1f3b9cc Reimplement /expand and /contract to conform to WE standards 2016-07-03 21:44:02 -05:00
Daniel Sosa d5e004be67 Fix /outset and /inset to conform to WE standards 2016-07-03 21:44:02 -05:00
Daniel Sosa fad021d0ba Remove some debug code 2016-07-03 21:44:02 -05:00
Daniel Sosa f1fe0c6bba Fix /shift command to conform to worldedit command standard 2016-07-03 21:44:02 -05:00
Daniel Sosa 40cee99700 Fix a crash that happened when trying to shift the cuboid using relative direction while looking straight up or down 2016-07-03 21:44:02 -05:00
Daniel Sosa 04d20de4cd Fix several typos 2016-07-03 21:44:02 -05:00
Daniel Sosa 859c6bd12a Implement /expand and /contract 2016-07-03 21:44:02 -05:00
Daniel Sosa 240380ff16 Implement /inset and /shift 2016-07-03 21:44:02 -05:00
Daniel Sosa 8d213d32a0 refactor code for mantainability and reusability. Start of chat command refactor. Implement /outset. 2016-07-03 21:44:02 -05:00
Daniel Sosa ae29a9f064 Reimplement inset/outset to make use of new marker functions 2016-07-03 21:44:02 -05:00
Daniel Sosa 6cf8b92434 Reimplement /shift to make use of new marker functions 2016-07-03 21:44:02 -05:00
Daniel Sosa 0b97a7c740 Finish /expand implementation. Add /contract command. 2016-07-03 21:44:02 -05:00
Daniel Sosa 900b2f25aa Fix faulty regex in expand. Add relative direction decoding. 2016-07-03 21:44:02 -05:00
Daniel Sosa f49663902c Major progress and simplification in /expand function 2016-07-03 21:44:02 -05:00
Daniel Sosa d475682d8f Add common functions to move and update the markers 2016-07-03 21:44:02 -05:00
Daniel Sosa 9e087ff995 Add outset, inset, shift and initial draft of expand 2016-07-03 21:44:02 -05:00
tenplus1 b23e92921c fix gui bug with back button (#116)
Fixed the Worldedit GUI problem where BACK button wouldn't return back to inventory plus menu.
2016-06-20 18:05:47 +02:00
sfan5 61ab240cea Add //drain 2016-03-23 22:09:55 +01:00
sfan5 b06e004d80 Merge pull request #108 from kilbith/master
Fix unability to browse inv. items on creative inventory
2016-02-22 15:54:25 +01:00
Jean-Patrick Guerrero 6dddc93ed7 Fix unability to browse inv. items on creative inventory 2016-02-22 12:29:06 +01:00
sfan5 b99a51f468 Merge pull request #107 from kilbith/master
Re-adapt WorldEdit's GUI button position to new creative inventory
2016-02-22 10:33:04 +01:00
Jean-Patrick Guerrero 91c5053e67 Re-adapt WorldEdit's GUI button position to creative inventory 2016-02-22 10:29:53 +01:00
ShadowNinja f2f714c19e Fix crash when loading version 3 schematics 2016-01-23 16:57:27 -05:00
Joseph Pickard 47712844a3 Added a WorldEdit wand item that can be used to select areas with worldedit. 2016-01-22 22:24:17 +01:00
sfan5 2bd4d6fa8f Merge pull request #101 from kilbith/master
Adapt WorldEdit GUI button to the new creative inventory
2016-01-17 15:29:06 +01:00
kilbith e9e8de385c Adapt WorldEdit GUI button to the new creative inventory 2016-01-17 15:26:47 +01:00
Sebastien Ponce 2e2fcfdfa2 Fixed Issue #83 : upsidedown pyramid not working well 2016-01-05 14:11:04 +01:00
sfan5 e0a2661700 Fix //stack2 not working (closes #94) 2016-01-05 13:57:48 +01:00
Rui 7a19c5303b Fix undeclared global variable 2016-01-05 13:33:12 +01:00
sfan5 35b68c481b Fixed dropdown menus in the WorldEdit GUI (closes #78, thanks to @sponce) 2016-01-05 13:14:47 +01:00
sfan5 51158eca9f Fix Save/Load from WorldEdit GUI, fixes #90 2016-01-05 12:42:01 +01:00
sfan5 3aa315f134 Fix Orient in WorldEdit GUI 2016-01-05 12:24:06 +01:00
Anthony Zhang 3d30588a68 Clarify `//deleteblocks` 2015-12-18 17:33:29 -05:00
Anthony Zhang 48f9c6c23f Fix #93 (thanks @za267!) 2015-11-01 16:50:36 -05:00
Sebastien Ponce 5f9efb1205 Added hollow pyramids 2015-09-13 22:08:04 +02:00
Anthony Zhang fc037e9c82 Fix formspec typo causing crashing upon using `/orient` in the GUI. 2015-06-18 18:14:45 -04:00
ShadowNinja 90d6b3d237 Allow more characters in file names 2015-06-01 17:08:43 -04:00
ShadowNinja 4bd5d56909 Localize mkdir helper 2015-06-01 16:53:03 -04:00
ShadowNinja 163dffccb3 Fix leaking {safe,check}_region 2015-05-16 19:52:48 -04:00
ShadowNinja 6b2fe397e6 Use minetest.mkdir when available 2015-05-16 19:46:33 -04:00
ShadowNinja 5c115e282c Fix existence check trying to open files for writing 2015-05-16 19:27:27 -04:00
sfan5 ab47385f7b Fix crash (worldedit/manipulations.lua:526: attempt to call global 'set_node' (a nil value)) 2015-05-04 18:48:27 +02:00
Anthony Zhang 78915d4a54 Merge pull request #76 from est31/fixserialisation
Load first node too with LuaJIT
2015-03-11 22:08:32 -04:00
est31 09de34aabf Load first node too with LuaJIT
Before, the first node would have had the version number prepended (e.g. "5:"), and therefore wouldn't be loaded.
2015-03-12 02:40:19 +01:00
Anthony Zhang c1bd4986b0 Merge pull request #74 from est31/deleteblocks
Add //deleteblocks command
2015-02-21 00:42:05 -05:00
est31 bea38a116a Add //deleteblocks command 2015-02-20 22:30:34 +01:00
Anthony Zhang 4336e7ca14 Merge pull request #73 from est31/hidebutton
Make inventory++ hide button for players without worldedit priv
2015-02-16 19:58:59 -05:00
est31 eca54f0851 Make inventory++ hide button for players without worldedit priv 2015-02-16 23:05:18 +01:00
Anthony Zhang b0fbcf197f Merge pull request #72 from HybridDog/globalfix
fix an error message
2015-02-14 23:25:20 -05:00
HybridDog b468e24a20 fix an error message 2015-02-14 23:10:21 +01:00
ShadowNinja bb8456b711 Cleanup and fixup
Non-stylistic changes:
  * Add LuaDoc/LDoc support.
  * Fix `clear_objects` area size calculation.
  * Fix `clear_objects` removing player objects.
  * Fix shadowing of marker entity name with player name.
  * Make visualization functions use `swap_node`.
  * Make hidden nodes unwalkable.
  * Prevent `hide` from hiding air.
  * Make deprecated functions log to deprecated stream when called.
  * Fixed `replaceinverse` not using normalized node names.
  * Added .gitignore.
  * Bump version to 1.1.

Stylistic changes:
  * Change `x = function` to `function x`.
  * Change comment format.
  * Make  missing VoxelManip error less obnoxious.
  * Move `sort_pos` into `common.lua`, which is a required module.
  * Remove local copies of `minetest`.
  * Remove `worldedit = worldedit or {}` from modules.
  * Replace replaceinverse with an inverse argument to `replace`.
  * Added `error()`s on on invalid axes.
  * Change `wip` to `TODO`.
  * Rename `clearobjects` to `clear_objects`.
  * Remove `hollow_{sphere,dome,cylinder}` and replace them with a hollow parameter to each function.
  * Add helpers to reduce code duplication.
  * Renamed `Chat Commands.md` to `ChatCommands.md`.
2015-02-01 15:56:16 -05:00
ShadowNinja 1f277147ca Fix Lua function 2014-12-30 00:11:49 -05:00
Anthony Zhang 04fdf92aca Fix crash when loading schematic in a LuaJIT build in recent Minetest versions (thanks LazyJ & VanessaE!). 2014-12-29 18:26:37 -05:00
Anthony Zhang 7f580611f5 Fix some warnings. 2014-12-29 18:16:36 -05:00
Anthony Zhang 4799068551 Merge pull request #68 from KodexKy/stack2_fix
Fix stack2 param
2014-12-29 17:51:51 -05:00
KodexKy 5b03c83f6b Fix stack2 param
Fix stack2 amount count
Fix stack2 sign recognition for y & z
2014-12-16 02:14:22 -04:30
sfan5 e4d007199f Fix bug with loading old .we file formats, thanks to @Sokomine 2014-12-13 21:13:11 +01:00
sfan5 fb49cc4bcc Revert "Use "we-" prefix for commands"
This is currently being discussed, see #62.
This reverts commit 1024eace6f.
2014-10-25 07:53:20 +02:00
Anthony Zhang eb88bf6ade Merge pull request #61 from ShadowNinja/cleanup-serialization
Clean up serialization
2014-10-24 22:03:31 -04:00
Anthony Zhang 02c36c16d5 Merge pull request #62 from ShadowNinja/command-prefix
Use "we-" prefix for commands
2014-10-24 17:04:00 -04:00
ShadowNinja 796aa3870d Clean up serialization
* Adds a header to serialized data (to make version checking sane).
  * Removes the duplicate deserialization for `worldedit.deserialize` and `worldedit.allocate`.
  * Optimizes `worldedit.deserialize` by only deserializing the data once.
  * Makes some fields optional.
  * Cleans up the comments and a little of the code style.
2014-10-24 16:45:10 -04:00
ShadowNinja 1024eace6f Use "we-" prefix for commands 2014-10-24 16:09:51 -04:00
Uberi 70c24c9501 Fix #57 (thanks Zeno-!). 2014-08-15 19:23:23 -04:00
Uberi 18efe50e1e Fix typo (thanks donat-b!). 2014-08-05 18:34:43 -04:00
Anthony Zhang 44c9576f8a Merge pull request #53 from ShadowNinja/fix-lua-runtime-error
Fix runtime error checking with lua* commands
2014-07-24 11:19:13 -04:00
ShadowNinja e383e8ce00 Fix runtime error checking with lua* commands 2014-07-23 18:21:21 -04:00
Uberi 82ef580fae Add `sort_pos` to make code module independent. 2014-07-22 18:42:15 -04:00
Uberi 38fbc0a046 Fix //mix and improve normalize_nodename. 2014-07-18 16:09:57 -04:00
Uberi 4b178bf709 Rename the randomized //set to //mix, style update, document changes. 2014-07-18 15:51:09 -04:00
Uberi d8aa7e72a7 Oops, fix //set. 2014-07-14 00:11:33 -04:00
Uberi 58d7a7134e Coding style updates. 2014-07-12 16:35:59 -04:00
Anthony Zhang 420655bd94 Merge pull request #51 from cyisfor/master
I think that's the optimization you mentioned?
2014-07-12 16:31:27 -04:00
sfan5 ea84eee0e5 Fix typo in README 2014-07-12 07:42:38 +02:00
Uberi 9ec1a799d8 Fix worldedit_gui not working in Minetest 0.4.10. 2014-07-10 20:17:12 -04:00
Cy f5b67c5bc2 CPS-ifying stack
Continuation Passing Style lets me use minetest.after, so the server
gets a chance to not hang in between every stack iteration. Could even
set minetest.after(1000,nextone) if you want to see it extend once every
second.
2014-07-09 23:50:41 -07:00
Cy 6084db9335 Slight optimization to //set
Just noticed I box the one type version in a list, to avoid testing
whether it's the one type version, but have to test for that to decide
whether to box it or not. Should shave like a whole 3ms from each //set
command.
2014-07-09 23:34:27 -07:00
Uberi b70fd16da4 Oops, fix `//set`. 2014-07-07 14:06:33 -04:00
Uberi 9616c7d944 Update `stack2` API documentation. 2014-07-06 19:59:56 -04:00
Uberi 175ac211ca Documentation for `//stack2`, code style fixes, add author section to README. 2014-07-06 19:42:02 -04:00
Anthony Zhang b32aadd7fa Merge pull request #47 from cyisfor/master
Stacking in any direction
2014-07-06 19:11:23 -04:00
Cy c22b556511 Improved stacking
This stack / copy uses a direction vector, so it's not limited to only
along the X/Y/Z axis, and can go diagonally. This enables things like
building staircases.
2014-06-30 16:14:14 -07:00
Cy 174416b010 Randomized set
Can /set node node2 node3 and it will randomly choose between those
three.
2014-06-30 16:13:44 -07:00
Uberi 2c4a791805 New inventory icon for GUI (thanks VanessaE!). 2014-05-16 16:59:56 -04:00
Uberi 4660927ca0 Fix safe region functionality (thanks ChaosWormz). 2014-04-19 23:33:20 -04:00
Uberi 2784a25561 Fix region checking (thanks PenguinDad). 2014-03-15 14:28:47 -04:00
Uberi b29523db4d Implement full size checking for every possible command. 2014-03-12 16:11:00 -04:00
Uberi 0dddffa544 Add warning for really huge regions. 2014-03-10 17:58:15 -04:00
Anthony b8b8db42bf Merge pull request #36 from spillz/master
Fix incorrect fixedpos var names in the GUI form
2014-02-24 09:46:26 -05:00
spillz 18417ff4fd Fix incorrect fixedpos var names in the GUI form
worldedit_gui_fixed_* -> worldedit_gui_fixedpos_*

Makes setting a fixed position in the GUI work correctly
2014-02-23 20:31:09 -05:00
Uberi 84f0e23d6c Add `worldedit_shortcommands`, which adds aliases to commonly used chat commands that allow users to type in long command names faster. Idea is courtesy of @electricface.
For example, `//move ? 5` can now be written as `//m ? 5`.
2014-02-23 14:16:22 -05:00
Uberi be913e6d8b Fix chat commands display. 2014-01-28 14:14:36 -05:00
Anthony 82f39460cc Merge pull request #33 from electricface/master
//fixedpos No commas between the numbers
2014-01-27 22:41:26 -08:00
electricface 6b50025ccc //fixedpos No commas between the numbers 2014-01-28 12:23:47 +08:00
Anthony 829f9eef73 Merge pull request #32 from ShadowNinja/admin-only-lua
Make /lua and /luatransform administrator-only
2014-01-21 18:42:16 -08:00
ShadowNinja f8d3614a32 Make /lua and /luatransform administrator-only 2014-01-21 11:14:24 -05:00
Uberi 86d35e97bf Fix for player signing off before WorldEdit GUI has finished loading. 2014-01-19 00:12:34 -05:00
Anthony 277b57a82a Merge pull request #28 from cheapie/master
Added compatibility with my version of creative_inventory.
2014-01-02 21:58:30 -08:00
Carter Kolwey d2635544c6 Added compatibility with my version of creative_inventory. 2014-01-02 22:17:25 -06:00
Uberi 2ac049c823 Oops, broke the back button with that last commit. 2014-01-02 18:17:28 -05:00
Uberi d73b93d8dd Fix the creative inventory being borked if neither Unified Inventory or Inventory++ is installed (thanks cheapie!). 2014-01-02 18:03:52 -05:00
Uberi 8c0b61194c The WorldEdit GUI now has no hard dependencies. 2013-12-30 14:27:47 -05:00
Uberi 2e41f0076d Mark the region with an entity cube. 2013-12-24 14:07:42 -05:00
Uberi 2fcea5a3c0 Add a screenshot to the README. 2013-12-22 13:51:50 -05:00
Uberi df6ab06e74 Update worldedit_gui to work with latest unified_inventory (thanks LazyJ!). 2013-12-22 13:48:56 -05:00
Anthony 4e78ced693 Merge pull request #26 from kaeza/wut
Fix crashes in GUI button.
2013-12-22 09:52:17 -08:00
kaeza ad299cacc5 Fix crashes in GUI button. 2013-12-21 03:06:32 -02:00
Uberi 06cb2934b1 Full unified_inventory and inventory_plus support! 2013-12-20 19:50:13 -05:00
Uberi c48cf3c051 All commands in WorldEdit are now accessible through WorldEdit GUI, except the MT Schematics stuff. 2013-12-20 18:42:47 -05:00
Uberi 674d6473e4 Improve node inspector to show player axis, replace //scale with //stretch, which supports per-axis stretching (full backwards compatibility retained), and secure schematic file loading functions. 2013-12-20 18:41:13 -05:00
Uberi 3767ea84d2 Include WorldEdit GUI in the documentation, improve privilege handling, simplify main dialog. 2013-12-18 15:56:50 -05:00
Uberi 36424e3dec Improve worldedit.spiral and the WorldEdit GUI, 2013-12-16 18:55:56 -05:00
Uberi 3cffae4948 Implement copy/move in the WorldEdit GUI. 2013-12-15 16:56:37 -05:00
Uberi a4881225a4 Merge fixedpos and position dialogs in worldedit_gui, add cylinder dialog. 2013-12-15 16:33:39 -05:00
Uberi 0797c47afb Fix worldedit.hollow_cylinder. 2013-12-15 16:33:31 -05:00
Uberi 89d4548595 More improvements to worldedit_gui 2013-12-12 16:28:00 -05:00
Uberi 6c03ab9c4a Refine GUI. 2013-12-12 15:34:05 -05:00
Uberi dbc2348014 Better installation instructions. 2013-12-11 15:58:58 -05:00
Uberi 3fa9dfd19c Improve worldedit_gui. 2013-12-10 17:07:10 -05:00
Uberi d4b39cba9b WorldEdit GUI is now live! Currently under construction. 2013-12-10 01:58:53 -05:00
Uberi ac7430e02f Version information is now available via the API. 2013-12-10 01:47:32 -05:00
Sfan5 b3ba6f2433 Revert "Change compatibility notices." because we have 0.4.8 now
This reverts commit d5ec1bcefc.
2013-11-24 14:18:04 +01:00
Uberi d5ec1bcefc Change compatibility notices. 2013-10-01 16:57:55 -04:00
Uberi 203c3da2af Add //about. 2013-08-28 20:28:49 -04:00
Uberi 272541c9da Changes to support the latest schematic probability specification. 2013-08-16 16:57:35 -04:00
Uberi 9ab52df56a Add worldedit_limited. 2013-08-16 16:56:38 -04:00
Anthony Zhang 4c43bf3600 Add initial code for worldedit_infinity. 2013-08-06 16:29:00 -04:00
Anthony Zhang 03327a767d Add version checker since so many people are confused about which version to use. 2013-08-06 16:28:05 -04:00
Anthony Zhang 8bd5db51b2 Final changes and version number bump. 2013-08-01 23:53:38 -04:00
Anthony Zhang 416ffb3709 Fix //pyramid examples. 2013-07-31 22:29:01 -04:00
Anthony Zhang 040282bbe6 New module loader properly halts when load-time error occurs in module. 2013-07-31 22:15:52 -04:00
Anthony Zhang b0bf52e9b6 Rewrite spirals from scratch and fix upside-down pyramids. Use voxelmanip for markers to ensure area is emerged. 2013-07-31 22:15:08 -04:00
Anthony Zhang 3c51ec8c4a //pyramid works along any axis and backwards. Working //clearobjects and //cylinder command, begin implementing super marker. Remove EnvRef usages and the block queue (the block queue does not work with VoxelManips). More block emergers. 2013-07-31 00:02:37 -04:00
Anthony Zhang 49b683f27f Support negative values to make upside-down domes and pyramids (slight changes to worldedit.pyramid interface for coming changes). Add experimental //clearobjects, make node inspector work per-player. 2013-07-29 12:43:24 -04:00
Anthony Zhang c1f3cfc1e4 Fix bugs in visualization API and make it ore robust. Fix bugs in //fixedpos, //suppress, and //highlight. 2013-07-28 18:15:46 -04:00
Anthony Zhang b252df2166 Slight optimizations, and make //pyramid work properly again. 2013-07-28 17:08:12 -04:00
Anthony Zhang 7e1be63344 Add //fixedpos command and document it. 2013-07-28 17:07:47 -04:00
Anthony Zhang faa4a7b065 More speedups for //hollowsphere, //sphere, //hollowdome, //dome, //cylinder, better module loader, fix message in //volume. 2013-07-27 21:34:42 -04:00
Anthony Zhang 8b9054a1cc Add tutorial for new users, and a note about compatibility. 2013-07-25 16:09:00 -04:00
Anthony Zhang dfcf02bbc2 Add a few notes on air. 2013-07-22 11:04:03 -04:00
Anthony Zhang 0fddd17f23 Add the node inspector, a tool for finding information about nodes. Configurable via the //inspect command. 2013-07-21 17:16:29 -04:00
Anthony Zhang e550604747 Add a section about the different types of schematics. 2013-07-21 17:15:04 -04:00
Anthony Zhang 8ebf9d3c2a Super duper VoxelManipulator speedups to nearly every API function, and plus support for unloaded areas. Still in progress. Also, fix //allocate for very large schematics. 2013-07-21 16:54:25 -04:00
Anthony Zhang ac5e801834 Even bigger speed gains by using LuaVoxelManipulator in a few choice places! Faster //set, //cylinder, etc., but plenty of room for improvements still. 2013-07-12 14:22:30 -04:00
Anthony Zhang b6bc841c39 Fix volume of spheres, domes, and their hollow variants. 2013-06-24 18:22:01 -04:00
Anthony Zhang 48009b6c62 Support aliases in node name resolution. For example, `//set mapgen_dirt`. Technique outlined by kharl in IRC. 2013-06-24 17:13:10 -04:00
Anthony Zhang 9db6192eba Avoid using slower and deprecated EnvRef, fix schematic data MapNodes, huge speed boost to worldedit.hollow_cylinder, fix some bugs. 2013-06-23 12:46:59 -04:00
Anthony Zhang 1e5f623cbb Super-speed a few primitives and visualization functions, including hollowsphere, sphere, hollowdome, dome, and suppress. 2013-06-22 23:08:51 -04:00
Anthony Zhang 58970e7fab Clarify the documentation regarding the the positioning of WorldEdit primitives relative to position 1. 2013-06-22 23:05:34 -04:00
Anthony Zhang 7070f81c59 Significant but highly experimental speed-ups for worldedit.set, replace, and scale, plus preparation for more. 2013-06-22 20:59:23 -04:00
Sfan5 a192d51a58 Add initial support for Minetest schematic API
Thanks to kwolekr for the code
2013-06-22 08:25:59 +02:00
Anthony Zhang d4187866db Add `//scale <factor>` command (suggested by Jordach), fix transposition description in docs. 2013-06-18 15:05:49 -04:00
Anthony Zhang 74018dab99 Support descriptions and partial node names in chat commands (e.g., `//set blue lightstone`, `//replace piston_normal_off bronze block`). 2013-06-12 19:59:13 -04:00
Anthony Zhang f1cfc47189 Fix //rotate for real this time. 2013-06-02 17:50:14 -04:00
Anthony Zhang 0664cde287 Fix //rotate, improve queue (order now preserved), typos fixed (quene -> queue). 2013-05-30 20:38:56 -04:00
Anthony Zhang f952ee4740 Block queue doesn't lag anymore on slow operations like digging and placing nodes, better and more efficient //fixlight. 2013-05-30 19:13:21 -04:00
Sfan5 a1dcc43210 Fix //move again 2013-05-20 20:48:12 +02:00
Anthony Zhang d7ae19b7b8 Fix the //move command (WorldEdit API is unaffected). 2013-05-18 19:00:12 -04:00
Anthony Zhang 52c1379bce Make chat messages consistent. 2013-05-15 17:52:12 -04:00
Anthony Zhang d52f3d649e Add //unmark chat command, fix position setting not being reset by //reset. 2013-05-15 17:03:26 -04:00
Anthony Zhang cedecac3f8 Merge remote-tracking branch 'refs/remotes/origin/master' 2013-05-02 18:20:59 -04:00
Anthony Zhang 18bc4e6815 Fix deserialization for real this time. 2013-04-29 19:52:00 -04:00
sfan5 9e1c2614d3 Update Copyright Year 2013-04-29 14:34:55 +03:00
Anthony Zhang 98c5bc5c5f Fix loading of schematics containing chests that had empty slots. 2013-04-28 19:01:24 -04:00
Sfan5 522edf1cd4 WorldEdit 0.6 2013-04-28 20:27:25 +02:00
Sfan5 5e5c1dc6c3 Add Block Queue 2013-04-28 19:08:09 +02:00
Anthony Zhang 71b6004b92 New commands //hollowdome and //dome, as well as new API functions worldedit.dome and worldedit.hollow_dome. Oh, and spheres generate faster too. 2013-04-27 18:28:20 -04:00
Anthony Zhang 20722389bd Avoid prepending "Server -!- " to WorldEdit messages. 2013-04-27 16:42:51 -04:00
Anthony Zhang f5145d6ba1 Horrible, vile, disgusting hack. But it works. 2013-04-20 20:28:21 -04:00
Anthony Zhang 0317deb101 Metadata fix for serialization. 2013-03-27 18:07:52 -04:00
Anthony Zhang 65c218c3e2 Dynamic module loading - you can now delete any file in the worldedit mod except init.lua, and the relevant functionality will simply not be included. 2013-03-20 17:31:00 -04:00
Anthony Zhang d1828affff Oops, forgot to load the compatibility module. 2013-03-20 17:18:21 -04:00
Anthony Zhang 9209d81d20 //metasave and //metaload are now superceded by //save and //load's new functionality. worldedit.deserialize now supports every version of the WorldEdit format that has ever existed, and the new worldedit.valueversion uses file characteristics to determine which type of file format a given file uses. The new WorldEdit file format is the same as the one used by MineTest for serializing data, and is capable of storing arbitrary data, as well as leaving fields for future improvements. In other words, this is the last forward-compatibility breaking change that will be made to the file format. 2013-03-20 17:12:48 -04:00
sfan5 34c4475d06 Fix styling issues in Chat Commands.md 2013-02-23 09:35:16 +01:00
Anthony Zhang 7eb102181b Fixed bug where unmodified axis used pos1 coords instead of pos2 coords. 2013-02-17 14:11:55 -05:00
Anthony Zhang 4a98773025 Use rounding rather than flooring for more intuitive player position setting. 2013-02-16 21:14:44 -05:00
Anthony Zhang cfaed1781a Require `server` priv for Lua commands. 2013-01-14 16:14:54 -05:00
Anthony Zhang 7cf84045d4 Replace //homogenize with //replaceinverse (//homogenize x is equivalent to //replaceinverse air x), add documentation for it. 2013-01-12 18:29:57 -05:00
Anthony Zhang e2f1c4ef17 Add //homogenize, //lua, and //luatransform commands, as well as their documentation. 2013-01-12 18:20:41 -05:00
Anthony Zhang c27ab877f1 New command: //orient, that rotates oriented nodes such as furnaces around the Y-axis by a specified angle. 2013-01-12 16:46:40 -05:00
Anthony Zhang 7cb2df24b8 Change all references of //dig to //fixlight, document the changes, fix a small typo. 2013-01-12 16:02:23 -05:00
sfan5 6fb039fb9d Finally fix anything 2013-01-12 08:24:04 +01:00
sfan5 0003770ffb Fix my Code again (I've use Python too much) 2013-01-12 08:19:24 +01:00
sfan5 9f35891996 Fix Code again 2013-01-12 08:17:14 +01:00
sfan5 6f3127f32e Fix Code 2013-01-12 08:14:20 +01:00
sfan5 5f1fb56b8b Rename //dig to //fixlight 2013-01-12 08:10:19 +01:00
sfan5 89e18d6108 Only dig air Nodes 2013-01-12 07:59:41 +01:00
Anthony Zhang e5331d39ae Many thanks to Smitje for providing a working example of roations.
Changes: fixed rotation and transposition of non-sqaure regions, makers are now moved to the new region boundaries after rotation/transposition, small consistency fixes, finally fix the /hide command.
2012-12-12 17:17:56 -05:00
Anthony Zhang f087c3ef71 Fix the missing command //suppress <node>. 2012-11-27 19:29:10 -05:00
Anthony Zhang 426dfb0bc3 Quick and dirty fix for metasave not saving ItemStack instances in node metadata inventories. 2012-10-23 23:21:02 -04:00
Anthony Zhang fdca506505 Fix //load and //allocate for old WorldEdit files. 2012-10-22 23:17:37 -04:00
Anthony Zhang aa4a70d237 Rename //find to //highlight and worldedit.find to worldedit.highlight, add //allocate and worldedit.allocate as well as related documentation. Improve documentation by fixing a few mistakes and adding return signatures. 2012-10-21 17:49:43 -04:00
Anthony Zhang 5f598db3e6 Implement and document the new //p set1 and //p set2 functionality. 2012-10-18 21:28:58 -04:00
Anthony Zhang 4f0ffe731f Merge remote-tracking branch 'origin/master' 2012-10-18 21:28:45 -04:00
Anthony Zhang e9cdf616ec Test 2012-10-18 21:28:33 -04:00
Anthony 5585b7d242 Merge pull request #19 from khonkhortisan/master
Hollow Sphere Returning Count
2012-10-17 17:32:56 -07:00
Kyle a76ee00391 worldedit.hollow_sphere returns non-nil count 2012-10-17 16:18:19 -07:00
Anthony Zhang 1c31bd7f0d Separate components into separate mods, add visualization API with hide(), suppress(), find(), and restore() for nondestructive node visualization. Corresponding chat commands are //hide, //suppress, //find, and //restore. Commands and functions documented. 2012-10-13 21:45:50 -04:00
cornernote 1b13538353 dofile table_save so metasave and metaload work 2012-10-04 13:22:18 +09:30
Anthony Zhang 12dfbd198d Use modpack for easier installation (idea is taken from cornernote's mod format), fix some grammar in API reference, move chat command reference and API reference to separate files. 2012-10-01 16:20:20 -04:00
Brett O'Donnell a82ab9176f Fix worldedit.spiral and the correspondign chat command, //spiral. 2012-09-26 18:36:11 -04:00
Anthony Zhang 382c57d008 Replace worldedit.spiral with worldedit.pyramid, as well as related chat commands. 2012-09-26 18:02:42 -04:00
Anthony Zhang def676cd2d Add worldedit.hollow_sphere, worldedit.sphere, //hollowsphere, and //sphere, improve cylinder docs slightly. 2012-09-25 18:42:10 -04:00
Anthony Zhang d89fded397 Fix negative cylinder lengths (thanks @cornernote). 2012-09-25 17:04:24 -04:00
Anthony Zhang 724cd6ccb6 Add cornernote to authors. 2012-09-20 16:25:29 -04:00
Anthony f42d2358fd Merge pull request #12 from cornernote/master
added //metasave and //metaload (clean pull)
2012-09-20 13:13:09 -07:00
Brett O'Donnell 474130d188 added table_save to load/save metadata 2012-09-20 14:00:20 +09:30
cornernote a95164b63b added metasave/metaload to readme 2012-09-20 13:58:00 +09:30
cornernote 0f9f247cc1 added metasave/metaload to readme 2012-09-20 13:57:01 +09:30
cornernote 7ce20dbfce added metasave/metaload to chat commands 2012-09-20 13:56:14 +09:30
cornernote c8cfd524dd added metasave/metaload to api 2012-09-20 13:55:23 +09:30
Anthony Zhang 6f96131382 Normalization. 2012-09-13 23:13:54 -04:00
Anthony 8102eb982e Merge pull request #11 from khonkhortisan/master
Allow multiple //moves
2012-09-10 17:06:34 -07:00
Kyle 93100a20be document the //move change 2012-09-10 10:09:03 -07:00
Kyle 17c1e3887c allow multiple //moves 2012-09-09 21:44:30 -07:00
sfan5 a429e104a2 Fix //replace <something> air 2012-09-08 08:34:21 +02:00
sfan5 ce9ce35a20 Fix //replace air <something> 2012-09-08 08:21:37 +02:00
Anthony a25a2a1fb0 Merge pull request #5 from khonkhortisan/master
Understands meta!
2012-08-30 21:38:00 -07:00
Anthony Zhang 340416c789 Fix player axis detection, make spirals code a bit easier to read. 2012-08-30 16:17:15 -04:00
Kyle 6eb08edd30 Rotate now works with x/y/z/? instead of just y: "//rotate x 90" 2012-08-28 15:57:45 -07:00
Kyle 91c24d3a60 Fix transpose and rotate 2012-08-26 13:14:47 -07:00
Kyle 1a9ce8b9c1 Understands meta! Tested in copy, stack (which copies), move, flip.
Transpose (and therefore rotate) do(es) not appear to be functional, but
meta stuff added anyway.
2012-08-25 17:23:39 -07:00
sfan5 21192ceb57 Remove unused Code 2012-08-19 09:38:16 +02:00
sfan5 e39493e789 Add //spiral and worldedit.spiral API function 2012-08-19 09:36:11 +02:00
Anthony Zhang 5b66b5ec25 Add the ? axis, which represents the axis the player is facing. 2012-08-18 14:43:12 -04:00
Anthony Zhang 66c7f1fb96 Add //hollowcylinder and //cylinder commands, add worldedit.hollow_cylinder and worldedit.cylinder API functions, document both, use better node validity check. 2012-08-18 00:51:20 -04:00
Anthony Zhang 87f23b8647 Fix //copy, //move, and //stack with negative amounts. 2012-08-16 16:21:00 -04:00
Anthony Zhang 8cd32bcc59 Add the //transpose, //flip, and //rotate commands as well as their documentation and related WorldEdit API functions. Fix chat parameter handling using pattern anchors. 2012-07-22 18:12:29 -04:00
Anthony Zhang a4a3613170 Remove WorldEdit markers if they are invalid. 2012-07-22 15:55:49 -04:00
Anthony Zhang 9067f73349 Add //mark command and document it. 2012-07-22 15:22:36 -04:00
Anthony Zhang 5ce4517e66 Use minetest.pos_to_string() and check for nil positions when using //p get (possible crash fixed). 2012-07-22 14:53:20 -04:00
63 changed files with 8295 additions and 806 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 ./

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

@ -0,0 +1,26 @@
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.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 }}"

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*~

15
.luacheckrc Normal file
View File

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

509
ChatCommands.md Normal file
View File

@ -0,0 +1,509 @@
Chat Commands
-------------
For more information, see the [README](README.md).
Many commands also have shorter names that can be typed faster. For example, if we wanted to use `//move ? 5`, we could instead type `//m ? 5`. All shortened names are listed below:
| Short Name | Original Name |
|:-----------|:-------------------|
| `//i` | `//inspect` |
| `//rst` | `//reset` |
| `//mk` | `//mark` |
| `//umk` | `//unmark` |
| `//1` | `//pos1` |
| `//2` | `//pos2` |
| `//fp` | `//fixedpos` |
| `//v` | `//volume` |
| `//s` | `//set` |
| `//r` | `//replace` |
| `//ri` | `//replaceinverse` |
| `//hcube` | `//hollowcube` |
| `//hspr` | `//hollowsphere` |
| `//spr` | `//sphere` |
| `//hdo` | `//hollowdome` |
| `//do` | `//dome` |
| `//hcyl` | `//hollowcylinder` |
| `//cyl` | `//cylinder` |
| `//hpyr` | `//hollowpyramid` |
| `//pyr` | `//pyramid` |
### `//about`
Get information about the WorldEdit mod.
//about
### `//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
### `//reset`
Reset the region so that it is empty.
//reset
### `//mark`
Show markers at the region positions.
//mark
### `//unmark`
Hide markers if currently shown.
//unmark
### `//pos1`
Set WorldEdit region position 1 to the player's location.
//pos1
### `//pos2`
Set WorldEdit region position 2 to the player's location.
//pos2
### `//p set/set1/set2/get`
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/set2 <x> <y> <z>`
Set the WorldEdit region position 1 or 2 to the position (`<x>`, `<y>`, `<z>`).
//fixedpos set1 0 0 0
//fixedpos set1 -30 5 28
//fixedpos set2 1004 -200 432
### `//volume`
Display the volume of the current WorldEdit region.
//volume
### `//deleteblocks`
Delete the MapBlocks (16x16x16 units) that contain the selected region. This means that mapgen will be invoked for that area. As only whole MapBlocks get removed, the deleted area is usually larger than the selected one. Also, mapgen can trigger mechanisms like mud reflow or cavegen, which affects nodes (up to 112 nodes away) outside the MapBlock, so dont use this near buildings. Note that active entities are not part of a MapBlock and do not get deleted.
//deleteblocks
### `//set <node>`
Set the current WorldEdit region to `<node>`.
//set air
//set cactus
//set Blue Lightstone
//set dirt with grass
### `//param2 <param2>`
Set the param2 value of all nodes in the current WorldEdit region to `<param2>`.
//param2 8
### `//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>`
Replace all instances of `<search node>` with `<replace node>` in the current WorldEdit region.
//replace Cobblestone air
//replace lightstone_blue glass
//replace dirt Bronze Block
//replace mesecons:wire_00000000_off flowers:flower_tulip
### `//replaceinverse <search node> <replace node>`
Replace all nodes other than `<search node>` with `<replace node>` in the current WorldEdit region.
//replaceinverse Cobblestone air
//replaceinverse flowers:flower_waterlily glass
//replaceinverse dirt Bronze Block
//replaceinverse mesecons:wire_00000000_off flowers:flower_tulip
### `//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>`.
//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>`.
//cube 6 5 6 Diamond Block
//cube 7 2 1 default:cobble
### `//hollowsphere <radius> <node>`
Add hollow sphere centered at WorldEdit position 1 with radius `<radius>`, composed of `<node>`.
//hollowsphere 5 Diamond Block
//hollowsphere 12 glass
//hollowsphere 17 mesecons:wire_00000000_off
### `//sphere <radius> <node>`
Add sphere centered at WorldEdit position 1 with radius `<radius>`, composed of `<node>`.
//sphere 5 Diamond Block
//sphere 12 glass
//sphere 17 mesecons:wire_00000000_off
### `//hollowdome <radius> <node>`
Add hollow dome centered at WorldEdit position 1 with radius `<radius>`, composed of `<node>`.
//hollowdome 5 Diamond Block
//hollowdome -12 glass
//hollowdome 17 mesecons:wire_00000000_off
### `//dome <radius> <node>`
Add dome centered at WorldEdit position 1 with radius `<radius>`, composed of `<node>`.
//dome 5 Diamond Block
//dome -12 glass
//dome 17 mesecons:wire_00000000_off
### `//hollowcylinder x/y/z/? <length> <radius1> [radius2] <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.
//hollowcylinder x +5 8 Bronze Block
//hollowcylinder y 28 10 glass
//hollowcylinder z -12 3 mesecons:wire_00000000_off
//hollowcylinder ? 2 4 default:stone
//hollowcylinder y 10 10 0 walls:cobble
//hollowcylinder x 6 0 5 Dirt
//hollowcylinder z 20 10 20 default:desert_stone
### `//cylinder x/y/z/? <length> <radius1> [radius2] <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
//cylinder y 28 10 glass
//cylinder z -12 3 mesecons:wire_00000000_off
//cylinder ? 2 4 default:stone
//cylinder y 10 10 0 walls:cobble
//cylinder x 6 0 5 Dirt
//cylinder z 20 10 20 default:desert_stone
### `//hollowpyramid x/y/z/? <height> <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>`
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
//pyramid z 2 mesecons:wire_00000000_off
//pyramid ? 12 mesecons:wire_00000000_off
### `//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>`.
//spiral 20 5 3 Diamond Block
//spiral 5 2 1 glass
//spiral 7 1 5 mesecons:wire_00000000_off
### `//copy x/y/z/? <amount>`
Copy the current WorldEdit region along the given axis by `<amount>` nodes.
//copy x 15
//copy y -7
//copy z +4
//copy ? 8
### `//move x/y/z/? <amount>`
Move the current WorldEdit positions and region along the given axis by `<amount>` nodes.
//move x 15
//move y -7
//move z +4
//move ? -1
### `//stack x/y/z/? <count>`
Stack the current WorldEdit region along the given axis `<count>` times.
//stack x 3
//stack y -1
//stack z +5
//stack ? 12
### `//stack2 <count> <x> <y> <z>`
Stack the current WorldEdit region `<count>` times by offset `<x>`, `<y>`, `<z>`.
//stack2 5 3 8 2
//stack2 1 -1 -1 -1
### `//stretch <stretchx> <stretchy> <stretchz>`
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.
//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 given axes.
//transpose x y
//transpose y z
//transpose ? y
### `//flip x/y/z/?`
Flip the current WorldEdit region along the given axis.
//flip x
//flip ?
### `//rotate x/y/z/? <angle>`
Rotate the current WorldEdit positions and region along the given axis by angle `<angle>` (90 degree increment).
//rotate x 90
//rotate y 180
//rotate z 270
//rotate ? -90
### `//orient <angle>`
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle `<angle>` (90 degree increment)
//orient 90
//orient 180
//orient 270
//orient -90
### `//fixlight`
Fixes the lighting in the current WorldEdit region.
//fixlight
### `//drain`
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.
//hide
### `//suppress <node>`
Suppress all `<node>` in the current WorldEdit region non-destructively.
//suppress Diamond Block
//suppress glass
//suppress mesecons:wire_00000000_off
### `//highlight <node>`
Highlight `<node>` in the current WorldEdit region by hiding everything else non-destructively.
//highlight Diamond Block
//highlight glass
//highlight mesecons:wire_00000000_off
### `//restore`
Restores nodes hidden with WorldEdit in the current WorldEdit region.
//restore
### `//save <file>`
Save the current WorldEdit region to "(world folder)/schems/`<file>`.we".
//save some random filename
//save huge_base
### `//allocate <file>`
Set the region defined by nodes from "(world folder)/schems/`<file>`.we" as the current WorldEdit region.
//allocate some random filename
//allocate huge_base
### `//load <file>`
Load nodes from "(world folder)/schems/`<file>`.we" with position 1 of the current WorldEdit region as the origin.
//load some random filename
//load huge_base
### `//lua <code>`
Executes `<code>` as a Lua chunk in the global namespace.
//lua worldedit.pos1["singleplayer"] = {x=0, y=0, z=0}
//lua worldedit.rotate(worldedit.pos1["singleplayer"], worldedit.pos2["singleplayer"], "y", 90)
### `//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.
//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>`
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/`<file>`.mts".
//mtschemcreate some random filename
//mtschemcreate huge_base
### `//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 a_tree 270
//mtschemplace huge_base
### `//mtschemprob start/finish/get`
After using `//mtschemprob start` all nodes punched will bring up a text field where a probablity can be entered.
This mode can be left with `//mtschemprob finish`. `//mtschemprob get` will display the probabilities saved for the nodes.
//mtschemprob get
### `//clearobjects`
Clears all objects within the WorldEdit region.
//clearobjects
### `//shift x/y/z/?/up/down/left/right/front/back [+/-]<amount>`
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]`
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]`
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 [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 using `h`
or vertically in the y axis using `v`.
//outset v 5
### `//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 using `h`
or vertically in the y axis using `v`.
//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

288
README.md
View File

@ -1,198 +1,192 @@
WorldEdit v0.5 for MineTest 0.4
===============================
In-game world editing for [MineTest](http://minetest.net/)! Tons of chat commands to help with building, fixing, and more.
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](http://minetest.net/forum/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).
![Screenshot](http://i.imgur.com/lwhodrv.png)
Installing
----------
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'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 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 Modpack** button.
9. Press the **Save** button. You can now use WorldEdit in that world. Repeat steps 7 to 9 to enable WorldEdit for other worlds too.
If you are having trouble, try asking for help in the [IRC channel](https://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 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`.
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.
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.
Regions
-------
Most WorldEdit commands operate on regions. Regions are a set of two positions that define a 3D cube. They are local to each player and chat commands affect only the region for the player giving the commands.
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.
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.
Interface
---------
WorldEdit is accessed in-game in two main ways.
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.
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.
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.
The chat interface adds many chat commands that perform various WorldEdit powered tasks. It is documented in the [Chat Commands Reference](ChatCommands.md).
To remove the entities, simply punch them. This does not reset the positions themselves.
Compatibility
-------------
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.
Commands
--------
WorldEdit works quite well with other mods and does not have any known mod conflicts.
### //reset
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).
Reset the region so that it is empty.
//reset
### //pos1
Set WorldEdit region position 1 to the player's location.
//pos1
### //pos2
Set WorldEdit region position 2 to the player's location.
//pos2
### //p set/get
Set WorldEdit region by punching two nodes, or display the current WorldEdit region.
//p set
//p get
### //volume
Display the volume of the current WorldEdit region.
//volume
### //set <node>
Set the current WorldEdit region to <node>.
//set dirt
//set default:glass
//set mesecons:mesecon
### //replace <search node> <replace node>
Replace all instances of <search node> with <place node> in the current WorldEdit region.
//replace cobble stone
//replace default:steelblock glass
//replace dirt flowers:flower_waterlily
//replace flowers:flower_rose flowers:flower_tulip
### //copy x/y/z <amount>
Copy the current WorldEdit region along the x/y/z axis by <amount> nodes.
//copy x 15
//copy y -7
//copy z +4
### //move x/y/z <amount>
Move the current WorldEdit region along the x/y/z axis by <amount> nodes.
//move x 15
//move y -7
//move z +4
### //stack x/y/z <count>
Stack the current WorldEdit region along the x/y/z axis <count> times.
//stack x 3
//stack y -1
//stack z +5
### //dig
Dig the current WorldEdit region.
//dig
### //save <file>
Save the current WorldEdit region to "(world folder)/schems/<file>.we".
//save some random filename
//save huge_base
### //load <file>
Load nodes from "(world folder)/schems/<file>.we" with position 1 of the current WorldEdit region as the origin.
//load some random filename
//load huge_base
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.
WorldEdit API
-------------
WorldEdit exposes all significant functionality in a simple interface. Adding WorldEdit to the file "depends.txt" in your mod gives you access to all of the `worldedit` functions. These are useful if you're looking for high-performance node manipulation without all the hassle of writing tons of code.
WorldEdit exposes all significant functionality in a simple Lua interface.
### worldedit.volume(pos1, pos2)
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.
Determines the volume of the region defined by positions `pos1` and `pos2`.
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.
Returns the volume.
This API is documented in the [WorldEdit API Reference](WorldEdit%20API.md).
### worldedit.set(pos1, pos2, nodename)
Axes
----
The coordinate system is the same as that used by Minetest; positive Y is upwards, positive X is rightwards, and positive Z is forwards, if a player is facing North (positive Z axis).
Sets a region defined by positions `pos1` and `pos2` to `nodename`. To clear to region, use "air" as the value of `nodename`.
When an axis is specified in a WorldEdit chat command, it is specified as one of the following values: `x`, `y`, `z`, or `?`.
Returns the number of nodes set.
In the GUI, there is a dropdown menu for this purpose. The "Look direction" option has the same effect as `?` does in chat commands.
### worldedit.replace(pos1, pos2, searchnode, replacenode)
The value `?` represents the axis the player is currently facing. If the player is facing more than one axis, the axis the player face direction is closest to will be used.
Replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`.
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.
Returns the number of nodes replaced.
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.
### worldedit.copy(pos1, pos2, axis, amount)
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.
Copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes.
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".
Returns the number of nodes copied.
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.
### worldedit.move(pos1, pos2, axis, amount)
Regions
-------
Most WorldEdit commands operate on regions. Regions are a set of two positions that define a 3D cuboid. They are local to each player and chat commands affect only the region for the player giving the commands.
Moves the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes.
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.
Returns the number of nodes moved.
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.
### worldedit.stack(pos1, pos2, axis, count)
Markers
-------
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.
duplicates the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") `count` times.
To remove the entities, simply punch them. This does not reset the positions themselves.
Returns the number of nodes stacked.
Schematics
----------
WorldEdit supports two different types of schematics.
### worldedit.dig(pos1, pos2)
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.
Digs a region defined by positions `pos1` and `pos2`.
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.
Returns the number of nodes dug.
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:
### worldedit.serialize(pos1, pos2)
5:return {
{
y = <y-axis coordinate>,
x = <x-axis coordinate>,
z = <z-axis coordinate>,
name = <node name>,
param1 = <param1 value>,
param2 = <param2 value>,
meta = <metadata table>,
},
<...>
}
Converts the region defined by positions `pos1` and `pos2` into a single string.
Returns the serialized data and the number of nodes serialized.
The ordering of the values and minor aspects of the syntax, such as trailing commas or newlines, are not guaranteed to stay the same in future versions.
### worldedit.deserialize(originpos, value)
The WorldEdit Schematic format is accessed via the WorldEdit API, or WorldEdit serialization chat commands such as `//serialize` and `//deserialize`.
Loads the nodes represented by string `value` at position `originpos`.
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`.
Returns the number of nodes deserialized.
Authors
-------
WorldEdit would not be possible without the contributions of many developers and designers. Below, they are listed alphabetically:
### worldedit.deserialize_old(originpos, value)
Loads the nodes represented by string `value` at position `originpos`, using the older table-based WorldEdit format.
This function is deprecated, and should not be used unless there is a need to support legacy WorldEdit save files.
Returns the number of nodes deserialized.
Alexander Weber
ANAND
beyondlimits
Carter Kolwey
cornernote
Cy
Daniel Sosa
electricface
est31
Eugen Wesseloh
h3ndrik
HybridDog
Isidor Zeuner
Jean-Patrick Guerrero
Joseph Pickard
kaeza
kilbith
KodexKy
Kyle
MT-Modder
Niwla23
Panquesito7
Pedro Gimeno
Rui
Sebastien Ponce
sfan5
ShadowNinja
shivajiva101
spillz
Starbeamrainbowlabs
TalkLounge
tenplus1
Uberi/Temperest
Wuzzy
License
-------
Copyright 2012 sfan5 and Anthony Zhang (Temperest)
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.
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.

118
Tutorial.md Normal file
View File

@ -0,0 +1,118 @@
WorldEdit Tutorial
==================
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, that is 5.0 or later.
* You have WorldEdit installed as a mod.
* 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.
* How to walk, jump, and climb.
* How to dig, place, and punch blocks.
* One of the following:
* How to type into the chat and read text from it.
* How to open the inventory screen and press buttons on it.
Overview
--------
WorldEdit has a "region", which is simply a cuboid area defined by two markers, both of which the player can move around. Every player can have their own region with their own two markers.
WorldEdit GUI buttons and chat commands generally work inside the region selected, or around the first marker.
If you are using the chat commands, follow the steps under **Chat Commands**. If you are using the WorldEdit GUI, follow the steps under **WorldEdit GUI**.
Step 1: Selecting a region
--------------------------
### Chat Commands
In the chat prompt, enter `//p set`. In the chat, you are prompted to punch two nodes to set the positions of the two markers.
Punch a nearby node. Be careful of breakable ones such as torches. A black cube reading "1" will appear around the node. This is the marker for WorldEdit position 1.
Walk away from the node you just punched. Now, punch another node. A black cube reading "2" will appear around the node. This is the marker for WorldEdit position 2.
### WorldEdit GUI
Open the main WorldEdit GUI from your inventory screen. The icon looks like a globe with a red dot in the center.
Press the "Get/Set Positions" button. On the new screen, press the "Set Position 1" button. The inventory screen should close.
Punch a nearby node. Be careful of breakable ones such as torches. A black cube reading "1" will appear around the node. This is the marker for WorldEdit position 1.
Walk away from the node you just punched. Open your inventory again. It should be on the same page as it was before.
Press the "Set Position 2" button. The inventory screen should close.
Now, punch another node. A black cube reading "2" will appear around the node. This is the marker for WorldEdit position 2.
Step 2: Region commands
-----------------------
### Chat Commands
In the chat prompt, enter `//set mese`. In the chat, you will see a message showing the number of nodes set after a small delay.
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 source`.
### WorldEdit GUI
Open the main WorldEdit GUI from your inventory screen.
Press the "Set Nodes" button. You should see a new screen with various options for setting nodes.
Enter "mese" in the "Name" field. Press Search if you would like to see what the node you just entered looks like.
Press the "Set Nodes" button on this screen. In the chat, you will see a message showing the number of nodes set after a small delay.
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 source".
Step 3: Position commands
-------------------------
### Chat Commands
In the chat prompt, enter `//hollowdome 30 glass`. In the chat, you will see a message showing the number of nodes set after a small delay.
Look around marker 1: it is now surrounded by a hollow glass dome!
The `//hollowdome <radius> <node>` command creates a hollow dome centered around marker 1, made of any node you want. It is a position-oriented command, which means it works around marker 1 and can go outside the WorldEdit region.
### WorldEdit GUI
Open the main WorldEdit GUI from your inventory screen.
Press the "Sphere/Dome" button. You should see a new screen with various options for making spheres or domes.
Enter "glass" in the "Name" field. Press Search if you would like to see what the node you just entered looks like.
Enter "30" in the "Radius" field.
Press the "Hollow Dome" button on this screen. In the chat, you will see a message showing the number of nodes added after a small delay.
Look around marker 1: it is now surrounded by a hollow glass dome!
The "Hollow Dome" function creates a hollow dome centered around marker 1, made of any node you want. It is a position-oriented command, which means it works around marker 1 and can go outside the WorldEdit region.
Step 4: Other commands
----------------------
### Chat Commands
There are many more commands than what is shown here. See the [Chat Commands Reference](ChatCommands.md) for a detailed list of them, along with descriptions and examples for every single one.
If you're in-game and forgot how a command works, just use the `/help <command name>` command, without the first forward slash. For example, to see some information about the `//set <node>` command mentioned earlier, simply use `/help /set`.
A very useful command to check out is the `//save <schematic>` command, which can save everything inside the WorldEdit region to a file, stored on the computer hosting the server (the player's computer, in single player mode). You can then later use `//load <schematic>` to load the data in a file into a world, even another world on another computer.
### WorldEdit GUI
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.

248
WorldEdit API.md Normal file
View File

@ -0,0 +1,248 @@
WorldEdit API
=============
The WorldEdit API is composed of multiple modules, each of which is independent and can be used without the other. Each module is contained within a single file.
If needed, individual modules such as visualization.lua can be removed without affecting the rest of the program. The only file that cannot be removed is init.lua, which is necessary for the mod to run.
For more information, see the [README](README.md).
General
-------
### value = worldedit.version
Contains the current version of WorldEdit in a table of the form `{major=MAJOR_INTEGER, minor=MINOR_INTEGER}`, where `MAJOR_INTEGER` is the major version (the number before the period) as an integer, and `MINOR_INTEGER` is the minor version (the number after the period) as an integer. This is intended for version checking purposes.
### value = worldedit.version_string
Contains the current version of WorldEdit in the form of a string `"MAJOR_INTEGER.MINOR_INTEGER"`, where `MAJOR_INTEGER` is the major version (the number before the period) as an integer, and `MINOR_INTEGER` is the minor version (the number after the period) as an integer. This is intended for display purposes.
Manipulations
-------------
Contained in manipulations.lua, this module allows several node operations to be applied over a region.
### 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.
### `count = worldedit.set_param2(pos1, pos2, param2)`
Sets the param2 values of all nodes in a region defined by positions `pos1` and `pos2` to `param2`.
Returns the number of nodes set.
### count = worldedit.replace(pos1, pos2, searchnode, replacenode)
Replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`.
Returns the number of nodes replaced.
### count = worldedit.replaceinverse(pos1, pos2, searchnode, replacenode)
Replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`.
Returns the number of nodes replaced.
### count = worldedit.copy(pos1, pos2, axis, amount)
Copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes.
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.
### count = worldedit.move(pos1, pos2, axis, amount)
Moves the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes.
Returns the number of nodes moved.
### count = worldedit.stack(pos1, pos2, axis, count)
Duplicates the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") `count` times.
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.
### count, newpos1, newpos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
Stretches the region defined by positions `pos1` and `pos2` by an factor of positive integers `stretchx`, `stretchy`. and `stretchz` along the X, Y, and Z axes, respectively, with `pos1` as the origin.
Returns the number of nodes stretched, the new scaled position 1, and the new scaled position 2.
### count, newpos1, newpos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
Transposes a region defined by the positions `pos1` and `pos2` between the `axis1` and `axis2` axes ("x" or "y" or "z").
Returns the number of nodes transposed, the new transposed position 1, and the new transposed position 2.
### count = worldedit.flip(pos1, pos2, axis)
Flips a region defined by the positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z").
Returns the number of nodes flipped.
### count, newpos2, newpos2 = worldedit.rotate(pos1, pos2, angle)
Rotates a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise around the y axis (supporting 90 degree increments only).
Returns the number of nodes rotated, the new position 1, and the new position 2.
### count = worldedit.orient(pos1, pos2, angle)
Rotates all oriented nodes in a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise (90 degree increment) around the Y axis.
Returns the number of nodes oriented.
### count = worldedit.fixlight(pos1, pos2)
Fixes the lighting in a region defined by positions `pos1` and `pos2`.
Returns the number of nodes updated.
### count = worldedit.clearobjects(pos1, pos2)
Clears all objects in a region defined by the positions `pos1` and `pos2`.
Returns the number of objects cleared.
Primitives
----------
Contained in primitives.lua, this module allows the creation of several geometric primitives.
### count = worldedit.cube(pos, width, height, length, node_name, hollow)
Adds a cube with its ground level centered at `pos`, the dimensions `width` x `height` x `length`, composed of `node_name`.
Returns the number of nodes added.
### count = worldedit.sphere(pos, radius, node_name, hollow)
Adds a sphere centered at `pos` with radius `radius`, composed of `node_name`.
Returns the number of nodes added.
### count = worldedit.dome(pos, radius, node_name, hollow)
Adds a dome centered at `pos` with radius `radius`, composed of `node_name`.
Returns the number of nodes added.
### count = worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, hollow)
Adds a cylinder-like at `pos` along the `axis` axis ("x" or "y" or "z") with length `length`, base radius `radius1` and top radius `radius2`, composed of `node_name`.
Returns the number of nodes added.
### count = worldedit.pyramid(pos, axis, height, node_name, hollow)
Adds a pyramid centered at `pos` along the `axis` axis ("x" or "y" or "z") with height `height`, composed of `node_name`.
Returns the number of nodes added.
### count = worldedit.spiral(pos, length, height, spacer, node_name)
Adds a spiral centered at `pos` with side length `length`, height `height`, space between walls `spacer`, composed of `node_name`.
Returns the number of nodes added.
Visualization
-------------
Contained in visualization.lua, this module allows nodes to be visualized in different ways.
### volume = worldedit.volume(pos1, pos2)
Determines the volume of the region defined by positions `pos1` and `pos2`.
Returns the volume.
### count = worldedit.hide(pos1, pos2)
Hides all nodes in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes.
Returns the number of nodes hidden.
### count = worldedit.suppress(pos1, pos2, node_name)
Suppresses all instances of `node_name` in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes.
Returns the number of nodes suppressed.
### count = worldedit.highlight(pos1, pos2, node_name)
Highlights all instances of `node_name` in a region defined by positions `pos1` and `pos2` by non-destructively hiding all other nodes.
Returns the number of nodes found.
### count = worldedit.restore(pos1, pos2)
Restores all nodes hidden with WorldEdit functions in a region defined by positions `pos1` and `pos2`.
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.
### version, extra_fields, content = worldedit.read_header(value)
Reads the header from serialized data `value`.
Returns the version as a positive integer (nil for unknown versions),
extra header fields (nil if not supported), and the content after the header.
### data, count = worldedit.serialize(pos1, pos2)
Converts the region defined by positions `pos1` and `pos2` into a single string.
Returns the serialized data and the number of nodes serialized, or nil.
### pos1, pos2, count = worldedit.allocate(origin_pos, value)
Determines the volume the nodes represented by string `value` would occupy if deserialized at `origin_pos`.
Returns the two corner positions and the number of nodes, or nil.
### count = worldedit.deserialize(origin_pos, value)
Loads the nodes represented by string `value` at position `origin_pos`.
Returns the number of nodes deserialized or nil.
Code
----
Contained in code.lua, this module allows arbitrary Lua code to be used with WorldEdit.
### error = worldedit.lua(code, name)
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
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)
Executes `code` as a Lua chunk in the global namespace with the variable `pos` available, for each node in a region defined by positions `pos1` and `pos2`.
Returns an error if the code fails or nil otherwise.

12
config.ld Normal file
View File

@ -0,0 +1,12 @@
project = "WorldEdit"
title = "WorldEdit API Documentation"
description = "Minetest mod to mass-modify nodes"
format = "markdown"
file = {"worldedit"}
topics = {
"README.md",
"Tutorial.md",
"ChatCommands.md",
"LICENSE.txt"
}

View File

@ -1,280 +0,0 @@
--modifies positions `pos1` and `pos2` so that each component of `pos1` is less than or equal to its corresponding conent of `pos2`, returning two new positions
worldedit.sort_pos = function(pos1, pos2)
pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}
pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
if pos1.x > pos2.x then
pos2.x, pos1.x = pos1.x, pos2.x
end
if pos1.y > pos2.y then
pos2.y, pos1.y = pos1.y, pos2.y
end
if pos1.z > pos2.z then
pos2.z, pos1.z = pos1.z, pos2.z
end
return pos1, pos2
end
--determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume
worldedit.volume = function(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1)
end
--sets a region defined by positions `pos1` and `pos2` to `nodename`, returning the number of nodes filled
worldedit.set = function(pos1, pos2, nodename)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local env = minetest.env
local node = {name=nodename}
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
env:add_node(pos, node)
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return worldedit.volume(pos1, pos2)
end
--replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced
worldedit.replace = function(pos1, pos2, searchnode, replacenode)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local env = minetest.env
if searchnode:find(":") == nil then
searchnode = "default:" .. searchnode
end
local pos = {x=pos1.x, y=0, z=0}
local node = {name=replacenode}
local count = 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
if env:get_node(pos).name == searchnode then
env:add_node(pos, node)
count = count + 1
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return count
end
--copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes copied
worldedit.copy = function(pos1, pos2, axis, amount)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local env = minetest.env
if amount < 0 then
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 = env:get_node(pos, node)
local value = pos[axis]
pos[axis] = value - amount
env:add_node(pos, node)
pos[axis] = value
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
else
local pos = {x=pos2.x, 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 = minetest.env:get_node(pos, node)
local value = pos[axis]
pos[axis] = value + amount
minetest.env:add_node(pos, node)
pos[axis] = value
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
end
return worldedit.volume(pos1, pos2)
end
--moves the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes moved
worldedit.move = function(pos1, pos2, axis, amount)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local env = minetest.env
if amount < 0 then
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 = env:get_node(pos, node)
env:remove_node(pos)
local value = pos[axis]
pos[axis] = value - amount
env:add_node(pos, node)
pos[axis] = value
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
else
local pos = {x=pos2.x, 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 = minetest.env:get_node(pos, node)
env:remove_node(pos)
local value = pos[axis]
pos[axis] = value + amount
minetest.env:add_node(pos, node)
pos[axis] = value
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
end
return worldedit.volume(pos1, pos2)
end
--duplicates the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") `count` times, returning the number of nodes stacked
worldedit.stack = function(pos1, pos2, axis, count)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local length = pos2[axis] - pos1[axis] + 1
local amount = 0
local copy = worldedit.copy
if count < 0 then
count = -count
length = -length
end
for i = 1, count do
amount = amount + length
copy(pos1, pos2, axis, amount)
end
return worldedit.volume(pos1, pos2)
end
--digs a region defined by positions `pos1` and `pos2`, returning the number of nodes dug
worldedit.dig = function(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local env = minetest.env
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
env:dig_node(pos)
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return worldedit.volume(pos1, pos2)
end
--converts the region defined by positions `pos1` and `pos2` into a single string, returning the serialized data and the number of nodes serialized
worldedit.serialize = function(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0}
local count = 0
local result = {}
local env = minetest.env
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 = env:get_node(pos)
if node.name ~= "air" and node.name ~= "ignore" then
count = count + 1
result[count] = pos.x - pos1.x .. " " .. pos.y - pos1.y .. " " .. pos.z - pos1.z .. " " .. node.name .. " " .. node.param1 .. " " .. node.param2
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
result = table.concat(result, "\n")
return result, count
end
--loads the nodes represented by string `value` at position `originpos`, returning the number of nodes deserialized
worldedit.deserialize = function(originpos, value)
local pos = {x=0, y=0, z=0}
local node = {name="", param1=0, param2=0}
local count = 0
local env = minetest.env
for x, y, z, name, param1, param2 in value:gmatch("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do
pos.x = originpos.x + tonumber(x)
pos.y = originpos.y + tonumber(y)
pos.z = originpos.z + tonumber(z)
node.name = name
node.param1 = param1
node.param2 = param2
env:add_node(pos, node)
count = count + 1
end
return count
end
--loads the nodes represented by string `value` at position `originpos`, returning the number of nodes deserialized
--based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) by ChillCode, available under the MIT license (GPL compatible)
worldedit.deserialize_old = function(originpos, value)
--obtain the node table
local count = 0
local get_tables = loadstring(value)
if get_tables == nil then --error loading value
return count
end
local tables = get_tables()
--transform the node table into an array of nodes
for i = 1, #tables do
for j, v in pairs(tables[i]) do
if type(v) == "table" then
tables[i][j] = tables[v[1]]
end
end
end
--load the node array
local env = minetest.env
for i, v in ipairs(tables[1]) do
local pos = v[1]
pos.x, pos.y, pos.z = originpos.x + pos.x, originpos.y + pos.y, originpos.z + pos.z
env:add_node(pos, v[2])
count = count + 1
end
return count
end

321
init.lua
View File

@ -1,321 +0,0 @@
minetest.register_privilege("worldedit", "Can use WorldEdit commands")
worldedit = {}
worldedit.set_pos = {}
worldedit.pos1 = {}
worldedit.pos2 = {}
dofile(minetest.get_modpath("worldedit") .. "/functions.lua")
dofile(minetest.get_modpath("worldedit") .. "/mark.lua")
--determines whether `nodename` is a valid node name, returning a boolean
worldedit.node_is_valid = function(temp_pos, nodename)
local originalnode = minetest.env:get_node(temp_pos) --save the original node to restore later
minetest.env:add_node(temp_pos, {name=nodename}) --attempt to add the node
local value = minetest.env:get_node(temp_pos).name --obtain the name of the newly added node
if value == nodename or value == "default:" .. nodename then --successfully added node
minetest.env:add_node(temp_pos, originalnode) --restore the original node
return true --node is valid
end
return false --node is not valid
end
minetest.register_chatcommand("/reset", {
params = "",
description = "Reset the region so that it is empty",
privs = {worldedit=true},
func = function(name, param)
worldedit.pos1[name] = nil
worldedit.pos2[name] = nil
worldedit.mark_pos1(name)
worldedit.mark_pos2(name)
minetest.chat_send_player(name, "WorldEdit region reset")
end,
})
minetest.register_chatcommand("/pos1", {
params = "",
description = "Set WorldEdit region position 1 to the player's location",
privs = {worldedit=true},
func = function(name, param)
local pos = minetest.env:get_player_by_name(name):getpos()
pos.x = math.floor(pos.x)
pos.y = math.floor(pos.y)
pos.z = math.floor(pos.z)
worldedit.pos1[name] = pos
worldedit.mark_pos1(name)
minetest.chat_send_player(name, "WorldEdit position 1 set to (" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
end,
})
minetest.register_chatcommand("/pos2", {
params = "",
description = "Set WorldEdit region position 2 to the player's location",
privs = {worldedit=true},
func = function(name, param)
local pos = minetest.env:get_player_by_name(name):getpos()
pos.x = math.floor(pos.x)
pos.y = math.floor(pos.y)
pos.z = math.floor(pos.z)
worldedit.pos2[name] = pos
worldedit.mark_pos2(name)
minetest.chat_send_player(name, "WorldEdit position 2 set to (" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
end,
})
minetest.register_chatcommand("/p", {
params = "set/get",
description = "Set WorldEdit region by punching two nodes, or display the current WorldEdit region",
privs = {worldedit=true},
func = function(name, param)
if param == "set" then --set both WorldEdit positions
worldedit.set_pos[name] = 1
minetest.chat_send_player(name, "Select positions by punching two nodes")
elseif param == "get" then --display current WorldEdit positions
local pos = worldedit.pos1[name]
minetest.chat_send_player(name, "WorldEdit position 1: (" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
local pos = worldedit.pos2[name]
minetest.chat_send_player(name, "WorldEdit position 2: (" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
else
minetest.chat_send_player(name, "Unknown subcommand: " .. param)
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] == 1 then --setting position 1
worldedit.set_pos[name] = 2 --set position 2 on the next invocation
worldedit.pos1[name] = pos
worldedit.mark_pos1(name)
minetest.chat_send_player(name, "WorldEdit region position 1 set to (" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
else --setting position 2
worldedit.set_pos[name] = nil --finished setting positions
worldedit.pos2[name] = pos
worldedit.mark_pos2(name)
minetest.chat_send_player(name, "WorldEdit region position 2 set to (" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")")
end
end
end)
minetest.register_chatcommand("/volume", {
params = "",
description = "Display the volume of the current WorldEdit region",
privs = {worldedit=true},
func = function(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
minetest.chat_send_player(name, "No WorldEdit region selected")
return
end
local volume = worldedit.volume(pos1, pos2)
minetest.chat_send_player(name, "Current WorldEdit region has a volume of " .. volume .. " nodes (" .. pos2.x - pos1.x .. "*" .. pos2.y - pos1.y .. "*" .. pos2.z - pos1.z .. ")")
end,
})
minetest.register_chatcommand("/set", {
params = "<node>",
description = "Set the current WorldEdit region to <node>",
privs = {worldedit=true},
func = function(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
minetest.chat_send_player(name, "No WorldEdit region selected")
return
end
if param == "" or not worldedit.node_is_valid(pos1, param) then
minetest.chat_send_player(name, "Invalid node name: " .. param)
return
end
local count = worldedit.set(pos1, pos2, param)
minetest.chat_send_player(name, count .. " nodes set")
end,
})
minetest.register_chatcommand("/replace", {
params = "<search node> <replace node>",
description = "Replace all instances of <search node> with <place node> in the current WorldEdit region",
privs = {worldedit=true},
func = function(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
minetest.chat_send_player(name, "No WorldEdit region selected")
return
end
local found, _, searchnode, replacenode = param:find("([^%s]+)%s+([^%s]+)")
if found == nil then
minetest.chat_send_player(name, "Invalid usage: " .. param)
return
end
if not worldedit.node_is_valid(pos1, searchnode) then
minetest.chat_send_player(name, "Invalid search node name: " .. searchnode)
return
end
if not worldedit.node_is_valid(pos1, replacenode) then
minetest.chat_send_player(name, "Invalid replace node name: " .. replacenode)
return
end
local count = worldedit.replace(pos1, pos2, searchnode, replacenode)
minetest.chat_send_player(name, count .. " nodes replaced")
end,
})
minetest.register_chatcommand("/copy", {
params = "x/y/z <amount>",
description = "Copy the current WorldEdit region along the x/y/z axis by <amount> nodes",
privs = {worldedit=true},
func = function(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
minetest.chat_send_player(name, "No WorldEdit region selected")
return
end
local found, _, axis, amount = param:find("([xyz])%s+([+-]?%d+)")
if found == nil then
minetest.chat_send_player(name, "Invalid usage: " .. param)
return
end
local count = worldedit.copy(pos1, pos2, axis, tonumber(amount))
minetest.chat_send_player(name, count .. " nodes copied")
end,
})
minetest.register_chatcommand("/move", {
params = "x/y/z <amount>",
description = "Move the current WorldEdit region along the x/y/z axis by <amount> nodes",
privs = {worldedit=true},
func = function(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
minetest.chat_send_player(name, "No WorldEdit region selected")
return
end
local found, _, axis, amount = param:find("([xyz])%s+([+-]?%d+)")
if found == nil then
minetest.chat_send_player(name, "Invalid usage: " .. param)
return
end
local count = worldedit.move(pos1, pos2, axis, tonumber(amount))
minetest.chat_send_player(name, count .. " nodes moved")
end,
})
minetest.register_chatcommand("/stack", {
params = "x/y/z <count>",
description = "Stack the current WorldEdit region along the x/y/z axis <count> times",
privs = {worldedit=true},
func = function(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
minetest.chat_send_player(name, "No WorldEdit region selected")
return
end
local found, _, axis, count = param:find("([xyz])%s+([+-]?%d+)")
if found == nil then
minetest.chat_send_player(name, "Invalid usage: " .. param)
return
end
local count = worldedit.stack(pos1, pos2, axis, tonumber(count))
minetest.chat_send_player(name, count .. " nodes stacked")
end,
})
minetest.register_chatcommand("/dig", {
params = "",
description = "Dig the current WorldEdit region",
privs = {worldedit=true},
func = function(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
minetest.chat_send_player(name, "No WorldEdit region selected")
return
end
local count = worldedit.dig(pos1, pos2)
minetest.chat_send_player(name, count .. " nodes dug")
end,
})
minetest.register_chatcommand("/save", {
params = "<file>",
description = "Save the current WorldEdit region to \"(world folder)/schems/<file>.we\"",
privs = {worldedit=true},
func = function(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
minetest.chat_send_player(name, "No WorldEdit region selected")
return
end
if param == "" then
minetest.chat_send_player(name, "Invalid usage: " .. param)
return
end
local result, count = worldedit.serialize(pos1, pos2)
local path = minetest.get_worldpath() .. "/schems"
local filename = path .. "/" .. param .. ".we"
os.execute("mkdir \"" .. path .. "\"") --create directory if it does not already exist
local file, err = io.open(filename, "wb")
if err ~= nil then
minetest.chat_send_player(name, "Could not save file to \"" .. filename .. "\"")
return
end
file:write(result)
file:flush()
file:close()
minetest.chat_send_player(name, count .. " nodes saved")
end,
})
minetest.register_chatcommand("/load", {
params = "<file>",
description = "Load nodes from \"(world folder)/schems/<file>.we\" with position 1 of the current WorldEdit region as the origin",
privs = {worldedit=true},
func = function(name, param)
local pos1 = worldedit.pos1[name]
if pos1 == nil then
minetest.chat_send_player(name, "No WorldEdit region selected")
return
end
if param == "" then
minetest.chat_send_player(name, "Invalid usage: " .. param)
return
end
local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we"
local file, err = io.open(filename, "rb")
if err ~= nil then
minetest.chat_send_player(name, "Could not open file \"" .. filename .. "\"")
return
end
local value = file:read("*a")
file:close()
local count
if value:find("{") then --old WorldEdit format
count = worldedit.deserialize_old(pos1, value)
else --new WorldEdit format
count = worldedit.deserialize(pos1, value)
end
minetest.chat_send_player(name, count .. " nodes loaded")
end,
})

View File

@ -1,58 +0,0 @@
worldedit.marker1 = {}
worldedit.marker2 = {}
--marks worldedit region position 1
worldedit.mark_pos1 = function(name)
local pos = worldedit.pos1[name]
if worldedit.marker1[name] ~= nil then --marker already exists
worldedit.marker1[name]:remove() --remove marker
worldedit.marker1[name] = nil
end
if pos ~= nil then --add marker
worldedit.marker1[name] = minetest.env:add_entity(pos, "worldedit:pos1")
end
end
--marks worldedit region position 2
worldedit.mark_pos2 = function(name)
local pos = worldedit.pos2[name]
if worldedit.marker2[name] ~= nil then --marker already exists
worldedit.marker2[name]:remove() --remove marker
worldedit.marker2[name] = nil
end
if pos ~= nil then --add marker
worldedit.marker2[name] = minetest.env:add_entity(pos, "worldedit:pos2")
end
end
minetest.register_entity("worldedit:pos1", {
initial_properties = {
visual = "cube",
visual_size = {x=1.1, y=1.1},
textures = {"worldedit_pos1.png", "worldedit_pos1.png",
"worldedit_pos1.png", "worldedit_pos1.png",
"worldedit_pos1.png", "worldedit_pos1.png"},
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
},
on_punch = function(self, hitter)
self.object:remove()
local name = hitter:get_player_name()
worldedit.marker1[name] = nil
end,
})
minetest.register_entity("worldedit:pos2", {
initial_properties = {
visual = "cube",
visual_size = {x=1.1, y=1.1},
textures = {"worldedit_pos2.png", "worldedit_pos2.png",
"worldedit_pos2.png", "worldedit_pos2.png",
"worldedit_pos2.png", "worldedit_pos2.png"},
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
},
on_punch = function(self, hitter)
self.object:remove()
local name = hitter:get_player_name()
worldedit.marker2[name] = nil
end,
})

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 B

64
worldedit/code.lua Normal file
View File

@ -0,0 +1,64 @@
--- Lua code execution functions.
-- @module worldedit.code
--- Executes `code` as a Lua chunk in the global namespace.
-- 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 func = factory()
local player, pos
if name then
player = minetest.get_player_by_name(name)
if player then
pos = vector.round(player:get_pos())
end
end
local good, err = pcall(func, name, player, pos)
if not good then -- Runtime error
return tostring(err)
end
return nil, dump(err)
end
--- Executes `code` as a Lua chunk in the global namespace with the variable
-- pos available, for each node in a region defined by positions `pos1` and
-- `pos2`.
-- @return An error message if the code fails, or nil on success.
function worldedit.luatransform(pos1, pos2, code)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local factory, err = loadstring("return function(pos) " .. code .. " end")
if not factory then -- Syntax error
return err
end
local func = factory()
worldedit.keep_loaded(pos1, pos2)
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 good, err = pcall(func, pos)
if not good then -- Runtime error
return tostring(err)
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return nil
end

193
worldedit/common.lua Normal file
View File

@ -0,0 +1,193 @@
--- Common functions [INTERNAL]. All of these functions are internal!
-- @module worldedit.common
--- Copies and modifies positions `pos1` and `pos2` so that each component of
-- `pos1` is less than or equal to the corresponding component of `pos2`.
-- Returns the new positions.
function worldedit.sort_pos(pos1, pos2)
pos1 = vector.new(pos1.x, pos1.y, pos1.z)
pos2 = vector.new(pos2.x, pos2.y, pos2.z)
if pos1.x > pos2.x then
pos2.x, pos1.x = pos1.x, pos2.x
end
if pos1.y > pos2.y then
pos2.y, pos1.y = pos1.y, pos2.y
end
if pos1.z > pos2.z then
pos2.z, pos1.z = pos1.z, pos2.z
end
return pos1, pos2
end
--- Determines the volume of the region defined by positions `pos1` and `pos2`.
-- @return The volume.
function worldedit.volume(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
return (pos2.x - pos1.x + 1) *
(pos2.y - pos1.y + 1) *
(pos2.z - pos1.z + 1)
end
--- Gets other axes given an axis.
-- @raise Axis must be x, y, or z!
function worldedit.get_axis_others(axis)
if axis == "x" then
return "y", "z"
elseif axis == "y" then
return "x", "z"
elseif axis == "z" then
return "x", "y"
else
error("Axis must be x, y, or z!")
end
end
function worldedit.keep_loaded(pos1, pos2)
-- Create a vmanip and read the area from map, this
-- causes all MapBlocks to be loaded into memory synchronously.
-- This doesn't actually *keep* them loaded, unlike the name implies.
if minetest.load_area then
-- same effect but without unnecessary data copying
minetest.load_area(pos1, pos2)
else
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
end
end
local mh = {}
worldedit.manip_helpers = mh
--- Generates an empty VoxelManip data table for an area.
-- @return The empty data table.
function mh.get_empty_data(area)
-- Fill emerged area with ignore so that blocks in the area that are
-- 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
data[i] = c_ignore
end
return data
end
function mh.init(pos1, pos2)
local manip = minetest.get_voxel_manip()
local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)
local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})
return manip, area
end
function mh.init_radius(pos, radius)
local pos1 = vector.subtract(pos, radius)
local pos2 = vector.add(pos, radius)
return mh.init(pos1, pos2)
end
function mh.init_axis_radius(base_pos, axis, radius)
return mh.init_axis_radius_length(base_pos, axis, radius, radius)
end
function mh.init_axis_radius_length(base_pos, axis, radius, length)
local other1, other2 = worldedit.get_axis_others(axis)
local pos1 = {
[axis] = base_pos[axis],
[other1] = base_pos[other1] - radius,
[other2] = base_pos[other2] - radius
}
local pos2 = {
[axis] = base_pos[axis] + length,
[other1] = base_pos[other1] + radius,
[other2] = base_pos[other2] + radius
}
return mh.init(pos1, pos2)
end
function mh.finish(manip, data)
-- Update map
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

@ -0,0 +1,77 @@
--- Compatibility functions.
-- @module worldedit.compatibility
local function deprecated(new_func)
local info = debug.getinfo(1, "n")
local msg = "worldedit." .. info.name .. "() is deprecated."
if new_func then
msg = msg .. " Use worldedit." .. new_func .. "() instead."
end
minetest.log("deprecated", msg)
end
worldedit.allocate_old = worldedit.allocate
worldedit.deserialize_old = worldedit.deserialize
function worldedit.metasave(pos1, pos2, filename)
deprecated("save")
local file, err = io.open(filename, "wb")
if err then return 0 end
local data, count = worldedit.serialize(pos1, pos2)
file:write(data)
file:close()
return count
end
function worldedit.metaload(originpos, file_name)
deprecated("load")
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
function worldedit.scale(pos1, pos2, factor)
deprecated("stretch")
return worldedit.stretch(pos1, pos2, factor, factor, factor)
end
function worldedit.valueversion(value)
deprecated("read_header")
local version = worldedit.read_header(value)
if not version or version > worldedit.LATEST_SERIALIZATION_VERSION then
return 0
end
return version
end
function worldedit.replaceinverse(pos1, pos2, search_node, replace_node)
deprecated("replace")
return worldedit.replace(pos1, pos2, search_node, replace_node, true)
end
function worldedit.clearobjects(...)
deprecated("clear_objects")
return worldedit.clear_objects(...)
end
function worldedit.hollow_sphere(pos, radius, node_name)
deprecated("sphere")
return worldedit.sphere(pos, radius, node_name, true)
end
function worldedit.hollow_dome(pos, radius, node_name)
deprecated("dome")
return worldedit.dome(pos, radius, node_name, true)
end
function worldedit.hollow_cylinder(pos, axis, length, radius, node_name)
deprecated("cylinder")
return worldedit.cylinder(pos, axis, length, radius, node_name, true)
end

246
worldedit/cuboid.lua Normal file
View File

@ -0,0 +1,246 @@
-- Expands or contracts the cuboid in all axes by amount (positive or negative)
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)
delta1 = vector.multiply(delta1, delta_dir1)
delta2 = vector.multiply(delta2, delta_dir2)
worldedit.pos1[name] = vector.add(pos1, delta1)
worldedit.pos2[name] = vector.add(pos2, delta2)
return true
end
-- Expands or contracts the cuboid in a single axis by amount (positive or negative)
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
deltavect.y = amount * direction
elseif axis == 'z' then
deltavect.z = amount * direction
else
return false, "invalid axis"
end
worldedit.marker_move(name, marker, deltavect)
return true
end
-- Shifts the cuboid by '+-amount' in axis 'axis'
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
elseif axis == 'y' then
worldedit.pos1[name].y = pos1.y + amount
worldedit.pos2[name].y = pos2.y + amount
elseif axis == 'z' then
worldedit.pos1[name].z = pos1.z + amount
worldedit.pos2[name].z = pos2.z + amount
else
return false, "invalid axis"
end
return true
end
-- Moves the location of a single marker by adding deltavector
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)
else
local pos = worldedit.pos2[name]
worldedit.pos2[name] = vector.add(deltavector, pos)
end
return true
end
-- Returns two vectors with the directions for volumetric expansion
worldedit.get_expansion_directions = function(mark1, mark2)
if mark1 == nil or mark2 == nil then
return
end
local dir1 = vector.new()
local dir2 = vector.new()
if mark1.x < mark2.x then
dir1.x = -1
dir2.x = 1
else
dir1.x = 1
dir2.x = -1
end
if mark1.y < mark2.y then
dir1.y = -1
dir2.y = 1
else
dir1.y = 1
dir2.y = -1
end
if mark1.z < mark2.z then
dir1.z = -1
dir2.z = 1
else
dir1.z = 1
dir2.z = -1
end
return dir1, dir2
end
-- Return the marker that is closest to the player
worldedit.marker_get_closest_to_player = function(name)
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
return 2
end
end
-- Returns the closest marker to the specified axis and direction
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
if axis == 'x' then
pos1.x = worldedit.pos1[name].x * direction
pos2.x = worldedit.pos2[name].x * direction
if pos1.x > pos2.x then
return 1
else
return 2
end
elseif axis == 'y' then
pos1.y = worldedit.pos1[name].y * direction
pos2.y = worldedit.pos2[name].y * direction
if pos1.y > pos2.y then
return 1
else
return 2
end
elseif axis == 'z' then
pos1.z = worldedit.pos1[name].z * direction
pos2.z = worldedit.pos2[name].z * direction
if pos1.z > pos2.z then
return 1
else
return 2
end
else
return nil
end
end
-- 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
resdir = nil
else
resaxis = axis
resdir = dir
end
end
if direction == "back" then
if axis == "y" then
resaxis = nil
resdir = nil
else
resaxis = axis
resdir = -dir
end
end
if direction == "left" then
if axis == 'x' then
resaxis = 'z'
resdir = dir
elseif axis == 'z' then
resaxis = 'x'
resdir = -dir
end
end
if direction == "right" then
if axis == 'x' then
resaxis = 'z'
resdir = -dir
elseif axis == 'z' then
resaxis = 'x'
resdir = dir
end
end
return resaxis, resdir
end

45
worldedit/init.lua Normal file
View File

@ -0,0 +1,45 @@
--- WorldEdit mod for the Minetest engine
-- @module worldedit
-- @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)
-- @author Bret O'Donnel (cornernote)
-- @author ShadowNinja
worldedit = {}
local ver = {major=1, minor=3}
worldedit.version = ver
worldedit.version_string = string.format("%d.%d", ver.major, ver.minor)
local path = minetest.get_modpath(minetest.get_current_modname())
local function load_module(path)
local file = io.open(path, "r")
if not file then return end
file:close()
return dofile(path)
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")
load_module(path .. "/compatibility.lua")
load_module(path .. "/cuboid.lua")
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

338
worldedit/manipulations.lua Normal file
View File

@ -0,0 +1,338 @@
--- Generic node manipulations.
-- @module worldedit.manipulations
local mh = worldedit.manip_helpers
--- Sets a region to `node_names`.
-- @param pos1
-- @param pos2
-- @param node_names Node name or list of node names.
-- @return The number of nodes set.
function worldedit.set(pos1, pos2, node_names)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local manip, area = mh.init(pos1, pos2)
local data = mh.get_empty_data(area)
if type(node_names) == "string" then -- Only one type of node
local id = minetest.get_content_id(node_names)
-- Fill area with node
for i in area:iterp(pos1, pos2) do
data[i] = id
end
else -- Several types of nodes specified
local node_ids = {}
for i, v in ipairs(node_names) do
node_ids[i] = minetest.get_content_id(v)
end
-- Fill area randomly with nodes
local id_count, rand = #node_ids, math.random
for i in area:iterp(pos1, pos2) do
data[i] = node_ids[rand(id_count)]
end
end
mh.finish(manip, data)
return worldedit.volume(pos1, pos2)
end
--- Sets param2 of a region.
-- @param pos1
-- @param pos2
-- @param param2 Value of param2 to set
-- @return The number of nodes set.
function worldedit.set_param2(pos1, pos2, param2)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local manip, area = mh.init(pos1, pos2)
local param2_data = manip:get_param2_data()
-- Set param2 for every node
for i in area:iterp(pos1, pos2) do
param2_data[i] = param2
end
-- Update map
manip:set_param2_data(param2_data)
manip:write_to_map()
manip:update_map()
return worldedit.volume(pos1, pos2)
end
--- Replaces all instances of `search_node` with `replace_node` in a region.
-- When `inverse` is `true`, replaces all instances that are NOT `search_node`.
-- @return The number of nodes replaced.
function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local manip, area = mh.init(pos1, pos2)
local data = manip:get_data()
local search_id = minetest.get_content_id(search_node)
local replace_id = minetest.get_content_id(replace_node)
local count = 0
if not inverse then
for i in area:iterp(pos1, pos2) do
if data[i] == search_id then
data[i] = replace_id
count = count + 1
end
end
else
for i in area:iterp(pos1, pos2) do
if data[i] ~= search_id then
data[i] = replace_id
count = count + 1
end
end
end
mh.finish(manip, data)
return count
end
--- Copies a region along `axis` by `amount` nodes.
-- @param pos1
-- @param pos2
-- @param axis Axis ("x", "y", or "z")
-- @param amount
-- @return The number of nodes copied.
function worldedit.copy(pos1, pos2, axis, amount)
local pos1, pos2 = worldedit.sort_pos(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 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, meta_backwards)
local pos1, pos2 = worldedit.sort_pos(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 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
end
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)
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
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
-- 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
--- Attempts to fix the lighting in a region.
-- @return The number of nodes updated.
function worldedit.fixlight(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local vmanip = minetest.get_voxel_manip(pos1, pos2)
vmanip:write_to_map()
vmanip:update_map() -- this updates the lighting
return worldedit.volume(pos1, pos2)
end
--- Clears all objects in a region.
-- @return The number of objects cleared.
function worldedit.clear_objects(pos1, pos2)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
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 = 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 - 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
end
end
end
return count
end

2
worldedit/mod.conf Normal file
View File

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

324
worldedit/primitives.lua Normal file
View File

@ -0,0 +1,324 @@
--- Functions for creating primitive shapes.
-- @module worldedit.primitives
local mh = worldedit.manip_helpers
--- Adds a cube
-- @param pos Position of ground level center of cube
-- @param width Cube width. (x)
-- @param height Cube height. (y)
-- @param length Cube length. (z)
-- @param node_name Name of node to make cube of.
-- @param hollow Whether the cube should be hollow.
-- @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 endpos = vector.add(basepos,
{x = width - 1, y = height - 1, z = 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 count = 0
local iterfunc
if hollow then
iterfunc = mh.iterp_hollowcuboid(area, basepos, endpos)
else
iterfunc = area:iterp(basepos, endpos)
end
for vi in iterfunc do
data[vi] = node_id
count = count + 1
end
mh.finish(manip, data)
return count
end
--- Adds a sphere of `node_name` centered at `pos`.
-- @param pos Position to center sphere at.
-- @param radius Sphere radius.
-- @param node_name Name of node to make shere of.
-- @param hollow Whether the sphere should be hollow.
-- @return The number of nodes added.
function worldedit.sphere(pos, radius, node_name, hollow)
local manip, area = mh.init_radius(pos, radius)
local data = mh.get_empty_data(area)
-- Fill selected area with node
local node_id = minetest.get_content_id(node_name)
local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
local stride_z, stride_y = area.zstride, area.ystride
local count = 0
for z = -radius, radius do
-- Offset contributed by z plus 1 to make it 1-indexed
local new_z = (z + offset_z) * stride_z + 1
for y = -radius, radius do
local new_y = new_z + (y + offset_y) * stride_y
for x = -radius, radius do
local squared = x * x + y * y + z * z
if squared <= max_radius and (not hollow or squared >= min_radius) then
-- Position is on surface of sphere
local i = new_y + (x + offset_x)
data[i] = node_id
count = count + 1
end
end
end
end
mh.finish(manip, data)
return count
end
--- Adds a dome.
-- @param pos Position to center dome at.
-- @param radius Dome radius. Negative for concave domes.
-- @param node_name Name of node to make dome of.
-- @param hollow Whether the dome should be hollow.
-- @return The number of nodes added.
-- TODO: Add axis option.
function worldedit.dome(pos, radius, node_name, hollow)
local min_y, max_y = 0, radius
if radius < 0 then
radius = -radius
min_y, max_y = -radius, 0
end
local manip, area = mh.init_axis_radius(pos, "y", radius)
local data = mh.get_empty_data(area)
-- Add dome
local node_id = minetest.get_content_id(node_name)
local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
local stride_z, stride_y = area.zstride, area.ystride
local count = 0
for z = -radius, radius do
local new_z = (z + offset_z) * stride_z + 1 --offset contributed by z plus 1 to make it 1-indexed
for y = min_y, max_y do
local new_y = new_z + (y + offset_y) * stride_y
for x = -radius, radius do
local squared = x * x + y * y + z * z
if squared <= max_radius and (not hollow or squared >= min_radius) then
-- Position is in dome
local i = new_y + (x + offset_x)
data[i] = node_id
count = count + 1
end
end
end
end
mh.finish(manip, data)
return count
end
--- Adds a cylinder.
-- @param pos Position to center base of cylinder at.
-- @param axis Axis ("x", "y", or "z")
-- @param length Cylinder length.
-- @param radius1 Cylinder base radius.
-- @param radius2 Cylinder top radius.
-- @param node_name Name of node to make cylinder of.
-- @param hollow Whether the cylinder should be hollow.
-- @return The number of nodes added.
function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, hollow)
local other1, other2 = worldedit.get_axis_others(axis)
-- Backwards compatibility
if type(radius2) == "string" then
hollow = node_name
node_name = radius2
radius2 = radius1 -- straight cylinder
end
-- Handle negative lengths
local current_pos = vector.new(pos)
if length < 0 then
length = -length
current_pos[axis] = current_pos[axis] - length
radius1, radius2 = radius2, radius1
end
-- Set up voxel manipulator
local manip, area = mh.init_axis_radius_length(current_pos, axis, math.max(radius1, radius2), length)
local data = mh.get_empty_data(area)
-- Add desired shape (anything inbetween cylinder & cone)
local node_id = minetest.get_content_id(node_name)
local stride = vector.new(1, area.ystride, area.zstride)
local offset = {
x = current_pos.x - area.MinEdge.x,
y = current_pos.y - area.MinEdge.y,
z = current_pos.z - area.MinEdge.z,
}
local count = 0
for i = 0, length - 1 do
-- Calulate radius for this "height" in the cylinder
local radius = radius1 + (radius2 - radius1) * (i + 1) / length
radius = math.floor(radius + 0.5) -- round
local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
for index2 = -radius, radius do
-- Offset contributed by other axis 1 plus 1 to make it 1-indexed
local new_index2 = (index2 + offset[other1]) * stride[other1] + 1
for index3 = -radius, radius do
local new_index3 = new_index2 + (index3 + offset[other2]) * stride[other2]
local squared = index2 * index2 + index3 * index3
if squared <= max_radius and (not hollow or squared >= min_radius) then
-- Position is in cylinder, add node here
local vi = new_index3 + (offset[axis] + i) * stride[axis]
data[vi] = node_id
count = count + 1
end
end
end
end
mh.finish(manip, data)
return count
end
--- Adds a pyramid.
-- @param pos Position to center base of pyramid at.
-- @param axis Axis ("x", "y", or "z")
-- @param height Pyramid height.
-- @param node_name Name of node to make pyramid of.
-- @param hollow Whether the pyramid should be hollow.
-- @return The number of nodes added.
function worldedit.pyramid(pos, axis, height, node_name, hollow)
local other1, other2 = worldedit.get_axis_others(axis)
-- Set up voxel manipulator
-- 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 step
if height > 0 then
height = height - 1
step = 1
else
height = height + 1
step = -1
end
-- Add pyramid
local node_id = minetest.get_content_id(node_name)
local stride = vector.new(1, area.ystride, area.zstride)
local offset = {
x = pos.x - area.MinEdge.x,
y = pos.y - area.MinEdge.y,
z = pos.z - area.MinEdge.z,
}
local size = math.abs(height * step)
local count = 0
-- For each level of the pyramid
for index1 = 0, height, step do
-- Offset contributed by axis plus 1 to make it 1-indexed
local new_index1 = (index1 + offset[axis]) * stride[axis] + 1
for index2 = -size, size do
local new_index2 = new_index1 + (index2 + offset[other1]) * stride[other1]
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
end
end
end
size = size - 1
end
mh.finish(manip, data)
return count
end
--- Adds a spiral.
-- @param pos Position to center spiral at.
-- @param length Spral length.
-- @param height Spiral height.
-- @param spacer Space between walls.
-- @param node_name Name of node to make spiral of.
-- @return Number of nodes added.
-- TODO: Add axis option.
function worldedit.spiral(pos, length, height, spacer, node_name)
local extent = math.ceil(length / 2)
local manip, area = mh.init_axis_radius_length(pos, "y", extent, height)
local data = mh.get_empty_data(area)
-- Set up variables
local node_id = minetest.get_content_id(node_name)
local stride = vector.new(1, area.ystride, area.zstride)
local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
local i = offset_z * stride.z + offset_y * stride.y + offset_x + 1
-- Add first column
local count = height
local column = i
for y = 1, height do
data[column] = node_id
column = column + stride.y
end
-- Add spiral segments
local stride_axis, stride_other = stride.x, stride.z
local sign = -1
local segment_length = 0
spacer = spacer + 1
-- Go through each segment except the last
for segment = 1, math.floor(length / spacer) * 2 do
-- Change sign and length every other turn starting with the first
if segment % 2 == 1 then
sign = -sign
segment_length = segment_length + spacer
end
-- Fill segment
for index = 1, segment_length do
-- Move along the direction of the segment
i = i + stride_axis * sign
local column = i
-- Add column
for y = 1, height do
data[column] = node_id
column = column + stride.y
end
end
count = count + segment_length * height
stride_axis, stride_other = stride_other, stride_axis -- Swap axes
end
-- Add shorter final segment
sign = -sign
for index = 1, segment_length do
i = i + stride_axis * sign
local column = i
-- Add column
for y = 1, height do
data[column] = node_id
column = column + stride.y
end
end
count = count + segment_length * height
mh.finish(manip, data)
return count
end

267
worldedit/serialization.lua Normal file
View File

@ -0,0 +1,267 @@
--- Schematic serialization and deserialiation.
-- @module worldedit.serialization
worldedit.LATEST_SERIALIZATION_VERSION = 5
local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":"
--[[
Serialization version history:
1: Original format. Serialized Lua table with a weird linked format...
2: Position and node seperated into sub-tables in fields `1` and `2`.
3: List of nodes, one per line, with fields seperated by spaces.
Format: <X> <Y> <Z> <Name> <Param1> <Param2>
4: Serialized Lua table containing a list of nodes with `x`, `y`, `z`,
`name`, `param1`, `param2`, and `meta` fields.
5: Added header and made `param1`, `param2`, and `meta` fields optional.
Header format: <Version>,<ExtraHeaderField1>,...:<Content>
--]]
--- Reads the header of serialized data.
-- @param value Serialized WorldEdit data.
-- @return The version as a positive natural number, or 0 for unknown versions.
-- @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
local header_end = value:find(":", 1, true)
local header = value:sub(1, header_end - 1):split(",")
local version = tonumber(header[1])
table.remove(header, 1)
local content = value:sub(header_end + 1)
return version, header, content
end
-- Old versions that didn't include a header with a version number
if value:find("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)") and not value:find("%{") then -- List format
return 3, nil, value
elseif value:find("^[^\"']+%{%d+%}") then
if value:find("%[\"meta\"%]") then -- Meta flat table format
return 2, nil, value
end
return 1, nil, value -- Flat table format
elseif value:find("%{") then -- Raw nested table format
return 4, nil, value
end
return nil
end
--- Converts the region defined by positions `pos1` and `pos2`
-- into a single string.
-- @return The serialized data.
-- @return The number of nodes serialized.
function worldedit.serialize(pos1, pos2)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
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 = {}
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)
if node.name ~= "air" and node.name ~= "ignore" then
count = count + 1
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
result[count] = {
x = pos.x - pos1.x,
y = pos.y - pos1.y,
z = pos.z - pos1.z,
name = node.name,
param1 = node.param1 ~= 0 and node.param1 or nil,
param2 = node.param2 ~= 0 and node.param2 or nil,
meta = meta,
}
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
-- Serialize entries
result = minetest.serialize(result)
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.
-- @return A node list in the latest format, or nil on failure.
local function load_schematic(value)
local version, _, content = worldedit.read_header(value)
local nodes = {}
if version == 1 or version == 2 then -- Original flat table format
local tables = minetest.deserialize(content, true)
if not tables then return nil end
-- Transform the node table into an array of nodes
for i = 1, #tables do
for j, v in pairs(tables[i]) do
if type(v) == "table" then
tables[i][j] = tables[v[1]]
end
end
end
nodes = tables[1]
if version == 1 then --original flat table format
for i, entry in ipairs(nodes) do
local pos = entry[1]
entry.x, entry.y, entry.z = pos.x, pos.y, pos.z
entry[1] = nil
local node = entry[2]
entry.name, entry.param1, entry.param2 = node.name, node.param1, node.param2
entry[2] = nil
end
end
elseif version == 3 then -- List format
for x, y, z, name, param1, param2 in content:gmatch(
"([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+" ..
"([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do
param1, param2 = tonumber(param1), tonumber(param2)
table.insert(nodes, {
x = tonumber(x),
y = tonumber(y),
z = tonumber(z),
name = name,
param1 = param1 ~= 0 and param1 or nil,
param2 = param2 ~= 0 and param2 or nil,
})
end
elseif version == 4 or version == 5 then -- Nested table format
nodes = deserialize_workaround(content)
else
return nil
end
return nodes
end
--- Determines the volume the nodes represented by string `value` would occupy
-- if deserialized at `origin_pos`.
-- @return Low corner position.
-- @return High corner position.
-- @return The number of nodes.
function worldedit.allocate(origin_pos, value)
local nodes = load_schematic(value)
if not nodes or #nodes == 0 then return nil end
return worldedit.allocate_with_nodes(origin_pos, nodes)
end
-- Internal
function worldedit.allocate_with_nodes(origin_pos, nodes)
local huge = math.huge
local pos1x, pos1y, pos1z = huge, huge, huge
local pos2x, pos2y, pos2z = -huge, -huge, -huge
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
for i, entry in ipairs(nodes) do
local x, y, z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
if x < pos1x then pos1x = x end
if y < pos1y then pos1y = y end
if z < pos1z then pos1z = z end
if x > pos2x then pos2x = x end
if y > pos2y then pos2y = y end
if z > pos2z then pos2z = z end
end
return vector.new(pos1x, pos1y, pos1z), vector.new(pos2x, pos2y, pos2z), #nodes
end
--- Loads the nodes represented by string `value` at position `origin_pos`.
-- @return The number of nodes deserialized.
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 add_node, get_meta = minetest.add_node, minetest.get_meta
local registered_nodes = minetest.registered_nodes
for i, entry in ipairs(nodes) do
if registered_nodes[entry.name] then
entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
-- Entry acts as both position and node
add_node(entry, entry)
if entry.meta then
get_meta(entry):from_table(entry.meta)
end
end
end
return #nodes
end

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

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

132
worldedit/visualization.lua Normal file
View File

@ -0,0 +1,132 @@
--- Functions for visibly hiding nodes
-- @module worldedit.visualization
minetest.register_node("worldedit:placeholder", {
drawtype = "airlike",
paramtype = "light",
sunlight_propagates = true,
diggable = false,
pointable = false,
walkable = false,
groups = {not_in_creative_inventory=1},
})
--- Hides all nodes in a region defined by positions `pos1` and `pos2` by
-- non-destructively replacing them with invisible nodes.
-- @return The number of nodes hidden.
function worldedit.hide(pos1, pos2)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
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
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)
if node.name ~= "air" and node.name ~= "worldedit:placeholder" then
-- Save the node's original name
get_meta(pos):set_string("worldedit_placeholder", node.name)
-- Swap in placeholder node
node.name = "worldedit:placeholder"
swap_node(pos, node)
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return worldedit.volume(pos1, pos2)
end
--- Suppresses all instances of `node_name` in a region defined by positions
-- `pos1` and `pos2` by non-destructively replacing them with invisible nodes.
-- @return The number of nodes suppressed.
function worldedit.suppress(pos1, pos2, node_name)
-- Ignore placeholder supression
if node_name == "worldedit:placeholder" then
return 0
end
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local nodes = minetest.find_nodes_in_area(pos1, pos2, node_name)
local get_node, get_meta, swap_node = minetest.get_node,
minetest.get_meta, minetest.swap_node
for _, pos in ipairs(nodes) do
local node = get_node(pos)
-- Save the node's original name
get_meta(pos):set_string("worldedit_placeholder", node.name)
-- Swap in placeholder node
node.name = "worldedit:placeholder"
swap_node(pos, node)
end
return #nodes
end
--- Highlights all instances of `node_name` in a region defined by positions
-- `pos1` and `pos2` by non-destructively hiding all other nodes.
-- @return The number of nodes found.
function worldedit.highlight(pos1, pos2, node_name)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
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
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)
if node.name == node_name then -- Node found
count = count + 1
elseif node.name ~= "worldedit:placeholder" then -- Hide other nodes
-- Save the node's original name
get_meta(pos):set_string("worldedit_placeholder", node.name)
-- Swap in placeholder node
node.name = "worldedit:placeholder"
swap_node(pos, node)
end
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
return count
end
-- Restores all nodes hidden with WorldEdit functions in a region defined
-- by positions `pos1` and `pos2`.
-- @return The number of nodes restored.
function worldedit.restore(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local nodes = minetest.find_nodes_in_area(pos1, pos2, "worldedit:placeholder")
local get_node, get_meta, swap_node = minetest.get_node,
minetest.get_meta, minetest.swap_node
for _, pos in ipairs(nodes) do
local node = get_node(pos)
local meta = get_meta(pos)
local data = meta:to_table()
node.name = data.fields.worldedit_placeholder
data.fields.worldedit_placeholder = nil
meta:from_table(data)
swap_node(pos, node)
end
return #nodes
end

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

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

@ -0,0 +1,273 @@
local S = minetest.get_translator("worldedit_commands")
worldedit.register_command("outset", {
params = "[h/v] <amount>",
description = S("Outset the selected region."),
category = S("Region operations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
if find == nil then
return false
end
local hv_test = dir:find("[^hv]+")
if hv_test ~= nil then
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
assert(worldedit.cuboid_linear_expand(name, 'x', 1, amount))
assert(worldedit.cuboid_linear_expand(name, 'x', -1, amount))
assert(worldedit.cuboid_linear_expand(name, 'z', 1, amount))
assert(worldedit.cuboid_linear_expand(name, 'z', -1, amount))
elseif dir == "v" then
assert(worldedit.cuboid_linear_expand(name, 'y', 1, amount))
assert(worldedit.cuboid_linear_expand(name, 'y', -1, amount))
else
return false, S("Invalid number of arguments")
end
worldedit.marker_update(name)
return true, S("Region outset by @1 nodes", amount)
end,
})
worldedit.register_command("inset", {
params = "[h/v] <amount>",
description = S("Inset the selected region."),
category = S("Region operations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
if find == nil then
return false
end
if dir:find("[^hv]") ~= nil then
return false, S("Invalid direction: @1", dir)
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
assert(worldedit.cuboid_linear_expand(name, 'x', 1, -amount))
assert(worldedit.cuboid_linear_expand(name, 'x', -1, -amount))
assert(worldedit.cuboid_linear_expand(name, 'z', 1, -amount))
assert(worldedit.cuboid_linear_expand(name, 'z', -1, -amount))
elseif dir == "v" then
assert(worldedit.cuboid_linear_expand(name, 'y', 1, -amount))
assert(worldedit.cuboid_linear_expand(name, 'y', -1, -amount))
else
return false, S("Invalid number of arguments")
end
worldedit.marker_update(name)
return true, S("Region inset by @1 nodes", amount)
end,
})
worldedit.register_command("shift", {
params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>",
description = S("Shifts the selection area without moving its contents"),
category = S("Region operations"),
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local find, _, direction, amount = param:find("([%?%l]+)%s*([+-]?%d+)")
if find == nil then
return false
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
elseif direction == "?" then
axis, dir = worldedit.player_axis(name)
else
axis, dir = worldedit.translate_direction(name, direction)
end
if axis == nil or dir == nil then
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, S("Region shifted by @1 nodes", amount)
end,
})
worldedit.register_command("expand", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = S("Expands the selection in the selected absolute or relative axis"),
category = S("Region operations"),
privs = {worldedit=true},
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
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
if direction == "?" then
axis, dir = worldedit.player_axis(name)
else
axis = direction
dir = 1
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, S("Region expanded by @1 nodes", amount + rev_amount)
end,
})
worldedit.register_command("contract", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = S("Contracts the selection in the selected absolute or relative axis"),
category = S("Region operations"),
privs = {worldedit=true},
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
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
if direction == "?" then
axis, dir = worldedit.player_axis(name)
else
axis = direction
dir = 1
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, 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,
})

322
worldedit_commands/init.lua Normal file
View File

@ -0,0 +1,322 @@
local S = minetest.get_translator("worldedit_commands")
minetest.register_privilege("worldedit", S("Can use WorldEdit commands"))
worldedit.pos1 = {}
worldedit.pos2 = {}
local safe_region, reset_pending = dofile(minetest.get_modpath("worldedit_commands") .. "/safe.lua")
worldedit.registered_commands = {}
local function copy_state(which, name)
if which == 0 then
return {}
elseif which == 1 then
return {
worldedit.pos1[name] and vector.copy(worldedit.pos1[name])
}
else
return {
worldedit.pos1[name] and vector.copy(worldedit.pos1[name]),
worldedit.pos2[name] and vector.copy(worldedit.pos2[name])
}
end
end
local function chatcommand_handler(cmd_name, name, param)
local def = assert(worldedit.registered_commands[cmd_name])
if def.require_pos == 2 then
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name, S("no region selected"), "error")
return
end
elseif def.require_pos == 1 then
local pos1 = worldedit.pos1[name]
if pos1 == nil then
worldedit.player_notify(name, S("no position 1 selected"), "error")
return
end
end
local parsed = {def.parse(param)}
local success = table.remove(parsed, 1)
if not success then
worldedit.player_notify(name, parsed[1] or S("invalid usage"), "error")
return
end
local run = function()
local ok, msg = def.func(name, unpack(parsed))
if msg then
worldedit.player_notify(name, msg, ok and "ok" or "error")
end
end
if not def.nodes_needed then
-- no safe region check
run()
return
end
local count = def.nodes_needed(name, unpack(parsed))
local old_state = copy_state(def.require_pos, name)
safe_region(name, count, function()
local state = copy_state(def.require_pos, name)
local ok = true
for i, v in ipairs(state) do
ok = ok and ( (v == nil and old_state[i] == nil) or vector.equals(v, old_state[i]) )
end
if not ok then
worldedit.player_notify(name, S("ERROR: the operation was cancelled because the region has changed."), "error")
return
end
run()
end)
end
-- Registers a chatcommand for WorldEdit
-- name = "about" -- Name of the chat command (without any /)
-- def = {
-- privs = {}, -- Privileges needed
-- params = "", -- Human readable parameter list (optional)
-- -- if params = "" then a parse() implementation will automatically be provided
-- description = "", -- Description
-- category = "", -- Category of the command (optional)
-- require_pos = 0, -- Number of positions required to be set (optional)
-- parse = function(param)
-- return true, foo, bar, ...
-- -- or
-- return false
-- -- or
-- return false, "error message"
-- end,
-- nodes_needed = function(name, foo, bar, ...), -- (optional)
-- return n
-- end,
-- func = function(name, foo, bar, ...)
-- return success, "message"
-- end,
-- }
function worldedit.register_command(name, def)
local def = table.copy(def)
assert(name and #name > 0)
def.name = name
assert(def.privs)
def.category = def.category or ""
def.require_pos = def.require_pos or 0
assert(def.require_pos >= 0 and def.require_pos < 3)
if def.params == "" and not def.parse then
def.parse = function(param) return true end
else
assert(def.parse)
end
assert(def.nodes_needed == nil or type(def.nodes_needed) == "function")
assert(def.func)
-- for development
--[[if def.require_pos == 2 and not def.nodes_needed then
minetest.log("warning", "//" .. name .. " might be missing nodes_needed")
end--]]
-- disable further modification
setmetatable(def, {__newindex = {}})
minetest.register_chatcommand("/" .. name, {
privs = def.privs,
params = def.params,
description = def.description,
func = function(player_name, param)
return chatcommand_handler(name, player_name, param)
end,
})
worldedit.registered_commands[name] = def
end
do
local modpath = minetest.get_modpath("worldedit_commands")
for _, name in ipairs({
"code", "cuboid", "manipulations", "marker", "nodename", "primitives",
"region", "schematics", "transform", "wand"
}) do
dofile(modpath .. "/" .. name .. ".lua")
end
end
-- Notifies a player of something related to WorldEdit.
-- Message types:
-- "error" = An operation did not work as expected.
-- "ok" = An operation completed successfully. Because notifications of this type
-- can be filtered, use this ONLY for generic messages like "1234 nodes set".
-- "info" = Other informational messages
-- @param name Name of player
-- @param message Message text
-- @param typ Type of message (optional but strongly recommend)
function worldedit.player_notify(name, message, typ)
local t = {
"WorldEdit",
"-!-",
tostring(message)
}
if typ == "error" then
t[2] = minetest.colorize("#f22", t[2])
elseif typ == "ok" then
t[2] = minetest.colorize("#2f2", t[2])
end
minetest.chat_send_player(name, table.concat(t, " "))
end
-- Determines the axis in which a player is facing, returning an axis ("x", "y", or "z") and the sign (1 or -1)
function worldedit.player_axis(name)
local player = minetest.get_player_by_name(name)
if not player then
-- we promised to return something valid...
return "y", -1
end
local dir = player:get_look_dir()
local x, y, z = math.abs(dir.x), math.abs(dir.y), math.abs(dir.z)
if x > y then
if x > z then
return "x", dir.x > 0 and 1 or -1
end
elseif y > z then
return "y", dir.y > 0 and 1 or -1
end
return "z", dir.z > 0 and 1 or -1
end
worldedit.register_command("about", {
privs = {},
params = "",
description = S("Get information about the WorldEdit mod"),
func = function(name)
worldedit.player_notify(name, S("WorldEdit @1"..
" is available on this server. Type @2 to get a list of "..
"commands, or find more information at @3",
worldedit.version_string, minetest.colorize("#00ffff", "//help"),
"https://github.com/Uberi/Minetest-WorldEdit"
), "info")
end,
})
-- initially copied from builtin/chatcommands.lua
local function help_command(name, param)
local function format_help_line(cmd, def, follow_alias)
local msg = minetest.colorize("#00ffff", "//"..cmd)
if def.name ~= cmd then
msg = msg .. ": " .. S("alias to @1",
minetest.colorize("#00ffff", "//"..def.name))
if follow_alias then
msg = msg .. "\n" .. format_help_line(def.name, def)
end
else
if def.params and def.params ~= "" then
msg = msg .. " " .. def.params
end
if def.description and def.description ~= "" then
msg = msg .. ": " .. def.description
end
end
return msg
end
-- @param cmds list of {cmd, def}
local function sort_cmds(cmds)
table.sort(cmds, function(c1, c2)
local cmd1, cmd2 = c1[1], c2[1]
local def1, def2 = c1[2], c2[2]
-- by category (this puts the empty category first)
if def1.category ~= def2.category then
return def1.category < def2.category
end
-- put aliases last
if (cmd1 ~= def1.name) ~= (cmd2 ~= def2.name) then
return cmd2 ~= def2.name
end
-- then by name
return c1[1] < c2[1]
end)
end
if not minetest.check_player_privs(name, "worldedit") then
return false, S("You are not allowed to use any WorldEdit commands.")
end
if param == "" then
local list = {}
for cmd, def in pairs(worldedit.registered_commands) do
if minetest.check_player_privs(name, def.privs) then
list[#list + 1] = cmd
end
end
table.sort(list)
local help = minetest.colorize("#00ffff", "//help")
return true, S("Available commands: @1@n"
.. "Use '@2' to get more information,"
.. " or '@3' to list everything.",
table.concat(list, " "), help .. " <cmd>", help .. " all")
elseif param == "all" then
local cmds = {}
for cmd, def in pairs(worldedit.registered_commands) do
if minetest.check_player_privs(name, def.privs) then
cmds[#cmds + 1] = {cmd, def}
end
end
sort_cmds(cmds)
local list = {}
local last_cat = ""
for _, e in ipairs(cmds) do
if e[2].category ~= last_cat then
last_cat = e[2].category
list[#list + 1] = "---- " .. last_cat
end
list[#list + 1] = format_help_line(e[1], e[2])
end
return true, S("Available commands:@n") .. table.concat(list, "\n")
else
local def = worldedit.registered_commands[param]
if not def then
return false, S("Command not available: ") .. param
else
return true, format_help_line(param, def, true)
end
end
end
worldedit.register_command("help", {
privs = {},
params = "[all/<cmd>]",
description = S("Get help for WorldEdit commands"),
parse = function(param)
return true, param
end,
func = function(name, param)
local ok, msg = help_command(name, param)
if msg then
worldedit.player_notify(name, msg, ok and "info" or "error")
end
end,
})
-- needs to be here due to reset_pending()
worldedit.register_command("reset", {
params = "",
description = S("Reset the region so that it is empty"),
category = S("Region operations"),
privs = {worldedit=true},
func = function(name)
worldedit.pos1[name] = nil
worldedit.pos2[name] = nil
worldedit.marker_update(name)
worldedit.set_pos[name] = nil
--make sure the user does not try to confirm an operation after resetting pos:
reset_pending(name)
return true, S("region reset")
end,
})

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

@ -0,0 +1,201 @@
worldedit.marker1 = {}
worldedit.marker2 = {}
worldedit.marker_region = {}
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 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", init_sentinel)
if worldedit.marker1[name] ~= nil then
worldedit.marker1[name]:get_luaentity().player_name = name
end
end
if region_too == nil or region_too then
worldedit.mark_region(name)
end
end
--marks worldedit region position 2
worldedit.mark_pos2 = function(name, region_too)
local pos2 = worldedit.pos2[name]
if worldedit.marker2[name] ~= nil then --marker already exists
worldedit.marker2[name]:remove() --remove marker
worldedit.marker2[name] = nil
end
if pos2 ~= nil then
worldedit.keep_loaded(pos2, pos2)
--add marker
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
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
for _, entity in ipairs(worldedit.marker_region[name]) do
entity:remove()
end
worldedit.marker_region[name] = nil
end
if pos1 ~= nil and pos2 ~= nil then
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local vec = vector.subtract(pos2, pos1)
local maxside = math.max(vec.x, math.max(vec.y, vec.z))
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
return
end
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
-- TODO maybe we could skip this actually?
worldedit.keep_loaded(pos1, pos2)
local markers = {}
--XY plane markers
for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
local entpos = {x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}
local 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},
collisionbox = {-sizex, -sizey, -thickness, sizex, sizey, thickness},
})
marker:get_luaentity().player_name = name
table.insert(markers, marker)
end
end
--YZ plane markers
for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
local entpos = {x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}
local 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:set_yaw(math.pi / 2)
marker:get_luaentity().player_name = name
table.insert(markers, marker)
end
end
worldedit.marker_region[name] = markers
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",
visual_size = {x=1.1, y=1.1},
textures = {"worldedit_pos1.png", "worldedit_pos1.png",
"worldedit_pos1.png", "worldedit_pos1.png",
"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_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,
on_punch = function(self, hitter)
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", {
initial_properties = {
visual = "cube",
visual_size = {x=1.1, y=1.1},
textures = {"worldedit_pos2.png", "worldedit_pos2.png",
"worldedit_pos2.png", "worldedit_pos2.png",
"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_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,
on_punch = function(self, hitter)
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",
textures = {"worldedit_cube.png"},
visual_size = {x=10, y=10},
physical = false,
static_save = false,
},
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,
on_punch = function(self, hitter)
local markers = worldedit.marker_region[self.player_name]
if not markers then
return
end
for _, entity in ipairs(markers) do
entity:remove()
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

@ -0,0 +1,51 @@
local S = minetest.get_translator("worldedit_commands")
local safe_region_callback = {}
--`count` is the number of nodes that would possibly be modified
--`callback` is a callback to run when the user confirms
local function safe_region(name, count, callback)
if count < 20000 then
return callback()
end
--save callback to call later
safe_region_callback[name] = callback
worldedit.player_notify(name, S("WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel",
count, minetest.colorize("#00ffff", "//y"), minetest.colorize("#00ffff", "//n")), "info")
end
local function reset_pending(name)
safe_region_callback[name] = nil
end
minetest.register_chatcommand("/y", {
params = "",
description = S("Confirm a pending operation"),
func = function(name)
local callback = safe_region_callback[name]
if not callback then
worldedit.player_notify(name, S("no operation pending"), "error")
return
end
reset_pending(name)
callback(name)
end,
})
minetest.register_chatcommand("/n", {
params = "",
description = S("Abort a pending operation"),
func = function(name)
if not safe_region_callback[name] then
worldedit.player_notify(name, S("no operation pending"), "error")
return
end
reset_pending(name)
end,
})
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.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

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

@ -0,0 +1,74 @@
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 = S("WorldEdit Wand tool\nLeft-click to set 1st position, right-click to set 2nd"),
inventory_image = "worldedit_wand.png",
stack_max = 1, -- there is no need to have more than one
liquids_pointable = true, -- ground with only water on can be selected as well
-- ignore marker cube so the clicking on the position markers works reliably
pointabilities = {
objects = {
["worldedit:region_cube"] = false
}
},
on_use = function(itemstack, placer, pointed_thing)
if placer == nil or pointed_thing == nil then return end
local name = placer:get_player_name()
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_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
end,
})

View File

@ -0,0 +1,936 @@
--saved state for each player
local gui_nodename1 = {} --mapping of player names to node names
local gui_nodename2 = {} --mapping of player names to node names
local gui_axis1 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below)
local gui_axis2 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below)
local gui_distance1 = {} --mapping of player names to a distance (arbitrary strings may also appear as values)
local gui_distance2 = {} --mapping of player names to a distance (arbitrary strings may also appear as values)
local gui_distance3 = {} --mapping of player names to a distance (arbitrary strings may also appear as values)
local gui_count1 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values)
local gui_count2 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values)
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})
setmetatable(gui_nodename2, {__index = function() return "Stone" end})
setmetatable(gui_axis1, {__index = function() return 4 end})
setmetatable(gui_axis2, {__index = function() return 1 end})
setmetatable(gui_distance1, {__index = function() return "10" end})
setmetatable(gui_distance2, {__index = function() return "5" end})
setmetatable(gui_distance3, {__index = function() return "2" end})
setmetatable(gui_count1, {__index = function() return "3" end})
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", "?"}
setmetatable(axis_indices, {__index = function () return 4 end})
setmetatable(axis_values, {__index = function () return "?" end})
local angle_indices = {["90 degrees"]=1, ["180 degrees"]=2, ["270 degrees"]=3}
local angle_values = {90, 180, 270}
setmetatable(angle_indices, {__index = function () return 1 end})
setmetatable(angle_values, {__index = function () return 90 end})
-- given multiple sets of privileges, produces a single set of privs that would have the same effect as requiring all of them at the same time
local combine_privs = function(...)
local result = {}
for i, privs in ipairs({...}) do
for name, value in pairs(privs) do
if result[name] ~= nil and result[name] ~= value then --the priv must be both true and false, which can never happen
return {__fake_priv_that_nobody_has__=true} --privilege table that can never be satisfied
end
result[name] = value
end
end
return result
end
-- display node (or unknown_node image otherwise) at specified pos in formspec
local formspec_node = function(pos, nodename)
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 worldedit.registered_commands[command].privs
end
local function combine_we_privs(list)
local args = {}
for _, t in ipairs(list) do
table.insert(args, we_privs(t))
end
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)
execute_worldedit_command("about", name, "")
end,
})
worldedit.register_gui_function("worldedit_gui_inspect", {
name = "Toggle Inspect",
privs = we_privs("inspect"),
on_select = function(name)
execute_worldedit_command("inspect", name,
worldedit.inspect[name] and "disable" or "enable")
end,
})
worldedit.register_gui_function("worldedit_gui_region", {
name = "Get/Set Region",
privs = combine_we_privs({"p", "pos1", "pos2", "reset", "mark", "unmark", "volume", "fixedpos"}),
get_formspec = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
return "size[9,7]" .. worldedit.get_formspec_header("worldedit_gui_region") ..
"button_exit[0,1;3,0.8;worldedit_gui_p_get;Get Positions]" ..
"button_exit[3,1;3,0.8;worldedit_gui_p_set1;Choose Position 1]" ..
"button_exit[6,1;3,0.8;worldedit_gui_p_set2;Choose Position 2]" ..
"button_exit[0,2;3,0.8;worldedit_gui_pos1;Position 1 Here]" ..
"button_exit[3,2;3,0.8;worldedit_gui_pos2;Position 2 Here]" ..
"button_exit[6,2;3,0.8;worldedit_gui_reset;Reset Region]" ..
"button_exit[0,3;3,0.8;worldedit_gui_mark;Mark Region]" ..
"button_exit[3,3;3,0.8;worldedit_gui_unmark;Unmark Region]" ..
"button_exit[6,3;3,0.8;worldedit_gui_volume;Region Volume]" ..
"label[0,4.7;Position 1]" ..
string.format("field[2,5;1.5,0.8;worldedit_gui_fixedpos_pos1x;X ;%s]", pos1 and pos1.x or "") ..
string.format("field[3.5,5;1.5,0.8;worldedit_gui_fixedpos_pos1y;Y ;%s]", pos1 and pos1.y or "") ..
string.format("field[5,5;1.5,0.8;worldedit_gui_fixedpos_pos1z;Z ;%s]", pos1 and pos1.z or "") ..
"button_exit[6.5,4.68;2.5,0.8;worldedit_gui_fixedpos_pos1_submit;Set Position 1]" ..
"label[0,6.2;Position 2]" ..
string.format("field[2,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2x;X ;%s]", pos2 and pos2.x or "") ..
string.format("field[3.5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2y;Y ;%s]", pos2 and pos2.y or "") ..
string.format("field[5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2z;Z ;%s]", pos2 and pos2.z or "") ..
"button_exit[6.5,6.18;2.5,0.8;worldedit_gui_fixedpos_pos2_submit;Set Position 2]"
end,
})
worldedit.register_gui_handler("worldedit_gui_region", function(name, fields)
if fields.worldedit_gui_p_get then
execute_worldedit_command("p", name, "get")
return true
elseif fields.worldedit_gui_p_set1 then
execute_worldedit_command("p", name, "set1")
return true
elseif fields.worldedit_gui_p_set2 then
execute_worldedit_command("p", name, "set2")
return true
elseif fields.worldedit_gui_pos1 then
execute_worldedit_command("pos1", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_pos2 then
execute_worldedit_command("pos2", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_reset then
execute_worldedit_command("reset", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_mark then
execute_worldedit_command("mark", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_unmark then
execute_worldedit_command("unmark", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_volume then
execute_worldedit_command("volume", name, "")
worldedit.show_page(name, "worldedit_gui_region")
return true
elseif fields.worldedit_gui_fixedpos_pos1_submit then
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
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)))
worldedit.show_page(name, "worldedit_gui_region")
return true
end
return false
end)
worldedit.register_gui_function("worldedit_gui_set", {
name = "Set Nodes",
privs = we_privs("set"),
get_formspec = function(name)
local node = gui_nodename1[name]
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]"
end,
})
worldedit.register_gui_handler("worldedit_gui_set", function(name, fields)
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")
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
execute_worldedit_command("set", name, n)
end
return true
end
return ret
end)
worldedit.register_gui_function("worldedit_gui_replace", {
name = "Replace Nodes",
privs = combine_we_privs({"replace", "replaceinverse"}),
get_formspec = function(name)
local search, replace = gui_nodename1[name], gui_nodename2[name]
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]" ..
"button_exit[3.5,3.5;3,0.8;worldedit_gui_replace_submit_inverse;Replace Inverse]"
end,
})
worldedit.register_gui_handler("worldedit_gui_replace", function(name, fields)
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 = "replace"
if fields.worldedit_gui_replace_submit_inverse then
submit = "replaceinverse"
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 ret
end)
worldedit.register_gui_function("worldedit_gui_sphere_dome", {
name = "Sphere/Dome",
privs = combine_we_privs({"hollowsphere", "sphere", "hollowdome", "dome"}),
get_formspec = function(name)
local node, radius = gui_nodename1[name], gui_distance2[name]
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]" ..
"button_exit[3.5,4.5;3,0.8;worldedit_gui_sphere_dome_submit_solid_dome;Solid Dome]"
end,
})
worldedit.register_gui_handler("worldedit_gui_sphere_dome", function(name, fields)
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
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_sphere_dome")
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
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 ret
end)
worldedit.register_gui_function("worldedit_gui_cylinder", {
name = "Cylinder",
privs = combine_we_privs({"hollowcylinder", "cylinder"}),
get_formspec = function(name)
local node, axis, length = gui_nodename1[name], gui_axis1[name], gui_distance1[name]
local radius1, radius2 = gui_distance2[name], gui_distance3[name]
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]" ..
"button_exit[3.5,5.5;3,0.8;worldedit_gui_cylinder_submit_solid;Solid Cylinder]"
end,
})
worldedit.register_gui_handler("worldedit_gui_cylinder", function(name, fields)
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 = "hollowcylinder"
if fields.worldedit_gui_cylinder_submit_solid then
submit = "cylinder"
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
return ret
end)
worldedit.register_gui_function("worldedit_gui_pyramid", {
name = "Pyramid",
privs = we_privs("pyramid"),
get_formspec = function(name)
local node, axis, length = gui_nodename1[name], gui_axis1[name], gui_distance1[name]
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)
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 = "pyramid"
if fields.worldedit_gui_pyramid_submit_hollow then
submit = "hollowpyramid"
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
return ret
end)
worldedit.register_gui_function("worldedit_gui_spiral", {
name = "Spiral",
privs = we_privs("spiral"),
get_formspec = function(name)
local node, length, height, space = gui_nodename1[name], gui_distance1[name], gui_distance2[name], gui_distance3[name]
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)
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")
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 ret
end)
worldedit.register_gui_function("worldedit_gui_copy_move", {
name = "Copy/Move",
privs = combine_we_privs({"copy", "move"}),
get_formspec = function(name)
local axis = gui_axis1[name] or 4
local amount = gui_distance1[name] or "10"
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
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_copy_move")
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
return ret
end)
worldedit.register_gui_function("worldedit_gui_stack", {
name = "Stack",
privs = we_privs("stack"),
get_formspec = function(name)
local axis, count = gui_axis1[name], gui_count1[name]
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
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_stack")
execute_worldedit_command("stack", name,
axis_values[gui_axis1[name]] .. " " .. gui_count1[name])
return true
end
return ret
end)
worldedit.register_gui_function("worldedit_gui_stretch", {
name = "Stretch",
privs = we_privs("stretch"),
get_formspec = function(name)
local stretchx, stretchy, stretchz = gui_count1[name], gui_count2[name], gui_count3[name]
return "size[5,5]" .. worldedit.get_formspec_header("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
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_stretch")
execute_worldedit_command("stretch", name, string.format("%s %s %s",
gui_count1[name], gui_count2[name], gui_count3[name]))
return true
end
return ret
end)
worldedit.register_gui_function("worldedit_gui_transpose", {
name = "Transpose",
privs = we_privs("transpose"),
get_formspec = function(name)
local axis1, axis2 = gui_axis1[name], gui_axis2[name]
return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_transpose") ..
string.format("dropdown[0,1;2.5;worldedit_gui_transpose_axis1;X axis,Y axis,Z axis,Look direction;%d]", axis1) ..
string.format("dropdown[3,1;2.5;worldedit_gui_transpose_axis2;X axis,Y axis,Z axis,Look direction;%d]", axis2) ..
"button_exit[0,2.5;3,0.8;worldedit_gui_transpose_submit;Transpose]"
end,
})
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
copy_changes(name, fields, cg)
execute_worldedit_command("transpose", name,
axis_values[gui_axis1[name]] .. " " .. axis_values[gui_axis2[name]])
return true
end
return ret
end)
worldedit.register_gui_function("worldedit_gui_flip", {
name = "Flip",
privs = we_privs("flip"),
get_formspec = function(name)
local axis = gui_axis1[name]
return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_flip") ..
string.format("dropdown[0,1;2.5;worldedit_gui_flip_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
"button_exit[0,2.5;3,0.8;worldedit_gui_flip_submit;Flip]"
end,
})
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
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_flip")
execute_worldedit_command("flip", name, axis_values[gui_axis1[name]])
return true
end
return ret
end)
worldedit.register_gui_function("worldedit_gui_rotate", {
name = "Rotate",
privs = we_privs("rotate"),
get_formspec = function(name)
local axis, angle = gui_axis1[name], gui_angle[name]
return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_rotate") ..
string.format("dropdown[0,1;2.5;worldedit_gui_rotate_angle;90 degrees,180 degrees,270 degrees;%s]", angle) ..
string.format("dropdown[3,1;2.5;worldedit_gui_rotate_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
"button_exit[0,2.5;3,0.8;worldedit_gui_rotate_submit;Rotate]"
end,
})
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
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_rotate")
execute_worldedit_command("rotate", name,
axis_values[gui_axis1[name]] .. " " .. angle_values[gui_angle[name]])
return true
end
return ret
end)
worldedit.register_gui_function("worldedit_gui_orient", {
name = "Orient",
privs = we_privs("orient"),
get_formspec = function(name)
local angle = gui_angle[name]
return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_orient") ..
string.format("dropdown[0,1;2.5;worldedit_gui_orient_angle;90 degrees,180 degrees,270 degrees;%s]", angle) ..
"button_exit[0,2.5;3,0.8;worldedit_gui_orient_submit;Orient]"
end,
})
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
copy_changes(name, fields, cg)
worldedit.show_page(name, "worldedit_gui_orient")
execute_worldedit_command("orient", name,
tostring(angle_values[gui_angle[name]]))
return true
end
return ret
end)
worldedit.register_gui_function("worldedit_gui_fixlight", {
name = "Fix Lighting",
privs = we_privs("fixlight"),
on_select = function(name)
execute_worldedit_command("fixlight", name, "")
end,
})
worldedit.register_gui_function("worldedit_gui_hide", {
name = "Hide Region",
privs = we_privs("hide"),
on_select = function(name)
execute_worldedit_command("hide", name, "")
end,
})
worldedit.register_gui_function("worldedit_gui_suppress", {
name = "Suppress Nodes",
privs = we_privs("suppress"),
get_formspec = function(name)
local node = gui_nodename1[name]
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]"
end,
})
worldedit.register_gui_handler("worldedit_gui_suppress", function(name, fields)
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")
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
execute_worldedit_command("suppress", name, n)
end
return true
end
return ret
end)
worldedit.register_gui_function("worldedit_gui_highlight", {
name = "Highlight Nodes",
privs = we_privs("highlight"),
get_formspec = function(name)
local node = gui_nodename1[name]
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]"
end,
})
worldedit.register_gui_handler("worldedit_gui_highlight", function(name, fields)
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")
local n = worldedit.normalize_nodename(gui_nodename1[name])
if n then
execute_worldedit_command("highlight", name, n)
end
return true
end
return ret
end)
worldedit.register_gui_function("worldedit_gui_restore", {
name = "Restore Region",
privs = we_privs("restore"),
on_select = function(name)
execute_worldedit_command("restore", name, "")
end,
})
worldedit.register_gui_function("worldedit_gui_save_load", {
name = "Save/Load",
privs = combine_we_privs({"save", "allocate", "load"}),
get_formspec = function(name)
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]"
end,
})
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
execute_worldedit_command("save", name, gui_filename[name])
elseif fields.worldedit_gui_save_load_submit_allocate then
execute_worldedit_command("allocate", name, gui_filename[name])
else --fields.worldedit_gui_save_load_submit_load
execute_worldedit_command("load", name, gui_filename[name])
end
return true
end
return false
end)
worldedit.register_gui_function("worldedit_gui_cube", {
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]
local node = gui_nodename1[name]
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)
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 = "hollowcube"
if fields.worldedit_gui_cube_submit_solid then
submit = "cube"
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 ret
end)
worldedit.register_gui_function("worldedit_gui_clearobjects", {
name = "Clear Objects",
privs = we_privs("clearobjects"),
on_select = function(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)

282
worldedit_gui/init.lua Normal file
View File

@ -0,0 +1,282 @@
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,
})
Use `nil` for the `options` parameter to unregister the function associated with the given identifier.
Use `nil` for the `get_formspec` field to denote that the function does not have its own screen.
The `privs` field may not be `nil`.
If the identifier is already registered to another function, it will be replaced by the new one.
The `on_select` function must not call `worldedit.show_page`
]]
worldedit.pages = {} --mapping of identifiers to options
local identifiers = {} --ordered list of identifiers
worldedit.register_gui_function = function(identifier, options)
if options.privs == nil or next(options.privs) == nil then
error("privs unset")
end
worldedit.pages[identifier] = options
table.insert(identifiers, identifier)
end
--[[
Example:
worldedit.register_gui_handler("worldedit_gui_hollow_cylinder", function(name, 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 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()
--ensure the player has permission to perform the action
local entry = worldedit.pages[identifier]
if entry and minetest.check_player_privs(name, entry.privs) then
return handler(name, fields)
end
return false
end)
end
worldedit.get_formspec_header = function(identifier)
local entry = worldedit.pages[identifier] or {}
return "button[0,0;2,0.5;worldedit_gui;Back]" ..
string.format("label[2,0;WorldEdit GUI > %s]", entry.name or "")
end
local get_formspec = function(name, identifier)
if worldedit.pages[identifier] then
return worldedit.pages[identifier].get_formspec(name)
end
return worldedit.pages["worldedit_gui"].get_formspec(name) --default to showing main page if an unknown page is given
end
--implement worldedit.show_page(name, page) in different ways depending on the available APIs
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)
unified_inventory.register_page(identifier, {get_formspec=function(player) return {formspec=options.get_formspec(player:get_player_name())} end})
end
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)
local name = player:get_player_name()
if fields.worldedit_gui then --main page
worldedit.show_page(name, "worldedit_gui")
return true
elseif fields.worldedit_gui_exit then --return to original page
local player = minetest.get_player_by_name(name)
if player then
unified_inventory.set_inventory_formspec(player, "craft")
end
return true
end
return false
end)
worldedit.show_page = function(name, page)
local player = minetest.get_player_by_name(name)
if player then
player:set_inventory_formspec(get_formspec(name, page))
end
end
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
inventory_plus.register_button(player, "worldedit_gui", "WorldEdit")
end
end)
--show the form when the button is pressed and hide it when done
local gui_player_formspecs = {}
minetest.register_on_player_receive_fields(function(player, formname, fields)
local name = player:get_player_name()
if fields.worldedit_gui then --main page
gui_player_formspecs[name] = player:get_inventory_formspec()
worldedit.show_page(name, "worldedit_gui")
return true
elseif fields.worldedit_gui_exit then --return to original page
if gui_player_formspecs[name] then
inventory_plus.set_inventory_formspec(player, inventory_plus.get_formspec(player, "main"))
end
return true
end
return false
end)
worldedit.show_page = function(name, page)
local player = minetest.get_player_by_name(name)
if player then
inventory_plus.set_inventory_formspec(player, get_formspec(name, page))
end
end
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;]" ..
"tooltip[worldedit_gui;Edit your World!]" or "")
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
sfinv.set_page(player, "sfinv:crafting")
return true
end
return false
end)
worldedit.show_page = function(name, page)
local player = minetest.get_player_by_name(name)
if player then
player:set_inventory_formspec(get_formspec(name, page))
end
end
else
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
worldedit.register_gui_function("worldedit_gui", {
name = "WorldEdit GUI",
privs = {interact=true},
get_formspec = function(name)
--create a form with all the buttons arranged in a grid
local buttons, x, y, index = {}, 0, 1, 0
local width, height = 3, 0.8
local columns = 5
for i, identifier in pairs(identifiers) do
if identifier ~= "worldedit_gui" then
local entry = worldedit.pages[identifier]
table.insert(buttons, string.format((entry.get_formspec and "button" or "button_exit") ..
"[%g,%g;%g,%g;%s;%s]", x, y, width, height, identifier, minetest.formspec_escape(entry.name)))
index, x = index + 1, x + width
if index == columns then --row is full
x, y = 0, y + height
index = 0
end
end
end
if index == 0 then --empty row
y = y - height
end
return string.format("size[%g,%g]", math.max(columns * width, 5), math.max(y + 0.5, 3)) ..
"button[0,0;2,0.5;worldedit_gui_exit;Back]" ..
"label[2,0;WorldEdit GUI]" ..
table.concat(buttons)
end,
})
worldedit.register_gui_handler("worldedit_gui", function(name, fields)
for identifier, entry in pairs(worldedit.pages) do --check for WorldEdit GUI main formspec button selection
if fields[identifier] and identifier ~= "worldedit_gui" then
--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, ", ") .. ")", "error")
return false
end
if entry.on_select then
entry.on_select(name)
end
if entry.get_formspec then
worldedit.show_page(name, identifier)
end
return true
end
end
return false
end)
dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/functionality.lua")

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.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

View File

@ -0,0 +1,52 @@
--provides shorter names for the commands in `worldedit_commands`
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
minetest.log("error", "worldedit_shortcommands: alias " .. alias .. " already exists")
return
end
minetest.register_chatcommand("/" .. alias, minetest.chatcommands["/" .. original])
worldedit.registered_commands[alias] = worldedit.registered_commands[original]
end
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