Compare commits
354 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ae77f73650 | ||
|
28374f4f27 | ||
|
fb7a37e87c | ||
|
57e7d4c488 | ||
|
e20a5a4e09 | ||
|
f8442ef9fd | ||
|
86de970552 | ||
|
4c8d42bf7b | ||
|
5a00c07c68 | ||
|
cda772b18d | ||
|
8c758671bc | ||
|
456ce8c800 | ||
|
8065e3d804 | ||
|
60b6b205ad | ||
|
1d8d9a704f | ||
|
d13ba673fe | ||
|
acb3ecefe4 | ||
|
dc1150fe3d | ||
|
74663869f7 | ||
|
4b470bdae6 | ||
|
75d101116d | ||
|
860d4a267d | ||
|
372847e774 | ||
|
883caff58d | ||
|
41d53180b1 | ||
|
602f175cc0 | ||
|
e6fac23c53 | ||
|
5914eab20b | ||
|
002dc462d6 | ||
|
a713efe051 | ||
|
469c3bf70b | ||
|
f75700ed76 | ||
|
575bfca67a | ||
|
eac05e3133 | ||
|
41efbaf210 | ||
|
045c7510bf | ||
|
b90eeb1e68 | ||
|
341014f94a | ||
|
b84aa8508a | ||
|
17df0bbf71 | ||
|
36b14413e0 | ||
|
1fc6d93112 | ||
|
ccfb6b4d61 | ||
|
8f60e6f729 | ||
|
8f86a2120c | ||
|
b4202ea779 | ||
|
689ff90a78 | ||
|
bf55f52197 | ||
|
79e5e64c44 | ||
|
375fbf3c68 | ||
|
cc3aab00bc | ||
|
eff01bc8e7 | ||
|
099d5047bd | ||
|
7f7e928dd9 | ||
|
1a9f66f091 | ||
|
7a5d76a9bc | ||
|
5260f595c6 | ||
|
7a645eba05 | ||
|
9417f2bbf1 | ||
|
abc9efeeb8 | ||
|
c223ca4cec | ||
|
c8afa95542 | ||
|
670e421f57 | ||
|
770601dd5d | ||
|
2f2f5a7def | ||
|
7f87f1658e | ||
|
4378750498 | ||
|
2f26fb7645 | ||
|
f1b6da3c20 | ||
|
418a30c89e | ||
|
2f535dd053 | ||
|
867cd6edc7 | ||
|
4918610c43 | ||
|
edd27636a6 | ||
|
7d0811fd47 | ||
|
4d08a1cc55 | ||
|
34844b2d38 | ||
|
0f5dcc790d | ||
|
37de177f72 | ||
|
f285a55dd5 | ||
|
9b26034aea | ||
|
963a9f6b96 | ||
|
5e8bfd5166 | ||
|
06a3b63578 | ||
|
5e47af8773 | ||
|
da51c688d2 | ||
|
ab93871877 | ||
|
116ae25ebd | ||
|
18444379a1 | ||
|
974dcaa918 | ||
|
abdecd4239 | ||
|
9c38b9f6ad | ||
|
7833b68c85 | ||
|
03bef18272 | ||
|
91d02f6f5b | ||
|
8feaf8a21d | ||
|
5bab991e0b | ||
|
9bb0ad0442 | ||
|
63a7069671 | ||
|
79097dc6c8 | ||
|
3bc7a0f97d | ||
|
45acf09e4a | ||
|
adab528f8a | ||
|
26b6682587 | ||
|
b4826aa821 | ||
|
a0181ea897 | ||
|
2a4aaae8a2 | ||
|
f6298d7b87 | ||
|
68f7bcc728 | ||
|
0919f7cab3 | ||
|
de566d2e3e | ||
|
cc897150f2 | ||
|
f39a8e264d | ||
|
da5abec273 | ||
|
ff53a71d58 | ||
|
0edcd1a59d | ||
|
a1b6a34e61 | ||
|
f589c988b1 | ||
|
f28d9b8d35 | ||
|
b8453a0e0b | ||
|
0a16f11d93 | ||
|
b50294d10c | ||
|
324eb556f6 | ||
|
3ffecdd9c4 | ||
|
40b49ee9bc | ||
|
4f2c7b18cc | ||
|
b2e086f9ec | ||
|
d1cbd420bb | ||
|
0aeee79af6 | ||
|
f242f2f722 | ||
|
f8e57559ec | ||
|
b2b2b9364d | ||
|
b37605943b | ||
|
f7256633c0 | ||
|
99fa0ebd75 | ||
|
a97cccd2a1 | ||
|
3322ef90c4 | ||
|
b259906fd0 | ||
|
12270dc094 | ||
|
415000e797 | ||
|
7b1deb1b61 | ||
|
4605596215 | ||
|
59dc2d34d9 | ||
|
283f47f10d | ||
|
35ad2f031d | ||
|
d572d769ce | ||
|
5bbb6b3909 | ||
|
d6a9b517b5 | ||
|
3c61759bae | ||
|
2f4eb19a3a | ||
|
2cb3fcde66 | ||
|
e680d8087e | ||
|
870873ad15 | ||
|
e356f4521c | ||
|
bf8e2a8233 | ||
|
6ceb56c3dc | ||
|
1fabe60d77 | ||
|
acc9188828 | ||
|
3240167b13 | ||
|
66b1fa032d | ||
|
a31f955fb1 | ||
|
38e9b42caf | ||
|
ea465f8fe4 | ||
|
0ce45a5900 | ||
|
56f77a2f27 | ||
|
610cd9981d | ||
|
c0f3bb6958 | ||
|
e49f717000 | ||
|
6e7b9a60be | ||
|
bcac45a476 | ||
|
426f3b949f | ||
|
78e4ba828e | ||
|
92fe95fab7 | ||
|
3a7fb5bf1e | ||
|
aa0e46d4e2 | ||
|
83288c969e | ||
|
f9311b2b15 | ||
|
15f0cea72e | ||
|
6e2e2385e9 | ||
|
152707a322 | ||
|
d040d324e8 | ||
|
df6b54d2f5 | ||
|
5abe1cee3d | ||
|
5afea424ba | ||
|
2aed498849 | ||
|
0b68b2aec6 | ||
|
f72abdb766 | ||
|
e18525d8c9 | ||
|
78890dde07 | ||
|
b922097e7a | ||
|
48f3f59fc3 | ||
|
f8f1f3b9cc | ||
|
d5e004be67 | ||
|
fad021d0ba | ||
|
f1fe0c6bba | ||
|
40cee99700 | ||
|
04d20de4cd | ||
|
859c6bd12a | ||
|
240380ff16 | ||
|
8d213d32a0 | ||
|
ae29a9f064 | ||
|
6cf8b92434 | ||
|
0b97a7c740 | ||
|
900b2f25aa | ||
|
f49663902c | ||
|
d475682d8f | ||
|
9e087ff995 | ||
|
b23e92921c | ||
|
61ab240cea | ||
|
b06e004d80 | ||
|
6dddc93ed7 | ||
|
b99a51f468 | ||
|
91c5053e67 | ||
|
f2f714c19e | ||
|
47712844a3 | ||
|
2bd4d6fa8f | ||
|
e9e8de385c | ||
|
2e2fcfdfa2 | ||
|
e0a2661700 | ||
|
7a19c5303b | ||
|
35b68c481b | ||
|
51158eca9f | ||
|
3aa315f134 | ||
|
3d30588a68 | ||
|
48f9c6c23f | ||
|
5f9efb1205 | ||
|
fc037e9c82 | ||
|
90d6b3d237 | ||
|
4bd5d56909 | ||
|
163dffccb3 | ||
|
6b2fe397e6 | ||
|
5c115e282c | ||
|
ab47385f7b | ||
|
78915d4a54 | ||
|
09de34aabf | ||
|
c1bd4986b0 | ||
|
bea38a116a | ||
|
4336e7ca14 | ||
|
eca54f0851 | ||
|
b0fbcf197f | ||
|
b468e24a20 | ||
|
bb8456b711 | ||
|
1f277147ca | ||
|
04fdf92aca | ||
|
7f580611f5 | ||
|
4799068551 | ||
|
5b03c83f6b | ||
|
e4d007199f | ||
|
fb49cc4bcc | ||
|
eb88bf6ade | ||
|
02c36c16d5 | ||
|
796aa3870d | ||
|
1024eace6f | ||
|
70c24c9501 | ||
|
18efe50e1e | ||
|
44c9576f8a | ||
|
e383e8ce00 | ||
|
82ef580fae | ||
|
38fbc0a046 | ||
|
4b178bf709 | ||
|
d8aa7e72a7 | ||
|
58d7a7134e | ||
|
420655bd94 | ||
|
ea84eee0e5 | ||
|
9ec1a799d8 | ||
|
f5b67c5bc2 | ||
|
6084db9335 | ||
|
b70fd16da4 | ||
|
9616c7d944 | ||
|
175ac211ca | ||
|
b32aadd7fa | ||
|
c22b556511 | ||
|
174416b010 | ||
|
2c4a791805 | ||
|
4660927ca0 | ||
|
2784a25561 | ||
|
b29523db4d | ||
|
0dddffa544 | ||
|
b8b8db42bf | ||
|
18417ff4fd | ||
|
84f0e23d6c | ||
|
be913e6d8b | ||
|
82f39460cc | ||
|
6b50025ccc | ||
|
829f9eef73 | ||
|
f8d3614a32 | ||
|
86d35e97bf | ||
|
277b57a82a | ||
|
d2635544c6 | ||
|
2ac049c823 | ||
|
d73b93d8dd | ||
|
8c0b61194c | ||
|
2e41f0076d | ||
|
2fcea5a3c0 | ||
|
df6ab06e74 | ||
|
4e78ced693 | ||
|
ad299cacc5 | ||
|
06cb2934b1 | ||
|
c48cf3c051 | ||
|
674d6473e4 | ||
|
3767ea84d2 | ||
|
36424e3dec | ||
|
3cffae4948 | ||
|
a4881225a4 | ||
|
0797c47afb | ||
|
89d4548595 | ||
|
6c03ab9c4a | ||
|
dbc2348014 | ||
|
3fa9dfd19c | ||
|
d4b39cba9b | ||
|
ac7430e02f | ||
|
b3ba6f2433 | ||
|
d5ec1bcefc | ||
|
203c3da2af | ||
|
272541c9da | ||
|
9ab52df56a | ||
|
4c43bf3600 | ||
|
03327a767d | ||
|
8bd5db51b2 | ||
|
416ffb3709 | ||
|
040282bbe6 | ||
|
b0bf52e9b6 | ||
|
3c51ec8c4a | ||
|
49b683f27f | ||
|
c1f3cfc1e4 | ||
|
b252df2166 | ||
|
7e1be63344 | ||
|
faa4a7b065 | ||
|
8b9054a1cc | ||
|
dfcf02bbc2 | ||
|
0fddd17f23 | ||
|
e550604747 | ||
|
8ebf9d3c2a | ||
|
ac5e801834 | ||
|
b6bc841c39 | ||
|
48009b6c62 | ||
|
9db6192eba | ||
|
1e5f623cbb | ||
|
58970e7fab | ||
|
7070f81c59 | ||
|
a192d51a58 | ||
|
d4187866db | ||
|
74018dab99 | ||
|
f1cfc47189 | ||
|
0664cde287 | ||
|
f952ee4740 | ||
|
a1dcc43210 | ||
|
d7ae19b7b8 | ||
|
52c1379bce | ||
|
d52f3d649e | ||
|
cedecac3f8 | ||
|
18bc4e6815 | ||
|
9e1c2614d3 | ||
|
98c5bc5c5f |
15
.github/workflows/check.yml
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
name: "Check"
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: "Luacheck"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: apt
|
||||
run: sudo apt-get install -y luarocks
|
||||
- name: luacheck install
|
||||
run: luarocks install --local luacheck
|
||||
- name: luacheck run
|
||||
run: $HOME/.luarocks/bin/luacheck ./
|
27
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: "Test"
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: "Unit Tests ${{ matrix.cfg.image }}"
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
strategy:
|
||||
matrix:
|
||||
cfg:
|
||||
- { image: 'registry.gitlab.com/minetest/minetest/server:5.0.1', mtg: false }
|
||||
- { image: 'registry.gitlab.com/minetest/minetest/server:5.5.1', mtg: false }
|
||||
- { image: '', mtg: true } # latest mater
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'minetest/minetest_game'
|
||||
path: minetest_game
|
||||
if: ${{ matrix.cfg.mtg }}
|
||||
|
||||
- name: Run tests
|
||||
run: ./.util/run_tests.sh --docker
|
||||
env:
|
||||
DOCKER_IMAGE: "${{ matrix.cfg.image }}"
|
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*~
|
||||
|
28
.luacheckrc
Normal file
|
@ -0,0 +1,28 @@
|
|||
read_globals = {
|
||||
"minetest", "VoxelArea", "ItemStack",
|
||||
"unified_inventory", "sfinv", "smart_inventory", "inventory_plus",
|
||||
"dump",
|
||||
|
||||
table = {fields = {"copy", "indexof", "insert_all"}},
|
||||
vector = {fields = {
|
||||
-- as of 5.0
|
||||
"new", "direction", "distance", "length", "normalize", "floor", "round",
|
||||
"apply", "equals", "sort", "add", "subtract", "multiply", "divide",
|
||||
-- polyfilled
|
||||
"copy"
|
||||
}},
|
||||
}
|
||||
globals = {"worldedit"}
|
||||
|
||||
-- Ignore these errors until someone decides to fix them
|
||||
ignore = {"212", "213", "411", "412", "421", "422", "431", "432", "631"}
|
||||
|
||||
files["worldedit/common.lua"] = {
|
||||
globals = {"vector"},
|
||||
}
|
||||
files["worldedit/test"] = {
|
||||
read_globals = {"testnode1", "testnode2", "testnode3", "area", "check", "place_pattern"},
|
||||
}
|
||||
files["worldedit/test/init.lua"] = {
|
||||
globals = {"testnode1", "testnode2", "testnode3", "area", "check", "place_pattern"},
|
||||
}
|
42
.util/run_tests.sh
Executable file
|
@ -0,0 +1,42 @@
|
|||
#!/bin/bash
|
||||
tempdir=$(mktemp -d)
|
||||
confpath=$tempdir/minetest.conf
|
||||
worldpath=$tempdir/world
|
||||
trap 'rm -rf "$tempdir"' EXIT
|
||||
|
||||
[ -f worldedit/mod.conf ] || { echo "Must be run in modpack root folder." >&2; exit 1; }
|
||||
|
||||
mtserver=
|
||||
if [ "$1" == "--docker" ]; then
|
||||
command -v docker >/dev/null || { echo "Docker is not installed." >&2; exit 1; }
|
||||
[ -d minetest_game ] || echo "A source checkout of minetest_game was not found. This can fail if your docker image does not ship a game." >&2;
|
||||
else
|
||||
mtserver=$(command -v minetestserver)
|
||||
[[ -z "$mtserver" && -x ../../bin/minetestserver ]] && mtserver=../../bin/minetestserver
|
||||
[ -z "$mtserver" ] && { echo "To run the test outside of Docker, an installation of minetestserver is required." >&2; exit 1; }
|
||||
fi
|
||||
|
||||
mkdir $worldpath
|
||||
printf '%s\n' mg_name=singlenode '[end_of_params]' >$worldpath/map_meta.txt
|
||||
printf '%s\n' worldedit_run_tests=true max_forceloaded_blocks=9999 >$confpath
|
||||
|
||||
if [ -z "$mtserver" ]; then
|
||||
chmod -R 777 $tempdir
|
||||
[ -z "$DOCKER_IMAGE" ] && DOCKER_IMAGE="ghcr.io/minetest/minetest:master"
|
||||
vol=(
|
||||
-v "$confpath":/etc/minetest/minetest.conf
|
||||
-v "$tempdir":/var/lib/minetest/.minetest
|
||||
-v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit
|
||||
)
|
||||
[ -d minetest_game ] && vol+=(
|
||||
-v "$PWD/minetest_game":/var/lib/minetest/.minetest/games/minetest_game
|
||||
)
|
||||
docker run --rm -i "${vol[@]}" "$DOCKER_IMAGE"
|
||||
else
|
||||
mkdir $worldpath/worldmods
|
||||
ln -s "$PWD/worldedit" $worldpath/worldmods/worldedit
|
||||
$mtserver --config "$confpath" --world "$worldpath" --logfile /dev/null
|
||||
fi
|
||||
|
||||
test -f $worldpath/tests_ok || exit 1
|
||||
exit 0
|
266
Chat Commands.md
|
@ -1,266 +0,0 @@
|
|||
Chat Commands
|
||||
-------------
|
||||
For more information, see the [README](README.md).
|
||||
|
||||
### //reset
|
||||
|
||||
Reset the region so that it is empty.
|
||||
|
||||
//reset
|
||||
|
||||
### //mark
|
||||
|
||||
Show markers at the region positions.
|
||||
|
||||
//mark
|
||||
|
||||
### //pos1
|
||||
|
||||
Set WorldEdit region position 1 to the player's location.
|
||||
|
||||
//pos1
|
||||
|
||||
### //pos2
|
||||
|
||||
Set WorldEdit region position 2 to the player's location.
|
||||
|
||||
//pos2
|
||||
|
||||
### //p set/set1/set2/get
|
||||
|
||||
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region.
|
||||
|
||||
//p set
|
||||
//p set1
|
||||
//p set2
|
||||
//p get
|
||||
|
||||
### //volume
|
||||
|
||||
Display the volume of the current WorldEdit region.
|
||||
|
||||
//volume
|
||||
|
||||
### //set <node>
|
||||
|
||||
Set the current WorldEdit region to <node>.
|
||||
|
||||
//set dirt
|
||||
//set default:glass
|
||||
//set mesecons:mesecon
|
||||
|
||||
### //replace <search node> <replace node>
|
||||
|
||||
Replace all instances of <search node> with <replace node> in the current WorldEdit region.
|
||||
|
||||
//replace cobble stone
|
||||
//replace default:steelblock glass
|
||||
//replace dirt flowers:flower_waterlily
|
||||
//replace flowers:flower_rose flowers:flower_tulip
|
||||
|
||||
### //replaceinverse <search node> <replace node>
|
||||
|
||||
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region.
|
||||
|
||||
//replaceinverse air stone
|
||||
//replaceinverse water_source default:dirt
|
||||
//replaceinverse mesecons:mesecon air
|
||||
//replaceinverse default:steelblock default:glass
|
||||
|
||||
### //hollowsphere <radius> <node>
|
||||
|
||||
Add hollow sphere at WorldEdit position 1 with radius <radius>, composed of <node>.
|
||||
|
||||
//hollowsphere 5 dirt
|
||||
//hollowsphere 12 default:glass
|
||||
//hollowsphere 17 mesecons:mesecon
|
||||
|
||||
### //sphere <radius> <node>
|
||||
|
||||
Add sphere at WorldEdit position 1 with radius <radius>, composed of <node>.
|
||||
|
||||
//sphere 5 dirt
|
||||
//sphere 12 default:glass
|
||||
//sphere 17 mesecons:mesecon
|
||||
|
||||
### //hollowdome <radius> <node>
|
||||
|
||||
Add hollow dome at WorldEdit position 1 with radius <radius>, composed of <node>.
|
||||
|
||||
//hollowdome 5 dirt
|
||||
//hollowdome 12 default:glass
|
||||
//hollowdome 17 mesecons:mesecon
|
||||
|
||||
### //dome <radius> <node>
|
||||
|
||||
Add dome at WorldEdit position 1 with radius <radius>, composed of <node>.
|
||||
|
||||
//dome 5 dirt
|
||||
//dome 12 default:glass
|
||||
//dome 17 mesecons:mesecon
|
||||
|
||||
### //hollowcylinder x/y/z/? <length> <radius> <node>
|
||||
|
||||
Add hollow cylinder at WorldEdit position 1 along the x/y/z/? axis with length <length> and radius <radius>, composed of <node>.
|
||||
|
||||
//hollowcylinder x +5 8 dirt
|
||||
//hollowcylinder y 28 10 default:glass
|
||||
//hollowcylinder z -12 3 mesecons:mesecon
|
||||
//hollowcylinder ? 2 4 stone
|
||||
|
||||
### //cylinder x/y/z/? <length> <radius> <node>
|
||||
|
||||
Add cylinder at WorldEdit position 1 along the x/y/z/? axis with length <length> and radius <radius>, composed of <node>.
|
||||
|
||||
//cylinder x +5 8 dirt
|
||||
//cylinder y 28 10 default:glass
|
||||
//cylinder z -12 3 mesecons:mesecon
|
||||
//cylinder ? 2 4 stone
|
||||
|
||||
### //pyramid <height> <node>
|
||||
|
||||
Add pyramid at WorldEdit position 1 with height <height>, composed of <node>.
|
||||
|
||||
//pyramid 8 dirt
|
||||
//pyramid 5 default:glass
|
||||
//pyramid 2 stone
|
||||
|
||||
### //spiral <width> <height> <spacer> <node>
|
||||
|
||||
Add spiral at WorldEdit position 1 with width <width>, height <height>, space between walls <spacer>, composed of <node>.
|
||||
|
||||
//spiral 20 5 3 dirt
|
||||
//spiral 5 2 1 default:glass
|
||||
//spiral 7 1 5 stone
|
||||
|
||||
### //copy x/y/z/? <amount>
|
||||
|
||||
Copy the current WorldEdit region along the x/y/z/? axis by <amount> nodes.
|
||||
|
||||
//copy x 15
|
||||
//copy y -7
|
||||
//copy z +4
|
||||
//copy ? 8
|
||||
|
||||
### //move x/y/z/? <amount>
|
||||
|
||||
Move the current WorldEdit positions and region along the x/y/z/? axis by <amount> nodes.
|
||||
|
||||
//move x 15
|
||||
//move y -7
|
||||
//move z +4
|
||||
//move ? -1
|
||||
|
||||
### //stack x/y/z/? <count>
|
||||
|
||||
Stack the current WorldEdit region along the x/y/z/? axis <count> times.
|
||||
|
||||
//stack x 3
|
||||
//stack y -1
|
||||
//stack z +5
|
||||
//stack ? 12
|
||||
|
||||
### //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 x y
|
||||
//transpose x z
|
||||
//transpose y z
|
||||
//transpose ? y
|
||||
|
||||
### //flip x/y/z/?
|
||||
|
||||
Flip the current WorldEdit region along the x/y/z/? axis.
|
||||
|
||||
//flip x
|
||||
//flip y
|
||||
//flip z
|
||||
//flip ?
|
||||
|
||||
### //rotate x/y/z/? <angle>
|
||||
|
||||
Rotate the current WorldEdit positions and region along the x/y/z/? axis by angle <angle> (90 degree increment).
|
||||
|
||||
//rotate x 90
|
||||
//rotate y 180
|
||||
//rotate z 270
|
||||
//rotate ? -90
|
||||
|
||||
### //orient <angle>
|
||||
|
||||
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)
|
||||
|
||||
//orient 90
|
||||
//orient 180
|
||||
//orient 270
|
||||
//orient -90
|
||||
|
||||
### //fixlight
|
||||
|
||||
Fixes the lighting in the current WorldEdit region.
|
||||
|
||||
//fixlight
|
||||
|
||||
### //hide
|
||||
|
||||
Hide all nodes in the current WorldEdit region non-destructively.
|
||||
|
||||
//hide
|
||||
|
||||
### //suppress <node>
|
||||
|
||||
Suppress all <node> in the current WorldEdit region non-destructively.
|
||||
|
||||
//suppress dirt
|
||||
//suppress default:glass
|
||||
//suppress mesecons:mesecon
|
||||
|
||||
### //highlight <node>
|
||||
|
||||
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively.
|
||||
|
||||
//highlight dirt
|
||||
//highlight default:glass
|
||||
//highlight mesecons:mesecon
|
||||
|
||||
### //restore
|
||||
|
||||
Restores nodes hidden with WorldEdit in the current WorldEdit region.
|
||||
|
||||
//restore
|
||||
|
||||
### //save <file>
|
||||
|
||||
Save the current WorldEdit region to "(world folder)/schems/<file>.we".
|
||||
|
||||
//save some random filename
|
||||
//save huge_base
|
||||
|
||||
### //allocate <file>
|
||||
|
||||
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region.
|
||||
|
||||
//allocate some random filename
|
||||
//allocate huge_base
|
||||
|
||||
### //load <file>
|
||||
|
||||
Load nodes from "(world folder)/schems/<file>.we" with position 1 of the current WorldEdit region as the origin.
|
||||
|
||||
//load some random filename
|
||||
//load huge_base
|
||||
|
||||
### //lua <code>
|
||||
|
||||
Executes <code> as a Lua chunk in the global namespace.
|
||||
|
||||
//lua worldedit.pos1["singleplayer"] = {x=0, y=0, z=0}
|
||||
//lua worldedit.rotate(worldedit.pos1["singleplayer"], worldedit.pos2["singleplayer"], "y", 90)
|
||||
|
||||
### //luatransform <code>
|
||||
|
||||
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region.
|
||||
|
||||
//luatransform minetest.env:add_node(pos, {name="default:stone"})
|
||||
//luatransform if minetest.env:get_node(pos).name == "air" then minetest.env:add_node(pos, {name="default:water_source"})
|
510
ChatCommands.md
Normal file
|
@ -0,0 +1,510 @@
|
|||
Chat Commands
|
||||
-------------
|
||||
For more information, see the [README](README.md).
|
||||
|
||||
Many commands also have shorter names that can be typed faster. For example, if we wanted to use `//move ? 5`, we could instead type `//m ? 5`. All shortened names are listed below:
|
||||
|
||||
| Short Name | Original Name |
|
||||
|:-----------|:-------------------|
|
||||
| `//i` | `//inspect` |
|
||||
| `//rst` | `//reset` |
|
||||
| `//mk` | `//mark` |
|
||||
| `//umk` | `//unmark` |
|
||||
| `//1` | `//pos1` |
|
||||
| `//2` | `//pos2` |
|
||||
| `//fp` | `//fixedpos` |
|
||||
| `//v` | `//volume` |
|
||||
| `//s` | `//set` |
|
||||
| `//r` | `//replace` |
|
||||
| `//ri` | `//replaceinverse` |
|
||||
| `//hcube` | `//hollowcube` |
|
||||
| `//hspr` | `//hollowsphere` |
|
||||
| `//spr` | `//sphere` |
|
||||
| `//hdo` | `//hollowdome` |
|
||||
| `//do` | `//dome` |
|
||||
| `//hcyl` | `//hollowcylinder` |
|
||||
| `//cyl` | `//cylinder` |
|
||||
| `//hpyr` | `//hollowpyramid` |
|
||||
| `//pyr` | `//pyramid` |
|
||||
|
||||
### `//about`
|
||||
|
||||
Get information about the WorldEdit mod.
|
||||
|
||||
//about
|
||||
|
||||
### `//help [all/<cmd>]`
|
||||
|
||||
Get help for WorldEdit commands. `all` shows all WorldEdit commands, `<cmd>`
|
||||
the help text for the given command.
|
||||
|
||||
//help
|
||||
//help all
|
||||
//help hollowpyramid
|
||||
|
||||
|
||||
### `//inspect [on/off/1/0/true/false/yes/no/enable/disable]`
|
||||
|
||||
Enable or disable node inspection.
|
||||
|
||||
//inspect on
|
||||
//inspect off
|
||||
//inspect
|
||||
|
||||
### `//reset`
|
||||
|
||||
Reset the region so that it is empty.
|
||||
|
||||
//reset
|
||||
|
||||
### `//mark`
|
||||
|
||||
Show markers at the region positions.
|
||||
|
||||
//mark
|
||||
|
||||
### `//unmark`
|
||||
|
||||
Hide markers if currently shown.
|
||||
|
||||
//unmark
|
||||
|
||||
### `//pos1`
|
||||
|
||||
Set WorldEdit region position 1 to the player's location.
|
||||
|
||||
//pos1
|
||||
|
||||
### `//pos2`
|
||||
|
||||
Set WorldEdit region position 2 to the player's location.
|
||||
|
||||
//pos2
|
||||
|
||||
### `//p set/set1/set2/get`
|
||||
|
||||
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by
|
||||
punching nodes, or print the current WorldEdit region.
|
||||
|
||||
//p set
|
||||
//p set1
|
||||
//p set2
|
||||
//p get
|
||||
|
||||
### `//fixedpos set1/set2 <x> <y> <z>`
|
||||
|
||||
Set the WorldEdit region position 1 or 2 to the position (`<x>`, `<y>`, `<z>`).
|
||||
|
||||
//fixedpos set1 0 0 0
|
||||
//fixedpos set1 -30 5 28
|
||||
//fixedpos set2 1004 -200 432
|
||||
|
||||
### `//volume`
|
||||
|
||||
Display the volume of the current WorldEdit region.
|
||||
|
||||
//volume
|
||||
|
||||
### `//deleteblocks`
|
||||
|
||||
Delete the MapBlocks (16x16x16 units) that contain the selected region. This means that mapgen will be invoked for that area. As only whole MapBlocks get removed, the deleted area is usually larger than the selected one. Also, mapgen can trigger mechanisms like mud reflow or cavegen, which affects nodes (up to 112 nodes away) outside the MapBlock, so dont use this near buildings. Note that active entities are not part of a MapBlock and do not get deleted.
|
||||
|
||||
//deleteblocks
|
||||
|
||||
### `//set <node>`
|
||||
|
||||
Set the current WorldEdit region to `<node>`.
|
||||
|
||||
//set air
|
||||
//set cactus
|
||||
//set Blue Lightstone
|
||||
//set dirt with grass
|
||||
|
||||
### `//param2 <param2>`
|
||||
|
||||
Set the param2 value of all nodes in the current WorldEdit region to `<param2>`.
|
||||
|
||||
//param2 8
|
||||
|
||||
### `//mix <node1> [count1] <node2> [count2] ...`
|
||||
|
||||
Fill the current WorldEdit region with a random mix of `<node1>`, `<node2>`, `...`.
|
||||
Weightings can be optionally specified via the `[count1]`, `[count2]`, `...` parameters after a node name.
|
||||
|
||||
//mix air
|
||||
//mix cactus stone glass sandstone
|
||||
//mix Bronze
|
||||
//mix default:cobble air
|
||||
//mix stone 3 dirt 2
|
||||
//mix cobblestone 8 stoneblock 2 stonebrick
|
||||
|
||||
### `//replace <search node> <replace node>`
|
||||
|
||||
Replace all instances of `<search node>` with `<replace node>` in the current WorldEdit region.
|
||||
|
||||
//replace Cobblestone air
|
||||
//replace lightstone_blue glass
|
||||
//replace dirt Bronze Block
|
||||
//replace mesecons:wire_00000000_off flowers:flower_tulip
|
||||
|
||||
### `//replaceinverse <search node> <replace node>`
|
||||
|
||||
Replace all nodes other than `<search node>` with `<replace node>` in the current WorldEdit region.
|
||||
|
||||
//replaceinverse Cobblestone air
|
||||
//replaceinverse flowers:flower_waterlily glass
|
||||
//replaceinverse dirt Bronze Block
|
||||
//replaceinverse mesecons:wire_00000000_off flowers:flower_tulip
|
||||
|
||||
### `//hollowcube <width> <height> <length> <node>`
|
||||
|
||||
Adds a hollow cube with its ground level centered at WorldEdit position 1 with
|
||||
dimensions `<width>` x `<height>` x `<length>`, composed of `<node>`.
|
||||
|
||||
//hollowcube 6 5 6 Diamond Block
|
||||
|
||||
### `//cube <width> <height> <length> <node>`
|
||||
|
||||
Adds a cube with its ground level centered at WorldEdit position 1 with
|
||||
dimensions `<width>` x `<height>` x `<length>`, composed of `<node>`.
|
||||
|
||||
//cube 6 5 6 Diamond Block
|
||||
//cube 7 2 1 default:cobble
|
||||
|
||||
### `//hollowsphere <radius> <node>`
|
||||
|
||||
Add hollow sphere centered at WorldEdit position 1 with radius `<radius>`, composed of `<node>`.
|
||||
|
||||
//hollowsphere 5 Diamond Block
|
||||
//hollowsphere 12 glass
|
||||
//hollowsphere 17 mesecons:wire_00000000_off
|
||||
|
||||
### `//sphere <radius> <node>`
|
||||
|
||||
Add sphere centered at WorldEdit position 1 with radius `<radius>`, composed of `<node>`.
|
||||
|
||||
//sphere 5 Diamond Block
|
||||
//sphere 12 glass
|
||||
//sphere 17 mesecons:wire_00000000_off
|
||||
|
||||
### `//hollowdome <radius> <node>`
|
||||
|
||||
Add hollow dome centered at WorldEdit position 1 with radius `<radius>`, composed of `<node>`.
|
||||
|
||||
//hollowdome 5 Diamond Block
|
||||
//hollowdome -12 glass
|
||||
//hollowdome 17 mesecons:wire_00000000_off
|
||||
|
||||
### `//dome <radius> <node>`
|
||||
|
||||
Add dome centered at WorldEdit position 1 with radius `<radius>`, composed of `<node>`.
|
||||
|
||||
//dome 5 Diamond Block
|
||||
//dome -12 glass
|
||||
//dome 17 mesecons:wire_00000000_off
|
||||
|
||||
### `//hollowcylinder x/y/z/? <length> <radius1> [radius2] <node>`
|
||||
|
||||
Add hollow cylinder at WorldEdit position 1 along the given axis with length `<length>`,
|
||||
base radius `<radius1>` (and top radius `[radius2]`), composed of `<node>`.
|
||||
|
||||
Despite its name this command allows you to create cones (`radius2` = 0) as well as any shapes inbetween (0 < `radius2` < `radius1`).
|
||||
Swapping `radius1` and `radius2` will create the same object but upside-down.
|
||||
|
||||
//hollowcylinder x +5 8 Bronze Block
|
||||
//hollowcylinder y 28 10 glass
|
||||
//hollowcylinder z -12 3 mesecons:wire_00000000_off
|
||||
//hollowcylinder ? 2 4 default:stone
|
||||
|
||||
//hollowcylinder y 10 10 0 walls:cobble
|
||||
//hollowcylinder x 6 0 5 Dirt
|
||||
//hollowcylinder z 20 10 20 default:desert_stone
|
||||
|
||||
### `//cylinder x/y/z/? <length> <radius1> [radius2] <node>`
|
||||
|
||||
Add cylinder at WorldEdit position 1 along the given axis with length `<length>`,
|
||||
base radius `<radius1>` (and top radius `[radius2]`), composed of `<node>`.
|
||||
Can also create shapes other than cylinders, e.g. cones (see documentation above).
|
||||
|
||||
//cylinder x +5 8 Bronze Block
|
||||
//cylinder y 28 10 glass
|
||||
//cylinder z -12 3 mesecons:wire_00000000_off
|
||||
//cylinder ? 2 4 default:stone
|
||||
|
||||
//cylinder y 10 10 0 walls:cobble
|
||||
//cylinder x 6 0 5 Dirt
|
||||
//cylinder z 20 10 20 default:desert_stone
|
||||
|
||||
### `//hollowpyramid x/y/z/? <height> <node>`
|
||||
|
||||
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height `<height>` composed of `<node>`.
|
||||
|
||||
//hollowpyramid x 8 Diamond Block
|
||||
//hollowpyramid y -5 glass
|
||||
//hollowpyramid z 2 mesecons:wire_00000000_off
|
||||
//hollowpyramid ? 12 mesecons:wire_00000000_off
|
||||
|
||||
### `//pyramid x/y/z/? <height> <node>`
|
||||
|
||||
Add pyramid centered at WorldEdit position 1 along the given axis with height `<height>` composed of `<node>`.
|
||||
|
||||
//pyramid x 8 Diamond Block
|
||||
//pyramid y -5 glass
|
||||
//pyramid z 2 mesecons:wire_00000000_off
|
||||
//pyramid ? 12 mesecons:wire_00000000_off
|
||||
|
||||
### `//spiral <length> <height> <spacer> <node>`
|
||||
|
||||
Add spiral centered at WorldEdit position 1 with side length `<length>`,
|
||||
height `<height>`, space between walls `<spacer>`, composed of `<node>`.
|
||||
|
||||
//spiral 20 5 3 Diamond Block
|
||||
//spiral 5 2 1 glass
|
||||
//spiral 7 1 5 mesecons:wire_00000000_off
|
||||
|
||||
### `//copy x/y/z/? <amount>`
|
||||
|
||||
Copy the current WorldEdit region along the given axis by `<amount>` nodes.
|
||||
|
||||
//copy x 15
|
||||
//copy y -7
|
||||
//copy z +4
|
||||
//copy ? 8
|
||||
|
||||
### `//move x/y/z/? <amount>`
|
||||
|
||||
Move the current WorldEdit positions and region along the given axis by `<amount>` nodes.
|
||||
|
||||
//move x 15
|
||||
//move y -7
|
||||
//move z +4
|
||||
//move ? -1
|
||||
|
||||
### `//stack x/y/z/? <count>`
|
||||
|
||||
Stack the current WorldEdit region along the given axis `<count>` times.
|
||||
|
||||
//stack x 3
|
||||
//stack y -1
|
||||
//stack z +5
|
||||
//stack ? 12
|
||||
|
||||
### `//stack2 <count> <x> <y> <z>`
|
||||
|
||||
Stack the current WorldEdit region `<count>` times by offset `<x>`, `<y>`, `<z>`.
|
||||
|
||||
//stack2 5 3 8 2
|
||||
//stack2 1 -1 -1 -1
|
||||
|
||||
### `//stretch <stretchx> <stretchy> <stretchz>`
|
||||
|
||||
Scale the current WorldEdit positions and region by a factor of
|
||||
`<stretchx>`, `<stretchy>`, `<stretchz>` along the X, Y, and Z axes,
|
||||
respectively, with position 1 as the origin.
|
||||
|
||||
//stretch 2 2 2
|
||||
//stretch 1 2 1
|
||||
//stretch 10 20 1
|
||||
|
||||
### `//transpose x/y/z/? x/y/z/?`
|
||||
|
||||
Transpose the current WorldEdit positions and region along given axes.
|
||||
|
||||
//transpose x y
|
||||
//transpose y z
|
||||
//transpose ? y
|
||||
|
||||
### `//flip x/y/z/?`
|
||||
|
||||
Flip the current WorldEdit region along the given axis.
|
||||
|
||||
//flip x
|
||||
//flip ?
|
||||
|
||||
### `//rotate x/y/z/? <angle>`
|
||||
|
||||
Rotate the current WorldEdit positions and region along the given axis by angle `<angle>` (90 degree increment).
|
||||
|
||||
//rotate x 90
|
||||
//rotate y 180
|
||||
//rotate z 270
|
||||
//rotate ? -90
|
||||
|
||||
### `//orient <angle>`
|
||||
|
||||
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle `<angle>` (90 degree increment)
|
||||
|
||||
//orient 90
|
||||
//orient 180
|
||||
//orient 270
|
||||
//orient -90
|
||||
|
||||
### `//fixlight`
|
||||
|
||||
Fixes the lighting in the current WorldEdit region.
|
||||
|
||||
//fixlight
|
||||
|
||||
### `//drain`
|
||||
|
||||
Removes any fluid node within the current WorldEdit region.
|
||||
|
||||
//drain
|
||||
|
||||
### `//clearcut`
|
||||
|
||||
Removes any plant, tree or foilage-like nodes in the selected region.
|
||||
The idea is to remove anything that isn't part of the terrain, leaving a "natural" empty space ready for building.
|
||||
|
||||
//clearcut
|
||||
|
||||
### `//hide`
|
||||
|
||||
Hide all nodes in the current WorldEdit region non-destructively.
|
||||
|
||||
//hide
|
||||
|
||||
### `//suppress <node>`
|
||||
|
||||
Suppress all `<node>` in the current WorldEdit region non-destructively.
|
||||
|
||||
//suppress Diamond Block
|
||||
//suppress glass
|
||||
//suppress mesecons:wire_00000000_off
|
||||
|
||||
### `//highlight <node>`
|
||||
|
||||
Highlight `<node>` in the current WorldEdit region by hiding everything else non-destructively.
|
||||
|
||||
//highlight Diamond Block
|
||||
//highlight glass
|
||||
//highlight mesecons:wire_00000000_off
|
||||
|
||||
### `//restore`
|
||||
|
||||
Restores nodes hidden with WorldEdit in the current WorldEdit region.
|
||||
|
||||
//restore
|
||||
|
||||
### `//save <file>`
|
||||
|
||||
Save the current WorldEdit region to "(world folder)/schems/`<file>`.we".
|
||||
|
||||
//save some random filename
|
||||
//save huge_base
|
||||
|
||||
### `//allocate <file>`
|
||||
|
||||
Set the region defined by nodes from "(world folder)/schems/`<file>`.we" as the current WorldEdit region.
|
||||
|
||||
//allocate some random filename
|
||||
//allocate huge_base
|
||||
|
||||
### `//load <file>`
|
||||
|
||||
Load nodes from "(world folder)/schems/`<file>`.we" with position 1 of the current WorldEdit region as the origin.
|
||||
|
||||
//load some random filename
|
||||
//load huge_base
|
||||
|
||||
### `//lua <code>`
|
||||
|
||||
Executes `<code>` as a Lua chunk in the global namespace with the variables `name`, `player` and `pos` (= player position) available.
|
||||
|
||||
//lua worldedit.pos1[name] = vector.new(0, 0, 0)
|
||||
//lua worldedit.rotate(worldedit.pos1["jones"], worldedit.pos2["jones"], "y", 90)
|
||||
//lua player:set_pos(worldedit.pos2[name])
|
||||
|
||||
### `//luatransform <code>`
|
||||
|
||||
Executes `<code>` as a Lua chunk in the global namespace with the variable `pos` available, for each node in the current WorldEdit region.
|
||||
|
||||
//luatransform minetest.swap_node(pos, {name="default:stone"})
|
||||
//luatransform if minetest.get_node(pos).name == "air" then minetest.add_node(pos, {name="default:water_source"}) end
|
||||
|
||||
### `//mtschemcreate <file>`
|
||||
|
||||
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/`<file>`.mts".
|
||||
|
||||
//mtschemcreate some random filename
|
||||
//mtschemcreate huge_base
|
||||
|
||||
### `//mtschemplace <file> [rotation]`
|
||||
|
||||
Load nodes from "(world folder)/schems/`<file>`.mts" with position 1 of the current WorldEdit region as the origin.
|
||||
Valid values for `[rotation]` are 0, 90, 180 and 270.
|
||||
|
||||
//mtschemplace a_tree 270
|
||||
//mtschemplace huge_base
|
||||
|
||||
### `//mtschemprob start/finish/get`
|
||||
|
||||
After using `//mtschemprob start` all nodes punched will bring up a text field where a probablity can be entered.
|
||||
This mode can be left with `//mtschemprob finish`. `//mtschemprob get` will display the probabilities saved for the nodes.
|
||||
|
||||
//mtschemprob get
|
||||
|
||||
### `//clearobjects`
|
||||
|
||||
Clears all objects within the WorldEdit region.
|
||||
|
||||
//clearobjects
|
||||
|
||||
### `//shift x/y/z/?/up/down/left/right/front/back [+/-]<amount>`
|
||||
|
||||
Shifts the selection area by `[+|-]<amount>` without moving its contents.
|
||||
The shifting axis can be absolute (`x/y/z`) or relative (`up/down/left/right/front/back`).
|
||||
|
||||
//shift left 5
|
||||
|
||||
### `//expand [+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]`
|
||||
|
||||
Expands the selection by `<amount>` in the selected absolute or relative axis.
|
||||
If specified, the selection can be expanded in the opposite direction over the same axis by `[reverse amount]`.
|
||||
|
||||
//expand right 7 5
|
||||
|
||||
### `//contract [+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]`
|
||||
|
||||
Contracts the selection by `<amount>` in the selected absolute or relative axis.
|
||||
If specified, the selection can be contracted in the opposite direction over the same axis by `[reverse amount]`.
|
||||
|
||||
//expand right 7 5
|
||||
|
||||
### `//outset [h/v] <amount>`
|
||||
|
||||
Expands the selection in all directions by `<amount>`. If specified,
|
||||
the selection can be expanded horizontally in the x and z axes using `h`
|
||||
or vertically in the y axis using `v`.
|
||||
|
||||
//outset v 5
|
||||
|
||||
### `//inset [h/v] <amount>`
|
||||
|
||||
Contracts the selection in all directions by `<amount>`. If specified,
|
||||
the selection can be contracted horizontally in the x and z axes using `h`
|
||||
or vertically in the y axis using `v`.
|
||||
|
||||
//inset h 5
|
||||
|
||||
### `//brush none/(<command> [parameters])`
|
||||
|
||||
Assigns the given `<command>` to the currently held brush item, it will be ran with the first pointed solid node (as determined via raycast) as
|
||||
WorldEdit position 1 when using that specific brush item.
|
||||
Passing `none` instead clears the command assigned to the currently held brush item.
|
||||
Note that this functionality requires the `worldedit_brush` mod enabled.
|
||||
|
||||
//brush cube 8 8 8 Cobblestone
|
||||
//brush spr 12 glass
|
||||
//brush none
|
||||
|
||||
### `//cubeapply <size>/(<sizex> <sizey> <sizez>) <command> [parameters]`
|
||||
|
||||
Selects a cube with side length of `<size>` around the WorldEdit position 1 and runs the given `<command>` on the newly selected region.
|
||||
If `<sizex>`, `<sizey>` and `<sizez>` are given, they instead specify the length of the cuboid in X, Y, Z direction.
|
||||
This is mostly useful for brushes since it allows commands such as `//replace` to be ran, but it can also be used standalone.
|
||||
|
||||
//cubeapply 10 replaceinverse air default:water_source
|
||||
//brush cubeapply 15 drain
|
||||
//brush cubeapply 12 3 12 drain
|
||||
//brush cubeapply 1 deleteblocks
|
201
README.md
|
@ -1,57 +1,192 @@
|
|||
WorldEdit v0.6 for MineTest 0.4
|
||||
===============================
|
||||
In-game world editing for [MineTest](http://minetest.net/)! Tons of chat commands to help with building, fixing, and more.
|
||||
WorldEdit v1.3
|
||||
==============
|
||||
The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more.
|
||||
|
||||
For more information, see the [forum topic](http://minetest.net/forum/viewtopic.php?id=572) at the MineTest forums.
|
||||
For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?t=572) at the Minetest forums.
|
||||
|
||||
# New users should see the [tutorial](Tutorial.md).
|
||||
|
||||
![Screenshot](http://i.imgur.com/lwhodrv.png)
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
There is a nice installation guide over at the [Minetest Wiki](http://wiki.minetest.com/wiki/Installing_mods). Here is a short summary:
|
||||
|
||||
1. Download the mod from the [official releases page](https://github.com/Uberi/Minetest-WorldEdit/releases). The download links are labelled "Source Code". If you are using Windows, you'll want to download the ZIP version.
|
||||
2. You should have a file named `Minetest-WorldEdit-x.x.zip`.
|
||||
3. Extract this file using your archiver of choice. If you are using Windows, open the ZIP file and move the folder inside to a safe place outside of the ZIP file.
|
||||
4. Make sure that you now have a folder with a file named README.md inside it. If you just have another folder inside this folder, use the nested folder instead.
|
||||
5. Move this folder into the `MINETEST_FOLDER/mods` folder, where `MINETEST_FOLDER` is the folder Minetest is located in.
|
||||
6. Open Minetest to a world selection screen.
|
||||
7. Select a world you want to use WorldEdit in by left clicking on it once and press the **Configure** button.
|
||||
8. You should have a mod selection screen. Select the one named something like `Minetest-WorldEdit` by left clicking once and press the **Enable Modpack** button.
|
||||
9. Press the **Save** button. You can now use WorldEdit in that world. Repeat steps 7 to 9 to enable WorldEdit for other worlds too.
|
||||
|
||||
If you are having trouble, try asking for help in the [IRC channel](https://web.libera.chat/#minetest) (faster but may not always have helpers online)
|
||||
or ask on the [forum topic](https://forum.minetest.net/viewtopic.php?id=572) (slower but more likely to get help).
|
||||
|
||||
Usage
|
||||
-----
|
||||
WorldEdit works primarily through chat commands. Depending on your key bindings, you can invoke chat entry with the "t" key, and open the chat console with the "F10" key.
|
||||
WorldEdit works primarily through the WorldEdit GUI and chat commands. Depending on your key bindings, you can invoke chat entry with the "T" key and open the chat console with the "F10" key.
|
||||
|
||||
WorldEdit has a huge potential for abuse by untrusted players. Therefore, users will not be able to use WorldEdit unless they have the "worldedit" privelege. This is available by default in single player, but in multiplayer the permission must be explicitly given by someone with the right credentials, using the follwoing chat command: `/grant <player name> worldedit`. This privelege can later be removed using the following chat command: `/revoke <player name> worldedit`.
|
||||
WorldEdit has a huge potential for abuse by untrusted players. Therefore, users will not be able to use WorldEdit unless they have the `worldedit` privilege.
|
||||
This is available by default in singleplayer, but in multiplayer the permission must be explicitly given by someone with the right credentials,
|
||||
using the following chat command: `/grant <player name> worldedit`. This privilege can later be removed using the following chat command: `/revoke <player name> worldedit`.
|
||||
|
||||
For in-game information about these commands, type `/help <command name>` in the chat. For example, to learn more about the `//copy` command, simply type `/help /copy` to display information relevant to copying a region.
|
||||
Certain functions/commands such as WorldEdit `//lua` and `//luatransform` chat commands additionally require the `server` privilege.
|
||||
This is because it is extremely dangerous to give access to these commands to untrusted players, since they essentially are able to control the computer the server is running on.
|
||||
Give this privilege only to people you trust with your computer.
|
||||
|
||||
Axes
|
||||
----
|
||||
The coordinate system is the same as that used by MineTest; Y is upwards, X is perpendicular, and Z is parallel.
|
||||
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.
|
||||
|
||||
When an axis is specified in a WorldEdit command, it is specified as one of the following values: x, y, z, or ?.
|
||||
Interface
|
||||
---------
|
||||
WorldEdit is accessed in-game in two main ways.
|
||||
|
||||
The value ? represents the axis the player is currently facing. If the player is facing more than one axis, the axis the player face direction is closest to will be used.
|
||||
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.
|
||||
|
||||
Regions
|
||||
-------
|
||||
Most WorldEdit commands operate on regions. Regions are a set of two positions that define a 3D cube. They are local to each player and chat commands affect only the region for the player giving the commands.
|
||||
The chat interface adds many chat commands that perform various WorldEdit powered tasks. It is documented in the [Chat Commands Reference](ChatCommands.md).
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
To remove the entities, simply punch them. This does not reset the positions themselves.
|
||||
|
||||
Chat Commands
|
||||
Compatibility
|
||||
-------------
|
||||
WorldEdit is accessed in-game through an interface. By default, the mod distribution includes a chat interface for this purpose. It is documented in the [Chat Commands Reference](Chat Commands.md).
|
||||
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.
|
||||
|
||||
If visual manipulation of nodes is desired, the [WorldEdit GUI](http://minetest.net/forum/viewtopic.php?id=3112) mod provides a simple interface with buttons and text entry fields for this purpose.
|
||||
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),
|
||||
[Unified Inventory](https://forum.minetest.net/viewtopic.php?t=12767),
|
||||
[Inventory++](https://forum.minetest.net/viewtopic.php?id=6204) or [Smart Inventory](https://forum.minetest.net/viewtopic.php?t=16597).
|
||||
|
||||
If you use any other inventory manager mods, note that they may conflict with the WorldEdit GUI. If this is the case, it may be necessary to disable them.
|
||||
|
||||
WorldEdit API
|
||||
-------------
|
||||
WorldEdit exposes all significant functionality in a simple interface. Adding WorldEdit to the file "depends.txt" in your mod gives you access to all of the `worldedit` functions. The API is useful for tasks such as high-performance node manipulation, alternative interfaces, and map creation.
|
||||
WorldEdit exposes all significant functionality in a simple Lua interface.
|
||||
|
||||
This API is documented in the [WorldEdit API Reference](WorldEdit API.md).
|
||||
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 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).
|
||||
|
||||
Axes
|
||||
----
|
||||
The coordinate system is the same as that used by Minetest; positive Y is upwards, positive X is rightwards, and positive Z is forwards, if a player is facing North (positive Z axis).
|
||||
|
||||
When an axis is specified in a WorldEdit chat command, it is specified as one of the following values: `x`, `y`, `z`, or `?`.
|
||||
|
||||
In the GUI, there is a dropdown menu for this purpose. The "Look direction" option has the same effect as `?` does in chat commands.
|
||||
|
||||
The value `?` represents the axis the player is currently facing. If the player is facing more than one axis, the axis the player face direction is closest to will be used.
|
||||
|
||||
Nodes
|
||||
-----
|
||||
Node names are required for many types of commands that identify or modify specific types of nodes. They can be specified in a number of ways.
|
||||
|
||||
First, by description - the tooltip that appears when hovering over the item in an inventory. This is case insensitive and includes values such as "Cobblestone" and "bronze block".
|
||||
Note that certain commands (namely, `//replace` and `//replaceinverse`) do not support descriptions that contain spaces in the `<searchnode>` field.
|
||||
|
||||
Second, by name - the node name that is defined by code, but without the mod name prefix. This is case sensitive and includes values such as "piston_normal_off" and "cactus".
|
||||
If there are multiple possible nodes (such as "a:celery" and "b:celery"), one is chosen in no particular order.
|
||||
|
||||
Finally, by full name - the unambiguous identifier of the node, prefixes and all. This is case sensitive and includes values such as "default:stone" and "mesecons:wire_00000000_off".
|
||||
|
||||
The node name "air" can be used anywhere a normal node name can and acts as a blank node. This is useful for clearing or removing nodes.
|
||||
For example, `//set air` would remove all the nodes in the current WorldEdit region. Similarly, `//sphere 10 air`, when WorldEdit position 1 underground, would dig a large sphere out of the ground.
|
||||
|
||||
Regions
|
||||
-------
|
||||
Most WorldEdit commands operate on regions. Regions are a set of two positions that define a 3D cuboid. They are local to each player and chat commands affect only the region for the player giving the commands.
|
||||
|
||||
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.
|
||||
|
||||
Markers
|
||||
-------
|
||||
Entities are used to mark the location of the WorldEdit regions. They appear as boxes containing the number 1 or 2 and represent the first and second position of the WorldEdit region, respectively.
|
||||
|
||||
To remove the entities, simply punch them. This does not reset the positions themselves.
|
||||
|
||||
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 obsolete.
|
||||
|
||||
As of version 5, WorldEdit schematics include a header. The header is seperated from the content by a colon (`:`). It may contain fields seperated by commas (`,`).
|
||||
Currently only one field is used, which contains the version as an ASCII decimal.
|
||||
|
||||
The current version of the WorldEdit Schematic format is essentially an array of node data tables in Lua 5.1 table syntax preceded by a header.
|
||||
Specifically it looks like this:
|
||||
|
||||
5:return {
|
||||
{
|
||||
y = <y-axis coordinate>,
|
||||
x = <x-axis coordinate>,
|
||||
z = <z-axis coordinate>,
|
||||
name = <node name>,
|
||||
param1 = <param1 value>,
|
||||
param2 = <param2 value>,
|
||||
meta = <metadata table>,
|
||||
},
|
||||
<...>
|
||||
}
|
||||
|
||||
|
||||
The ordering of the values and minor aspects of the syntax, such as trailing commas or newlines, are not guaranteed to stay the same in future versions.
|
||||
|
||||
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`.
|
||||
|
||||
Authors
|
||||
-------
|
||||
WorldEdit would not be possible without the contributions of many developers and designers. Below, they are listed alphabetically:
|
||||
|
||||
Alexander Weber
|
||||
ANAND
|
||||
beyondlimits
|
||||
Carter Kolwey
|
||||
cornernote
|
||||
Cy
|
||||
Daniel Sosa
|
||||
electricface
|
||||
est31
|
||||
Eugen Wesseloh
|
||||
h3ndrik
|
||||
HybridDog
|
||||
Isidor Zeuner
|
||||
Jean-Patrick Guerrero
|
||||
Joseph Pickard
|
||||
kaeza
|
||||
kilbith
|
||||
KodexKy
|
||||
Kyle
|
||||
MT-Modder
|
||||
Niwla23
|
||||
Panquesito7
|
||||
Pedro Gimeno
|
||||
Rui
|
||||
Sebastien Ponce
|
||||
sfan5
|
||||
ShadowNinja
|
||||
shivajiva101
|
||||
spillz
|
||||
Starbeamrainbowlabs
|
||||
TalkLounge
|
||||
tenplus1
|
||||
Uberi/Temperest
|
||||
Wuzzy
|
||||
|
||||
License
|
||||
-------
|
||||
Copyright 2012 sfan5, Anthony Zhang (Temperest), and Brett O'Donnell (cornernote).
|
||||
Copyright (c) 2012 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote).
|
||||
|
||||
This mod is licensed under the [GNU Affero General Public License](http://www.gnu.org/licenses/agpl-3.0.html).
|
||||
This mod is licensed under the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.html).
|
||||
|
||||
Basically, this means everyone is free to use, modify, and distribute the files, as long as these modifications are also licensed the same way.
|
||||
|
||||
Most importantly, the Affero variant of the GPL requires you to publish your modifications in source form, even if the mod is run only on the server, and not distributed.
|
||||
|
|
118
Tutorial.md
Normal file
|
@ -0,0 +1,118 @@
|
|||
WorldEdit Tutorial
|
||||
==================
|
||||
This is a step-by-step tutorial outlining the basic usage of WorldEdit.
|
||||
|
||||
Let's start with a few assumptions:
|
||||
|
||||
* You have a compatible version of Minetest working, that is 5.0 or later.
|
||||
* You have WorldEdit installed as a mod.
|
||||
* Simply download the file, extract the archive, and move it to the correct mod folder for Minetest.
|
||||
* See the installation instructions in [README](README.md) if you need more details.
|
||||
* You are familiar with the basics of the game.
|
||||
* How to walk, jump, and climb.
|
||||
* How to dig, place, and punch blocks.
|
||||
* One of the following:
|
||||
* How to type into the chat and read text from it.
|
||||
* How to open the inventory screen and press buttons on it.
|
||||
|
||||
Overview
|
||||
--------
|
||||
WorldEdit has a "region", which is simply a cuboid area defined by two markers, both of which the player can move around. Every player can have their own region with their own two markers.
|
||||
|
||||
WorldEdit GUI buttons and chat commands generally work inside the region selected, or around the first marker.
|
||||
|
||||
If you are using the chat commands, follow the steps under **Chat Commands**. If you are using the WorldEdit GUI, follow the steps under **WorldEdit GUI**.
|
||||
|
||||
Step 1: Selecting a region
|
||||
--------------------------
|
||||
### Chat Commands
|
||||
|
||||
In the chat prompt, enter `//p set`. In the chat, you are prompted to punch two nodes to set the positions of the two markers.
|
||||
|
||||
Punch a nearby node. Be careful of breakable ones such as torches. A black cube reading "1" will appear around the node. This is the marker for WorldEdit position 1.
|
||||
|
||||
Walk away from the node you just punched. Now, punch another node. A black cube reading "2" will appear around the node. This is the marker for WorldEdit position 2.
|
||||
|
||||
### WorldEdit GUI
|
||||
|
||||
Open the main WorldEdit GUI from your inventory screen. The icon looks like a globe with a red dot in the center.
|
||||
|
||||
Press the "Get/Set Positions" button. On the new screen, press the "Set Position 1" button. The inventory screen should close.
|
||||
|
||||
Punch a nearby node. Be careful of breakable ones such as torches. A black cube reading "1" will appear around the node. This is the marker for WorldEdit position 1.
|
||||
|
||||
Walk away from the node you just punched. Open your inventory again. It should be on the same page as it was before.
|
||||
|
||||
Press the "Set Position 2" button. The inventory screen should close.
|
||||
|
||||
Now, punch another node. A black cube reading "2" will appear around the node. This is the marker for WorldEdit position 2.
|
||||
|
||||
Step 2: Region commands
|
||||
-----------------------
|
||||
### Chat Commands
|
||||
|
||||
In the chat prompt, enter `//set mese`. In the chat, you will see a message showing the number of nodes set after a small delay.
|
||||
|
||||
Look at the place between the two markers: it is now filled with MESE blocks!
|
||||
|
||||
The `//set <node>` command fills the region with whatever node you want. It is a region-oriented command, which means it works inside the WorldEdit region only.
|
||||
|
||||
Now, try a few different variations, such as `//set torch`, `//set cobble`, and `//set water source`.
|
||||
|
||||
### WorldEdit GUI
|
||||
|
||||
Open the main WorldEdit GUI from your inventory screen.
|
||||
|
||||
Press the "Set Nodes" button. You should see a new screen with various options for setting nodes.
|
||||
|
||||
Enter "mese" in the "Name" field. Press Search if you would like to see what the node you just entered looks like.
|
||||
|
||||
Press the "Set Nodes" button on this screen. In the chat, you will see a message showing the number of nodes set after a small delay.
|
||||
|
||||
Look at the place between the two markers: it is now filled with MESE blocks!
|
||||
|
||||
The "Set Nodes" function fills the region with whatever node you want. It is a region-oriented command, which means it works inside the WorldEdit region only.
|
||||
|
||||
Now, try a few different variations on the node name, such as "torch", "cobble", and "water source".
|
||||
|
||||
Step 3: Position commands
|
||||
-------------------------
|
||||
### Chat Commands
|
||||
|
||||
In the chat prompt, enter `//hollowdome 30 glass`. In the chat, you will see a message showing the number of nodes set after a small delay.
|
||||
|
||||
Look around marker 1: it is now surrounded by a hollow glass dome!
|
||||
|
||||
The `//hollowdome <radius> <node>` command creates a hollow dome centered around marker 1, made of any node you want. It is a position-oriented command, which means it works around marker 1 and can go outside the WorldEdit region.
|
||||
|
||||
### WorldEdit GUI
|
||||
|
||||
Open the main WorldEdit GUI from your inventory screen.
|
||||
|
||||
Press the "Sphere/Dome" button. You should see a new screen with various options for making spheres or domes.
|
||||
|
||||
Enter "glass" in the "Name" field. Press Search if you would like to see what the node you just entered looks like.
|
||||
|
||||
Enter "30" in the "Radius" field.
|
||||
|
||||
Press the "Hollow Dome" button on this screen. In the chat, you will see a message showing the number of nodes added after a small delay.
|
||||
|
||||
Look around marker 1: it is now surrounded by a hollow glass dome!
|
||||
|
||||
The "Hollow Dome" function creates a hollow dome centered around marker 1, made of any node you want. It is a position-oriented command, which means it works around marker 1 and can go outside the WorldEdit region.
|
||||
|
||||
Step 4: Other commands
|
||||
----------------------
|
||||
### Chat Commands
|
||||
|
||||
There are many more commands than what is shown here. See the [Chat Commands Reference](ChatCommands.md) for a detailed list of them, along with descriptions and examples for every single one.
|
||||
|
||||
If you're in-game and forgot how a command works, just use the `/help <command name>` command, without the first forward slash. For example, to see some information about the `//set <node>` command mentioned earlier, simply use `/help /set`.
|
||||
|
||||
A very useful command to check out is the `//save <schematic>` command, which can save everything inside the WorldEdit region to a file, stored on the computer hosting the server (the player's computer, in single player mode). You can then later use `//load <schematic>` to load the data in a file into a world, even another world on another computer.
|
||||
|
||||
### WorldEdit GUI
|
||||
|
||||
This only scratches the surface of what WorldEdit is capable of. Most of the functions in the WorldEdit GUI correspond to chat commands, and so the [Chat Commands Reference](ChatCommands.md) may be useful if you get stuck.
|
||||
|
||||
It is helpful to explore the various buttons in the interface and check out what they do. Learning the chat command interface is also useful if you use WorldEdit intensively - an experienced chat command user can usually work faster than an experienced WorldEdit GUI user.
|
133
WorldEdit API.md
|
@ -6,13 +6,31 @@ If needed, individual modules such as visualization.lua can be removed without a
|
|||
|
||||
For more information, see the [README](README.md).
|
||||
|
||||
General
|
||||
-------
|
||||
|
||||
### value = worldedit.version
|
||||
|
||||
Contains the current version of WorldEdit in a table of the form `{major=MAJOR_INTEGER, minor=MINOR_INTEGER}`, where `MAJOR_INTEGER` is the major version (the number before the period) as an integer, and `MINOR_INTEGER` is the minor version (the number after the period) as an integer. This is intended for version checking purposes.
|
||||
|
||||
### value = worldedit.version_string
|
||||
|
||||
Contains the current version of WorldEdit in the form of a string `"MAJOR_INTEGER.MINOR_INTEGER"`, where `MAJOR_INTEGER` is the major version (the number before the period) as an integer, and `MINOR_INTEGER` is the minor version (the number after the period) as an integer. This is intended for display purposes.
|
||||
|
||||
Manipulations
|
||||
-------------
|
||||
Contained in manipulations.lua, this module allows several node operations to be applied over a region.
|
||||
|
||||
### count = worldedit.set(pos1, pos2, nodename)
|
||||
### count = worldedit.set(pos1, pos2, node_name)
|
||||
|
||||
Sets a region defined by positions `pos1` and `pos2` to `nodename`. To clear to region, use "air" as the value of `nodename`.
|
||||
Sets a region defined by positions `pos1` and `pos2` to `node_name`. To clear a region, use "air" as the value of `node_name`.
|
||||
If `node_name` is a list of nodes, each set node is randomly picked from it.
|
||||
|
||||
Returns the number of nodes set.
|
||||
|
||||
### `count = worldedit.set_param2(pos1, pos2, param2)`
|
||||
|
||||
Sets the param2 values of all nodes in a region defined by positions `pos1` and `pos2` to `param2`.
|
||||
|
||||
Returns the number of nodes set.
|
||||
|
||||
|
@ -34,6 +52,13 @@ Copies the region defined by positions `pos1` and `pos2` along the `axis` axis (
|
|||
|
||||
Returns the number of nodes copied.
|
||||
|
||||
### count = worldedit.copy2(pos1, pos2, off)
|
||||
|
||||
Copies the region defined by positions `pos1` and `pos2` by the offset vector `off`.
|
||||
Note that the offset needs to be big enough that there is no overlap.
|
||||
|
||||
Returns the number of nodes copied.
|
||||
|
||||
### count = worldedit.move(pos1, pos2, axis, amount)
|
||||
|
||||
Moves the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes.
|
||||
|
@ -46,11 +71,24 @@ Duplicates the region defined by positions `pos1` and `pos2` along the `axis` ax
|
|||
|
||||
Returns the number of nodes stacked.
|
||||
|
||||
### count = worldedit.stack2(pos1, pos2, direction, amount)
|
||||
|
||||
Duplicates the region defined by positions `pos1` and `pos2` `amount` times with offset vector `direction`.
|
||||
Note that the offset vector needs to be big enough that there is no overlap.
|
||||
|
||||
Returns the number of nodes stacked.
|
||||
|
||||
### count, newpos1, newpos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
|
||||
|
||||
Stretches the region defined by positions `pos1` and `pos2` by an factor of positive integers `stretchx`, `stretchy`. and `stretchz` along the X, Y, and Z axes, respectively, with `pos1` as the origin.
|
||||
|
||||
Returns the number of nodes stretched, the new scaled position 1, and the new scaled position 2.
|
||||
|
||||
### count, newpos1, newpos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
|
||||
|
||||
Transposes a region defined by the positions `pos1` and `pos2` between the `axis1` and `axis2` axes ("x" or "y" or "z").
|
||||
|
||||
Returns the number of nodes transposed, the new position 1, and the new position 2.
|
||||
Returns the number of nodes transposed, the new transposed position 1, and the new transposed position 2.
|
||||
|
||||
### count = worldedit.flip(pos1, pos2, axis)
|
||||
|
||||
|
@ -76,55 +114,49 @@ Fixes the lighting in a region defined by positions `pos1` and `pos2`.
|
|||
|
||||
Returns the number of nodes updated.
|
||||
|
||||
### count = worldedit.clearobjects(pos1, pos2)
|
||||
|
||||
Clears all objects in a region defined by the positions `pos1` and `pos2`.
|
||||
|
||||
Returns the number of objects cleared.
|
||||
|
||||
Primitives
|
||||
----------
|
||||
Contained in primitives.lua, this module allows the creation of several geometric primitives.
|
||||
|
||||
### count = worldedit.hollow_sphere(pos, radius, nodename)
|
||||
### count = worldedit.cube(pos, width, height, length, node_name, hollow)
|
||||
|
||||
Adds a hollow sphere at `pos` with radius `radius`, composed of `nodename`.
|
||||
Adds a cube with its ground level centered at `pos`, the dimensions `width` x `height` x `length`, composed of `node_name`.
|
||||
|
||||
Returns the number of nodes added.
|
||||
|
||||
### count = worldedit.sphere(pos, radius, nodename)
|
||||
### count = worldedit.sphere(pos, radius, node_name, hollow)
|
||||
|
||||
Adds a sphere at `pos` with radius `radius`, composed of `nodename`.
|
||||
Adds a sphere centered at `pos` with radius `radius`, composed of `node_name`.
|
||||
|
||||
Returns the number of nodes added.
|
||||
|
||||
### count = worldedit.hollow_dome(pos, radius, nodename)
|
||||
### count = worldedit.dome(pos, radius, node_name, hollow)
|
||||
|
||||
Adds a hollow dome at `pos` with radius `radius`, composed of `nodename`.
|
||||
Adds a dome centered at `pos` with radius `radius`, composed of `node_name`.
|
||||
|
||||
Returns the number of nodes added.
|
||||
|
||||
### count = worldedit.dome(pos, radius, nodename)
|
||||
### count = worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, hollow)
|
||||
|
||||
Adds a dome at `pos` with radius `radius`, composed of `nodename`.
|
||||
Adds a cylinder-like at `pos` along the `axis` axis ("x" or "y" or "z") with length `length`, base radius `radius1` and top radius `radius2`, composed of `node_name`.
|
||||
|
||||
Returns the number of nodes added.
|
||||
|
||||
### count = worldedit.hollow_cylinder(pos, axis, length, radius, nodename)
|
||||
### count = worldedit.pyramid(pos, axis, height, node_name, hollow)
|
||||
|
||||
Adds a hollow cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`.
|
||||
Adds a pyramid centered at `pos` along the `axis` axis ("x" or "y" or "z") with height `height`, composed of `node_name`.
|
||||
|
||||
Returns the number of nodes added.
|
||||
|
||||
### count = worldedit.cylinder(pos, axis, length, radius, nodename)
|
||||
### count = worldedit.spiral(pos, length, height, spacer, node_name)
|
||||
|
||||
Adds a cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`.
|
||||
|
||||
Returns the number of nodes added.
|
||||
|
||||
### count = worldedit.pyramid(pos, height, nodename)
|
||||
|
||||
Adds a pyramid at `pos` with height `height`.
|
||||
|
||||
Returns the number of nodes added.
|
||||
|
||||
### count = worldedit.spiral(pos, width, height, spacer, nodename)
|
||||
|
||||
Adds a spiral at `pos` with width `width`, height `height`, space between walls `spacer`, composed of `nodename`.
|
||||
Adds a spiral centered at `pos` with side length `length`, height `height`, space between walls `spacer`, composed of `node_name`.
|
||||
|
||||
Returns the number of nodes added.
|
||||
|
||||
|
@ -144,15 +176,15 @@ Hides all nodes in a region defined by positions `pos1` and `pos2` by non-destru
|
|||
|
||||
Returns the number of nodes hidden.
|
||||
|
||||
### count = worldedit.suppress(pos1, pos2, nodename)
|
||||
### count = worldedit.suppress(pos1, pos2, node_name)
|
||||
|
||||
Suppresses all instances of `nodename` in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes.
|
||||
Suppresses all instances of `node_name` in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes.
|
||||
|
||||
Returns the number of nodes suppressed.
|
||||
|
||||
### count = worldedit.highlight(pos1, pos2, nodename)
|
||||
### count = worldedit.highlight(pos1, pos2, node_name)
|
||||
|
||||
Highlights all instances of `nodename` in a region defined by positions `pos1` and `pos2` by non-destructively hiding all other nodes.
|
||||
Highlights all instances of `node_name` in a region defined by positions `pos1` and `pos2` by non-destructively hiding all other nodes.
|
||||
|
||||
Returns the number of nodes found.
|
||||
|
||||
|
@ -164,44 +196,53 @@ Returns the number of nodes restored.
|
|||
|
||||
Serialization
|
||||
-------------
|
||||
Contained in serialization.lua, this module allows regions of nodes to be serialized and deserialized to formats suitable for use outside MineTest.
|
||||
Contained in serialization.lua, this module allows regions of nodes to be serialized and deserialized to formats suitable for use outside Minetest.
|
||||
|
||||
### version = worldedit.valueversion(value)
|
||||
### version, extra_fields, content = worldedit.read_header(value)
|
||||
|
||||
Determines the version of serialized data `value`.
|
||||
Reads the header from serialized data `value`.
|
||||
|
||||
Returns the version as a positive integer or 0 for unknown versions.
|
||||
Returns the version as a positive integer (nil for unknown versions),
|
||||
extra header fields (nil if not supported), and the content after the header.
|
||||
|
||||
### data, count = worldedit.serialize(pos1, pos2)
|
||||
|
||||
Converts the region defined by positions `pos1` and `pos2` into a single string.
|
||||
|
||||
Returns the serialized data and the number of nodes serialized.
|
||||
Returns the serialized data and the number of nodes serialized, or nil.
|
||||
|
||||
### pos1, pos2, count = worldedit.allocate(originpos, value)
|
||||
### pos1, pos2, count = worldedit.allocate(origin_pos, value)
|
||||
|
||||
Determines the volume the nodes represented by string `value` would occupy if deserialized at `originpos`.
|
||||
Determines the volume the nodes represented by string `value` would occupy if deserialized at `origin_pos`.
|
||||
|
||||
Returns the two corner positions and the number of nodes.
|
||||
Returns the two corner positions and the number of nodes, or nil.
|
||||
|
||||
### count = worldedit.deserialize(originpos, value)
|
||||
### count = worldedit.deserialize(origin_pos, value)
|
||||
|
||||
Loads the nodes represented by string `value` at position `originpos`.
|
||||
Loads the nodes represented by string `value` at position `origin_pos`.
|
||||
|
||||
Returns the number of nodes deserialized.
|
||||
Returns the number of nodes deserialized or nil.
|
||||
|
||||
Code
|
||||
----
|
||||
Contained in code.lua, this module allows arbitrary Lua code to be used with WorldEdit.
|
||||
|
||||
### error = worldedit.lua(code)
|
||||
### error = worldedit.lua(code, name)
|
||||
|
||||
Executes `code` as a Lua chunk in the global namespace.
|
||||
the given code gets encapsulated into a function with parameters `name`, `player`, `pos`
|
||||
where
|
||||
* `name` is a playername or `nil`
|
||||
* `player` is the player object of the above player if applicable, otherwise `nil`
|
||||
* `pos` is the position of the aforementioned player (if applicable, otherwise `nil`) rounded to integers
|
||||
|
||||
Returns an error if the code fails or nil otherwise.
|
||||
the resulting function is then executed as a Lua chunk in the global namespace.
|
||||
|
||||
The return is
|
||||
* a string in case of an error
|
||||
* a tuple of `nil` and return of the function converted to a string in case of success
|
||||
|
||||
### error = worldedit.luatransform(pos1, pos2, code)
|
||||
|
||||
Executes `code` as a Lua chunk in the global namespace with the variable pos available, for each node in a region defined by positions `pos1` and `pos2`.
|
||||
Executes `code` as a Lua chunk in the global namespace with the variable `pos` available, for each node in a region defined by positions `pos1` and `pos2`.
|
||||
|
||||
Returns an error if the code fails or nil otherwise.
|
12
config.ld
Normal file
|
@ -0,0 +1,12 @@
|
|||
project = "WorldEdit"
|
||||
title = "WorldEdit API Documentation"
|
||||
description = "Minetest mod to mass-modify nodes"
|
||||
format = "markdown"
|
||||
file = {"worldedit"}
|
||||
topics = {
|
||||
"README.md",
|
||||
"Tutorial.md",
|
||||
"ChatCommands.md",
|
||||
"LICENSE.txt"
|
||||
}
|
||||
|
3
modpack.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
name = Minetest-WorldEdit
|
||||
description = WorldEdit is an in-game world editor. Use it to repair griefing, or just create awesome buildings in seconds.
|
||||
min_minetest_version = 5.0
|
4
settingtypes.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
# For operations that potentially affect more than the specified amount of nodes
|
||||
# WorldEdit will require additional confirmation via //y before proceeding.
|
||||
# Set to 0 to disable the confirmation in all cases.
|
||||
worldedit_safe_region_limit (Limit for safe region warning) int 20000
|
|
@ -1,37 +1,57 @@
|
|||
worldedit = worldedit or {}
|
||||
--- Lua code execution functions.
|
||||
-- @module worldedit.code
|
||||
|
||||
--executes `code` as a Lua chunk in the global namespace, returning an error if the code fails or nil otherwise
|
||||
worldedit.lua = function(code)
|
||||
local operation, message = loadstring(code)
|
||||
if operation == nil then --code parsing failed
|
||||
return message
|
||||
--- Executes `code` as a Lua chunk in the global namespace.
|
||||
-- the code will be encapsulated into a function with parameters
|
||||
-- * name (the name of the player issuing the //lua command)
|
||||
-- * player (the player object of the player)
|
||||
-- * pos (the position of the player rounded to integers)
|
||||
-- @return string in case of error, tuple of nil, return of code as string in case of success
|
||||
function worldedit.lua(code, name)
|
||||
local factory, err = loadstring("return function(name, player, pos)\n" .. code .. "\nend")
|
||||
if not factory then -- Syntax error
|
||||
return err
|
||||
end
|
||||
local status, message = pcall(operation)
|
||||
if status == nil then --operation failed
|
||||
return message
|
||||
local func = factory()
|
||||
local player, pos
|
||||
if name then
|
||||
player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
pos = vector.round(player:get_pos())
|
||||
end
|
||||
end
|
||||
return nil
|
||||
local good, err = pcall(func, name, player, pos)
|
||||
if not good then -- Runtime error
|
||||
return tostring(err)
|
||||
end
|
||||
return nil, dump(err)
|
||||
end
|
||||
|
||||
--executes `code` as a Lua chunk in the global namespace with the variable pos available, for each node in a region defined by positions `pos1` and `pos2`, returning an error if the code fails or nil otherwise
|
||||
worldedit.luatransform = function(pos1, pos2, code)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local factory, message = loadstring("return function(pos) " .. code .. " end")
|
||||
if factory == nil then --code parsing failed
|
||||
return message
|
||||
--- Executes `code` as a Lua chunk in the global namespace with the variable
|
||||
-- pos available, for each node in a region defined by positions `pos1` and
|
||||
-- `pos2`.
|
||||
-- @return An error message if the code fails, or nil on success.
|
||||
function worldedit.luatransform(pos1, pos2, code)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local factory, err = loadstring("return function(pos) " .. code .. " end")
|
||||
if not factory then -- Syntax error
|
||||
return err
|
||||
end
|
||||
local operation = factory()
|
||||
local func = factory()
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local status, message = pcall(operation, pos)
|
||||
if status == nil then --operation failed
|
||||
return message
|
||||
local good, err = pcall(func, pos)
|
||||
if not good then -- Runtime error
|
||||
return tostring(err)
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
|
@ -41,3 +61,4 @@ worldedit.luatransform = function(pos1, pos2, code)
|
|||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
|
207
worldedit/common.lua
Normal file
|
@ -0,0 +1,207 @@
|
|||
--- Common functions [INTERNAL]. All of these functions are internal!
|
||||
-- @module worldedit.common
|
||||
|
||||
-- Polyfill for vector.copy (added in 5.5.0)
|
||||
if not vector.copy then
|
||||
local vnew = vector.new
|
||||
vector.copy = function(v)
|
||||
return vnew(v.x, v.y, v.z)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Copies and modifies positions `pos1` and `pos2` so that each component of
|
||||
-- `pos1` is less than or equal to the corresponding component of `pos2`.
|
||||
-- Returns the new positions.
|
||||
function worldedit.sort_pos(pos1, pos2)
|
||||
pos1 = vector.copy(pos1)
|
||||
pos2 = vector.copy(pos2)
|
||||
if pos1.x > pos2.x then
|
||||
pos2.x, pos1.x = pos1.x, pos2.x
|
||||
end
|
||||
if pos1.y > pos2.y then
|
||||
pos2.y, pos1.y = pos1.y, pos2.y
|
||||
end
|
||||
if pos1.z > pos2.z then
|
||||
pos2.z, pos1.z = pos1.z, pos2.z
|
||||
end
|
||||
return pos1, pos2
|
||||
end
|
||||
|
||||
|
||||
--- Determines the volume of the region defined by positions `pos1` and `pos2`.
|
||||
-- @return The volume.
|
||||
function worldedit.volume(pos1, pos2)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
return (pos2.x - pos1.x + 1) *
|
||||
(pos2.y - pos1.y + 1) *
|
||||
(pos2.z - pos1.z + 1)
|
||||
end
|
||||
|
||||
|
||||
--- Gets other axes given an axis.
|
||||
-- @raise Axis must be x, y, or z!
|
||||
function worldedit.get_axis_others(axis)
|
||||
if axis == "x" then
|
||||
return "y", "z"
|
||||
elseif axis == "y" then
|
||||
return "x", "z"
|
||||
elseif axis == "z" then
|
||||
return "x", "y"
|
||||
else
|
||||
error("Axis must be x, y, or z!")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Create a vmanip and read the area from map, this causes all
|
||||
-- MapBlocks to be loaded into memory synchronously.
|
||||
-- This doesn't actually *keep* them loaded, unlike the name implies.
|
||||
function worldedit.keep_loaded(pos1, pos2)
|
||||
-- rough estimate, a MapNode is 4 bytes in the engine
|
||||
if worldedit.volume(pos1, pos2) > 268400000 then
|
||||
print("[WorldEdit] Requested to load an area bigger than 1GB, refusing. The subsequent operation may fail.")
|
||||
return
|
||||
end
|
||||
if minetest.load_area then
|
||||
-- same effect but without unnecessary data copying
|
||||
minetest.load_area(pos1, pos2)
|
||||
else
|
||||
local manip = minetest.get_voxel_manip()
|
||||
manip:read_from_map(pos1, pos2)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local mh = {}
|
||||
worldedit.manip_helpers = mh
|
||||
|
||||
|
||||
--- Generates an empty VoxelManip data table for an area.
|
||||
-- @return The empty data table.
|
||||
function mh.get_empty_data(area)
|
||||
-- Fill emerged area with ignore so that blocks in the area that are
|
||||
-- only partially modified aren't overwriten.
|
||||
local data = {}
|
||||
local c_ignore = minetest.get_content_id("ignore")
|
||||
for i = 1, area:getVolume() do
|
||||
data[i] = c_ignore
|
||||
end
|
||||
return data
|
||||
end
|
||||
|
||||
|
||||
function mh.init(pos1, pos2)
|
||||
local manip = minetest.get_voxel_manip()
|
||||
local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)
|
||||
local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})
|
||||
return manip, area
|
||||
end
|
||||
|
||||
|
||||
function mh.init_radius(pos, radius)
|
||||
local pos1 = vector.subtract(pos, radius)
|
||||
local pos2 = vector.add(pos, radius)
|
||||
return mh.init(pos1, pos2)
|
||||
end
|
||||
|
||||
|
||||
function mh.init_axis_radius(base_pos, axis, radius)
|
||||
return mh.init_axis_radius_length(base_pos, axis, radius, radius)
|
||||
end
|
||||
|
||||
|
||||
function mh.init_axis_radius_length(base_pos, axis, radius, length)
|
||||
local other1, other2 = worldedit.get_axis_others(axis)
|
||||
local pos1 = {
|
||||
[axis] = base_pos[axis],
|
||||
[other1] = base_pos[other1] - radius,
|
||||
[other2] = base_pos[other2] - radius
|
||||
}
|
||||
local pos2 = {
|
||||
[axis] = base_pos[axis] + length,
|
||||
[other1] = base_pos[other1] + radius,
|
||||
[other2] = base_pos[other2] + radius
|
||||
}
|
||||
return mh.init(pos1, pos2)
|
||||
end
|
||||
|
||||
|
||||
function mh.finish(manip, data)
|
||||
-- Update map
|
||||
if data ~= nil then
|
||||
manip:set_data(data)
|
||||
end
|
||||
manip:write_to_map()
|
||||
manip:update_map()
|
||||
end
|
||||
|
||||
|
||||
-- returns an iterator that returns voxelarea indices for a hollow cuboid
|
||||
function mh.iter_hollowcuboid(area, minx, miny, minz, maxx, maxy, maxz)
|
||||
local i = area:index(minx, miny, minz) - 1
|
||||
local xrange = maxx - minx + 1
|
||||
local nextaction = i + 1 + xrange
|
||||
local do_hole = false
|
||||
|
||||
local y = 0
|
||||
local ydiff = maxy - miny
|
||||
local ystride = area.ystride
|
||||
local ymultistride = ydiff * ystride
|
||||
|
||||
local z = 0
|
||||
local zdiff = maxz - minz
|
||||
local zstride = area.zstride
|
||||
local zcorner = true
|
||||
|
||||
return function()
|
||||
-- continue i until it needs to jump ystride
|
||||
i = i + 1
|
||||
if i ~= nextaction then
|
||||
return i
|
||||
end
|
||||
|
||||
-- add the x offset if y (and z) are not 0 or maxy (or maxz)
|
||||
if do_hole then
|
||||
do_hole = false
|
||||
i = i + xrange - 2
|
||||
nextaction = i + 1
|
||||
return i
|
||||
end
|
||||
|
||||
-- continue y until maxy is exceeded
|
||||
y = y+1
|
||||
if y ~= ydiff + 1 then
|
||||
i = i + ystride - xrange
|
||||
if zcorner
|
||||
or y == ydiff then
|
||||
nextaction = i + xrange
|
||||
else
|
||||
nextaction = i + 1
|
||||
do_hole = true
|
||||
end
|
||||
return i
|
||||
end
|
||||
|
||||
-- continue z until maxz is exceeded
|
||||
z = z+1
|
||||
if z == zdiff + 1 then
|
||||
-- hollowcuboid finished, return nil
|
||||
return
|
||||
end
|
||||
|
||||
-- set i to index(minx, miny, minz + z) - 1
|
||||
i = i + zstride - (ymultistride + xrange)
|
||||
zcorner = z == zdiff
|
||||
|
||||
-- y is 0, so traverse the xs
|
||||
y = 0
|
||||
nextaction = i + xrange
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
function mh.iterp_hollowcuboid(area, minp, maxp)
|
||||
return mh.iter_hollowcuboid(area, minp.x, minp.y, minp.z,
|
||||
maxp.x, maxp.y, maxp.z)
|
||||
end
|
|
@ -1,8 +1,21 @@
|
|||
worldedit = worldedit or {}
|
||||
--- Compatibility functions.
|
||||
-- @module worldedit.compatibility
|
||||
|
||||
local function deprecated(new_func)
|
||||
local info = debug.getinfo(1, "n")
|
||||
local msg = "worldedit." .. info.name .. "() is deprecated."
|
||||
if new_func then
|
||||
msg = msg .. " Use worldedit." .. new_func .. "() instead."
|
||||
end
|
||||
minetest.log("deprecated", msg)
|
||||
end
|
||||
|
||||
worldedit.allocate_old = worldedit.allocate
|
||||
|
||||
worldedit.deserialize_old = worldedit.deserialize
|
||||
worldedit.metasave = function(pos1, pos2, filename)
|
||||
|
||||
function worldedit.metasave(pos1, pos2, filename)
|
||||
deprecated("save")
|
||||
local file, err = io.open(filename, "wb")
|
||||
if err then return 0 end
|
||||
local data, count = worldedit.serialize(pos1, pos2)
|
||||
|
@ -10,10 +23,55 @@ worldedit.metasave = function(pos1, pos2, filename)
|
|||
file:close()
|
||||
return count
|
||||
end
|
||||
worldedit.metaload = function(originpos, filename)
|
||||
filename = minetest.get_worldpath() .. "/schems/" .. file .. ".wem"
|
||||
local file, err = io.open(filename, "wb")
|
||||
if err then return 0 end
|
||||
|
||||
function worldedit.metaload(originpos, file_name)
|
||||
deprecated("load")
|
||||
local file_path = minetest.get_worldpath() ..
|
||||
"/schems/" .. file_name .. ".wem"
|
||||
local file, err = io.open(file_path, "wb")
|
||||
if err then
|
||||
return 0
|
||||
end
|
||||
local data = file:read("*a")
|
||||
return worldedit.deserialize(originpos, data)
|
||||
end
|
||||
|
||||
function worldedit.scale(pos1, pos2, factor)
|
||||
deprecated("stretch")
|
||||
return worldedit.stretch(pos1, pos2, factor, factor, factor)
|
||||
end
|
||||
|
||||
function worldedit.valueversion(value)
|
||||
deprecated("read_header")
|
||||
local version = worldedit.read_header(value)
|
||||
if not version or version > worldedit.LATEST_SERIALIZATION_VERSION then
|
||||
return 0
|
||||
end
|
||||
return version
|
||||
end
|
||||
|
||||
function worldedit.replaceinverse(pos1, pos2, search_node, replace_node)
|
||||
deprecated("replace")
|
||||
return worldedit.replace(pos1, pos2, search_node, replace_node, true)
|
||||
end
|
||||
|
||||
function worldedit.clearobjects(...)
|
||||
deprecated("clear_objects")
|
||||
return worldedit.clear_objects(...)
|
||||
end
|
||||
|
||||
function worldedit.hollow_sphere(pos, radius, node_name)
|
||||
deprecated("sphere")
|
||||
return worldedit.sphere(pos, radius, node_name, true)
|
||||
end
|
||||
|
||||
function worldedit.hollow_dome(pos, radius, node_name)
|
||||
deprecated("dome")
|
||||
return worldedit.dome(pos, radius, node_name, true)
|
||||
end
|
||||
|
||||
function worldedit.hollow_cylinder(pos, axis, length, radius, node_name)
|
||||
deprecated("cylinder")
|
||||
return worldedit.cylinder(pos, axis, length, radius, node_name, true)
|
||||
end
|
||||
|
||||
|
|
246
worldedit/cuboid.lua
Normal file
|
@ -0,0 +1,246 @@
|
|||
-- Expands or contracts the cuboid in all axes by amount (positive or negative)
|
||||
worldedit.cuboid_volumetric_expand = function(name, amount)
|
||||
local pos1 = worldedit.pos1[name]
|
||||
local pos2 = worldedit.pos2[name]
|
||||
|
||||
if pos1 == nil or pos2 == nil then
|
||||
return false, "Undefined cuboid"
|
||||
end
|
||||
|
||||
local delta1 = vector.new()
|
||||
local delta2 = vector.new()
|
||||
local delta_dir1
|
||||
local delta_dir2
|
||||
|
||||
delta1 = vector.add(delta1, amount)
|
||||
delta2 = vector.add(delta2, amount)
|
||||
delta_dir1, delta_dir2 = worldedit.get_expansion_directions(pos1, pos2)
|
||||
delta1 = vector.multiply(delta1, delta_dir1)
|
||||
delta2 = vector.multiply(delta2, delta_dir2)
|
||||
worldedit.pos1[name] = vector.add(pos1, delta1)
|
||||
worldedit.pos2[name] = vector.add(pos2, delta2)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
-- Expands or contracts the cuboid in a single axis by amount (positive or negative)
|
||||
worldedit.cuboid_linear_expand = function(name, axis, direction, amount)
|
||||
local pos1 = worldedit.pos1[name]
|
||||
local pos2 = worldedit.pos2[name]
|
||||
|
||||
if pos1 == nil or pos2 == nil then
|
||||
return false, "undefined cuboid"
|
||||
end
|
||||
|
||||
if direction ~= 1 and direction ~= -1 then
|
||||
return false, "invalid marker"
|
||||
end
|
||||
|
||||
local marker = worldedit.marker_get_closest_to_axis(name, axis, direction)
|
||||
local deltavect = vector.new()
|
||||
|
||||
if axis == 'x' then
|
||||
deltavect.x = amount * direction
|
||||
elseif axis == 'y' then
|
||||
deltavect.y = amount * direction
|
||||
elseif axis == 'z' then
|
||||
deltavect.z = amount * direction
|
||||
else
|
||||
return false, "invalid axis"
|
||||
end
|
||||
|
||||
worldedit.marker_move(name, marker, deltavect)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
-- Shifts the cuboid by '+-amount' in axis 'axis'
|
||||
worldedit.cuboid_shift = function(name, axis, amount)
|
||||
local pos1 = worldedit.pos1[name]
|
||||
local pos2 = worldedit.pos2[name]
|
||||
|
||||
if pos1 == nil or pos2 == nil then
|
||||
return false, "undefined cuboid"
|
||||
end
|
||||
|
||||
assert(not rawequal(pos1, pos2)) -- vectors must not alias
|
||||
|
||||
if axis == 'x' then
|
||||
worldedit.pos1[name].x = pos1.x + amount
|
||||
worldedit.pos2[name].x = pos2.x + amount
|
||||
elseif axis == 'y' then
|
||||
worldedit.pos1[name].y = pos1.y + amount
|
||||
worldedit.pos2[name].y = pos2.y + amount
|
||||
elseif axis == 'z' then
|
||||
worldedit.pos1[name].z = pos1.z + amount
|
||||
worldedit.pos2[name].z = pos2.z + amount
|
||||
else
|
||||
return false, "invalid axis"
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
-- Moves the location of a single marker by adding deltavector
|
||||
worldedit.marker_move = function(name, marker, deltavector)
|
||||
if marker ~= 1 and marker ~= 2 then
|
||||
return false
|
||||
end
|
||||
|
||||
if marker == 1 then
|
||||
local pos = worldedit.pos1[name]
|
||||
worldedit.pos1[name] = vector.add(deltavector, pos)
|
||||
else
|
||||
local pos = worldedit.pos2[name]
|
||||
worldedit.pos2[name] = vector.add(deltavector, pos)
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
-- Returns two vectors with the directions for volumetric expansion
|
||||
worldedit.get_expansion_directions = function(mark1, mark2)
|
||||
if mark1 == nil or mark2 == nil then
|
||||
return
|
||||
end
|
||||
local dir1 = vector.new()
|
||||
local dir2 = vector.new()
|
||||
|
||||
if mark1.x < mark2.x then
|
||||
dir1.x = -1
|
||||
dir2.x = 1
|
||||
else
|
||||
dir1.x = 1
|
||||
dir2.x = -1
|
||||
end
|
||||
if mark1.y < mark2.y then
|
||||
dir1.y = -1
|
||||
dir2.y = 1
|
||||
else
|
||||
dir1.y = 1
|
||||
dir2.y = -1
|
||||
end
|
||||
if mark1.z < mark2.z then
|
||||
dir1.z = -1
|
||||
dir2.z = 1
|
||||
else
|
||||
dir1.z = 1
|
||||
dir2.z = -1
|
||||
end
|
||||
return dir1, dir2
|
||||
end
|
||||
|
||||
|
||||
-- Return the marker that is closest to the player
|
||||
worldedit.marker_get_closest_to_player = function(name)
|
||||
local player = assert(minetest.get_player_by_name(name))
|
||||
local playerpos = player:get_pos()
|
||||
local dist1 = vector.distance(playerpos, worldedit.pos1[name])
|
||||
local dist2 = vector.distance(playerpos, worldedit.pos2[name])
|
||||
|
||||
if dist1 < dist2 then
|
||||
return 1
|
||||
else
|
||||
return 2
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Returns the closest marker to the specified axis and direction
|
||||
worldedit.marker_get_closest_to_axis = function(name, axis, direction)
|
||||
local pos1 = vector.new()
|
||||
local pos2 = vector.new()
|
||||
|
||||
if direction ~= 1 and direction ~= -1 then
|
||||
return nil
|
||||
end
|
||||
|
||||
if axis == 'x' then
|
||||
pos1.x = worldedit.pos1[name].x * direction
|
||||
pos2.x = worldedit.pos2[name].x * direction
|
||||
if pos1.x > pos2.x then
|
||||
return 1
|
||||
else
|
||||
return 2
|
||||
end
|
||||
elseif axis == 'y' then
|
||||
pos1.y = worldedit.pos1[name].y * direction
|
||||
pos2.y = worldedit.pos2[name].y * direction
|
||||
if pos1.y > pos2.y then
|
||||
return 1
|
||||
else
|
||||
return 2
|
||||
end
|
||||
elseif axis == 'z' then
|
||||
pos1.z = worldedit.pos1[name].z * direction
|
||||
pos2.z = worldedit.pos2[name].z * direction
|
||||
if pos1.z > pos2.z then
|
||||
return 1
|
||||
else
|
||||
return 2
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Translates up, down, left, right, front, back to their corresponding axes and
|
||||
-- directions according to faced direction
|
||||
worldedit.translate_direction = function(name, direction)
|
||||
local axis, dir = worldedit.player_axis(name)
|
||||
local resaxis, resdir
|
||||
|
||||
if direction == "up" then
|
||||
return 'y', 1
|
||||
end
|
||||
|
||||
if direction == "down" then
|
||||
return 'y', -1
|
||||
end
|
||||
|
||||
if direction == "front" then
|
||||
if axis == "y" then
|
||||
resaxis = nil
|
||||
resdir = nil
|
||||
else
|
||||
resaxis = axis
|
||||
resdir = dir
|
||||
end
|
||||
end
|
||||
|
||||
if direction == "back" then
|
||||
if axis == "y" then
|
||||
resaxis = nil
|
||||
resdir = nil
|
||||
else
|
||||
resaxis = axis
|
||||
resdir = -dir
|
||||
end
|
||||
end
|
||||
|
||||
if direction == "left" then
|
||||
if axis == 'x' then
|
||||
resaxis = 'z'
|
||||
resdir = dir
|
||||
elseif axis == 'z' then
|
||||
resaxis = 'x'
|
||||
resdir = -dir
|
||||
end
|
||||
end
|
||||
|
||||
if direction == "right" then
|
||||
if axis == 'x' then
|
||||
resaxis = 'z'
|
||||
resdir = -dir
|
||||
elseif axis == 'z' then
|
||||
resaxis = 'x'
|
||||
resdir = dir
|
||||
end
|
||||
end
|
||||
|
||||
return resaxis, resdir
|
||||
end
|
|
@ -1,15 +1,45 @@
|
|||
local path = minetest.get_modpath("worldedit")
|
||||
--- WorldEdit mod for the Minetest engine
|
||||
-- @module worldedit
|
||||
-- @release 1.3
|
||||
-- @copyright 2012 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote)
|
||||
-- @license GNU Affero General Public License version 3 (AGPLv3)
|
||||
-- @author sfan5
|
||||
-- @author Anthony Zang (Uberi/Temperest)
|
||||
-- @author Bret O'Donnel (cornernote)
|
||||
-- @author ShadowNinja
|
||||
|
||||
local loadmodule = function(path)
|
||||
return pcall(function()
|
||||
dofile(path)
|
||||
end)
|
||||
|
||||
worldedit = {}
|
||||
|
||||
local ver = {major=1, minor=3}
|
||||
worldedit.version = ver
|
||||
worldedit.version_string = string.format("%d.%d", ver.major, ver.minor)
|
||||
|
||||
local path = minetest.get_modpath(minetest.get_current_modname())
|
||||
|
||||
local function load_module(path)
|
||||
local file = io.open(path, "r")
|
||||
if not file then return end
|
||||
file:close()
|
||||
return dofile(path)
|
||||
end
|
||||
|
||||
loadmodule(path .. "/manipulations.lua")
|
||||
loadmodule(path .. "/primitives.lua")
|
||||
loadmodule(path .. "/visualization.lua")
|
||||
loadmodule(path .. "/serialization.lua")
|
||||
loadmodule(path .. "/code.lua")
|
||||
loadmodule(path .. "/compatibility.lua")
|
||||
loadmodule(path .. "/queue.lua")
|
||||
dofile(path .. "/common.lua")
|
||||
load_module(path .. "/manipulations.lua")
|
||||
load_module(path .. "/primitives.lua")
|
||||
load_module(path .. "/transformations.lua")
|
||||
load_module(path .. "/visualization.lua")
|
||||
load_module(path .. "/serialization.lua")
|
||||
load_module(path .. "/code.lua")
|
||||
load_module(path .. "/compatibility.lua")
|
||||
load_module(path .. "/cuboid.lua")
|
||||
|
||||
|
||||
if minetest.settings:get_bool("log_mods") then
|
||||
print("[WorldEdit] Loaded!")
|
||||
end
|
||||
|
||||
if minetest.settings:get_bool("worldedit_run_tests") then
|
||||
dofile(path .. "/test/init.lua")
|
||||
minetest.after(0, worldedit.run_tests)
|
||||
end
|
||||
|
|
|
@ -1,417 +1,338 @@
|
|||
worldedit = worldedit or {}
|
||||
--- Generic node manipulations.
|
||||
-- @module worldedit.manipulations
|
||||
|
||||
--modifies positions `pos1` and `pos2` so that each component of `pos1` is less than or equal to its corresponding conent of `pos2`, returning two new positions
|
||||
worldedit.sort_pos = function(pos1, pos2)
|
||||
pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}
|
||||
pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
|
||||
if pos1.x > pos2.x then
|
||||
pos2.x, pos1.x = pos1.x, pos2.x
|
||||
end
|
||||
if pos1.y > pos2.y then
|
||||
pos2.y, pos1.y = pos1.y, pos2.y
|
||||
end
|
||||
if pos1.z > pos2.z then
|
||||
pos2.z, pos1.z = pos1.z, pos2.z
|
||||
end
|
||||
return pos1, pos2
|
||||
end
|
||||
local mh = worldedit.manip_helpers
|
||||
|
||||
--determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume
|
||||
worldedit.volume = function(pos1, pos2)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1)
|
||||
end
|
||||
|
||||
--sets a region defined by positions `pos1` and `pos2` to `nodename`, returning the number of nodes filled
|
||||
worldedit.set = function(pos1, pos2, nodename, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
--- Sets a region to `node_names`.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param node_names Node name or list of node names.
|
||||
-- @return The number of nodes set.
|
||||
function worldedit.set(pos1, pos2, node_names)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local node = {name=nodename}
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
env:add_node(pos, node)
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
local manip, area = mh.init(pos1, pos2)
|
||||
local data = mh.get_empty_data(area)
|
||||
|
||||
if type(node_names) == "string" then -- Only one type of node
|
||||
local id = minetest.get_content_id(node_names)
|
||||
-- Fill area with node
|
||||
for i in area:iterp(pos1, pos2) do
|
||||
data[i] = id
|
||||
end
|
||||
else -- Several types of nodes specified
|
||||
local node_ids = {}
|
||||
for i, v in ipairs(node_names) do
|
||||
node_ids[i] = minetest.get_content_id(v)
|
||||
end
|
||||
-- Fill area randomly with nodes
|
||||
local id_count, rand = #node_ids, math.random
|
||||
for i in area:iterp(pos1, pos2) do
|
||||
data[i] = node_ids[rand(id_count)]
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
|
||||
mh.finish(manip, data)
|
||||
|
||||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
--replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced
|
||||
worldedit.replace = function(pos1, pos2, searchnode, replacenode, env)
|
||||
--- Sets param2 of a region.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param param2 Value of param2 to set
|
||||
-- @return The number of nodes set.
|
||||
function worldedit.set_param2(pos1, pos2, param2)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local manip, area = mh.init(pos1, pos2)
|
||||
local param2_data = manip:get_param2_data()
|
||||
|
||||
-- Set param2 for every node
|
||||
for i in area:iterp(pos1, pos2) do
|
||||
param2_data[i] = param2
|
||||
end
|
||||
|
||||
-- Update map
|
||||
manip:set_param2_data(param2_data)
|
||||
manip:write_to_map()
|
||||
manip:update_map()
|
||||
|
||||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
--- Replaces all instances of `search_node` with `replace_node` in a region.
|
||||
-- When `inverse` is `true`, replaces all instances that are NOT `search_node`.
|
||||
-- @return The number of nodes replaced.
|
||||
function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
|
||||
if minetest.registered_nodes[searchnode] == nil then
|
||||
searchnode = "default:" .. searchnode
|
||||
end
|
||||
local manip, area = mh.init(pos1, pos2)
|
||||
local data = manip:get_data()
|
||||
|
||||
local search_id = minetest.get_content_id(search_node)
|
||||
local replace_id = minetest.get_content_id(replace_node)
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
local node = {name=replacenode}
|
||||
local count = 0
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
if env:get_node(pos).name == searchnode then
|
||||
env:add_node(pos, node)
|
||||
count = count + 1
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
|
||||
if not inverse then
|
||||
for i in area:iterp(pos1, pos2) do
|
||||
if data[i] == search_id then
|
||||
data[i] = replace_id
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
else
|
||||
for i in area:iterp(pos1, pos2) do
|
||||
if data[i] ~= search_id then
|
||||
data[i] = replace_id
|
||||
count = count + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
|
||||
mh.finish(manip, data)
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
--replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced
|
||||
worldedit.replaceinverse = function(pos1, pos2, searchnode, replacenode, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
|
||||
if minetest.registered_nodes[searchnode] == nil then
|
||||
searchnode = "default:" .. searchnode
|
||||
--- Copies a region along `axis` by `amount` nodes.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param axis Axis ("x", "y", or "z")
|
||||
-- @param amount
|
||||
-- @return The number of nodes copied.
|
||||
function worldedit.copy(pos1, pos2, axis, amount)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
-- Decide if we need to copy stuff backwards (only applies to metadata)
|
||||
local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1)
|
||||
|
||||
local off = vector.new()
|
||||
off[axis] = amount
|
||||
return worldedit.copy2(pos1, pos2, off, backwards)
|
||||
end
|
||||
|
||||
--- Copies a region by offset vector `off`.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param off
|
||||
-- @param meta_backwards (not officially part of API)
|
||||
-- @return The number of nodes copied.
|
||||
function worldedit.copy2(pos1, pos2, off, meta_backwards)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local src_manip, src_area = mh.init(pos1, pos2)
|
||||
local src_stride = vector.new(1, src_area.ystride, src_area.zstride)
|
||||
local src_offset = vector.subtract(pos1, src_area.MinEdge)
|
||||
|
||||
local dpos1 = vector.add(pos1, off)
|
||||
local dpos2 = vector.add(pos2, off)
|
||||
local dim = vector.add(vector.subtract(pos2, pos1), 1)
|
||||
|
||||
local dst_manip, dst_area = mh.init(dpos1, dpos2)
|
||||
local dst_stride = vector.new(1, dst_area.ystride, dst_area.zstride)
|
||||
local dst_offset = vector.subtract(dpos1, dst_area.MinEdge)
|
||||
|
||||
local function do_copy(src_data, dst_data)
|
||||
for z = 0, dim.z-1 do
|
||||
local src_index_z = (src_offset.z + z) * src_stride.z + 1 -- +1 for 1-based indexing
|
||||
local dst_index_z = (dst_offset.z + z) * dst_stride.z + 1
|
||||
for y = 0, dim.y-1 do
|
||||
local src_index_y = src_index_z + (src_offset.y + y) * src_stride.y
|
||||
local dst_index_y = dst_index_z + (dst_offset.y + y) * dst_stride.y
|
||||
-- Copy entire row at once
|
||||
local src_index_x = src_index_y + src_offset.x
|
||||
local dst_index_x = dst_index_y + dst_offset.x
|
||||
for x = 0, dim.x-1 do
|
||||
dst_data[dst_index_x + x] = src_data[src_index_x + x]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
local node = {name=replacenode}
|
||||
-- Copy node data
|
||||
local src_data = src_manip:get_data()
|
||||
local dst_data = dst_manip:get_data()
|
||||
do_copy(src_data, dst_data)
|
||||
dst_manip:set_data(dst_data)
|
||||
|
||||
-- Copy param1
|
||||
src_manip:get_light_data(src_data)
|
||||
dst_manip:get_light_data(dst_data)
|
||||
do_copy(src_data, dst_data)
|
||||
dst_manip:set_light_data(dst_data)
|
||||
|
||||
-- Copy param2
|
||||
src_manip:get_param2_data(src_data)
|
||||
dst_manip:get_param2_data(dst_data)
|
||||
do_copy(src_data, dst_data)
|
||||
dst_manip:set_param2_data(dst_data)
|
||||
|
||||
mh.finish(dst_manip)
|
||||
|
||||
-- Copy metadata
|
||||
local get_meta = minetest.get_meta
|
||||
if meta_backwards then
|
||||
for z = dim.z-1, 0, -1 do
|
||||
for y = dim.y-1, 0, -1 do
|
||||
for x = dim.x-1, 0, -1 do
|
||||
local pos = vector.new(pos1.x+x, pos1.y+y, pos1.z+z)
|
||||
local meta = get_meta(pos):to_table()
|
||||
pos = vector.add(pos, off)
|
||||
get_meta(pos):from_table(meta)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
for z = 0, dim.z-1 do
|
||||
for y = 0, dim.y-1 do
|
||||
for x = 0, dim.x-1 do
|
||||
local pos = vector.new(pos1.x+x, pos1.y+y, pos1.z+z)
|
||||
local meta = get_meta(pos):to_table()
|
||||
pos = vector.add(pos, off)
|
||||
get_meta(pos):from_table(meta)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
--- Deletes all node metadata in the region
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @return The number of nodes that had their meta deleted.
|
||||
function worldedit.delete_meta(pos1, pos2)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)
|
||||
local get_meta = minetest.get_meta
|
||||
for _, pos in ipairs(meta_positions) do
|
||||
get_meta(pos):from_table(nil)
|
||||
end
|
||||
|
||||
return #meta_positions
|
||||
end
|
||||
|
||||
--- Moves a region along `axis` by `amount` nodes.
|
||||
-- @return The number of nodes moved.
|
||||
function worldedit.move(pos1, pos2, axis, amount)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local dim = vector.add(vector.subtract(pos2, pos1), 1)
|
||||
local overlap = math.abs(amount) < dim[axis]
|
||||
-- Decide if we need to copy metadata backwards
|
||||
local backwards = overlap and amount > 0
|
||||
|
||||
local function nuke_area(my_off, my_dim)
|
||||
if my_dim.x == 0 or my_dim.y == 0 or my_dim.z == 0 then
|
||||
return
|
||||
end
|
||||
local my_pos1 = vector.add(pos1, my_off)
|
||||
local my_pos2 = vector.subtract(vector.add(my_pos1, my_dim), 1)
|
||||
worldedit.set(my_pos1, my_pos2, "air")
|
||||
worldedit.delete_meta(my_pos1, my_pos2)
|
||||
end
|
||||
|
||||
-- Copy stuff to new location
|
||||
local off = vector.new()
|
||||
off[axis] = amount
|
||||
worldedit.copy2(pos1, pos2, off, backwards)
|
||||
-- Nuke old area
|
||||
if not overlap then
|
||||
nuke_area(vector.new(), dim)
|
||||
else
|
||||
-- Source and destination region are overlapping, which means we can't
|
||||
-- blindly delete the [pos1, pos2] area
|
||||
local leftover = vector.new(dim) -- size of the leftover slice
|
||||
leftover[axis] = math.abs(amount)
|
||||
if amount > 0 then
|
||||
nuke_area(vector.new(), leftover)
|
||||
else
|
||||
local top = vector.new() -- offset of the leftover slice from pos1
|
||||
top[axis] = dim[axis] - math.abs(amount)
|
||||
nuke_area(top, leftover)
|
||||
end
|
||||
end
|
||||
|
||||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
|
||||
--- Attempts to fix the lighting in a region.
|
||||
-- @return The number of nodes updated.
|
||||
function worldedit.fixlight(pos1, pos2)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local vmanip = minetest.get_voxel_manip(pos1, pos2)
|
||||
vmanip:write_to_map()
|
||||
vmanip:update_map() -- this updates the lighting
|
||||
|
||||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
|
||||
--- Clears all objects in a region.
|
||||
-- @return The number of objects cleared.
|
||||
function worldedit.clear_objects(pos1, pos2)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local function should_delete(obj)
|
||||
-- Avoid players and WorldEdit entities
|
||||
if obj:is_player() then
|
||||
return false
|
||||
end
|
||||
local entity = obj:get_luaentity()
|
||||
return not (entity and entity.name:find("^worldedit:"))
|
||||
end
|
||||
|
||||
-- Offset positions to include full nodes (positions are in the center of nodes)
|
||||
pos1 = vector.add(pos1, -0.5)
|
||||
pos2 = vector.add(pos2, 0.5)
|
||||
|
||||
local count = 0
|
||||
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 name = env:get_node(pos).name
|
||||
if name ~= "ignore" and name ~= searchnode then
|
||||
env:add_node(pos, node)
|
||||
count = count + 1
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
if minetest.get_objects_in_area then
|
||||
local objects = minetest.get_objects_in_area(pos1, pos2)
|
||||
|
||||
for _, obj in pairs(objects) do
|
||||
if should_delete(obj) then
|
||||
obj:remove()
|
||||
count = count + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes copied
|
||||
worldedit.copy = function(pos1, pos2, axis, amount, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
|
||||
if amount < 0 then
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node = env:get_node(pos) --obtain current node
|
||||
local meta = env:get_meta(pos):to_table() --get meta of current node
|
||||
local value = pos[axis] --store current position
|
||||
pos[axis] = value + amount --move along axis
|
||||
env:add_node(pos, node) --copy node to new position
|
||||
env: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 = {x=pos2.x, y=0, z=0}
|
||||
while pos.x >= pos1.x do
|
||||
pos.y = pos2.y
|
||||
while pos.y >= pos1.y do
|
||||
pos.z = pos2.z
|
||||
while pos.z >= pos1.z do
|
||||
local node = minetest.env:get_node(pos) --obtain current node
|
||||
local meta = env:get_meta(pos):to_table() --get meta of current node
|
||||
local value = pos[axis] --store current position
|
||||
pos[axis] = value + amount --move along axis
|
||||
minetest.env:add_node(pos, node) --copy node to new position
|
||||
env: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
|
||||
|
||||
--moves the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes moved
|
||||
worldedit.move = function(pos1, pos2, axis, amount, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
|
||||
if amount < 0 then
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node = env:get_node(pos) --obtain current node
|
||||
local meta = env:get_meta(pos):to_table() --get metadata of current node
|
||||
env:remove_node(pos)
|
||||
local value = pos[axis] --store current position
|
||||
pos[axis] = value + amount --move along axis
|
||||
env:add_node(pos, node) --move node to new position
|
||||
env: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 = {x=pos2.x, y=0, z=0}
|
||||
while pos.x >= pos1.x do
|
||||
pos.y = pos2.y
|
||||
while pos.y >= pos1.y do
|
||||
pos.z = pos2.z
|
||||
while pos.z >= pos1.z do
|
||||
local node = env:get_node(pos) --obtain current node
|
||||
local meta = env:get_meta(pos):to_table() --get metadata of current node
|
||||
env:remove_node(pos)
|
||||
local value = pos[axis] --store current position
|
||||
pos[axis] = value + amount --move along axis
|
||||
env:add_node(pos, node) --move node to new position
|
||||
env: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
|
||||
|
||||
--duplicates the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") `count` times, returning the number of nodes stacked
|
||||
worldedit.stack = function(pos1, pos2, axis, count, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local length = pos2[axis] - pos1[axis] + 1
|
||||
if count < 0 then
|
||||
count = -count
|
||||
length = -length
|
||||
end
|
||||
local amount = 0
|
||||
local copy = worldedit.copy
|
||||
for i = 1, count do
|
||||
amount = amount + length
|
||||
copy(pos1, pos2, axis, amount, env)
|
||||
end
|
||||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
--transposes a region defined by the positions `pos1` and `pos2` between the `axis1` and `axis2` axes, returning the number of nodes transposed, the new position 1, and the new position 2
|
||||
worldedit.transpose = function(pos1, pos2, axis1, axis2, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local compare
|
||||
local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]
|
||||
|
||||
if extent1 > extent2 then
|
||||
compare = function(extent1, extent2)
|
||||
return extent1 > extent2
|
||||
end
|
||||
else
|
||||
compare = function(extent1, extent2)
|
||||
return extent1 < extent2
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--calculate the new position 2 after transposition
|
||||
local newpos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
|
||||
newpos2[axis1] = pos1[axis1] + extent2
|
||||
newpos2[axis2] = pos1[axis2] + extent1
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
if env == nil then env = minetest.env end
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
|
||||
if compare(extent1, extent2) then --transpose only if below the diagonal
|
||||
local node1 = env:get_node(pos)
|
||||
local meta1 = env:get_meta(pos):to_table()
|
||||
local value1, value2 = pos[axis1], pos[axis2] --save position values
|
||||
pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents
|
||||
local node2 = env:get_node(pos)
|
||||
local meta2 = env:get_meta(pos):to_table()
|
||||
env:add_node(pos, node1)
|
||||
env:get_meta(pos):from_table(meta1)
|
||||
pos[axis1], pos[axis2] = value1, value2 --restore position values
|
||||
env:add_node(pos, node2)
|
||||
env:get_meta(pos):from_table(meta2)
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
return worldedit.volume(pos1, pos2), pos1, newpos2
|
||||
end
|
||||
|
||||
--flips a region defined by the positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z"), returning the number of nodes flipped
|
||||
worldedit.flip = function(pos1, pos2, axis, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
local start = pos1[axis] + pos2[axis]
|
||||
pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
|
||||
if env == nil then env = minetest.env end
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node1 = env:get_node(pos)
|
||||
local meta1 = env:get_meta(pos):to_table()
|
||||
local value = pos[axis]
|
||||
pos[axis] = start - value
|
||||
local node2 = env:get_node(pos)
|
||||
local meta2 = env:get_meta(pos):to_table()
|
||||
env:add_node(pos, node1)
|
||||
env:get_meta(pos):from_table(meta1)
|
||||
pos[axis] = value
|
||||
env:add_node(pos, node2)
|
||||
env:get_meta(pos):from_table(meta2)
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
--rotates a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise around axis `axis` (90 degree increment), returning the number of nodes rotated
|
||||
worldedit.rotate = function(pos1, pos2, axis, angle, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local axis1, axis2
|
||||
if axis == "x" then
|
||||
axis1, axis2 = "z", "y"
|
||||
elseif axis == "y" then
|
||||
axis1, axis2 = "x", "z"
|
||||
else --axis == "z"
|
||||
axis1, axis2 = "y", "x"
|
||||
end
|
||||
angle = angle % 360
|
||||
|
||||
local count
|
||||
if angle == 90 then
|
||||
worldedit.flip(pos1, pos2, axis1, env)
|
||||
count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env)
|
||||
elseif angle == 180 then
|
||||
worldedit.flip(pos1, pos2, axis1, env)
|
||||
count = worldedit.flip(pos1, pos2, axis2, env)
|
||||
elseif angle == 270 then
|
||||
worldedit.flip(pos1, pos2, axis2, env)
|
||||
count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2, env)
|
||||
end
|
||||
return count, pos1, pos2
|
||||
end
|
||||
|
||||
--rotates all oriented nodes in a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise (90 degree increment) around the Y axis, returning the number of nodes oriented
|
||||
worldedit.orient = function(pos1, pos2, angle, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local nodes = minetest.registered_nodes
|
||||
if env == nil then env = minetest.env end
|
||||
local wallmounted = {
|
||||
[90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3},
|
||||
[180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4},
|
||||
[270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2}
|
||||
-- Fallback implementation via get_objects_inside_radius
|
||||
-- Center of region
|
||||
local center = {
|
||||
x = pos1.x + ((pos2.x - pos1.x) / 2),
|
||||
y = pos1.y + ((pos2.y - pos1.y) / 2),
|
||||
z = pos1.z + ((pos2.z - pos1.z) / 2)
|
||||
}
|
||||
local facedir = {
|
||||
[90]={[0]=1, [1]=2, [2]=3, [3]=0},
|
||||
[180]={[0]=2, [1]=3, [2]=0, [3]=1},
|
||||
[270]={[0]=3, [1]=0, [2]=1, [3]=2}
|
||||
}
|
||||
|
||||
angle = angle % 360
|
||||
if angle == 0 then
|
||||
return 0
|
||||
end
|
||||
local wallmounted_substitution = wallmounted[angle]
|
||||
local facedir_substitution = facedir[angle]
|
||||
|
||||
local count = 0
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node = env:get_node(pos)
|
||||
local def = nodes[node.name]
|
||||
if def then
|
||||
if def.paramtype2 == "wallmounted" then
|
||||
node.param2 = wallmounted_substitution[node.param2]
|
||||
local meta = env:get_meta(pos):to_table()
|
||||
env:add_node(pos, node)
|
||||
env:get_meta(pos):from_table(meta)
|
||||
count = count + 1
|
||||
elseif def.paramtype2 == "facedir" then
|
||||
node.param2 = facedir_substitution[node.param2]
|
||||
local meta = env:get_meta(pos):to_table()
|
||||
env:add_node(pos, node)
|
||||
env:get_meta(pos):from_table(meta)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
-- Bounding sphere radius
|
||||
local radius = math.sqrt(
|
||||
(center.x - pos1.x) ^ 2 +
|
||||
(center.y - pos1.y) ^ 2 +
|
||||
(center.z - pos1.z) ^ 2)
|
||||
local objects = minetest.get_objects_inside_radius(center, radius)
|
||||
for _, obj in pairs(objects) do
|
||||
if should_delete(obj) then
|
||||
local pos = obj:get_pos()
|
||||
if pos.x >= pos1.x and pos.x <= pos2.x and
|
||||
pos.y >= pos1.y and pos.y <= pos2.y and
|
||||
pos.z >= pos1.z and pos.z <= pos2.z then
|
||||
-- Inside region
|
||||
obj:remove()
|
||||
count = count + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated
|
||||
worldedit.fixlight = function(pos1, pos2, env)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
local count = 0
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
if env:get_node(pos).name == "air" then
|
||||
env:dig_node(pos)
|
||||
count = count + 1
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
|
2
worldedit/mod.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
name = worldedit
|
||||
description = WorldEdit main functionality & API
|
|
@ -1,311 +1,316 @@
|
|||
worldedit = worldedit or {}
|
||||
--- Functions for creating primitive shapes.
|
||||
-- @module worldedit.primitives
|
||||
|
||||
--adds a hollow sphere at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added
|
||||
worldedit.hollow_sphere = function(pos, radius, nodename, env)
|
||||
local node = {name=nodename}
|
||||
local pos1 = {x=0, y=0, z=0}
|
||||
local min_radius = radius * (radius - 1)
|
||||
local max_radius = radius * (radius + 1)
|
||||
local mh = worldedit.manip_helpers
|
||||
|
||||
|
||||
--- Adds a cube
|
||||
-- @param pos Position of ground level center of cube
|
||||
-- @param width Cube width. (x)
|
||||
-- @param height Cube height. (y)
|
||||
-- @param length Cube length. (z)
|
||||
-- @param node_name Name of node to make cube of.
|
||||
-- @param hollow Whether the cube should be hollow.
|
||||
-- @return The number of nodes added.
|
||||
function worldedit.cube(pos, width, height, length, node_name, hollow)
|
||||
-- Set up voxel manipulator
|
||||
local basepos = vector.subtract(pos,
|
||||
vector.new(math.floor(width / 2), 0, math.floor(length / 2)))
|
||||
local endpos = vector.add(basepos,
|
||||
vector.new(width - 1, height - 1, length - 1))
|
||||
local manip, area = mh.init(basepos, endpos)
|
||||
local data = mh.get_empty_data(area)
|
||||
|
||||
-- Add cube
|
||||
local node_id = minetest.get_content_id(node_name)
|
||||
local count = 0
|
||||
if env == nil then env = minetest.env end
|
||||
for x = -radius, radius do
|
||||
pos1.x = pos.x + x
|
||||
local iterfunc
|
||||
if hollow then
|
||||
iterfunc = mh.iterp_hollowcuboid(area, basepos, endpos)
|
||||
else
|
||||
iterfunc = area:iterp(basepos, endpos)
|
||||
end
|
||||
|
||||
for vi in iterfunc do
|
||||
data[vi] = node_id
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
mh.finish(manip, data)
|
||||
return count
|
||||
end
|
||||
|
||||
--- Adds a sphere of `node_name` centered at `pos`.
|
||||
-- @param pos Position to center sphere at.
|
||||
-- @param radius Sphere radius.
|
||||
-- @param node_name Name of node to make shere of.
|
||||
-- @param hollow Whether the sphere should be hollow.
|
||||
-- @return The number of nodes added.
|
||||
function worldedit.sphere(pos, radius, node_name, hollow)
|
||||
local manip, area = mh.init_radius(pos, radius)
|
||||
|
||||
local data = mh.get_empty_data(area)
|
||||
|
||||
-- Fill selected area with node
|
||||
local node_id = minetest.get_content_id(node_name)
|
||||
local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
|
||||
local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
|
||||
local stride_z, stride_y = area.zstride, area.ystride
|
||||
local count = 0
|
||||
for z = -radius, radius do
|
||||
-- Offset contributed by z plus 1 to make it 1-indexed
|
||||
local new_z = (z + offset_z) * stride_z + 1
|
||||
for y = -radius, radius do
|
||||
pos1.y = pos.y + y
|
||||
for z = -radius, radius do
|
||||
if x*x+y*y+z*z >= min_radius and x*x+y*y+z*z <= max_radius then
|
||||
pos1.z = pos.z + z
|
||||
env:add_node(pos1, node)
|
||||
local new_y = new_z + (y + offset_y) * stride_y
|
||||
for x = -radius, radius do
|
||||
local squared = x * x + y * y + z * z
|
||||
if squared <= max_radius and (not hollow or squared >= min_radius) then
|
||||
-- Position is on surface of sphere
|
||||
local i = new_y + (x + offset_x)
|
||||
data[i] = node_id
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mh.finish(manip, data)
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
--adds a sphere at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added
|
||||
worldedit.sphere = function(pos, radius, nodename, env)
|
||||
local node = {name=nodename}
|
||||
local pos1 = {x=0, y=0, z=0}
|
||||
local max_radius = radius * (radius + 1)
|
||||
|
||||
--- Adds a dome.
|
||||
-- @param pos Position to center dome at.
|
||||
-- @param radius Dome radius. Negative for concave domes.
|
||||
-- @param node_name Name of node to make dome of.
|
||||
-- @param hollow Whether the dome should be hollow.
|
||||
-- @return The number of nodes added.
|
||||
-- TODO: Add axis option.
|
||||
function worldedit.dome(pos, radius, node_name, hollow)
|
||||
local min_y, max_y = 0, radius
|
||||
if radius < 0 then
|
||||
radius = -radius
|
||||
min_y, max_y = -radius, 0
|
||||
end
|
||||
|
||||
local manip, area = mh.init_axis_radius(pos, "y", radius)
|
||||
local data = mh.get_empty_data(area)
|
||||
|
||||
-- Add dome
|
||||
local node_id = minetest.get_content_id(node_name)
|
||||
local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
|
||||
local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
|
||||
local stride_z, stride_y = area.zstride, area.ystride
|
||||
local count = 0
|
||||
if env == nil then env = minetest.env end
|
||||
for x = -radius, radius do
|
||||
pos1.x = pos.x + x
|
||||
for y = -radius, radius do
|
||||
pos1.y = pos.y + y
|
||||
for z = -radius, radius do
|
||||
if x*x+y*y+z*z <= max_radius then
|
||||
pos1.z = pos.z + z
|
||||
env:add_node(pos1, node)
|
||||
for z = -radius, radius do
|
||||
local new_z = (z + offset_z) * stride_z + 1 --offset contributed by z plus 1 to make it 1-indexed
|
||||
for y = min_y, max_y do
|
||||
local new_y = new_z + (y + offset_y) * stride_y
|
||||
for x = -radius, radius do
|
||||
local squared = x * x + y * y + z * z
|
||||
if squared <= max_radius and (not hollow or squared >= min_radius) then
|
||||
-- Position is in dome
|
||||
local i = new_y + (x + offset_x)
|
||||
data[i] = node_id
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
mh.finish(manip, data)
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
--adds a hollow dome at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added
|
||||
worldedit.hollow_dome = function(pos, radius, nodename, env) --wip: use bresenham sphere for maximum speed
|
||||
local node = {name=nodename}
|
||||
local pos1 = {x=0, y=0, z=0}
|
||||
local min_radius = radius * (radius - 1)
|
||||
local max_radius = radius * (radius + 1)
|
||||
local count = 0
|
||||
if env == nil then env = minetest.env end
|
||||
for x = -radius, radius do
|
||||
pos1.x = pos.x + x
|
||||
for y = 0, radius do
|
||||
pos1.y = pos.y + y
|
||||
for z = -radius, radius do
|
||||
if x*x+y*y+z*z >= min_radius and x*x+y*y+z*z <= max_radius then
|
||||
pos1.z = pos.z + z
|
||||
env:add_node(pos1, node)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
--- Adds a cylinder.
|
||||
-- @param pos Position to center base of cylinder at.
|
||||
-- @param axis Axis ("x", "y", or "z")
|
||||
-- @param length Cylinder length.
|
||||
-- @param radius1 Cylinder base radius.
|
||||
-- @param radius2 Cylinder top radius.
|
||||
-- @param node_name Name of node to make cylinder of.
|
||||
-- @param hollow Whether the cylinder should be hollow.
|
||||
-- @return The number of nodes added.
|
||||
function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, hollow)
|
||||
local other1, other2 = worldedit.get_axis_others(axis)
|
||||
|
||||
--adds a dome at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added
|
||||
worldedit.dome = function(pos, radius, nodename, env) --wip: use bresenham sphere for maximum speed
|
||||
local node = {name=nodename}
|
||||
local pos1 = {x=0, y=0, z=0}
|
||||
local max_radius = radius * (radius + 1)
|
||||
local count = 0
|
||||
if env == nil then env = minetest.env end
|
||||
for x = -radius, radius do
|
||||
pos1.x = pos.x + x
|
||||
for y = 0, radius do
|
||||
pos1.y = pos.y + y
|
||||
for z = -radius, radius do
|
||||
if x*x+y*y+z*z <= max_radius then
|
||||
pos1.z = pos.z + z
|
||||
env:add_node(pos1, node)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--adds a hollow cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`, returning the number of nodes added
|
||||
worldedit.hollow_cylinder = function(pos, axis, length, radius, nodename, env)
|
||||
local other1, other2
|
||||
if axis == "x" then
|
||||
other1, other2 = "y", "z"
|
||||
elseif axis == "y" then
|
||||
other1, other2 = "x", "z"
|
||||
else --axis == "z"
|
||||
other1, other2 = "x", "y"
|
||||
-- Backwards compatibility
|
||||
if type(radius2) == "string" then
|
||||
hollow = node_name
|
||||
node_name = radius2
|
||||
radius2 = radius1 -- straight cylinder
|
||||
end
|
||||
|
||||
if env == nil then env = minetest.env end
|
||||
local currentpos = {x=pos.x, y=pos.y, z=pos.z}
|
||||
local node = {name=nodename}
|
||||
local count = 0
|
||||
local step = 1
|
||||
-- Handle negative lengths
|
||||
local current_pos = vector.new(pos)
|
||||
if length < 0 then
|
||||
length = -length
|
||||
step = -1
|
||||
end
|
||||
for i = 1, length do
|
||||
local offset1, offset2 = 0, radius
|
||||
local delta = -radius
|
||||
while offset1 <= offset2 do
|
||||
--add node at each octant
|
||||
local first1, first2 = pos[other1] + offset1, pos[other1] - offset1
|
||||
local second1, second2 = pos[other2] + offset2, pos[other2] - offset2
|
||||
currentpos[other1], currentpos[other2] = first1, second1
|
||||
env:add_node(currentpos, node) --octant 1
|
||||
currentpos[other1] = first2
|
||||
env:add_node(currentpos, node) --octant 4
|
||||
currentpos[other2] = second2
|
||||
env:add_node(currentpos, node) --octant 5
|
||||
currentpos[other1] = first1
|
||||
env:add_node(currentpos, node) --octant 8
|
||||
local first1, first2 = pos[other1] + offset2, pos[other1] - offset2
|
||||
local second1, second2 = pos[other2] + offset1, pos[other2] - offset1
|
||||
currentpos[other1], currentpos[other2] = first1, second1
|
||||
env:add_node(currentpos, node) --octant 2
|
||||
currentpos[other1] = first2
|
||||
env:add_node(currentpos, node) --octant 3
|
||||
currentpos[other2] = second2
|
||||
env:add_node(currentpos, node) --octant 6
|
||||
currentpos[other1] = first1
|
||||
env:add_node(currentpos, node) --octant 7
|
||||
|
||||
count = count + 8 --wip: broken
|
||||
|
||||
--move to next location
|
||||
delta = delta + (offset1 * 2) + 1
|
||||
if delta >= 0 then
|
||||
offset2 = offset2 - 1
|
||||
delta = delta - (offset2 * 2)
|
||||
end
|
||||
offset1 = offset1 + 1
|
||||
end
|
||||
currentpos[axis] = currentpos[axis] + step
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--adds a cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`, returning the number of nodes added
|
||||
worldedit.cylinder = function(pos, axis, length, radius, nodename, env)
|
||||
local other1, other2
|
||||
if axis == "x" then
|
||||
other1, other2 = "y", "z"
|
||||
elseif axis == "y" then
|
||||
other1, other2 = "x", "z"
|
||||
else --axis == "z"
|
||||
other1, other2 = "x", "y"
|
||||
current_pos[axis] = current_pos[axis] - length
|
||||
radius1, radius2 = radius2, radius1
|
||||
end
|
||||
|
||||
if env == nil then env = minetest.env end
|
||||
local currentpos = {x=pos.x, y=pos.y, z=pos.z}
|
||||
local node = {name=nodename}
|
||||
-- Set up voxel manipulator
|
||||
local manip, area = mh.init_axis_radius_length(current_pos, axis, math.max(radius1, radius2), length)
|
||||
local data = mh.get_empty_data(area)
|
||||
|
||||
-- Add desired shape (anything inbetween cylinder & cone)
|
||||
local node_id = minetest.get_content_id(node_name)
|
||||
local stride = vector.new(1, area.ystride, area.zstride)
|
||||
local offset = vector.subtract(current_pos, area.MinEdge)
|
||||
local count = 0
|
||||
local step = 1
|
||||
if length < 0 then
|
||||
length = -length
|
||||
step = -1
|
||||
end
|
||||
for i = 1, length do
|
||||
local offset1, offset2 = 0, radius
|
||||
local delta = -radius
|
||||
while offset1 <= offset2 do
|
||||
--connect each pair of octants
|
||||
currentpos[other1] = pos[other1] - offset1
|
||||
local second1, second2 = pos[other2] + offset2, pos[other2] - offset2
|
||||
for i = 0, offset1 * 2 do
|
||||
currentpos[other2] = second1
|
||||
env:add_node(currentpos, node) --octant 1 to 4
|
||||
currentpos[other2] = second2
|
||||
env:add_node(currentpos, node) --octant 5 to 8
|
||||
currentpos[other1] = currentpos[other1] + 1
|
||||
end
|
||||
currentpos[other1] = pos[other1] - offset2
|
||||
local second1, second2 = pos[other2] + offset1, pos[other2] - offset1
|
||||
for i = 0, offset2 * 2 do
|
||||
currentpos[other2] = second1
|
||||
env:add_node(currentpos, node) --octant 2 to 3
|
||||
currentpos[other2] = second2
|
||||
env:add_node(currentpos, node) --octant 6 to 7
|
||||
currentpos[other1] = currentpos[other1] + 1
|
||||
end
|
||||
for i = 0, length - 1 do
|
||||
-- Calulate radius for this "height" in the cylinder
|
||||
local radius = radius1 + (radius2 - radius1) * (i + 1) / length
|
||||
radius = math.floor(radius + 0.5) -- round
|
||||
local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
|
||||
|
||||
count = count + (offset1 * 4) + (offset2 * 4) + 4 --wip: broken
|
||||
|
||||
--move to next location
|
||||
delta = delta + (offset1 * 2) + 1
|
||||
offset1 = offset1 + 1
|
||||
if delta >= 0 then
|
||||
offset2 = offset2 - 1
|
||||
delta = delta - (offset2 * 2)
|
||||
end
|
||||
end
|
||||
currentpos[axis] = currentpos[axis] + step
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--adds a pyramid at `pos` with height `height`, composed of `nodename`, returning the number of nodes added
|
||||
worldedit.pyramid = function(pos, height, nodename, env)
|
||||
local pos1x, pos1y, pos1z = pos.x - height, pos.y, pos.z - height
|
||||
local pos2x, pos2y, pos2z = pos.x + height, pos.y + height, pos.z + height
|
||||
local pos = {x=0, y=pos1y, z=0}
|
||||
|
||||
local count = 0
|
||||
local node = {name=nodename}
|
||||
if env == nil then env = minetest.env end
|
||||
while pos.y <= pos2y do --each vertical level of the pyramid
|
||||
pos.x = pos1x
|
||||
while pos.x <= pos2x do
|
||||
pos.z = pos1z
|
||||
while pos.z <= pos2z do
|
||||
env:add_node(pos, node)
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
count = count + ((pos2y - pos.y) * 2 + 1) ^ 2
|
||||
pos.y = pos.y + 1
|
||||
|
||||
pos1x, pos2x = pos1x + 1, pos2x - 1
|
||||
pos1z, pos2z = pos1z + 1, pos2z - 1
|
||||
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
--adds a spiral at `pos` with width `width`, height `height`, space between walls `spacer`, composed of `nodename`, returning the number of nodes added
|
||||
worldedit.spiral = function(pos, width, height, spacer, nodename, env) --wip: clean this up
|
||||
-- spiral matrix - http://rosettacode.org/wiki/Spiral_matrix#Lua
|
||||
av, sn = math.abs, function(s) return s~=0 and s/av(s) or 0 end
|
||||
local function sindex(z, x) -- returns the value at (x, z) in a spiral that starts at 1 and goes outwards
|
||||
if z == -x and z >= x then return (2*z+1)^2 end
|
||||
local l = math.max(av(z), av(x))
|
||||
return (2*l-1)^2+4*l+2*l*sn(x+z)+sn(z^2-x^2)*(l-(av(z)==l and sn(z)*x or sn(x)*z)) -- OH GOD WHAT
|
||||
end
|
||||
local function spiralt(side)
|
||||
local ret, id, start, stop = {}, 0, math.floor((-side+1)/2), math.floor((side-1)/2)
|
||||
for i = 1, side do
|
||||
for j = 1, side do
|
||||
local id = side^2 - sindex(stop - i + 1,start + j - 1)
|
||||
ret[id] = {x=i,z=j}
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
if env == nil then env = minetest.env end
|
||||
-- connect the joined parts
|
||||
local spiral = spiralt(width)
|
||||
height = tonumber(height)
|
||||
if height < 1 then height = 1 end
|
||||
spacer = tonumber(spacer)-1
|
||||
if spacer < 1 then spacer = 1 end
|
||||
local count = 0
|
||||
local node = {name=nodename}
|
||||
local np,lp
|
||||
for y=0,height do
|
||||
lp = nil
|
||||
for _,v in ipairs(spiral) do
|
||||
np = {x=pos.x+v.x*spacer, y=pos.y+y, z=pos.z+v.z*spacer}
|
||||
if lp~=nil then
|
||||
if lp.x~=np.x then
|
||||
if lp.x<np.x then
|
||||
for i=lp.x+1,np.x do
|
||||
env:add_node({x=i, y=np.y, z=np.z}, node)
|
||||
count = count + 1
|
||||
end
|
||||
else
|
||||
for i=np.x,lp.x-1 do
|
||||
env:add_node({x=i, y=np.y, z=np.z}, node)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if lp.z~=np.z then
|
||||
if lp.z<np.z then
|
||||
for i=lp.z+1,np.z do
|
||||
env:add_node({x=np.x, y=np.y, z=i}, node)
|
||||
count = count + 1
|
||||
end
|
||||
else
|
||||
for i=np.z,lp.z-1 do
|
||||
env:add_node({x=np.x, y=np.y, z=i}, node)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
for index2 = -radius, radius do
|
||||
-- Offset contributed by other axis 1 plus 1 to make it 1-indexed
|
||||
local new_index2 = (index2 + offset[other1]) * stride[other1] + 1
|
||||
for index3 = -radius, radius do
|
||||
local new_index3 = new_index2 + (index3 + offset[other2]) * stride[other2]
|
||||
local squared = index2 * index2 + index3 * index3
|
||||
if squared <= max_radius and (not hollow or squared >= min_radius) then
|
||||
-- Position is in cylinder, add node here
|
||||
local vi = new_index3 + (offset[axis] + i) * stride[axis]
|
||||
data[vi] = node_id
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
lp = np
|
||||
end
|
||||
end
|
||||
|
||||
mh.finish(manip, data)
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
|
||||
--- Adds a pyramid.
|
||||
-- @param pos Position to center base of pyramid at.
|
||||
-- @param axis Axis ("x", "y", or "z")
|
||||
-- @param height Pyramid height.
|
||||
-- @param node_name Name of node to make pyramid of.
|
||||
-- @param hollow Whether the pyramid should be hollow.
|
||||
-- @return The number of nodes added.
|
||||
function worldedit.pyramid(pos, axis, height, node_name, hollow)
|
||||
local other1, other2 = worldedit.get_axis_others(axis)
|
||||
|
||||
-- Set up voxel manipulator
|
||||
-- FIXME: passing negative <radius> causes mis-sorted pos to be passed
|
||||
-- into mh.init() which is technically not allowed but works
|
||||
local manip, area = mh.init_axis_radius(pos, axis, height)
|
||||
local data = mh.get_empty_data(area)
|
||||
|
||||
-- Handle inverted pyramids
|
||||
local step
|
||||
if height > 0 then
|
||||
height = height - 1
|
||||
step = 1
|
||||
else
|
||||
height = height + 1
|
||||
step = -1
|
||||
end
|
||||
|
||||
-- Add pyramid
|
||||
local node_id = minetest.get_content_id(node_name)
|
||||
local stride = vector.new(1, area.ystride, area.zstride)
|
||||
local offset = vector.subtract(pos, area.MinEdge)
|
||||
local size = math.abs(height * step)
|
||||
local count = 0
|
||||
-- For each level of the pyramid
|
||||
for index1 = 0, height, step do
|
||||
-- Offset contributed by axis plus 1 to make it 1-indexed
|
||||
local new_index1 = (index1 + offset[axis]) * stride[axis] + 1
|
||||
for index2 = -size, size do
|
||||
local new_index2 = new_index1 + (index2 + offset[other1]) * stride[other1]
|
||||
for index3 = -size, size do
|
||||
local i = new_index2 + (index3 + offset[other2]) * stride[other2]
|
||||
if (not hollow or size - math.abs(index2) < 2 or size - math.abs(index3) < 2) then
|
||||
data[i] = node_id
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
size = size - 1
|
||||
end
|
||||
|
||||
mh.finish(manip, data)
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
--- Adds a spiral.
|
||||
-- @param pos Position to center spiral at.
|
||||
-- @param length Spral length.
|
||||
-- @param height Spiral height.
|
||||
-- @param spacer Space between walls.
|
||||
-- @param node_name Name of node to make spiral of.
|
||||
-- @return Number of nodes added.
|
||||
-- TODO: Add axis option.
|
||||
function worldedit.spiral(pos, length, height, spacer, node_name)
|
||||
local extent = math.ceil(length / 2)
|
||||
|
||||
local manip, area = mh.init_axis_radius_length(pos, "y", extent, height)
|
||||
local data = mh.get_empty_data(area)
|
||||
|
||||
-- Set up variables
|
||||
local node_id = minetest.get_content_id(node_name)
|
||||
local stride = vector.new(1, area.ystride, area.zstride)
|
||||
local offset = vector.subtract(pos, area.MinEdge)
|
||||
local i = offset.z * stride.z + offset.y * stride.y + offset.x + 1
|
||||
|
||||
-- Add first column
|
||||
local count = height
|
||||
local column = i
|
||||
for y = 1, height do
|
||||
data[column] = node_id
|
||||
column = column + stride.y
|
||||
end
|
||||
|
||||
-- Add spiral segments
|
||||
local stride_axis, stride_other = stride.x, stride.z
|
||||
local sign = -1
|
||||
local segment_length = 0
|
||||
spacer = spacer + 1
|
||||
-- Go through each segment except the last
|
||||
for segment = 1, math.floor(length / spacer) * 2 do
|
||||
-- Change sign and length every other turn starting with the first
|
||||
if segment % 2 == 1 then
|
||||
sign = -sign
|
||||
segment_length = segment_length + spacer
|
||||
end
|
||||
-- Fill segment
|
||||
for index = 1, segment_length do
|
||||
-- Move along the direction of the segment
|
||||
i = i + stride_axis * sign
|
||||
local column = i
|
||||
-- Add column
|
||||
for y = 1, height do
|
||||
data[column] = node_id
|
||||
column = column + stride.y
|
||||
end
|
||||
end
|
||||
count = count + segment_length * height
|
||||
stride_axis, stride_other = stride_other, stride_axis -- Swap axes
|
||||
end
|
||||
|
||||
-- Add shorter final segment
|
||||
sign = -sign
|
||||
for index = 1, segment_length do
|
||||
i = i + stride_axis * sign
|
||||
local column = i
|
||||
-- Add column
|
||||
for y = 1, height do
|
||||
data[column] = node_id
|
||||
column = column + stride.y
|
||||
end
|
||||
end
|
||||
count = count + segment_length * height
|
||||
|
||||
mh.finish(manip, data)
|
||||
|
||||
return count
|
||||
end
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
worldedit = worldedit or {}
|
||||
|
||||
worldedit.queue = {}
|
||||
|
||||
worldedit.ENABLE_QUEUE = true
|
||||
worldedit.BLOCKS_PER_GLOBALSTEP = 512
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
i = 1
|
||||
while i <= #worldedit.queue and i <= worldedit.BLOCKS_PER_GLOBALSTEP do
|
||||
idx = (#worldedit.queue + 1) - i -- we use the last entry, so we don't spend days moving stuff in the table because we removed the first entry
|
||||
if worldedit.queue[idx].t == "set_node" then
|
||||
minetest.env:set_node(worldedit.queue[idx].pos, worldedit.queue[idx].node)
|
||||
elseif worldedit.queue[idx].t == "remove_node" then
|
||||
minetest.env:remove_node(worldedit.queue[idx].pos)
|
||||
elseif worldedit.queue[idx].t == "place_node" then
|
||||
minetest.env:place_node(worldedit.queue[idx].pos, worldedit.queue[idx].node)
|
||||
elseif worldedit.queue[idx].t == "dig_node" then
|
||||
minetest.env:dig_node(worldedit.queue[idx].pos)
|
||||
elseif worldedit.queue[idx].t == "add_entity" then
|
||||
minetest.env:add_entity(worldedit.queue[idx].pos, worldedit.queue[idx].name)
|
||||
elseif worldedit.queue[idx].t == "add_item" then
|
||||
minetest.env:add_item(worldedit.queue[idx].pos, worldedit.queue[idx].item)
|
||||
elseif worldedit.queue[idx].t == "meta_from_table" then
|
||||
minetest.env:get_meta(worldedit.queue[idx].pos):from_table(worldedit.queue[idx].table)
|
||||
else
|
||||
print("Unknown queue event type: " .. worldedit.queue[idx].t)
|
||||
end
|
||||
table.remove(worldedit.queue, idx)
|
||||
i = i + 1
|
||||
end
|
||||
end)
|
||||
|
||||
function table.copy(t, seen)
|
||||
seen = seen or {}
|
||||
if t == nil then return nil end
|
||||
if seen[t] then return seen[t] end
|
||||
|
||||
local nt = {}
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == 'table' then
|
||||
nt[k] = table.copy(v, seen)
|
||||
else
|
||||
nt[k] = v
|
||||
end
|
||||
end
|
||||
seen[t] = nt
|
||||
return nt
|
||||
end
|
||||
|
||||
local quene_setnode = function(self, pos_, node_)
|
||||
table.insert(worldedit.queue, {pos=table.copy(pos_), node=table.copy(node_), t="set_node"})
|
||||
end
|
||||
|
||||
local quene_removenode = function(self, pos_)
|
||||
table.insert(worldedit.queue, {pos=table.copy(pos_), t="remove_node"})
|
||||
end
|
||||
|
||||
local quene_placenode = function(self, pos_, node_)
|
||||
table.insert(worldedit.queue, {pos=table.copy(pos_), node=table.copy(node_), t="place_node"})
|
||||
end
|
||||
|
||||
local quene_dignode = function(self, pos_)
|
||||
table.insert(worldedit.queue, {pos=table.copy(pos_), t="dig_node"})
|
||||
end
|
||||
|
||||
local quene_addentity = function(self, pos_, name_)
|
||||
table.insert(worldedit.queue, {pos=table.copy(pos_), name=name_.."", t="add_entity"})
|
||||
end
|
||||
|
||||
local quene_additem = function(self, pos_, item_)
|
||||
table.insert(worldedit.queue, {pos=table.copy(pos_), item=item_.."", t="add_item"})
|
||||
end
|
||||
|
||||
local quene_setmeta = function(self, pos_, table_)
|
||||
table.insert(worldedit.queue, {pos=table.copy(pos_), table=table.copy(table_), t="meta_from_table"})
|
||||
end
|
||||
|
||||
local aliasmeta = {
|
||||
-- the other functions are left out because they are not used in worldedit
|
||||
to_table = function(self) return minetest.env:get_meta(self._pos):to_table() end,
|
||||
set_string = function(self, name_, value_) minetest.env:get_meta(self._pos):set_string(name_, value_) end,
|
||||
from_table = function(self, tbl) quene_setmeta(nil, self._pos, tbl) end,
|
||||
}
|
||||
|
||||
local get_meta_alias = function(self, pos)
|
||||
local am = table.copy(aliasmeta)
|
||||
am._pos = pos
|
||||
return am
|
||||
end
|
||||
|
||||
worldedit.quene_aliasenv = {
|
||||
-- ALL functions that are not just piped to the real minetest.env function must copy the arguments, not just reference them
|
||||
set_node = quene_setnode,
|
||||
add_node = quene_setnode,
|
||||
remove_node = quene_removenode,
|
||||
get_node = function(self, pos) return minetest.env:get_node(pos) end,
|
||||
get_node_or_nil = function(self, pos) return minetest.env:get_node_or_nil(pos) end,
|
||||
get_node_light = function(self, pos, timeofday) return minetest.env:get_node_light(pos, timeofday) end,
|
||||
place_node = quene_placenode,
|
||||
dig_node = quene_dignode,
|
||||
punch_node = function(self, pos) return minetest.env:punch_node(pos) end,
|
||||
get_meta = get_meta_alias,
|
||||
get_node_timer = function(self, pos) return minetest.env:get_node_timer(pos) end,
|
||||
add_entity = quene_addentity,
|
||||
add_item = quene_additem,
|
||||
}
|
||||
|
|
@ -1,57 +1,95 @@
|
|||
worldedit = worldedit or {}
|
||||
--- Schematic serialization and deserialiation.
|
||||
-- @module worldedit.serialization
|
||||
|
||||
--modifies positions `pos1` and `pos2` so that each component of `pos1` is less than or equal to its corresponding conent of `pos2`, returning two new positions
|
||||
worldedit.sort_pos = function(pos1, pos2)
|
||||
pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}
|
||||
pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
|
||||
if pos1.x > pos2.x then
|
||||
pos2.x, pos1.x = pos1.x, pos2.x
|
||||
end
|
||||
if pos1.y > pos2.y then
|
||||
pos2.y, pos1.y = pos1.y, pos2.y
|
||||
end
|
||||
if pos1.z > pos2.z then
|
||||
pos2.z, pos1.z = pos1.z, pos2.z
|
||||
end
|
||||
return pos1, pos2
|
||||
end
|
||||
worldedit.LATEST_SERIALIZATION_VERSION = 5
|
||||
local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":"
|
||||
|
||||
--determines the version of serialized data `value`, returning the version as a positive integer or 0 for unknown versions
|
||||
worldedit.valueversion = function(value)
|
||||
if value:find("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)") and not value:find("%{") then --previous list format
|
||||
return 3
|
||||
|
||||
--[[
|
||||
Serialization version history:
|
||||
1: Original format. Serialized Lua table with a weird linked format...
|
||||
2: Position and node seperated into sub-tables in fields `1` and `2`.
|
||||
3: List of nodes, one per line, with fields seperated by spaces.
|
||||
Format: <X> <Y> <Z> <Name> <Param1> <Param2>
|
||||
4: Serialized Lua table containing a list of nodes with `x`, `y`, `z`,
|
||||
`name`, `param1`, `param2`, and `meta` fields.
|
||||
5: Added header and made `param1`, `param2`, and `meta` fields optional.
|
||||
Header format: <Version>,<ExtraHeaderField1>,...:<Content>
|
||||
--]]
|
||||
|
||||
|
||||
--- Reads the header of serialized data.
|
||||
-- @param value Serialized WorldEdit data.
|
||||
-- @return The version as a positive natural number, or 0 for unknown versions.
|
||||
-- @return Extra header fields as a list of strings, or nil if not supported.
|
||||
-- @return Content (data after header).
|
||||
function worldedit.read_header(value)
|
||||
if value:find("^[0-9]+[,:]") then
|
||||
local header_end = value:find(":", 1, true)
|
||||
local header = value:sub(1, header_end - 1):split(",")
|
||||
local version = tonumber(header[1])
|
||||
table.remove(header, 1)
|
||||
local content = value:sub(header_end + 1)
|
||||
return version, header, content
|
||||
end
|
||||
-- Old versions that didn't include a header with a version number
|
||||
if value:find("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)") and not value:find("%{") then -- List format
|
||||
return 3, nil, value
|
||||
elseif value:find("^[^\"']+%{%d+%}") then
|
||||
if value:find("%[\"meta\"%]") then --previous meta flat table format
|
||||
return 2
|
||||
if value:find("%[\"meta\"%]") then -- Meta flat table format
|
||||
return 2, nil, value
|
||||
end
|
||||
return 1 --original flat table format
|
||||
elseif value:find("%{") then --current nested table format
|
||||
return 4
|
||||
return 1, nil, value -- Flat table format
|
||||
elseif value:find("%{") then -- Raw nested table format
|
||||
return 4, nil, value
|
||||
end
|
||||
return 0 --unknown format
|
||||
return nil
|
||||
end
|
||||
|
||||
--converts the region defined by positions `pos1` and `pos2` into a single string, returning the serialized data and the number of nodes serialized
|
||||
worldedit.serialize = function(pos1, pos2) --wip: check for ItemStacks and whether they can be serialized
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
|
||||
--- Converts the region defined by positions `pos1` and `pos2`
|
||||
-- into a single string.
|
||||
-- @return The serialized data.
|
||||
-- @return The number of nodes serialized.
|
||||
function worldedit.serialize(pos1, pos2)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local get_node, get_meta, hash_node_position =
|
||||
minetest.get_node, minetest.get_meta, minetest.hash_node_position
|
||||
|
||||
-- Find the positions which have metadata
|
||||
local has_meta = {}
|
||||
local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)
|
||||
for i = 1, #meta_positions do
|
||||
has_meta[hash_node_position(meta_positions[i])] = true
|
||||
end
|
||||
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
local count = 0
|
||||
local result = {}
|
||||
local env = minetest.env
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node = env:get_node(pos)
|
||||
local node = get_node(pos)
|
||||
if node.name ~= "air" and node.name ~= "ignore" then
|
||||
count = count + 1
|
||||
local meta = env:get_meta(pos):to_table()
|
||||
|
||||
--convert metadata itemstacks to itemstrings
|
||||
for name, inventory in pairs(meta.inventory) do
|
||||
for index, stack in ipairs(inventory) do
|
||||
inventory[index] = stack:to_string()
|
||||
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
|
||||
|
||||
|
@ -60,8 +98,8 @@ worldedit.serialize = function(pos1, pos2) --wip: check for ItemStacks and wheth
|
|||
y = pos.y - pos1.y,
|
||||
z = pos.z - pos1.z,
|
||||
name = node.name,
|
||||
param1 = node.param1,
|
||||
param2 = node.param2,
|
||||
param1 = node.param1 ~= 0 and node.param1 or nil,
|
||||
param2 = node.param2 ~= 0 and node.param2 or nil,
|
||||
meta = meta,
|
||||
}
|
||||
end
|
||||
|
@ -71,172 +109,159 @@ worldedit.serialize = function(pos1, pos2) --wip: check for ItemStacks and wheth
|
|||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
result = minetest.serialize(result) --convert entries to a string
|
||||
return result, count
|
||||
-- Serialize entries
|
||||
result = minetest.serialize(result)
|
||||
return LATEST_SERIALIZATION_HEADER .. result, count
|
||||
end
|
||||
|
||||
--determines the volume the nodes represented by string `value` would occupy if deserialized at `originpos`, returning the two corner positions and the number of nodes
|
||||
--contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) by ChillCode, available under the MIT license (GPL compatible)
|
||||
worldedit.allocate = function(originpos, value)
|
||||
local huge = math.huge
|
||||
local pos1x, pos1y, pos1z = huge, huge, huge
|
||||
local pos2x, pos2y, pos2z = -huge, -huge, -huge
|
||||
local originx, originy, originz = originpos.x, originpos.y, originpos.z
|
||||
local count = 0
|
||||
local version = worldedit.valueversion(value)
|
||||
if version == 1 or version == 2 then --flat table format
|
||||
--obtain the node table
|
||||
local get_tables = loadstring(value)
|
||||
if get_tables then --error loading value
|
||||
return originpos, originpos, count
|
||||
end
|
||||
local tables = get_tables()
|
||||
|
||||
--transform the node table into an array of nodes
|
||||
for i = 1, #tables do
|
||||
for j, v in pairs(tables[i]) do
|
||||
if type(v) == "table" then
|
||||
tables[i][j] = tables[v[1]]
|
||||
end
|
||||
end
|
||||
end
|
||||
local nodes = tables[1]
|
||||
|
||||
--check the node array
|
||||
count = #nodes
|
||||
if version == 1 then --original flat table format
|
||||
for index = 1, count do
|
||||
local entry = nodes[index]
|
||||
local pos = entry[1]
|
||||
local x, y, z = originx - pos.x, originy - pos.y, originz - pos.z
|
||||
if x < pos1x then pos1x = x end
|
||||
if y < pos1y then pos1y = y end
|
||||
if z < pos1z then pos1z = z end
|
||||
if x > pos2x then pos2x = x end
|
||||
if y > pos2y then pos2y = y end
|
||||
if z > pos2z then pos2z = z end
|
||||
end
|
||||
else --previous meta flat table format
|
||||
for index = 1, count do
|
||||
local entry = nodes[index]
|
||||
local x, y, z = originx - entry.x, originy - entry.y, originz - entry.z
|
||||
if x < pos1x then pos1x = x end
|
||||
if y < pos1y then pos1y = y end
|
||||
if z < pos1z then pos1z = z end
|
||||
if x > pos2x then pos2x = x end
|
||||
if y > pos2y then pos2y = y end
|
||||
if z > pos2z then pos2z = z end
|
||||
end
|
||||
end
|
||||
elseif version == 3 then --previous list format
|
||||
for x, y, z, name, param1, param2 in value:gmatch("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do --match node entries
|
||||
x, y, z = originx + tonumber(x), originy + tonumber(y), originz + tonumber(z)
|
||||
if x < pos1x then pos1x = x end
|
||||
if y < pos1y then pos1y = y end
|
||||
if z < pos1z then pos1z = z end
|
||||
if x > pos2x then pos2x = x end
|
||||
if y > pos2y then pos2y = y end
|
||||
if z > pos2z then pos2z = z end
|
||||
count = count + 1
|
||||
end
|
||||
elseif version == 4 then --current nested table format
|
||||
local nodes = minetest.deserialize(value)
|
||||
count = #nodes
|
||||
for index = 1, count do
|
||||
local entry = nodes[index]
|
||||
x, y, z = originx + entry.x, originy + entry.y, originz + entry.z
|
||||
if x < pos1x then pos1x = x end
|
||||
if y < pos1y then pos1y = y end
|
||||
if z < pos1z then pos1z = z end
|
||||
if x > pos2x then pos2x = x end
|
||||
if y > pos2y then pos2y = y end
|
||||
if z > pos2z then pos2z = z end
|
||||
end
|
||||
end
|
||||
local pos1 = {x=pos1x, y=pos1y, z=pos1z}
|
||||
local pos2 = {x=pos2x, y=pos2y, z=pos2z}
|
||||
return pos1, pos2, count
|
||||
end
|
||||
|
||||
--loads the nodes represented by string `value` at position `originpos`, returning the number of nodes deserialized
|
||||
--contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) by ChillCode, available under the MIT license (GPL compatible)
|
||||
worldedit.deserialize = function(originpos, value, env)
|
||||
local originx, originy, originz = originpos.x, originpos.y, originpos.z
|
||||
local count = 0
|
||||
if env == nil then env = minetest.env end
|
||||
local version = worldedit.valueversion(value)
|
||||
if version == 1 or version == 2 then --original flat table format
|
||||
--obtain the node table
|
||||
local get_tables = loadstring(value)
|
||||
if not get_tables then --error loading value
|
||||
return count
|
||||
end
|
||||
local tables = get_tables()
|
||||
|
||||
--transform the node table into an array of nodes
|
||||
for i = 1, #tables do
|
||||
for j, v in pairs(tables[i]) do
|
||||
if type(v) == "table" then
|
||||
tables[i][j] = tables[v[1]]
|
||||
end
|
||||
end
|
||||
end
|
||||
local nodes = tables[1]
|
||||
|
||||
--load the node array
|
||||
count = #nodes
|
||||
if version == 1 then --original flat table format
|
||||
for index = 1, count do
|
||||
local entry = nodes[index]
|
||||
local pos = entry[1]
|
||||
pos.x, pos.y, pos.z = originx - pos.x, originy - pos.y, originz - pos.z
|
||||
env:add_node(pos, entry[2])
|
||||
end
|
||||
else --previous meta flat table format
|
||||
for index = 1, #nodes do
|
||||
local entry = nodes[index]
|
||||
entry.x, entry.y, entry.z = originx + entry.x, originy + entry.y, originz + entry.z
|
||||
env:add_node(entry, entry) --entry acts both as position and as node
|
||||
env:get_meta(entry):from_table(entry.meta)
|
||||
end
|
||||
end
|
||||
elseif version == 3 then --previous list format
|
||||
local pos = {x=0, y=0, z=0}
|
||||
local node = {name="", param1=0, param2=0}
|
||||
for x, y, z, name, param1, param2 in value:gmatch("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do --match node entries
|
||||
pos.x = originx + tonumber(x)
|
||||
pos.y = originy + tonumber(y)
|
||||
pos.z = originz + tonumber(z)
|
||||
node.name = name
|
||||
node.param1 = param1
|
||||
node.param2 = param2
|
||||
env:add_node(pos, node)
|
||||
count = count + 1
|
||||
end
|
||||
elseif version == 4 then --current nested table format
|
||||
--wip: this is a filthy hack that works surprisingly well
|
||||
value = value:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1)
|
||||
local escaped = value:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]+\")", function(s) return string.rep("@", #s) end)
|
||||
local startpos, startpos1, endpos = 1, 1
|
||||
local nodes = {}
|
||||
while true do
|
||||
startpos, endpos = escaped:find("},%s*{", startpos)
|
||||
local function deserialize_workaround(content)
|
||||
local nodes, err
|
||||
if not minetest.global_exists("jit") then
|
||||
nodes, err = minetest.deserialize(content, true)
|
||||
elseif not content:match("^%s*return%s*{") then
|
||||
-- The data doesn't look like we expect it to so we can't apply the workaround.
|
||||
-- hope for the best
|
||||
minetest.log("warning", "WorldEdit: deserializing data but can't apply LuaJIT workaround")
|
||||
nodes, err = minetest.deserialize(content, true)
|
||||
else
|
||||
-- XXX: This is a filthy hack that works surprisingly well
|
||||
-- in LuaJIT, `minetest.deserialize` will fail due to the register limit
|
||||
nodes = {}
|
||||
content = content:gsub("^%s*return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data
|
||||
-- remove string contents strings while preserving their length
|
||||
local escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)
|
||||
local startpos, startpos1 = 1, 1
|
||||
local endpos
|
||||
local entry
|
||||
while true do -- go through each individual node entry (except the last)
|
||||
startpos, endpos = escaped:find("}%s*,%s*{", startpos)
|
||||
if not startpos then
|
||||
break
|
||||
end
|
||||
local current = value:sub(startpos1, startpos)
|
||||
table.insert(nodes, minetest.deserialize("return " .. current))
|
||||
local current = content:sub(startpos1, startpos)
|
||||
entry, err = minetest.deserialize("return " .. current, true)
|
||||
if not entry then
|
||||
break
|
||||
end
|
||||
table.insert(nodes, entry)
|
||||
startpos, startpos1 = endpos, endpos
|
||||
end
|
||||
|
||||
--local nodes = minetest.deserialize(value) --wip: this is broken for larger tables in the current version of LuaJIT
|
||||
count = #nodes
|
||||
for index = 1, count do
|
||||
local entry = nodes[index]
|
||||
entry.x, entry.y, entry.z = originx + entry.x, originy + entry.y, originz + entry.z
|
||||
env:add_node(entry, entry) --entry acts both as position and as node
|
||||
env:get_meta(entry):from_table(entry.meta)
|
||||
if not err then
|
||||
entry = minetest.deserialize("return " .. content:sub(startpos1), true) -- process the last entry
|
||||
table.insert(nodes, entry)
|
||||
end
|
||||
end
|
||||
return count
|
||||
if err then
|
||||
minetest.log("warning", "WorldEdit: deserialize: " .. err)
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
--- Loads the schematic in `value` into a node list in the latest format.
|
||||
-- @return A node list in the latest format, or nil on failure.
|
||||
local function load_schematic(value)
|
||||
local version, _, content = worldedit.read_header(value)
|
||||
local nodes = {}
|
||||
if version == 1 or version == 2 then -- Original flat table format
|
||||
local tables = minetest.deserialize(content, true)
|
||||
if not tables then return nil end
|
||||
|
||||
-- Transform the node table into an array of nodes
|
||||
for i = 1, #tables do
|
||||
for j, v in pairs(tables[i]) do
|
||||
if type(v) == "table" then
|
||||
tables[i][j] = tables[v[1]]
|
||||
end
|
||||
end
|
||||
end
|
||||
nodes = tables[1]
|
||||
|
||||
if version == 1 then --original flat table format
|
||||
for i, entry in ipairs(nodes) do
|
||||
local pos = entry[1]
|
||||
entry.x, entry.y, entry.z = pos.x, pos.y, pos.z
|
||||
entry[1] = nil
|
||||
local node = entry[2]
|
||||
entry.name, entry.param1, entry.param2 = node.name, node.param1, node.param2
|
||||
entry[2] = nil
|
||||
end
|
||||
end
|
||||
elseif version == 3 then -- List format
|
||||
for x, y, z, name, param1, param2 in content:gmatch(
|
||||
"([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+" ..
|
||||
"([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do
|
||||
param1, param2 = tonumber(param1), tonumber(param2)
|
||||
table.insert(nodes, {
|
||||
x = tonumber(x),
|
||||
y = tonumber(y),
|
||||
z = tonumber(z),
|
||||
name = name,
|
||||
param1 = param1 ~= 0 and param1 or nil,
|
||||
param2 = param2 ~= 0 and param2 or nil,
|
||||
})
|
||||
end
|
||||
elseif version == 4 or version == 5 then -- Nested table format
|
||||
nodes = deserialize_workaround(content)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
--- Determines the volume the nodes represented by string `value` would occupy
|
||||
-- if deserialized at `origin_pos`.
|
||||
-- @return Low corner position.
|
||||
-- @return High corner position.
|
||||
-- @return The number of nodes.
|
||||
function worldedit.allocate(origin_pos, value)
|
||||
local nodes = load_schematic(value)
|
||||
if not nodes or #nodes == 0 then return nil end
|
||||
return worldedit.allocate_with_nodes(origin_pos, nodes)
|
||||
end
|
||||
|
||||
|
||||
-- Internal
|
||||
function worldedit.allocate_with_nodes(origin_pos, nodes)
|
||||
local huge = math.huge
|
||||
local pos1x, pos1y, pos1z = huge, huge, huge
|
||||
local pos2x, pos2y, pos2z = -huge, -huge, -huge
|
||||
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
|
||||
for i, entry in ipairs(nodes) do
|
||||
local x, y, z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
|
||||
if x < pos1x then pos1x = x end
|
||||
if y < pos1y then pos1y = y end
|
||||
if z < pos1z then pos1z = z end
|
||||
if x > pos2x then pos2x = x end
|
||||
if y > pos2y then pos2y = y end
|
||||
if z > pos2z then pos2z = z end
|
||||
end
|
||||
return vector.new(pos1x, pos1y, pos1z), vector.new(pos2x, pos2y, pos2z), #nodes
|
||||
end
|
||||
|
||||
|
||||
--- Loads the nodes represented by string `value` at position `origin_pos`.
|
||||
-- @return The number of nodes deserialized.
|
||||
function worldedit.deserialize(origin_pos, value)
|
||||
local nodes = load_schematic(value)
|
||||
if not nodes then return nil end
|
||||
if #nodes == 0 then return #nodes end
|
||||
|
||||
local pos1, pos2 = worldedit.allocate_with_nodes(origin_pos, nodes)
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
|
||||
local add_node, get_meta = minetest.add_node, minetest.get_meta
|
||||
local registered_nodes = minetest.registered_nodes
|
||||
for i, entry in ipairs(nodes) do
|
||||
if registered_nodes[entry.name] then
|
||||
entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z
|
||||
-- Entry acts as both position and node
|
||||
add_node(entry, entry)
|
||||
if entry.meta then
|
||||
get_meta(entry):from_table(entry.meta)
|
||||
end
|
||||
end
|
||||
end
|
||||
return #nodes
|
||||
end
|
||||
|
||||
|
|
382
worldedit/test/init.lua
Normal file
|
@ -0,0 +1,382 @@
|
|||
-- TODO: don't shit individual variables into the globals
|
||||
|
||||
---------------------
|
||||
-- Helpers
|
||||
---------------------
|
||||
local vec = vector.new
|
||||
local vecw = function(axis, n, base)
|
||||
local ret = vec(base)
|
||||
ret[axis] = n
|
||||
return ret
|
||||
end
|
||||
local pos2str = minetest.pos_to_string
|
||||
local get_node = minetest.get_node
|
||||
local set_node = minetest.set_node
|
||||
|
||||
---------------------
|
||||
-- Nodes
|
||||
---------------------
|
||||
local air = "air"
|
||||
rawset(_G, "testnode1", "")
|
||||
rawset(_G, "testnode2", "")
|
||||
rawset(_G, "testnode3", "")
|
||||
-- Loads nodenames to use for tests
|
||||
local function init_nodes()
|
||||
testnode1 = minetest.registered_aliases["mapgen_stone"]
|
||||
testnode2 = minetest.registered_aliases["mapgen_dirt"]
|
||||
testnode3 = minetest.registered_aliases["mapgen_cobble"] or minetest.registered_aliases["mapgen_dirt_with_grass"]
|
||||
assert(testnode1 and testnode2 and testnode3)
|
||||
end
|
||||
-- Writes repeating pattern into given area
|
||||
rawset(_G, "place_pattern", function(pos1, pos2, pattern)
|
||||
local pos = vec()
|
||||
local node = {name=""}
|
||||
local i = 1
|
||||
for z = pos1.z, pos2.z do
|
||||
pos.z = z
|
||||
for y = pos1.y, pos2.y do
|
||||
pos.y = y
|
||||
for x = pos1.x, pos2.x do
|
||||
pos.x = x
|
||||
node.name = pattern[i]
|
||||
set_node(pos, node)
|
||||
i = i % #pattern + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
---------------------
|
||||
-- Area management
|
||||
---------------------
|
||||
assert(minetest.get_mapgen_setting("mg_name") == "singlenode")
|
||||
rawset(_G, "area", {})
|
||||
do
|
||||
local areamin, areamax
|
||||
local off
|
||||
local c_air = minetest.get_content_id(air)
|
||||
local vbuffer = {}
|
||||
-- Assign a new area for use, will emerge and then call ready()
|
||||
area.assign = function(min, max, ready)
|
||||
areamin = min
|
||||
areamax = max
|
||||
minetest.emerge_area(min, max, function(bpos, action, remaining)
|
||||
assert(action ~= minetest.EMERGE_ERRORED)
|
||||
if remaining > 0 then return end
|
||||
minetest.after(0, function()
|
||||
area.clear()
|
||||
ready()
|
||||
end)
|
||||
end)
|
||||
end
|
||||
-- Reset area contents and state
|
||||
area.clear = function()
|
||||
if off and vector.equals(off, vec(0, 0, 0)) then
|
||||
return
|
||||
end
|
||||
local vmanip = minetest.get_voxel_manip(areamin, areamax)
|
||||
local vpos1, vpos2 = vmanip:get_emerged_area()
|
||||
local vcount = (vpos2.x - vpos1.x + 1) * (vpos2.y - vpos1.y + 1) * (vpos2.z - vpos1.z + 1)
|
||||
if #vbuffer ~= vcount then
|
||||
vbuffer = {}
|
||||
for i = 1, vcount do
|
||||
vbuffer[i] = c_air
|
||||
end
|
||||
end
|
||||
vmanip:set_data(vbuffer)
|
||||
vmanip:write_to_map()
|
||||
off = vec(0, 0, 0)
|
||||
end
|
||||
-- Returns an usable area [pos1, pos2] that does not overlap previous ones
|
||||
area.get = function(sizex, sizey, sizez)
|
||||
local size
|
||||
if sizey == nil and sizez == nil then
|
||||
size = vec(sizex, sizex, sizex)
|
||||
else
|
||||
size = vec(sizex, sizey, sizez)
|
||||
end
|
||||
local pos1 = vector.add(areamin, off)
|
||||
local pos2 = vector.subtract(vector.add(pos1, size), 1)
|
||||
if pos2.x > areamax.x or pos2.y > areamax.y or pos2.z > areamax.z then
|
||||
error("Internal failure: out of space")
|
||||
end
|
||||
off = vector.add(off, size)
|
||||
return pos1, pos2
|
||||
end
|
||||
-- Returns an axis and count (= n) relative to the last-requested area that is unoccupied
|
||||
area.dir = function(n)
|
||||
local pos1 = vector.add(areamin, off)
|
||||
if pos1.x + n <= areamax.x then
|
||||
off.x = off.x + n
|
||||
return "x", n
|
||||
elseif pos1.x + n <= areamax.y then
|
||||
off.y = off.y + n
|
||||
return "y", n
|
||||
elseif pos1.z + n <= areamax.z then
|
||||
off.z = off.z + n
|
||||
return "z", n
|
||||
end
|
||||
error("Internal failure: out of space")
|
||||
end
|
||||
-- Returns [XYZ] margin (list of pos pairs) of n around last-requested area
|
||||
-- (may actually be larger but doesn't matter)
|
||||
area.margin = function(n)
|
||||
local pos1, pos2 = area.get(n)
|
||||
return {
|
||||
{ vec(areamin.x, areamin.y, pos1.z), pos2 }, -- X/Y
|
||||
{ vec(areamin.x, pos1.y, areamin.z), pos2 }, -- X/Z
|
||||
{ vec(pos1.x, areamin.y, areamin.z), pos2 }, -- Y/Z
|
||||
}
|
||||
end
|
||||
end
|
||||
-- Split an existing area into two non-overlapping [pos1, half1], [half2, pos2] parts; returns half1, half2
|
||||
area.split = function(pos1, pos2)
|
||||
local axis
|
||||
if pos2.x - pos1.x >= 1 then
|
||||
axis = "x"
|
||||
elseif pos2.y - pos1.y >= 1 then
|
||||
axis = "y"
|
||||
elseif pos2.z - pos1.z >= 1 then
|
||||
axis = "z"
|
||||
else
|
||||
error("Internal failure: area too small to split")
|
||||
end
|
||||
local hspan = math.floor((pos2[axis] - pos1[axis] + 1) / 2)
|
||||
local half1 = vecw(axis, pos1[axis] + hspan - 1, pos2)
|
||||
local half2 = vecw(axis, pos1[axis] + hspan, pos2)
|
||||
return half1, half2
|
||||
end
|
||||
|
||||
|
||||
---------------------
|
||||
-- Checks
|
||||
---------------------
|
||||
rawset(_G, "check", {})
|
||||
-- Check that all nodes in [pos1, pos2] are the node(s) specified
|
||||
check.filled = function(pos1, pos2, nodes)
|
||||
if type(nodes) == "string" then
|
||||
nodes = { nodes }
|
||||
end
|
||||
local _, counts = minetest.find_nodes_in_area(pos1, pos2, nodes)
|
||||
local total = worldedit.volume(pos1, pos2)
|
||||
local sum = 0
|
||||
for _, n in pairs(counts) do
|
||||
sum = sum + n
|
||||
end
|
||||
if sum ~= total then
|
||||
error((total - sum) .. " " .. table.concat(nodes, ",") .. " nodes missing in " ..
|
||||
pos2str(pos1) .. " -> " .. pos2str(pos2))
|
||||
end
|
||||
end
|
||||
-- Check that none of the nodes in [pos1, pos2] are the node(s) specified
|
||||
check.not_filled = function(pos1, pos2, nodes)
|
||||
if type(nodes) == "string" then
|
||||
nodes = { nodes }
|
||||
end
|
||||
local _, counts = minetest.find_nodes_in_area(pos1, pos2, nodes)
|
||||
for nodename, n in pairs(counts) do
|
||||
if n ~= 0 then
|
||||
error(counts[nodename] .. " " .. nodename .. " nodes found in " ..
|
||||
pos2str(pos1) .. " -> " .. pos2str(pos2))
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Check that all of the areas are only made of node(s) specified
|
||||
check.filled2 = function(list, nodes)
|
||||
for _, pos in ipairs(list) do
|
||||
check.filled(pos[1], pos[2], nodes)
|
||||
end
|
||||
end
|
||||
-- Check that none of the areas contain the node(s) specified
|
||||
check.not_filled2 = function(list, nodes)
|
||||
for _, pos in ipairs(list) do
|
||||
check.not_filled(pos[1], pos[2], nodes)
|
||||
end
|
||||
end
|
||||
-- Checks presence of a repeating pattern in [pos1, po2] (cf. place_pattern)
|
||||
check.pattern = function(pos1, pos2, pattern)
|
||||
local pos = vec()
|
||||
local i = 1
|
||||
for z = pos1.z, pos2.z do
|
||||
pos.z = z
|
||||
for y = pos1.y, pos2.y do
|
||||
pos.y = y
|
||||
for x = pos1.x, pos2.x do
|
||||
pos.x = x
|
||||
local node = get_node(pos)
|
||||
if node.name ~= pattern[i] then
|
||||
error(pattern[i] .. " not found at " .. pos2str(pos) .. " (i=" .. i .. ")")
|
||||
end
|
||||
i = i % #pattern + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---------------------
|
||||
-- The actual tests
|
||||
---------------------
|
||||
local tests = {}
|
||||
worldedit.register_test = function(name, func, opts)
|
||||
assert(type(name) == "string")
|
||||
assert(func == nil or type(func) == "function")
|
||||
if not opts then
|
||||
opts = {}
|
||||
else
|
||||
opts = table.copy(opts)
|
||||
end
|
||||
opts.name = name
|
||||
opts.func = func
|
||||
table.insert(tests, opts)
|
||||
end
|
||||
local register_test = worldedit.register_test
|
||||
-- How this works:
|
||||
-- register_test registers a test with a name and function
|
||||
-- The function should return if the test passes or otherwise cause a Lua error
|
||||
-- The basic structure is: get areas + do operations + check results
|
||||
-- Helpers:
|
||||
-- area.get must be used to retrieve areas that can be operated on (these will be cleared before each test)
|
||||
-- check.filled / check.not_filled can be used to check the result
|
||||
-- area.margin + check.filled2 is useful to make sure nodes weren't placed too far
|
||||
-- place_pattern + check.pattern is useful to test ops that operate on existing data
|
||||
|
||||
|
||||
register_test("Internal self-test")
|
||||
register_test("is area loaded?", function()
|
||||
local pos1, _ = area.get(1)
|
||||
assert(get_node(pos1).name == air)
|
||||
end)
|
||||
|
||||
register_test("area.split", function()
|
||||
for i = 2, 6 do
|
||||
local pos1, pos2 = area.get(1, 1, i)
|
||||
local half1, half2 = area.split(pos1, pos2)
|
||||
assert(pos1.x == half1.x and pos1.y == half1.y)
|
||||
assert(half1.x == half2.x and half1.y == half2.y)
|
||||
assert(half1.z + 1 == half2.z)
|
||||
if i % 2 == 0 then
|
||||
assert((half1.z - pos1.z) == (pos2.z - half2.z)) -- divided equally
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
register_test("check.filled", function()
|
||||
local pos1, pos2 = area.get(1, 2, 1)
|
||||
set_node(pos1, {name=testnode1})
|
||||
set_node(pos2, {name=testnode2})
|
||||
check.filled(pos1, pos1, testnode1)
|
||||
check.filled(pos1, pos2, {testnode1, testnode2})
|
||||
check.not_filled(pos1, pos1, air)
|
||||
check.not_filled(pos1, pos2, {air, testnode3})
|
||||
end)
|
||||
|
||||
register_test("pattern", function()
|
||||
local pos1, pos2 = area.get(3, 2, 1)
|
||||
local pattern = {testnode1, testnode3}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
assert(get_node(pos1).name == testnode1)
|
||||
check.pattern(pos1, pos2, pattern)
|
||||
end)
|
||||
|
||||
|
||||
for _, name in ipairs({
|
||||
"manipulations", "primitives", "schematic"
|
||||
}) do
|
||||
dofile(minetest.get_modpath("worldedit") .. "/test/" .. name .. ".lua")
|
||||
end
|
||||
|
||||
|
||||
register_test("Code")
|
||||
register_test("worldedit.lua", function()
|
||||
-- syntax error
|
||||
local err, ret = worldedit.lua("?")
|
||||
assert(ret == nil)
|
||||
assert(err:find("unexpected symbol"))
|
||||
|
||||
-- runtime error
|
||||
local err, ret = worldedit.lua("error(1234)")
|
||||
assert(ret == nil)
|
||||
assert(err:find("1234"))
|
||||
|
||||
-- normal operation
|
||||
local err, ret = worldedit.lua("return name..tostring(player == nil)..tostring(pos == nil)", "nobody")
|
||||
assert(err == nil)
|
||||
assert(ret == "\"nobodytruetrue\"")
|
||||
end)
|
||||
|
||||
register_test("worldedit.luatransform", function()
|
||||
local pos1, pos2 = area.get(2)
|
||||
|
||||
-- syntax error
|
||||
local err = worldedit.luatransform(pos1, pos2, "?")
|
||||
assert(err:find("unexpected symbol"))
|
||||
|
||||
-- runtime error
|
||||
local err = worldedit.luatransform(pos1, pos2, "error(2345)")
|
||||
assert(err:find("2345"))
|
||||
|
||||
-- normal operation
|
||||
local err = worldedit.luatransform(pos1, pos2,
|
||||
"minetest.swap_node(pos, {name=" .. ("%q"):format(testnode1) .. "})")
|
||||
assert(err == nil)
|
||||
check.filled(pos1, pos1, testnode1)
|
||||
end)
|
||||
|
||||
---------------------
|
||||
-- Main function
|
||||
---------------------
|
||||
worldedit.run_tests = function()
|
||||
do
|
||||
local v = minetest.get_version()
|
||||
print("Running " .. #tests .. " tests for WorldEdit " ..
|
||||
worldedit.version_string .. " on " .. v.project .. " " .. (v.hash or v.string))
|
||||
end
|
||||
|
||||
init_nodes()
|
||||
|
||||
-- emerge area from (0,0,0) ~ (56,56,56) and keep it loaded
|
||||
-- Note: making this area smaller speeds up tests
|
||||
local wanted = vec(56, 56, 56)
|
||||
for x = 0, math.floor(wanted.x/16) do
|
||||
for y = 0, math.floor(wanted.y/16) do
|
||||
for z = 0, math.floor(wanted.z/16) do
|
||||
assert(minetest.forceload_block(vec(x*16, y*16, z*16), true, -1))
|
||||
end
|
||||
end
|
||||
end
|
||||
area.assign(vec(0, 0, 0), wanted, function()
|
||||
|
||||
local failed = 0
|
||||
for _, test in ipairs(tests) do
|
||||
if not test.func then
|
||||
local s = "---- " .. test.name .. " "
|
||||
print(s .. string.rep("-", 60 - #s))
|
||||
else
|
||||
area.clear()
|
||||
local ok, err = pcall(test.func)
|
||||
print(string.format("%-60s %s", test.name, ok and "pass" or "FAIL"))
|
||||
if not ok then
|
||||
print(" " .. err)
|
||||
failed = failed + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print("Done, " .. failed .. " tests failed.")
|
||||
if failed == 0 then
|
||||
io.close(io.open(minetest.get_worldpath() .. "/tests_ok", "w"))
|
||||
end
|
||||
minetest.request_shutdown()
|
||||
end)
|
||||
end
|
||||
|
||||
-- for debug purposes
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
minetest.set_player_privs(player:get_player_name(),
|
||||
minetest.string_to_privs("fly,fast,noclip,basic_debug,debug,interact"))
|
||||
end)
|
||||
minetest.register_on_punchnode(function(pos, node, puncher)
|
||||
minetest.chat_send_player(puncher:get_player_name(), pos2str(pos))
|
||||
end)
|
121
worldedit/test/manipulations.lua
Normal file
|
@ -0,0 +1,121 @@
|
|||
---------------------
|
||||
local vec = vector.new
|
||||
local vecw = function(axis, n, base)
|
||||
local ret = vec(base)
|
||||
ret[axis] = n
|
||||
return ret
|
||||
end
|
||||
local air = "air"
|
||||
---------------------
|
||||
|
||||
|
||||
worldedit.register_test("Generic node manipulations")
|
||||
worldedit.register_test("worldedit.set", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local m = area.margin(1)
|
||||
|
||||
worldedit.set(pos1, pos2, testnode1)
|
||||
|
||||
check.filled(pos1, pos2, testnode1)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.set mix", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local m = area.margin(1)
|
||||
|
||||
worldedit.set(pos1, pos2, {testnode1, testnode2})
|
||||
|
||||
check.filled(pos1, pos2, {testnode1, testnode2})
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.replace", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local half1, half2 = area.split(pos1, pos2)
|
||||
|
||||
worldedit.set(pos1, half1, testnode1)
|
||||
worldedit.set(half2, pos2, testnode2)
|
||||
worldedit.replace(pos1, pos2, testnode1, testnode3)
|
||||
|
||||
check.not_filled(pos1, pos2, testnode1)
|
||||
check.filled(pos1, half1, testnode3)
|
||||
check.filled(half2, pos2, testnode2)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.replace inverse", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local half1, half2 = area.split(pos1, pos2)
|
||||
|
||||
worldedit.set(pos1, half1, testnode1)
|
||||
worldedit.set(half2, pos2, testnode2)
|
||||
worldedit.replace(pos1, pos2, testnode1, testnode3, true)
|
||||
|
||||
check.filled(pos1, half1, testnode1)
|
||||
check.filled(half2, pos2, testnode3)
|
||||
end)
|
||||
|
||||
-- FIXME?: this one looks overcomplicated
|
||||
worldedit.register_test("worldedit.copy", function()
|
||||
local pos1, pos2 = area.get(4)
|
||||
local axis, n = area.dir(2)
|
||||
local m = area.margin(1)
|
||||
local b = pos1[axis]
|
||||
|
||||
-- create one slice with testnode1, one with testnode2
|
||||
worldedit.set(pos1, vecw(axis, b + 1, pos2), testnode1)
|
||||
worldedit.set(vecw(axis, b + 2, pos1), pos2, testnode2)
|
||||
worldedit.copy(pos1, pos2, axis, n)
|
||||
|
||||
-- should have three slices now
|
||||
check.filled(pos1, vecw(axis, b + 1, pos2), testnode1)
|
||||
check.filled(vecw(axis, b + 2, pos1), pos2, testnode1)
|
||||
check.filled(vecw(axis, b + 4, pos1), vector.add(pos2, vecw(axis, n)), testnode2)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.copy2", function()
|
||||
local pos1, pos2 = area.get(6)
|
||||
local m1 = area.margin(1)
|
||||
local pos1_, pos2_ = area.get(6)
|
||||
local m2 = area.margin(1)
|
||||
|
||||
local pattern = {testnode1, testnode2, testnode3, testnode1, testnode2}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
worldedit.copy2(pos1, pos2, vector.subtract(pos1_, pos1))
|
||||
|
||||
check.pattern(pos1, pos2, pattern)
|
||||
check.pattern(pos1_, pos2_, pattern)
|
||||
check.filled2(m1, air)
|
||||
check.filled2(m2, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.move (overlap)", function()
|
||||
local pos1, pos2 = area.get(7)
|
||||
local axis, n = area.dir(2)
|
||||
local m = area.margin(1)
|
||||
|
||||
local pattern = {testnode2, testnode1, testnode2, testnode3, testnode3}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
worldedit.move(pos1, pos2, axis, n)
|
||||
|
||||
check.filled(pos1, vecw(axis, pos1[axis] + n - 1, pos2), air)
|
||||
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.move", function()
|
||||
local pos1, pos2 = area.get(10)
|
||||
local axis, n = area.dir(10)
|
||||
local m = area.margin(1)
|
||||
|
||||
local pattern = {testnode1, testnode3, testnode3, testnode2}
|
||||
place_pattern(pos1, pos2, pattern)
|
||||
worldedit.move(pos1, pos2, axis, n)
|
||||
|
||||
check.filled(pos1, pos2, air)
|
||||
check.pattern(vecw(axis, pos1[axis] + n, pos1), vecw(axis, pos2[axis] + n, pos2), pattern)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
-- TODO: the rest (also testing param2 + metadata)
|
59
worldedit/test/primitives.lua
Normal file
|
@ -0,0 +1,59 @@
|
|||
---------------------
|
||||
local vec = vector.new
|
||||
local vecw = function(axis, n, base)
|
||||
local ret = vec(base)
|
||||
ret[axis] = n
|
||||
return ret
|
||||
end
|
||||
local air = "air"
|
||||
---------------------
|
||||
|
||||
|
||||
worldedit.register_test("Primitives")
|
||||
worldedit.register_test("worldedit.cube", function()
|
||||
local pos1, pos2 = area.get(6, 5, 4)
|
||||
local m = area.margin(1)
|
||||
|
||||
local center = vec(pos1.x + 3, pos1.y, pos1.z + 2)
|
||||
|
||||
worldedit.cube(center, 6, 5, 4, testnode2)
|
||||
|
||||
check.filled(pos1, pos2, testnode2)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.cube hollow small", function()
|
||||
for n = 1, 2 do
|
||||
local pos1, pos2 = area.get(n)
|
||||
local m = area.margin(1)
|
||||
|
||||
local center = vec(pos1.x + math.floor(n/2), pos1.y, pos1.z + math.floor(n/2))
|
||||
|
||||
worldedit.cube(center, n, n, n, testnode1, true)
|
||||
|
||||
check.filled(pos1, pos2, testnode1) -- filled entirely
|
||||
check.filled2(m, air)
|
||||
end
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.cube hollow", function()
|
||||
local pos1, pos2 = area.get(6, 5, 4)
|
||||
local m = area.margin(1)
|
||||
|
||||
local center = vec(pos1.x + 3, pos1.y, pos1.z + 2)
|
||||
|
||||
worldedit.cube(center, 6, 5, 4, testnode1, true)
|
||||
|
||||
check.filled(vector.add(pos1, vec(1,1,1)), vector.subtract(pos2, vec(1,1,1)), air)
|
||||
check.filled2({
|
||||
{ vecw("x", pos2.x, pos1), pos2 },
|
||||
{ vecw("y", pos2.y, pos1), pos2 },
|
||||
{ vecw("z", pos2.z, pos1), pos2 },
|
||||
{ pos1, vecw("x", pos1.x, pos2) },
|
||||
{ pos1, vecw("y", pos1.y, pos2) },
|
||||
{ pos1, vecw("z", pos1.z, pos2) },
|
||||
}, testnode1)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
|
||||
|
162
worldedit/test/schematic.lua
Normal file
|
@ -0,0 +1,162 @@
|
|||
---------------------
|
||||
local vec = vector.new
|
||||
local air = "air"
|
||||
---------------------
|
||||
|
||||
|
||||
local function output_weird(numbers, body)
|
||||
local s = {"return {"}
|
||||
for _, parts in ipairs(numbers) do
|
||||
s[#s+1] = "{"
|
||||
for _, n in ipairs(parts) do
|
||||
s[#s+1] = string.format(" {%d},", n)
|
||||
end
|
||||
s[#s+1] = "},"
|
||||
end
|
||||
return table.concat(s, "\n") .. table.concat(body, "\n") .. "}"
|
||||
end
|
||||
|
||||
local fmt1p = '{\n ["x"]=%d,\n ["y"]=%d,\n ["z"]=%d,\n},'
|
||||
local fmt1n = '{\n ["name"]="%s",\n},'
|
||||
local fmt4 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["meta"] = { ["fields"] = { }, ["inventory"] = { } }, ["param2"] = 0, ["param1"] = 0, ["name"] = "%s" }'
|
||||
local fmt5 = '{ ["x"] = %d, ["y"] = %d, ["z"] = %d, ["name"] = "%s" }'
|
||||
local fmt51 = '{[r2]=0,x=%d,y=%d,z=%d,name=r%d}'
|
||||
local fmt52 = '{x=%d,y=%d,z=%d,name=_[%d]}'
|
||||
|
||||
local test_data = {
|
||||
-- used by WorldEdit 0.2 (first public release)
|
||||
{
|
||||
name = "v1", ver = 1,
|
||||
gen = function(pat)
|
||||
local numbers = {
|
||||
{2, 3, 4, 5, 6},
|
||||
{7, 8}, {9, 10}, {11, 12},
|
||||
{13, 14}, {15, 16}
|
||||
}
|
||||
return output_weird(numbers, {
|
||||
fmt1p:format(0, 0, 0),
|
||||
fmt1n:format(pat[1]),
|
||||
fmt1p:format(0, 1, 0),
|
||||
fmt1n:format(pat[3]),
|
||||
fmt1p:format(1, 1, 0),
|
||||
fmt1n:format(pat[1]),
|
||||
fmt1p:format(1, 0, 1),
|
||||
fmt1n:format(pat[3]),
|
||||
fmt1p:format(0, 1, 1),
|
||||
fmt1n:format(pat[1]),
|
||||
})
|
||||
end
|
||||
},
|
||||
|
||||
-- v2: missing because I couldn't find any code in my archives that actually wrote this format
|
||||
|
||||
{
|
||||
name = "v3", ver = 3,
|
||||
gen = function(pat)
|
||||
assert(pat[2] == air)
|
||||
return table.concat({
|
||||
"0 0 0 " .. pat[1] .. " 0 0",
|
||||
"0 1 0 " .. pat[3] .. " 0 0",
|
||||
"1 1 0 " .. pat[1] .. " 0 0",
|
||||
"1 0 1 " .. pat[3] .. " 0 0",
|
||||
"0 1 1 " .. pat[1] .. " 0 0",
|
||||
}, "\n")
|
||||
end
|
||||
},
|
||||
|
||||
{
|
||||
name = "v4", ver = 4,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
"return { " .. fmt4:format(0, 0, 0, pat[1]),
|
||||
fmt4:format(0, 1, 0, pat[3]),
|
||||
fmt4:format(1, 1, 0, pat[1]),
|
||||
fmt4:format(1, 0, 1, pat[3]),
|
||||
fmt4:format(0, 1, 1, pat[1]) .. " }",
|
||||
}, ", ")
|
||||
end
|
||||
},
|
||||
|
||||
-- like v4 but no meta and param (if empty)
|
||||
{
|
||||
name = "v5 (pre-5.6)", ver = 5,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
"5:return { " .. fmt5:format(0, 0, 0, pat[1]),
|
||||
fmt5:format(0, 1, 0, pat[3]),
|
||||
fmt5:format(1, 1, 0, pat[1]),
|
||||
fmt5:format(1, 0, 1, pat[3]),
|
||||
fmt5:format(0, 1, 1, pat[1]) .. " }",
|
||||
}, ", ")
|
||||
end
|
||||
},
|
||||
|
||||
-- reworked engine serialization in 5.6
|
||||
{
|
||||
name = "v5 (5.6)", ver = 5,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
'5:r1="' .. pat[1] .. '";r2="param1";r3="' .. pat[3] .. '";return {'
|
||||
.. fmt51:format(0, 0, 0, 1),
|
||||
fmt51:format(0, 1, 0, 3),
|
||||
fmt51:format(1, 1, 0, 1),
|
||||
fmt51:format(1, 0, 1, 3),
|
||||
fmt51:format(0, 1, 1, 1) .. "}",
|
||||
}, ",")
|
||||
end
|
||||
},
|
||||
|
||||
-- small changes on engine side again
|
||||
{
|
||||
name = "v5 (post-5.7)", ver = 5,
|
||||
gen = function(pat)
|
||||
return table.concat({
|
||||
'5:local _={};_[1]="' .. pat[1] .. '";_[3]="' .. pat[3] .. '";return {'
|
||||
.. fmt52:format(0, 0, 0, 1),
|
||||
fmt52:format(0, 1, 0, 3),
|
||||
fmt52:format(1, 1, 0, 1),
|
||||
fmt52:format(1, 0, 1, 3),
|
||||
fmt52:format(0, 1, 1, 1) .. "}",
|
||||
}, ",")
|
||||
end
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
worldedit.register_test("Schematics")
|
||||
worldedit.register_test("worldedit.read_header", function()
|
||||
local value = '5,foo,BAR,-1,234:the content'
|
||||
local version, header, content = worldedit.read_header(value)
|
||||
assert(version == 5)
|
||||
assert(#header == 4)
|
||||
assert(header[1] == "foo" and header[2] == "BAR")
|
||||
assert(header[3] == "-1" and header[4] == "234")
|
||||
assert(content == "the content")
|
||||
end)
|
||||
|
||||
worldedit.register_test("worldedit.allocate", function()
|
||||
local value = '3:-1 0 0 dummy 0 0\n0 0 4 dummy 0 0\n0 1 0 dummy 0 0'
|
||||
local pos1, pos2, count = worldedit.allocate(vec(1, 1, 1), value)
|
||||
assert(vector.equals(pos1, vec(0, 1, 1)))
|
||||
assert(vector.equals(pos2, vec(1, 2, 5)))
|
||||
assert(count == 3)
|
||||
end)
|
||||
|
||||
for _, e in ipairs(test_data) do
|
||||
worldedit.register_test("worldedit.deserialize " .. e.name, function()
|
||||
local pos1, pos2 = area.get(2)
|
||||
local m = area.margin(1)
|
||||
|
||||
local pat = {testnode3, air, testnode2}
|
||||
local value = e.gen(pat)
|
||||
assert(type(value) == "string")
|
||||
|
||||
local version = worldedit.read_header(value)
|
||||
assert(version == e.ver, "version: got " .. tostring(version) .. " expected " .. e.ver)
|
||||
local count = worldedit.deserialize(pos1, value)
|
||||
assert(count ~= nil and count > 0)
|
||||
|
||||
check.pattern(pos1, pos2, pat)
|
||||
check.filled2(m, air)
|
||||
end)
|
||||
end
|
357
worldedit/transformations.lua
Normal file
|
@ -0,0 +1,357 @@
|
|||
--- Node transformations.
|
||||
-- @module worldedit.transformations
|
||||
|
||||
worldedit.deferred_execution = function(next_one, finished)
|
||||
-- Allocate 80% of server step for execution
|
||||
local allocated_usecs =
|
||||
tonumber(minetest.settings:get("dedicated_server_step"):split(" ")[1]) * 1000000 * 0.8
|
||||
local function f()
|
||||
local deadline = minetest.get_us_time() + allocated_usecs
|
||||
repeat
|
||||
local is_done = next_one()
|
||||
if is_done then
|
||||
if finished then
|
||||
finished()
|
||||
end
|
||||
return
|
||||
end
|
||||
until minetest.get_us_time() >= deadline
|
||||
minetest.after(0, f)
|
||||
end
|
||||
f()
|
||||
end
|
||||
|
||||
--- Duplicates a region `amount` times with offset vector `direction`.
|
||||
-- Stacking is spread across server steps.
|
||||
-- @return The number of nodes stacked.
|
||||
function worldedit.stack2(pos1, pos2, direction, amount, finished)
|
||||
-- Protect arguments from external changes during execution
|
||||
pos1 = vector.copy(pos1)
|
||||
pos2 = vector.copy(pos2)
|
||||
direction = vector.copy(direction)
|
||||
|
||||
local i = 0
|
||||
local translated = vector.new()
|
||||
local function step()
|
||||
translated.x = translated.x + direction.x
|
||||
translated.y = translated.y + direction.y
|
||||
translated.z = translated.z + direction.z
|
||||
worldedit.copy2(pos1, pos2, translated)
|
||||
i = i + 1
|
||||
return i >= amount
|
||||
end
|
||||
worldedit.deferred_execution(step, finished)
|
||||
|
||||
return worldedit.volume(pos1, pos2) * amount
|
||||
end
|
||||
|
||||
|
||||
--- Duplicates a region along `axis` `amount` times.
|
||||
-- Stacking is spread across server steps.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param axis Axis direction, "x", "y", or "z".
|
||||
-- @param count
|
||||
-- @return The number of nodes stacked.
|
||||
function worldedit.stack(pos1, pos2, axis, count, finished)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local length = pos2[axis] - pos1[axis] + 1
|
||||
if count < 0 then
|
||||
count = -count
|
||||
length = -length
|
||||
end
|
||||
|
||||
local i, distance = 0, 0
|
||||
local function step()
|
||||
distance = distance + length
|
||||
worldedit.copy(pos1, pos2, axis, distance)
|
||||
i = i + 1
|
||||
return i >= count
|
||||
end
|
||||
worldedit.deferred_execution(step, finished)
|
||||
|
||||
return worldedit.volume(pos1, pos2) * count
|
||||
end
|
||||
|
||||
|
||||
--- Stretches a region by a factor of positive integers along the X, Y, and Z
|
||||
-- axes, respectively, with `pos1` as the origin.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param stretch_x Amount to stretch along X axis.
|
||||
-- @param stretch_y Amount to stretch along Y axis.
|
||||
-- @param stretch_z Amount to stretch along Z axis.
|
||||
-- @return The number of nodes scaled.
|
||||
-- @return The new scaled position 1.
|
||||
-- @return The new scaled position 2.
|
||||
function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
-- Prepare schematic of large node
|
||||
local get_node, get_meta, place_schematic = minetest.get_node,
|
||||
minetest.get_meta, minetest.place_schematic
|
||||
local placeholder_node = {name="", param1=255, param2=0}
|
||||
local nodes = {}
|
||||
for i = 1, stretch_x * stretch_y * stretch_z do
|
||||
nodes[i] = placeholder_node
|
||||
end
|
||||
local schematic = {size=vector.new(stretch_x, stretch_y, stretch_z), data=nodes}
|
||||
|
||||
local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1
|
||||
|
||||
local new_pos2 = {
|
||||
x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,
|
||||
y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,
|
||||
z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,
|
||||
}
|
||||
worldedit.keep_loaded(pos1, new_pos2)
|
||||
|
||||
local pos = vector.new(pos2.x, 0, 0)
|
||||
local big_pos = vector.new()
|
||||
while pos.x >= pos1.x do
|
||||
pos.y = pos2.y
|
||||
while pos.y >= pos1.y do
|
||||
pos.z = pos2.z
|
||||
while pos.z >= pos1.z do
|
||||
local node = get_node(pos) -- Get current node
|
||||
local meta = get_meta(pos):to_table() -- Get meta of current node
|
||||
|
||||
-- Calculate far corner of the big node
|
||||
local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x
|
||||
local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y
|
||||
local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z
|
||||
|
||||
-- Create large node
|
||||
placeholder_node.name = node.name
|
||||
placeholder_node.param2 = node.param2
|
||||
big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z
|
||||
place_schematic(big_pos, schematic)
|
||||
|
||||
-- Fill in large node meta
|
||||
if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
|
||||
-- Node has meta fields
|
||||
for x = 0, size_x do
|
||||
for y = 0, size_y do
|
||||
for z = 0, size_z do
|
||||
big_pos.x = pos_x + x
|
||||
big_pos.y = pos_y + y
|
||||
big_pos.z = pos_z + z
|
||||
-- Set metadata of new node
|
||||
get_meta(big_pos):from_table(meta)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
pos.z = pos.z - 1
|
||||
end
|
||||
pos.y = pos.y - 1
|
||||
end
|
||||
pos.x = pos.x - 1
|
||||
end
|
||||
return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2
|
||||
end
|
||||
|
||||
|
||||
--- Transposes a region between two axes.
|
||||
-- @return The number of nodes transposed.
|
||||
-- @return The new transposed position 1.
|
||||
-- @return The new transposed position 2.
|
||||
function worldedit.transpose(pos1, pos2, axis1, axis2)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local compare
|
||||
local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]
|
||||
|
||||
if extent1 > extent2 then
|
||||
compare = function(extent1, extent2)
|
||||
return extent1 > extent2
|
||||
end
|
||||
else
|
||||
compare = function(extent1, extent2)
|
||||
return extent1 < extent2
|
||||
end
|
||||
end
|
||||
|
||||
-- Calculate the new position 2 after transposition
|
||||
local new_pos2 = vector.new(pos2)
|
||||
new_pos2[axis1] = pos1[axis1] + extent2
|
||||
new_pos2[axis2] = pos1[axis2] + extent1
|
||||
|
||||
local upper_bound = vector.new(pos2)
|
||||
if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end
|
||||
if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end
|
||||
worldedit.keep_loaded(pos1, upper_bound)
|
||||
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
local get_node, get_meta, set_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.set_node
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
|
||||
if compare(extent1, extent2) then -- Transpose only if below the diagonal
|
||||
local node1 = get_node(pos)
|
||||
local meta1 = get_meta(pos):to_table()
|
||||
local value1, value2 = pos[axis1], pos[axis2] -- Save position values
|
||||
pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents
|
||||
local node2 = get_node(pos)
|
||||
local meta2 = get_meta(pos):to_table()
|
||||
set_node(pos, node1)
|
||||
get_meta(pos):from_table(meta1)
|
||||
pos[axis1], pos[axis2] = value1, value2 -- Restore position values
|
||||
set_node(pos, node2)
|
||||
get_meta(pos):from_table(meta2)
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
return worldedit.volume(pos1, pos2), pos1, new_pos2
|
||||
end
|
||||
|
||||
|
||||
--- Flips a region along `axis`.
|
||||
-- @return The number of nodes flipped.
|
||||
function worldedit.flip(pos1, pos2, axis)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
--- TODO: Flip the region slice by slice along the flip axis using schematic method.
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
local start = pos1[axis] + pos2[axis]
|
||||
pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
|
||||
local get_node, get_meta, set_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.set_node
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node1 = get_node(pos)
|
||||
local meta1 = get_meta(pos):to_table()
|
||||
local value = pos[axis] -- Save position
|
||||
pos[axis] = start - value -- Shift position
|
||||
local node2 = get_node(pos)
|
||||
local meta2 = get_meta(pos):to_table()
|
||||
set_node(pos, node1)
|
||||
get_meta(pos):from_table(meta1)
|
||||
pos[axis] = value -- Restore position
|
||||
set_node(pos, node2)
|
||||
get_meta(pos):from_table(meta2)
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
|
||||
--- Rotates a region clockwise around an axis.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param axis Axis ("x", "y", or "z").
|
||||
-- @param angle Angle in degrees (90 degree increments only).
|
||||
-- @return The number of nodes rotated.
|
||||
-- @return The new first position.
|
||||
-- @return The new second position.
|
||||
function worldedit.rotate(pos1, pos2, axis, angle)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local other1, other2 = worldedit.get_axis_others(axis)
|
||||
angle = angle % 360
|
||||
|
||||
local count
|
||||
if angle == 90 then
|
||||
worldedit.flip(pos1, pos2, other1)
|
||||
count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
|
||||
elseif angle == 180 then
|
||||
worldedit.flip(pos1, pos2, other1)
|
||||
count = worldedit.flip(pos1, pos2, other2)
|
||||
elseif angle == 270 then
|
||||
worldedit.flip(pos1, pos2, other2)
|
||||
count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
|
||||
else
|
||||
error("Only 90 degree increments are supported!")
|
||||
end
|
||||
return count, pos1, pos2
|
||||
end
|
||||
|
||||
|
||||
--- Rotates all oriented nodes in a region clockwise around the Y axis.
|
||||
-- @param pos1
|
||||
-- @param pos2
|
||||
-- @param angle Angle in degrees (90 degree increments only).
|
||||
-- @return The number of nodes oriented.
|
||||
function worldedit.orient(pos1, pos2, angle)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local registered_nodes = minetest.registered_nodes
|
||||
|
||||
local wallmounted = {
|
||||
[90] = {0, 1, 5, 4, 2, 3, 0, 0},
|
||||
[180] = {0, 1, 3, 2, 5, 4, 0, 0},
|
||||
[270] = {0, 1, 4, 5, 3, 2, 0, 0}
|
||||
}
|
||||
local facedir = {
|
||||
[90] = { 1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16,
|
||||
9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22},
|
||||
[180] = { 2, 3, 0, 1, 10, 11, 8, 9, 6, 7, 4, 5,
|
||||
18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21},
|
||||
[270] = { 3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14,
|
||||
7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20}
|
||||
}
|
||||
|
||||
angle = angle % 360
|
||||
if angle == 0 then
|
||||
return 0
|
||||
end
|
||||
if angle % 90 ~= 0 then
|
||||
error("Only 90 degree increments are supported!")
|
||||
end
|
||||
local wallmounted_substitution = wallmounted[angle]
|
||||
local facedir_substitution = facedir[angle]
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local count = 0
|
||||
local get_node, swap_node = minetest.get_node, minetest.swap_node
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node = get_node(pos)
|
||||
local def = registered_nodes[node.name]
|
||||
if def then
|
||||
local paramtype2 = def.paramtype2
|
||||
if paramtype2 == "wallmounted" or
|
||||
paramtype2 == "colorwallmounted" then
|
||||
local orient = node.param2 % 8
|
||||
node.param2 = node.param2 - orient +
|
||||
wallmounted_substitution[orient + 1]
|
||||
swap_node(pos, node)
|
||||
count = count + 1
|
||||
elseif paramtype2 == "facedir" or
|
||||
paramtype2 == "colorfacedir" then
|
||||
local orient = node.param2 % 32
|
||||
node.param2 = node.param2 - orient +
|
||||
facedir_substitution[orient + 1]
|
||||
swap_node(pos, node)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
return count
|
||||
end
|
|
@ -1,54 +1,40 @@
|
|||
worldedit = worldedit or {}
|
||||
|
||||
--modifies positions `pos1` and `pos2` so that each component of `pos1` is less than or equal to its corresponding conent of `pos2`, returning two new positions
|
||||
worldedit.sort_pos = function(pos1, pos2)
|
||||
pos1 = {x=pos1.x, y=pos1.y, z=pos1.z}
|
||||
pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
|
||||
if pos1.x > pos2.x then
|
||||
pos2.x, pos1.x = pos1.x, pos2.x
|
||||
end
|
||||
if pos1.y > pos2.y then
|
||||
pos2.y, pos1.y = pos1.y, pos2.y
|
||||
end
|
||||
if pos1.z > pos2.z then
|
||||
pos2.z, pos1.z = pos1.z, pos2.z
|
||||
end
|
||||
return pos1, pos2
|
||||
end
|
||||
|
||||
--determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume
|
||||
worldedit.volume = function(pos1, pos2)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1)
|
||||
end
|
||||
--- Functions for visibly hiding nodes
|
||||
-- @module worldedit.visualization
|
||||
|
||||
minetest.register_node("worldedit:placeholder", {
|
||||
drawtype = "airlike",
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
diggable = false,
|
||||
pointable = false,
|
||||
walkable = false,
|
||||
groups = {not_in_creative_inventory=1},
|
||||
})
|
||||
|
||||
--hides all nodes in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes, returning the number of nodes hidden
|
||||
worldedit.hide = function(pos1, pos2, tenv)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
--- Hides all nodes in a region defined by positions `pos1` and `pos2` by
|
||||
-- non-destructively replacing them with invisible nodes.
|
||||
-- @return The number of nodes hidden.
|
||||
function worldedit.hide(pos1, pos2)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
local placeholder = {name="worldedit:placeholder", param1=0, param2=0}
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
local get_node, get_meta, swap_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.swap_node
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node = env:get_node(pos)
|
||||
placeholder.param1, placeholder.param2 = node.param1, node.param2 --copy node's param1 and param2
|
||||
local data = env:get_meta(pos):to_table() --obtain metadata of original node
|
||||
env:add_node(pos, placeholder) --add placeholder node
|
||||
local meta = env:get_meta(pos) --obtain placeholder meta
|
||||
meta:from_table(data) --set placeholder metadata to the original node's metadata
|
||||
meta:set_string("worldedit_placeholder", node.name) --add the node's name
|
||||
local node = get_node(pos)
|
||||
if node.name ~= "air" and node.name ~= "worldedit:placeholder" then
|
||||
-- Save the node's original name
|
||||
get_meta(pos):set_string("worldedit_placeholder", node.name)
|
||||
-- Swap in placeholder node
|
||||
node.name = "worldedit:placeholder"
|
||||
swap_node(pos, node)
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
|
@ -58,32 +44,59 @@ worldedit.hide = function(pos1, pos2, tenv)
|
|||
return worldedit.volume(pos1, pos2)
|
||||
end
|
||||
|
||||
--suppresses all instances of `nodename` in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes, returning the number of nodes suppressed
|
||||
worldedit.suppress = function(pos1, pos2, nodename, tenv)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
|
||||
if minetest.registered_nodes[nodename] == nil then
|
||||
nodename = "default:" .. nodename
|
||||
--- Suppresses all instances of `node_name` in a region defined by positions
|
||||
-- `pos1` and `pos2` by non-destructively replacing them with invisible nodes.
|
||||
-- @return The number of nodes suppressed.
|
||||
function worldedit.suppress(pos1, pos2, node_name)
|
||||
-- Ignore placeholder supression
|
||||
if node_name == "worldedit:placeholder" then
|
||||
return 0
|
||||
end
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
local placeholder = {name="worldedit:placeholder", param1=0, param2=0}
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local nodes = minetest.find_nodes_in_area(pos1, pos2, node_name)
|
||||
local get_node, get_meta, swap_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.swap_node
|
||||
for _, pos in ipairs(nodes) do
|
||||
local node = get_node(pos)
|
||||
-- Save the node's original name
|
||||
get_meta(pos):set_string("worldedit_placeholder", node.name)
|
||||
-- Swap in placeholder node
|
||||
node.name = "worldedit:placeholder"
|
||||
swap_node(pos, node)
|
||||
end
|
||||
return #nodes
|
||||
end
|
||||
|
||||
--- Highlights all instances of `node_name` in a region defined by positions
|
||||
-- `pos1` and `pos2` by non-destructively hiding all other nodes.
|
||||
-- @return The number of nodes found.
|
||||
function worldedit.highlight(pos1, pos2, node_name)
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local pos = vector.new(pos1.x, 0, 0)
|
||||
local get_node, get_meta, swap_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.swap_node
|
||||
local count = 0
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node = env:get_node(pos)
|
||||
if node.name == nodename then
|
||||
placeholder.param1, placeholder.param2 = node.param1, node.param2 --copy node's param1 and param2
|
||||
local data = env:get_meta(pos):to_table() --obtain metadata of original node
|
||||
env:add_node(pos, placeholder) --add placeholder node
|
||||
local meta = env:get_meta(pos) --obtain placeholder meta
|
||||
meta:from_table(data) --set placeholder metadata to the original node's metadata
|
||||
meta:set_string("worldedit_placeholder", nodename) --add the node's name
|
||||
local node = get_node(pos)
|
||||
if node.name == node_name then -- Node found
|
||||
count = count + 1
|
||||
elseif node.name ~= "worldedit:placeholder" then -- Hide other nodes
|
||||
-- Save the node's original name
|
||||
get_meta(pos):set_string("worldedit_placeholder", node.name)
|
||||
-- Swap in placeholder node
|
||||
node.name = "worldedit:placeholder"
|
||||
swap_node(pos, node)
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
|
@ -94,71 +107,26 @@ worldedit.suppress = function(pos1, pos2, nodename, tenv)
|
|||
return count
|
||||
end
|
||||
|
||||
--highlights all instances of `nodename` in a region defined by positions `pos1` and `pos2` by non-destructively hiding all other nodes, returning the number of nodes found
|
||||
worldedit.highlight = function(pos1, pos2, nodename, tenv)
|
||||
-- Restores all nodes hidden with WorldEdit functions in a region defined
|
||||
-- by positions `pos1` and `pos2`.
|
||||
-- @return The number of nodes restored.
|
||||
function worldedit.restore(pos1, pos2)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
|
||||
if minetest.registered_nodes[nodename] == nil then
|
||||
nodename = "default:" .. nodename
|
||||
end
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
local placeholder = {name="worldedit:placeholder", param1=0, param2=0}
|
||||
local count = 0
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local node = env:get_node(pos)
|
||||
if node.name == nodename then --node found
|
||||
count = count + 1
|
||||
else --hide other nodes
|
||||
placeholder.param1, placeholder.param2 = node.param1, node.param2 --copy node's param1 and param2
|
||||
local data = env:get_meta(pos):to_table() --obtain metadata of original node
|
||||
env:add_node(pos, placeholder) --add placeholder node
|
||||
local meta = env:get_meta(pos) --obtain placeholder meta
|
||||
meta:from_table(data) --set placeholder metadata to the original node's metadata
|
||||
meta:set_string("worldedit_placeholder", node.name) --add the node's name
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
local nodes = minetest.find_nodes_in_area(pos1, pos2, "worldedit:placeholder")
|
||||
local get_node, get_meta, swap_node = minetest.get_node,
|
||||
minetest.get_meta, minetest.swap_node
|
||||
for _, pos in ipairs(nodes) do
|
||||
local node = get_node(pos)
|
||||
local meta = get_meta(pos)
|
||||
local data = meta:to_table()
|
||||
node.name = data.fields.worldedit_placeholder
|
||||
data.fields.worldedit_placeholder = nil
|
||||
meta:from_table(data)
|
||||
swap_node(pos, node)
|
||||
end
|
||||
return count
|
||||
return #nodes
|
||||
end
|
||||
|
||||
--restores all nodes hidden with WorldEdit functions in a region defined by positions `pos1` and `pos2`, returning the number of nodes restored
|
||||
worldedit.restore = function(pos1, pos2, tenv)
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
if env == nil then env = minetest.env end
|
||||
|
||||
local pos = {x=pos1.x, y=0, z=0}
|
||||
local node = {name="", param1=0, param2=0}
|
||||
local count = 0
|
||||
while pos.x <= pos2.x do
|
||||
pos.y = pos1.y
|
||||
while pos.y <= pos2.y do
|
||||
pos.z = pos1.z
|
||||
while pos.z <= pos2.z do
|
||||
local currentnode = env:get_node(pos)
|
||||
if currentnode.name == "worldedit:placeholder" then
|
||||
node.param1, node.param2 = currentnode.param1, currentnode.param2 --copy node's param1 and param2
|
||||
local data = env:get_meta(pos):to_table() --obtain node metadata
|
||||
node.name = data.fields.worldedit_placeholder --set node name
|
||||
data.fields.worldedit_placeholder = nil --delete old nodename
|
||||
env:add_node(pos, node) --add original node
|
||||
env:get_meta(pos):from_table(data) --set original node metadata
|
||||
count = count + 1
|
||||
end
|
||||
pos.z = pos.z + 1
|
||||
end
|
||||
pos.y = pos.y + 1
|
||||
end
|
||||
pos.x = pos.x + 1
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
|
122
worldedit_brush/init.lua
Normal file
|
@ -0,0 +1,122 @@
|
|||
local S = minetest.get_translator("worldedit_brush")
|
||||
|
||||
local BRUSH_MAX_DIST = 150
|
||||
local brush_on_use = function(itemstack, placer)
|
||||
local meta = itemstack:get_meta()
|
||||
local name = placer:get_player_name()
|
||||
|
||||
local cmd = meta:get_string("command")
|
||||
if cmd == "" then
|
||||
worldedit.player_notify(name,
|
||||
S("This brush is not bound, use @1 to bind a command to it.",
|
||||
minetest.colorize("#00ffff", "//brush")), "info")
|
||||
return false
|
||||
end
|
||||
|
||||
local cmddef = worldedit.registered_commands[cmd]
|
||||
if cmddef == nil then return false end -- shouldn't happen as //brush checks this
|
||||
|
||||
local has_privs, missing_privs = minetest.check_player_privs(name, cmddef.privs)
|
||||
if not has_privs then
|
||||
worldedit.player_notify(name,
|
||||
S("Missing privileges: @1", table.concat(missing_privs, ", ")), "error")
|
||||
return false
|
||||
end
|
||||
|
||||
local raybegin = vector.add(placer:get_pos(),
|
||||
vector.new(0, placer:get_properties().eye_height, 0))
|
||||
local rayend = vector.add(raybegin, vector.multiply(placer:get_look_dir(), BRUSH_MAX_DIST))
|
||||
local ray = minetest.raycast(raybegin, rayend, false, true)
|
||||
local pointed_thing = ray:next()
|
||||
if pointed_thing == nil then
|
||||
worldedit.player_notify(name, S("Too far away."), "error")
|
||||
return false
|
||||
end
|
||||
|
||||
assert(pointed_thing.type == "node")
|
||||
worldedit.pos1[name] = pointed_thing.under
|
||||
worldedit.pos2[name] = nil
|
||||
worldedit.marker_update(name)
|
||||
|
||||
assert(cmddef.require_pos < 2)
|
||||
local parsed = {cmddef.parse(meta:get_string("params"))}
|
||||
if not table.remove(parsed, 1) then return false end -- shouldn't happen
|
||||
|
||||
-- discard success messages
|
||||
local player_notify_old = worldedit.player_notify
|
||||
worldedit.player_notify = function(name, msg, typ)
|
||||
if typ == "ok" then return end
|
||||
return player_notify_old(name, msg, typ)
|
||||
end
|
||||
|
||||
minetest.log("action", string.format("%s uses WorldEdit brush (//%s) at %s",
|
||||
name, cmd, minetest.pos_to_string(pointed_thing.under)))
|
||||
cmddef.func(name, unpack(parsed))
|
||||
|
||||
worldedit.player_notify = player_notify_old
|
||||
return true
|
||||
end
|
||||
|
||||
minetest.register_tool(":worldedit:brush", {
|
||||
description = S("WorldEdit Brush"),
|
||||
inventory_image = "worldedit_brush.png",
|
||||
stack_max = 1, -- no need to stack these (metadata prevents this anyway)
|
||||
range = 0,
|
||||
on_use = function(itemstack, placer, pointed_thing)
|
||||
brush_on_use(itemstack, placer)
|
||||
return itemstack -- nothing consumed, nothing changed
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("brush", {
|
||||
privs = {worldedit=true},
|
||||
params = "none/<cmd> [parameters]",
|
||||
description = S("Assign command to WorldEdit brush item or clear assignment using 'none'"),
|
||||
parse = function(param)
|
||||
local found, _, cmd, params = param:find("^([^%s]+)%s+(.+)$")
|
||||
if not found then
|
||||
params = ""
|
||||
found, _, cmd = param:find("^(.+)$")
|
||||
end
|
||||
if not found then
|
||||
return false
|
||||
end
|
||||
return true, cmd, params
|
||||
end,
|
||||
func = function(name, cmd, params)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if not player then return end
|
||||
local itemstack = player:get_wielded_item()
|
||||
if itemstack == nil or itemstack:get_name() ~= "worldedit:brush" then
|
||||
return false, S("Not holding brush item.")
|
||||
end
|
||||
|
||||
cmd = cmd:lower()
|
||||
local meta = itemstack:get_meta()
|
||||
if cmd == "none" then
|
||||
meta:from_table(nil)
|
||||
worldedit.player_notify(name, S("Brush assignment cleared."), "ok")
|
||||
else
|
||||
local cmddef = worldedit.registered_commands[cmd]
|
||||
if cmddef == nil or cmddef.require_pos ~= 1 then
|
||||
return false, S("@1 cannot be used with brushes",
|
||||
minetest.colorize("#00ffff", "//"..cmd))
|
||||
end
|
||||
|
||||
-- Try parsing command params so we can give the user feedback
|
||||
local ok, err = cmddef.parse(params)
|
||||
if not ok then
|
||||
err = err or S("invalid usage")
|
||||
return false, S("Error with command: @1", err)
|
||||
end
|
||||
|
||||
meta:set_string("command", cmd)
|
||||
meta:set_string("params", params)
|
||||
local fullcmd = minetest.colorize("#00ffff", "//"..cmd) .. " " .. params
|
||||
meta:set_string("description",
|
||||
minetest.registered_tools["worldedit:brush"].description .. ": " .. fullcmd)
|
||||
worldedit.player_notify(name, S("Brush assigned to command: @1", fullcmd), "ok")
|
||||
end
|
||||
player:set_wielded_item(itemstack)
|
||||
end,
|
||||
})
|
12
worldedit_brush/locale/template.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
# textdomain: worldedit_brush
|
||||
This brush is not bound, use @1 to bind a command to it.=
|
||||
Missing privileges: @1=
|
||||
Too far away.=
|
||||
WorldEdit Brush=
|
||||
Assign command to WorldEdit brush item or clear assignment using 'none'=
|
||||
Not holding brush item.=
|
||||
Brush assignment cleared.=
|
||||
@1 cannot be used with brushes=
|
||||
invalid usage=
|
||||
Error with command: @1=
|
||||
Brush assigned to command: @1=
|
12
worldedit_brush/locale/worldedit_brush.de.tr
Normal file
|
@ -0,0 +1,12 @@
|
|||
# textdomain: worldedit_brush
|
||||
This brush is not bound, use @1 to bind a command to it.=Dieser Pinsel ist nicht konfiguriert, nutzen Sie @1 um ein Befehl an ihn zu binden.
|
||||
Missing privileges: @1=Fehlende Privilegien: @1
|
||||
Too far away.=Zu weit weg.
|
||||
WorldEdit Brush=WorldEdit-Pinsel
|
||||
Assign command to WorldEdit brush item or clear assignment using 'none'=Befehl an WorldEdit-Pinsel binden oder Zuweisung mittels 'none' aufheben
|
||||
Not holding brush item.=Kein Pinsel in der Hand.
|
||||
Brush assignment cleared.=Pinsel-Zuweisung aufgehoben.
|
||||
@1 cannot be used with brushes=@1 kann nicht mit Pinseln verwendet werden
|
||||
invalid usage=Ungültige Verwendung
|
||||
Error with command: @1=Befehl ist fehlerhaft: @1
|
||||
Brush assigned to command: @1=Pinsel an Befehl gebunden: @1
|
3
worldedit_brush/mod.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
name = worldedit_brush
|
||||
description = WorldEdit brush
|
||||
depends = worldedit, worldedit_commands
|
BIN
worldedit_brush/textures/worldedit_brush.png
Normal file
After Width: | Height: | Size: 301 B |
59
worldedit_commands/code.lua
Normal file
|
@ -0,0 +1,59 @@
|
|||
local S = minetest.get_translator("worldedit_commands")
|
||||
|
||||
local function check_region(name)
|
||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||
end
|
||||
|
||||
|
||||
worldedit.register_command("lua", {
|
||||
params = "<code>",
|
||||
description = S("Executes <code> as a Lua chunk in the global namespace"),
|
||||
category = S("Code"),
|
||||
privs = {worldedit=true, server=true},
|
||||
parse = function(param)
|
||||
if param == "" then
|
||||
return false
|
||||
end
|
||||
return true, param
|
||||
end,
|
||||
func = function(name, param)
|
||||
-- shorthand like in the Lua interpreter
|
||||
if param:sub(1, 1) == "=" then
|
||||
param = "return " .. param:sub(2)
|
||||
end
|
||||
local err, ret = worldedit.lua(param, name)
|
||||
if err == nil then
|
||||
minetest.log("action", name .. " executed " .. param)
|
||||
if ret ~= "nil" then
|
||||
worldedit.player_notify(name, "code successfully executed, returned " .. ret, "info")
|
||||
else
|
||||
worldedit.player_notify(name, "code successfully executed", "ok")
|
||||
end
|
||||
else
|
||||
minetest.log("action", name .. " tried to execute " .. param)
|
||||
worldedit.player_notify(name, "code error: " .. err, "error")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("luatransform", {
|
||||
params = "<code>",
|
||||
description = S("Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region"),
|
||||
category = S("Code"),
|
||||
privs = {worldedit=true, server=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
return true, param
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, param)
|
||||
local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param)
|
||||
if err then
|
||||
worldedit.player_notify(name, "code error: " .. err, "error")
|
||||
minetest.log("action", name.." tried to execute luatransform "..param)
|
||||
else
|
||||
worldedit.player_notify(name, "code successfully executed", "ok")
|
||||
minetest.log("action", name.." executed luatransform "..param)
|
||||
end
|
||||
end,
|
||||
})
|
273
worldedit_commands/cuboid.lua
Normal file
|
@ -0,0 +1,273 @@
|
|||
local S = minetest.get_translator("worldedit_commands")
|
||||
|
||||
worldedit.register_command("outset", {
|
||||
params = "[h/v] <amount>",
|
||||
description = S("Outset the selected region."),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
|
||||
if find == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
local hv_test = dir:find("[^hv]+")
|
||||
if hv_test ~= nil then
|
||||
return false, S("Invalid direction: @1", dir)
|
||||
end
|
||||
|
||||
return true, dir, tonumber(amount)
|
||||
end,
|
||||
func = function(name, dir, amount)
|
||||
if dir == "" or dir == "hv" or dir == "vh" then
|
||||
assert(worldedit.cuboid_volumetric_expand(name, amount))
|
||||
elseif dir == "h" then
|
||||
assert(worldedit.cuboid_linear_expand(name, 'x', 1, amount))
|
||||
assert(worldedit.cuboid_linear_expand(name, 'x', -1, amount))
|
||||
assert(worldedit.cuboid_linear_expand(name, 'z', 1, amount))
|
||||
assert(worldedit.cuboid_linear_expand(name, 'z', -1, amount))
|
||||
elseif dir == "v" then
|
||||
assert(worldedit.cuboid_linear_expand(name, 'y', 1, amount))
|
||||
assert(worldedit.cuboid_linear_expand(name, 'y', -1, amount))
|
||||
else
|
||||
return false, S("Invalid number of arguments")
|
||||
end
|
||||
|
||||
worldedit.marker_update(name)
|
||||
return true, S("Region outset by @1 nodes", amount)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
worldedit.register_command("inset", {
|
||||
params = "[h/v] <amount>",
|
||||
description = S("Inset the selected region."),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
|
||||
if find == nil then
|
||||
return false
|
||||
end
|
||||
if dir:find("[^hv]") ~= nil then
|
||||
return false, S("Invalid direction: @1", dir)
|
||||
end
|
||||
|
||||
return true, dir, tonumber(amount)
|
||||
end,
|
||||
func = function(name, dir, amount)
|
||||
if dir == "" or dir == "vh" or dir == "hv" then
|
||||
assert(worldedit.cuboid_volumetric_expand(name, -amount))
|
||||
elseif dir == "h" then
|
||||
assert(worldedit.cuboid_linear_expand(name, 'x', 1, -amount))
|
||||
assert(worldedit.cuboid_linear_expand(name, 'x', -1, -amount))
|
||||
assert(worldedit.cuboid_linear_expand(name, 'z', 1, -amount))
|
||||
assert(worldedit.cuboid_linear_expand(name, 'z', -1, -amount))
|
||||
elseif dir == "v" then
|
||||
assert(worldedit.cuboid_linear_expand(name, 'y', 1, -amount))
|
||||
assert(worldedit.cuboid_linear_expand(name, 'y', -1, -amount))
|
||||
else
|
||||
return false, S("Invalid number of arguments")
|
||||
end
|
||||
|
||||
worldedit.marker_update(name)
|
||||
return true, S("Region inset by @1 nodes", amount)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
worldedit.register_command("shift", {
|
||||
params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>",
|
||||
description = S("Shifts the selection area without moving its contents"),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local find, _, direction, amount = param:find("([%?%l]+)%s*([+-]?%d+)")
|
||||
if find == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
return true, direction, tonumber(amount)
|
||||
end,
|
||||
func = function(name, direction, amount)
|
||||
local axis, dir
|
||||
if direction == "x" or direction == "y" or direction == "z" then
|
||||
axis, dir = direction, 1
|
||||
elseif direction == "?" then
|
||||
axis, dir = worldedit.player_axis(name)
|
||||
else
|
||||
axis, dir = worldedit.translate_direction(name, direction)
|
||||
end
|
||||
|
||||
if axis == nil or dir == nil then
|
||||
return false, S("Invalid if looking straight up or down")
|
||||
end
|
||||
|
||||
assert(worldedit.cuboid_shift(name, axis, amount * dir))
|
||||
worldedit.marker_update(name)
|
||||
|
||||
return true, S("Region shifted by @1 nodes", amount)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
worldedit.register_command("expand", {
|
||||
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
|
||||
description = S("Expands the selection in the selected absolute or relative axis"),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local find, _, sign, direction, amount,
|
||||
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)")
|
||||
if find == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
if rev_amount == "" then
|
||||
rev_amount = "0"
|
||||
end
|
||||
|
||||
return true, sign, direction, tonumber(amount), tonumber(rev_amount)
|
||||
end,
|
||||
func = function(name, sign, direction, amount, rev_amount)
|
||||
local absolute = direction:find("[xyz?]")
|
||||
local dir, axis
|
||||
|
||||
if absolute == nil then
|
||||
axis, dir = worldedit.translate_direction(name, direction)
|
||||
|
||||
if axis == nil or dir == nil then
|
||||
return false, S("Invalid if looking straight up or down")
|
||||
end
|
||||
else
|
||||
if direction == "?" then
|
||||
axis, dir = worldedit.player_axis(name)
|
||||
else
|
||||
axis = direction
|
||||
dir = 1
|
||||
end
|
||||
end
|
||||
|
||||
if sign == "-" then
|
||||
dir = -dir
|
||||
end
|
||||
|
||||
worldedit.cuboid_linear_expand(name, axis, dir, amount)
|
||||
worldedit.cuboid_linear_expand(name, axis, -dir, rev_amount)
|
||||
worldedit.marker_update(name)
|
||||
return true, S("Region expanded by @1 nodes", amount + rev_amount)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
worldedit.register_command("contract", {
|
||||
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
|
||||
description = S("Contracts the selection in the selected absolute or relative axis"),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local find, _, sign, direction, amount,
|
||||
rev_amount = param:find("([+-]?)([%?%l]+)%s*(%d+)%s*(%d*)")
|
||||
if find == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
if rev_amount == "" then
|
||||
rev_amount = "0"
|
||||
end
|
||||
|
||||
return true, sign, direction, tonumber(amount), tonumber(rev_amount)
|
||||
end,
|
||||
func = function(name, sign, direction, amount, rev_amount)
|
||||
local absolute = direction:find("[xyz?]")
|
||||
local dir, axis
|
||||
|
||||
if absolute == nil then
|
||||
axis, dir = worldedit.translate_direction(name, direction)
|
||||
|
||||
if axis == nil or dir == nil then
|
||||
return false, S("Invalid if looking straight up or down")
|
||||
end
|
||||
else
|
||||
if direction == "?" then
|
||||
axis, dir = worldedit.player_axis(name)
|
||||
else
|
||||
axis = direction
|
||||
dir = 1
|
||||
end
|
||||
end
|
||||
|
||||
if sign == "-" then
|
||||
dir = -dir
|
||||
end
|
||||
|
||||
worldedit.cuboid_linear_expand(name, axis, dir, -amount)
|
||||
worldedit.cuboid_linear_expand(name, axis, -dir, -rev_amount)
|
||||
worldedit.marker_update(name)
|
||||
return true, S("Region contracted by @1 nodes", amount + rev_amount)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("cubeapply", {
|
||||
params = "<size>/(<sizex> <sizey> <sizez>) <command> [parameters]",
|
||||
description = S("Select a cube with side length <size> around position 1 and run <command> on region"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = function(param)
|
||||
local found, _, sidex, sidey, sidez, cmd, args =
|
||||
param:find("^(%d+)%s+(%d+)%s+(%d+)%s+([^%s]+)%s*(.*)$")
|
||||
if found == nil then
|
||||
found, _, sidex, cmd, args = param:find("^(%d+)%s+([^%s]+)%s*(.*)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
sidey = sidex
|
||||
sidez = sidex
|
||||
end
|
||||
sidex = tonumber(sidex)
|
||||
sidey = tonumber(sidey)
|
||||
sidez = tonumber(sidez)
|
||||
if sidex < 1 or sidey < 1 or sidez < 1 then
|
||||
return false
|
||||
end
|
||||
local cmddef = worldedit.registered_commands[cmd]
|
||||
if cmddef == nil or cmddef.require_pos ~= 2 then
|
||||
return false, S("invalid usage: @1 cannot be used with cubeapply",
|
||||
minetest.colorize("#00ffff", "//"..cmd))
|
||||
end
|
||||
-- run parsing of target command
|
||||
local parsed = {cmddef.parse(args)}
|
||||
if not table.remove(parsed, 1) then
|
||||
return false, parsed[1]
|
||||
end
|
||||
return true, sidex, sidey, sidez, cmd, parsed
|
||||
end,
|
||||
nodes_needed = function(name, sidex, sidey, sidez, cmd, parsed)
|
||||
-- its not possible to defer to the target command at this point
|
||||
-- FIXME: why not?
|
||||
return sidex * sidey * sidez
|
||||
end,
|
||||
func = function(name, sidex, sidey, sidez, cmd, parsed)
|
||||
local cmddef = assert(worldedit.registered_commands[cmd])
|
||||
local success, missing_privs = minetest.check_player_privs(name, cmddef.privs)
|
||||
if not success then
|
||||
return false, S("Missing privileges: @1", table.concat(missing_privs, ", "))
|
||||
end
|
||||
|
||||
-- update region to be the cuboid the user wanted
|
||||
local half = vector.divide(vector.new(sidex, sidey, sidez), 2)
|
||||
local sizea, sizeb = vector.apply(half, math.floor), vector.apply(half, math.ceil)
|
||||
local center = worldedit.pos1[name]
|
||||
worldedit.pos1[name] = vector.subtract(center, sizea)
|
||||
worldedit.pos2[name] = vector.add(center, vector.subtract(sizeb, 1))
|
||||
worldedit.marker_update(name)
|
||||
|
||||
-- actually run target command
|
||||
return cmddef.func(name, unpack(parsed))
|
||||
end,
|
||||
})
|
|
@ -1 +0,0 @@
|
|||
worldedit
|
153
worldedit_commands/locale/template.txt
Normal file
|
@ -0,0 +1,153 @@
|
|||
# textdomain: worldedit_commands
|
||||
Outset the selected region.=
|
||||
Invalid direction: @1=
|
||||
Invalid number of arguments=
|
||||
Region outset by @1 nodes=
|
||||
Inset the selected region.=
|
||||
Region inset by @1 nodes=
|
||||
Shifts the selection area without moving its contents=
|
||||
Invalid if looking straight up or down=
|
||||
Region shifted by @1 nodes=
|
||||
Expands the selection in the selected absolute or relative axis=
|
||||
Region expanded by @1 nodes=
|
||||
Contracts the selection in the selected absolute or relative axis=
|
||||
Region contracted by @1 nodes=
|
||||
Select a cube with side length <size> around position 1 and run <command> on region=
|
||||
invalid usage: @1 cannot be used with cubeapply=
|
||||
Missing privileges: @1=
|
||||
Region operations=
|
||||
Can use WorldEdit commands=
|
||||
no region selected=
|
||||
no position 1 selected=
|
||||
invalid usage=
|
||||
ERROR: the operation was cancelled because the region has changed.=
|
||||
Could not open file "@1"=
|
||||
Invalid file format!=
|
||||
Schematic was created with a newer version of WorldEdit.=
|
||||
Get information about the WorldEdit mod=
|
||||
WorldEdit @1 is available on this server. Type @2 to get a list of commands, or find more information at @3=
|
||||
Get help for WorldEdit commands=
|
||||
alias to @1=
|
||||
You are not allowed to use any WorldEdit commands.=
|
||||
Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=
|
||||
Available commands:@n=
|
||||
Command not available: =
|
||||
Enable or disable node inspection=
|
||||
inspector: inspection enabled for @1, currently facing the @2 axis=
|
||||
inspector: inspection disabled=
|
||||
inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=
|
||||
Reset the region so that it is empty=
|
||||
region reset=
|
||||
Show markers at the region positions=
|
||||
region marked=
|
||||
Hide markers if currently shown=
|
||||
region unmarked=
|
||||
Set WorldEdit region position @1 to the player's location=
|
||||
position @1 set to @2=
|
||||
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region=
|
||||
unknown subcommand: @1=
|
||||
select positions by punching two nodes=
|
||||
select position @1 by punching a node=
|
||||
position @1: @2=
|
||||
position @1 not set=
|
||||
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=
|
||||
Display the volume of the current WorldEdit region=
|
||||
current region has a volume of @1 nodes (@2*@3*@4)=
|
||||
Remove all MapBlocks (16x16x16) containing the selected area from the map=
|
||||
Node manipulation=
|
||||
Area deleted.=
|
||||
There was an error during deletion of the area.=
|
||||
Set the current WorldEdit region to <node>=
|
||||
invalid node name: @1=
|
||||
@1 nodes set=
|
||||
Set param2 of all nodes in the current WorldEdit region to <param2>=
|
||||
Param2 is out of range (must be between 0 and 255 inclusive!)=
|
||||
@1 nodes altered=
|
||||
Fill the current WorldEdit region with a random mix of <node1>, ...=
|
||||
invalid search node name: @1=
|
||||
invalid replace node name: @1=
|
||||
Replace all instances of <search node> with <replace node> in the current WorldEdit region=
|
||||
@1 nodes replaced=
|
||||
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=
|
||||
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
|
||||
Shapes=
|
||||
@1 nodes added=
|
||||
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
|
||||
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
|
||||
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
|
||||
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
|
||||
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
|
||||
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
|
||||
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
|
||||
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
|
||||
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
|
||||
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=
|
||||
Copy the current WorldEdit region along the given axis by <amount> nodes=
|
||||
Transformations=
|
||||
@1 nodes copied=
|
||||
Move the current WorldEdit region along the given axis by <amount> nodes=
|
||||
@1 nodes moved=
|
||||
Stack the current WorldEdit region along the given axis <count> times=
|
||||
@1 nodes stacked=
|
||||
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=
|
||||
invalid count: @1=
|
||||
invalid increments: @1=
|
||||
Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=
|
||||
invalid scaling factors: @1=
|
||||
@1 nodes stretched=
|
||||
Transpose the current WorldEdit region along the given axes=
|
||||
invalid usage: axes must be different=
|
||||
@1 nodes transposed=
|
||||
Flip the current WorldEdit region along the given axis=
|
||||
@1 nodes flipped=
|
||||
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=
|
||||
invalid usage: angle must be multiple of 90=
|
||||
@1 nodes rotated=
|
||||
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=
|
||||
@1 nodes oriented=
|
||||
Fix the lighting in the current WorldEdit region=
|
||||
@1 nodes updated=
|
||||
Remove any fluid node within the current WorldEdit region=
|
||||
Remove any plant, tree or foliage-like nodes in the selected region=
|
||||
@1 nodes removed=
|
||||
Hide all nodes in the current WorldEdit region non-destructively=
|
||||
@1 nodes hidden=
|
||||
Suppress all <node> in the current WorldEdit region non-destructively=
|
||||
@1 nodes suppressed=
|
||||
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=
|
||||
@1 nodes highlighted=
|
||||
Restores nodes hidden with WorldEdit in the current WorldEdit region=
|
||||
@1 nodes restored=
|
||||
Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=
|
||||
Save the current WorldEdit region to "(world folder)/schems/<file>.we"=
|
||||
Schematics=
|
||||
Disallowed file name: @1=
|
||||
Could not save file to "@1"=
|
||||
@1 nodes saved=
|
||||
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=
|
||||
Schematic empty, nothing allocated=
|
||||
@1 nodes allocated=
|
||||
Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=
|
||||
Loading failed!=
|
||||
@1 nodes loaded=
|
||||
Executes <code> as a Lua chunk in the global namespace=
|
||||
Code=
|
||||
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=
|
||||
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=
|
||||
Failed to create Minetest schematic=
|
||||
Saved Minetest schematic to @1=
|
||||
Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=
|
||||
failed to place Minetest schematic=
|
||||
placed Minetest schematic @1 at @2=
|
||||
Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=
|
||||
select Minetest schematic probability values by punching nodes=
|
||||
finished Minetest schematic probability selection=
|
||||
currently set node probabilities:=
|
||||
invalid node probability given, not saved=
|
||||
Clears all objects within the WorldEdit region=
|
||||
@1 objects cleared=
|
||||
WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=
|
||||
Confirm a pending operation=
|
||||
no operation pending=
|
||||
Abort a pending operation=
|
||||
WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=
|
164
worldedit_commands/locale/worldedit_commands.de.tr
Normal file
|
@ -0,0 +1,164 @@
|
|||
# textdomain: worldedit_commands
|
||||
##
|
||||
# Übersetzungshinweise:
|
||||
# node = Block
|
||||
# command = Befehl
|
||||
# region = Gebiet
|
||||
# position 1/2 = Position 1/2
|
||||
# schematic = Schematic
|
||||
# Der Anwender wird gesiezt.
|
||||
# Diese Datei auf Rechtschreibfehler prüfen (unter Linux):
|
||||
# sed -rne 's|^.*[^@]=(.*)$|\1|gp' worldedit_commands/locale/worldedit_commands.de.tr | sed -re 's/\b(WorldEdit|Schematic|Minetest)\b/Beispiel/g' | LANG=de_DE.UTF-8 hunspell -L
|
||||
##
|
||||
Outset the selected region.=Ausgewähltes Gebiet vergrößern.
|
||||
Invalid direction: @1=Ungültige Richtung: @1
|
||||
Invalid number of arguments=Ungültige Anzahl der Argumente
|
||||
Region outset by @1 nodes=Gebiet um @1 Blöcke vergrößert
|
||||
Inset the selected region.=Ausgewähltes Gebiet verkleinern.
|
||||
Region inset by @1 nodes=Gebiet um @1 Blöcke verkleinert
|
||||
Shifts the selection area without moving its contents=Verschiebt das ausgewählte Gebiet ohne dessen Inhalt
|
||||
Invalid if looking straight up or down=Nicht zulässig bei gerade Blickrichtung nach unten oder oben
|
||||
Region shifted by @1 nodes=Gebiet um @1 Blöcke verschoben
|
||||
Expands the selection in the selected absolute or relative axis=Erweitert das Gebiet in Richtung einer absoluten oder relativen Achse
|
||||
Region expanded by @1 nodes=Gebiet um @1 Blöcke erweitert
|
||||
Contracts the selection in the selected absolute or relative axis=Schrumpft das Gebiet in Richtung einer absoluten oder relativen Achse
|
||||
Region contracted by @1 nodes=Gebiet um @1 Blöcke geschrumpft
|
||||
Select a cube with side length <size> around position 1 and run <command> on region=Wähle einen Würfel mit Seitenlänge <size> um Position 1 und führe <command> auf diesem Gebiet aus
|
||||
invalid usage: @1 cannot be used with cubeapply=Ungültige Verwendung: @1 kann nicht mit cubeapply verwendet werden
|
||||
Missing privileges: @1=Fehlende Privilegien: @1
|
||||
Region operations=Gebietsoperationen
|
||||
Can use WorldEdit commands=Kann WorldEdit-Befehle benutzen
|
||||
no region selected=Kein Gebiet ausgewählt
|
||||
no position 1 selected=Keine Position 1 ausgewählt
|
||||
invalid usage=Ungültige Verwendung
|
||||
ERROR: the operation was cancelled because the region has changed.=FEHLER: Der Vorgang wurde abgebrochen, weil das Gebiet verändert wurde.
|
||||
Could not open file "@1"=Konnte die Datei „@1“ nicht öffnen
|
||||
Invalid file format!=Ungültiges Dateiformat!
|
||||
Schematic was created with a newer version of WorldEdit.=Schematic wurde mit einer neueren Version von WorldEdit erstellt.
|
||||
Get information about the WorldEdit mod=Informationen über den WorldEdit-Mod erhalten.
|
||||
WorldEdit @1 is available on this server. Type @2 to get a list of commands, or find more information at @3=WorldEdit @1 ist auf diesem Server verfügbar. Nutzen Sie @2, um eine Liste der Befehle zu erhalten, oder finden Sie weitere Informationen unter @3
|
||||
Get help for WorldEdit commands=Hilfe für WorldEdit-Befehle erhalten
|
||||
alias to @1=Alias für @1
|
||||
You are not allowed to use any WorldEdit commands.=Ihnen ist nicht erlaubt WorldEdit-Befehle zu nutzen.
|
||||
Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=Verfügbare Befehle: @1@n„@2“ benutzen, um mehr Informationen zu erhalten, oder „@3“, um alles aufzulisten.
|
||||
Available commands:@n=Verfügbare Befehle:@n
|
||||
Command not available: =Befehl nicht verfügbar:
|
||||
Enable or disable node inspection=Inspizieren von Blöcken ein- oder ausschalten
|
||||
inspector: inspection enabled for @1, currently facing the @2 axis=Inspektur: für @1 aktiviert, aktuell der @2-Achse zugewandt
|
||||
inspector: inspection disabled=Inspektur: deaktiviert
|
||||
inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=Inspektur: @1 bei @2 (param1@=@3, param2@=@4, einfallendes Licht@=@5), der „@6“-Achse zugewandt, gehauen
|
||||
Reset the region so that it is empty=Gebiet zurücksetzen, sodass es leer ist
|
||||
region reset=Gebiet zurückgesetzt
|
||||
Show markers at the region positions=Markierungen bei den Gebietsposition anzeigen
|
||||
region marked=Gebiet markiert
|
||||
Hide markers if currently shown=Markierungen verstecken falls derzeit sichtbar
|
||||
region unmarked=Gebiet nicht mehr markiert
|
||||
Set WorldEdit region position @1 to the player's location=Position @1 des WorldEdit-Gebiets auf die Position des Spieler setzen
|
||||
position @1 set to @2=Position @1 auf @2 gesetzt
|
||||
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region=
|
||||
unknown subcommand: @1=Unbekannter Unterbefehl: @1
|
||||
select positions by punching two nodes=Wählen Sie die Position durch Hauen zweier Blöcke
|
||||
select position @1 by punching a node=Wählen Sie Position @1 durch Hauen eines Blocks
|
||||
position @1: @2=Position @1: @2
|
||||
position @1 not set=Position @1 ist nicht gesetzt
|
||||
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=Position des WorldEdit-Gebiets auf (<x>, <y>, <z>) setzen
|
||||
Display the volume of the current WorldEdit region=Volumen des aktuellen WorldEdit-Gebiets anzeigen
|
||||
current region has a volume of @1 nodes (@2*@3*@4)=Das aktuelle Gebiet hat ein Volumen von @1 Blöcken (@2*@3*@4)
|
||||
Remove all MapBlocks (16x16x16) containing the selected area from the map=Alle Kartenblöcke (16x16x16) entfernen, die das Gebiet enthalten.
|
||||
Node manipulation=Block-Manipulation
|
||||
Area deleted.=Gebiet gelöscht.
|
||||
There was an error during deletion of the area.=Während des Löschens des Gebiets trat ein Fehler aus.
|
||||
Set the current WorldEdit region to <node>=Das aktuelle WorldEdit-Gebiet mit <node> füllen
|
||||
invalid node name: @1=Ungültiger Blockname: @1
|
||||
@1 nodes set=@1 Blöcke gesetzt
|
||||
Set param2 of all nodes in the current WorldEdit region to <param2>=Den param2 aller Blocke im aktuellen WorldEdit-Gebiet auf <param2> setzen
|
||||
Param2 is out of range (must be between 0 and 255 inclusive!)=Param2 muss zwischen 0 und 255 (inklusiv) sein
|
||||
@1 nodes altered=@1 Blöcke verändert
|
||||
Fill the current WorldEdit region with a random mix of <node1>, ...=Das aktuelle WorldEdit-Gebiet mit einer zufälligen Mischung aus <node1>, ... füllen
|
||||
invalid search node name: @1=Ungültiger Blockname für die Suche: @1
|
||||
invalid replace node name: @1=Ungültiger Blockname für das Ersetzen: @1
|
||||
Replace all instances of <search node> with <replace node> in the current WorldEdit region=Alle Vorkommen von <search node> im aktuellen WorldEdit-Gebiet mit <replace node> ersetzen
|
||||
@1 nodes replaced=@1 Blöcke ersetzt
|
||||
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=Alle Blöcke außer <search node> im aktuellen WorldEdit-Gebiet mit <replace node> ersetzen.
|
||||
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
|
||||
Shapes=Formen
|
||||
@1 nodes added=@1 Blöcke hinzugefügt
|
||||
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=
|
||||
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
|
||||
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=
|
||||
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
|
||||
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=
|
||||
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
|
||||
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=
|
||||
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
|
||||
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=
|
||||
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=
|
||||
Copy the current WorldEdit region along the given axis by <amount> nodes=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse um <amount> Blöcke kopieren
|
||||
Transformations=Transformationen
|
||||
@1 nodes copied=@1 Blöcke kopiert
|
||||
Move the current WorldEdit region along the given axis by <amount> nodes=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse um <amount> Blöcke verschieben
|
||||
@1 nodes moved=@1 Blöcke verschoben
|
||||
Stack the current WorldEdit region along the given axis <count> times=Das aktuelle WorldEdit-Gebiet entlang der gegebenen Achse <count>-mal stapeln
|
||||
@1 nodes stacked=@1 Blöcke gestapelt
|
||||
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Das aktuelle WorldEdit-Gebiet <count>-mal um <x>, <y>, <z> versetzt stapeln
|
||||
invalid count: @1=Ungültige Anzahl: @1
|
||||
invalid increments: @1=Ungültige Schrittweite: @1
|
||||
Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=
|
||||
invalid scaling factors: @1=Ungültige Skalierungsfaktoren: @1
|
||||
@1 nodes stretched=@1 Blöcke gestreckt
|
||||
Transpose the current WorldEdit region along the given axes=
|
||||
invalid usage: axes must be different=Ungültige Verwendung: Achsen müssen verschieden sein
|
||||
@1 nodes transposed=
|
||||
Flip the current WorldEdit region along the given axis=
|
||||
@1 nodes flipped=
|
||||
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=
|
||||
invalid usage: angle must be multiple of 90=Ungültige Verwendung: Winkel muss ein Vielfaches von 90 sein
|
||||
@1 nodes rotated=@1 Blöcke gedreht
|
||||
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=
|
||||
@1 nodes oriented=
|
||||
Fix the lighting in the current WorldEdit region=Belichtung im aktuellen WorldEdit-Gebiet korrigieren
|
||||
@1 nodes updated=@1 Blöcke verändert
|
||||
Remove any fluid node within the current WorldEdit region=Alle flüssigen Blöcke im WorldEdit-Gebiet entfernen
|
||||
Remove any plant, tree or foliage-like nodes in the selected region=Alle Pflanzen, Baum oder Laub-ähnliche Blöcke im WorldEdit-Gebiet entfernen
|
||||
@1 nodes removed=@1 Blöcke entfernt
|
||||
Hide all nodes in the current WorldEdit region non-destructively=Alle Blöcke im WorldEdit-Gebiet zerstörungsfrei verstecken
|
||||
@1 nodes hidden=@1 Blöcke versteckt
|
||||
Suppress all <node> in the current WorldEdit region non-destructively=
|
||||
@1 nodes suppressed=
|
||||
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Alle <node> Blöcke im WorldEdit-Gebiet hervorheben, indem alle anderen zerstörungsfrei versteckt werden
|
||||
@1 nodes highlighted=@1 Blöcke hervorgehoben
|
||||
Restores nodes hidden with WorldEdit in the current WorldEdit region=Versteckte Blöcke im WorldEdit-Gebiet wiederherstellen.
|
||||
@1 nodes restored=@1 Blöcke wiederhergestellt
|
||||
Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=Warnung: Das Schematic hat überschüssigen freien Platz und WIRD nach dem Laden falsch ausgerichtet sein. Um dies zu vermeiden, verkleinern Sie das Gebiet sodass es die zu speichernden Blöcke exakt umfasst.
|
||||
Save the current WorldEdit region to "(world folder)/schems/<file>.we"=Das aktuelle WorldEdit-Gebiet in "(Weltordner)/schems/<file>.we" speichern
|
||||
Schematics=Schematic
|
||||
Disallowed file name: @1=Nicht erlaubter Dateiname: @1
|
||||
Could not save file to "@1"=Konnte die Datei nicht unter "@1" speichern
|
||||
@1 nodes saved=@1 Blöcke gespeichert
|
||||
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=Das aktuelle WorldEdit-Gebiet anhand der Blöcke in "(Weltordner)/schems/<file>.we" setzen
|
||||
Schematic empty, nothing allocated=Schematic leer, nichts reserviert
|
||||
@1 nodes allocated=@1 Blöcke reserviert
|
||||
Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=Blöcke aus "(Weltordner)/schems/<file>[.we[m]]" mit Position 1 als Ausgangspunkt laden
|
||||
Loading failed!=Laden fehlgeschlagen!
|
||||
@1 nodes loaded=@1 Blöcke geladen
|
||||
Executes <code> as a Lua chunk in the global namespace=
|
||||
Code=Code
|
||||
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=
|
||||
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=Das aktuelle WorldEdit-Gebiet als Minetest-Schematic in "(Weltordner)/schems/<filename>.mts" speichern
|
||||
Failed to create Minetest schematic=Konnte Minetest-Schematic nicht erstellen
|
||||
Saved Minetest schematic to @1=Minetest-Schematic in @1 gespeichert
|
||||
Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=Blöcke aus "(Weltordner)/schems/<file>.mts" mit Position 1 als Ausgangspunkt laden
|
||||
failed to place Minetest schematic=Konnte Minetest-Schematic nicht platzieren
|
||||
placed Minetest schematic @1 at @2=Minetest-Schematic @1 bei @2 platziert
|
||||
Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=
|
||||
select Minetest schematic probability values by punching nodes=
|
||||
finished Minetest schematic probability selection=
|
||||
currently set node probabilities:=
|
||||
invalid node probability given, not saved=
|
||||
Clears all objects within the WorldEdit region=Löscht alle Objekte innerhalb des WorldEdit-Gebiets
|
||||
@1 objects cleared=@1 Objekte gelöscht
|
||||
WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=WARNUNG: Dieser Vorgang könnte bis zu @1 Blöcke betreffen; @2 zum Fortfahren oder @3 zum Abbrechen
|
||||
Confirm a pending operation=Einen ausstehenden Vorgang bestätigen
|
||||
no operation pending=Kein Vorgang ausstehend
|
||||
Abort a pending operation=Einen ausstehenden Vorgang abbrechen
|
||||
WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=WorldEdit Zauberstab@nSetzen der 1. Position mit Linksklick, der 2. mit Rechtsklick
|
155
worldedit_commands/locale/worldedit_commands.ru.tr
Normal file
|
@ -0,0 +1,155 @@
|
|||
# textdomain: worldedit_commands
|
||||
|
||||
### init.lua ###
|
||||
Can use WorldEdit commands=Возможность редактировать мир с помощью команд WorldEdit
|
||||
|
||||
no region selected=не выделен регион
|
||||
no position 1 selected=не установлена позиция региона 1
|
||||
invalid usage=не верное использование команды
|
||||
Could not open file "@1"=Не удаётся открыть файл "@1"
|
||||
Invalid file format!=Не верный формат файла!
|
||||
Schematic was created with a newer version of WorldEdit.=Схема была создана с использованием более новой версии WorldEdit.
|
||||
|
||||
Get information about the WorldEdit mod=Вывести информацию о WorldEdit
|
||||
WorldEdit @1 is available on this server. Type @2 to get a list of commands, or get more information at @3=WorldEdit @1 доступен на этом сервере. Наберите команду @2 чтобы увидеть список команд, больше информации по ссылке @3
|
||||
Get help for WorldEdit commands=Вывести информацию об использовании команд WorldEdit
|
||||
You are not allowed to use any WorldEdit commands.=У вас нет привилегий, чтобы использовать команды WorldEdit.
|
||||
Available commands: @1@nUse '@2' to get more information, or '@3' to list everything.=Доступные команды: @1@nИспользуйте '@2' для получения информации по команде или '@3' для вывода подсказок по всем командам.
|
||||
Available commands:@n=Доступные команды:@n
|
||||
Command not available: =Команда не найдена:
|
||||
Enable or disable node inspection=Включить/отключить инспекцию блоков
|
||||
inspector: inspection enabled for @1, currently facing the @2 axis=inspector: инспекция включена для @1, текущий взор в направлении оси @2
|
||||
inspector: inspection disabled=inspector: инспекция отключена
|
||||
inspector: @1 at @2 (param1@=@3, param2@=@4, received light@=@5) punched facing the @6 axis=inspector: @1 в @2 (param1@=@3, param2@=@4, received light@=@5) ударен по поверхности @6
|
||||
Reset the region so that it is empty=Сбросить выделение области
|
||||
region reset=регион сброшен
|
||||
Show markers at the region positions=Отобразить маркеры выделенной области
|
||||
region marked=маркеры отображены
|
||||
Hide markers if currently shown=Скрыть маркеры выделенной области
|
||||
region unmarked=маркеры скрыты
|
||||
Set WorldEdit region position @1 to the player's location=Установить маркер @1 для WorldEdit-региона в месте нахождения игрока
|
||||
Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region=Выделить WorldEdit-регион или установить маркеры для WorldEdit-региона, либо отобразить уже выбранную область
|
||||
unknown subcommand: @1=неизвестная подкоманда: @1
|
||||
select positions by punching two nodes=выберите позиции, ударив по блокам
|
||||
select position @1 by punching a node=выберите позицию @1, ударив по блоку
|
||||
position @1: @2=позиция @1: @2
|
||||
position @1 not set=позиция @1 не установлена
|
||||
Set a WorldEdit region position to the position at (<x>, <y>, <z>)=Установить маркер для WorldEdit в позиции (<x>, <y>, <z>)
|
||||
position @1 set to @2=позиция @1 установлена в @2
|
||||
Display the volume of the current WorldEdit region=Вывести информацию об объёме текущей выделенной области WorldEdit (кол-во нод, размеры)
|
||||
current region has a volume of @1 nodes (@2*@3*@4)=текущий регион имеет объем @1 нод (@2*@3*@4)
|
||||
Remove all MapBlocks (16x16x16) containing the selected area from the map=Удалить MapBlocks (16x16x16), содержащие выбранную область
|
||||
Area deleted.=Область удалена.
|
||||
There was an error during deletion of the area.=Что-то пошло не так при удалении области.
|
||||
Set the current WorldEdit region to <node>=Заполнить выбранный WorldEdit-регион указанным типом блоков
|
||||
invalid node name: @1=неверное название блока: @1
|
||||
@1 nodes set=@1 блок(а/ов) установленно
|
||||
Set param2 of all nodes in the current WorldEdit region to <param2>=Проставить param2 для всех блоков в текущем WorldEdit-регионе
|
||||
Param2 is out of range (must be between 0 and 255 inclusive!)=Значение param2 должно быть от 0 до 255 (включительно!)
|
||||
@1 nodes altered=изменено @1 блок(а/ов)
|
||||
Fill the current WorldEdit region with a random mix of <node1>, ...=Заполнить выбранный WorldEdit-регион смесью указанных типов блоков
|
||||
invalid search node name: @1=неверное название блока-поиска: @1
|
||||
invalid replace node name: @1=неверное название блока-замены: @1
|
||||
Replace all instances of <search node> with <replace node> in the current WorldEdit region=Заменить все блоки <search node> на <replace node> в выбранной WorldEdit-области
|
||||
@1 nodes replaced=заменено @1 нод(а/ы)
|
||||
Replace all nodes other than <search node> with <replace node> in the current WorldEdit region=Заменить все блоки, кроме <search node>, на <replace node> в выбранной WorldEdit-области
|
||||
Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Установить полый куб с центром нижней грани в позиции 1 и указанными размерами, состоящий из блоков <node>.
|
||||
@1 nodes added=добавлен(о) @1 блок(а/ов)
|
||||
Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>.=Установить куб с центром нижней грани в позиции 1 и указанными размерами, состоящий из блоков <node>.
|
||||
Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить полую сферу с центром в WorldEdit-позиции 1 радиусом <radius>, состоящую из блоков <node>
|
||||
Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить сферу с центром в WorldEdit-позиции 1 радиусом <radius>, состоящую из блоков <node>
|
||||
Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить полый купол с центром в WorldEdit-позиции 1 радиусом <radius>, состоящий из блоков <node>
|
||||
Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>=Установить купол с центром в WorldEdit-позиции 1 радиусом <radius>, состоящий из блоков <node>
|
||||
Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Установить полый цилиндр вдоль указанной оси, с центром в позиции 1, высотой/длинной <length>, с радиусом основания <radius> (и радиусом вершины [radius 2]), состоящий из блоков <node>
|
||||
Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>=Установить цилиндр вдоль указанной оси, с центром в позиции 1, высотой/длинной <length>, с радиусом основания <radius> (и радиусом вершины [radius 2]), состоящий из блоков <node>
|
||||
Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Установить полую пирамиду вдоль указанной оси, с центром в WorldEdit-позиции 1 высотой <height>, состоящую из блоков <node>
|
||||
Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>=Установить пирамиду вдоль указанной оси, с центром в WorldEdit-позиции 1 высотой <height>, состоящую из блоков <node>
|
||||
Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>=Установить спираль с центром в WorldEdit-позиции 1 шириной <length>, высотой <height> и с расстоянием между витками <space>, состоящую из блоков <node>
|
||||
Copy the current WorldEdit region along the given axis by <amount> nodes=Копировать текущий WorldEdit-регион со смещением вдоль указанной оси (x/y/z) на <amount> блоков
|
||||
@1 nodes copied=скопировано @1 нод(а/ы)
|
||||
Move the current WorldEdit region along the given axis by <amount> nodes=Переместить текущий WorldEdit-регион вдоль указанной оси (x/y/z) на <amount> блоков
|
||||
@1 nodes moved=перемещено @1 нод(а/ы)
|
||||
Stack the current WorldEdit region along the given axis <count> times=Размножить текущий WorldEdit-регион вдоль указанной оси <count> раз
|
||||
@1 nodes stacked=размножено @1 нод(а/ы)
|
||||
Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>=Размножить текущий WorldEdit-регион <count> раз с шагом <x>, <y>, <z> по соответствующим осям
|
||||
invalid count: @1=неверное количество: @1
|
||||
invalid increments: @1=неверные приращения(шаг)
|
||||
Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin=Масштабировать текущий WorldEdit-регион с коэффициентами <stretchx>, <stretchy>, <stretchz> вдоль осей X, Y и Z, используя WorldEdit-позицию 1 в качестве точки отсчёта
|
||||
invalid scaling factors: @1=неверные коэффициенты масштабирования: @1
|
||||
@1 nodes stretched=масштабировано @1 нод(а/ы)
|
||||
Transpose the current WorldEdit region along the given axes=Транспонировать текущий WorldEdit-регион по заданным осям.
|
||||
invalid usage: axes must be different=недопустимое использование: оси должны быть разными
|
||||
@1 nodes transposed=транспонировано @1 нод(а/ы)
|
||||
Flip the current WorldEdit region along the given axis=Перевернуть/Отразить текущий WorldEdit-регион вдоль указанной оси
|
||||
@1 nodes flipped=отражено @1 нод(а/ы)
|
||||
Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)=Повернуть текущий WorldEdit-регион вокруг оси <axis> на угол <angle> (шаг - 90 градусов)
|
||||
invalid usage: angle must be multiple of 90=недопустимое использование: угол должен быть кратен 90
|
||||
@1 nodes rotated=повёрнуто @1 нод(а/ы)
|
||||
Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)=Повернуть блоки в текущем WorldEdit-регионе вокруг оси Y на угол <angle> (шаг - 90 градусов)
|
||||
@1 nodes oriented=повёрнуто @1 нод(а/ы)
|
||||
Fix the lighting in the current WorldEdit region=Исправить освещение в текущем WorldEdit-регионе
|
||||
@1 nodes updated=обновлено @1 нод(а/ы)
|
||||
Remove any fluid node within the current WorldEdit region=Удалить любые жидкости в текущем WorldEdit-регионе
|
||||
Remove any plant, tree or foliage-like nodes in the selected region=Удалить любые растения, деревья или листье-подобные ноды в текущем WorldEdit-регионе
|
||||
@1 nodes removed=удалено @1 нод(а/ы)
|
||||
Hide all nodes in the current WorldEdit region non-destructively=Скрыть узлы текущего WorldEdit-региона, не удаляя их
|
||||
@1 nodes hidden=скрыто @1 нод(а/ы)
|
||||
Suppress all <node> in the current WorldEdit region non-destructively=Скрыть все блоки <node> в текущем WorldEdit-регионе, не удаляя их
|
||||
@1 nodes suppressed=скрыто @1 нод(а/ы)
|
||||
Highlight <node> in the current WorldEdit region by hiding everything else non-destructively=Скрыть все блоки, кроме <node>, в текущем WorldEdit-регионе, не удаляя их
|
||||
@1 nodes highlighted="подсвечено" @1 нод(а/ы)
|
||||
Restores nodes hidden with WorldEdit in the current WorldEdit region=Восстановить скрытые WorldEdit'ом узлы в текущем WorldEdit-регионе
|
||||
@1 nodes restored=восстановлено @1 нод(а/ы)
|
||||
Warning: The schematic contains excessive free space and WILL be misaligned when allocated or loaded. To avoid this, shrink your area to cover exactly the nodes to be saved.=Предупреждение: Схема содержит слишком много свободного места и будет смещена при размещении или загрузке. Чтобы избежать этого, уменьшите область так, чтобы она охватывала именно те узлы, которые необходимо сохранить.
|
||||
Save the current WorldEdit region to "(world folder)/schems/<file>.we"=Сохранить текущий WorldEdit-регион в файл "(world folder)/schems/<file>.we"
|
||||
Disallowed file name: @1=Недопустимое имя файла: @1
|
||||
Could not save file to "@1"=Не удалось сохранить файл в "@1"
|
||||
@1 nodes saved=сохранено @1 нод(а/ы)
|
||||
Set the region defined by nodes from "(world folder)/schems/<file>.we" as the current WorldEdit region=Выделить область, определённую узлами из "(world folder)/schems/<file>.we", как текущий WorldEdit-регион
|
||||
Schematic empty, nothing allocated=Схема пуста, ничего не выделено
|
||||
@1 nodes allocated=выделено @1 нод(а/ы)
|
||||
Load nodes from "(world folder)/schems/<file>[.we[m]]" with position 1 of the current WorldEdit region as the origin=Загрузить регион из "(world folder)/schems/<file>[.we[m]]" с WorldEdit-позицией 1 в качестве точки отсчёта
|
||||
Loading failed!=Не удалось загрузить!
|
||||
@1 nodes loaded=загружено @1 нод(а/ы)
|
||||
Executes <code> as a Lua chunk in the global namespace=Выполнить <code> как Lua-код в глобальном пространстве имён
|
||||
Executes <code> as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region=Выполнить <code> как Lua-код в глобальном пространстве имён, с доступом к переменным позиций для каждого блока в текущем WordEdit-регионе
|
||||
Save the current WorldEdit region using the Minetest Schematic format to "(world folder)/schems/<filename>.mts"=Сохранить текущий WorldEdit-регион с использованием сжатия в формат Minetest Schematic в файл "(world folder)/schems/<filename>.mts"
|
||||
Failed to create Minetest schematic=Не удалось создать Minetest-схему
|
||||
Saved Minetest schematic to @1=Minetest-схема сохранена в @1
|
||||
Load nodes from "(world folder)/schems/<file>.mts" with position 1 of the current WorldEdit region as the origin=Загрузить блоки из "(world folder)/schems/<file>.mts" с WorldEdit-позицией 1 в качестве точки отсчёта
|
||||
failed to place Minetest schematic=не удалось загрузить Minetest-схему
|
||||
placed Minetest schematic @1 at @2=Minetest-схема @1 загружена в @2
|
||||
Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry=Начать запись вероятностей для Minetest Schematic, ударяя по блокам, закончить запись вероятностей или вывести уже записанные вероятности
|
||||
select Minetest schematic probability values by punching nodes=выберите значения вероятностей для Minetest-схемы, ударя по нодам
|
||||
finished Minetest schematic probability selection=выбор вероятностей для Minetest-схемы завершен
|
||||
currently set node probabilities:=заданные вероятности нод на данный момент:
|
||||
invalid node probability given, not saved=недопустимая вероятность ноды, не сохранена
|
||||
Clears all objects within the WorldEdit region=Очистить все объекты в текущем WorldEdit-регионе
|
||||
@1 objects cleared=очищено @1 объектов
|
||||
|
||||
### safe.lua ###
|
||||
WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel=ПРЕДУПРЕЖДЕНИЕ: эта операция может затронуть до @1 нод; введите @2 для продолжения или @3 для отмены
|
||||
Confirm a pending operation=Подтвердить отложенную операцию
|
||||
no operation pending=нет ожидающей операции
|
||||
Abort a pending operation=Отклонить отложенную операцию
|
||||
|
||||
### cuboid.lua ###
|
||||
Outset the selected region.=Расширить выделение региона.
|
||||
Invalid direction: @1=Недопустимое направление: @1
|
||||
Invalid number of arguments=Недопустимое количество аргументов
|
||||
Region outset by @1 nodes=Регион расширен на @1 нод(у/ы)
|
||||
Inset the selected region.=Сузить выделение региона.
|
||||
Region inset by @1 nodes=Регион сужен на @1 нод(у/ы)
|
||||
Shifts the selection area without moving its contents=Сдвинуть выделение региона без перемещения его содержимого
|
||||
Invalid if looking straight up or down=Недопустимо, если смотреть прямо вверх или вниз
|
||||
Region shifted by @1 nodes=Регион сдвинут на @1 нод(у/ы)
|
||||
Expands the selection in the selected absolute or relative axis=Увеличить выделение региона по выбранной абсолютной или относительной оси
|
||||
Region expanded by @1 nodes=Регион увеличен на @1 нод(у/ы)
|
||||
Contracts the selection in the selected absolute or relative axis=Уменьшить выделение региона по выбранной абсолютной или относительной оси
|
||||
Region contracted by @1 nodes=Регион уменьшен на @1 нод(у/ы)
|
||||
Select a cube with side length <size> around position 1 and run <command> on region=Выделить куб с длиной стороны <size> вокруг позиции 1 и запустите <команду> в области
|
||||
invalid usage: @1 cannot be used with cubeapply=недопустимое использование: @1 не может быть применено в cubeapply
|
||||
Missing privileges: @1=Отсутствуют привилегии: @1
|
||||
|
||||
### wand.lua ###
|
||||
WorldEdit Wand tool@nLeft-click to set 1st position, right-click to set 2nd=Инструмент WorldEdit Wand@nЛевая кнопка мыши, чтобы установить 1-ю позицию, правая кнопка мыши, чтобы установить 2-ю
|
366
worldedit_commands/manipulations.lua
Normal file
|
@ -0,0 +1,366 @@
|
|||
local S = minetest.get_translator("worldedit_commands")
|
||||
|
||||
local function check_region(name)
|
||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||
end
|
||||
|
||||
|
||||
worldedit.register_command("deleteblocks", {
|
||||
params = "",
|
||||
description = S("Remove all MapBlocks (16x16x16) containing the selected area from the map"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
nodes_needed = check_region,
|
||||
func = function(name)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
local success = minetest.delete_area(pos1, pos2)
|
||||
if success then
|
||||
return true, S("Area deleted.")
|
||||
else
|
||||
return false, S("There was an error during deletion of the area.")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("clearobjects", {
|
||||
params = "",
|
||||
description = S("Clears all objects within the WorldEdit region"),
|
||||
category = S("Node manipulation"), -- not really, but it doesn't fit anywhere else
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
nodes_needed = check_region,
|
||||
func = function(name)
|
||||
local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])
|
||||
return true, S("@1 objects cleared", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("set", {
|
||||
params = "<node>",
|
||||
description = S("Set the current WorldEdit region to <node>"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local node = worldedit.normalize_nodename(param)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", param)
|
||||
end
|
||||
return true, node
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, node)
|
||||
local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node)
|
||||
return true, S("@1 nodes set", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("param2", {
|
||||
params = "<param2>",
|
||||
description = S("Set param2 of all nodes in the current WorldEdit region to <param2>"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local param2 = tonumber(param)
|
||||
if not param2 then
|
||||
return false
|
||||
elseif param2 < 0 or param2 > 255 then
|
||||
return false, S("Param2 is out of range (must be between 0 and 255 inclusive!)")
|
||||
end
|
||||
return true, param2
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, param2)
|
||||
local count = worldedit.set_param2(worldedit.pos1[name], worldedit.pos2[name], param2)
|
||||
return true, S("@1 nodes altered", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("mix", {
|
||||
params = "<node1> [count1] <node2> [count2] ...",
|
||||
description = S("Fill the current WorldEdit region with a random mix of <node1>, ..."),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local nodes = {}
|
||||
for nodename in param:gmatch("[^%s]+") do
|
||||
if tonumber(nodename) ~= nil and #nodes > 0 then
|
||||
local last_node = nodes[#nodes]
|
||||
for i = 1, tonumber(nodename) do
|
||||
nodes[#nodes + 1] = last_node
|
||||
end
|
||||
else
|
||||
local node = worldedit.normalize_nodename(nodename)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", nodename)
|
||||
end
|
||||
nodes[#nodes + 1] = node
|
||||
end
|
||||
end
|
||||
if #nodes == 0 then
|
||||
return false
|
||||
end
|
||||
return true, nodes
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, nodes)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
local count = worldedit.set(pos1, pos2, nodes)
|
||||
return true, S("@1 nodes set", count)
|
||||
end,
|
||||
})
|
||||
|
||||
local check_replace = function(param)
|
||||
local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
local newsearchnode = worldedit.normalize_nodename(searchnode)
|
||||
if not newsearchnode then
|
||||
return false, S("invalid search node name: @1", searchnode)
|
||||
end
|
||||
local newreplacenode = worldedit.normalize_nodename(replacenode)
|
||||
if not newreplacenode then
|
||||
return false, S("invalid replace node name: @1", replacenode)
|
||||
end
|
||||
return true, newsearchnode, newreplacenode
|
||||
end
|
||||
|
||||
worldedit.register_command("replace", {
|
||||
params = "<search node> <replace node>",
|
||||
description = S("Replace all instances of <search node> with <replace node> in the current WorldEdit region"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = check_replace,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, search_node, replace_node)
|
||||
local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
|
||||
search_node, replace_node)
|
||||
return true, S("@1 nodes replaced", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("replaceinverse", {
|
||||
params = "<search node> <replace node>",
|
||||
description = S("Replace all nodes other than <search node> with <replace node> in the current WorldEdit region"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = check_replace,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, search_node, replace_node)
|
||||
local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
|
||||
search_node, replace_node, true)
|
||||
return true, S("@1 nodes replaced", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("fixlight", {
|
||||
params = "",
|
||||
description = S("Fix the lighting in the current WorldEdit region"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
nodes_needed = check_region,
|
||||
func = function(name)
|
||||
local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name])
|
||||
return true, S("@1 nodes updated", count)
|
||||
end,
|
||||
})
|
||||
|
||||
local drain_cache
|
||||
|
||||
local function drain(pos1, pos2)
|
||||
if drain_cache == nil then
|
||||
drain_cache = {}
|
||||
for name, d in pairs(minetest.registered_nodes) do
|
||||
if d.drawtype == "liquid" or d.drawtype == "flowingliquid" then
|
||||
drain_cache[name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
local count = 0
|
||||
|
||||
local get_node, remove_node = minetest.get_node, minetest.remove_node
|
||||
for x = pos1.x, pos2.x do
|
||||
for y = pos1.y, pos2.y do
|
||||
for z = pos1.z, pos2.z do
|
||||
local p = vector.new(x, y, z)
|
||||
local n = get_node(p).name
|
||||
if drain_cache[n] then
|
||||
remove_node(p)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
worldedit.register_command("drain", {
|
||||
params = "",
|
||||
description = S("Remove any fluid node within the current WorldEdit region"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
nodes_needed = check_region,
|
||||
func = function(name)
|
||||
local count = drain(worldedit.pos1[name], worldedit.pos2[name])
|
||||
return true, S("@1 nodes updated", count)
|
||||
end,
|
||||
})
|
||||
|
||||
local clearcut_cache
|
||||
|
||||
local function clearcut(pos1, pos2)
|
||||
-- decide which nodes we consider plants
|
||||
if clearcut_cache == nil then
|
||||
clearcut_cache = {}
|
||||
for name, def in pairs(minetest.registered_nodes) do
|
||||
local groups = def.groups or {}
|
||||
if (
|
||||
-- the groups say so
|
||||
groups.flower or groups.grass or groups.flora or groups.plant or
|
||||
groups.leaves or groups.tree or groups.leafdecay or groups.sapling or
|
||||
-- drawtype heuristic
|
||||
(def.is_ground_content and def.buildable_to and
|
||||
(def.sunlight_propagates or not def.walkable)
|
||||
and def.drawtype == "plantlike") or
|
||||
-- if it's flammable, it probably needs to go too
|
||||
(def.is_ground_content and not def.walkable and groups.flammable)
|
||||
) then
|
||||
clearcut_cache[name] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
local plants = clearcut_cache
|
||||
|
||||
local count = 0
|
||||
local prev, any
|
||||
|
||||
local get_node, remove_node = minetest.get_node, minetest.remove_node
|
||||
for x = pos1.x, pos2.x do
|
||||
for z = pos1.z, pos2.z do
|
||||
prev = false
|
||||
any = false
|
||||
-- first pass: remove floating nodes that would be left over
|
||||
for y = pos1.y, pos2.y do
|
||||
local pos = vector.new(x, y, z)
|
||||
local n = get_node(pos).name
|
||||
if plants[n] then
|
||||
prev = true
|
||||
any = true
|
||||
elseif prev then
|
||||
local def = minetest.registered_nodes[n] or {}
|
||||
local groups = def.groups or {}
|
||||
if groups.attached_node or (def.buildable_to and groups.falling_node) then
|
||||
remove_node(pos)
|
||||
count = count + 1
|
||||
else
|
||||
prev = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- second pass: remove plants, top-to-bottom to avoid item drops
|
||||
if any then
|
||||
for y = pos2.y, pos1.y, -1 do
|
||||
local pos = vector.new(x, y, z)
|
||||
local n = get_node(pos).name
|
||||
if plants[n] then
|
||||
remove_node(pos)
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
worldedit.register_command("clearcut", {
|
||||
params = "",
|
||||
description = S("Remove any plant, tree or foliage-like nodes in the selected region"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
nodes_needed = check_region,
|
||||
func = function(name)
|
||||
local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
|
||||
local count = clearcut(pos1, pos2)
|
||||
return true, S("@1 nodes removed", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("hide", {
|
||||
params = "",
|
||||
description = S("Hide all nodes in the current WorldEdit region non-destructively"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
nodes_needed = check_region,
|
||||
func = function(name)
|
||||
local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name])
|
||||
return true, S("@1 nodes hidden", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("suppress", {
|
||||
params = "<node>",
|
||||
description = S("Suppress all <node> in the current WorldEdit region non-destructively"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local node = worldedit.normalize_nodename(param)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", param)
|
||||
end
|
||||
return true, node
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, node)
|
||||
local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node)
|
||||
return true, S("@1 nodes suppressed", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("highlight", {
|
||||
params = "<node>",
|
||||
description = S("Highlight <node> in the current WorldEdit region by hiding everything else non-destructively"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local node = worldedit.normalize_nodename(param)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", param)
|
||||
end
|
||||
return true, node
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, node)
|
||||
local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node)
|
||||
return true, S("@1 nodes highlighted", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("restore", {
|
||||
params = "",
|
||||
description = S("Restores nodes hidden with WorldEdit in the current WorldEdit region"),
|
||||
category = S("Node manipulation"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
nodes_needed = check_region,
|
||||
func = function(name)
|
||||
local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name])
|
||||
return true, S("@1 nodes restored", count)
|
||||
end,
|
||||
})
|
|
@ -1,70 +0,0 @@
|
|||
worldedit.marker1 = {}
|
||||
worldedit.marker2 = {}
|
||||
|
||||
--marks worldedit region position 1
|
||||
worldedit.mark_pos1 = function(name)
|
||||
local pos = worldedit.pos1[name]
|
||||
if worldedit.marker1[name] ~= nil then --marker already exists
|
||||
worldedit.marker1[name]:remove() --remove marker
|
||||
worldedit.marker1[name] = nil
|
||||
end
|
||||
if pos ~= nil then --add marker
|
||||
worldedit.marker1[name] = minetest.env:add_entity(pos, "worldedit:pos1")
|
||||
worldedit.marker1[name]:get_luaentity().active = true
|
||||
end
|
||||
end
|
||||
|
||||
--marks worldedit region position 2
|
||||
worldedit.mark_pos2 = function(name)
|
||||
local pos = worldedit.pos2[name]
|
||||
if worldedit.marker2[name] ~= nil then --marker already exists
|
||||
worldedit.marker2[name]:remove() --remove marker
|
||||
worldedit.marker2[name] = nil
|
||||
end
|
||||
if pos ~= nil then --add marker
|
||||
worldedit.marker2[name] = minetest.env:add_entity(pos, "worldedit:pos2")
|
||||
worldedit.marker2[name]:get_luaentity().active = true
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity(":worldedit:pos1", {
|
||||
initial_properties = {
|
||||
visual = "cube",
|
||||
visual_size = {x=1.1, y=1.1},
|
||||
textures = {"worldedit_pos1.png", "worldedit_pos1.png",
|
||||
"worldedit_pos1.png", "worldedit_pos1.png",
|
||||
"worldedit_pos1.png", "worldedit_pos1.png"},
|
||||
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
|
||||
},
|
||||
on_step = function(self, dtime)
|
||||
if self.active == nil then
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
on_punch = function(self, hitter)
|
||||
self.object:remove()
|
||||
local name = hitter:get_player_name()
|
||||
worldedit.marker1[name] = nil
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_entity(":worldedit:pos2", {
|
||||
initial_properties = {
|
||||
visual = "cube",
|
||||
visual_size = {x=1.1, y=1.1},
|
||||
textures = {"worldedit_pos2.png", "worldedit_pos2.png",
|
||||
"worldedit_pos2.png", "worldedit_pos2.png",
|
||||
"worldedit_pos2.png", "worldedit_pos2.png"},
|
||||
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
|
||||
},
|
||||
on_step = function(self, dtime)
|
||||
if self.active == nil then
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
on_punch = function(self, hitter)
|
||||
self.object:remove()
|
||||
local name = hitter:get_player_name()
|
||||
worldedit.marker2[name] = nil
|
||||
end,
|
||||
})
|
201
worldedit_commands/marker.lua
Normal file
|
@ -0,0 +1,201 @@
|
|||
worldedit.marker1 = {}
|
||||
worldedit.marker2 = {}
|
||||
worldedit.marker_region = {}
|
||||
|
||||
local init_sentinel = "new" .. tostring(math.random(99999))
|
||||
|
||||
--marks worldedit region position 1
|
||||
worldedit.mark_pos1 = function(name, region_too)
|
||||
local pos1 = worldedit.pos1[name]
|
||||
|
||||
if worldedit.marker1[name] ~= nil then --marker already exists
|
||||
worldedit.marker1[name]:remove() --remove marker
|
||||
worldedit.marker1[name] = nil
|
||||
end
|
||||
if pos1 ~= nil then
|
||||
worldedit.keep_loaded(pos1, pos1)
|
||||
|
||||
--add marker
|
||||
worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1", init_sentinel)
|
||||
if worldedit.marker1[name] ~= nil then
|
||||
worldedit.marker1[name]:get_luaentity().player_name = name
|
||||
end
|
||||
end
|
||||
if region_too == nil or region_too then
|
||||
worldedit.mark_region(name)
|
||||
end
|
||||
end
|
||||
|
||||
--marks worldedit region position 2
|
||||
worldedit.mark_pos2 = function(name, region_too)
|
||||
local pos2 = worldedit.pos2[name]
|
||||
|
||||
if worldedit.marker2[name] ~= nil then --marker already exists
|
||||
worldedit.marker2[name]:remove() --remove marker
|
||||
worldedit.marker2[name] = nil
|
||||
end
|
||||
if pos2 ~= nil then
|
||||
worldedit.keep_loaded(pos2, pos2)
|
||||
|
||||
--add marker
|
||||
worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2", init_sentinel)
|
||||
if worldedit.marker2[name] ~= nil then
|
||||
worldedit.marker2[name]:get_luaentity().player_name = name
|
||||
end
|
||||
end
|
||||
if region_too == nil or region_too then
|
||||
worldedit.mark_region(name)
|
||||
end
|
||||
end
|
||||
|
||||
worldedit.mark_region = function(name)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
|
||||
if worldedit.marker_region[name] ~= nil then --marker already exists
|
||||
for _, entity in ipairs(worldedit.marker_region[name]) do
|
||||
entity:remove()
|
||||
end
|
||||
worldedit.marker_region[name] = nil
|
||||
end
|
||||
|
||||
if pos1 ~= nil and pos2 ~= nil then
|
||||
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||
|
||||
local vec = vector.subtract(pos2, pos1)
|
||||
local maxside = math.max(vec.x, math.max(vec.y, vec.z))
|
||||
local limit = tonumber(minetest.settings:get("active_object_send_range_blocks")) * 16
|
||||
if maxside > limit * 1.5 then
|
||||
-- The client likely won't be able to see the plane markers as intended anyway,
|
||||
-- thus don't place them and also don't load the area into memory
|
||||
return
|
||||
end
|
||||
|
||||
local thickness = 0.2
|
||||
local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2
|
||||
|
||||
-- TODO maybe we could skip this actually?
|
||||
worldedit.keep_loaded(pos1, pos2)
|
||||
|
||||
local markers = {}
|
||||
|
||||
--XY plane markers
|
||||
for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do
|
||||
local entpos = vector.new(pos1.x + sizex - 0.5, pos1.y + sizey - 0.5, z)
|
||||
local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel)
|
||||
if marker ~= nil then
|
||||
marker:set_properties({
|
||||
visual_size={x=sizex * 2, y=sizey * 2},
|
||||
collisionbox = {-sizex, -sizey, -thickness, sizex, sizey, thickness},
|
||||
})
|
||||
marker:get_luaentity().player_name = name
|
||||
table.insert(markers, marker)
|
||||
end
|
||||
end
|
||||
|
||||
--YZ plane markers
|
||||
for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do
|
||||
local entpos = vector.new(x, pos1.y + sizey - 0.5, pos1.z + sizez - 0.5)
|
||||
local marker = minetest.add_entity(entpos, "worldedit:region_cube", init_sentinel)
|
||||
if marker ~= nil then
|
||||
marker:set_properties({
|
||||
visual_size={x=sizez * 2, y=sizey * 2},
|
||||
collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez},
|
||||
})
|
||||
marker:set_yaw(math.pi / 2)
|
||||
marker:get_luaentity().player_name = name
|
||||
table.insert(markers, marker)
|
||||
end
|
||||
end
|
||||
|
||||
worldedit.marker_region[name] = markers
|
||||
end
|
||||
end
|
||||
|
||||
--convenience function that calls everything
|
||||
worldedit.marker_update = function(name)
|
||||
worldedit.mark_pos1(name, false)
|
||||
worldedit.mark_pos2(name, false)
|
||||
worldedit.mark_region(name)
|
||||
end
|
||||
|
||||
minetest.register_entity(":worldedit:pos1", {
|
||||
initial_properties = {
|
||||
visual = "cube",
|
||||
visual_size = {x=1.1, y=1.1},
|
||||
textures = {"worldedit_pos1.png", "worldedit_pos1.png",
|
||||
"worldedit_pos1.png", "worldedit_pos1.png",
|
||||
"worldedit_pos1.png", "worldedit_pos1.png"},
|
||||
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
|
||||
physical = false,
|
||||
static_save = false,
|
||||
},
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
if staticdata ~= init_sentinel then
|
||||
-- we were loaded from before static_save = false was added
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
on_punch = function(self, hitter)
|
||||
self.object:remove()
|
||||
worldedit.marker1[self.player_name] = nil
|
||||
end,
|
||||
on_blast = function(self, damage)
|
||||
return false, false, {} -- don't damage or knockback
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_entity(":worldedit:pos2", {
|
||||
initial_properties = {
|
||||
visual = "cube",
|
||||
visual_size = {x=1.1, y=1.1},
|
||||
textures = {"worldedit_pos2.png", "worldedit_pos2.png",
|
||||
"worldedit_pos2.png", "worldedit_pos2.png",
|
||||
"worldedit_pos2.png", "worldedit_pos2.png"},
|
||||
collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55},
|
||||
physical = false,
|
||||
static_save = false,
|
||||
},
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
if staticdata ~= init_sentinel then
|
||||
-- we were loaded from before static_save = false was added
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
on_punch = function(self, hitter)
|
||||
self.object:remove()
|
||||
worldedit.marker2[self.player_name] = nil
|
||||
end,
|
||||
on_blast = function(self, damage)
|
||||
return false, false, {} -- don't damage or knockback
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_entity(":worldedit:region_cube", {
|
||||
initial_properties = {
|
||||
visual = "upright_sprite",
|
||||
textures = {"worldedit_cube.png"},
|
||||
visual_size = {x=10, y=10},
|
||||
physical = false,
|
||||
static_save = false,
|
||||
},
|
||||
on_activate = function(self, staticdata, dtime_s)
|
||||
if staticdata ~= init_sentinel then
|
||||
-- we were loaded from before static_save = false was added
|
||||
self.object:remove()
|
||||
end
|
||||
end,
|
||||
on_punch = function(self, hitter)
|
||||
local markers = worldedit.marker_region[self.player_name]
|
||||
if not markers then
|
||||
return
|
||||
end
|
||||
for _, entity in ipairs(markers) do
|
||||
entity:remove()
|
||||
end
|
||||
worldedit.marker_region[self.player_name] = nil
|
||||
end,
|
||||
on_blast = function(self, damage)
|
||||
return false, false, {} -- don't damage or knockback
|
||||
end,
|
||||
})
|
||||
|
3
worldedit_commands/mod.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
name = worldedit_commands
|
||||
description = WorldEdit chat commands
|
||||
depends = worldedit
|
85
worldedit_commands/nodename.lua
Normal file
|
@ -0,0 +1,85 @@
|
|||
-- Strips any kind of escape codes (translation, colors) from a string
|
||||
-- https://github.com/minetest/minetest/blob/53dd7819277c53954d1298dfffa5287c306db8d0/src/util/string.cpp#L777
|
||||
local function strip_escapes(input)
|
||||
local s = function(idx) return input:sub(idx, idx) end
|
||||
local out = ""
|
||||
local i = 1
|
||||
while i <= #input do
|
||||
if s(i) == "\027" then -- escape sequence
|
||||
i = i + 1
|
||||
if s(i) == "(" then -- enclosed
|
||||
i = i + 1
|
||||
while i <= #input and s(i) ~= ")" do
|
||||
if s(i) == "\\" then
|
||||
i = i + 2
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
out = out .. s(i)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
--print(("%q -> %q"):format(input, out))
|
||||
return out
|
||||
end
|
||||
|
||||
local function string_endswith(full, part)
|
||||
return full:find(part, 1, true) == #full - #part + 1
|
||||
end
|
||||
|
||||
local description_cache = nil
|
||||
|
||||
-- normalizes node "description" `nodename`, returning a string (or nil)
|
||||
worldedit.normalize_nodename = function(nodename)
|
||||
nodename = nodename:gsub("^%s*(.-)%s*$", "%1") -- strip spaces
|
||||
if nodename == "" then return nil end
|
||||
|
||||
local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases
|
||||
if minetest.registered_nodes[fullname] or fullname == "air" then -- full name
|
||||
return fullname
|
||||
end
|
||||
nodename = nodename:lower()
|
||||
|
||||
for key, _ in pairs(minetest.registered_nodes) do
|
||||
if string_endswith(key:lower(), ":" .. nodename) then -- matches name (w/o mod part)
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
if description_cache == nil then
|
||||
-- cache stripped descriptions
|
||||
description_cache = {}
|
||||
for key, value in pairs(minetest.registered_nodes) do
|
||||
local desc = strip_escapes(value.description):gsub("\n.*", "", 1):lower()
|
||||
if desc ~= "" then
|
||||
description_cache[key] = desc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for key, desc in pairs(description_cache) do
|
||||
if desc == nodename then -- matches description
|
||||
return key
|
||||
end
|
||||
end
|
||||
for key, desc in pairs(description_cache) do
|
||||
if desc == nodename .. " block" then
|
||||
-- fuzzy description match (e.g. "Steel" == "Steel Block")
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
local match = nil
|
||||
for key, value in pairs(description_cache) do
|
||||
if value:find(nodename, 1, true) ~= nil then
|
||||
if match ~= nil then
|
||||
return nil
|
||||
end
|
||||
match = key -- substring description match (only if no ambiguities)
|
||||
end
|
||||
end
|
||||
return match
|
||||
end
|
276
worldedit_commands/primitives.lua
Normal file
|
@ -0,0 +1,276 @@
|
|||
local S = minetest.get_translator("worldedit_commands")
|
||||
|
||||
|
||||
local check_cube = function(param)
|
||||
local found, _, w, h, l, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
local node = worldedit.normalize_nodename(nodename)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", nodename)
|
||||
end
|
||||
return true, tonumber(w), tonumber(h), tonumber(l), node
|
||||
end
|
||||
|
||||
worldedit.register_command("hollowcube", {
|
||||
params = "<width> <height> <length> <node>",
|
||||
description = S("Add a hollow cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>."),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_cube,
|
||||
nodes_needed = function(name, w, h, l, node)
|
||||
return w * h * l
|
||||
end,
|
||||
func = function(name, w, h, l, node)
|
||||
local count = worldedit.cube(worldedit.pos1[name], w, h, l, node, true)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("cube", {
|
||||
params = "<width> <height> <length> <node>",
|
||||
description = S("Add a cube with its ground level centered at WorldEdit position 1 with dimensions <width> x <height> x <length>, composed of <node>."),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_cube,
|
||||
nodes_needed = function(name, w, h, l, node)
|
||||
return w * h * l
|
||||
end,
|
||||
func = function(name, w, h, l, node)
|
||||
local count = worldedit.cube(worldedit.pos1[name], w, h, l, node)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
local check_sphere = function(param)
|
||||
local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
local node = worldedit.normalize_nodename(nodename)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", nodename)
|
||||
end
|
||||
return true, tonumber(radius), node
|
||||
end
|
||||
|
||||
worldedit.register_command("hollowsphere", {
|
||||
params = "<radius> <node>",
|
||||
description = S("Add hollow sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_sphere,
|
||||
nodes_needed = function(name, radius, node)
|
||||
return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
|
||||
end,
|
||||
func = function(name, radius, node)
|
||||
local count = worldedit.sphere(worldedit.pos1[name], radius, node, true)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("sphere", {
|
||||
params = "<radius> <node>",
|
||||
description = S("Add sphere centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_sphere,
|
||||
nodes_needed = function(name, radius, node)
|
||||
return math.ceil((4 * math.pi * (radius ^ 3)) / 3) --volume of sphere
|
||||
end,
|
||||
func = function(name, radius, node)
|
||||
local count = worldedit.sphere(worldedit.pos1[name], radius, node)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
local check_dome = function(param)
|
||||
local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
local node = worldedit.normalize_nodename(nodename)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", nodename)
|
||||
end
|
||||
return true, tonumber(radius), node
|
||||
end
|
||||
|
||||
worldedit.register_command("hollowdome", {
|
||||
params = "<radius> <node>",
|
||||
description = S("Add hollow dome centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_dome,
|
||||
nodes_needed = function(name, radius, node)
|
||||
return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
|
||||
end,
|
||||
func = function(name, radius, node)
|
||||
local count = worldedit.dome(worldedit.pos1[name], radius, node, true)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("dome", {
|
||||
params = "<radius> <node>",
|
||||
description = S("Add dome centered at WorldEdit position 1 with radius <radius>, composed of <node>"),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_dome,
|
||||
nodes_needed = function(name, radius, node)
|
||||
return math.ceil((2 * math.pi * (radius ^ 3)) / 3) --volume of dome
|
||||
end,
|
||||
func = function(name, radius, node)
|
||||
local count = worldedit.dome(worldedit.pos1[name], radius, node)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
local check_cylinder = function(param)
|
||||
-- two radii
|
||||
local found, _, axis, length, radius1, radius2, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
|
||||
if found == nil then
|
||||
-- single radius
|
||||
found, _, axis, length, radius1, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$")
|
||||
radius2 = radius1
|
||||
end
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
local node = worldedit.normalize_nodename(nodename)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", nodename)
|
||||
end
|
||||
return true, axis, tonumber(length), tonumber(radius1), tonumber(radius2), node
|
||||
end
|
||||
|
||||
worldedit.register_command("hollowcylinder", {
|
||||
params = "x/y/z/? <length> <radius1> [radius2] <node>",
|
||||
description = S("Add hollow cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>"),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_cylinder,
|
||||
nodes_needed = function(name, axis, length, radius1, radius2, node)
|
||||
local radius = math.max(radius1, radius2)
|
||||
return math.ceil(math.pi * (radius ^ 2) * length)
|
||||
end,
|
||||
func = function(name, axis, length, radius1, radius2, node)
|
||||
if axis == "?" then
|
||||
local sign
|
||||
axis, sign = worldedit.player_axis(name)
|
||||
length = length * sign
|
||||
end
|
||||
local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node, true)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("cylinder", {
|
||||
params = "x/y/z/? <length> <radius1> [radius2] <node>",
|
||||
description = S("Add cylinder at WorldEdit position 1 along the given axis with length <length>, base radius <radius1> (and top radius [radius2]), composed of <node>"),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_cylinder,
|
||||
nodes_needed = function(name, axis, length, radius1, radius2, node)
|
||||
local radius = math.max(radius1, radius2)
|
||||
return math.ceil(math.pi * (radius ^ 2) * length)
|
||||
end,
|
||||
func = function(name, axis, length, radius1, radius2, node)
|
||||
if axis == "?" then
|
||||
local sign
|
||||
axis, sign = worldedit.player_axis(name)
|
||||
length = length * sign
|
||||
end
|
||||
local count = worldedit.cylinder(worldedit.pos1[name], axis, length, radius1, radius2, node)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
local check_pyramid = function(param)
|
||||
local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
local node = worldedit.normalize_nodename(nodename)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", nodename)
|
||||
end
|
||||
return true, axis, tonumber(height), node
|
||||
end
|
||||
|
||||
worldedit.register_command("hollowpyramid", {
|
||||
params = "x/y/z/? <height> <node>",
|
||||
description = S("Add hollow pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>"),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_pyramid,
|
||||
nodes_needed = function(name, axis, height, node)
|
||||
return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
|
||||
end,
|
||||
func = function(name, axis, height, node)
|
||||
if axis == "?" then
|
||||
local sign
|
||||
axis, sign = worldedit.player_axis(name)
|
||||
height = height * sign
|
||||
end
|
||||
local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node, true)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("pyramid", {
|
||||
params = "x/y/z/? <height> <node>",
|
||||
description = S("Add pyramid centered at WorldEdit position 1 along the given axis with height <height>, composed of <node>"),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = check_pyramid,
|
||||
nodes_needed = function(name, axis, height, node)
|
||||
return math.ceil(((height * 2 + 1) ^ 2) * height / 3)
|
||||
end,
|
||||
func = function(name, axis, height, node)
|
||||
if axis == "?" then
|
||||
local sign
|
||||
axis, sign = worldedit.player_axis(name)
|
||||
height = height * sign
|
||||
end
|
||||
local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("spiral", {
|
||||
params = "<length> <height> <space> <node>",
|
||||
description = S("Add spiral centered at WorldEdit position 1 with side length <length>, height <height>, space between walls <space>, composed of <node>"),
|
||||
category = S("Shapes"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = function(param)
|
||||
local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
local node = worldedit.normalize_nodename(nodename)
|
||||
if not node then
|
||||
return false, S("invalid node name: @1", nodename)
|
||||
end
|
||||
return true, tonumber(length), tonumber(height), tonumber(space), node
|
||||
end,
|
||||
nodes_needed = function(name, length, height, space, node)
|
||||
return (length + space) * height -- TODO: this is not the upper bound
|
||||
end,
|
||||
func = function(name, length, height, space, node)
|
||||
local count = worldedit.spiral(worldedit.pos1[name], length, height, space, node)
|
||||
return true, S("@1 nodes added", count)
|
||||
end,
|
||||
})
|
234
worldedit_commands/region.lua
Normal file
|
@ -0,0 +1,234 @@
|
|||
local S = minetest.get_translator("worldedit_commands")
|
||||
|
||||
worldedit.set_pos = {}
|
||||
worldedit.inspect = {}
|
||||
|
||||
|
||||
worldedit.register_command("inspect", {
|
||||
params = "[on/off/1/0/true/false/yes/no/enable/disable]",
|
||||
description = S("Enable or disable node inspection"),
|
||||
privs = {worldedit=true},
|
||||
parse = function(param)
|
||||
if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then
|
||||
return true, true
|
||||
elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then
|
||||
return true, false
|
||||
end
|
||||
return false
|
||||
end,
|
||||
func = function(name, enable)
|
||||
if enable then
|
||||
worldedit.inspect[name] = true
|
||||
local axis, sign = worldedit.player_axis(name)
|
||||
worldedit.player_notify(name, S(
|
||||
"inspector: inspection enabled for @1, currently facing the @2 axis",
|
||||
name,
|
||||
axis .. (sign > 0 and "+" or "-")
|
||||
), "info")
|
||||
else
|
||||
worldedit.inspect[name] = nil
|
||||
worldedit.player_notify(name, S("inspector: inspection disabled"), "info")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
local VEC_6DIRS = {
|
||||
vector.new( 1, 0, 0),
|
||||
vector.new(-1, 0, 0),
|
||||
vector.new( 0, 1, 0),
|
||||
vector.new( 0,-1, 0),
|
||||
vector.new( 0, 0, 1),
|
||||
vector.new( 0, 0,-1),
|
||||
}
|
||||
local function get_node_rlight(pos)
|
||||
local ret = 0
|
||||
for _, v in ipairs(VEC_6DIRS) do
|
||||
ret = math.max(ret, minetest.get_node_light(vector.add(pos, v)))
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
minetest.register_on_punchnode(function(pos, node, puncher)
|
||||
local name = puncher:get_player_name()
|
||||
if worldedit.inspect[name] then
|
||||
local axis, sign = worldedit.player_axis(name)
|
||||
local message = S(
|
||||
"inspector: @1 at @2 (param1=@3, param2=@4, received light=@5) punched facing the @6 axis",
|
||||
node.name,
|
||||
minetest.pos_to_string(pos),
|
||||
node.param1,
|
||||
node.param2,
|
||||
get_node_rlight(pos),
|
||||
axis .. (sign > 0 and "+" or "-")
|
||||
)
|
||||
worldedit.player_notify(name, message, "info")
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
worldedit.register_command("mark", {
|
||||
params = "",
|
||||
description = S("Show markers at the region positions"),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
func = function(name)
|
||||
worldedit.marker_update(name)
|
||||
return true, S("region marked")
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("unmark", {
|
||||
params = "",
|
||||
description = S("Hide markers if currently shown"),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
func = function(name)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
worldedit.pos1[name] = nil
|
||||
worldedit.pos2[name] = nil
|
||||
worldedit.marker_update(name)
|
||||
worldedit.pos1[name] = pos1
|
||||
worldedit.pos2[name] = pos2
|
||||
return true, S("region unmarked")
|
||||
end,
|
||||
})
|
||||
|
||||
local function set_pos1(name, pos)
|
||||
assert(pos)
|
||||
worldedit.pos1[name] = pos
|
||||
worldedit.mark_pos1(name)
|
||||
worldedit.player_notify(name, S("position @1 set to @2", 1, minetest.pos_to_string(pos)), "ok")
|
||||
end
|
||||
|
||||
local function set_pos2(name, pos)
|
||||
assert(pos)
|
||||
worldedit.pos2[name] = pos
|
||||
worldedit.mark_pos2(name)
|
||||
worldedit.player_notify(name, S("position @1 set to @2", 2, minetest.pos_to_string(pos)), "ok")
|
||||
end
|
||||
|
||||
worldedit.register_command("pos1", {
|
||||
params = "",
|
||||
description = S("Set WorldEdit region position @1 to the player's location", 1),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
func = function(name)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if not player then return end
|
||||
set_pos1(name, vector.round(player:get_pos()))
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("pos2", {
|
||||
params = "",
|
||||
description = S("Set WorldEdit region position @1 to the player's location", 2),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
func = function(name)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if not player then return end
|
||||
set_pos2(name, vector.round(player:get_pos()))
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("p", {
|
||||
params = "set/set1/set2/get",
|
||||
description = S("Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region"),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
parse = function(param)
|
||||
if param == "set" or param == "set1" or param == "set2" or param == "get" then
|
||||
return true, param
|
||||
end
|
||||
return false, S("unknown subcommand: @1", param)
|
||||
end,
|
||||
func = function(name, param)
|
||||
local msg
|
||||
if param == "set" then --set both WorldEdit positions
|
||||
worldedit.set_pos[name] = "pos1"
|
||||
msg = S("select positions by punching two nodes")
|
||||
elseif param == "set1" then --set WorldEdit position 1
|
||||
worldedit.set_pos[name] = "pos1only"
|
||||
msg = S("select position @1 by punching a node", 1)
|
||||
elseif param == "set2" then --set WorldEdit position 2
|
||||
worldedit.set_pos[name] = "pos2"
|
||||
msg = S("select position @1 by punching a node", 2)
|
||||
elseif param == "get" then --display current WorldEdit positions
|
||||
if worldedit.pos1[name] ~= nil then
|
||||
msg = S("position @1: @2", 1, minetest.pos_to_string(worldedit.pos1[name]))
|
||||
else
|
||||
msg = S("position @1 not set", 1)
|
||||
end
|
||||
msg = msg .. "\n"
|
||||
if worldedit.pos2[name] ~= nil then
|
||||
msg = msg .. S("position @1: @2", 2, minetest.pos_to_string(worldedit.pos2[name]))
|
||||
else
|
||||
msg = msg .. S("position @1 not set", 2)
|
||||
end
|
||||
end
|
||||
if msg then
|
||||
worldedit.player_notify(name, msg, "info")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("fixedpos", {
|
||||
params = "set1/set2 <x> <y> <z>",
|
||||
description = S("Set a WorldEdit region position to the position at (<x>, <y>, <z>)"),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
parse = function(param)
|
||||
local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
return true, flag, vector.new(tonumber(x), tonumber(y), tonumber(z))
|
||||
end,
|
||||
func = function(name, flag, pos)
|
||||
if flag == "set1" then
|
||||
set_pos1(name, pos)
|
||||
else --flag == "set2"
|
||||
set_pos2(name, pos)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_on_punchnode(function(pos, node, puncher)
|
||||
local name = puncher:get_player_name()
|
||||
if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position
|
||||
if worldedit.set_pos[name] == "pos1" then --setting position 1
|
||||
set_pos1(name, pos)
|
||||
worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation
|
||||
elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only
|
||||
set_pos1(name, pos)
|
||||
worldedit.set_pos[name] = nil --finished setting positions
|
||||
elseif worldedit.set_pos[name] == "pos2" then --setting position 2
|
||||
set_pos2(name, pos)
|
||||
worldedit.set_pos[name] = nil --finished setting positions
|
||||
elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities
|
||||
worldedit.prob_pos[name] = pos
|
||||
minetest.show_formspec(name, "prob_val_enter", "field[text;;]")
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
worldedit.register_command("volume", {
|
||||
params = "",
|
||||
description = S("Display the volume of the current WorldEdit region"),
|
||||
category = S("Region operations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
func = function(name)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
|
||||
local volume = worldedit.volume(pos1, pos2)
|
||||
local abs = math.abs
|
||||
worldedit.player_notify(name, S(
|
||||
"current region has a volume of @1 nodes (@2*@3*@4)",
|
||||
volume,
|
||||
abs(pos2.x - pos1.x) + 1,
|
||||
abs(pos2.y - pos1.y) + 1,
|
||||
abs(pos2.z - pos1.z) + 1
|
||||
), "info")
|
||||
end,
|
||||
})
|
63
worldedit_commands/safe.lua
Normal file
|
@ -0,0 +1,63 @@
|
|||
local S = minetest.get_translator("worldedit_commands")
|
||||
|
||||
local safe_region_limit = tonumber(minetest.settings:get("worldedit_safe_region_limit") or "20000")
|
||||
|
||||
local safe_region_callback = {}
|
||||
|
||||
--`count` is the number of nodes that would possibly be modified
|
||||
--`callback` is a callback to run when the user confirms
|
||||
local function safe_region(name, count, callback)
|
||||
if safe_region_limit <= 0 or count < safe_region_limit then
|
||||
return callback()
|
||||
end
|
||||
|
||||
-- save callback to call later
|
||||
safe_region_callback[name] = callback
|
||||
|
||||
local count_str = tostring(count)
|
||||
-- highlight millions, 1 mln <=> 100x100x100 cube
|
||||
if #count_str > 6 then
|
||||
count_str = minetest.colorize("#f33", count_str:sub(1, -7)) .. count_str:sub(-6, -1)
|
||||
end
|
||||
|
||||
local yes_cmd = minetest.colorize("#00ffff", "//y")
|
||||
local no_cmd = minetest.colorize("#00ffff", "//n")
|
||||
local msg = S("WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 to cancel",
|
||||
count_str, yes_cmd, no_cmd)
|
||||
worldedit.player_notify(name, msg, "info")
|
||||
end
|
||||
|
||||
local function reset_pending(name)
|
||||
safe_region_callback[name] = nil
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("/y", {
|
||||
params = "",
|
||||
description = S("Confirm a pending operation"),
|
||||
func = function(name)
|
||||
local callback = safe_region_callback[name]
|
||||
if not callback then
|
||||
worldedit.player_notify(name, S("no operation pending"), "error")
|
||||
return
|
||||
end
|
||||
|
||||
reset_pending(name)
|
||||
callback(name)
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("/n", {
|
||||
params = "",
|
||||
description = S("Abort a pending operation"),
|
||||
func = function(name)
|
||||
if not safe_region_callback[name] then
|
||||
worldedit.player_notify(name, S("no operation pending"), "error")
|
||||
return
|
||||
end
|
||||
|
||||
reset_pending(name)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
return safe_region, reset_pending
|
284
worldedit_commands/schematics.lua
Normal file
|
@ -0,0 +1,284 @@
|
|||
local S = minetest.get_translator("worldedit_commands")
|
||||
|
||||
worldedit.prob_pos = {}
|
||||
worldedit.prob_list = {}
|
||||
|
||||
local function check_region(name)
|
||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||
end
|
||||
|
||||
local function check_filename(name)
|
||||
return name:find("^[%w%s%^&'@{}%[%],%$=!%-#%(%)%%%.%+~_]+$") ~= nil
|
||||
end
|
||||
|
||||
local function open_schematic(name, param)
|
||||
-- find the file in the world path
|
||||
local testpaths = {
|
||||
minetest.get_worldpath() .. "/schems/" .. param,
|
||||
minetest.get_worldpath() .. "/schems/" .. param .. ".we",
|
||||
minetest.get_worldpath() .. "/schems/" .. param .. ".wem",
|
||||
}
|
||||
local file, err
|
||||
for index, path in ipairs(testpaths) do
|
||||
file, err = io.open(path, "rb")
|
||||
if not err then
|
||||
break
|
||||
end
|
||||
end
|
||||
if err then
|
||||
worldedit.player_notify(name, S("Could not open file \"@1\"", param), "error")
|
||||
return
|
||||
end
|
||||
local value = file:read("*a")
|
||||
file:close()
|
||||
|
||||
local version = worldedit.read_header(value)
|
||||
if version == nil or version == 0 then
|
||||
worldedit.player_notify(name, S("Invalid file format!"), "error")
|
||||
return
|
||||
elseif version > worldedit.LATEST_SERIALIZATION_VERSION then
|
||||
worldedit.player_notify(name, S("Schematic was created with a newer version of WorldEdit."), "error")
|
||||
return
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
local function detect_misaligned_schematic(name, pos1, pos2)
|
||||
pos1 = worldedit.sort_pos(pos1, pos2)
|
||||
-- Check that allocate/save can position the schematic correctly
|
||||
-- The expected behaviour is that the (0,0,0) corner of the schematic stays
|
||||
-- at pos1, this only works when the minimum position is actually present
|
||||
-- in the schematic.
|
||||
local node = minetest.get_node(pos1)
|
||||
local have_node_at_origin = node.name ~= "air" and node.name ~= "ignore"
|
||||
if not have_node_at_origin then
|
||||
worldedit.player_notify(name,
|
||||
S("Warning: The schematic contains excessive free space and WILL be "..
|
||||
"misaligned when allocated or loaded. To avoid this, shrink your "..
|
||||
"area to cover exactly the nodes to be saved.")
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
worldedit.register_command("save", {
|
||||
params = "<file>",
|
||||
description = S("Save the current WorldEdit region to \"(world folder)/schems/<file>.we\""),
|
||||
category = S("Schematics"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
if param == "" then
|
||||
return false
|
||||
end
|
||||
if not check_filename(param) then
|
||||
return false, S("Disallowed file name: @1", param)
|
||||
end
|
||||
return true, param
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, param)
|
||||
local result, count = worldedit.serialize(worldedit.pos1[name],
|
||||
worldedit.pos2[name])
|
||||
detect_misaligned_schematic(name, worldedit.pos1[name], worldedit.pos2[name])
|
||||
|
||||
local path = minetest.get_worldpath() .. "/schems"
|
||||
-- Create directory if it does not already exist
|
||||
minetest.mkdir(path)
|
||||
|
||||
local filename = path .. "/" .. param .. ".we"
|
||||
local file, err = io.open(filename, "wb")
|
||||
if err ~= nil then
|
||||
return false, S("Could not save file to \"@1\"", filename)
|
||||
end
|
||||
file:write(result)
|
||||
file:flush()
|
||||
file:close()
|
||||
|
||||
return true, S("@1 nodes saved", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("allocate", {
|
||||
params = "<file>",
|
||||
description = S("Set the region defined by nodes from \"(world folder)/schems/<file>.we\" as the current WorldEdit region"),
|
||||
category = S("Schematics"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = function(param)
|
||||
if param == "" then
|
||||
return false
|
||||
end
|
||||
if not check_filename(param) then
|
||||
return false, S("Disallowed file name: @1", param)
|
||||
end
|
||||
return true, param
|
||||
end,
|
||||
func = function(name, param)
|
||||
local pos = worldedit.pos1[name]
|
||||
|
||||
local value = open_schematic(name, param)
|
||||
if not value then
|
||||
return false
|
||||
end
|
||||
|
||||
local nodepos1, nodepos2, count = worldedit.allocate(pos, value)
|
||||
if not nodepos1 then
|
||||
return false, S("Schematic empty, nothing allocated")
|
||||
end
|
||||
|
||||
worldedit.pos1[name] = nodepos1
|
||||
worldedit.pos2[name] = nodepos2
|
||||
worldedit.marker_update(name)
|
||||
|
||||
return true, S("@1 nodes allocated", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("load", {
|
||||
params = "<file>",
|
||||
description = S("Load nodes from \"(world folder)/schems/<file>[.we[m]]\" with position 1 of the current WorldEdit region as the origin"),
|
||||
category = S("Schematics"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = function(param)
|
||||
if param == "" then
|
||||
return false
|
||||
end
|
||||
if not check_filename(param) then
|
||||
return false, S("Disallowed file name: @1", param)
|
||||
end
|
||||
return true, param
|
||||
end,
|
||||
func = function(name, param)
|
||||
local pos = worldedit.pos1[name]
|
||||
|
||||
local value = open_schematic(name, param)
|
||||
if not value then
|
||||
return false
|
||||
end
|
||||
|
||||
local count = worldedit.deserialize(pos, value)
|
||||
if count == nil then
|
||||
return false, S("Loading failed!")
|
||||
end
|
||||
return true, S("@1 nodes loaded", count)
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
worldedit.register_command("mtschemcreate", {
|
||||
params = "<file>",
|
||||
description = S("Save the current WorldEdit region using the Minetest "..
|
||||
"Schematic format to \"(world folder)/schems/<filename>.mts\""),
|
||||
category = S("Schematics"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
if param == "" then
|
||||
return false
|
||||
end
|
||||
if not check_filename(param) then
|
||||
return false, S("Disallowed file name: @1", param)
|
||||
end
|
||||
return true, param
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, param)
|
||||
local path = minetest.get_worldpath() .. "/schems"
|
||||
-- Create directory if it does not already exist
|
||||
minetest.mkdir(path)
|
||||
|
||||
local filename = path .. "/" .. param .. ".mts"
|
||||
local ret = minetest.create_schematic(worldedit.pos1[name],
|
||||
worldedit.pos2[name], worldedit.prob_list[name],
|
||||
filename)
|
||||
worldedit.prob_list[name] = {}
|
||||
if ret == nil then
|
||||
return false, S("Failed to create Minetest schematic")
|
||||
end
|
||||
return true, S("Saved Minetest schematic to @1", param)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("mtschemplace", {
|
||||
params = "<file>",
|
||||
description = S("Load nodes from \"(world folder)/schems/<file>.mts\" with position 1 of the current WorldEdit region as the origin"),
|
||||
category = S("Schematics"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 1,
|
||||
parse = function(param)
|
||||
local found, _, filename, rotation = param:find("^(.+)%s+([012789]+)$")
|
||||
if found == nil then
|
||||
filename = param
|
||||
elseif rotation ~= "0" and rotation ~= "90" and rotation ~= "180" and rotation ~= "270" then
|
||||
return false, S("Invalid rotation: @1", rotation)
|
||||
end
|
||||
if not check_filename(filename) then
|
||||
return false, S("Disallowed file name: @1", filename)
|
||||
end
|
||||
return true, filename, rotation
|
||||
end,
|
||||
func = function(name, filename, rotation)
|
||||
local pos = worldedit.pos1[name]
|
||||
|
||||
local path = minetest.get_worldpath() .. "/schems/" .. filename .. ".mts"
|
||||
if minetest.place_schematic(pos, path, rotation) == nil then
|
||||
return false, S("failed to place Minetest schematic")
|
||||
end
|
||||
return true, S("placed Minetest schematic @1 at @2",
|
||||
filename, minetest.pos_to_string(pos))
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("mtschemprob", {
|
||||
params = "start/finish/get",
|
||||
description = S("Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry"),
|
||||
category = S("Schematics"),
|
||||
privs = {worldedit=true},
|
||||
parse = function(param)
|
||||
if param ~= "start" and param ~= "finish" and param ~= "get" then
|
||||
return false, S("unknown subcommand: @1", param)
|
||||
end
|
||||
return true, param
|
||||
end,
|
||||
func = function(name, param)
|
||||
if param == "start" then --start probability setting
|
||||
worldedit.set_pos[name] = "prob"
|
||||
worldedit.prob_list[name] = {}
|
||||
worldedit.player_notify(name, S("select Minetest schematic probability values by punching nodes"), "info")
|
||||
elseif param == "finish" then --finish probability setting
|
||||
worldedit.set_pos[name] = nil
|
||||
worldedit.player_notify(name, S("finished Minetest schematic probability selection"), "info")
|
||||
elseif param == "get" then --get all nodes that had probabilities set on them
|
||||
local text = ""
|
||||
local problist = worldedit.prob_list[name]
|
||||
if problist == nil then
|
||||
return
|
||||
end
|
||||
for k,v in pairs(problist) do
|
||||
local prob = math.floor(((v.prob / 256) * 100) * 100 + 0.5) / 100
|
||||
text = text .. minetest.pos_to_string(v.pos) .. ": " .. prob .. "% | "
|
||||
end
|
||||
worldedit.player_notify(name, S("currently set node probabilities:") .. "\n" .. text, "info")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname == "prob_val_enter" then
|
||||
local name = player:get_player_name()
|
||||
local problist = worldedit.prob_list[name]
|
||||
if problist == nil then
|
||||
return
|
||||
end
|
||||
local e = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)}
|
||||
if e.pos == nil or e.prob == nil or e.prob < 0 or e.prob > 256 then
|
||||
worldedit.player_notify(name, S("invalid node probability given, not saved"), "error")
|
||||
return
|
||||
end
|
||||
problist[#problist+1] = e
|
||||
end
|
||||
end)
|
||||
|
BIN
worldedit_commands/textures/worldedit_cube.png
Normal file
After Width: | Height: | Size: 79 B |
Before Width: | Height: | Size: 142 B After Width: | Height: | Size: 100 B |
Before Width: | Height: | Size: 157 B After Width: | Height: | Size: 112 B |
BIN
worldedit_commands/textures/worldedit_wand.png
Normal file
After Width: | Height: | Size: 303 B |
269
worldedit_commands/transform.lua
Normal file
|
@ -0,0 +1,269 @@
|
|||
local S = minetest.get_translator("worldedit_commands")
|
||||
|
||||
local function check_region(name)
|
||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||
end
|
||||
|
||||
|
||||
worldedit.register_command("copy", {
|
||||
params = "x/y/z/? <amount>",
|
||||
description = S("Copy the current WorldEdit region along the given axis by <amount> nodes"),
|
||||
category = S("Transformations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
return true, axis, tonumber(amount)
|
||||
end,
|
||||
nodes_needed = function(name, axis, amount)
|
||||
return check_region(name) * 2
|
||||
end,
|
||||
func = function(name, axis, amount)
|
||||
if axis == "?" then
|
||||
local sign
|
||||
axis, sign = worldedit.player_axis(name)
|
||||
amount = amount * sign
|
||||
end
|
||||
|
||||
local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)
|
||||
return true, S("@1 nodes copied", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("move", {
|
||||
params = "x/y/z/? <amount>",
|
||||
description = S("Move the current WorldEdit region along the given axis by <amount> nodes"),
|
||||
category = S("Transformations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
return true, axis, tonumber(amount)
|
||||
end,
|
||||
nodes_needed = function(name, axis, amount)
|
||||
return check_region(name) * 2
|
||||
end,
|
||||
func = function(name, axis, amount)
|
||||
if axis == "?" then
|
||||
local sign
|
||||
axis, sign = worldedit.player_axis(name)
|
||||
amount = amount * sign
|
||||
end
|
||||
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
local count = worldedit.move(pos1, pos2, axis, amount)
|
||||
|
||||
pos1[axis] = pos1[axis] + amount
|
||||
pos2[axis] = pos2[axis] + amount
|
||||
worldedit.marker_update(name)
|
||||
return true, S("@1 nodes moved", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("stack", {
|
||||
params = "x/y/z/? <count>",
|
||||
description = S("Stack the current WorldEdit region along the given axis <count> times"),
|
||||
category = S("Transformations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
return true, axis, tonumber(repetitions)
|
||||
end,
|
||||
nodes_needed = function(name, axis, repetitions)
|
||||
return check_region(name) * math.abs(repetitions)
|
||||
end,
|
||||
func = function(name, axis, repetitions)
|
||||
if axis == "?" then
|
||||
local sign
|
||||
axis, sign = worldedit.player_axis(name)
|
||||
repetitions = repetitions * sign
|
||||
end
|
||||
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)
|
||||
worldedit.stack(pos1, pos2, axis, repetitions, function()
|
||||
worldedit.player_notify(name, S("@1 nodes stacked", count), "ok")
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("stack2", {
|
||||
params = "<count> <x> <y> <z>",
|
||||
description = S("Stack the current WorldEdit region <count> times by offset <x>, <y>, <z>"),
|
||||
category = S("Transformations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local repetitions, incs = param:match("(%d+)%s*(.+)")
|
||||
if repetitions == nil then
|
||||
return false, S("invalid count: @1", param)
|
||||
end
|
||||
local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)")
|
||||
if x == nil then
|
||||
return false, S("invalid increments: @1", param)
|
||||
end
|
||||
|
||||
return true, tonumber(repetitions), vector.new(tonumber(x), tonumber(y), tonumber(z))
|
||||
end,
|
||||
nodes_needed = function(name, repetitions, offset)
|
||||
return check_region(name) * repetitions
|
||||
end,
|
||||
func = function(name, repetitions, offset)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
local count = worldedit.volume(pos1, pos2) * repetitions
|
||||
worldedit.stack2(pos1, pos2, offset, repetitions, function()
|
||||
worldedit.player_notify(name, S("@1 nodes stacked", count), "ok")
|
||||
end)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("stretch", {
|
||||
params = "<stretchx> <stretchy> <stretchz>",
|
||||
description = S("Scale the current WorldEdit positions and region by a factor of <stretchx>, <stretchy>, <stretchz> along the X, Y, and Z axes, repectively, with position 1 as the origin"),
|
||||
category = S("Transformations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz)
|
||||
if stretchx == 0 or stretchy == 0 or stretchz == 0 then
|
||||
return false, S("invalid scaling factors: @1", param)
|
||||
end
|
||||
return true, stretchx, stretchy, stretchz
|
||||
end,
|
||||
nodes_needed = function(name, stretchx, stretchy, stretchz)
|
||||
return check_region(name) * stretchx * stretchy * stretchz
|
||||
end,
|
||||
func = function(name, stretchx, stretchy, stretchz)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
|
||||
|
||||
--reset markers to scaled positions
|
||||
worldedit.pos1[name] = pos1
|
||||
worldedit.pos2[name] = pos2
|
||||
worldedit.marker_update(name)
|
||||
|
||||
return true, S("@1 nodes stretched", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("transpose", {
|
||||
params = "x/y/z/? x/y/z/?",
|
||||
description = S("Transpose the current WorldEdit region along the given axes"),
|
||||
category = S("Transformations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$")
|
||||
if found == nil then
|
||||
return false
|
||||
elseif axis1 == axis2 then
|
||||
return false, S("invalid usage: axes must be different")
|
||||
end
|
||||
return true, axis1, axis2
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, axis1, axis2)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
if axis1 == "?" then axis1 = worldedit.player_axis(name) end
|
||||
if axis2 == "?" then axis2 = worldedit.player_axis(name) end
|
||||
local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
|
||||
|
||||
--reset markers to transposed positions
|
||||
worldedit.pos1[name] = pos1
|
||||
worldedit.pos2[name] = pos2
|
||||
worldedit.marker_update(name)
|
||||
|
||||
return true, S("@1 nodes transposed", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("flip", {
|
||||
params = "x/y/z/?",
|
||||
description = S("Flip the current WorldEdit region along the given axis"),
|
||||
category = S("Transformations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then
|
||||
return false
|
||||
end
|
||||
return true, param
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, param)
|
||||
if param == "?" then param = worldedit.player_axis(name) end
|
||||
local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param)
|
||||
return true, S("@1 nodes flipped", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("rotate", {
|
||||
params = "x/y/z/? <angle>",
|
||||
description = S("Rotate the current WorldEdit region around the given axis by angle <angle> (90 degree increment)"),
|
||||
category = S("Transformations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
angle = tonumber(angle)
|
||||
if angle % 90 ~= 0 or angle % 360 == 0 then
|
||||
return false, S("invalid usage: angle must be multiple of 90")
|
||||
end
|
||||
return true, axis, angle
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, axis, angle)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
if axis == "?" then axis = worldedit.player_axis(name) end
|
||||
local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle)
|
||||
|
||||
--reset markers to rotated positions
|
||||
worldedit.pos1[name] = pos1
|
||||
worldedit.pos2[name] = pos2
|
||||
worldedit.marker_update(name)
|
||||
|
||||
return true, S("@1 nodes rotated", count)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_command("orient", {
|
||||
params = "<angle>",
|
||||
description = S("Rotate oriented nodes in the current WorldEdit region around the Y axis by angle <angle> (90 degree increment)"),
|
||||
category = S("Transformations"),
|
||||
privs = {worldedit=true},
|
||||
require_pos = 2,
|
||||
parse = function(param)
|
||||
local found, _, angle = param:find("^([+-]?%d+)$")
|
||||
if found == nil then
|
||||
return false
|
||||
end
|
||||
angle = tonumber(angle)
|
||||
if angle % 90 ~= 0 then
|
||||
return false, S("invalid usage: angle must be multiple of 90")
|
||||
end
|
||||
return true, angle
|
||||
end,
|
||||
nodes_needed = check_region,
|
||||
func = function(name, angle)
|
||||
local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle)
|
||||
return true, S("@1 nodes oriented", count)
|
||||
end,
|
||||
})
|
||||
|
74
worldedit_commands/wand.lua
Normal file
|
@ -0,0 +1,74 @@
|
|||
local S = minetest.get_translator("worldedit_commands")
|
||||
|
||||
local function above_or_under(placer, pointed_thing)
|
||||
if placer:get_player_control().sneak then
|
||||
return pointed_thing.above
|
||||
else
|
||||
return pointed_thing.under
|
||||
end
|
||||
end
|
||||
|
||||
local punched_air_time = {}
|
||||
|
||||
minetest.register_tool(":worldedit:wand", {
|
||||
description = S("WorldEdit Wand tool\nLeft-click to set 1st position, right-click to set 2nd"),
|
||||
inventory_image = "worldedit_wand.png",
|
||||
stack_max = 1, -- there is no need to have more than one
|
||||
liquids_pointable = true, -- ground with only water on can be selected as well
|
||||
|
||||
-- ignore marker cube so the clicking on the position markers works reliably
|
||||
pointabilities = {
|
||||
objects = {
|
||||
["worldedit:region_cube"] = false
|
||||
}
|
||||
},
|
||||
|
||||
on_use = function(itemstack, placer, pointed_thing)
|
||||
if placer == nil or pointed_thing == nil then return end
|
||||
local name = placer:get_player_name()
|
||||
if pointed_thing.type == "node" then
|
||||
-- set and mark pos1
|
||||
worldedit.pos1[name] = above_or_under(placer, pointed_thing)
|
||||
worldedit.mark_pos1(name)
|
||||
elseif pointed_thing.type == "nothing" then
|
||||
local now = minetest.get_us_time()
|
||||
if now - (punched_air_time[name] or 0) < 1000 * 1000 then
|
||||
-- reset markers
|
||||
worldedit.registered_commands["reset"].func(name)
|
||||
end
|
||||
punched_air_time[name] = now
|
||||
elseif pointed_thing.type == "object" then
|
||||
local entity = pointed_thing.ref:get_luaentity()
|
||||
if entity and entity.name == "worldedit:pos2" then
|
||||
-- set pos1 = pos2
|
||||
worldedit.pos1[name] = vector.copy(worldedit.pos2[name])
|
||||
worldedit.mark_pos1(name)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
on_place = function(itemstack, placer, pointed_thing)
|
||||
if placer == nil or (pointed_thing or {}).type ~= "node" then
|
||||
return itemstack
|
||||
end
|
||||
local name = placer:get_player_name()
|
||||
-- set and mark pos2
|
||||
worldedit.pos2[name] = above_or_under(placer, pointed_thing)
|
||||
worldedit.mark_pos2(name)
|
||||
return itemstack -- nothing consumed, nothing changed
|
||||
end,
|
||||
|
||||
on_secondary_use = function(itemstack, user, pointed_thing)
|
||||
if user == nil or (pointed_thing or {}).type ~= "object" then
|
||||
return itemstack
|
||||
end
|
||||
local name = user:get_player_name()
|
||||
local entity = pointed_thing.ref:get_luaentity()
|
||||
if entity and entity.name == "worldedit:pos1" then
|
||||
-- set pos2 = pos1
|
||||
worldedit.pos2[name] = vector.copy(worldedit.pos1[name])
|
||||
worldedit.mark_pos2(name)
|
||||
end
|
||||
return itemstack -- nothing consumed, nothing changed
|
||||
end,
|
||||
})
|
936
worldedit_gui/functionality.lua
Normal file
|
@ -0,0 +1,936 @@
|
|||
--saved state for each player
|
||||
local gui_nodename1 = {} --mapping of player names to node names
|
||||
local gui_nodename2 = {} --mapping of player names to node names
|
||||
local gui_axis1 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below)
|
||||
local gui_axis2 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below)
|
||||
local gui_distance1 = {} --mapping of player names to a distance (arbitrary strings may also appear as values)
|
||||
local gui_distance2 = {} --mapping of player names to a distance (arbitrary strings may also appear as values)
|
||||
local gui_distance3 = {} --mapping of player names to a distance (arbitrary strings may also appear as values)
|
||||
local gui_count1 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values)
|
||||
local gui_count2 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values)
|
||||
local gui_count3 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values)
|
||||
local gui_angle = {} --mapping of player names to an angle (one of 90, 180, 270, representing the angle in degrees clockwise)
|
||||
local gui_filename = {} --mapping of player names to file names
|
||||
local gui_param2 = {} --mapping of player names to param2 values
|
||||
|
||||
--set default values
|
||||
setmetatable(gui_nodename1, {__index = function() return "Cobblestone" end})
|
||||
setmetatable(gui_nodename2, {__index = function() return "Stone" end})
|
||||
setmetatable(gui_axis1, {__index = function() return 4 end})
|
||||
setmetatable(gui_axis2, {__index = function() return 1 end})
|
||||
setmetatable(gui_distance1, {__index = function() return "10" end})
|
||||
setmetatable(gui_distance2, {__index = function() return "5" end})
|
||||
setmetatable(gui_distance3, {__index = function() return "2" end})
|
||||
setmetatable(gui_count1, {__index = function() return "3" end})
|
||||
setmetatable(gui_count2, {__index = function() return "6" end})
|
||||
setmetatable(gui_count3, {__index = function() return "4" end})
|
||||
setmetatable(gui_angle, {__index = function() return 90 end})
|
||||
setmetatable(gui_filename, {__index = function() return "building" end})
|
||||
setmetatable(gui_param2, {__index = function() return "0" end})
|
||||
|
||||
local axis_indices = {["X axis"]=1, ["Y axis"]=2, ["Z axis"]=3, ["Look direction"]=4}
|
||||
local axis_values = {"x", "y", "z", "?"}
|
||||
setmetatable(axis_indices, {__index = function () return 4 end})
|
||||
setmetatable(axis_values, {__index = function () return "?" end})
|
||||
|
||||
local angle_indices = {["90 degrees"]=1, ["180 degrees"]=2, ["270 degrees"]=3}
|
||||
local angle_values = {90, 180, 270}
|
||||
setmetatable(angle_indices, {__index = function () return 1 end})
|
||||
setmetatable(angle_values, {__index = function () return 90 end})
|
||||
|
||||
-- given multiple sets of privileges, produces a single set of privs that would have the same effect as requiring all of them at the same time
|
||||
local combine_privs = function(...)
|
||||
local result = {}
|
||||
for i, privs in ipairs({...}) do
|
||||
for name, value in pairs(privs) do
|
||||
if result[name] ~= nil and result[name] ~= value then --the priv must be both true and false, which can never happen
|
||||
return {__fake_priv_that_nobody_has__=true} --privilege table that can never be satisfied
|
||||
end
|
||||
result[name] = value
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- display node (or unknown_node image otherwise) at specified pos in formspec
|
||||
local formspec_node = function(pos, nodename)
|
||||
local ndef = nodename and minetest.registered_nodes[nodename]
|
||||
if nodename and ndef then
|
||||
return string.format("item_image[%s;1,1;%s]", pos, nodename) ..
|
||||
string.format("tooltip[%s;1,1;%s]", pos, minetest.formspec_escape(ndef.description))
|
||||
else
|
||||
return string.format("image[%s;1,1;worldedit_gui_unknown.png]", pos)
|
||||
end
|
||||
end
|
||||
|
||||
-- two further priv helpers
|
||||
local function we_privs(command)
|
||||
return worldedit.registered_commands[command].privs
|
||||
end
|
||||
|
||||
local function combine_we_privs(list)
|
||||
local args = {}
|
||||
for _, t in ipairs(list) do
|
||||
table.insert(args, we_privs(t))
|
||||
end
|
||||
return combine_privs(unpack(args))
|
||||
end
|
||||
|
||||
-- functions that handle value changing & page reshowing (without submitting)
|
||||
local function copy_changes(name, fields, def)
|
||||
for field, into in pairs(def) do
|
||||
if into ~= true and fields[field] then
|
||||
local value = tostring(fields[field])
|
||||
if into == gui_axis1 or into == gui_axis2 then
|
||||
into[name] = axis_indices[value]
|
||||
elseif into == gui_angle then
|
||||
into[name] = angle_indices[value]
|
||||
else
|
||||
into[name] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function handle_changes(name, identifier, fields, def)
|
||||
local any = false
|
||||
for field, into in pairs(def) do
|
||||
if fields.key_enter_field == field then
|
||||
any = true
|
||||
end
|
||||
-- first condition: buttons (value not saved)
|
||||
-- others: dropdowns which will be sent when their value changes
|
||||
if into == true or into == gui_axis1 or into == gui_axis2 or into == gui_angle then
|
||||
if fields[field] then
|
||||
any = true
|
||||
end
|
||||
end
|
||||
end
|
||||
if not any then
|
||||
return false
|
||||
end
|
||||
|
||||
any = false
|
||||
for field, into in pairs(def) do
|
||||
if into ~= true and fields[field] then
|
||||
local value = tostring(fields[field])
|
||||
if into == gui_axis1 or into == gui_axis2 then
|
||||
into[name] = axis_indices[value]
|
||||
elseif into == gui_angle then
|
||||
into[name] = angle_indices[value]
|
||||
else
|
||||
into[name] = value
|
||||
end
|
||||
|
||||
if into == gui_nodename1 or into == gui_nodename2 then
|
||||
any = true
|
||||
end
|
||||
end
|
||||
end
|
||||
-- Only nodename fields change based on the value, so only re-show the page if necessary
|
||||
if any then
|
||||
worldedit.show_page(name, identifier)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- This has the same behaviour as the player invoking the chat command
|
||||
local function execute_worldedit_command(command_name, player_name, params)
|
||||
local chatcmd = minetest.registered_chatcommands["/" .. command_name]
|
||||
assert(chatcmd, "unknown command: " .. command_name)
|
||||
local _, msg = chatcmd.func(player_name, params)
|
||||
if msg then
|
||||
minetest.chat_send_player(player_name, msg)
|
||||
end
|
||||
end
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_about", {
|
||||
name = "About",
|
||||
privs = {interact=true},
|
||||
on_select = function(name)
|
||||
execute_worldedit_command("about", name, "")
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_inspect", {
|
||||
name = "Toggle Inspect",
|
||||
privs = we_privs("inspect"),
|
||||
on_select = function(name)
|
||||
execute_worldedit_command("inspect", name,
|
||||
worldedit.inspect[name] and "disable" or "enable")
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_region", {
|
||||
name = "Get/Set Region",
|
||||
privs = combine_we_privs({"p", "pos1", "pos2", "reset", "mark", "unmark", "volume", "fixedpos"}),
|
||||
get_formspec = function(name)
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
return "size[9,7]" .. worldedit.get_formspec_header("worldedit_gui_region") ..
|
||||
"button_exit[0,1;3,0.8;worldedit_gui_p_get;Get Positions]" ..
|
||||
"button_exit[3,1;3,0.8;worldedit_gui_p_set1;Choose Position 1]" ..
|
||||
"button_exit[6,1;3,0.8;worldedit_gui_p_set2;Choose Position 2]" ..
|
||||
"button_exit[0,2;3,0.8;worldedit_gui_pos1;Position 1 Here]" ..
|
||||
"button_exit[3,2;3,0.8;worldedit_gui_pos2;Position 2 Here]" ..
|
||||
"button_exit[6,2;3,0.8;worldedit_gui_reset;Reset Region]" ..
|
||||
"button_exit[0,3;3,0.8;worldedit_gui_mark;Mark Region]" ..
|
||||
"button_exit[3,3;3,0.8;worldedit_gui_unmark;Unmark Region]" ..
|
||||
"button_exit[6,3;3,0.8;worldedit_gui_volume;Region Volume]" ..
|
||||
"label[0,4.7;Position 1]" ..
|
||||
string.format("field[2,5;1.5,0.8;worldedit_gui_fixedpos_pos1x;X ;%s]", pos1 and pos1.x or "") ..
|
||||
string.format("field[3.5,5;1.5,0.8;worldedit_gui_fixedpos_pos1y;Y ;%s]", pos1 and pos1.y or "") ..
|
||||
string.format("field[5,5;1.5,0.8;worldedit_gui_fixedpos_pos1z;Z ;%s]", pos1 and pos1.z or "") ..
|
||||
"button_exit[6.5,4.68;2.5,0.8;worldedit_gui_fixedpos_pos1_submit;Set Position 1]" ..
|
||||
"label[0,6.2;Position 2]" ..
|
||||
string.format("field[2,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2x;X ;%s]", pos2 and pos2.x or "") ..
|
||||
string.format("field[3.5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2y;Y ;%s]", pos2 and pos2.y or "") ..
|
||||
string.format("field[5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2z;Z ;%s]", pos2 and pos2.z or "") ..
|
||||
"button_exit[6.5,6.18;2.5,0.8;worldedit_gui_fixedpos_pos2_submit;Set Position 2]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_region", function(name, fields)
|
||||
if fields.worldedit_gui_p_get then
|
||||
execute_worldedit_command("p", name, "get")
|
||||
return true
|
||||
elseif fields.worldedit_gui_p_set1 then
|
||||
execute_worldedit_command("p", name, "set1")
|
||||
return true
|
||||
elseif fields.worldedit_gui_p_set2 then
|
||||
execute_worldedit_command("p", name, "set2")
|
||||
return true
|
||||
elseif fields.worldedit_gui_pos1 then
|
||||
execute_worldedit_command("pos1", name, "")
|
||||
worldedit.show_page(name, "worldedit_gui_region")
|
||||
return true
|
||||
elseif fields.worldedit_gui_pos2 then
|
||||
execute_worldedit_command("pos2", name, "")
|
||||
worldedit.show_page(name, "worldedit_gui_region")
|
||||
return true
|
||||
elseif fields.worldedit_gui_reset then
|
||||
execute_worldedit_command("reset", name, "")
|
||||
worldedit.show_page(name, "worldedit_gui_region")
|
||||
return true
|
||||
elseif fields.worldedit_gui_mark then
|
||||
execute_worldedit_command("mark", name, "")
|
||||
worldedit.show_page(name, "worldedit_gui_region")
|
||||
return true
|
||||
elseif fields.worldedit_gui_unmark then
|
||||
execute_worldedit_command("unmark", name, "")
|
||||
worldedit.show_page(name, "worldedit_gui_region")
|
||||
return true
|
||||
elseif fields.worldedit_gui_volume then
|
||||
execute_worldedit_command("volume", name, "")
|
||||
worldedit.show_page(name, "worldedit_gui_region")
|
||||
return true
|
||||
elseif fields.worldedit_gui_fixedpos_pos1_submit then
|
||||
execute_worldedit_command("fixedpos", name, ("set1 %s %s %s"):format(
|
||||
tostring(fields.worldedit_gui_fixedpos_pos1x),
|
||||
tostring(fields.worldedit_gui_fixedpos_pos1y),
|
||||
tostring(fields.worldedit_gui_fixedpos_pos1z)))
|
||||
worldedit.show_page(name, "worldedit_gui_region")
|
||||
return true
|
||||
elseif fields.worldedit_gui_fixedpos_pos2_submit then
|
||||
execute_worldedit_command("fixedpos", name, ("set2 %s %s %s"):format(
|
||||
tostring(fields.worldedit_gui_fixedpos_pos2x),
|
||||
tostring(fields.worldedit_gui_fixedpos_pos2y),
|
||||
tostring(fields.worldedit_gui_fixedpos_pos2z)))
|
||||
worldedit.show_page(name, "worldedit_gui_region")
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_set", {
|
||||
name = "Set Nodes",
|
||||
privs = we_privs("set"),
|
||||
get_formspec = function(name)
|
||||
local node = gui_nodename1[name]
|
||||
local nodename = worldedit.normalize_nodename(node)
|
||||
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_set") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_set_node;Name;%s]", minetest.formspec_escape(node)) ..
|
||||
"field_close_on_enter[worldedit_gui_set_node;false]" ..
|
||||
"button[4,1.18;1.5,0.8;worldedit_gui_set_search;Search]" ..
|
||||
formspec_node("5.5,1.1", nodename) ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_set_submit;Set Nodes]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_set", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_set_search = true,
|
||||
worldedit_gui_set_node = gui_nodename1,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_set", fields, cg)
|
||||
if fields.worldedit_gui_set_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_set")
|
||||
|
||||
local n = worldedit.normalize_nodename(gui_nodename1[name])
|
||||
if n then
|
||||
execute_worldedit_command("set", name, n)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_replace", {
|
||||
name = "Replace Nodes",
|
||||
privs = combine_we_privs({"replace", "replaceinverse"}),
|
||||
get_formspec = function(name)
|
||||
local search, replace = gui_nodename1[name], gui_nodename2[name]
|
||||
local search_nodename, replace_nodename = worldedit.normalize_nodename(search), worldedit.normalize_nodename(replace)
|
||||
return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_replace") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_replace_search;Name;%s]", minetest.formspec_escape(search)) ..
|
||||
"field_close_on_enter[worldedit_gui_replace_search;false]" ..
|
||||
"button[4,1.18;1.5,0.8;worldedit_gui_replace_search_search;Search]" ..
|
||||
formspec_node("5.5,1.1", search_nodename) ..
|
||||
string.format("field[0.5,2.5;4,0.8;worldedit_gui_replace_replace;Name;%s]", minetest.formspec_escape(replace)) ..
|
||||
"field_close_on_enter[worldedit_gui_replace_replace;false]" ..
|
||||
"button[4,2.18;1.5,0.8;worldedit_gui_replace_replace_search;Search]" ..
|
||||
formspec_node("5.5,2.1", replace_nodename) ..
|
||||
"button_exit[0,3.5;3,0.8;worldedit_gui_replace_submit;Replace Nodes]" ..
|
||||
"button_exit[3.5,3.5;3,0.8;worldedit_gui_replace_submit_inverse;Replace Inverse]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_replace", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_replace_search_search = true,
|
||||
worldedit_gui_replace_replace_search = true,
|
||||
worldedit_gui_replace_search = gui_nodename1,
|
||||
worldedit_gui_replace_replace = gui_nodename2,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_replace", fields, cg)
|
||||
if fields.worldedit_gui_replace_submit or fields.worldedit_gui_replace_submit_inverse then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_replace")
|
||||
|
||||
local submit = "replace"
|
||||
if fields.worldedit_gui_replace_submit_inverse then
|
||||
submit = "replaceinverse"
|
||||
end
|
||||
local n1 = worldedit.normalize_nodename(gui_nodename1[name])
|
||||
local n2 = worldedit.normalize_nodename(gui_nodename2[name])
|
||||
if n1 and n2 then
|
||||
execute_worldedit_command(submit, name, n1 .. " " .. n2)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_sphere_dome", {
|
||||
name = "Sphere/Dome",
|
||||
privs = combine_we_privs({"hollowsphere", "sphere", "hollowdome", "dome"}),
|
||||
get_formspec = function(name)
|
||||
local node, radius = gui_nodename1[name], gui_distance2[name]
|
||||
local nodename = worldedit.normalize_nodename(node)
|
||||
return "size[6.5,5]" .. worldedit.get_formspec_header("worldedit_gui_sphere_dome") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_sphere_dome_node;Name;%s]", minetest.formspec_escape(node)) ..
|
||||
"field_close_on_enter[worldedit_gui_sphere_dome_node;false]" ..
|
||||
"button[4,1.18;1.5,0.8;worldedit_gui_sphere_dome_search;Search]" ..
|
||||
formspec_node("5.5,1.1", nodename) ..
|
||||
string.format("field[0.5,2.5;4,0.8;worldedit_gui_sphere_dome_radius;Radius;%s]", minetest.formspec_escape(radius)) ..
|
||||
"field_close_on_enter[worldedit_gui_sphere_dome_radius;false]" ..
|
||||
"button_exit[0,3.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow;Hollow Sphere]" ..
|
||||
"button_exit[3.5,3.5;3,0.8;worldedit_gui_sphere_dome_submit_solid;Solid Sphere]" ..
|
||||
"button_exit[0,4.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow_dome;Hollow Dome]" ..
|
||||
"button_exit[3.5,4.5;3,0.8;worldedit_gui_sphere_dome_submit_solid_dome;Solid Dome]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_sphere_dome", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_sphere_dome_search = true,
|
||||
worldedit_gui_sphere_dome_node = gui_nodename1,
|
||||
worldedit_gui_sphere_dome_radius = gui_distance2,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_sphere_dome", fields, cg)
|
||||
if fields.worldedit_gui_sphere_dome_submit_hollow or fields.worldedit_gui_sphere_dome_submit_solid
|
||||
or fields.worldedit_gui_sphere_dome_submit_hollow_dome or fields.worldedit_gui_sphere_dome_submit_solid_dome then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_sphere_dome")
|
||||
|
||||
local submit = "hollowsphere"
|
||||
if fields.worldedit_gui_sphere_dome_submit_solid then
|
||||
submit = "sphere"
|
||||
elseif fields.worldedit_gui_sphere_dome_submit_hollow_dome then
|
||||
submit = "hollowdome"
|
||||
elseif fields.worldedit_gui_sphere_dome_submit_solid_dome then
|
||||
submit = "dome"
|
||||
end
|
||||
local n = worldedit.normalize_nodename(gui_nodename1[name])
|
||||
if n then
|
||||
execute_worldedit_command(submit, name,
|
||||
gui_distance2[name] .. " " .. n)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_cylinder", {
|
||||
name = "Cylinder",
|
||||
privs = combine_we_privs({"hollowcylinder", "cylinder"}),
|
||||
get_formspec = function(name)
|
||||
local node, axis, length = gui_nodename1[name], gui_axis1[name], gui_distance1[name]
|
||||
local radius1, radius2 = gui_distance2[name], gui_distance3[name]
|
||||
local nodename = worldedit.normalize_nodename(node)
|
||||
return "size[6.5,6]" .. worldedit.get_formspec_header("worldedit_gui_cylinder") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_cylinder_node;Name;%s]", minetest.formspec_escape(node)) ..
|
||||
"field_close_on_enter[worldedit_gui_cylinder_node;false]" ..
|
||||
"button[4,1.18;1.5,0.8;worldedit_gui_cylinder_search;Search]" ..
|
||||
formspec_node("5.5,1.1", nodename) ..
|
||||
string.format("field[0.5,2.5;4,0.8;worldedit_gui_cylinder_length;Length;%s]", minetest.formspec_escape(length)) ..
|
||||
string.format("dropdown[4,2.18;2.5;worldedit_gui_cylinder_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
|
||||
string.format("field[0.5,3.5;2,0.8;worldedit_gui_cylinder_radius1;Base Radius;%s]", minetest.formspec_escape(radius1)) ..
|
||||
string.format("field[2.5,3.5;2,0.8;worldedit_gui_cylinder_radius2;Top Radius;%s]", minetest.formspec_escape(radius2)) ..
|
||||
"field_close_on_enter[worldedit_gui_cylinder_length;false]" ..
|
||||
"field_close_on_enter[worldedit_gui_cylinder_radius1;false]" ..
|
||||
"field_close_on_enter[worldedit_gui_cylinder_radius2;false]" ..
|
||||
"label[0.25,4;Equal base and top radius creates a cylinder,\n"..
|
||||
"zero top radius creates a cone.\nConsult documentation for more information.]"..
|
||||
"button_exit[0,5.5;3,0.8;worldedit_gui_cylinder_submit_hollow;Hollow Cylinder]" ..
|
||||
"button_exit[3.5,5.5;3,0.8;worldedit_gui_cylinder_submit_solid;Solid Cylinder]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_cylinder", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_cylinder_search = true,
|
||||
worldedit_gui_cylinder_node = gui_nodename1,
|
||||
worldedit_gui_cylinder_axis = gui_axis1,
|
||||
worldedit_gui_cylinder_length = gui_distance1,
|
||||
worldedit_gui_cylinder_radius1 = gui_distance2,
|
||||
worldedit_gui_cylinder_radius2 = gui_distance3,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_cylinder", fields, cg)
|
||||
if fields.worldedit_gui_cylinder_submit_hollow or fields.worldedit_gui_cylinder_submit_solid then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_cylinder")
|
||||
|
||||
local submit = "hollowcylinder"
|
||||
if fields.worldedit_gui_cylinder_submit_solid then
|
||||
submit = "cylinder"
|
||||
end
|
||||
local n = worldedit.normalize_nodename(gui_nodename1[name])
|
||||
if n then
|
||||
local args = string.format("%s %s %s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_distance2[name], gui_distance3[name], n)
|
||||
execute_worldedit_command(submit, name, args)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_pyramid", {
|
||||
name = "Pyramid",
|
||||
privs = we_privs("pyramid"),
|
||||
get_formspec = function(name)
|
||||
local node, axis, length = gui_nodename1[name], gui_axis1[name], gui_distance1[name]
|
||||
local nodename = worldedit.normalize_nodename(node)
|
||||
return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_pyramid") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_pyramid_node;Name;%s]", minetest.formspec_escape(node)) ..
|
||||
"field_close_on_enter[worldedit_gui_pyramid_node;false]" ..
|
||||
"button[4,1.18;1.5,0.8;worldedit_gui_pyramid_search;Search]" ..
|
||||
formspec_node("5.5,1.1", nodename) ..
|
||||
string.format("field[0.5,2.5;4,0.8;worldedit_gui_pyramid_length;Length;%s]", minetest.formspec_escape(length)) ..
|
||||
string.format("dropdown[4,2.18;2.5;worldedit_gui_pyramid_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
|
||||
"field_close_on_enter[worldedit_gui_pyramid_length;false]" ..
|
||||
"button_exit[0,3.5;3,0.8;worldedit_gui_pyramid_submit_hollow;Hollow Pyramid]" ..
|
||||
"button_exit[3.5,3.5;3,0.8;worldedit_gui_pyramid_submit_solid;Solid Pyramid]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_pyramid", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_pyramid_search = true,
|
||||
worldedit_gui_pyramid_node = gui_nodename1,
|
||||
worldedit_gui_pyramid_axis = gui_axis1,
|
||||
worldedit_gui_pyramid_length = gui_distance1,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_pyramid", fields, cg)
|
||||
if fields.worldedit_gui_pyramid_submit_solid or fields.worldedit_gui_pyramid_submit_hollow then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_pyramid")
|
||||
|
||||
local submit = "pyramid"
|
||||
if fields.worldedit_gui_pyramid_submit_hollow then
|
||||
submit = "hollowpyramid"
|
||||
end
|
||||
local n = worldedit.normalize_nodename(gui_nodename1[name])
|
||||
if n then
|
||||
execute_worldedit_command(submit, name,
|
||||
string.format("%s %s %s", axis_values[gui_axis1[name]],
|
||||
gui_distance1[name], n))
|
||||
end
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_spiral", {
|
||||
name = "Spiral",
|
||||
privs = we_privs("spiral"),
|
||||
get_formspec = function(name)
|
||||
local node, length, height, space = gui_nodename1[name], gui_distance1[name], gui_distance2[name], gui_distance3[name]
|
||||
local nodename = worldedit.normalize_nodename(node)
|
||||
return "size[6.5,6]" .. worldedit.get_formspec_header("worldedit_gui_spiral") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_spiral_node;Name;%s]", minetest.formspec_escape(node)) ..
|
||||
"field_close_on_enter[worldedit_gui_spiral_node;false]" ..
|
||||
"button[4,1.18;1.5,0.8;worldedit_gui_spiral_search;Search]" ..
|
||||
formspec_node("5.5,1.1", nodename) ..
|
||||
string.format("field[0.5,2.5;4,0.8;worldedit_gui_spiral_length;Side Length;%s]", minetest.formspec_escape(length)) ..
|
||||
string.format("field[0.5,3.5;4,0.8;worldedit_gui_spiral_height;Height;%s]", minetest.formspec_escape(height)) ..
|
||||
string.format("field[0.5,4.5;4,0.8;worldedit_gui_spiral_space;Wall Spacing;%s]", minetest.formspec_escape(space)) ..
|
||||
"field_close_on_enter[worldedit_gui_spiral_length;false]" ..
|
||||
"field_close_on_enter[worldedit_gui_spiral_height;false]" ..
|
||||
"field_close_on_enter[worldedit_gui_spiral_space;false]" ..
|
||||
"button_exit[0,5.5;3,0.8;worldedit_gui_spiral_submit;Spiral]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_spiral", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_spiral_search = true,
|
||||
worldedit_gui_spiral_node = gui_nodename1,
|
||||
worldedit_gui_spiral_length = gui_distance1,
|
||||
worldedit_gui_spiral_height = gui_distance2,
|
||||
worldedit_gui_spiral_space = gui_distance3,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_spiral", fields, cg)
|
||||
if fields.worldedit_gui_spiral_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_spiral")
|
||||
|
||||
local n = worldedit.normalize_nodename(gui_nodename1[name])
|
||||
if n then
|
||||
execute_worldedit_command("spiral", name,
|
||||
string.format("%s %s %s %s", gui_distance1[name],
|
||||
gui_distance2[name], gui_distance3[name], n))
|
||||
end
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_copy_move", {
|
||||
name = "Copy/Move",
|
||||
privs = combine_we_privs({"copy", "move"}),
|
||||
get_formspec = function(name)
|
||||
local axis = gui_axis1[name] or 4
|
||||
local amount = gui_distance1[name] or "10"
|
||||
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_copy_move") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_copy_move_amount;Amount;%s]", minetest.formspec_escape(amount)) ..
|
||||
string.format("dropdown[4,1.18;2.5;worldedit_gui_copy_move_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
|
||||
"field_close_on_enter[worldedit_gui_copy_move_amount;false]" ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_copy_move_copy;Copy Region]" ..
|
||||
"button_exit[3.5,2.5;3,0.8;worldedit_gui_copy_move_move;Move Region]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_copy_move", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_copy_move_amount = gui_distance1,
|
||||
worldedit_gui_copy_move_axis = gui_axis1,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_spiral", fields, cg)
|
||||
if fields.worldedit_gui_copy_move_copy or fields.worldedit_gui_copy_move_move then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_copy_move")
|
||||
|
||||
local submit = "copy"
|
||||
if fields.worldedit_gui_copy_move_move then
|
||||
submit = "move"
|
||||
end
|
||||
execute_worldedit_command(submit, name,
|
||||
axis_values[gui_axis1[name]] .. " " .. gui_distance1[name])
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_stack", {
|
||||
name = "Stack",
|
||||
privs = we_privs("stack"),
|
||||
get_formspec = function(name)
|
||||
local axis, count = gui_axis1[name], gui_count1[name]
|
||||
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_stack") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_stack_count;Count;%s]", minetest.formspec_escape(count)) ..
|
||||
string.format("dropdown[4,1.18;2.5;worldedit_gui_stack_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
|
||||
"field_close_on_enter[worldedit_gui_stack_count;false]" ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_stack_submit;Stack]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_stack", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_stack_axis = gui_axis1,
|
||||
worldedit_gui_stack_count = gui_count1,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_stack", fields, cg)
|
||||
if fields.worldedit_gui_stack_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_stack")
|
||||
|
||||
execute_worldedit_command("stack", name,
|
||||
axis_values[gui_axis1[name]] .. " " .. gui_count1[name])
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_stretch", {
|
||||
name = "Stretch",
|
||||
privs = we_privs("stretch"),
|
||||
get_formspec = function(name)
|
||||
local stretchx, stretchy, stretchz = gui_count1[name], gui_count2[name], gui_count3[name]
|
||||
return "size[5,5]" .. worldedit.get_formspec_header("worldedit_gui_stretch") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_stretch_x;Stretch X;%s]", minetest.formspec_escape(stretchx)) ..
|
||||
string.format("field[0.5,2.5;4,0.8;worldedit_gui_stretch_y;Stretch Y;%s]", minetest.formspec_escape(stretchy)) ..
|
||||
string.format("field[0.5,3.5;4,0.8;worldedit_gui_stretch_z;Stretch Z;%s]", minetest.formspec_escape(stretchz)) ..
|
||||
"field_close_on_enter[worldedit_gui_stretch_x;false]" ..
|
||||
"field_close_on_enter[worldedit_gui_stretch_y;false]" ..
|
||||
"field_close_on_enter[worldedit_gui_stretch_z;false]" ..
|
||||
"button_exit[0,4.5;3,0.8;worldedit_gui_stretch_submit;Stretch]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_stretch", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_stretch_x = gui_count1,
|
||||
worldedit_gui_stretch_y = gui_count2,
|
||||
worldedit_gui_stretch_z = gui_count3,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_stretch", fields, cg)
|
||||
if fields.worldedit_gui_stretch_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_stretch")
|
||||
|
||||
execute_worldedit_command("stretch", name, string.format("%s %s %s",
|
||||
gui_count1[name], gui_count2[name], gui_count3[name]))
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_transpose", {
|
||||
name = "Transpose",
|
||||
privs = we_privs("transpose"),
|
||||
get_formspec = function(name)
|
||||
local axis1, axis2 = gui_axis1[name], gui_axis2[name]
|
||||
return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_transpose") ..
|
||||
string.format("dropdown[0,1;2.5;worldedit_gui_transpose_axis1;X axis,Y axis,Z axis,Look direction;%d]", axis1) ..
|
||||
string.format("dropdown[3,1;2.5;worldedit_gui_transpose_axis2;X axis,Y axis,Z axis,Look direction;%d]", axis2) ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_transpose_submit;Transpose]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_transpose", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_transpose_axis1 = gui_axis1,
|
||||
worldedit_gui_transpose_axis2 = gui_axis2,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_transpose", fields, cg)
|
||||
if fields.worldedit_gui_transpose_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
|
||||
execute_worldedit_command("transpose", name,
|
||||
axis_values[gui_axis1[name]] .. " " .. axis_values[gui_axis2[name]])
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_flip", {
|
||||
name = "Flip",
|
||||
privs = we_privs("flip"),
|
||||
get_formspec = function(name)
|
||||
local axis = gui_axis1[name]
|
||||
return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_flip") ..
|
||||
string.format("dropdown[0,1;2.5;worldedit_gui_flip_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_flip_submit;Flip]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_flip", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_flip_axis = gui_axis1
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_flip", fields, cg)
|
||||
if fields.worldedit_gui_flip_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_flip")
|
||||
|
||||
execute_worldedit_command("flip", name, axis_values[gui_axis1[name]])
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_rotate", {
|
||||
name = "Rotate",
|
||||
privs = we_privs("rotate"),
|
||||
get_formspec = function(name)
|
||||
local axis, angle = gui_axis1[name], gui_angle[name]
|
||||
return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_rotate") ..
|
||||
string.format("dropdown[0,1;2.5;worldedit_gui_rotate_angle;90 degrees,180 degrees,270 degrees;%s]", angle) ..
|
||||
string.format("dropdown[3,1;2.5;worldedit_gui_rotate_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_rotate_submit;Rotate]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_rotate", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_rotate_axis = gui_axis1,
|
||||
worldedit_gui_rotate_angle = gui_angle,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_rotate", fields, cg)
|
||||
if fields.worldedit_gui_rotate_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_rotate")
|
||||
|
||||
execute_worldedit_command("rotate", name,
|
||||
axis_values[gui_axis1[name]] .. " " .. angle_values[gui_angle[name]])
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_orient", {
|
||||
name = "Orient",
|
||||
privs = we_privs("orient"),
|
||||
get_formspec = function(name)
|
||||
local angle = gui_angle[name]
|
||||
return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_orient") ..
|
||||
string.format("dropdown[0,1;2.5;worldedit_gui_orient_angle;90 degrees,180 degrees,270 degrees;%s]", angle) ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_orient_submit;Orient]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_orient", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_orient_angle = gui_angle,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_orient", fields, cg)
|
||||
if fields.worldedit_gui_orient_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_orient")
|
||||
|
||||
execute_worldedit_command("orient", name,
|
||||
tostring(angle_values[gui_angle[name]]))
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_fixlight", {
|
||||
name = "Fix Lighting",
|
||||
privs = we_privs("fixlight"),
|
||||
on_select = function(name)
|
||||
execute_worldedit_command("fixlight", name, "")
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_hide", {
|
||||
name = "Hide Region",
|
||||
privs = we_privs("hide"),
|
||||
on_select = function(name)
|
||||
execute_worldedit_command("hide", name, "")
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_suppress", {
|
||||
name = "Suppress Nodes",
|
||||
privs = we_privs("suppress"),
|
||||
get_formspec = function(name)
|
||||
local node = gui_nodename1[name]
|
||||
local nodename = worldedit.normalize_nodename(node)
|
||||
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_suppress") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_suppress_node;Name;%s]", minetest.formspec_escape(node)) ..
|
||||
"field_close_on_enter[worldedit_gui_suppress_node;false]" ..
|
||||
"button[4,1.18;1.5,0.8;worldedit_gui_suppress_search;Search]" ..
|
||||
formspec_node("5.5,1.1", nodename) ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_suppress_submit;Suppress Nodes]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_suppress", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_suppress_search = true,
|
||||
worldedit_gui_suppress_node = gui_nodename1,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_suppress", fields, cg)
|
||||
if fields.worldedit_gui_suppress_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_suppress")
|
||||
|
||||
local n = worldedit.normalize_nodename(gui_nodename1[name])
|
||||
if n then
|
||||
execute_worldedit_command("suppress", name, n)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_highlight", {
|
||||
name = "Highlight Nodes",
|
||||
privs = we_privs("highlight"),
|
||||
get_formspec = function(name)
|
||||
local node = gui_nodename1[name]
|
||||
local nodename = worldedit.normalize_nodename(node)
|
||||
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_highlight") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_highlight_node;Name;%s]", minetest.formspec_escape(node)) ..
|
||||
"field_close_on_enter[worldedit_gui_highlight_node;false]" ..
|
||||
"button[4,1.18;1.5,0.8;worldedit_gui_highlight_search;Search]" ..
|
||||
formspec_node("5.5,1.1", nodename) ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_highlight_submit;Highlight Nodes]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_highlight", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_highlight_search = true,
|
||||
worldedit_gui_highlight_node = gui_nodename1,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_highlight", fields, cg)
|
||||
if fields.worldedit_gui_highlight_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_highlight")
|
||||
|
||||
local n = worldedit.normalize_nodename(gui_nodename1[name])
|
||||
if n then
|
||||
execute_worldedit_command("highlight", name, n)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_restore", {
|
||||
name = "Restore Region",
|
||||
privs = we_privs("restore"),
|
||||
on_select = function(name)
|
||||
execute_worldedit_command("restore", name, "")
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_save_load", {
|
||||
name = "Save/Load",
|
||||
privs = combine_we_privs({"save", "allocate", "load"}),
|
||||
get_formspec = function(name)
|
||||
local filename = gui_filename[name]
|
||||
return "size[6,4]" .. worldedit.get_formspec_header("worldedit_gui_save_load") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_save_filename;Filename;%s]", minetest.formspec_escape(filename)) ..
|
||||
"field_close_on_enter[worldedit_gui_save_filename;false]" ..
|
||||
"button_exit[0,2.5;3,0.8;worldedit_gui_save_load_submit_save;Save]" ..
|
||||
"button_exit[3,2.5;3,0.8;worldedit_gui_save_load_submit_allocate;Allocate]" ..
|
||||
"button_exit[0,3.5;3,0.8;worldedit_gui_save_load_submit_load;Load]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_save_load", function(name, fields)
|
||||
if fields.worldedit_gui_save_load_submit_save or fields.worldedit_gui_save_load_submit_allocate or fields.worldedit_gui_save_load_submit_load then
|
||||
gui_filename[name] = tostring(fields.worldedit_gui_save_filename)
|
||||
worldedit.show_page(name, "worldedit_gui_save_load")
|
||||
|
||||
if fields.worldedit_gui_save_load_submit_save then
|
||||
execute_worldedit_command("save", name, gui_filename[name])
|
||||
elseif fields.worldedit_gui_save_load_submit_allocate then
|
||||
execute_worldedit_command("allocate", name, gui_filename[name])
|
||||
else --fields.worldedit_gui_save_load_submit_load
|
||||
execute_worldedit_command("load", name, gui_filename[name])
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_cube", {
|
||||
name = "Cube",
|
||||
privs = combine_we_privs({"hollowcube", "cube"}),
|
||||
get_formspec = function(name)
|
||||
local width, height, length = gui_distance1[name], gui_distance2[name], gui_distance3[name]
|
||||
local node = gui_nodename1[name]
|
||||
local nodename = worldedit.normalize_nodename(node)
|
||||
return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_cube") ..
|
||||
string.format("field[0.5,1.5;4,0.8;worldedit_gui_cube_node;Name;%s]", minetest.formspec_escape(node)) ..
|
||||
"field_close_on_enter[worldedit_gui_cube_node;false]" ..
|
||||
"button[4,1.18;1.5,0.8;worldedit_gui_cube_search;Search]" ..
|
||||
formspec_node("5.5,1.1", nodename) ..
|
||||
string.format("field[0.5,2.5;1,0.8;worldedit_gui_cube_width;Width;%s]", minetest.formspec_escape(width)) ..
|
||||
string.format("field[1.5,2.5;1,0.8;worldedit_gui_cube_height;Height;%s]", minetest.formspec_escape(height)) ..
|
||||
string.format("field[2.5,2.5;1,0.8;worldedit_gui_cube_length;Length;%s]", minetest.formspec_escape(length)) ..
|
||||
"field_close_on_enter[worldedit_gui_cube_width;false]" ..
|
||||
"field_close_on_enter[worldedit_gui_cube_height;false]" ..
|
||||
"field_close_on_enter[worldedit_gui_cube_length;false]" ..
|
||||
"button_exit[0,3.5;3,0.8;worldedit_gui_cube_submit_hollow;Hollow Cuboid]" ..
|
||||
"button_exit[3.5,3.5;3,0.8;worldedit_gui_cube_submit_solid;Solid Cuboid]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_cube", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_cube_search = true,
|
||||
worldedit_gui_cube_node = gui_nodename1,
|
||||
worldedit_gui_cube_width = gui_distance1,
|
||||
worldedit_gui_cube_height = gui_distance2,
|
||||
worldedit_gui_cube_length = gui_distance3,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_cube", fields, cg)
|
||||
if fields.worldedit_gui_cube_submit_hollow or fields.worldedit_gui_cube_submit_solid then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_cube")
|
||||
|
||||
local submit = "hollowcube"
|
||||
if fields.worldedit_gui_cube_submit_solid then
|
||||
submit = "cube"
|
||||
end
|
||||
local n = worldedit.normalize_nodename(gui_nodename1[name])
|
||||
if n then
|
||||
local args = string.format("%s %s %s %s", gui_distance1[name], gui_distance2[name], gui_distance3[name], n)
|
||||
execute_worldedit_command(submit, name, args)
|
||||
end
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_clearobjects", {
|
||||
name = "Clear Objects",
|
||||
privs = we_privs("clearobjects"),
|
||||
on_select = function(name)
|
||||
execute_worldedit_command("clearobjects", name, "")
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_param2", {
|
||||
name = "Set Param2",
|
||||
privs = we_privs("param2"),
|
||||
get_formspec = function(name)
|
||||
local value = gui_param2[name] or "0"
|
||||
return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_param2") ..
|
||||
"textarea[0.5,1;5,2;;;Some values may break the node!]"..
|
||||
string.format("field[0.5,2.5;2,0.8;worldedit_gui_param2_value;New Param2;%s]", minetest.formspec_escape(value)) ..
|
||||
"field_close_on_enter[worldedit_gui_param2_value;false]" ..
|
||||
"button_exit[3.5,2.5;3,0.8;worldedit_gui_param2_submit;Set Param2]"
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_param2", function(name, fields)
|
||||
local cg = {
|
||||
worldedit_gui_param2_value = gui_param2,
|
||||
}
|
||||
local ret = handle_changes(name, "worldedit_gui_param2", fields, cg)
|
||||
if fields.worldedit_gui_param2_submit then
|
||||
copy_changes(name, fields, cg)
|
||||
worldedit.show_page(name, "worldedit_gui_param2")
|
||||
|
||||
execute_worldedit_command("param2", name, gui_param2[name])
|
||||
return true
|
||||
end
|
||||
return ret
|
||||
end)
|
282
worldedit_gui/init.lua
Normal file
|
@ -0,0 +1,282 @@
|
|||
worldedit = worldedit or {}
|
||||
|
||||
--[[
|
||||
Example:
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui_hollow_cylinder", {
|
||||
name = "Make Hollow Cylinder",
|
||||
privs = {worldedit=true},
|
||||
get_formspec = function(name) return "some formspec here" end,
|
||||
on_select = function(name) print(name .. " clicked the button!") end,
|
||||
})
|
||||
|
||||
Use `nil` for the `options` parameter to unregister the function associated with the given identifier.
|
||||
|
||||
Use `nil` for the `get_formspec` field to denote that the function does not have its own screen.
|
||||
|
||||
The `privs` field may not be `nil`.
|
||||
|
||||
If the identifier is already registered to another function, it will be replaced by the new one.
|
||||
|
||||
The `on_select` function must not call `worldedit.show_page`
|
||||
]]
|
||||
|
||||
worldedit.pages = {} --mapping of identifiers to options
|
||||
local identifiers = {} --ordered list of identifiers
|
||||
worldedit.register_gui_function = function(identifier, options)
|
||||
if options.privs == nil or next(options.privs) == nil then
|
||||
error("privs unset")
|
||||
end
|
||||
worldedit.pages[identifier] = options
|
||||
table.insert(identifiers, identifier)
|
||||
end
|
||||
|
||||
--[[
|
||||
Example:
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui_hollow_cylinder", function(name, fields)
|
||||
print(minetest.serialize(fields))
|
||||
end)
|
||||
]]
|
||||
|
||||
worldedit.register_gui_handler = function(identifier, handler)
|
||||
local enabled = true
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if not enabled or formname ~= "" or fields.worldedit_gui then return false end
|
||||
enabled = false
|
||||
minetest.after(0.2, function() enabled = true end)
|
||||
local name = player:get_player_name()
|
||||
|
||||
--ensure the player has permission to perform the action
|
||||
local entry = worldedit.pages[identifier]
|
||||
if entry and minetest.check_player_privs(name, entry.privs) then
|
||||
return handler(name, fields)
|
||||
end
|
||||
return false
|
||||
end)
|
||||
end
|
||||
|
||||
worldedit.get_formspec_header = function(identifier)
|
||||
local entry = worldedit.pages[identifier] or {}
|
||||
return "button[0,0;2,0.5;worldedit_gui;Back]" ..
|
||||
string.format("label[2,0;WorldEdit GUI > %s]", entry.name or "")
|
||||
end
|
||||
|
||||
local get_formspec = function(name, identifier)
|
||||
if worldedit.pages[identifier] then
|
||||
return worldedit.pages[identifier].get_formspec(name)
|
||||
end
|
||||
return worldedit.pages["worldedit_gui"].get_formspec(name) --default to showing main page if an unknown page is given
|
||||
end
|
||||
|
||||
--implement worldedit.show_page(name, page) in different ways depending on the available APIs
|
||||
if minetest.global_exists("unified_inventory") then -- unified inventory installed
|
||||
local old_func = worldedit.register_gui_function
|
||||
worldedit.register_gui_function = function(identifier, options)
|
||||
old_func(identifier, options)
|
||||
unified_inventory.register_page(identifier, {get_formspec=function(player) return {formspec=options.get_formspec(player:get_player_name())} end})
|
||||
end
|
||||
|
||||
unified_inventory.register_button("worldedit_gui", {
|
||||
type = "image",
|
||||
image = "inventory_plus_worldedit_gui.png",
|
||||
tooltip = "Edit your World!",
|
||||
condition = function(player)
|
||||
return minetest.check_player_privs(player:get_player_name(), {worldedit=true})
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
local name = player:get_player_name()
|
||||
if fields.worldedit_gui then --main page
|
||||
worldedit.show_page(name, "worldedit_gui")
|
||||
return true
|
||||
elseif fields.worldedit_gui_exit then --return to original page
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
unified_inventory.set_inventory_formspec(player, "craft")
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
|
||||
worldedit.show_page = function(name, page)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
player:set_inventory_formspec(get_formspec(name, page))
|
||||
end
|
||||
end
|
||||
elseif minetest.global_exists("inventory_plus") then -- inventory++ installed
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local can_worldedit = minetest.check_player_privs(player:get_player_name(), {worldedit=true})
|
||||
if can_worldedit then
|
||||
inventory_plus.register_button(player, "worldedit_gui", "WorldEdit")
|
||||
end
|
||||
end)
|
||||
|
||||
--show the form when the button is pressed and hide it when done
|
||||
local gui_player_formspecs = {}
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
local name = player:get_player_name()
|
||||
if fields.worldedit_gui then --main page
|
||||
gui_player_formspecs[name] = player:get_inventory_formspec()
|
||||
worldedit.show_page(name, "worldedit_gui")
|
||||
return true
|
||||
elseif fields.worldedit_gui_exit then --return to original page
|
||||
if gui_player_formspecs[name] then
|
||||
inventory_plus.set_inventory_formspec(player, inventory_plus.get_formspec(player, "main"))
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
|
||||
worldedit.show_page = function(name, page)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
inventory_plus.set_inventory_formspec(player, get_formspec(name, page))
|
||||
end
|
||||
end
|
||||
elseif minetest.global_exists("smart_inventory") then -- smart_inventory installed
|
||||
-- redefinition: Update the code element on inventory page to show the we-page
|
||||
function worldedit.show_page(name, page)
|
||||
local state = smart_inventory.get_page_state("worldedit_gui", name)
|
||||
if state then
|
||||
state:get("code"):set_we_formspec(page)
|
||||
state.location.rootState:show() -- update inventory page
|
||||
end
|
||||
end
|
||||
|
||||
-- smart_inventory page callback. Contains just a "custom code" element
|
||||
local function smart_worldedit_gui_callback(state)
|
||||
local codebox = state:element("code", { name = "code", code = "" })
|
||||
function codebox:set_we_formspec(we_page)
|
||||
local new_formspec = get_formspec(state.location.rootState.location.player, we_page)
|
||||
new_formspec = new_formspec:gsub('button_exit','button') --no inventory closing
|
||||
self.data.code = "container[1,1]".. new_formspec .. "container_end[]"
|
||||
end
|
||||
codebox:set_we_formspec("worldedit_gui")
|
||||
|
||||
-- process input (the back button)
|
||||
state:onInput(function(state, fields, player)
|
||||
if fields.worldedit_gui then --main page
|
||||
state:get("code"):set_we_formspec("worldedit_gui")
|
||||
elseif fields.worldedit_gui_exit then --return to original page
|
||||
state:get("code"):set_we_formspec("worldedit_gui")
|
||||
state.location.parentState:get("crafting_button"):submit() -- switch to the crafting tab
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- all handler should return false to force inventory UI update
|
||||
local orig_register_gui_handler = worldedit.register_gui_handler
|
||||
worldedit.register_gui_handler = function(identifier, handler)
|
||||
local wrapper = function(...)
|
||||
handler(...)
|
||||
return false
|
||||
end
|
||||
orig_register_gui_handler(identifier, wrapper)
|
||||
end
|
||||
|
||||
-- register the inventory button
|
||||
smart_inventory.register_page({
|
||||
name = "worldedit_gui",
|
||||
tooltip = "Edit your World!",
|
||||
icon = "inventory_plus_worldedit_gui.png",
|
||||
smartfs_callback = smart_worldedit_gui_callback,
|
||||
sequence = 99
|
||||
})
|
||||
elseif minetest.global_exists("sfinv") then -- sfinv installed
|
||||
assert(sfinv.enabled)
|
||||
local orig_get = sfinv.pages["sfinv:crafting"].get
|
||||
sfinv.override_page("sfinv:crafting", {
|
||||
get = function(self, player, context)
|
||||
local can_worldedit = minetest.check_player_privs(player, {worldedit=true})
|
||||
local fs = orig_get(self, player, context)
|
||||
return fs .. (can_worldedit and "image_button[0,0;1,1;inventory_plus_worldedit_gui.png;worldedit_gui;]" ..
|
||||
"tooltip[worldedit_gui;Edit your World!]" or "")
|
||||
end
|
||||
})
|
||||
|
||||
--show the form when the button is pressed and hide it when done
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if fields.worldedit_gui then --main page
|
||||
worldedit.show_page(player:get_player_name(), "worldedit_gui")
|
||||
return true
|
||||
elseif fields.worldedit_gui_exit then --return to original page
|
||||
sfinv.set_page(player, "sfinv:crafting")
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
|
||||
worldedit.show_page = function(name, page)
|
||||
local player = minetest.get_player_by_name(name)
|
||||
if player then
|
||||
player:set_inventory_formspec(get_formspec(name, page))
|
||||
end
|
||||
end
|
||||
else
|
||||
return minetest.log("error",
|
||||
"worldedit_gui requires a supported gui management mod to be installed.\n"..
|
||||
"To use the it you need to either:\n"..
|
||||
"* use minetest_game or another sfinv-compatible subgame\n"..
|
||||
"* install Unified Inventory, Inventory++ or Smart Inventory\n"..
|
||||
"If you don't want to use worldedit_gui, disable it by editing world.mt or from the main menu."
|
||||
)
|
||||
end
|
||||
|
||||
worldedit.register_gui_function("worldedit_gui", {
|
||||
name = "WorldEdit GUI",
|
||||
privs = {interact=true},
|
||||
get_formspec = function(name)
|
||||
--create a form with all the buttons arranged in a grid
|
||||
local buttons, x, y, index = {}, 0, 1, 0
|
||||
local width, height = 3, 0.8
|
||||
local columns = 5
|
||||
for i, identifier in pairs(identifiers) do
|
||||
if identifier ~= "worldedit_gui" then
|
||||
local entry = worldedit.pages[identifier]
|
||||
table.insert(buttons, string.format((entry.get_formspec and "button" or "button_exit") ..
|
||||
"[%g,%g;%g,%g;%s;%s]", x, y, width, height, identifier, minetest.formspec_escape(entry.name)))
|
||||
|
||||
index, x = index + 1, x + width
|
||||
if index == columns then --row is full
|
||||
x, y = 0, y + height
|
||||
index = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
if index == 0 then --empty row
|
||||
y = y - height
|
||||
end
|
||||
return string.format("size[%g,%g]", math.max(columns * width, 5), math.max(y + 0.5, 3)) ..
|
||||
"button[0,0;2,0.5;worldedit_gui_exit;Back]" ..
|
||||
"label[2,0;WorldEdit GUI]" ..
|
||||
table.concat(buttons)
|
||||
end,
|
||||
})
|
||||
|
||||
worldedit.register_gui_handler("worldedit_gui", function(name, fields)
|
||||
for identifier, entry in pairs(worldedit.pages) do --check for WorldEdit GUI main formspec button selection
|
||||
if fields[identifier] and identifier ~= "worldedit_gui" then
|
||||
--ensure player has permission to perform action
|
||||
local has_privs, missing_privs = minetest.check_player_privs(name, entry.privs)
|
||||
if not has_privs then
|
||||
worldedit.player_notify(name, "you are not allowed to use this function (missing privileges: " .. table.concat(missing_privs, ", ") .. ")", "error")
|
||||
return false
|
||||
end
|
||||
if entry.on_select then
|
||||
entry.on_select(name)
|
||||
end
|
||||
if entry.get_formspec then
|
||||
worldedit.show_page(name, identifier)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end)
|
||||
|
||||
dofile(minetest.get_modpath(minetest.get_current_modname()) .. "/functionality.lua")
|
4
worldedit_gui/mod.conf
Normal file
|
@ -0,0 +1,4 @@
|
|||
name = worldedit_gui
|
||||
description = WorldEdit GUI
|
||||
depends = worldedit, worldedit_commands
|
||||
optional_depends = unified_inventory, inventory_plus, sfinv, creative, smart_inventory
|
BIN
worldedit_gui/textures/inventory_plus_worldedit_gui.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
worldedit_gui/textures/worldedit_gui_unknown.png
Normal file
After Width: | Height: | Size: 382 B |
52
worldedit_shortcommands/init.lua
Normal file
|
@ -0,0 +1,52 @@
|
|||
--provides shorter names for the commands in `worldedit_commands`
|
||||
|
||||
worldedit.alias_command = function(alias, original)
|
||||
if not worldedit.registered_commands[original] then
|
||||
minetest.log("error", "worldedit_shortcommands: original " .. original .. " does not exist")
|
||||
return
|
||||
end
|
||||
if minetest.chatcommands["/" .. alias] then
|
||||
minetest.log("error", "worldedit_shortcommands: alias " .. alias .. " already exists")
|
||||
return
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("/" .. alias, minetest.chatcommands["/" .. original])
|
||||
worldedit.registered_commands[alias] = worldedit.registered_commands[original]
|
||||
end
|
||||
|
||||
worldedit.alias_command("i", "inspect")
|
||||
worldedit.alias_command("rst", "reset")
|
||||
worldedit.alias_command("mk", "mark")
|
||||
worldedit.alias_command("umk", "unmark")
|
||||
worldedit.alias_command("1", "pos1")
|
||||
worldedit.alias_command("2", "pos2")
|
||||
worldedit.alias_command("fp", "fixedpos")
|
||||
worldedit.alias_command("v", "volume")
|
||||
worldedit.alias_command("s", "set")
|
||||
worldedit.alias_command("r", "replace")
|
||||
worldedit.alias_command("ri", "replaceinverse")
|
||||
worldedit.alias_command("hcube", "hollowcube")
|
||||
worldedit.alias_command("hspr", "hollowsphere")
|
||||
worldedit.alias_command("spr", "sphere")
|
||||
worldedit.alias_command("hdo", "hollowdome")
|
||||
worldedit.alias_command("do", "dome")
|
||||
worldedit.alias_command("hcyl", "hollowcylinder")
|
||||
worldedit.alias_command("cyl", "cylinder")
|
||||
worldedit.alias_command("hpyr", "hollowpyramid")
|
||||
worldedit.alias_command("pyr", "pyramid")
|
||||
worldedit.alias_command("spl", "spiral")
|
||||
worldedit.alias_command("m", "move")
|
||||
worldedit.alias_command("c", "copy")
|
||||
worldedit.alias_command("stk", "stack")
|
||||
worldedit.alias_command("sch", "stretch")
|
||||
worldedit.alias_command("tps", "transpose")
|
||||
worldedit.alias_command("fl", "flip")
|
||||
worldedit.alias_command("rot", "rotate")
|
||||
worldedit.alias_command("ort", "orient")
|
||||
worldedit.alias_command("hi", "hide")
|
||||
worldedit.alias_command("sup", "suppress")
|
||||
worldedit.alias_command("hlt", "highlight")
|
||||
worldedit.alias_command("rsr", "restore")
|
||||
worldedit.alias_command("l", "lua")
|
||||
worldedit.alias_command("lt", "luatransform")
|
||||
worldedit.alias_command("clro", "clearobjects")
|
3
worldedit_shortcommands/mod.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
name = worldedit_shortcommands
|
||||
description = WorldEdit command short aliases
|
||||
depends = worldedit_commands
|