185 Commits

Author SHA1 Message Date
d42ac33d62 fix 2021-09-12 20:02:56 +02:00
5b19c17117 Revert "Warn when "misaligned" schematics are created"
This reverts commit 68f7bcc72832ce753a3659f44dd26fbc4a5c066a.
2021-09-12 20:02:54 +02:00
4de0eb489f implement loading 2021-09-12 20:02:12 +02:00
adc6e00423 wip 2021-09-12 20:00:41 +02:00
5c06ae59ef wip 2021-09-12 20:00:41 +02:00
3c5f6b9665 wip 2021-09-12 20:00:41 +02:00
e387a57e15 wip 2021-09-12 20:00:41 +02:00
f43bc5278e wip 2021-09-12 20:00:41 +02:00
2f2f5a7def Use minetest.global_exists for LuaJIT check
closes #199
2021-09-12 19:35:57 +02:00
7f87f1658e Add param2 button to WorldEdit GUI 2021-07-23 23:34:13 +02:00
4378750498 Use minetest.get_objects_in_area when possible 2021-04-30 19:33:27 +02:00
2f26fb7645 WorldEdit 1.3 2021-01-31 00:49:58 +01:00
f1b6da3c20 Hide gui button in unified_inventory on missing privilege (#195)
this fixes #151
2020-12-05 14:12:32 +01:00
418a30c89e Raise safe region limit to 20000 2020-10-06 13:53:22 +02:00
2f535dd053 worldedit: Document inner working of worldedit.keep_loaded 2020-05-25 19:48:54 +02:00
867cd6edc7 Improve node name normalization again
so that "desert stone" won't select "desert stone block"
2020-05-12 21:53:01 +02:00
4918610c43 Fix rotate in worldedit GUI 2020-05-12 21:29:51 +02:00
edd27636a6 Do not allow //mix with no nodes 2020-05-09 22:34:29 +02:00
7d0811fd47 Update help texts to be consistent 2020-04-26 16:53:55 +02:00
4d08a1cc55 Implement //cubeapply with three side lengths 2020-04-26 16:01:07 +02:00
34844b2d38 Set static_save = false for marker entities 2020-04-16 23:50:15 +02:00
0f5dcc790d Implement //clearcut to delete trees, plants from generated terrain
closes #165
2020-04-07 03:05:30 +02:00
37de177f72 Fix off-by-one in //cubeapply 2020-04-07 00:46:13 +02:00
f285a55dd5 Improve brush command error messages 2020-04-07 00:38:11 +02:00
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
963a9f6b96 Implement //cubeapply 2020-04-06 23:32:11 +02:00
5e8bfd5166 Improve node name normalization in case of multi-line descriptions 2020-03-29 21:55:47 +02:00
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
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
da51c688d2 Call minetest.deserialize with safe=true
wtf?
2020-02-06 11:53:14 +01:00
ab93871877 Move some deserialization code around 2020-02-06 11:51:44 +01:00
116ae25ebd Unbreak //move (closes #188) 2019-12-20 20:29:41 +01:00
18444379a1 worldedit_gui: Improve behaviour of all input fields
continuation of 45acf09e4a3ea7ed563d2d43cc63425b04fc63ef
2019-12-19 18:20:24 +01:00
974dcaa918 Improve nodename normalization in some cases 2019-12-19 16:58:33 +01:00
abdecd4239 Fix some broken commands
This happens when you don't test.
2019-12-19 16:45:38 +01:00
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
7833b68c85 worldedit_brush: Remove "safe region" hack 2019-12-19 15:58:26 +01:00
03bef18272 Update worldedit_shortcommands for new changes 2019-12-19 15:16:20 +01:00
91d02f6f5b Rewrite command registration 2019-12-19 15:13:55 +01:00
8feaf8a21d Fix area clearing step of negative //move's
fixes #187
2019-11-13 20:49:25 +01:00
5bab991e0b Make object right-click work with wand 2019-11-10 00:40:31 +01:00
9bb0ad0442 Fix double-definition of visual_size in entity def 2019-10-14 12:46:58 +02:00
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
79097dc6c8 Reset region by double-punching air using the wand 2019-09-18 23:56:21 +02:00
3bc7a0f97d worldedit_gui: Add tooltips to node search result 2019-09-18 18:55:55 +02:00
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
adab528f8a Fix nodename normalization with translated descriptions 2019-09-18 18:48:31 +02:00
26b6682587 Update API docs 2019-09-17 18:44:13 +02:00
b4826aa821 Use faster vmanip copying for //move too 2019-09-17 18:42:11 +02:00
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
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
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
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
0919f7cab3 Clean up command code for //stack, //stack2 2019-09-08 22:14:07 +02:00
de566d2e3e Update API docs 2019-09-08 22:14:07 +02:00
cc897150f2 Fix performance of //stack, //stack2 2019-09-08 22:14:07 +02:00
f39a8e264d Faster moving using vmanips (in some cases) 2019-09-08 22:14:07 +02:00
da5abec273 Faster copying using vmanips 2019-09-08 22:14:07 +02:00
ff53a71d58 Remove outdated function check 2019-09-08 18:57:54 +02:00
0edcd1a59d Fix crash on loading invalid schematic (fixes #160) 2019-09-06 17:56:11 +02:00
a1b6a34e61 Use eye_height property for brush raytracing 2019-09-06 17:45:51 +02:00
f589c988b1 Handle invalid //rotate usage gracefully, fixes #150 2019-07-17 02:35:57 +02:00
f28d9b8d35 Update README 2019-07-17 02:23:39 +02:00
b8453a0e0b Remove deprecated depends.txt, add descriptions to mod.conf 2019-07-17 02:15:04 +02:00
0a16f11d93 Replace more deprecated functions 2019-07-17 02:06:29 +02:00
b50294d10c Adjust GUI compatibility code 2019-07-17 01:44:15 +02:00
324eb556f6 Make compatibility error in worldedit_brush fatal again
This reverts commit a97cccd2a1.
2019-07-17 01:42:28 +02:00
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
40b49ee9bc Make placeholders not pointable 2019-07-17 01:22:12 +02:00
4f2c7b18cc Fix //allocate with 0 nodes 2019-07-17 01:20:40 +02:00
b2e086f9ec Fix //load with 0 nodes (#177) 2019-06-15 16:46:12 +02:00
d1cbd420bb serialize: Fix detecting empty metadata (#176) 2019-04-24 22:51:18 +02:00
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
f242f2f722 Change description for //about, fix #170 2018-12-08 21:52:36 +01:00
f8e57559ec replace deprecated function call 2018-11-25 13:46:27 +01:00
b2b2b9364d Warn about invalid //stretch usage 2018-11-15 20:49:19 +01:00
b37605943b //mix: Add node weighting support 2018-10-29 00:36:33 +01:00
f7256633c0 Wand: select node under or above depending on pressed sneak key. 2018-08-06 17:43:08 +02:00
99fa0ebd75 Document //stretch, remove old documentation for //scale
scale was replaced with stretch in 674d647
2018-07-26 11:52:10 +02:00
a97cccd2a1 Disable worldedit_brush mod instead of throwing error (#156) 2018-04-10 09:25:08 +02:00
3322ef90c4 Fix negative height pyramids (fixes #152) 2018-01-04 01:03:51 +01:00
b259906fd0 Add support for smart_inventory 2017-11-13 09:58:42 +01:00
12270dc094 Correct spelling of Minetest 2017-11-07 14:20:50 +01:00
415000e797 Fix a few assignments to undeclared globals 2017-11-07 14:19:57 +01:00
7b1deb1b61 Add configurable brush item
texture was supposed to be a brush, i suck at this :(
2017-09-17 23:47:29 +02:00
4605596215 Fix assignment to undeclared global "sign" @ worldedit_commands/init.lua:791 2017-09-12 14:58:26 +02:00
59dc2d34d9 WorldEdit 1.2 2017-09-12 14:56:01 +02:00
283f47f10d Fix invalid node error message of //set and //mix 2017-09-12 14:56:01 +02:00
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
d572d769ce Optimize gui icon texture 2017-09-12 13:30:11 +02:00
5bbb6b3909 Support //cube in the GUI 2017-09-12 13:25:33 +02:00
d6a9b517b5 Remove Lua functions from WorldEdit GUI 2017-09-12 13:04:25 +02:00
3c61759bae Allow to bulk-set param2 of regions (#144) 2017-09-05 14:40:46 +02:00
2f4eb19a3a Fix one-node high cylinders
closes #146
2017-09-03 20:12:38 +02:00
2cb3fcde66 Improve node name normalization 2017-08-31 20:38:38 +02:00
e680d8087e Fix small logic error in worldedit.cylinder 2017-08-31 20:10:08 +02:00
870873ad15 Add //hollowcube and //cube
closes #143
2017-08-31 19:08:10 +02:00
e356f4521c WorldEdit GUI support for new //cylinder functionality 2017-08-31 16:46:57 +02:00
bf8e2a8233 Dual-based cylinder 2017-08-31 16:25:22 +02:00
6ceb56c3dc Disable worldedit_gui fallback code (closes #141) 2017-08-20 12:21:41 +02:00
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
acc9188828 Add logging for //lua and //luatransform 2017-05-21 17:31:59 +02:00
3240167b13 Forgot this one during refactor (a31f955fb1) 2017-05-13 11:53:52 +02:00
66b1fa032d Remove formspec tester from worldedit_gui 2017-05-13 11:52:45 +02:00
a31f955fb1 Refactor priv determination in worldedit_gui 2017-05-13 11:51:36 +02:00
38e9b42caf Do not allow any worldedit_gui commands without privs 2017-05-13 11:51:36 +02:00
ea465f8fe4 Remove useless privilege checks
This is covered by the privs = {...},
2017-05-13 11:51:36 +02:00
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
56f77a2f27 Remove unused toolcaps of the wand tool 2017-03-11 12:50:27 +01:00
610cd9981d Use sfinv if possible, fixes #124 with recent minetest versions 2017-03-11 12:47:30 +01:00
c0f3bb6958 GUI: Use nicer picture to indicate unknown nodes (-> no search results) 2017-03-11 12:24:47 +01:00
e49f717000 GUI: Always pass normalized node names to commands 2017-03-11 12:03:06 +01:00
6e7b9a60be Update wand definition (#129)
Fixes "maxwear" deprecated warning
2017-02-11 00:04:04 +01:00
bcac45a476 Fix "safe region" functionality errornously rejecting pos1-only commands (e.g. //sphere) 2017-01-31 19:52:54 +01:00
426f3b949f Show light level a node receives with //inspect, fixes #128 2017-01-26 22:27:30 +01:00
78e4ba828e Show light level with //inspect 2017-01-03 17:11:38 +01:00
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
3a7fb5bf1e Add //help command (fixes #123) 2017-01-01 22:27:22 +01:00
aa0e46d4e2 Handle failure of minetest.add_entity()
(see minetest/minetest#4923)
2016-12-21 14:26:24 +01:00
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
f9311b2b15 Move wand.lua to worldedit_commands 2016-12-10 21:54:51 +01:00
15f0cea72e Disable wand tool on entities 2016-12-10 21:39:44 +01:00
6e2e2385e9 Merge remote-tracking branch 'tmp/hollowpyramid' 2016-10-13 17:51:01 +02:00
152707a322 Fix 'Run Lua' button in WorldEdit GUI (fixes #121) 2016-10-12 20:10:36 +02:00
d040d324e8 Fix //spiral requiring a selected region despite using only pos1 2016-10-12 19:45:38 +02:00
df6b54d2f5 Fix blowing up TNT near worldedit markers 2016-10-12 19:38:41 +02:00
5abe1cee3d Fix exiting mtschemprob entry dialog with ESC 2016-08-05 16:41:37 +02:00
5afea424ba Move cuboidapi.lua to correct place 2016-07-04 19:57:48 +02:00
2aed498849 Add completion chat message to //expand and //contract 2016-07-04 19:53:12 +02:00
0b68b2aec6 Fix //shift with absolute axis (x/y/z) 2016-07-04 19:51:28 +02:00
f72abdb766 Merge remote-tracking branch 'tmp/cuboidmanip' 2016-07-04 19:49:23 +02:00
e18525d8c9 Modify outset and inset to accept both hv and vh combinations. Update documentation accordingly. 2016-07-03 21:44:02 -05:00
78890dde07 Clean some documentation issues 2016-07-03 21:44:02 -05:00
b922097e7a Update command documentation 2016-07-03 21:44:02 -05:00
48f3f59fc3 Implement some safety mechanisms 2016-07-03 21:44:02 -05:00
f8f1f3b9cc Reimplement /expand and /contract to conform to WE standards 2016-07-03 21:44:02 -05:00
d5e004be67 Fix /outset and /inset to conform to WE standards 2016-07-03 21:44:02 -05:00
fad021d0ba Remove some debug code 2016-07-03 21:44:02 -05:00
f1fe0c6bba Fix /shift command to conform to worldedit command standard 2016-07-03 21:44:02 -05:00
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
04d20de4cd Fix several typos 2016-07-03 21:44:02 -05:00
859c6bd12a Implement /expand and /contract 2016-07-03 21:44:02 -05:00
240380ff16 Implement /inset and /shift 2016-07-03 21:44:02 -05:00
8d213d32a0 refactor code for mantainability and reusability. Start of chat command refactor. Implement /outset. 2016-07-03 21:44:02 -05:00
ae29a9f064 Reimplement inset/outset to make use of new marker functions 2016-07-03 21:44:02 -05:00
6cf8b92434 Reimplement /shift to make use of new marker functions 2016-07-03 21:44:02 -05:00
0b97a7c740 Finish /expand implementation. Add /contract command. 2016-07-03 21:44:02 -05:00
900b2f25aa Fix faulty regex in expand. Add relative direction decoding. 2016-07-03 21:44:02 -05:00
f49663902c Major progress and simplification in /expand function 2016-07-03 21:44:02 -05:00
d475682d8f Add common functions to move and update the markers 2016-07-03 21:44:02 -05:00
9e087ff995 Add outset, inset, shift and initial draft of expand 2016-07-03 21:44:02 -05:00
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
61ab240cea Add //drain 2016-03-23 22:09:55 +01:00
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
6dddc93ed7 Fix unability to browse inv. items on creative inventory 2016-02-22 12:29:06 +01:00
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
91c5053e67 Re-adapt WorldEdit's GUI button position to creative inventory 2016-02-22 10:29:53 +01:00
f2f714c19e Fix crash when loading version 3 schematics 2016-01-23 16:57:27 -05:00
47712844a3 Added a WorldEdit wand item that can be used to select areas with worldedit. 2016-01-22 22:24:17 +01:00
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
e9e8de385c Adapt WorldEdit GUI button to the new creative inventory 2016-01-17 15:26:47 +01:00
2e2fcfdfa2 Fixed Issue #83 : upsidedown pyramid not working well 2016-01-05 14:11:04 +01:00
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
35b68c481b Fixed dropdown menus in the WorldEdit GUI (closes #78, thanks to @sponce) 2016-01-05 13:14:47 +01:00
51158eca9f Fix Save/Load from WorldEdit GUI, fixes #90 2016-01-05 12:42:01 +01:00
3aa315f134 Fix Orient in WorldEdit GUI 2016-01-05 12:24:06 +01:00
3d30588a68 Clarify //deleteblocks 2015-12-18 17:33:29 -05:00
48f9c6c23f Fix #93 (thanks @za267!) 2015-11-01 16:50:36 -05:00
5f9efb1205 Added hollow pyramids 2015-09-13 22:08:04 +02:00
fc037e9c82 Fix formspec typo causing crashing upon using /orient in the GUI. 2015-06-18 18:14:45 -04:00
90d6b3d237 Allow more characters in file names 2015-06-01 17:08:43 -04:00
4bd5d56909 Localize mkdir helper 2015-06-01 16:53:03 -04:00
163dffccb3 Fix leaking {safe,check}_region 2015-05-16 19:52:48 -04:00
6b2fe397e6 Use minetest.mkdir when available 2015-05-16 19:46:33 -04:00
5c115e282c Fix existence check trying to open files for writing 2015-05-16 19:27:27 -04:00
ab47385f7b Fix crash (worldedit/manipulations.lua:526: attempt to call global 'set_node' (a nil value)) 2015-05-04 18:48:27 +02:00
78915d4a54 Merge pull request #76 from est31/fixserialisation
Load first node too with LuaJIT
2015-03-11 22:08:32 -04:00
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
c1bd4986b0 Merge pull request #74 from est31/deleteblocks
Add //deleteblocks command
2015-02-21 00:42:05 -05:00
bea38a116a Add //deleteblocks command 2015-02-20 22:30:34 +01:00
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
eca54f0851 Make inventory++ hide button for players without worldedit priv 2015-02-16 23:05:18 +01:00
b0fbcf197f Merge pull request #72 from HybridDog/globalfix
fix an error message
2015-02-14 23:25:20 -05:00
b468e24a20 fix an error message 2015-02-14 23:10:21 +01:00
38 changed files with 3433 additions and 1490 deletions

View File

@ -17,32 +17,38 @@ Many commands also have shorter names that can be typed faster. For example, if
| `//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 mod.
Get information about the WorldEdit mod.
//about
### `//inspect on/off/1/0/true/false/yes/no/enable/disable/<blank>`
### `//help [all/<cmd>]`
Get help for WorldEdit commands. `all` shows all WorldEdit commands, `<cmd>`
the help text for the given command.
//help
//help all
//help hollowpyramid
### `//inspect [on/off/1/0/true/false/yes/no/enable/disable]`
Enable or disable node inspection.
//inspect on
//inspect off
//inspect 1
//inspect 0
//inspect true
//inspect false
//inspect yes
//inspect no
//inspect enable
//inspect disable
//inspect
### `//reset`
@ -77,18 +83,19 @@ Set WorldEdit region position 2 to the player's location.
### `//p set/set1/set2/get`
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region.
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by
punching nodes, or print the current WorldEdit region.
//p set
//p set1
//p set2
//p get
### `//fixedpos set1 x y z`
### `//fixedpos set1/set2 <x> <y> <z>`
Set a WorldEdit region position to the position at (`<x>`, `<y>`, `<z>`).
Set the WorldEdit region position 1 or 2 to the position (`<x>`, `<y>`, `<z>`).
//fixedpos set1 0 0 0
//fixedpos set1 0 0 0
//fixedpos set1 -30 5 28
//fixedpos set2 1004 -200 432
@ -98,6 +105,12 @@ 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>`.
@ -107,14 +120,23 @@ Set the current WorldEdit region to `<node>`.
//set Blue Lightstone
//set dirt with grass
### `//mix <node1> ...`
### `//param2 <param2>`
Fill the current WorldEdit region with a random mix of `<node1>`, `...`.
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>`
@ -134,6 +156,21 @@ Replace all nodes other than `<search node>` with `<replace node>` in the curren
//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>`.
@ -166,27 +203,50 @@ Add dome centered at WorldEdit position 1 with radius `<radius>`, composed of `<
//dome -12 glass
//dome 17 mesecons:wire_00000000_off
### `//hollowcylinder x/y/z/? <length> <radius> <node>`
### `//hollowcylinder x/y/z/? <length> <radius1> [radius2] <node>`
Add hollow cylinder at WorldEdit position 1 along the x/y/z/? axis with length `<length>` and 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>`.
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
### `//cylinder x/y/z/? <length> <radius> <node>`
//hollowcylinder y 10 10 0 walls:cobble
//hollowcylinder x 6 0 5 Dirt
//hollowcylinder z 20 10 20 default:desert_stone
Add cylinder at WorldEdit position 1 along the x/y/z/? axis with length `<length>` and radius `<radius>`, composed of `<node>`.
### `//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
### `//pyramid x/y/z? <height> <node>`
Add pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height `<height>`, composed of `<node>`.
//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
@ -195,7 +255,8 @@ Add pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height
### `//spiral <length> <height> <spacer> <node>`
Add spiral centered at WorldEdit position 1 with side length `<length>`, height `<height>`, space between walls `<spacer>`, composed of `<node>`.
Add spiral centered at WorldEdit position 1 with side length `<length>`,
height `<height>`, space between walls `<spacer>`, composed of `<node>`.
//spiral 20 5 3 Diamond Block
//spiral 5 2 1 glass
@ -203,7 +264,7 @@ Add spiral centered at WorldEdit position 1 with side length `<length>`, height
### `//copy x/y/z/? <amount>`
Copy the current WorldEdit region along the x/y/z/? axis by `<amount>` nodes.
Copy the current WorldEdit region along the given axis by `<amount>` nodes.
//copy x 15
//copy y -7
@ -212,7 +273,7 @@ Copy the current WorldEdit region along the x/y/z/? axis by `<amount>` nodes.
### `//move x/y/z/? <amount>`
Move the current WorldEdit positions and region along the x/y/z/? axis by `<amount>` nodes.
Move the current WorldEdit positions and region along the given axis by `<amount>` nodes.
//move x 15
//move y -7
@ -221,7 +282,7 @@ Move the current WorldEdit positions and region along the x/y/z/? axis by `<amou
### `//stack x/y/z/? <count>`
Stack the current WorldEdit region along the x/y/z/? axis `<count>` times.
Stack the current WorldEdit region along the given axis `<count>` times.
//stack x 3
//stack y -1
@ -235,35 +296,34 @@ Stack the current WorldEdit region `<count>` times by offset `<x>`, `<y>`, `<z>`
//stack2 5 3 8 2
//stack2 1 -1 -1 -1
### `//scale <factor>`
### `//stretch <stretchx> <stretchy> <stretchz>`
Scale the current WorldEdit positions and region by a factor of positive integer `<factor>` with position 1 as the origin.
Scale the current WorldEdit positions and region by a factor of
`<stretchx>`, `<stretchy>`, `<stretchz>` along the X, Y, and Z axes,
respectively, with position 1 as the origin.
//scale 2
//scale 1
//scale 10
//stretch 2 2 2
//stretch 1 2 1
//stretch 10 20 1
### `//transpose x/y/z/? x/y/z/?`
Transpose the current WorldEdit positions and region along the x/y/z/? and x/y/z/? axes.
Transpose the current WorldEdit positions and region along given axes.
//transpose x y
//transpose x z
//transpose y z
//transpose ? y
### `//flip x/y/z/?`
Flip the current WorldEdit region along the x/y/z/? axis.
Flip the current WorldEdit region along the given axis.
//flip x
//flip y
//flip z
//flip ?
### `//rotate x/y/z/? <angle>`
Rotate the current WorldEdit positions and region along the x/y/z/? axis by angle `<angle>` (90 degree increment).
Rotate the current WorldEdit positions and region along the given axis by angle `<angle>` (90 degree increment).
//rotate x 90
//rotate y 180
@ -285,6 +345,19 @@ 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.
@ -293,7 +366,7 @@ Hide all nodes in the current WorldEdit region non-destructively.
### `//suppress <node>`
Suppress all <node> in the current WorldEdit region non-destructively.
Suppress all `<node>` in the current WorldEdit region non-destructively.
//suppress Diamond Block
//suppress glass
@ -301,7 +374,7 @@ Suppress all <node> in the current WorldEdit region non-destructively.
### `//highlight <node>`
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively.
Highlight `<node>` in the current WorldEdit region by hiding everything else non-destructively.
//highlight Diamond Block
//highlight glass
@ -345,8 +418,8 @@ Executes `<code>` as a Lua chunk in the global namespace.
Executes `<code>` as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region.
//luatransform minetest.add_node(pos, {name="default:stone"})
//luatransform if minetest.get_node(pos).name == "air" then minetest.add_node(pos, {name="default:water_source"})
//luatransform minetest.swap_node(pos, {name="default:stone"})
//luatransform if minetest.get_node(pos).name == "air" then minetest.add_node(pos, {name="default:water_source"}) end
### `//mtschemcreate <file>`
@ -374,3 +447,62 @@ This mode can be left with `//mtschemprob finish`. `//mtschemprob get` will disp
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

108
README.md
View File

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

View File

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

View File

@ -24,6 +24,13 @@ Contained in manipulations.lua, this module allows several node operations to be
### count = worldedit.set(pos1, pos2, node_name)
Sets a region defined by positions `pos1` and `pos2` to `node_name`. To clear a region, use "air" as the value of `node_name`.
If `node_name` is a list of nodes, each set node is randomly picked from it.
Returns the number of nodes set.
### `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.
@ -45,6 +52,13 @@ Copies the region defined by positions `pos1` and `pos2` along the `axis` axis (
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.
@ -60,6 +74,7 @@ Returns the number of nodes stacked.
### count = worldedit.stack2(pos1, pos2, direction, amount)
Duplicates the region defined by positions `pos1` and `pos2` `amount` times with offset vector `direction`.
Note that the offset vector needs to be big enough that there is no overlap.
Returns the number of nodes stacked.
@ -109,6 +124,12 @@ 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`.
@ -121,15 +142,15 @@ 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, radius, node_name, hollow)
### count = worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, hollow)
Adds a cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `node_name`.
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)
### 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`.
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.
@ -175,7 +196,7 @@ Returns the number of nodes restored.
Serialization
-------------
Contained in serialization.lua, this module allows regions of nodes to be serialized and deserialized to formats suitable for use outside MineTest.
Contained in serialization.lua, this module allows regions of nodes to be serialized and deserialized to formats suitable for use outside Minetest.
### version, extra_fields, content = worldedit.read_header(value)

2
modpack.conf Normal file
View File

@ -0,0 +1,2 @@
name = Minetest-WorldEdit
description = WorldEdit is an in-game world editor. Use it to repair griefing, or just create awesome buildings in seconds.

View File

View File

@ -46,6 +46,9 @@ 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.
-- This doesn't actually *keep* them loaded, unlike the name implies.
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
end
@ -107,7 +110,9 @@ end
function mh.finish(manip, data)
-- Update map
manip:set_data(data)
if data ~= nil then
manip:set_data(data)
end
manip:write_to_map()
manip:update_map()
end

243
worldedit/cuboid.lua Normal file
View File

@ -0,0 +1,243 @@
-- 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
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 playerpos = minetest.get_player_by_name(name):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

View File

@ -1,29 +1,24 @@
--- Worldedit.
--- WorldEdit mod for the Minetest engine
-- @module worldedit
-- @release 1.1
-- @copyright 2013 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote).
-- @release 1.3
-- @copyright 2012 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote)
-- @license GNU Affero General Public License version 3 (AGPLv3)
-- @author sfan5
-- @author Anthony Zang (Uberi/Temperest)
-- @author Bret O'Donnel (cornernote)
-- @author ShadowNinja
worldedit = {}
worldedit.version = {1, 1, major=1, minor=1}
worldedit.version_string = table.concat(worldedit.version, ".")
if not minetest.get_voxel_manip then
local err_msg = "This version of WorldEdit requires Minetest 0.4.8 or later! You have an old version."
minetest.log("error", string.rep("#", 128))
minetest.log("error", err_msg)
minetest.log("error", string.rep("#", 128))
error(err_msg)
end
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)
local file = io.open(path, "r")
if not file then return end
file:close()
return dofile(path)
@ -36,9 +31,10 @@ 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.setting_getbool("log_mods") then
if minetest.settings:get_bool("log_mods") then
print("[WorldEdit] Loaded!")
end

View File

@ -38,6 +38,29 @@ function worldedit.set(pos1, pos2, node_names)
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`.
@ -53,9 +76,6 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
local count = 0
--- TODO: This could be shortened by checking `inverse` in the loop,
-- but that would have a speed penalty. Is the penalty big enough
-- to matter?
if not inverse then
for i in area:iterp(pos1, pos2) do
if data[i] == search_id then
@ -78,27 +98,47 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
end
local function deferred_execution(next_one, finished)
-- Allocate 100% of server step for execution (might lag a little)
local allocated_usecs =
tonumber(minetest.settings:get("dedicated_server_step")) * 1000000
local function f()
local deadline = minetest.get_us_time() + allocated_usecs
repeat
local is_done = next_one()
if is_done then
if finished then
finished()
end
return
end
until minetest.get_us_time() >= deadline
minetest.after(0, f)
end
f()
end
--- Duplicates a region `amount` times with offset vector `direction`.
-- Stacking is spread across server steps, one copy per step.
-- Stacking is spread across server steps.
-- @return The number of nodes stacked.
function worldedit.stack2(pos1, pos2, direction, amount, finished)
-- Protect arguments from external changes during execution
pos1 = table.copy(pos1)
pos2 = table.copy(pos2)
direction = table.copy(direction)
local i = 0
local translated = {x=0, y=0, z=0}
local function next_one()
if i < amount then
i = i + 1
translated.x = translated.x + direction.x
translated.y = translated.y + direction.y
translated.z = translated.z + direction.z
worldedit.copy2(pos1, pos2, translated, volume)
minetest.after(0, next_one)
else
if finished then
finished()
end
end
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
next_one()
deferred_execution(step, finished)
return worldedit.volume(pos1, pos2) * amount
end
@ -112,149 +152,187 @@ end
function worldedit.copy(pos1, pos2, axis, amount)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
-- Decide if we need to copy stuff backwards (only applies to metadata)
local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1)
local get_node, get_meta, set_node = minetest.get_node,
minetest.get_meta, minetest.set_node
-- Copy things backwards when negative to avoid corruption.
-- FIXME: Lots of code duplication here.
if amount < 0 then
local pos = {}
pos.x = pos1.x
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get meta of current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Copy node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z + 1
local off = {x=0, y=0, z=0}
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 = {x=1, y=src_area.ystride, z=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 = {x=1, y=dst_area.ystride, z=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
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
else
local pos = {}
pos.x = pos2.x
while pos.x >= pos1.x do
pos.y = pos2.y
while pos.y >= pos1.y do
pos.z = pos2.z
while pos.z >= pos1.z do
local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get meta of current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Copy node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
end
-- 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)
src_data = nil
dst_data = nil
-- 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 = {x=pos1.x+x, y=pos1.y+y, z=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 = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}
local meta = get_meta(pos):to_table()
pos = vector.add(pos, off)
get_meta(pos):from_table(meta)
end
end
end
end
return worldedit.volume(pos1, pos2)
end
--- Deletes all node metadata in the region
-- @param pos1
-- @param pos2
-- @return The number of nodes that had their meta deleted.
function worldedit.delete_meta(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)
local get_meta = minetest.get_meta
for _, pos in ipairs(meta_positions) do
get_meta(pos):from_table(nil)
end
return #meta_positions
end
--- Moves a region along `axis` by `amount` nodes.
-- @return The number of nodes moved.
function worldedit.move(pos1, pos2, axis, amount)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local dim = vector.add(vector.subtract(pos2, pos1), 1)
local overlap = math.abs(amount) < dim[axis]
-- Decide if we need to copy metadata backwards
local backwards = overlap and amount > 0
--- TODO: Move slice by slice using schematic method in the move axis
-- and transfer metadata in separate loop (and if the amount is
-- greater than the length in the axis, copy whole thing at a time and
-- erase original after, using schematic method).
local get_node, get_meta, set_node, remove_node = minetest.get_node,
minetest.get_meta, minetest.set_node, minetest.remove_node
-- Copy things backwards when negative to avoid corruption.
--- FIXME: Lots of code duplication here.
if amount < 0 then
local pos = {}
pos.x = pos1.x
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get metadata of current node
remove_node(pos) -- Remove current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Move node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
local function nuke_area(my_off, my_dim)
if my_dim.x == 0 or my_dim.y == 0 or my_dim.z == 0 then
return
end
local my_pos1 = vector.add(pos1, my_off)
local my_pos2 = vector.subtract(vector.add(my_pos1, my_dim), 1)
worldedit.set(my_pos1, my_pos2, "air")
worldedit.delete_meta(my_pos1, my_pos2)
end
-- Copy stuff to new location
local off = {x=0, y=0, z=0}
off[axis] = amount
worldedit.copy2(pos1, pos2, off, backwards)
-- Nuke old area
if not overlap then
nuke_area({x=0, y=0, z=0}, dim)
else
local pos = {}
pos.x = pos2.x
while pos.x >= pos1.x do
pos.y = pos2.y
while pos.y >= pos1.y do
pos.z = pos2.z
while pos.z >= pos1.z do
local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get metadata of current node
remove_node(pos) -- Remove current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Move node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
-- Source and destination region are overlapping, which means we can't
-- blindly delete the [pos1, pos2] area
local leftover = vector.new(dim) -- size of the leftover slice
leftover[axis] = math.abs(amount)
if amount > 0 then
nuke_area({x=0, y=0, z=0}, leftover)
else
local top = {x=0, y=0, z=0} -- offset of the leftover slice from pos1
top[axis] = dim[axis] - math.abs(amount)
nuke_area(top, leftover)
end
end
return worldedit.volume(pos1, pos2)
end
--- Duplicates a region along `axis` `amount` times.
-- Stacking is spread across server steps, one copy per step.
-- 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)
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 amount = 0
local copy = worldedit.copy
local i = 1
function next_one()
if i <= count then
i = i + 1
amount = amount + length
copy(pos1, pos2, axis, amount)
minetest.after(0, next_one)
end
local i, distance = 0, 0
local function step()
distance = distance + length
worldedit.copy(pos1, pos2, axis, distance)
i = i + 1
return i >= count
end
next_one()
deferred_execution(step, finished)
return worldedit.volume(pos1, pos2) * count
end
@ -474,20 +552,22 @@ end
-- @param pos2
-- @param angle Angle in degrees (90 degree increments only).
-- @return The number of nodes oriented.
-- TODO: Support 6D facedir rotation along arbitrary axis.
function worldedit.orient(pos1, pos2, angle)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local registered_nodes = minetest.registered_nodes
local wallmounted = {
[90] = {[0]=0, 1, 5, 4, 2, 3},
[180] = {[0]=0, 1, 3, 2, 5, 4},
[270] = {[0]=0, 1, 4, 5, 3, 2}
[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] = {[0]=1, 2, 3, 0},
[180] = {[0]=2, 3, 0, 1},
[270] = {[0]=3, 0, 1, 2}
[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
@ -503,8 +583,7 @@ function worldedit.orient(pos1, pos2, angle)
worldedit.keep_loaded(pos1, pos2)
local count = 0
local get_node, get_meta, swap_node = minetest.get_node,
minetest.get_meta, minetest.swap_node
local get_node, swap_node = minetest.get_node, minetest.swap_node
local pos = {x=pos1.x, y=0, z=0}
while pos.x <= pos2.x do
pos.y = pos1.y
@ -514,17 +593,20 @@ function worldedit.orient(pos1, pos2, angle)
local node = get_node(pos)
local def = registered_nodes[node.name]
if def then
if def.paramtype2 == "wallmounted" then
node.param2 = wallmounted_substitution[node.param2]
local meta = get_meta(pos):to_table()
set_node(pos, node)
get_meta(pos):from_table(meta)
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 def.paramtype2 == "facedir" then
node.param2 = facedir_substitution[node.param2]
local meta = get_meta(pos):to_table()
set_node(pos, node)
get_meta(pos):from_table(meta)
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
@ -543,14 +625,11 @@ end
function worldedit.fixlight(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local vmanip = minetest.get_voxel_manip(pos1, pos2)
vmanip:write_to_map()
vmanip:update_map() -- this updates the lighting
local nodes = minetest.find_nodes_in_area(pos1, pos2, "air")
local dig_node = minetest.dig_node
for _, pos in ipairs(nodes) do
dig_node(pos)
end
return #nodes
return worldedit.volume(pos1, pos2)
end
@ -561,10 +640,34 @@ function worldedit.clear_objects(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 or not entity.name:find("^worldedit:")
end
-- Offset positions to include full nodes (positions are in the center of nodes)
local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5
local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5
local count = 0
if minetest.get_objects_in_area then
local objects = minetest.get_objects_in_area({x=pos1x, y=pos1y, z=pos1z},
{x=pos2x, y=pos2y, z=pos2z})
for _, obj in pairs(objects) do
if should_delete(obj) then
obj:remove()
count = count + 1
end
end
return count
end
-- Fallback implementation via get_objects_inside_radius
-- Center of region
local center = {
x = pos1x + ((pos2x - pos1x) / 2),
@ -576,13 +679,9 @@ function worldedit.clear_objects(pos1, pos2)
(center.x - pos1x) ^ 2 +
(center.y - pos1y) ^ 2 +
(center.z - pos1z) ^ 2)
local count = 0
for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do
local entity = obj:get_luaentity()
-- Avoid players and WorldEdit entities
if not obj:is_player() and (not entity or
not entity.name:find("^worldedit:")) then
local pos = obj:getpos()
if should_delete(obj) then
local pos = obj:get_pos()
if pos.x >= pos1x and pos.x <= pos2x and
pos.y >= pos1y and pos.y <= pos2y and
pos.z >= pos1z and pos.z <= pos2z then

2
worldedit/mod.conf Normal file
View File

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

View File

@ -4,6 +4,47 @@
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 manip, area = mh.init(basepos, vector.add(basepos, {x=width, y=height, z=length}))
local data = mh.get_empty_data(area)
-- Add cube
local node_id = minetest.get_content_id(node_name)
local stride = {x=1, y=area.ystride, z=area.zstride}
local offset = vector.subtract(basepos, area.MinEdge)
local count = 0
for z = 0, length-1 do
local index_z = (offset.z + z) * stride.z + 1 -- +1 for 1-based indexing
for y = 0, height-1 do
local index_y = index_z + (offset.y + y) * stride.y
for x = 0, width-1 do
local is_wall = z == 0 or z == length-1
or y == 0 or y == height-1
or x == 0 or x == width-1
if not hollow or is_wall then
local i = index_y + (offset.x + x)
data[i] = node_id
count = count + 1
end
end
end
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.
@ -92,49 +133,60 @@ end
-- @param pos Position to center base of cylinder at.
-- @param axis Axis ("x", "y", or "z")
-- @param length Cylinder length.
-- @param radius Cylinder radius.
-- @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, radius, node_name, hollow)
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 = {x=pos.x, y=pos.y, z=pos.z}
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, radius, length)
local manip, area = mh.init_axis_radius_length(current_pos, axis, math.max(radius1, radius2), length)
local data = mh.get_empty_data(area)
-- Add cylinder
-- Add desired shape (anything inbetween cylinder & cone)
local node_id = minetest.get_content_id(node_name)
local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
local stride = {x=1, y=area.ystride, z=area.zstride}
local offset = {
x = current_pos.x - area.MinEdge.x,
y = current_pos.y - area.MinEdge.y,
z = current_pos.z - area.MinEdge.z,
}
local min_slice, max_slice = offset[axis], offset[axis] + length - 1
local count = 0
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 column along axis
for index1 = min_slice, max_slice do
local vi = new_index3 + index1 * stride[axis]
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
count = count + length
end
end
end
@ -150,17 +202,19 @@ end
-- @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)
function worldedit.pyramid(pos, axis, height, node_name, hollow)
local other1, other2 = worldedit.get_axis_others(axis)
-- Set up voxel manipulator
local manip, area = mh.init_axis_radius(pos, axis,
height >= 0 and height or -height)
local data = mh.get_empty_data()
-- FIXME: passing negative <radius> causes mis-sorted pos to be passed
-- into mh.init() which is technically not allowed but works
local manip, area = mh.init_axis_radius(pos, axis, height)
local data = mh.get_empty_data(area)
-- Handle inverted pyramids
local start_axis, end_axis, step
local step
if height > 0 then
height = height - 1
step = 1
@ -177,7 +231,7 @@ function worldedit.pyramid(pos, axis, height, node_name)
y = pos.y - area.MinEdge.y,
z = pos.z - area.MinEdge.z,
}
local size = height * step
local size = math.abs(height * step)
local count = 0
-- For each level of the pyramid
for index1 = 0, height, step do
@ -187,10 +241,12 @@ function worldedit.pyramid(pos, axis, height, node_name)
local new_index2 = new_index1 + (index2 + offset[other1]) * stride[other1]
for index3 = -size, size do
local i = new_index2 + (index3 + offset[other2]) * stride[other2]
data[i] = node_id
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
count = count + (size * 2 + 1) ^ 2
size = size - 1
end

View File

@ -1,9 +1,7 @@
--- Schematic serialization and deserialiation.
-- @module worldedit.serialization
worldedit.LATEST_SERIALIZATION_VERSION = 5
local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":"
worldedit.LATEST_SERIALIZATION_VERSION = 6
--[[
Serialization version history:
@ -15,6 +13,7 @@ Serialization version history:
`name`, `param1`, `param2`, and `meta` fields.
5: Added header and made `param1`, `param2`, and `meta` fields optional.
Header format: <Version>,<ExtraHeaderField1>,...:<Content>
6: Much more complicated but also better format
--]]
@ -24,7 +23,7 @@ Serialization version history:
-- @return Extra header fields as a list of strings, or nil if not supported.
-- @return Content (data after header).
function worldedit.read_header(value)
if value:find("^[0-9]+[%-:]") then
if value:find("^[0-9]+[,:]") then
local header_end = value:find(":", 1, true)
local header = value:sub(1, header_end - 1):split(",")
local version = tonumber(header[1])
@ -56,66 +55,232 @@ function worldedit.serialize(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0}
local get_node, get_meta, hash_node_position =
minetest.get_node, minetest.get_meta, minetest.hash_node_position
-- Find the positions which have metadata
local has_meta = {}
local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)
for i = 1, #meta_positions do
has_meta[hash_node_position(meta_positions[i])] = true
end
-- Decide axis of saved rows
local dim = vector.add(vector.subtract(pos2, pos1), 1)
local axis
if dim.x * dim.y < math.min(dim.y * dim.z, dim.x * dim.z) then
axis = "z"
elseif dim.x * dim.z < math.min(dim.x * dim.y, dim.y * dim.z) then
axis = "y"
elseif dim.y * dim.z < math.min(dim.x * dim.y, dim.x * dim.z) then
axis = "x"
else
axis = "x" -- X or Z are usually most efficient
end
local other1, other2 = worldedit.get_axis_others(axis)
-- Helper functions (1)
local MATCH_DIST = 8
local function match_init(array, first_value)
array[1] = first_value
return {first_value}
end
local function match_try(cache, prev_pushed, value)
local i = #cache
while i >= 1 do
if cache[i] == value then
local ret = -(#cache - i + 1)
local was_value = type(prev_pushed) ~= "number" or prev_pushed >= 0
return ret, (was_value and ret == -1) or prev_pushed == ret
end
i = i - 1
end
return nil, false
end
local function match_push(cache, match, value)
if match ~= nil then -- don't advance cache
return match
end
local idx = #cache + 1
cache[idx] = value
if idx > MATCH_DIST then
table.remove(cache, 1)
end
return value
end
-- Helper functions (2)
local function cur_new(pos, pos1)
return {
a = axis,
p = {pos.x - pos1.x, pos.y - pos1.y, pos.z - pos1.z},
c = 1,
data = {},
param1 = {},
param2 = {},
meta = {},
}
end
local function is_emptyish(t)
-- returns true if <t> contains only one element and that one element is == 0
local seen = false
for _, value in pairs(t) do
if not seen then
if value ~= 0 then
return false
end
seen = true
else
return false
end
end
return true
end
local function cur_finish(result, cur)
if is_emptyish(cur.param1) then
cur.param1 = nil
end
if is_emptyish(cur.param2) then
cur.param2 = nil
end
if next(cur.meta) == nil then
cur.meta = nil
end
result[#result + 1] = cur
end
-- Serialize stuff
local pos = {}
local count = 0
local result = {}
local get_node, get_meta = minetest.get_node, minetest.get_meta
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local cur
local cache_data, cache_param1, cache_param2
local prev_data, prev_param1, prev_param2
pos[other1] = pos1[other1]
while pos[other1] <= pos2[other1] do
pos[other2] = pos1[other2]
while pos[other2] <= pos2[other2] do
pos[axis] = pos1[axis]
while pos[axis] <= pos2[axis] do
local node = get_node(pos)
if node.name ~= "air" and node.name ~= "ignore" then
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
if cur == nil then -- Start a new row
cur = cur_new(pos, pos1, axis, other1, other2)
cache_data = match_init(cur.data, node.name)
cache_param1 = match_init(cur.param1, node.param1)
cache_param2 = match_init(cur.param2, node.param2)
prev_data = cur.data[1]
prev_param1 = cur.param1[1]
prev_param2 = cur.param2[1]
cur.meta[1] = meta
else -- Append to existing row
local next_c = cur.c + 1
cur.c = next_c
local value, m, can_omit
value = node.name
m, can_omit = match_try(cache_data, prev_data, node.name)
if not can_omit then
prev_data = match_push(cache_data, m, value)
cur.data[next_c] = prev_data
end
value = node.param1
m, can_omit = match_try(cache_param1, prev_param1, value)
if not can_omit then
prev_param1 = match_push(cache_param1, m, value)
cur.param1[next_c] = prev_param1
end
value = node.param2
m, can_omit = match_try(cache_param2, prev_param2, value)
if not can_omit then
prev_param2 = match_push(cache_param2, m, value)
cur.param2[next_c] = prev_param2
end
cur.meta[next_c] = meta
end
count = count + 1
local meta = get_meta(pos):to_table()
local meta_empty = true
-- Convert metadata item stacks to item strings
for name, inventory in pairs(meta.inventory) do
for index, stack in ipairs(inventory) do
meta_empty = false
inventory[index] = stack.to_string and stack:to_string() or stack
end
else
if cur ~= nil then -- Finish row
cur_finish(result, cur)
cur = nil
end
for k in pairs(meta) do
if k ~= "inventory" then
meta_empty = false
break
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 = not meta_empty and meta or nil,
}
end
pos.z = pos.z + 1
pos[axis] = pos[axis] + 1
end
pos.y = pos.y + 1
if cur ~= nil then -- Finish leftover row
cur_finish(result, cur)
cur = nil
end
pos[other2] = pos[other2] + 1
end
pos.x = pos.x + 1
pos[other1] = pos[other1] + 1
end
-- Serialize entries
result = minetest.serialize(result)
return LATEST_SERIALIZATION_HEADER .. result, count
return tonumber(worldedit.LATEST_SERIALIZATION_VERSION) .. "," ..
string.format("%d,%d,%d:", dim.x, dim.y, dim.z) .. result, count
end
--- Loads the schematic in `value` into a node list in the latest format.
-- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile)
-- by ChillCode, available under the MIT license.
local function deserialize_workaround(content)
local nodes
if not minetest.global_exists("jit") then
nodes = 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
while true do -- go through each individual node entry (except the last)
startpos, endpos = escaped:find("},%s*{", startpos)
if not startpos then
break
end
local current = content:sub(startpos1, startpos)
local entry = minetest.deserialize("return " .. current, true)
table.insert(nodes, entry)
startpos, startpos1 = endpos, endpos
end
local entry = minetest.deserialize("return " .. content:sub(startpos1), true) -- process the last entry
table.insert(nodes, entry)
end
return nodes
end
--- Loads the schematic in `value` into a node list in the latest format.
-- @return A node list in the latest format, or nil on failure.
local function load_schematic(value)
local version, header, content = worldedit.read_header(value)
local function legacy_load_schematic(version, header, content)
local nodes = {}
if version == 1 or version == 2 then -- Original flat table format
local tables = minetest.deserialize(content)
local tables = minetest.deserialize(content, true)
if not tables then return nil end
-- Transform the node table into an array of nodes
@ -144,37 +309,18 @@ local function load_schematic(value)
"([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do
param1, param2 = tonumber(param1), tonumber(param2)
table.insert(nodes, {
x = originx + tonumber(x),
y = originy + tonumber(y),
z = originz + tonumber(z),
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
if not jit then
-- This is broken for larger tables in the current version of LuaJIT
nodes = minetest.deserialize(content)
else
-- XXX: This is a filthy hack that works surprisingly well - in LuaJIT, `minetest.deserialize` will fail due to the register limit
nodes = {}
value = value:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data
local escaped = value:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)
local startpos, startpos1, endpos = 1, 1
while true do -- go through each individual node entry (except the last)
startpos, endpos = escaped:find("},%s*{", startpos)
if not startpos then
break
end
local current = value:sub(startpos1, startpos)
local entry = minetest.deserialize("return " .. current)
table.insert(nodes, entry)
startpos, startpos1 = endpos, endpos
end
local entry = minetest.deserialize("return " .. value:sub(startpos1)) -- process the last entry
table.insert(nodes, entry)
end
nodes = deserialize_workaround(content)
elseif version >= 6 then
error("legacy_load_schematic called for non-legacy schematic")
else
return nil
end
@ -187,14 +333,29 @@ end
-- @return High corner position.
-- @return The number of nodes.
function worldedit.allocate(origin_pos, value)
local nodes = load_schematic(value)
if not nodes then return nil end
return worldedit.allocate_with_nodes(origin_pos, nodes)
local version, header, content = worldedit.read_header(value)
if version == 6 then
local content = deserialize_workaround(content)
local pos2 = {
x = origin_pos.x + tonumber(header[1]) - 1,
y = origin_pos.y + tonumber(header[2]) - 1,
z = origin_pos.z + tonumber(header[3]) - 1,
}
local count = 0
for _, row in ipairs(content) do
count = count + row.c
end
return origin_pos, pos2, count
else
local nodes = legacy_load_schematic(version, header, content)
if not nodes or #nodes == 0 then return nil end
return worldedit.legacy_allocate_with_nodes(origin_pos, nodes)
end
end
-- Internal
function worldedit.allocate_with_nodes(origin_pos, nodes)
function worldedit.legacy_allocate_with_nodes(origin_pos, nodes)
local huge = math.huge
local pos1x, pos1y, pos1z = huge, huge, huge
local pos2x, pos2y, pos2z = -huge, -huge, -huge
@ -217,23 +378,110 @@ 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
local version, header, content = worldedit.read_header(value)
if version == 6 then
local content = deserialize_workaround(content)
local pos2 = {
x = origin_pos.x + tonumber(header[1]) - 1,
y = origin_pos.y + tonumber(header[2]) - 1,
z = origin_pos.z + tonumber(header[3]) - 1,
}
worldedit.keep_loaded(origin_pos, pos2)
local pos1, pos2 = worldedit.allocate_with_nodes(origin_pos, nodes)
worldedit.keep_loaded(pos1, pos2)
return worldedit.deserialize_with_content(origin_pos, content)
else
local nodes = legacy_load_schematic(version, header, content)
if not nodes or #nodes == 0 then return nil end
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
local count = 0
local add_node, get_meta = minetest.add_node, minetest.get_meta
for i, entry in ipairs(nodes) do
entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
-- Entry acts as both position and node
add_node(entry, entry)
if entry.meta then
get_meta(entry):from_table(entry.meta)
local pos1, pos2 = worldedit.legacy_allocate_with_nodes(origin_pos, nodes)
worldedit.keep_loaded(pos1, pos2)
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
local count = 0
local add_node, get_meta = minetest.add_node, minetest.get_meta
for i, entry in ipairs(nodes) do
entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
-- Entry acts as both position and node
add_node(entry, entry)
if entry.meta then
get_meta(entry):from_table(entry.meta)
end
end
return #nodes
end
return #nodes
end
-- Internal
function worldedit.deserialize_with_content(origin_pos, content)
-- Helper functions
local function resolve_refs(array)
-- find (and cache) highest index
local max_i = 1
for i, _ in pairs(array) do
if i > max_i then max_i = i end
end
array.max_i = max_i
-- resolve references
local cache = {}
for i = 1, max_i do
local v = array[i]
if v ~= nil then
if type(v) == "number" and v < 0 then -- is a reference
array[i] = cache[#cache + v + 1]
else
cache[#cache + 1] = v
end
end
end
end
local function read_in_array(array, idx)
if idx > array.max_i then
return array[array.max_i]
end
-- go backwards until we find something
repeat
local v = array[idx]
if v ~= nil then
return v
end
idx = idx - 1
until idx == 0
assert(false)
end
-- Actually deserialize
local count = 0
local entry = {}
local add_node, get_meta = minetest.add_node, minetest.get_meta
for _, row in ipairs(content) do
local axis = row.a
local pos = {
x = origin_pos.x + row.p[1],
y = origin_pos.y + row.p[2],
z = origin_pos.z + row.p[3],
}
if row.param1 == nil then row.param1 = {0} end
if row.param2 == nil then row.param2 = {0} end
if row.meta == nil then row.meta = {} end
resolve_refs(row.data)
resolve_refs(row.param1)
resolve_refs(row.param2)
for i = 1, row.c do
entry.name = read_in_array(row.data, i)
entry.param1 = read_in_array(row.param1, i)
entry.param2 = read_in_array(row.param2, i)
add_node(pos, entry)
local meta = row.meta[i]
if meta then
get_meta(pos):from_table(meta)
end
pos[axis] = pos[axis] + 1
end
count = count + row.c
end
return count
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

View File

@ -6,6 +6,7 @@ minetest.register_node("worldedit:placeholder", {
paramtype = "light",
sunlight_propagates = true,
diggable = false,
pointable = false,
walkable = false,
groups = {not_in_creative_inventory=1},
})

125
worldedit_brush/init.lua Normal file
View File

@ -0,0 +1,125 @@
if minetest.raycast == nil then
error(
"worldedit_brush requires at least Minetest 5.0"
)
end
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,
"This brush is not bound, use //brush to bind a command to it.")
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,
"Missing privileges: " .. table.concat(missing_privs, ", "))
return false
end
local raybegin = vector.add(placer:get_pos(),
{x=0, y=placer:get_properties().eye_height, z=0})
local rayend = vector.add(raybegin, vector.multiply(placer:get_look_dir(), BRUSH_MAX_DIST))
local ray = minetest.raycast(raybegin, rayend, false, true)
local pointed_thing = ray:next()
if pointed_thing == nil then
worldedit.player_notify(name, "Too far away.")
return false
end
assert(pointed_thing.type == "node")
worldedit.pos1[name] = pointed_thing.under
worldedit.pos2[name] = nil
worldedit.marker_update(name)
-- this isn't really clean...
local player_notify_old = worldedit.player_notify
worldedit.player_notify = function(name, msg)
if string.match(msg, "^%d") then return end -- discard "1234 nodes added."
return player_notify_old(name, msg)
end
assert(cmddef.require_pos < 2)
local parsed = {cmddef.parse(meta:get_string("params"))}
if not table.remove(parsed, 1) then return false end -- shouldn't happen
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 = "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 = "Assign command to WorldEdit brush item",
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 itemstack = minetest.get_player_by_name(name):get_wielded_item()
if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then
worldedit.player_notify(name, "Not holding brush item.")
return
end
cmd = cmd:lower()
local meta = itemstack:get_meta()
if cmd == "none" then
meta:from_table(nil)
worldedit.player_notify(name, "Brush assignment cleared.")
else
local cmddef = worldedit.registered_commands[cmd]
if cmddef == nil or cmddef.require_pos ~= 1 then
worldedit.player_notify(name, "//" .. cmd .. " cannot be used with brushes")
return
end
-- Try parsing command params so we can give the user feedback
local ok, err = cmddef.parse(params)
if not ok then
err = err or "invalid usage"
worldedit.player_notify(name, "Error with brush command: " .. err)
return
end
meta:set_string("command", cmd)
meta:set_string("params", params)
local fullcmd = "//" .. cmd .. " " .. params
meta:set_string("description",
minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd)
worldedit.player_notify(name, "Brush assigned to command: " .. fullcmd)
end
minetest.get_player_by_name(name):set_wielded_item(itemstack)
end,
})

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: 337 B

1
worldedit_commands/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*~

View File

@ -0,0 +1,266 @@
worldedit.register_command("outset", {
params = "[h/v] <amount>",
description = "Outset the selected region.",
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, "Invalid direction."
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, "Invalid number of arguments"
end
worldedit.marker_update(name)
return true, "Region outset by " .. amount .. " blocks"
end,
})
worldedit.register_command("inset", {
params = "[h/v] <amount>",
description = "Inset the selected region.",
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, "Invalid direction."
end
return true, dir, tonumber(amount)
end,
func = function(name, dir, amount)
if dir == "" or dir == "vh" or dir == "hv" then
assert(worldedit.cuboid_volumetric_expand(name, -amount))
elseif dir == "h" then
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, "Invalid number of arguments"
end
worldedit.marker_update(name)
return true, "Region inset by " .. amount .. " blocks"
end,
})
worldedit.register_command("shift", {
params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>",
description = "Shifts the selection area without moving its contents",
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, "Invalid if looking straight up or down"
end
assert(worldedit.cuboid_shift(name, axis, amount * dir))
worldedit.marker_update(name)
return true, "Region shifted by " .. amount .. " nodes"
end,
})
worldedit.register_command("expand", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = "Expands the selection in the selected absolute or relative axis",
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, "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, "Region expanded by " .. (amount + rev_amount) .. " nodes"
end,
})
worldedit.register_command("contract", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = "Contracts the selection in the selected absolute or relative axis",
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, "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, "Region contracted by " .. (amount + rev_amount) .. " nodes"
end,
})
worldedit.register_command("cubeapply", {
params = "<size>/(<sizex> <sizey> <sizez>) <command> [parameters]",
description = "Select a cube with side length <size> around position 1 and run <command> on region",
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, "invalid usage: //" .. cmd .. " cannot be used with cubeapply"
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
return sidex * sidey * sidez
end,
func = function(name, sidex, sidey, sidez, cmd, parsed)
local cmddef = assert(worldedit.registered_commands[cmd])
local success, missing_privs = minetest.check_player_privs(name, cmddef.privs)
if not success then
worldedit.player_notify(name, "Missing privileges: " ..
table.concat(missing_privs, ", "))
return
end
-- update region to be the cuboid the user wanted
local half = vector.divide(vector.new(sidex, sidey, sidez), 2)
local sizea, sizeb = vector.apply(half, math.floor), vector.apply(half, math.ceil)
local center = worldedit.pos1[name]
worldedit.pos1[name] = vector.subtract(center, sizea)
worldedit.pos2[name] = vector.add(center, vector.subtract(sizeb, 1))
worldedit.marker_update(name)
-- actually run target command
return cmddef.func(name, unpack(parsed))
end,
})

View File

@ -1 +0,0 @@
worldedit

File diff suppressed because it is too large Load Diff

View File

@ -2,64 +2,78 @@ 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)
worldedit.mark_pos1 = function(name, region_too)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos1 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos1)
end
if worldedit.marker1[name] ~= nil then --marker already exists
worldedit.marker1[name]:remove() --remove marker
worldedit.marker1[name] = nil
end
if pos1 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos1)
--add marker
worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1")
worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel)
if worldedit.marker1[name] ~= nil then
worldedit.marker1[name]:get_luaentity().player_name = name
end
end
worldedit.mark_region(name)
if region_too == nil or region_too then
worldedit.mark_region(name)
end
end
--marks worldedit region position 2
worldedit.mark_pos2 = function(name)
worldedit.mark_pos2 = function(name, region_too)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if pos2 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos2, pos2)
end
if worldedit.marker2[name] ~= nil then --marker already exists
worldedit.marker2[name]:remove() --remove marker
worldedit.marker2[name] = nil
end
if pos2 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos2, pos2)
--add marker
worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2")
worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel)
if worldedit.marker2[name] ~= nil then
worldedit.marker2[name]:get_luaentity().player_name = name
end
end
worldedit.mark_region(name)
if region_too == nil or region_too then
worldedit.mark_region(name)
end
end
worldedit.mark_region = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if worldedit.marker_region[name] ~= nil then --marker already exists
--wip: make the area stay loaded somehow
for _, entity in ipairs(worldedit.marker_region[name]) do
entity:remove()
end
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
@ -71,31 +85,44 @@ worldedit.mark_region = function(name)
--XY plane markers
for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
local marker = minetest.add_entity({x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}, "worldedit:region_cube")
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)
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 marker = minetest.add_entity({x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}, "worldedit:region_cube")
marker:set_properties({
visual_size={x=sizez * 2, y=sizey * 2},
collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez},
})
marker:setyaw(math.pi / 2)
marker:get_luaentity().player_name = name
table.insert(markers, marker)
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",
@ -105,9 +132,11 @@ minetest.register_entity(":worldedit:pos1", {
"worldedit_pos1.png", "worldedit_pos1.png"},
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
physical = false,
static_save = false,
},
on_step = function(self, dtime)
if worldedit.marker1[self.player_name] == nil then
on_activate = function(self, staticdata, dtime_s)
if staticdata ~= init_sentinel then
-- we were loaded from before static_save = false was added
self.object:remove()
end
end,
@ -115,6 +144,9 @@ minetest.register_entity(":worldedit:pos1", {
self.object:remove()
worldedit.marker1[self.player_name] = nil
end,
on_blast = function(self, damage)
return false, false, {} -- don't damage or knockback
end,
})
minetest.register_entity(":worldedit:pos2", {
@ -126,9 +158,11 @@ minetest.register_entity(":worldedit:pos2", {
"worldedit_pos2.png", "worldedit_pos2.png"},
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
physical = false,
static_save = false,
},
on_step = function(self, dtime)
if worldedit.marker2[self.player_name] == nil then
on_activate = function(self, staticdata, dtime_s)
if staticdata ~= init_sentinel then
-- we were loaded from before static_save = false was added
self.object:remove()
end
end,
@ -136,27 +170,37 @@ minetest.register_entity(":worldedit:pos2", {
self.object:remove()
worldedit.marker2[self.player_name] = nil
end,
on_blast = function(self, damage)
return false, false, {} -- don't damage or knockback
end,
})
minetest.register_entity(":worldedit:region_cube", {
initial_properties = {
visual = "upright_sprite",
visual_size = {x=1.1, y=1.1},
textures = {"worldedit_cube.png"},
visual_size = {x=10, y=10},
physical = false,
static_save = false,
},
on_step = function(self, dtime)
if worldedit.marker_region[self.player_name] == nil then
on_activate = function(self, staticdata, dtime_s)
if staticdata ~= init_sentinel then
-- we were loaded from before static_save = false was added
self.object:remove()
return
end
end,
on_punch = function(self, hitter)
for _, entity in ipairs(worldedit.marker_region[self.player_name]) do
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

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

View File

@ -0,0 +1,65 @@
local function above_or_under(placer, pointed_thing)
if placer:get_player_control().sneak then
return pointed_thing.above
else
return pointed_thing.under
end
end
local punched_air_time = {}
minetest.register_tool(":worldedit:wand", {
description = "WorldEdit Wand tool\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
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] = 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] = worldedit.pos1[name]
worldedit.mark_pos2(name)
end
return itemstack -- nothing consumed, nothing changed
end,
})

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ Use `nil` for the `options` parameter to unregister the function associated with
Use `nil` for the `get_formspec` field to denote that the function does not have its own screen.
Use `nil` for the `privs` field to denote that no special privileges are required to use the function.
The `privs` field may not be `nil`.
If the identifier is already registered to another function, it will be replaced by the new one.
@ -24,6 +24,9 @@ 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
@ -46,7 +49,7 @@ worldedit.register_gui_handler = function(identifier, handler)
--ensure the player has permission to perform the action
local entry = worldedit.pages[identifier]
if entry and minetest.check_player_privs(name, entry.privs or {}) then
if entry and minetest.check_player_privs(name, entry.privs) then
return handler(name, fields)
end
return false
@ -67,7 +70,7 @@ local get_formspec = function(name, identifier)
end
--implement worldedit.show_page(name, page) in different ways depending on the available APIs
if unified_inventory then --unified inventory installed
if minetest.global_exists("unified_inventory") then -- unified inventory installed
local old_func = worldedit.register_gui_function
worldedit.register_gui_function = function(identifier, options)
old_func(identifier, options)
@ -77,6 +80,9 @@ if unified_inventory then --unified inventory installed
unified_inventory.register_button("worldedit_gui", {
type = "image",
image = "inventory_plus_worldedit_gui.png",
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)
@ -100,9 +106,12 @@ if unified_inventory then --unified inventory installed
player:set_inventory_formspec(get_formspec(name, page))
end
end
elseif inventory_plus then --inventory++ installed
elseif minetest.global_exists("inventory_plus") then -- inventory++ installed
minetest.register_on_joinplayer(function(player)
inventory_plus.register_button(player, "worldedit_gui", "WorldEdit")
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
@ -115,7 +124,7 @@ elseif inventory_plus then --inventory++ installed
return true
elseif fields.worldedit_gui_exit then --return to original page
if gui_player_formspecs[name] then
inventory_plus.set_inventory_formspec(player, gui_player_formspecs[name])
inventory_plus.set_inventory_formspec(player, inventory_plus.get_formspec(player, "main"))
end
return true
end
@ -128,60 +137,76 @@ elseif inventory_plus then --inventory++ installed
inventory_plus.set_inventory_formspec(player, get_formspec(name, page))
end
end
else --fallback button
local player_formspecs = {}
local update_main_formspec = function(name)
local formspec = player_formspecs[name]
if not formspec then
return
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
local player = minetest.get_player_by_name(name)
if not player then --this is in case the player signs off while the media is loading
return
end
if (minetest.check_player_privs(name, {creative=true}) or minetest.setting_getbool("creative_mode")) and creative_inventory then --creative_inventory is active, add button to modified formspec
formspec = player:get_inventory_formspec() .. "image_button[6,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]"
else
formspec = formspec .. "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]"
end
player:set_inventory_formspec(formspec)
end
minetest.register_on_joinplayer(function(player)
local name = player:get_player_name()
minetest.after(1, function()
if minetest.get_player_by_name(name) then --ensure the player is still signed in
player_formspecs[name] = player:get_inventory_formspec()
minetest.after(0.01, function()
update_main_formspec(name)
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)
end
minetest.register_on_leaveplayer(function(player)
player_formspecs[player:get_player_name()] = nil
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
local gui_player_formspecs = {}
-- register the inventory button
smart_inventory.register_page({
name = "worldedit_gui",
tooltip = "Edit your World!",
icon = "inventory_plus_worldedit_gui.png",
smartfs_callback = smart_worldedit_gui_callback,
sequence = 99
})
elseif minetest.global_exists("sfinv") then -- sfinv installed
assert(sfinv.enabled)
local orig_get = sfinv.pages["sfinv:crafting"].get
sfinv.override_page("sfinv:crafting", {
get = function(self, player, context)
local can_worldedit = minetest.check_player_privs(player, {worldedit=true})
local fs = orig_get(self, player, context)
return fs .. (can_worldedit and "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]" or "")
end
})
--show the form when the button is pressed and hide it when done
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")
worldedit.show_page(player:get_player_name(), "worldedit_gui")
return true
elseif fields.worldedit_gui_exit then --return to original page
if gui_player_formspecs[name] then
player:set_inventory_formspec(gui_player_formspecs[name])
end
sfinv.set_page(player, "sfinv:crafting")
return true
else --deal with creative_inventory setting the formspec on every single message
minetest.after(0.01,function()
update_main_formspec(name)
end)
return false --continue processing in creative inventory
end
return false
end)
worldedit.show_page = function(name, page)
@ -190,10 +215,19 @@ else --fallback button
player:set_inventory_formspec(get_formspec(name, page))
end
end
else
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
@ -226,7 +260,7 @@ 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 or {})
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, ", ") .. ")")
return false

4
worldedit_gui/mod.conf Normal file
View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

View File

@ -1,103 +0,0 @@
worldedit = worldedit or {}
local minetest = minetest --local copy of global
local get_pointed = function(pos, nearest, distance)
if distance > 100 then
return false
end
--check for collision with node
local nodename = minetest.get_node(pos).name
if nodename ~= "air"
and nodename ~= "default:water_source"
and nodename ~= "default:water_flowing" then
if nodename ~= "ignore" then
return nearest
end
return false
end
end
local use = function(itemstack, user, pointed_thing)
if pointed_thing.type == "nothing" then --pointing at nothing
local placepos = worldedit.raytrace(user:getpos(), user:get_look_dir(), get_pointed)
if placepos then --extended reach
pointed_thing.type = "node"
pointed_thing.under = nil --wip
pointed_thing.above = nil --wip
end
end
return minetest.item_place_node(itemstack, user, pointed_thing)
end
--
worldedit.raytrace = function(pos, dir, callback)
local base = {x=math.floor(pos.x), y=math.floor(pos.y), z=math.floor(pos.z)}
local stepx, stepy, stepz = 0, 0, 0
local componentx, componenty, componentz = 0, 0, 0
local intersectx, intersecty, intersectz = 0, 0, 0
if dir.x == 0 then
intersectx = math.huge
elseif dir.x > 0 then
stepx = 1
componentx = 1 / dir.x
intersectx = ((base.x - pos.x) + 1) * componentx
else
stepx = -1
componentx = 1 / -dir.x
intersectx = (pos.x - base.x) * componentx
end
if dir.y == 0 then
intersecty = math.huge
elseif dir.y > 0 then
stepy = 1
componenty = 1 / dir.y
intersecty = ((base.y - pos.y) + 1) * componenty
else
stepy = -1
componenty = 1 / -dir.y
intersecty = (pos.y - base.y) * componenty
end
if dir.z == 0 then
intersectz = math.huge
elseif dir.z > 0 then
stepz = 1
componentz = 1 / dir.z
intersectz = ((base.z - pos.z) + 1) * componentz
else
stepz = -1
componentz = 1 / -dir.z
intersectz = (pos.z - base.z) * componentz
end
local distance = 0
local nearest = {x=base.x, y=base.y, z=base.z}
while true do
local values = {callback(base, nearest, distance)}
if #values > 0 then
return unpack(values)
end
nearest.x, nearest.y, nearest.z = base.x, base.y, base.z
if intersectx < intersecty then
if intersectx < intersectz then
base.x = base.x + stepx
distance = intersectx
intersectx = intersectx + componentx
else
base.z = base.z + stepz
distance = intersectz
intersectz = intersectz + componentz
end
elseif intersecty < intersectz then
base.y = base.y + stepy
distance = intersecty
intersecty = intersecty + componenty
else
base.z = base.z + stepz
distance = intersectz
intersectz = intersectz + componentz
end
end
end

View File

@ -1 +0,0 @@
worldedit

View File

@ -1,120 +0,0 @@
do return end
do
local MAX_VOLUME = 30 * 30 * 30
local we = worldedit
local volume = we.volume
local safewrap = function(func)
return function(pos1, pos2, ...)
if validbox(pos1, pos2) then
return func(pos1, pos2, ...)
end
return 0, pos1, pos2
end
end
local validbox = function(pos1, pos2)
tpos1, tpos2 = we.sort_pos(pos1, pos2)
if volume(tpos1, tpos2) > MAX_VOLUME then
return false
end
--check for ownership of area if ownership mod is installed
if owner_defs then
local inside = false
for _, def in pairs(owner_defs) do
--sort positions
local tdef = {x1=def.x1, x2 = def.x2, y1=def.y1, y2=def.y2, z1=def.z1, z2=def.z2}
if tdef.x1 > tdef.x2 then
tdef.x1, tdef.x2 = tdef.x2, tdef.x1
end
if tdef.y1 > tdef.y2 then
tdef.y1, tdef.y2 = tdef.y2, tdef.y1
end
if tdef.z1 > tdef.z2 then
tdef.z1, tdef.z2 = tdef.z2, tdef.z1
end
--check ownership
if tpos1.x >= tdef.x1 and tpos1.x <= tdef.x2
and tpos2.x >= tdef.x1 and tpos2.x <= tdef.x2
and tpos1.y >= tdef.y1 and tpos1.y <= tdef.y2
and tpos2.y >= tdef.y1 and tpos2.y <= tdef.y2
and tpos1.z >= tdef.z1 and tpos1.z <= tdef.z2
and tpos2.z >= tdef.z1 and tpos2.z <= tdef.z2
and name == def.owner then --wip: name isn't available here
inside = true
break
end
end
if not inside then
return false
end
end
return true
end
worldedit = {
sort_pos = we.sort_pos,
set = safewrap(we.set),
replace = safewrap(we.replace),
replaceinverse = safewrap(we.replaceinverse),
copy = function(pos1, pos2, axis, amount)
tpos1, tpos2 = we.sort_pos(pos1, pos2)
tpos1[axis] = tpos1[axis] + amount
tpos2[axis] = tpos2[axis] + amount
if validbox(pos1, pos2) and validbox(tpos1, tpos2) then
we.copy(pos1, pos2, axis, amount)
else
return 0
end
end,
move = function(pos1, pos2, axis, amount)
tpos1, tpos2 = we.sort_pos(pos1, pos2)
tpos1[axis] = tpos1[axis] + amount
tpos2[axis] = tpos2[axis] + amount
if validbox(pos1, pos2) and validbox(tpos1, tpos2) then
we.move(pos1, pos2, axis, amount)
else
return 0
end
end,
stack = function(pos1, pos2, axis, count)
tpos1, tpos2 = we.sort_pos(pos1, pos2)
local length = (tpos2[axis] - tpos1[axis] + 1) * count
if count < 0 then
tpos1[axis] = tpos1[axis] + length
else
tpos2[axis] = tpos2[axis] + length
end
if validbox(tpos1, tpos2) then
we.stack(pos1, pos2, axis, amount)
else
return 0
end
end,
--wip: add transpose, rotate safely
flip = safewrap(we.flip),
orient = safewrap(we.orient),
fixlight = safewrap(we.fixlight),
--wip: add primitives here
volume = we.volume,
hide = safewrap(we.hide),
suppress = safewrap(we.suppress),
highlight = safewrap(we.highlight),
restore = safewrap(we.restore),
serialize = safewrap(we.serialize),
allocate = we.allocate,
deserialize = function(originpos, value)
local tpos1, tpos2 = we.allocate(originpos, value)
if validbox(tpos1, tpos2) then
we.deserialize(originpos, value)
else
return 0
end
end,
}
end

View File

@ -1 +0,0 @@
worldedit_commands

View File

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

View File

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