1
0
mirror of https://github.com/Uberi/Minetest-WorldEdit.git synced 2025-06-29 22:50:46 +02:00

86 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 68f7bcc728.
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 45acf09e4a
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
31 changed files with 2468 additions and 1419 deletions

View File

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

View File

@ -1,4 +1,4 @@
WorldEdit v1.2 WorldEdit v1.3
============== ==============
The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more. The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more.
@ -11,47 +11,52 @@ For more information, see the [forum topic](https://forum.minetest.net/viewtopic
Installing 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: 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. 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 `SOMETHING.zip` or `SOMETHING.tar.gz`. 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. 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. 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. 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. 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. 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. 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 Usage
----- -----
WorldEdit works primarily through the WorldEdit GUI and chat commands. Depending on your key bindings, you can invoke chat entry with the "t" key, and open the chat console with the "F10" key. WorldEdit works primarily through the WorldEdit GUI and chat commands. Depending on your key bindings, you can invoke chat entry with the "T" key and open the chat console with the "F10" key.
WorldEdit has a huge potential for abuse by untrusted players. Therefore, users will not be able to use WorldEdit unless they have the `worldedit` privelege. This is available by default in single player, but in multiplayer the permission must be explicitly given by someone with the right credentials, using the follwoing chat command: `/grant <player name> worldedit`. This privelege can later be removed using the following chat command: `/revoke <player name> worldedit`. WorldEdit has a huge potential for abuse by untrusted players. Therefore, users will not be able to use WorldEdit unless they have the `worldedit` privilege.
This is available by default in singleplayer, but in multiplayer the permission must be explicitly given by someone with the right credentials,
using the following chat command: `/grant <player name> worldedit`. This privilege can later be removed using the following chat command: `/revoke <player name> worldedit`.
Certain functions/commands such as WorldEdit `//lua` and `//luatransform` chat commands additionally require the `server` privilege. This is because it is extremely dangerous to give access to these commands to untrusted players, since they essentially are able to control the computer the server is running on. Give this privilege only to people you trust with your computer. Certain functions/commands such as WorldEdit `//lua` and `//luatransform` chat commands additionally require the `server` privilege.
This is because it is extremely dangerous to give access to these commands to untrusted players, since they essentially are able to control the computer the server is running on.
Give this privilege only to people you trust with your computer.
For in-game information about these commands, type `/help <command name>` in the chat. For example, to learn more about the `//copy` command, simply type `/help /copy` to display information relevant to copying a region. For in-game information about these commands, type `//help <command name>` in the chat. For example, to learn more about the `//copy` command, simply type `//help copy` to display information relevant to copying a region.
Interface Interface
--------- ---------
WorldEdit is accessed in-game in two main ways. 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). The chat interface adds many chat commands that perform various WorldEdit powered tasks. It is documented in the [Chat Commands Reference](ChatCommands.md).
Compatibility Compatibility
------------- -------------
This mod supports Minetest versions 0.4.8 and newer. Older versions of WorldEdit may work with older versions of Minetest, but are not recommended or supported. This mod supports Minetest versions 5.0 and newer. Older versions of WorldEdit may work with older versions of Minetest, but are not recommended or supported.
WorldEdit works quite well with other mods, and does not have any known mod conflicts. WorldEdit works quite well with other mods and does not have any known mod conflicts.
WorldEdit GUI requires one of [sfinv](https://github.com/minetest/minetest_game/tree/master/mods/sfinv) (included in minetest_game since 0.4.15), [Unified Inventory](https://forum.minetest.net/viewtopic.php?id=3933) or [Inventory++](https://forum.minetest.net/viewtopic.php?id=6204). WorldEdit GUI requires one of [sfinv](https://github.com/minetest/minetest_game/tree/master/mods/sfinv) (included in minetest_game),
[Unified Inventory](https://forum.minetest.net/viewtopic.php?t=12767),
[Inventory++](https://forum.minetest.net/viewtopic.php?id=6204) or [Smart Inventory](https://forum.minetest.net/viewtopic.php?t=16597).
If you use any other inventory manager mods, note that they may conflict with the WorldEdit GUI. If this is the case, it may be necessary to disable them. If you use any other inventory manager mods, note that they may conflict with the WorldEdit GUI. If this is the case, it may be necessary to disable them.
@ -59,9 +64,9 @@ WorldEdit API
------------- -------------
WorldEdit exposes all significant functionality in a simple Lua interface. WorldEdit exposes all significant functionality in a simple Lua interface.
Adding WorldEdit to the file "depends.txt" in your mod gives you access to all of the `worldedit` functions. The API is useful for tasks such as high-performance node manipulation, alternative interfaces, and map creation. Adding WorldEdit as a dependency to your mod gives you access to all of the `worldedit` functions. The API is useful for tasks such as high-performance node manipulation, alternative interfaces and map creation.
AGPLv3 compatible mods may further include WorldEdit files in their own mods. This may be useful if a modder wishes to completely avoid any dependencies on WorldEdit. Note that it is required to give credit to the authors. AGPLv3 compatible mods may further include WorldEdit files in their own mods. This can be useful if a modder wishes to completely avoid any dependency on WorldEdit. Note that it is required to give credit to the authors in this case.
This API is documented in the [WorldEdit API Reference](WorldEdit%20API.md). This API is documented in the [WorldEdit API Reference](WorldEdit%20API.md).
@ -79,13 +84,16 @@ Nodes
----- -----
Node names are required for many types of commands that identify or modify specific types of nodes. They can be specified in a number of ways. 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". 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 Regions
------- -------
@ -93,11 +101,11 @@ Most WorldEdit commands operate on regions. Regions are a set of two positions t
Each positions together define two opposing corners of the cube. With two opposing corners it is possible to determine both the location and dimensions of the region. 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 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. To remove the entities, simply punch them. This does not reset the positions themselves.
@ -105,9 +113,11 @@ Schematics
---------- ----------
WorldEdit supports two different types of schematics. 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. 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: Specifically it looks like this:
@ -130,32 +140,53 @@ The ordering of the values and minor aspects of the syntax, such as trailing com
The WorldEdit Schematic format is accessed via the WorldEdit API, or WorldEdit serialization chat commands such as `//serialize` and `//deserialize`. The 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 Authors
------- -------
WorldEdit would not be possible without the contributions of many developers and designers. Below, they are listed alphabetically: 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 cornernote
cyisfor Cy
danierukun Daniel Sosa
electricface electricface
est31 est31
Eugen Wesseloh
h3ndrik
HybridDog
Isidor Zeuner
Jean-Patrick Guerrero
Joseph Pickard
kaeza kaeza
khonkhortisan kilbith
pickardjoe KodexKy
Kyle
MT-Modder
Niwla23
Panquesito7
Pedro Gimeno
Rui
Sebastien Ponce Sebastien Ponce
sfan5 sfan5
ShadowNinja ShadowNinja
shivajiva101
spillz spillz
Starbeamrainbowlabs
TalkLounge
tenplus1
Uberi/Temperest Uberi/Temperest
Wuzzy
License 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. Basically, this means everyone is free to use, modify, and distribute the files, as long as these modifications are also licensed the same way.
Most importantly, the Affero variant of the GPL requires you to publish your modifications in source form, even if the mod is run only on the server, and not distributed. Most importantly, the Affero variant of the GPL requires you to publish your modifications in source form, even if the mod is run only on the server, and not distributed.

View File

@ -1,13 +1,11 @@
WorldEdit Tutorial 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: Let's start with a few assumptions:
* You have a compatible version of Minetest working. * You have a compatible version of Minetest working, that is 5.0 or later.
* See the [README](README.md) for compatibility information.
* You have WorldEdit installed as a mod. * 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. * 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. * See the installation instructions in [README](README.md) if you need more details.
* You are familiar with the basics of the game. * 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. 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 ### 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. 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 Step 3: Position commands
------------------------- -------------------------

View File

@ -24,6 +24,7 @@ Contained in manipulations.lua, this module allows several node operations to be
### count = worldedit.set(pos1, pos2, node_name) ### 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`. 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. Returns the number of nodes set.
@ -54,6 +55,7 @@ Returns the number of nodes copied.
### count = worldedit.copy2(pos1, pos2, off) ### count = worldedit.copy2(pos1, pos2, off)
Copies the region defined by positions `pos1` and `pos2` by the offset vector `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. Returns the number of nodes copied.
@ -72,6 +74,7 @@ Returns the number of nodes stacked.
### count = worldedit.stack2(pos1, pos2, direction, amount) ### count = worldedit.stack2(pos1, pos2, direction, amount)
Duplicates the region defined by positions `pos1` and `pos2` `amount` times with offset vector `direction`. 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. Returns the number of nodes stacked.
@ -193,7 +196,7 @@ Returns the number of nodes restored.
Serialization 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) ### 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) 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() local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2) manip:read_from_map(pos1, pos2)
end end
@ -107,7 +110,9 @@ end
function mh.finish(manip, data) function mh.finish(manip, data)
-- Update map -- Update map
manip:set_data(data) if data ~= nil then
manip:set_data(data)
end
manip:write_to_map() manip:write_to_map()
manip:update_map() manip:update_map()
end end

View File

@ -98,21 +98,6 @@ worldedit.marker_move = function(name, marker, deltavector)
return true return true
end end
-- Updates the location ingame of the markers
worldedit.marker_update = function(name, marker)
if marker == nil then
worldedit.mark_pos1(name)
worldedit.mark_pos2(name)
elseif marker == 1 then
worldedit.mark_pos1(name)
elseif marker == 2 then
worldedit.mark_pos2(name)
else
minetest.debug(
"worldedit: Invalid execution of function update_markers")
end
end
-- Returns two vectors with the directions for volumetric expansion -- Returns two vectors with the directions for volumetric expansion
worldedit.get_expansion_directions = function(mark1, mark2) worldedit.get_expansion_directions = function(mark1, mark2)
@ -149,7 +134,7 @@ end
-- Return the marker that is closest to the player -- Return the marker that is closest to the player
worldedit.marker_get_closest_to_player = function(name) worldedit.marker_get_closest_to_player = function(name)
local playerpos = minetest.get_player_by_name(name):getpos() local playerpos = minetest.get_player_by_name(name):get_pos()
local dist1 = vector.distance(playerpos, worldedit.pos1[name]) local dist1 = vector.distance(playerpos, worldedit.pos1[name])
local dist2 = vector.distance(playerpos, worldedit.pos2[name]) local dist2 = vector.distance(playerpos, worldedit.pos2[name])

View File

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

View File

@ -76,9 +76,6 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
local count = 0 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 if not inverse then
for i in area:iterp(pos1, pos2) do for i in area:iterp(pos1, pos2) do
if data[i] == search_id then if data[i] == search_id then
@ -101,27 +98,47 @@ function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
end 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`. --- 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. -- @return The number of nodes stacked.
function worldedit.stack2(pos1, pos2, direction, amount, finished) 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 i = 0
local translated = {x=0, y=0, z=0} local translated = {x=0, y=0, z=0}
local function next_one() local function step()
if i < amount then translated.x = translated.x + direction.x
i = i + 1 translated.y = translated.y + direction.y
translated.x = translated.x + direction.x translated.z = translated.z + direction.z
translated.y = translated.y + direction.y worldedit.copy2(pos1, pos2, translated)
translated.z = translated.z + direction.z i = i + 1
worldedit.copy2(pos1, pos2, translated) return i >= amount
minetest.after(0, next_one)
else
if finished then
finished()
end
end
end end
next_one() deferred_execution(step, finished)
return worldedit.volume(pos1, pos2) * amount return worldedit.volume(pos1, pos2) * amount
end end
@ -135,181 +152,187 @@ end
function worldedit.copy(pos1, pos2, axis, amount) function worldedit.copy(pos1, pos2, axis, amount)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2) 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, local off = {x=0, y=0, z=0}
minetest.get_meta, minetest.set_node off[axis] = amount
-- Copy things backwards when negative to avoid corruption. return worldedit.copy2(pos1, pos2, off, backwards)
-- FIXME: Lots of code duplication here.
if amount < 0 then
local pos = {}
pos.x = pos1.x
while pos.x <= pos2.x do
pos.y = pos1.y
while pos.y <= pos2.y do
pos.z = pos1.z
while pos.z <= pos2.z do
local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get meta of current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Copy node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z + 1
end
pos.y = pos.y + 1
end
pos.x = pos.x + 1
end
else
local pos = {}
pos.x = pos2.x
while pos.x >= pos1.x do
pos.y = pos2.y
while pos.y >= pos1.y do
pos.z = pos2.z
while pos.z >= pos1.z do
local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() -- Get meta of current node
local value = pos[axis] -- Store current position
pos[axis] = value + amount -- Move along axis
set_node(pos, node) -- Copy node to new position
get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value -- Restore old position
pos.z = pos.z - 1
end
pos.y = pos.y - 1
end
pos.x = pos.x - 1
end
end
return worldedit.volume(pos1, pos2)
end end
--- Copies a region by offset vector `off`. --- Copies a region by offset vector `off`.
-- @param pos1 -- @param pos1
-- @param pos2 -- @param pos2
-- @param off -- @param off
-- @param meta_backwards (not officially part of API)
-- @return The number of nodes copied. -- @return The number of nodes copied.
function worldedit.copy2(pos1, pos2, off) function worldedit.copy2(pos1, pos2, off, meta_backwards)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2) local src_manip, src_area = mh.init(pos1, pos2)
local src_stride = {x=1, y=src_area.ystride, z=src_area.zstride}
local src_offset = vector.subtract(pos1, src_area.MinEdge)
local get_node, get_meta, set_node = minetest.get_node, local dpos1 = vector.add(pos1, off)
minetest.get_meta, minetest.set_node local dpos2 = vector.add(pos2, off)
local pos = {} local dim = vector.add(vector.subtract(pos2, pos1), 1)
pos.x = pos2.x
while pos.x >= pos1.x do local dst_manip, dst_area = mh.init(dpos1, dpos2)
pos.y = pos2.y local dst_stride = {x=1, y=dst_area.ystride, z=dst_area.zstride}
while pos.y >= pos1.y do local dst_offset = vector.subtract(dpos1, dst_area.MinEdge)
pos.z = pos2.z
while pos.z >= pos1.z do local function do_copy(src_data, dst_data)
local node = get_node(pos) -- Obtain current node for z = 0, dim.z-1 do
local meta = get_meta(pos):to_table() -- Get meta of current node local src_index_z = (src_offset.z + z) * src_stride.z + 1 -- +1 for 1-based indexing
local newpos = vector.add(pos, off) -- Calculate new position local dst_index_z = (dst_offset.z + z) * dst_stride.z + 1
set_node(newpos, node) -- Copy node to new position for y = 0, dim.y-1 do
get_meta(newpos):from_table(meta) -- Set metadata of new node local src_index_y = src_index_z + (src_offset.y + y) * src_stride.y
pos.z = pos.z - 1 local dst_index_y = dst_index_z + (dst_offset.y + y) * dst_stride.y
-- Copy entire row at once
local src_index_x = src_index_y + src_offset.x
local dst_index_x = dst_index_y + dst_offset.x
for x = 0, dim.x-1 do
dst_data[dst_index_x + x] = src_data[src_index_x + x]
end
end end
pos.y = pos.y - 1
end 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) return worldedit.volume(pos1, pos2)
end 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. --- Moves a region along `axis` by `amount` nodes.
-- @return The number of nodes moved. -- @return The number of nodes moved.
function worldedit.move(pos1, pos2, axis, amount) function worldedit.move(pos1, pos2, axis, amount)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2) 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 local function nuke_area(my_off, my_dim)
-- and transfer metadata in separate loop (and if the amount is if my_dim.x == 0 or my_dim.y == 0 or my_dim.z == 0 then
-- greater than the length in the axis, copy whole thing at a time and return
-- 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
end 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 else
local pos = {} -- Source and destination region are overlapping, which means we can't
pos.x = pos2.x -- blindly delete the [pos1, pos2] area
while pos.x >= pos1.x do local leftover = vector.new(dim) -- size of the leftover slice
pos.y = pos2.y leftover[axis] = math.abs(amount)
while pos.y >= pos1.y do if amount > 0 then
pos.z = pos2.z nuke_area({x=0, y=0, z=0}, leftover)
while pos.z >= pos1.z do else
local node = get_node(pos) -- Obtain current node local top = {x=0, y=0, z=0} -- offset of the leftover slice from pos1
local meta = get_meta(pos):to_table() -- Get metadata of current node top[axis] = dim[axis] - math.abs(amount)
remove_node(pos) -- Remove current node nuke_area(top, leftover)
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
end end
end end
return worldedit.volume(pos1, pos2) return worldedit.volume(pos1, pos2)
end end
--- Duplicates a region along `axis` `amount` times. --- 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 pos1
-- @param pos2 -- @param pos2
-- @param axis Axis direction, "x", "y", or "z". -- @param axis Axis direction, "x", "y", or "z".
-- @param count -- @param count
-- @return The number of nodes stacked. -- @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 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local length = pos2[axis] - pos1[axis] + 1 local length = pos2[axis] - pos1[axis] + 1
if count < 0 then if count < 0 then
count = -count count = -count
length = -length length = -length
end end
local amount = 0
local copy = worldedit.copy local i, distance = 0, 0
local i = 1 local function step()
function next_one() distance = distance + length
if i <= count then worldedit.copy(pos1, pos2, axis, distance)
i = i + 1 i = i + 1
amount = amount + length return i >= count
copy(pos1, pos2, axis, amount)
minetest.after(0, next_one)
end
end end
next_one() deferred_execution(step, finished)
return worldedit.volume(pos1, pos2) * count return worldedit.volume(pos1, pos2) * count
end end
@ -529,20 +552,22 @@ end
-- @param pos2 -- @param pos2
-- @param angle Angle in degrees (90 degree increments only). -- @param angle Angle in degrees (90 degree increments only).
-- @return The number of nodes oriented. -- @return The number of nodes oriented.
-- TODO: Support 6D facedir rotation along arbitrary axis.
function worldedit.orient(pos1, pos2, angle) function worldedit.orient(pos1, pos2, angle)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local registered_nodes = minetest.registered_nodes local registered_nodes = minetest.registered_nodes
local wallmounted = { local wallmounted = {
[90] = {[0]=0, 1, 5, 4, 2, 3}, [90] = {0, 1, 5, 4, 2, 3, 0, 0},
[180] = {[0]=0, 1, 3, 2, 5, 4}, [180] = {0, 1, 3, 2, 5, 4, 0, 0},
[270] = {[0]=0, 1, 4, 5, 3, 2} [270] = {0, 1, 4, 5, 3, 2, 0, 0}
} }
local facedir = { local facedir = {
[90] = {[0]=1, 2, 3, 0}, [90] = { 1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16,
[180] = {[0]=2, 3, 0, 1}, 9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22},
[270] = {[0]=3, 0, 1, 2} [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 angle = angle % 360
@ -558,8 +583,7 @@ function worldedit.orient(pos1, pos2, angle)
worldedit.keep_loaded(pos1, pos2) worldedit.keep_loaded(pos1, pos2)
local count = 0 local count = 0
local set_node, get_node, get_meta, swap_node = minetest.set_node, local get_node, swap_node = minetest.get_node, minetest.swap_node
minetest.get_node, minetest.get_meta, minetest.swap_node
local pos = {x=pos1.x, y=0, z=0} local pos = {x=pos1.x, y=0, z=0}
while pos.x <= pos2.x do while pos.x <= pos2.x do
pos.y = pos1.y pos.y = pos1.y
@ -569,17 +593,20 @@ function worldedit.orient(pos1, pos2, angle)
local node = get_node(pos) local node = get_node(pos)
local def = registered_nodes[node.name] local def = registered_nodes[node.name]
if def then if def then
if def.paramtype2 == "wallmounted" then local paramtype2 = def.paramtype2
node.param2 = wallmounted_substitution[node.param2] if paramtype2 == "wallmounted" or
local meta = get_meta(pos):to_table() paramtype2 == "colorwallmounted" then
set_node(pos, node) local orient = node.param2 % 8
get_meta(pos):from_table(meta) node.param2 = node.param2 - orient +
wallmounted_substitution[orient + 1]
swap_node(pos, node)
count = count + 1 count = count + 1
elseif def.paramtype2 == "facedir" then elseif paramtype2 == "facedir" or
node.param2 = facedir_substitution[node.param2] paramtype2 == "colorfacedir" then
local meta = get_meta(pos):to_table() local orient = node.param2 % 32
set_node(pos, node) node.param2 = node.param2 - orient +
get_meta(pos):from_table(meta) facedir_substitution[orient + 1]
swap_node(pos, node)
count = count + 1 count = count + 1
end end
end end
@ -613,10 +640,34 @@ function worldedit.clear_objects(pos1, pos2)
worldedit.keep_loaded(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) -- 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 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 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 -- Center of region
local center = { local center = {
x = pos1x + ((pos2x - pos1x) / 2), x = pos1x + ((pos2x - pos1x) / 2),
@ -628,13 +679,9 @@ function worldedit.clear_objects(pos1, pos2)
(center.x - pos1x) ^ 2 + (center.x - pos1x) ^ 2 +
(center.y - pos1y) ^ 2 + (center.y - pos1y) ^ 2 +
(center.z - pos1z) ^ 2) (center.z - pos1z) ^ 2)
local count = 0
for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do
local entity = obj:get_luaentity() if should_delete(obj) then
-- Avoid players and WorldEdit entities local pos = obj:get_pos()
if not obj:is_player() and (not entity or
not entity.name:find("^worldedit:")) then
local pos = obj:getpos()
if pos.x >= pos1x and pos.x <= pos2x and if pos.x >= pos1x and pos.x <= pos2x and
pos.y >= pos1y and pos.y <= pos2y and pos.y >= pos1y and pos.y <= pos2y and
pos.z >= pos1z and pos.z <= pos2z then 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

@ -208,12 +208,13 @@ function worldedit.pyramid(pos, axis, height, node_name, hollow)
local other1, other2 = worldedit.get_axis_others(axis) local other1, other2 = worldedit.get_axis_others(axis)
-- Set up voxel manipulator -- Set up voxel manipulator
local manip, area = mh.init_axis_radius(pos, axis, -- FIXME: passing negative <radius> causes mis-sorted pos to be passed
height >= 0 and height or -height) -- 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) local data = mh.get_empty_data(area)
-- Handle inverted pyramids -- Handle inverted pyramids
local start_axis, end_axis, step local step
if height > 0 then if height > 0 then
height = height - 1 height = height - 1
step = 1 step = 1

View File

@ -1,9 +1,7 @@
--- Schematic serialization and deserialiation. --- Schematic serialization and deserialiation.
-- @module worldedit.serialization -- @module worldedit.serialization
worldedit.LATEST_SERIALIZATION_VERSION = 5 worldedit.LATEST_SERIALIZATION_VERSION = 6
local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":"
--[[ --[[
Serialization version history: Serialization version history:
@ -15,6 +13,7 @@ Serialization version history:
`name`, `param1`, `param2`, and `meta` fields. `name`, `param1`, `param2`, and `meta` fields.
5: Added header and made `param1`, `param2`, and `meta` fields optional. 5: Added header and made `param1`, `param2`, and `meta` fields optional.
Header format: <Version>,<ExtraHeaderField1>,...:<Content> 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 Extra header fields as a list of strings, or nil if not supported.
-- @return Content (data after header). -- @return Content (data after header).
function worldedit.read_header(value) 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_end = value:find(":", 1, true)
local header = value:sub(1, header_end - 1):split(",") local header = value:sub(1, header_end - 1):split(",")
local version = tonumber(header[1]) local version = tonumber(header[1])
@ -56,66 +55,232 @@ function worldedit.serialize(pos1, pos2)
worldedit.keep_loaded(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 count = 0
local result = {} local result = {}
local get_node, get_meta = minetest.get_node, minetest.get_meta local cur
while pos.x <= pos2.x do local cache_data, cache_param1, cache_param2
pos.y = pos1.y local prev_data, prev_param1, prev_param2
while pos.y <= pos2.y do pos[other1] = pos1[other1]
pos.z = pos1.z while pos[other1] <= pos2[other1] do
while pos.z <= pos2.z 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) local node = get_node(pos)
if node.name ~= "air" and node.name ~= "ignore" then 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 count = count + 1
local meta = get_meta(pos):to_table() else
if cur ~= nil then -- Finish row
local meta_empty = true cur_finish(result, cur)
-- Convert metadata item stacks to item strings cur = nil
for name, inventory in pairs(meta.inventory) do
for index, stack in ipairs(inventory) do
meta_empty = false
inventory[index] = stack.to_string and stack:to_string() or stack
end
end 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 end
pos.z = pos.z + 1 pos[axis] = pos[axis] + 1
end 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 end
pos.x = pos.x + 1 pos[other1] = pos[other1] + 1
end end
-- Serialize entries -- Serialize entries
result = minetest.serialize(result) 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 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) -- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile)
-- by ChillCode, available under the MIT license. -- 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. -- @return A node list in the latest format, or nil on failure.
local function load_schematic(value) local function legacy_load_schematic(version, header, content)
local version, header, content = worldedit.read_header(value)
local nodes = {} local nodes = {}
if version == 1 or version == 2 then -- Original flat table format 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 if not tables then return nil end
-- Transform the node table into an array of nodes -- Transform the node table into an array of nodes
@ -153,28 +318,9 @@ local function load_schematic(value)
}) })
end end
elseif version == 4 or version == 5 then -- Nested table format elseif version == 4 or version == 5 then -- Nested table format
if not jit then nodes = deserialize_workaround(content)
-- This is broken for larger tables in the current version of LuaJIT elseif version >= 6 then
nodes = minetest.deserialize(content) error("legacy_load_schematic called for non-legacy schematic")
else
-- XXX: This is a filthy hack that works surprisingly well - in LuaJIT, `minetest.deserialize` will fail due to the register limit
nodes = {}
content = content:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data
local escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)
local startpos, startpos1, endpos = 1, 1
while true do -- go through each individual node entry (except the last)
startpos, endpos = escaped:find("},%s*{", startpos)
if not startpos then
break
end
local current = content:sub(startpos1, startpos)
local entry = minetest.deserialize("return " .. current)
table.insert(nodes, entry)
startpos, startpos1 = endpos, endpos
end
local entry = minetest.deserialize("return " .. content:sub(startpos1)) -- process the last entry
table.insert(nodes, entry)
end
else else
return nil return nil
end end
@ -187,14 +333,29 @@ end
-- @return High corner position. -- @return High corner position.
-- @return The number of nodes. -- @return The number of nodes.
function worldedit.allocate(origin_pos, value) function worldedit.allocate(origin_pos, value)
local nodes = load_schematic(value) local version, header, content = worldedit.read_header(value)
if not nodes then return nil end if version == 6 then
return worldedit.allocate_with_nodes(origin_pos, nodes) 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 end
-- Internal -- Internal
function worldedit.allocate_with_nodes(origin_pos, nodes) function worldedit.legacy_allocate_with_nodes(origin_pos, nodes)
local huge = math.huge local huge = math.huge
local pos1x, pos1y, pos1z = huge, huge, huge local pos1x, pos1y, pos1z = huge, huge, huge
local pos2x, pos2y, pos2z = -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`. --- Loads the nodes represented by string `value` at position `origin_pos`.
-- @return The number of nodes deserialized. -- @return The number of nodes deserialized.
function worldedit.deserialize(origin_pos, value) function worldedit.deserialize(origin_pos, value)
local nodes = load_schematic(value) local version, header, content = worldedit.read_header(value)
if not nodes then return nil end 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) return worldedit.deserialize_with_content(origin_pos, content)
worldedit.keep_loaded(pos1, pos2) 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 pos1, pos2 = worldedit.legacy_allocate_with_nodes(origin_pos, nodes)
local count = 0 worldedit.keep_loaded(pos1, pos2)
local add_node, get_meta = minetest.add_node, minetest.get_meta
for i, entry in ipairs(nodes) do local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z local count = 0
-- Entry acts as both position and node local add_node, get_meta = minetest.add_node, minetest.get_meta
add_node(entry, entry) for i, entry in ipairs(nodes) do
if entry.meta then entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
get_meta(entry):from_table(entry.meta) -- Entry acts as both position and node
add_node(entry, entry)
if entry.meta then
get_meta(entry):from_table(entry.meta)
end
end end
return #nodes
end end
return #nodes
end 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

View File

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

View File

@ -1,28 +1,22 @@
minetest.register_chatcommand("/outset", { worldedit.register_command("outset", {
params = "[h|v] <amount>", params = "[h/v] <amount>",
description = "outset the selection", description = "Outset the selected region.",
privs = {worldedit=true}, privs = {worldedit=true},
func = function(name, param) require_pos = 2,
parse = function(param)
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)") local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
if find == nil then if find == nil then
return false, "invalid usage: " .. param return false
end
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
return false,
"Undefined region. Region must be defined beforehand."
end end
local hv_test = dir:find("[^hv]+") local hv_test = dir:find("[^hv]+")
if hv_test ~= nil then if hv_test ~= nil then
return false, "Invalid direction." return false, "Invalid direction."
end end
return true, dir, tonumber(amount)
end,
func = function(name, dir, amount)
if dir == "" or dir == "hv" or dir == "vh" then if dir == "" or dir == "hv" or dir == "vh" then
assert(worldedit.cuboid_volumetric_expand(name, amount)) assert(worldedit.cuboid_volumetric_expand(name, amount))
elseif dir == "h" then elseif dir == "h" then
@ -40,35 +34,26 @@ minetest.register_chatcommand("/outset", {
worldedit.marker_update(name) worldedit.marker_update(name)
return true, "Region outset by " .. amount .. " blocks" return true, "Region outset by " .. amount .. " blocks"
end, end,
} })
)
minetest.register_chatcommand("/inset", { worldedit.register_command("inset", {
params = "[h|v] <amount>", params = "[h/v] <amount>",
description = "inset the selection", description = "Inset the selected region.",
privs = {worldedit=true}, privs = {worldedit=true},
func = function(name, param) require_pos = 2,
parse = function(param)
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)") local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
if find == nil then if find == nil then
return false, "invalid usage: " .. param return false
end end
if dir:find("[^hv]") ~= nil then
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if pos1 == nil or pos2 == nil then
return false,
"Undefined region. Region must be defined beforehand."
end
local hv_test = dir:find("[^hv]+")
if hv_test ~= nil then
return false, "Invalid direction." return false, "Invalid direction."
end end
return true, dir, tonumber(amount)
end,
func = function(name, dir, amount)
if dir == "" or dir == "vh" or dir == "hv" then if dir == "" or dir == "vh" or dir == "hv" then
assert(worldedit.cuboid_volumetric_expand(name, -amount)) assert(worldedit.cuboid_volumetric_expand(name, -amount))
elseif dir == "h" then elseif dir == "h" then
@ -86,30 +71,23 @@ minetest.register_chatcommand("/inset", {
worldedit.marker_update(name) worldedit.marker_update(name)
return true, "Region inset by " .. amount .. " blocks" return true, "Region inset by " .. amount .. " blocks"
end, end,
} })
)
minetest.register_chatcommand("/shift", { worldedit.register_command("shift", {
params = "[x|y|z|?|up|down|left|right|front|back] [+|-]<amount>", params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>",
description = "Moves the selection region. Does not move contents.", description = "Shifts the selection area without moving its contents",
privs = {worldedit=true}, privs = {worldedit=true},
func = function(name, param) require_pos = 2,
local pos1 = worldedit.pos1[name] parse = function(param)
local pos2 = worldedit.pos2[name]
local find, _, direction, amount = param:find("([%?%l]+)%s*([+-]?%d+)") local find, _, direction, amount = param:find("([%?%l]+)%s*([+-]?%d+)")
if find == nil then if find == nil then
worldedit.player_notify(name, "invalid usage: " .. param) return false
return
end
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name,
"Undefined region. Region must be defined beforehand.")
return
end end
return true, direction, tonumber(amount)
end,
func = function(name, direction, amount)
local axis, dir local axis, dir
if direction == "x" or direction == "y" or direction == "z" then if direction == "x" or direction == "y" or direction == "z" then
axis, dir = direction, 1 axis, dir = direction, 1
@ -128,113 +106,161 @@ minetest.register_chatcommand("/shift", {
return true, "Region shifted by " .. amount .. " nodes" return true, "Region shifted by " .. amount .. " nodes"
end, end,
} })
)
minetest.register_chatcommand("/expand", { worldedit.register_command("expand", {
params = "[+|-]<x|y|z|?|up|down|left|right|front|back> <amount> [reverse-amount]", params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = "expand the selection in one or two directions at once", description = "Expands the selection in the selected absolute or relative axis",
privs = {worldedit=true}, privs = {worldedit=true},
func = function(name, param) require_pos = 2,
local find, _, sign, direction, amount, parse = function(param)
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)") local find, _, sign, direction, amount,
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)")
if find == nil then if find == nil then
worldedit.player_notify(name, "invalid use: " .. param) return false
return
end
if worldedit.pos1[name] == nil or worldedit.pos2[name] == nil then
worldedit.player_notify(name,
"Undefined region. Region must be defined beforehand.")
return
end
local absolute = direction:find("[xyz?]")
local dir, axis
if rev_amount == "" then
rev_amount = 0
end
if absolute == nil then
axis, dir = worldedit.translate_direction(name, direction)
if axis == nil or dir == nil then
return false, "Invalid if looking straight up or down"
end end
else
if direction == "?" then if rev_amount == "" then
axis, dir = worldedit.player_axis(name) 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 else
axis = direction if direction == "?" then
dir = 1 axis, dir = worldedit.player_axis(name)
else
axis = direction
dir = 1
end
end end
end
if sign == "-" then if sign == "-" then
dir = -dir dir = -dir
end end
worldedit.cuboid_linear_expand(name, axis, dir, amount) worldedit.cuboid_linear_expand(name, axis, dir, amount)
worldedit.cuboid_linear_expand(name, axis, -dir, rev_amount) worldedit.cuboid_linear_expand(name, axis, -dir, rev_amount)
worldedit.marker_update(name) worldedit.marker_update(name)
return true, "Region expanded by " .. (amount + rev_amount) .. " nodes" return true, "Region expanded by " .. (amount + rev_amount) .. " nodes"
end, end,
} })
)
minetest.register_chatcommand("/contract", { worldedit.register_command("contract", {
params = "[+|-]<x|y|z|?|up|down|left|right|front|back> <amount> [reverse-amount]", params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
description = "contract the selection in one or two directions at once", description = "Contracts the selection in the selected absolute or relative axis",
privs = {worldedit=true}, privs = {worldedit=true},
func = function(name, param) require_pos = 2,
local find, _, sign, direction, amount, parse = function(param)
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)") local find, _, sign, direction, amount,
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)")
if find == nil then if find == nil then
worldedit.player_notify(name, "invalid use: " .. param) return false
return
end
if worldedit.pos1[name] == nil or worldedit.pos2[name] == nil then
worldedit.player_notify(name,
"Undefined region. Region must be defined beforehand.")
return
end
local absolute = direction:find("[xyz?]")
local dir, axis
if rev_amount == "" then
rev_amount = 0
end
if absolute == nil then
axis, dir = worldedit.translate_direction(name, direction)
if axis == nil or dir == nil then
return false, "Invalid if looking straight up or down"
end end
else
if direction == "?" then if rev_amount == "" then
axis, dir = worldedit.player_axis(name) 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 else
axis = direction if direction == "?" then
dir = 1 axis, dir = worldedit.player_axis(name)
else
axis = direction
dir = 1
end
end end
end
if sign == "-" then if sign == "-" then
dir = -dir dir = -dir
end end
worldedit.cuboid_linear_expand(name, axis, dir, -amount) worldedit.cuboid_linear_expand(name, axis, dir, -amount)
worldedit.cuboid_linear_expand(name, axis, -dir, -rev_amount) worldedit.cuboid_linear_expand(name, axis, -dir, -rev_amount)
worldedit.marker_update(name) worldedit.marker_update(name)
return true, "Region contracted by " .. (amount + rev_amount) .. " nodes" return true, "Region contracted by " .. (amount + rev_amount) .. " nodes"
end, 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,57 +2,60 @@ worldedit.marker1 = {}
worldedit.marker2 = {} worldedit.marker2 = {}
worldedit.marker_region = {} worldedit.marker_region = {}
local init_sentinel = "new" .. tostring(math.random(99999))
--marks worldedit region position 1 --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] 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 if worldedit.marker1[name] ~= nil then --marker already exists
worldedit.marker1[name]:remove() --remove marker worldedit.marker1[name]:remove() --remove marker
worldedit.marker1[name] = nil worldedit.marker1[name] = nil
end end
if pos1 ~= nil then if pos1 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos1)
--add marker --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 if worldedit.marker1[name] ~= nil then
worldedit.marker1[name]:get_luaentity().player_name = name worldedit.marker1[name]:get_luaentity().player_name = name
end end
end end
worldedit.mark_region(name) if region_too == nil or region_too then
worldedit.mark_region(name)
end
end end
--marks worldedit region position 2 --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] 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 if worldedit.marker2[name] ~= nil then --marker already exists
worldedit.marker2[name]:remove() --remove marker worldedit.marker2[name]:remove() --remove marker
worldedit.marker2[name] = nil worldedit.marker2[name] = nil
end end
if pos2 ~= nil then if pos2 ~= nil then
--make area stay loaded
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos2, pos2)
--add marker --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 if worldedit.marker2[name] ~= nil then
worldedit.marker2[name]:get_luaentity().player_name = name worldedit.marker2[name]:get_luaentity().player_name = name
end end
end end
worldedit.mark_region(name) if region_too == nil or region_too then
worldedit.mark_region(name)
end
end end
worldedit.mark_region = function(name) worldedit.mark_region = function(name)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
if worldedit.marker_region[name] ~= nil then --marker already exists 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 for _, entity in ipairs(worldedit.marker_region[name]) do
entity:remove() entity:remove()
end end
@ -64,7 +67,7 @@ worldedit.mark_region = function(name)
local vec = vector.subtract(pos2, pos1) local vec = vector.subtract(pos2, pos1)
local maxside = math.max(vec.x, math.max(vec.y, vec.z)) local maxside = math.max(vec.x, math.max(vec.y, vec.z))
local limit = tonumber(minetest.setting_get("active_object_send_range_blocks")) * 16 local limit = tonumber(minetest.settings:get("active_object_send_range_blocks")) * 16
if maxside > limit * 1.5 then if maxside > limit * 1.5 then
-- The client likely won't be able to see the plane markers as intended anyway, -- 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 -- thus don't place them and also don't load the area into memory
@ -82,7 +85,8 @@ worldedit.mark_region = function(name)
--XY plane markers --XY plane markers
for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
local marker = minetest.add_entity({x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}, "worldedit:region_cube") local entpos = {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 if marker ~= nil then
marker:set_properties({ marker:set_properties({
visual_size={x=sizex * 2, y=sizey * 2}, visual_size={x=sizex * 2, y=sizey * 2},
@ -95,13 +99,14 @@ worldedit.mark_region = function(name)
--YZ plane markers --YZ plane markers
for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
local marker = minetest.add_entity({x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}, "worldedit:region_cube") local entpos = {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 if marker ~= nil then
marker:set_properties({ marker:set_properties({
visual_size={x=sizez * 2, y=sizey * 2}, visual_size={x=sizez * 2, y=sizey * 2},
collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez}, collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez},
}) })
marker:setyaw(math.pi / 2) marker:set_yaw(math.pi / 2)
marker:get_luaentity().player_name = name marker:get_luaentity().player_name = name
table.insert(markers, marker) table.insert(markers, marker)
end end
@ -111,6 +116,13 @@ worldedit.mark_region = function(name)
end end
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", { minetest.register_entity(":worldedit:pos1", {
initial_properties = { initial_properties = {
visual = "cube", visual = "cube",
@ -120,9 +132,11 @@ minetest.register_entity(":worldedit:pos1", {
"worldedit_pos1.png", "worldedit_pos1.png"}, "worldedit_pos1.png", "worldedit_pos1.png"},
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
physical = false, physical = false,
static_save = false,
}, },
on_step = function(self, dtime) on_activate = function(self, staticdata, dtime_s)
if worldedit.marker1[self.player_name] == nil then if staticdata ~= init_sentinel then
-- we were loaded from before static_save = false was added
self.object:remove() self.object:remove()
end end
end, end,
@ -130,6 +144,9 @@ minetest.register_entity(":worldedit:pos1", {
self.object:remove() self.object:remove()
worldedit.marker1[self.player_name] = nil worldedit.marker1[self.player_name] = nil
end, end,
on_blast = function(self, damage)
return false, false, {} -- don't damage or knockback
end,
}) })
minetest.register_entity(":worldedit:pos2", { minetest.register_entity(":worldedit:pos2", {
@ -141,9 +158,11 @@ minetest.register_entity(":worldedit:pos2", {
"worldedit_pos2.png", "worldedit_pos2.png"}, "worldedit_pos2.png", "worldedit_pos2.png"},
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
physical = false, physical = false,
static_save = false,
}, },
on_step = function(self, dtime) on_activate = function(self, staticdata, dtime_s)
if worldedit.marker2[self.player_name] == nil then if staticdata ~= init_sentinel then
-- we were loaded from before static_save = false was added
self.object:remove() self.object:remove()
end end
end, end,
@ -151,20 +170,23 @@ minetest.register_entity(":worldedit:pos2", {
self.object:remove() self.object:remove()
worldedit.marker2[self.player_name] = nil worldedit.marker2[self.player_name] = nil
end, end,
on_blast = function(self, damage)
return false, false, {} -- don't damage or knockback
end,
}) })
minetest.register_entity(":worldedit:region_cube", { minetest.register_entity(":worldedit:region_cube", {
initial_properties = { initial_properties = {
visual = "upright_sprite", visual = "upright_sprite",
visual_size = {x=1.1, y=1.1},
textures = {"worldedit_cube.png"}, textures = {"worldedit_cube.png"},
visual_size = {x=10, y=10}, visual_size = {x=10, y=10},
physical = false, physical = false,
static_save = false,
}, },
on_step = function(self, dtime) on_activate = function(self, staticdata, dtime_s)
if worldedit.marker_region[self.player_name] == nil then if staticdata ~= init_sentinel then
-- we were loaded from before static_save = false was added
self.object:remove() self.object:remove()
return
end end
end, end,
on_punch = function(self, hitter) on_punch = function(self, hitter)
@ -177,5 +199,8 @@ minetest.register_entity(":worldedit:region_cube", {
end end
worldedit.marker_region[self.player_name] = nil worldedit.marker_region[self.player_name] = nil
end, 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_callback = {}
local safe_region_param = {}
local function check_region(name, param)
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] --obtain positions
if pos1 == nil or pos2 == nil then
worldedit.player_notify(name, "no region selected")
return nil
end
return worldedit.volume(pos1, pos2)
end
--`count` is the number of nodes that would possibly be modified
--`callback` is a callback to run when the user confirms --`callback` is a callback to run when the user confirms
--`nodes_needed` is a function accepting `param`, `pos1`, and `pos2` to calculate the number of nodes needed local function safe_region(name, count, callback)
local function safe_region(callback, nodes_needed) if count < 20000 then
--default node volume calculation return callback()
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 end
--save callback to call later
safe_region_callback[name] = callback
worldedit.player_notify(name, "WARNING: this operation could affect up to " .. count .. " nodes; type //y to continue or //n to cancel")
end end
local function reset_pending(name) local function reset_pending(name)
safe_region_callback[name], safe_region_param[name] = nil, nil safe_region_callback[name] = nil
end end
minetest.register_chatcommand("/y", { minetest.register_chatcommand("/y", {
params = "", params = "",
description = "Confirm a pending operation", description = "Confirm a pending operation",
func = function(name) func = function(name)
local callback, param = safe_region_callback[name], safe_region_param[name] local callback = safe_region_callback[name]
if not callback then if not callback then
worldedit.player_notify(name, "no operation pending") worldedit.player_notify(name, "no operation pending")
return return
end end
safe_region_callback[name], safe_region_param[name] = nil, nil --reset pending operation reset_pending(name)
callback(name, param) callback(name)
end, end,
}) })
minetest.register_chatcommand("/n", { minetest.register_chatcommand("/n", {
params = "", params = "",
description = "Confirm a pending operation", description = "Abort a pending operation",
func = function(name) func = function(name)
if not safe_region_callback[name] then if not safe_region_callback[name] then
worldedit.player_notify(name, "no operation pending") worldedit.player_notify(name, "no operation pending")
return return
end end
safe_region_callback[name], safe_region_param[name] = nil, nil
reset_pending(name)
end, end,
}) })
return safe_region, check_region, reset_pending return safe_region, reset_pending

View File

@ -1,22 +1,63 @@
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", { minetest.register_tool(":worldedit:wand", {
description = "WorldEdit Wand tool, Left-click to set 1st position, right-click to set 2nd", description = "WorldEdit Wand tool\nLeft-click to set 1st position, right-click to set 2nd",
inventory_image = "worldedit_wand.png", inventory_image = "worldedit_wand.png",
stack_max = 1, -- there is no need to have more than one 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 liquids_pointable = true, -- ground with only water on can be selected as well
on_use = function(itemstack, placer, pointed_thing) on_use = function(itemstack, placer, pointed_thing)
if placer ~= nil and pointed_thing ~= nil and pointed_thing.type == "node" then if placer == nil or pointed_thing == nil then return end
local name = placer:get_player_name() local name = placer:get_player_name()
worldedit.pos1[name] = pointed_thing.under if pointed_thing.type == "node" then
-- set and mark pos1
worldedit.pos1[name] = above_or_under(placer, pointed_thing)
worldedit.mark_pos1(name) 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
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 return itemstack -- nothing consumed, nothing changed
end, end,
on_place = function(itemstack, placer, pointed_thing) -- Left Click on_secondary_use = function(itemstack, user, pointed_thing)
if placer ~= nil and pointed_thing ~= nil and pointed_thing.type == "node" then if user == nil or (pointed_thing or {}).type ~= "object" then
local name = placer:get_player_name() return itemstack
worldedit.pos2[name] = pointed_thing.under 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) worldedit.mark_pos2(name)
end end
return itemstack -- nothing consumed, nothing changed return itemstack -- nothing consumed, nothing changed

View File

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

View File

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

View File

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

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

View File

@ -1 +0,0 @@
worldedit_commands

View File

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

View File

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