Merge pull request #112 from sbrl/selection-tools-refactor

Selection tools refactor
This commit is contained in:
VorTechnix 2024-10-01 11:52:04 -07:00 committed by GitHub
commit f24ceffd2c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 660 additions and 618 deletions

1
.gitignore vendored

@ -242,6 +242,7 @@ temp/
# VorTechnix stuff
.vdev/
*.old.lua
# Created by https://www.toptal.com/developers/gitignore/api/archives
# Edit at https://www.toptal.com/developers/gitignore?templates=archives

@ -96,6 +96,15 @@ describe("parse_axes", function()
assert.are.same(Vector3.new(40, 77, 99), maxv)
end)
it("should infer that directions before a value are connected to that value", function()
local minv, maxv = parse_axes({
"xy", "-x", "5"
}, facing_dirs.z_neg)
assert.is.truthy(minv)
assert.are.same(Vector3.new(-5, 0, 0), minv)
assert.are.same(Vector3.new(5, 5, 0), maxv)
end)
it("should return 2 0,0,0 vectors if no input", function()
local minv, maxv = parse_axes({
-- No input

@ -4,7 +4,7 @@ It's about time I started a changelog! This will serve from now on as the main c
Note to self: See the bottom of this file for the release template text.
## v1.15: The untitled update (unreleased)
## v1.15: The direction update (unreleased)
- Added the optional argument `all` to [`//unmark`](https://worldeditadditions.mooncarrot.space/Reference/#unmark)
- Added a (rather nuclear) fix (attempt 4) at finally exterminating all zombie region marker walls forever
- This is not a hotfix to avoid endless small releases fixing the bug, as it's clear it's much more difficult to fix on all systems than initially expected
@ -16,11 +16,21 @@ Note to self: See the bottom of this file for the release template text.
- Added [`//set+`](https://worldeditadditions.mooncarrot.space/Reference/#set) for setting nodes and param2/light levels quickly.
- NOTE TO SELF: Setting light values doesn't appear to be working very well for some reason
- Added [`//ndef`](https://worldeditadditions.mooncarrot.space/Reference/#ndef) to print a given node's definition table. This is for debugging and development purposes.
- Added `//sgrow` and `//sshrink` commands to enlarge and shrink selection regions and aliased them over WorldEdit equivalents (`//expand`, `//outset` and `//contract`, `//inset` respectively).
- Added Unified Axis Syntax (UAS) parser. - Implementation by @VorTechnix
- See [UAS System reference] for details. (Note to self hook up hyperlink)
- Added `//uasparse` command to show the vectors produced by a given UAS expression. - Implementation by @VorTechnix
### Bugfixes and changes
- Don't warn on failed registration of `//flora` → [`//bonemeal`](https://worldeditadditions.mooncarrot.space/Reference/#bonemeal) if the `bonemeal` mod isn't installed (e.g. in MineClone2) - thanks @VorTechnix in #106
- Improve documentation of [`//noise2d`](https://worldeditadditions.mooncarrot.space/Reference/#noise2d). If it still doesn't make sense, please let me know. It's a complicated command that needs reworking a bit to be easier to use.
- Alias `//napply` to [`//nodeapply`](https://worldeditadditions.mooncarrot.space/Reference/#nodeapply)
- Re-factored selection tools to all use WEA position system and UAS Parser if applicable. - Re-factor by @VorTechnix
- `//sshift` now overrides `//shift` from WorldEdit. - Re-factor by @VorTechnix
### Deprecations
- Deprecated `//scol`, `//srect` and `//scube`. Now that `//srel` is using UAS parser there is no need for them. - Deprecated by @VorTechnix
- Deprecated `//sfactor`. Now that `//sgrow` and `//sshrink` exist it is no longer needed. - Deprecated by @VorTechnix
### Lua API changes

@ -1201,6 +1201,16 @@ This command is intended for debugging and development purposes, but if you're i
//ndef glass
```
### `//uasparse <unified axis syntax>`
Short for *Unified Axis Syntax Parse*. Parses the given UAS expression and prints the resulting vectors to the chat window.
```weacmd
//uasparse front right 5
//uasparse y 12 h -2
//uasparse left 3 up 5 -front 7
//uasparse -z 12 -y -2 x -2
```
## Selection
@ -1212,6 +1222,58 @@ This command is intended for debugging and development purposes, but if you're i
███████ ███████ ███████ ███████ ██████ ██ ██ ██████ ██ ████
-->
### Unified Axis Syntax (UAS)
The Unified Axis Syntax system allows users to input direction and distance information in three dimensions using "natural" measurement syntax. The key features include axis clumping, double negatives, relative directions, mirroring and compass directions (more information below).
*Note: negatives can be applied to axes, directions **AND** distances*
#### Relative Directions
|Key Words | Interpretation|
|----------|---------------|
|`f[ront]\|facing\|?` | The direction the player is facing most toward in the world
|`b[ack]\|behind\|rear` | The opposite of the direction the player is facing most toward in the world
|`l[eft]` | The direction to the left of the player
|`r[ight]` | The direction to the right of the player
```weacmd
back 1
f r 4
-facing 13
```
#### Compass Directions
|Key Words | Interpretation|
|----------|---------------|
|`n[orth]` | z
|`s[outh]` | -z
|`e[ast]` | x
|`w[est]` | -x
|`u[p]` | y
|`d[own]` | -y
```weacmd
south 3
north west 5
e d -2
```
#### Axis Clumping
Supported axes are `x`, `y`, `z`, `h`, `v`. All horizontal axes are covered by `h` and both vertical ones are covered by `v`.
- `h 5` == `xz -xz 5` == `x 5 z 5 x -5 z -5`
- `v 5` == `up down 5` == `y -y 5`
- `vxz 5` == `xyz -y 5` == `xyz 5 y -5`
#### Inference and Omnidirectionality
The UAS parser takes command input that is split by whitespace and interprets it as a series of numbers preceded by directional cues. If a number is preceded by another number or nothing it assumes that the number is to be applied on all axes in both positive and negative directions.
- `10` == `hv 10` == `xyz -xyz 10`
- `x 3 6` == `x 3 hv 6`
From the above examples you can also see the principle of inference. All direction modifiers before a value are interpreted as belonging to that value. So `x v 5` is equivalent to `x 5 v 5` and `xv 5`.
Because UAS parses "natural" measurement syntax, there are many ways to express the same direction and distance. This caters to users with different ways of thinking and different play styles which will hopefully make the tools easier to use.
### `//unmark`
> First overridden in v1.14
@ -1310,7 +1372,6 @@ Short for _select column_. Sets the pos2 at a set distance along 1 axis from pos
//scol x 3
```
### `//srect [<axis1> [<axis2>]] <length>`
> Added in v1.12; deprecated in favour of [`//srel`](#srel) in v1.15
@ -1365,17 +1426,43 @@ Short for _select center_. Sets pos1 and pos2 to the centre point(s) of the curr
//scentre
```
### `//srel <axis1> <length1> [<axis2> <length2> [<axis3> <length3>]]`
### `//srel <unified axis syntax>`
Short for _select relative_. Sets the pos2 at set distances along 3 axes relative to pos1. If pos1 is not set it will default to the node directly under the player. The axis arguments accept `x, y, z` as well as `up, down, left, right, front, back`. Left, right, front and back are relative to player facing direction. Negative (`-`) can be applied to the axis, the length or both. Implementation thanks to @VorTechnix.
```weacmd
//srel front 5
//srel y 12 right -2
//srel y 12 right -2
//srel left 3 up 5 -front 7
//srel -z 12 -y -2 x -2
```
### `//sshift <axis1> <length1> [<axis2> <length2> [<axis3> <length3>]]`
### `//sgrow <unified axis syntax>`
> Added in v1.15
Short for _selection grow_. Grows the current selection along specified axes/directions.
Aliases: `//extend`, `//outset`.
```weacmd
//sgrow back 4
//sgrow left -2 v r 2
//sgrow h 4
//sgrow -zy -2 x -2
```
### `//sshrink <unified axis syntax>`
> Added in v1.15
Short for _selection shrink_. Shrinks the current selection along specified axes/directions.
Aliases: `//contract`, `//inset`.
```weacmd
//sshrink left 4
//sshrink right -2 up 2
//sshrink v 4
//sshrink -hy 2 x -3 true
```
### `//sshift <unified axis syntax>`
> Added in v1.13
Short for _selection shift_. Shifts the WorldEdit region along 3 axes. The axis arguments accept `x, y, z` as well as `up, down, left, right, front, back`. Left, right, front and back are relative to player facing direction. Negative (`-`) can be applied to the axis, the length or both. Implementation thanks to @VorTechnix.

@ -80,20 +80,23 @@ The detailed explanations have moved! Check them out [here](https://worldeditadd
- [`//basename <name>`](https://worldeditadditions.mooncarrot.space/Reference/#basename)
- [`//ngroups <node_name> [v[erbose]]`](https://worldeditadditions.mooncarrot.space/Reference/#ngroups) _(new in v1.15)_
- [`//ndef <node_name>`](https://worldeditadditions.mooncarrot.space/Reference/#ndef) _(new in v1.15)_
- [`//uasparse <unified axis syntax>`](https://worldeditadditions.mooncarrot.space/Reference/#uasparse) _(new in v1.15)_
### Selection
- [`//scol [<axis1> ] <length>`](https://worldeditadditions.mooncarrot.space/Reference/#scol)
- [`//srect [<axis1> [<axis2>]] <length>`](https://worldeditadditions.mooncarrot.space/Reference/#srect)
- [`//scube [<axis1> [<axis2> [<axis3>]]] <length>`](https://worldeditadditions.mooncarrot.space/Reference/#scube)
- [~~`//scol [<axis1> ] <length>`~~](https://worldeditadditions.mooncarrot.space/Reference/#scol) (REMOVED in v1.15)
- [~~`//srect [<axis1> [<axis2>]] <length>`~~](https://worldeditadditions.mooncarrot.space/Reference/#srect) (REMOVED in v1.15)
- [~~`//scube [<axis1> [<axis2> [<axis3>]]] <length>`~~](https://worldeditadditions.mooncarrot.space/Reference/#scube) (REMOVED in v1.15)
- [`//scloud <0-6|stop|reset>`](https://worldeditadditions.mooncarrot.space/Reference/#scloud)
- [`//scentre`](https://worldeditadditions.mooncarrot.space/Reference/#scentre)
- [`//srel <axis1> <length1> [<axis2> <length2> [<axis3> <length3>]]`](https://worldeditadditions.mooncarrot.space/Reference/#srel)
- [`//sgrow <unified axis syntax>`](https://worldeditadditions.mooncarrot.space/Reference/#sgrow) _(new in v1.15)_
- [`//srel <unified axis syntax>`](https://worldeditadditions.mooncarrot.space/Reference/#srel)
- [`//smake <operation:odd|even|equal> <mode:grow|shrink|average> [<target=xz> [<base>]]`](https://worldeditadditions.mooncarrot.space/Reference/#smake)
- [`//sshrink <unified axis syntax>`](https://worldeditadditions.mooncarrot.space/Reference/#sshrink) _(new in v1.15)_
- [`//sstack`](https://worldeditadditions.mooncarrot.space/Reference/#sstack)
- [`//spush`](https://worldeditadditions.mooncarrot.space/Reference/#spush)
- [`//spop`](https://worldeditadditions.mooncarrot.space/Reference/#spop)
- [`//sshift <axis1> <length1> [<axis2> <length2> [<axis3> <length3>]]`](https://worldeditadditions.mooncarrot.space/Reference/#sshift)
- [`//sfactor <mode:grow|shrink|average> <factor> [<target=xz>]`](https://worldeditadditions.mooncarrot.space/Reference/#sfactor)
- [`//sshift <unified axis syntax>`](https://worldeditadditions.mooncarrot.space/Reference/#sshift)
- [~~`//sfactor <mode:grow|shrink|average> <factor> [<target=xz>]`~~](https://worldeditadditions.mooncarrot.space/Reference/#sfactor) (REMOVED in v1.15)
- [`//pos <index>`](https://worldeditadditions.mooncarrot.space/Reference/#pos)
- [`//pos1`](https://worldeditadditions.mooncarrot.space/Reference/#pos1)
- [`//pos2`](https://worldeditadditions.mooncarrot.space/Reference/#pos2)

@ -9,19 +9,24 @@
local wea_cmdpath = worldeditadditions_commands.modpath .. "/commands/selectors/"
local weac = worldeditadditions_core
dofile(wea_cmdpath.."srel.lua")
dofile(wea_cmdpath.."scentre.lua")
dofile(wea_cmdpath.."scloud.lua")
dofile(wea_cmdpath.."scol.lua")
dofile(wea_cmdpath.."scube.lua")
dofile(wea_cmdpath.."sfactor.lua")
dofile(wea_cmdpath.."sgrow.lua")
dofile(wea_cmdpath.."smake.lua")
dofile(wea_cmdpath.."spop.lua")
dofile(wea_cmdpath.."spush.lua")
dofile(wea_cmdpath.."srect.lua")
dofile(wea_cmdpath.."srel.lua")
dofile(wea_cmdpath.."sshift.lua")
dofile(wea_cmdpath.."sshrink.lua")
dofile(wea_cmdpath.."sstack.lua")
--- DEPRECATED ---
dofile(wea_cmdpath.."scol.lua")
dofile(wea_cmdpath.."scube.lua")
dofile(wea_cmdpath.."srect.lua")
dofile(wea_cmdpath.."sfactor.lua")
--- END DEPRECATED ---
dofile(wea_cmdpath.."unmark.lua")
dofile(wea_cmdpath.."mark.lua")
dofile(wea_cmdpath.."pos1-2.lua")
@ -30,5 +35,12 @@ dofile(wea_cmdpath.."reset.lua")
-- Aliases
weac.register_alias("sfac", "sfactor")
weac.register_alias("sgrow", "expand", true) -- true = override target
weac.register_alias("sgrow", "outset", true) -- true = override target
weac.register_alias("sshrink", "contract", true) -- true = override target
weac.register_alias("sshrink", "inset", true) -- true = override target
weac.register_alias("sshift", "shift", true) -- true = override target
weac.register_alias("1", "pos1", true) -- true = override target
weac.register_alias("2", "pos2", true) -- true = override target

@ -16,19 +16,17 @@ worldeditadditions_core.register_command("scentre", {
end,
func = function(name)
local mean = Vector3.mean(
Vector3.clone(worldedit.pos1[name]),
Vector3.clone(worldedit.pos2[name])
Vector3.clone(wea_c.pos.get(name, 1)),
Vector3.clone(wea_c.pos.get(name, 2))
)
local pos1, pos2 = Vector3.clone(mean), Vector3.clone(mean)
pos1 = pos1:floor()
pos2 = pos2:ceil()
worldedit.pos1[name], worldedit.pos2[name] = pos1, pos2
worldedit.mark_pos1(name)
worldedit.mark_pos2(name)
wea_c.pos.set_all(name, {pos1, pos2})
return true, "position 1 set to "..pos1..", position 2 set to "..pos2
return true, "Position 1 to "..pos1..", Position 2 to "..pos2
end,
})

@ -7,37 +7,15 @@ local Vector3 = wea_c.Vector3
-- ██ ██ ██ ██ ██
-- ███████ ██████ ██████ ███████
worldeditadditions_core.register_command("scol", {
params = "[<axis1>] <length>",
description = "Set WorldEdit region position 2 at a set distance along 1 axis.",
privs = {worldedit=true},
params = "None",
description = "DEPRECATED: please use //srel instead.",
privs = { worldedit = true },
require_pos = 1,
parse = function(params_text)
local vec, tmp = Vector3.new(0, 0, 0), {}
local find = wea_c.split(params_text, "%s", false)
local ax1, sn1, len = (tostring(find[1]):match('[xyz]') or "g"):sub(1,1), wea_c.getsign(find[1]), find[table.maxn(find)]
tmp.len = tonumber(len)
-- If len == nil cancel the operation
if not tmp.len then return false, "No length specified." end
-- If ax1 is bad send "get" order
if ax1 == "g" then tmp.get = true
else vec[ax1] = sn1 * tmp.len end
return true, vec, tmp
-- tmp carries:
-- The length (len) arguement to the main function for use if "get" is invoked there
-- The bool value "get" to tell the main function if it needs to populate missing information in vec
return params_text
end,
func = function(name, vec, tmp)
if tmp.get then
local ax, dir = wea_c.player_axis2d(name)
vec[ax] = tmp.len * dir
end
local pos2 = vec + Vector3.clone(worldedit.pos1[name])
worldedit.pos2[name] = pos2
worldedit.mark_pos2(name)
return true, "position 2 set to "..pos2
func = function(name, paramtext)
return false, "DEPRECATED: please use //srel instead..."
end,
})

@ -7,48 +7,15 @@ local Vector3 = wea_c.Vector3
-- ██ ██ ██ ██ ██ ██ ██
-- ███████ ██████ ██████ ██████ ███████
worldeditadditions_core.register_command("scube", {
params = "[<axis1> [<axis2> [<axis3>]]] <length>",
description = "Set WorldEdit region position 2 at a set distance along 3 axes.",
params = "None",
description = "DEPRECATED: please use //srel instead.",
privs = { worldedit = true },
require_pos = 1,
parse = function(params_text)
local vec, tmp = Vector3.new(0, 0, 0), {}
local find = wea_c.split(params_text, "%s", false)
local ax1, ax2, ax3 = (tostring(find[1]):match('[xyz]') or "g"):sub(1,1), (tostring(find[2]):match('[xyz]') or "g"):sub(1,1),
(tostring(find[3]):match('[xyz]') or "g"):sub(1,1)
local sn1, sn2, sn3, len = wea_c.getsign(find[1]), wea_c.getsign(find[2]), wea_c.getsign(find[3]), find[table.maxn(find)]
tmp.len = tonumber(len)
-- If len is nill cancel the operation
if not tmp.len then return false, "No length specified." end
-- If axis is bad send "get" order
if ax1 == "g" then tmp.get = true
else vec[ax1] = sn1 * tmp.len end
if ax2 == "g" then tmp.get = true
else vec[ax2] = sn2 * tmp.len end
if ax3 == "g" then tmp.get = true
else vec[ax3] = sn3 * tmp.len end
tmp.axes = ax1..","..ax2..","..ax3
return true, vec, tmp
-- tmp carries:
-- The length (len) arguement to the main function for use if "get" is invoked there
-- The bool value "get" to tell the main function if it needs to populate missing information in vec
-- The string "axes" to tell the main function what axes are and/or need to be populated if "get" is invoked
return params_text
end,
func = function(name, vec, tmp)
if tmp.get then
local ax, dir = wea_c.player_axis2d(name)
local _, left, sn = wea_c.axis_left(ax,dir)
if not tmp.axes:find("x") then vec.x = tmp.len * (ax == "x" and dir or sn) end
if not tmp.axes:find("z") then vec.z = tmp.len * (ax == "z" and dir or sn) end
if not tmp.axes:find("y") then vec.y = tmp.len end
end
local pos2 = vec + Vector3.clone(worldedit.pos1[name])
worldedit.pos2[name] = pos2
worldedit.mark_pos2(name)
return true, "position 2 set to "..pos2
func = function(name, paramtext)
return false, "DEPRECATED: please use //srel instead..."
end,
})

@ -9,79 +9,14 @@ local Vector3 = wea_c.Vector3
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ███████ ██ ██ ██ ██████ ██ ██████ ██ ██
worldeditadditions_core.register_command("sfactor", {
params = "<mode> <factor> [<target=xz>]",
description = "Make the length of one or more target axes of the current selection to be multiple(s) of <factor>.",
params = "None",
description = "DEPRECATED: please use //grow or //shrink instead.",
privs = { worldedit = true },
require_pos = 2,
parse = function(params_text)
local parts = wea_c.split(params_text, "%s+", false)
if #parts < 2 then
return false, "Error: Not enough arguments. Expected \"<mode> <factor> [<target>]\"."
end
local mode, fac, targ = wea_c.table.unpack(parts)
local modeSet = wea_c.table.makeset {"grow", "shrink", "avg"}
-- Mode parsing
if mode == "average" then -- If mode is average set to avg
mode = "avg"
elseif not modeSet[mode] then -- If mode is invalid throw error
return false, "Error: Invalid <mode> \""..mode.."\". Expected \"grow\", \"shrink\", or \"average\"/\"avg\"."
end
-- Factor parsing
local factest = tonumber(fac)
if not factest then
return false, "Error: Invalid <factor> \""..fac.."\". Expected a number."
elseif factest < 2 then
return false, "Error: <factor> is too low. Expected a number equal to or greater than 2."
else
fac = math.floor(factest+0.5)
end
-- Target parsing
if not targ then -- If no target set to default (xz)
targ = "xz"
elseif targ:match("[xyz]+") then -- ensure correct target syntax
targ = table.concat(wea_c.tochars(targ:match("[xyz]+"),true,true))
else
return false, "Error: Invalid <target> \""..targ.."\". Expected \"x\" and or \"y\" and or \"z\"."
end
return true, mode, fac, targ
return params_text
end,
func = function(name, mode, fac, targ)
local pos1, pos2 = Vector3.clone(worldedit.pos1[name]), Vector3.clone(worldedit.pos2[name])
local delta = pos2 - pos1 -- local delta equation: Vd(a) = V2(a) - V1(a)
local _tl = #targ -- Get targ length as a variable incase mode is "average"/"avg"
local targ = wea_c.tocharset(targ) -- Break up targ string into set table
local _m = 0 -- _m is the container to hold the average of the axes in targ
-- set _m to the max, min or mean of the target axes depending on mode (_tl is the length of targ) or base if it exists
if mode == "avg" then
for k,v in pairs(targ) do _m = _m + math.abs(delta[k]) end
_m = _m / _tl
end
-- Equation: round(delta[<axis>] / factor) * factor
local eval = function(int,fac_inner)
local tmp, abs, neg = int / fac_inner, math.abs(int), int < 0
if mode == "avg" then
if int > _m then int = math.floor(abs / fac_inner) * fac_inner
else int = math.ceil(abs / fac_inner) * fac_inner end
elseif mode == "shrink" then int = math.floor(abs / fac_inner) * fac_inner
else int = math.ceil(abs / fac_inner) * fac_inner end
if int < fac_inner then int = fac_inner end -- Ensure selection doesn't collapse to 0
if neg then int = int * -1 end -- Ensure correct facing direction
return int
end
for k,v in pairs(targ) do delta[k] = eval(delta[k],fac) end
worldedit.pos2[name] = pos1 + delta
worldedit.mark_pos2(name)
return true, "position 2 set to "..pos2
func = function(name, paramtext)
return false, "DEPRECATED: please use //grow or //shrink instead..."
end
})

@ -0,0 +1,42 @@
-- local wea = worldeditadditions
local wea_c = worldeditadditions_core
local Vector3 = wea_c.Vector3
-- ███████ ██████ ██████ ██████ ██ ██
-- ██ ██ ██ ██ ██ ██ ██ ██
-- ███████ ██ ███ ██████ ██ ██ ██ █ ██
-- ██ ██ ██ ██ ██ ██ ██ ██ ███ ██
-- ███████ ██████ ██ ██ ██████ ███ ███
worldeditadditions_core.register_command("sgrow", {
params = "<unified axis syntax>",
description = "Grow selection region",
privs = { worldedit = true },
require_pos = 0,
parse = function(params_text)
local ret = wea_c.split(params_text)
if #ret < 1 then return false, "SGROW: No params found!"
else return true, ret end
end,
func = function(name, params_text)
local facing = wea_c.player_dir(name)
local min, max = wea_c.parse.directions(params_text, facing)
if not min then return false, max end
local pos1 = wea_c.pos.get(name, 1)
local pos2 = wea_c.pos.get(name, 2)
if not pos2 then wea_c.pos.set(name, 2, pos1)
else pos1, pos2 = Vector3.sort(pos1, pos2) end
pos1, pos2 = pos1:add(min), pos2:add(max)
wea_c.pos.set_all(name, {pos1, pos2})
return true, "Position 1 to "..pos1..", Position 2 to "..pos2
end,
})
-- Tests
-- //srel front 5 left 3 y 2

@ -1,4 +1,3 @@
local wea = worldeditadditions
local wea_c = worldeditadditions_core
local Vector3 = wea_c.Vector3
@ -68,7 +67,7 @@ worldeditadditions_core.register_command("smake", {
return true, oper, mode, targ, base
end,
func = function(name, oper, mode, targ, base)
local pos1, pos2 = Vector3.clone(worldedit.pos1[name]), Vector3.clone(worldedit.pos2[name])
local pos1, pos2 = Vector3.clone(wea_c.pos.get(name, 1)), Vector3.clone(wea_c.pos.get(name, 2))
local eval -- Declare eval placeholder function to edit later
local delta = pos2 - pos1 -- local delta equation: Vd(a) = V2(a) - V1(a)
@ -124,10 +123,9 @@ worldeditadditions_core.register_command("smake", {
end
end
for k,v in pairs(targ) do delta[k] = eval(delta[k]) end
for k,_ in pairs(targ) do delta[k] = eval(delta[k]) end
worldedit.pos2[name] = pos1 + delta
worldedit.mark_pos2(name)
return true, "position 2 set to "..pos2
wea_c.pos.set(name, 2, pos1 + delta)
return true, "Position 2 to "..pos2
end
})

@ -1,4 +1,3 @@
local wea = worldeditadditions
local wea_c = worldeditadditions_core
local Vector3 = wea_c.Vector3
@ -8,43 +7,15 @@ local Vector3 = wea_c.Vector3
-- ██ ██ ██ ██ ██ ██
-- ███████ ██ ██ ███████ ██████ ██
worldeditadditions_core.register_command("srect", {
params = "[<axis1> [<axis2>]] <length>",
description = "Set WorldEdit region position 2 at a set distance along 2 axes.",
params = "None",
description = "DEPRECATED: please use //srel instead.",
privs = { worldedit = true },
require_pos = 1,
parse = function(params_text)
local vec, tmp = Vector3.new(0, 0, 0), {}
local find = wea_c.split(params_text, "%s", false)
local ax1, ax2 = (tostring(find[1]):match('[xyz]') or "g"):sub(1,1), (tostring(find[2]):match('[xyz]') or "g"):sub(1,1)
local sn1, sn2, len = wea_c.getsign(find[1]), wea_c.getsign(find[2]), find[table.maxn(find)]
tmp.len = tonumber(len)
-- If len == nill cancel the operation
if not tmp.len then return false, "No length specified." end
-- If axis is bad send "get" order
if ax1 == "g" then tmp.get = true
else vec[ax1] = sn1 * tmp.len end
if ax2 == "g" then tmp.get = true
else vec[ax2] = sn2 * tmp.len end
tmp.axes = ax1..","..ax2
return true, vec, tmp
-- tmp carries:
-- The length (len) arguement to the main function for use if "get" is invoked there
-- The bool value "get" to tell the main function if it needs to populate missing information in vec
-- The string "axes" to tell the main function what axes are and/or need to be populated if "get" is invoked
return params_text
end,
func = function(name, vec, tmp)
if tmp.get then
local ax, dir = wea_c.player_axis2d(name)
if not tmp.axes:find("[xz]") then vec[ax] = tmp.len * dir end
if not tmp.axes:find("y") then vec.y = tmp.len end
end
local p2 = vec + Vector3.clone(worldedit.pos1[name])
worldedit.pos2[name] = p2
worldedit.mark_pos2(name)
return true, "position 2 set to "..p2
func = function(name, paramtext)
return false, "DEPRECATED: please use //srel instead..."
end,
})

@ -1,4 +1,4 @@
local wea = worldeditadditions
-- local wea = worldeditadditions
local wea_c = worldeditadditions_core
local Vector3 = wea_c.Vector3
@ -8,49 +8,28 @@ local Vector3 = wea_c.Vector3
-- ███████ ██████ █████ ██
-- ██ ██ ██ ██ ██
-- ███████ ██ ██ ███████ ███████
local function parse_with_name(name,args)
local vec, tmp = Vector3.new(0, 0, 0), {}
local find, _, i = {}, 0, 0
repeat
_, i, tmp.proc = args:find("([%l%s+-]+%d+)%s*", i)
if tmp.proc:match("[xyz]") then
tmp.ax = tmp.proc:match("[xyz]")
tmp.dir = tonumber(tmp.proc:match("[+-]?%d+")) * (tmp.proc:match("-%l+") and -1 or 1)
else
tmp.ax, _ = wea_c.dir_to_xyz(name, tmp.proc:match("%l+"))
if not tmp.ax then return false, _ end
tmp.dir = tonumber(tmp.proc:match("[+-]?%d+")) * (tmp.proc:match("-%l+") and -1 or 1) * _
end
vec[tmp.ax] = tmp.dir
until not args:find("([%l%s+-]+%d+)%s*", i)
return true, vec
end
worldeditadditions_core.register_command("srel", {
params = "<axis1> <length1> [<axis2> <length2> [<axis3> <length3>]]",
description = "Set WorldEdit region position 2 at set distances along 3 axes.",
params = "<unified axis syntax>",
description = "Set WorldEdit region position 2 relative to position 1 and player facing.",
privs = { worldedit = true },
require_pos = 0,
parse = function(params_text)
if params_text:match("([%l%s+-]+%d+)") then return true, params_text
else return false, "No acceptable params found" end
local ret = wea_c.split(params_text)
if #ret < 1 then return false, "SREL: No params found!"
else return true, ret end
end,
func = function(name, params_text)
local ret = ""
local _, vec = parse_with_name(name,params_text)
if not _ then return false, vec end
local facing = wea_c.player_dir(name)
local vec, err = wea_c.parse.directions(params_text, facing, true)
if not vec then return false, err end
if not worldedit.pos1[name] then
local pos = wea_c.player_vector(name) + Vector3.new(0.5, -0.5, 0.5)
pos = pos:floor()
worldedit.pos1[name] = pos
worldedit.mark_pos1(name)
ret = "position 1 set to "..pos..", "
end
local pos1 = wea_c.pos.get(name, 1)
local pos2 = pos1:add(vec)
local p2 = vec + Vector3.clone(worldedit.pos1[name])
worldedit.pos2[name] = p2
worldedit.mark_pos2(name)
return true, ret.."position 2 set to "..p2
wea_c.pos.set_all(name, {pos1, pos2})
return true, "Position 1 to "..pos1..", Position 2 to "..pos2
end,
})

@ -1,4 +1,3 @@
local wea = worldeditadditions
local wea_c = worldeditadditions_core
local Vector3 = worldeditadditions.Vector3
@ -8,46 +7,26 @@ local Vector3 = worldeditadditions.Vector3
-- ██ ██ ██ ██ ██ ██ ██
-- ███████ ███████ ██ ██ ██ ██ ██
local function parse_with_name(name,args)
local vec, tmp = Vector3.new(0, 0, 0), {}
local find, _, i = {}, 0, 0
repeat
_, i, tmp.proc = args:find("([%l%s+-]+%d+)%s*", i)
if tmp.proc:match("[xyz]") then
tmp.ax = tmp.proc:match("[xyz]")
tmp.dir = tonumber(tmp.proc:match("[+-]?%d+")) * (tmp.proc:match("-%l+") and -1 or 1)
else
tmp.ax, _ = wea_c.dir_to_xyz(name, tmp.proc:match("%l+"))
if not tmp.ax then return false, _ end
tmp.dir = tonumber(tmp.proc:match("[+-]?%d+")) * (tmp.proc:match("-%l+") and -1 or 1) * _
end
vec[tmp.ax] = tmp.dir
until not args:find("([%l%s+-]+%d+)%s*", i)
return true, vec
end
worldeditadditions_core.register_command("sshift", {
params = "<axis1> <distance1> [<axis2> <distance2> [<axis3> <distance3>]]",
params = "<unified axis syntax>",
description = "Shift the WorldEdit region in 3 dimensions.",
privs = { worldedit = true },
require_pos = 2,
parse = function(params_text)
if params_text:match("([%l%s+-]+%d+)") then return true, params_text
else return false, "No acceptable params found" end
local ret = wea_c.split(params_text)
if #ret < 1 then return false, "Error: No params found!"
else return true, ret end
end,
func = function(name, params_text)
local _, vec = parse_with_name(name,params_text)
if not _ then return false, vec end
local facing = wea_c.player_dir(name)
local vec, err = wea_c.parse.directions(params_text, facing, true)
if not vec then return false, err end
local pos1 = vec:add(worldedit.pos1[name])
worldedit.pos1[name] = pos1
worldedit.mark_pos1(name)
local pos1 = vec:add(wea_c.pos.get(name, 1))
local pos2 = vec:add(wea_c.pos.get(name, 2))
local pos2 = vec:add(worldedit.pos2[name])
worldedit.pos2[name] = pos2
worldedit.mark_pos2(name)
return true, "Region shifted by " .. (vec.x + vec.y + vec.z) .. " nodes."
wea_c.pos.set_all(name, {pos1, pos2})
return true, "Position 1 to "..pos1..", Position 2 to "..pos2
end,
})

@ -0,0 +1,42 @@
-- local wea = worldeditadditions
local wea_c = worldeditadditions_core
local Vector3 = wea_c.Vector3
-- ███████ ███████ ██ ██ ██████ ██ ███ ██ ██ ██
-- ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██
-- ███████ ███████ ███████ ██████ ██ ██ ██ ██ █████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ███████ ███████ ██ ██ ██ ██ ██ ██ ████ ██ ██
worldeditadditions_core.register_command("sshrink", {
params = "<unified axis syntax>",
description = "Shrink selection region",
privs = { worldedit = true },
require_pos = 0,
parse = function(params_text)
local ret = wea_c.split(params_text)
if #ret < 1 then return false, "Error: No params found!"
else return true, ret end
end,
func = function(name, params_text)
local facing = wea_c.player_dir(name)
local min, max = wea_c.parse.directions(params_text, facing)
if not min then return false, max end
local pos1 = wea_c.pos.get(name, 1)
local pos2 = wea_c.pos.get(name, 2)
if not pos2 then wea_c.pos.set(name, 2, pos1)
else pos1, pos2 = Vector3.sort(pos1, pos2) end
pos1, pos2 = pos1:add(max), pos2:add(min)
wea_c.pos.set_all(name, {pos1, pos2})
return true, "Position 1 to "..pos1..", Position 2 to "..pos2
end,
})
-- Tests
-- //srel front 5 left 3 y 2

@ -0,0 +1,29 @@
local wea_c = worldeditadditions_core
-- local Vector3 = wea_c.Vector3
-- ██ ██ █████ ███████ ██████ █████ ██████ ███████ ███████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ███████ ███████ ██████ ███████ ██████ ███████ █████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██████ ██ ██ ███████ ██ ██ ██ ██ ██ ███████ ███████
worldeditadditions_core.register_command("uasparse", {
params = "<unified axis syntax>",
description = "Debug command. Returns min and max vectors for given inputs",
privs = { worldedit = true },
-- require_pos = 2,
parse = function(params_text)
local ret = wea_c.split(params_text)
if #ret < 1 then return false, "Error: No params found!"
else return true, ret end
end,
func = function(name, params_text)
local facing = wea_c.player_dir(name)
local min, max = wea_c.parse.directions(params_text, facing)
if not min then
return false, max
else
return true, "Min: "..min.." Max: "..max
end
end
})

@ -41,6 +41,7 @@ dofile(wea_cmd.modpath.."/commands/revolve.lua")
dofile(wea_cmd.modpath.."/commands/rotate.lua")
dofile(wea_cmd.modpath.."/commands/orient.lua")
dofile(wea_cmd.modpath.."/commands/set.lua")
dofile(wea_cmd.modpath.."/commands/uasparse.lua")
-- Meta Commands
dofile(wea_cmd.modpath .. "/commands/meta/init.lua")

@ -23,7 +23,7 @@ else
end
--- Unified Axis Keywords banks
--- Unified Axis Syntax banks
local keywords = {
-- Compass keywords
compass = {
@ -36,7 +36,9 @@ local keywords = {
["w"] = "-x", ["west"] = "-x",
["-w"] = "x", ["-west"] = "x",
["u"] = "y", ["up"] = "y",
["-u"] = "-y", ["-up"] = "-y",
["d"] = "-y", ["down"] = "-y",
["-d"] = "y", ["-down"] = "y",
},
-- Direction keywords
@ -90,9 +92,9 @@ function parse.num(str)
else return false end
end
--- Checks if a string is a valid Unified Axis Keyword. (Supports axis clumping)
-- @param: str: String: Keyword instance to parse
-- @returns: Key Instance: returns keyword type, processed keyword content and signed number (or nil)
--- Checks if a string is a valid Unified Axis Syntax. (Supports axis clumping)
-- @param: str: String: Keyword to parse
-- @returns: keyword type, processed keyword content and signed number (or nil)
function parse.keyword(str)
if type(str) ~= "string" then
return "err", "Error: \""..tostring(str).."\" is not a string.", 404
@ -135,68 +137,65 @@ end
--- Converts Unified Axis Keyword table into Vector3 instances.
--- Converts Unified Axis Syntax table into Vector3 instances.
-- @param: tbl: Table: Keyword table to parse
-- @param: facing: Table: Output from worldeditadditions_core.player_dir(name)
-- @param: sum: Bool | String | nil: Return a single vector by summing the 2 output vectors together
-- @returns: Vector3, [Vector3]: returns min, max Vector3 or sum Vector3 (if @param: sum ~= nil)
-- if error: @returns: false, String: error message
function parse.keytable(tbl, facing, sum)
local min, max = Vector3.new(), Vector3.new()
local expected = 1
local tmp = {axes = {}, num = 0, sign = 1, mirror = false}
local min, max, mir= Vector3.new(), Vector3.new(), false
local neg, pos, v0 = Vector3.new(), Vector3.new(), Vector3.new()
--- Processes a number and adds it to the min and max vectors.
-- @param num The number to process.
-- @param axes The axes to apply the number to.
-- @param sign The sign of the number.
local function parseNumber(num, axes, sign)
if axes.rev then parseNumber(num, axes.rev, -sign) end
if num * sign >= 0 then
max = max:add(parse.vectorize(axes, num, sign))
local function update(num) -- Update "min" and "max"
if num < 0 then
min, max = min:add(pos:mul(num)), max:add(neg:mul(num))
else
min = min:add(parse.vectorize(axes, num, sign))
min, max = min:add(neg:mul(num)), max:add(pos:mul(num))
end
neg, pos = v0:clone(), v0:clone()
end
local function udir(axes, sign) -- Update "neg" and "pos"
if axes.rev then udir(axes.rev, -sign) end
for _, v in ipairs(axes) do
if sign < 0 then neg[v] = -1
else pos[v] = 1 end
end
end
for i, v in ipairs(tbl) do
for i,v in ipairs(tbl) do
if v:sub(1, 1) == "+" then
v = v:sub(2)
end
tmp.num = parse.num(v)
local num = parse.num(v)
if expected == 1 then
if tmp.num then
parseNumber(tmp.num, {"x", "y", "z", rev={"x", "y", "z"}}, tmp.sign)
else
local key_type, key_entry, key_sign = parse.keyword(v)
-- If we have a dimension add it to output
-- Else gather direction statements
if num then
-- Check for direction vectors
if neg == v0 and pos == v0 then
neg, pos = v0:add(-1), v0:add(1)
end
update(num)
else
local key_type, key_entry, key_sign = parse.keyword(v)
if key_type == "axis" then
tmp.axes = key_entry
tmp.sign = key_sign
udir(key_entry, key_sign)
elseif key_type == "dir" then
tmp.axes = {facing[key_entry].axis}
tmp.sign = facing[key_entry].sign * key_sign
udir({facing[key_entry].axis},
facing[key_entry].sign * key_sign)
elseif key_type == "rev" then
tmp.mirror = true
mir = true
else
return false, key_entry
end
expected = 2
end
else
if tmp.num then
parseNumber(tmp.num, tmp.axes, tmp.sign)
expected = 1
else
return false, "Error: Expected number after \""..tostring(tbl[i-1]).."\". Got \""..tostring(v).."\"."
end
end
end
if tmp.mirror and not sum then
if mir and not sum then
max = max:max(min:abs())
min = max:multiply(-1)
end
@ -209,6 +208,7 @@ function parse.keytable(tbl, facing, sum)
end
return {
keyword = parse.keyword,
keytable = parse.keytable,

@ -58,11 +58,12 @@ end
--- Split a string into substrings separated by a pattern.
-- @param str string The string to iterate over
-- @param dlm string The delimiter (separator) pattern
-- @param dlm="%s+" string The delimiter (separator) pattern. If a falsey value is passed, then the default value is used.
-- @param plain boolean If true (or truthy), pattern is interpreted as a
-- plain string, not a Lua pattern
-- @returns table A sequence table containing the substrings
local function split(str,dlm,plain)
if not dlm then dlm = "%s+" end
local pos, ret = 0, {}
local ins, i = str:find(dlm,pos,plain)
-- "if plain" shaves off some time in the while statement