13 Commits

Author SHA1 Message Date
sfan5
6a902a8040 Tune node name normalization 2025-10-16 15:28:08 +02:00
sfan5
fe7a552c40 Move and refactor cuboid functions 2025-09-24 22:04:47 +02:00
sfan5
be2a3d6ca7 Fix wand tool description 2025-09-24 21:36:16 +02:00
sfan5
08ab19fcbb Check empty command parameters strictly 2025-09-24 21:29:39 +02:00
sfan5
9670a6d4e0 Refresh README 2025-09-24 18:33:08 +02:00
sfan5
ebb0a95e08 Support relative directions in transformations
closes #256
2025-09-24 18:09:42 +02:00
sfan5
0573cd8755 Genericize relative direction handling 2025-09-24 18:05:55 +02:00
sfan5
3a79209268 Add some basic testing to worldedit_commands 2025-09-20 15:03:30 +02:00
sfan5
5121ffab8b Allow relative coordinate parsing
for //fixedpos, since that's the only relevant command
closes #260
2025-09-20 15:03:30 +02:00
sfan5
1f9b8ef55b Minor maintenance changes 2025-09-20 15:03:30 +02:00
sfan5
bf154b12c7 Use new 5.13.0 explicit VoxelManip free 2025-05-30 18:04:55 +02:00
sfan5
03d4e45e59 Fix CI tests for new name and repo 2025-05-30 15:40:06 +02:00
blut
6e316105e0 fixes #252: Missing placeholder in Russian translation. 2025-04-05 18:40:22 +02:00
22 changed files with 634 additions and 468 deletions

View File

@@ -11,13 +11,14 @@ jobs:
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
- { image: 'ghcr.io/minetest/minetest:5.10.0', mtg: true }
- { image: 'ghcr.io/luanti-org/luanti:master', mtg: true } # latest git
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
repository: 'minetest/minetest_game'
repository: 'luanti-org/minetest_game'
path: minetest_game
if: ${{ matrix.cfg.mtg }}

View File

@@ -2,6 +2,10 @@
tempdir=$(mktemp -d)
confpath=$tempdir/minetest.conf
worldpath=$tempdir/world
modlist=(
worldedit
worldedit_commands
)
trap 'rm -rf "$tempdir"' EXIT
[ -f worldedit/mod.conf ] || { echo "Must be run in modpack root folder." >&2; exit 1; }
@@ -11,9 +15,9 @@ 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; }
mtserver=$(command -v luantiserver)
[[ -z "$mtserver" && -x ../../bin/luantiserver ]] && mtserver=../../bin/luantiserver
[ -z "$mtserver" ] && { echo "To run the test outside of Docker, an installation of luantiserver is required." >&2; exit 1; }
fi
mkdir $worldpath
@@ -22,19 +26,23 @@ 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"
[ -n "$DOCKER_IMAGE" ] || { echo "Missing DOCKER_IMAGE env variable" >&2; exit 1; }
vol=(
-v "$confpath":/etc/minetest/minetest.conf
-v "$tempdir":/var/lib/minetest/.minetest
-v "$PWD/worldedit":/var/lib/minetest/.minetest/world/worldmods/worldedit
)
for mod in "${modlist[@]}"; do
vol+=(-v "$PWD/$mod":/var/lib/minetest/.minetest/world/worldmods/$mod)
done
[ -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
for mod in "${modlist[@]}"; do
ln -s "$PWD/$mod" $worldpath/worldmods/$mod
done
$mtserver --config "$confpath" --world "$worldpath" --logfile /dev/null
fi

View File

@@ -99,6 +99,8 @@ Set the WorldEdit region position 1 or 2 to the position (`<x>`, `<y>`, `<z>`).
//fixedpos set1 -30 5 28
//fixedpos set2 1004 -200 432
Note that the `~` syntax can be used here to indicate a coordinate relative to the player.
### `//volume`
Display the volume of the current WorldEdit region.
@@ -107,7 +109,13 @@ Display the volume of the current WorldEdit region.
### `//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.
Delete the MapBlocks (16x16x16 units) that contain the selected region.
This means that mapgen will run again 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 entities are not part of a MapBlock and will not get deleted (use `//clearobjects`).
//deleteblocks
@@ -262,7 +270,7 @@ height `<height>`, space between walls `<spacer>`, composed of `<node>`.
//spiral 5 2 1 glass
//spiral 7 1 5 mesecons:wire_00000000_off
### `//copy x/y/z/? <amount>`
### `//copy x/y/z/?/up/down/left/right/front/back <amount>`
Copy the current WorldEdit region along the given axis by `<amount>` nodes.
@@ -271,7 +279,7 @@ Copy the current WorldEdit region along the given axis by `<amount>` nodes.
//copy z +4
//copy ? 8
### `//move x/y/z/? <amount>`
### `//move x/y/z/?/up/down/left/right/front/back <amount>`
Move the current WorldEdit positions and region along the given axis by `<amount>` nodes.
@@ -280,7 +288,7 @@ Move the current WorldEdit positions and region along the given axis by `<amount
//move z +4
//move ? -1
### `//stack x/y/z/? <count>`
### `//stack x/y/z/?/up/down/left/right/front/back <count>`
Stack the current WorldEdit region along the given axis `<count>` times.
@@ -314,14 +322,14 @@ Transpose the current WorldEdit positions and region along given axes.
//transpose y z
//transpose ? y
### `//flip x/y/z/?`
### `//flip x/y/z/?/up/down/left/right/front/back`
Flip the current WorldEdit region along the given axis.
//flip x
//flip ?
### `//rotate x/y/z/? <angle>`
### `//rotate x/y/z/?/up/down/left/right/front/back <angle>`
Rotate the current WorldEdit positions and region along the given axis by angle `<angle>` (90 degree increment).
@@ -354,7 +362,9 @@ Removes any fluid node within the current WorldEdit region.
### `//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.
However note that this relies on heuristics.
//clearcut
@@ -440,6 +450,7 @@ Valid values for `[rotation]` are 0, 90, 180 and 270.
### `//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
@@ -492,6 +503,7 @@ or vertically in the y axis using `v`.
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
@@ -502,6 +514,7 @@ Note that this functionality requires the `worldedit_brush` mod enabled.
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

View File

@@ -1,30 +1,32 @@
WorldEdit v1.3
==============
The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more.
The ultimate in-game world editing tool for [Luanti](https://www.luanti.org/)! Tons of functionality to help with building, fixing, and more.
For more information, see the [forum topic](https://forum.minetest.net/viewtopic.php?t=572) at the Minetest forums.
For more information, see the [forum topic](https://forum.luanti.org/viewtopic.php?t=572) at the Luanti forums.
# New users should see the [tutorial](Tutorial.md).
**New users should see the [tutorial](Tutorial.md).**
![Screenshot](http://i.imgur.com/lwhodrv.png)
![Screenshot](https://i.imgur.com/lwhodrv.png)
Installing
----------
[![ContentDB](https://content.luanti.org/packages/sfan5/worldedit/shields/downloads/)](https://content.luanti.org/packages/sfan5/worldedit/)
There is a nice installation guide over at the [Minetest Wiki](http://wiki.minetest.com/wiki/Installing_mods). Here is a short summary:
Head over to ContentDB **⇈** to install it directly.
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.
Otherwise, you may install it manually:
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).
1. Download the ZIP file from the [here](https://github.com/Uberi/Minetest-WorldEdit/archive/refs/heads/master.zip) (rolling release version)
2. You should have a file named `Minetest-WorldEdit-xxxx.zip`.
3. Extract this file. 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 called "README.md" inside it. If you just have another folder inside this folder, use the nested folder instead.
5. Move this folder into the `mods` folder of your Luanti installation.
6. Open Luanti 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. Double-click on the entry named something like `Minetest-WorldEdit`, which will enable it.
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 prefer to have a "stable" release, take a look at the [releases page](https://github.com/Uberi/Minetest-WorldEdit/releases).
Usage
-----
@@ -50,7 +52,8 @@ The chat interface adds many chat commands that perform various WorldEdit powere
Compatibility
-------------
This mod supports Minetest versions 5.0 and newer. Older versions of WorldEdit may work with older versions of Minetest, but are not recommended or supported.
This mod supports Luanti (or Minetest) versions 5.0 and newer.
Older versions of WorldEdit are not recommended or supported, and may be **insecure**.
WorldEdit works quite well with other mods and does not have any known mod conflicts.
@@ -58,7 +61,8 @@ WorldEdit GUI requires one of [sfinv](https://github.com/minetest/minetest_game/
[Unified Inventory](https://forum.minetest.net/viewtopic.php?t=12767),
[Inventory++](https://forum.minetest.net/viewtopic.php?id=6204) or [Smart Inventory](https://forum.minetest.net/viewtopic.php?t=16597).
If you use any other inventory manager mods, note that they may conflict with the WorldEdit GUI. If this is the case, it may be necessary to disable them.
If you use any other inventory manager mods, note that they may conflict with the WorldEdit GUI.
If this is the case, it may be necessary to disable them.
WorldEdit API
-------------
@@ -72,7 +76,7 @@ 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).
The coordinate system is the same as that used by Luanti; 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 `?`.
@@ -140,7 +144,7 @@ The ordering of the values and minor aspects of the syntax, such as trailing com
The WorldEdit Schematic format is accessed via the WorldEdit API, or WorldEdit serialization chat commands such as `//serialize` and `//deserialize`.
The second is the Minetest Schematic format (MTS). The details of this format may be found in the Minetest documentation and are out of the scope of this document.
The second is the *Minetest Schematic format (MTS)*. The details of this format may be found in the Luanti 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

View File

@@ -133,7 +133,9 @@ function mh.finish(manip, data)
manip:set_data(data)
end
manip:write_to_map()
manip:update_map()
if manip.close ~= nil then
manip:close() -- explicitly free memory
end
end

View File

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

View File

@@ -1,4 +1,4 @@
--- WorldEdit mod for the Minetest engine
--- WorldEdit mod for the Luanti engine
-- @module worldedit
-- @release 1.3
-- @copyright 2012 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote)
@@ -32,7 +32,6 @@ 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

View File

@@ -56,8 +56,7 @@ function worldedit.set_param2(pos1, pos2, param2)
-- Update map
manip:set_param2_data(param2_data)
manip:write_to_map()
manip:update_map()
mh.finish(manip)
return worldedit.volume(pos1, pos2)
end
@@ -269,8 +268,10 @@ 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
vmanip:write_to_map() -- this updates the lighting
if vmanip.close ~= nil then
vmanip:close()
end
return worldedit.volume(pos1, pos2)
end

View File

@@ -5,7 +5,7 @@
---------------------
local vec = vector.new
local vecw = function(axis, n, base)
local ret = vec(base)
local ret = vector.copy(base)
ret[axis] = n
return ret
end

View File

@@ -9,7 +9,7 @@ local brush_on_use = function(itemstack, placer)
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")
minetest.colorize("#0ff", "//brush")), "info")
return false
end
@@ -100,7 +100,7 @@ worldedit.register_command("brush", {
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))
minetest.colorize("#0ff", "//"..cmd))
end
-- Try parsing command params so we can give the user feedback
@@ -112,7 +112,7 @@ worldedit.register_command("brush", {
meta:set_string("command", cmd)
meta:set_string("params", params)
local fullcmd = minetest.colorize("#00ffff", "//"..cmd) .. " " .. params
local fullcmd = minetest.colorize("#0ff", "//"..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")

View File

@@ -1,5 +1,7 @@
local S = minetest.get_translator("worldedit_commands")
local VALID_DIR = worldedit.valid_directions
worldedit.register_command("outset", {
params = "[h/v] <amount>",
description = S("Outset the selected region."),
@@ -7,7 +9,7 @@ worldedit.register_command("outset", {
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
local find, _, dir, amount = param:find("^(%a*)%s+([+-]?%d+)$")
if find == nil then
return false
end
@@ -47,7 +49,7 @@ worldedit.register_command("inset", {
privs = {worldedit=true},
require_pos = 2,
parse = function(param)
local find, _, dir, amount = param:find("(%a*)%s*([+-]?%d+)")
local find, _, dir, amount = param:find("^(%a*)%s+([+-]?%d+)$")
if find == nil then
return false
end
@@ -79,29 +81,21 @@ worldedit.register_command("inset", {
worldedit.register_command("shift", {
params = "x/y/z/?/up/down/left/right/front/back [+/-]<amount>",
params = tostring(VALID_DIR) .. " [+/-]<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
local find, _, direction, amount = param:find("^([^%s]+)%s+([+-]?%d+)$")
if find == nil or not VALID_DIR[direction] 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
local axis, dir = worldedit.player_direction(name, direction)
if axis == nil or dir == nil then
return false, S("Invalid if looking straight up or down")
end
@@ -115,15 +109,15 @@ worldedit.register_command("shift", {
worldedit.register_command("expand", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
params = "[+/-]" .. tostring(VALID_DIR) .. " <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
rev_amount = param:find("^([+-]?)([^%s]+)%s+(%d+)%s*(%d*)$")
if find == nil or not VALID_DIR[direction] then
return false
end
@@ -134,24 +128,10 @@ worldedit.register_command("expand", {
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
local axis, dir = worldedit.player_direction(name, direction)
if axis == nil or dir == nil then
return false, S("Invalid if looking straight up or down")
end
if sign == "-" then
dir = -dir
end
@@ -165,15 +145,15 @@ worldedit.register_command("expand", {
worldedit.register_command("contract", {
params = "[+/-]x/y/z/?/up/down/left/right/front/back <amount> [reverse amount]",
params = "[+/-]" .. tostring(VALID_DIR) .. " [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
rev_amount = param:find("^([+-]?)([^%s]+)%s+(%d+)%s*(%d*)$")
if find == nil or not VALID_DIR[direction] then
return false
end
@@ -184,24 +164,10 @@ worldedit.register_command("contract", {
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
local axis, dir = worldedit.player_direction(name, direction)
if axis == nil or dir == nil then
return false, S("Invalid if looking straight up or down")
end
if sign == "-" then
dir = -dir
end
@@ -238,7 +204,7 @@ worldedit.register_command("cubeapply", {
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))
minetest.colorize("#0ff", "//"..cmd))
end
-- run parsing of target command
local parsed = {cmddef.parse(args)}

View File

@@ -0,0 +1,152 @@
-- Moves the location of a single marker by adding deltavector
local function marker_move(name, marker, deltavector)
if marker == 1 then
local pos = worldedit.pos1[name]
worldedit.pos1[name] = vector.add(deltavector, pos)
elseif marker == 2 then
local pos = worldedit.pos2[name]
worldedit.pos2[name] = vector.add(deltavector, pos)
else
assert(false)
end
return true
end
-- Returns two vectors with the directions for volumetric expansion
local function get_expansion_directions(mark1, mark2)
assert(mark1 and mark2)
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
-- Returns the closest marker to the specified axis and direction
local function marker_get_closest_to_axis(name, axis, direction)
assert(direction == 1 or direction == -1)
local pos1 = worldedit.pos1[name]
local pos2 = worldedit.pos2[name]
if axis == "x" then
if pos1.x * direction > pos2.x * direction then
return 1
else
return 2
end
elseif axis == "y" then
if pos1.y * direction > pos2.y * direction then
return 1
else
return 2
end
elseif axis == "z" then
if pos1.z * direction > pos2.z * direction then
return 1
else
return 2
end
else
assert(false)
end
end
-- 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(amount, amount, amount)
local delta2 = vector.new(amount, amount, amount)
local delta_dir1, delta_dir2 = 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 = 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
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
local delta = vector.new()
if axis == "x" then
delta.x = amount
elseif axis == "y" then
delta.y = amount
elseif axis == "z" then
delta.z = amount
else
return false, "invalid axis"
end
worldedit.pos1[name] = vector.add(pos1, delta)
worldedit.pos2[name] = vector.add(pos2, delta)
return true
end

View File

@@ -25,6 +25,15 @@ local function copy_state(which, name)
end
end
local function compare_state(state, old_state)
for i, v in ipairs(state) do
if not (v == nil and old_state[i] == nil) and not vector.equals(v, old_state[i]) then
return false
end
end
return true
end
local function chatcommand_handler(cmd_name, name, param)
local def = assert(worldedit.registered_commands[cmd_name])
@@ -42,6 +51,7 @@ local function chatcommand_handler(cmd_name, name, param)
end
end
param = param:trim()
local parsed = {def.parse(param)}
local success = table.remove(parsed, 1)
if not success then
@@ -66,11 +76,7 @@ local function chatcommand_handler(cmd_name, name, param)
local old_state = copy_state(def.require_pos, name)
safe_region(name, count, function()
local state = copy_state(def.require_pos, name)
local ok = true
for i, v in ipairs(state) do
ok = ok and ( (v == nil and old_state[i] == nil) or vector.equals(v, old_state[i]) )
end
if not ok then
if not compare_state(state, old_state) then
worldedit.player_notify(name, S("ERROR: the operation was cancelled because the region has changed."), "error")
return
end
@@ -111,7 +117,9 @@ function worldedit.register_command(name, def)
def.require_pos = def.require_pos or 0
assert(def.require_pos >= 0 and def.require_pos < 3)
if def.params == "" and not def.parse then
def.parse = function(param) return true end
def.parse = function(param)
return param == ""
end
else
assert(def.parse)
end
@@ -124,7 +132,7 @@ function worldedit.register_command(name, def)
end--]]
-- disable further modification
setmetatable(def, {__newindex = {}})
setmetatable(def, {__newindex = function() end})
minetest.register_chatcommand("/" .. name, {
privs = def.privs,
@@ -137,18 +145,6 @@ function worldedit.register_command(name, def)
worldedit.registered_commands[name] = def
end
do
local modpath = minetest.get_modpath("worldedit_commands")
for _, name in ipairs({
"code", "cuboid", "manipulations", "marker", "nodename", "primitives",
"region", "schematics", "transform", "wand"
}) do
dofile(modpath .. "/" .. name .. ".lua")
end
end
-- Notifies a player of something related to WorldEdit.
-- Message types:
-- "error" = An operation did not work as expected.
@@ -172,7 +168,9 @@ function worldedit.player_notify(name, message, typ)
minetest.chat_send_player(name, table.concat(t, " "))
end
-- Determines the axis in which a player is facing, returning an axis ("x", "y", or "z") and the sign (1 or -1)
-- Determines the axis in which a player is facing
-- @return axis ("x", "y", or "z") and the sign (1 or -1)
-- @note Not part of API
function worldedit.player_axis(name)
local player = minetest.get_player_by_name(name)
if not player then
@@ -191,6 +189,79 @@ function worldedit.player_axis(name)
return "z", dir.z > 0 and 1 or -1
end
-- Look-up table of valid directions (for worldedit.player_direction)
-- Can be stringified for usage in help texts
-- @note Not part of API
worldedit.valid_directions = setmetatable({
x = true, y = true, z = true,
["?"] = true,
up = true, down = true,
front = true, back = true,
left = true, right = true,
}, {
__tostring = function()
return "x/y/z/?/up/down/left/right/front/back"
end
})
-- Accepts a valid directions as above
-- @return axis ("x", "y", or "z") and the sign (1 or -1) *or* nil for invalid combinations
-- @note Not part of API
worldedit.player_direction = function(name, str)
if str == "x" or str == "y" or str == "z" then
return str, 1
elseif str == "up" then
return "y", 1
elseif str == "down" then
return "y", -1
end
local axis, dir = worldedit.player_axis(name)
if str == "?" then
return axis, dir
elseif str == "front" then
if axis ~= "y" then
return axis, dir
end
elseif str == "back" then
if axis ~= "y" then
return axis, -dir
end
elseif str == "left" then
if axis == "x" then
return "z", dir
elseif axis == "z" then
return "x", -dir
end
elseif str == "right" then
if axis == "x" then
return "z", -dir
elseif axis == "z" then
return "x", dir
end
end
return nil, nil
end
-- Wrapper for the engine"s parse_coordinates
-- @return vector or nil
-- @note Not part of API
function worldedit.parse_coordinates(x, y, z, player_name)
local relpos
local player = minetest.get_player_by_name(player_name or "")
if player then
relpos = player:get_pos()
end
-- we don't bother to support ~ in the fallback path here
if not minetest.parse_coordinates then
x, y, z = tonumber(x), tonumber(y), tonumber(z)
return x and y and z and vector.new(x, y, z)
end
return minetest.parse_coordinates(x, y, z, relpos)
end
worldedit.register_command("about", {
privs = {},
@@ -200,7 +271,7 @@ worldedit.register_command("about", {
worldedit.player_notify(name, S("WorldEdit @1"..
" is available on this server. Type @2 to get a list of "..
"commands, or find more information at @3",
worldedit.version_string, minetest.colorize("#00ffff", "//help"),
worldedit.version_string, minetest.colorize("#0ff", "//help"),
"https://github.com/Uberi/Minetest-WorldEdit"
), "info")
end,
@@ -209,10 +280,10 @@ worldedit.register_command("about", {
-- initially copied from builtin/chatcommands.lua
local function help_command(name, param)
local function format_help_line(cmd, def, follow_alias)
local msg = minetest.colorize("#00ffff", "//"..cmd)
local msg = minetest.colorize("#0ff", "//"..cmd)
if def.name ~= cmd then
msg = msg .. ": " .. S("alias to @1",
minetest.colorize("#00ffff", "//"..def.name))
minetest.colorize("#0ff", "//"..def.name))
if follow_alias then
msg = msg .. "\n" .. format_help_line(def.name, def)
end
@@ -255,7 +326,7 @@ local function help_command(name, param)
end
end
table.sort(list)
local help = minetest.colorize("#00ffff", "//help")
local help = minetest.colorize("#0ff", "//help")
return true, S("Available commands: @1@n"
.. "Use '@2' to get more information,"
.. " or '@3' to list everything.",
@@ -320,3 +391,17 @@ worldedit.register_command("reset", {
end,
})
-- Load the other parts
do
local modpath = minetest.get_modpath("worldedit_commands")
for _, name in ipairs({
"code", "cuboid_funcs", "cuboid", "manipulations", "marker", "nodename",
"primitives", "region", "schematics", "transform", "wand"
}) do
dofile(modpath .. "/" .. name .. ".lua")
end
if worldedit.register_test then
dofile(modpath .. "/test/init.lua")
end
end

View File

@@ -51,6 +51,7 @@ 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>)=
invalid position=
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=
@@ -150,4 +151,5 @@ WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 t
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=
WorldEdit Wand tool=
Left-click to set 1st position, right-click to set 2nd=

View File

@@ -62,6 +62,7 @@ select position @1 by punching a node=Wählen Sie Position @1 durch Hauen eines
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>)=Eine Position des WorldEdit-Gebiets auf (<x>, <y>, <z>) setzen
invalid position=Ungültige Position
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 (16×16×16) entfernen, die das gewählte Gebiet enthalten
@@ -161,4 +162,5 @@ WARNING: this operation could affect up to @1 nodes; type @2 to continue or @3 t
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
WorldEdit Wand tool=WorldEdit-Zauberstab
Left-click to set 1st position, right-click to set 2nd=Setzen der 1. Position mit Linksklick, der 2. mit Rechtsklick

View File

@@ -73,7 +73,7 @@ Stack the current WorldEdit region along the given axis <count> times=Размн
@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=неверные приращения(шаг)
invalid increments: @1=неверные приращения(шаг): @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 нод(а/ы)
@@ -152,4 +152,5 @@ invalid usage: @1 cannot be used with 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-ю
WorldEdit Wand tool=Инструмент WorldEdit Wand
Left-click to set 1st position, right-click to set 2nd=Левая кнопка мыши, чтобы установить 1-ю позицию, правая кнопка мыши, чтобы установить 2-ю

View File

@@ -1,6 +1,4 @@
-- 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 strip_escapes = minetest.strip_escapes or function(input)
local s = function(idx) return input:sub(idx, idx) end
local out = ""
local i = 1
@@ -22,42 +20,63 @@ local function strip_escapes(input)
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
if #full < #part then
return false
end
return full:sub(-#part) == part
end
local function make_description_cache()
local t = {}
for key, def in pairs(minetest.registered_nodes) do
local desc = def.short_description or (def.description or ""):gsub("\n.*", "", 1)
desc = strip_escapes(desc):lower()
if def.groups.not_in_creative_inventory ~= 1 and desc ~= "" then
t[key] = desc
end
end
return t
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
nodename = nodename:trim()
if nodename == "" then
return nil
end
nodename = nodename:lower()
if nodename:find(" ", 1, true) == nil then
local fullname = ItemStack({name=nodename}):get_name() -- resolve aliases
if minetest.registered_nodes[fullname] then -- full name
return fullname
end
end
local match
for key, _ in pairs(minetest.registered_nodes) do
if string_endswith(key:lower(), ":" .. nodename) then -- matches name (w/o mod part)
return key
if string_endswith(key, ":" .. nodename) then
if match then
match = nil
break
end
match = key -- matches name w/o mod part (only if unique)
end
end
if match then
return match
end
nodename = nodename:lower()
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
-- Note: since we don't handle translations this will work only in the original
-- language of the description (English)
description_cache = make_description_cache()
end
for key, desc in pairs(description_cache) do
@@ -72,14 +91,16 @@ worldedit.normalize_nodename = function(nodename)
end
end
local match = nil
match = nil
for key, value in pairs(description_cache) do
if value:find(nodename, 1, true) ~= nil then
if match ~= nil then
return nil
if match then
match = nil
break
end
match = key -- substring description match (only if no ambiguities)
match = key -- substring description match (only if unique)
end
end
return match
end

View File

@@ -95,6 +95,7 @@ worldedit.register_command("unmark", {
local function set_pos1(name, pos)
assert(pos)
pos = vector.round(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")
@@ -102,6 +103,7 @@ end
local function set_pos2(name, pos)
assert(pos)
pos = vector.round(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")
@@ -115,7 +117,7 @@ worldedit.register_command("pos1", {
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()))
set_pos1(name, player:get_pos())
end,
})
@@ -127,7 +129,7 @@ worldedit.register_command("pos2", {
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()))
set_pos2(name, player:get_pos())
end,
})
@@ -178,13 +180,18 @@ worldedit.register_command("fixedpos", {
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
local found, _, flag, x, y, z = param:find("^(set[12])%s+(~?[+-]?%d+)%s+(~?[+-]?%d+)%s+(~?[+-]?%d+)$")
if not found then
return false
end
return true, flag, vector.new(tonumber(x), tonumber(y), tonumber(z))
return true, flag, x, y, z
end,
func = function(name, flag, pos)
func = function(name, flag, x, y, z)
-- Parse here, since player name isn't known in parse()
local pos = worldedit.parse_coordinates(x, y, z, name)
if not pos then
return false, S("invalid position")
end
if flag == "set1" then
set_pos1(name, pos)
else --flag == "set2"

View File

@@ -20,8 +20,8 @@ local function safe_region(name, count, callback)
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 yes_cmd = minetest.colorize("#0ff", "//y")
local no_cmd = minetest.colorize("#0ff", "//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")

View File

@@ -0,0 +1,143 @@
local register_test = worldedit.register_test
-- Basic test that just checks if certain parameter combinations
-- parse correctly (valid or invalid)
local make_parsing_test = function(cmd, valid, invalid)
return function()
local def = worldedit.registered_commands[cmd]
assert(def, "Command not defined")
for _, param in ipairs(valid or {}) do
local parsed = {def.parse(param)}
assert(parsed[1], string.format("Did not parse: %q", param))
end
for _, param in ipairs(invalid or {}) do
local parsed = {def.parse(param)}
assert(not parsed[1], string.format("Did parse: %q", param))
end
end
end
register_test("Command parsing")
register_test("//set", make_parsing_test("set", {
"air",
"mapgen_stone",
minetest.registered_aliases["mapgen_dirt"],
}, {
"this long text could not possibly ever match a node",
"",
}))
register_test("//mix", make_parsing_test("mix", {
"air",
"air 2",
"air mapgen_stone",
"air 2 air 1 mapgen_stone 1",
}, {
"this_will_never_match_any_node",
"air 1 this_will_never_match_any_node",
"air this_will_never_match_any_node",
"",
}))
register_test("//fixedpos", make_parsing_test("fixedpos", {
"set1 0 0 0",
"set2 -10 20 31000",
"set1 ~0 ~0 ~0",
"set2 ~-5 2 ~+2",
}, {
"set1 0 0",
"set 1 2 3",
"set2 ~ ~ ~",
"set2 + 0 0",
"",
}))
register_test("//copy", make_parsing_test("copy", {
"x 10",
"right +1",
"? -4",
}, {
"eee 1",
"up 0",
"",
}))
register_test("//rotate", make_parsing_test("rotate", {
"z 90",
"left -180",
}, {
"x 0",
"back 77",
"",
}))
register_test("//flip", make_parsing_test("flip", {
"y",
"down",
"?",
}, {
"1",
"",
}))
register_test("//inset", make_parsing_test("inset", {
"h 1",
"v 0",
"hv 2",
"vh 3",
}, {
"x 4",
"xyz 5",
"v foo",
"",
}))
register_test("//shift", make_parsing_test("shift", {
"x 1",
"x -4",
"back 1",
"? 1",
}, {
"+z 1212",
"-z 9",
"xx -5",
"?? 123",
"",
}))
register_test("//expand", make_parsing_test("expand", {
"x 1",
"z 1 2",
"? 1",
"+? 1",
"+left 1",
"-right 1",
}, {
"x -4",
"? 4 -333",
"stupid 5 5",
"",
}))
register_test("//cubeapply", make_parsing_test("cubeapply", {
"2 orient 90",
"2 3 4 orient 90",
"1 1 1 drain",
"4 stack z 1",
}, {
"1 1 1 orient",
"0 drain",
"4 stack z",
"2 2 2 asasasasasas",
"",
}))
register_test("//save", make_parsing_test("save", {
"filename",
"filename.abc",
}, {
"\"hmm",
"../../oops",
"",
}))

View File

@@ -1,32 +1,38 @@
local S = minetest.get_translator("worldedit_commands")
local VALID_DIR = worldedit.valid_directions
local function check_region(name)
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
end
local function parse_copylike(param)
local found, _, direction, amount = param:find("^([^%s]+)%s+([+-]?%d+)$")
if found == nil or not VALID_DIR[direction] then
return false
end
if tonumber(amount) == 0 then
return false
end
return true, direction, tonumber(amount)
end
worldedit.register_command("copy", {
params = "x/y/z/? <amount>",
params = tostring(VALID_DIR) .. " <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)
parse = parse_copylike,
nodes_needed = function(name, direction, 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
func = function(name, direction, amount)
local axis, sign = worldedit.player_direction(name, direction)
if not axis or not sign then
return false, S("Invalid if looking straight up or down")
end
amount = amount * sign
local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount)
return true, S("@1 nodes copied", count)
@@ -34,27 +40,21 @@ worldedit.register_command("copy", {
})
worldedit.register_command("move", {
params = "x/y/z/? <amount>",
params = tostring(VALID_DIR) .. " <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)
parse = parse_copylike,
nodes_needed = function(name, direction, 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
func = function(name, direction, amount)
local axis, sign = worldedit.player_direction(name, direction)
if not axis or not sign then
return false, S("Invalid if looking straight up or down")
end
amount = amount * sign
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count = worldedit.move(pos1, pos2, axis, amount)
@@ -67,27 +67,21 @@ worldedit.register_command("move", {
})
worldedit.register_command("stack", {
params = "x/y/z/? <count>",
params = tostring(VALID_DIR) .. " <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)
parse = parse_copylike,
nodes_needed = function(name, direction, 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
func = function(name, direction, repetitions)
local axis, sign = worldedit.player_direction(name, direction)
if not axis or not sign then
return false, S("Invalid if looking straight up or down")
end
repetitions = repetitions * sign
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count = worldedit.volume(pos1, pos2) * math.abs(repetitions)
@@ -149,9 +143,10 @@ worldedit.register_command("stretch", {
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)
local count
count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz)
--reset markers to scaled positions
-- reset markers to scaled positions
worldedit.pos1[name] = pos1
worldedit.pos2[name] = pos2
worldedit.marker_update(name)
@@ -180,9 +175,10 @@ worldedit.register_command("transpose", {
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)
local count
count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2)
--reset markers to transposed positions
-- reset markers to transposed positions
worldedit.pos1[name] = pos1
worldedit.pos2[name] = pos2
worldedit.marker_update(name)
@@ -192,49 +188,58 @@ worldedit.register_command("transpose", {
})
worldedit.register_command("flip", {
params = "x/y/z/?",
params = tostring(VALID_DIR),
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
if not VALID_DIR[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)
local axis = worldedit.player_direction(name, param)
if axis == nil then
return false, S("Invalid if looking straight up or down")
end
local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], axis)
return true, S("@1 nodes flipped", count)
end,
})
worldedit.register_command("rotate", {
params = "x/y/z/? <angle>",
params = tostring(VALID_DIR) .. " <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
local found, _, direction, angle = param:find("^([^%s]+)%s+([+-]?%d+)$")
if found == nil or not VALID_DIR[direction] 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
return true, direction, 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)
func = function(name, direction, angle)
local axis = worldedit.player_direction(name, direction)
if axis == nil then
return false, S("Invalid if looking straight up or down")
end
--reset markers to rotated positions
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
local count
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)

View File

@@ -11,7 +11,7 @@ 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"),
description = S("WorldEdit Wand tool") .. "\n" .. S("Left-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