diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed13af..6b1813d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,14 @@ Note to self: See the bottom of this file for the release template text. - Add `//spush`, `//spop`, and `//sstack` - Add `//srect` (_select rectangle_), `//scol` (_select column_), `//scube` (_select cube_) - thanks, @VorTechnix! - Add `//scloud` (_select point cloud_), `//scentre` (_select centre node(s)_), `//srel` (_select relative_) - thanks, @VorTechnix! + - Add `//smake` (_selection make_) - thanks, @VorTechnix! - Significantly refactored backend utility functions (more to come in future updates) - `//bonemeal`: Try bonemealing everything that isn't an air block (#49) - Add new universal chance parsing - Any `` can now either be a 1-in-N number (e.g. `4`, `10`), or a percentage chance (e.g. `50%`, `10%`). - Caveat: Percentages are converted to a 1-in-N chance, but additionally that number is rounded down in some places - `//torus`, `//hollowtorus`: Add optional new axes - - `//torus`: Add optional hollow keyword - @VorTechnix + - `//torus`, `//ellipsoid`: Add optional hollow keyword - @VorTechnix - `//multi`: Add curly brace syntax for nesting command calls ([more information](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#multi-command_a-command_b-command_c-)) - `//erode`: Add new `river` erosion algorithm for filling in potholes and removing pillars diff --git a/Chat-Command-Reference.md b/Chat-Command-Reference.md index 441ad03..3629ed5 100644 --- a/Chat-Command-Reference.md +++ b/Chat-Command-Reference.md @@ -109,13 +109,15 @@ Note that the *entire* cave you want filling must be selected, as `//fillcaves` //fillcaves brick ``` -## `//ellipsoid ` +## `//ellipsoid [h[ollow]]` Creates a solid ellipsoid at position 1 with the radius `(rx, ry, rz)`. ``` //ellipsoid 10 5 15 ice //ellipsoid 3 5 10 dirt //ellipsoid 20 10 40 air +//ellipsoid 14 5 8 steelblock h +//ellipsoid 7 4 7 papyrus hollow ``` ## `//hollowellipsoid ` @@ -447,7 +449,7 @@ maxdiff | `float` | 0.4 | The maximum difference in height (between 0 and count | `float` | 25000 | The number of snowballs to simulate. noconv | any | n/a | When set to any value, disables to automatic 3x3 gaussian convolution. -Example invocations: +Usage examples: ``` //erode @@ -468,7 +470,7 @@ raise_sides | `string` | 4,3 | Comma separated list of numbers. Columns wit doraise | `boolean` | true | Whether to raise columns in height. If false, then no columns will be raised in height even if they are eligible to be so according to `raise_sides`. dolower | `boolean` | true | Whether to lower columns in height. If false, then no columns will be lowered in height even if they are eligible to be so according to `lower_sides`. -Example invocations: +Usage examples: ``` //erode river @@ -584,14 +586,14 @@ Short for _select point cloud_. Sets pos1 and pos2 to include the nodes you punc ``` ## `//scentre` -Short for _select center_. Sets pos1 and pos2 to the centre point(s) of the current selection area. 1, 2, 4 or 8 nodes may be selected depending on what parts of the original selection are even in distance. Implementation by @VorTechnix. +Short for _select center_. Sets pos1 and pos2 to the centre point(s) of the current selection area. 1, 2, 4 or 8 nodes may be selected depending on what parts of the original selection are even in distance. Implementation thanks to @VorTechnix. ``` //scentre ``` ## `//srel [ [ ]]` -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 by @VorTechnix. +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. ``` //srel front 5 @@ -600,6 +602,55 @@ Short for _select relative_. Sets the pos2 at set distances along 3 axes relativ //scube -z 12 -y -2 x -2 ``` +## `//smake [ []]` +Short for _selection make_. Modifies existing selection by moving pos2. Allows you to make the selection an odd or even length on one or more axes or set two or more axes equal to each other or the longest, shortest or average of them. Implementation thanks to @VorTechnix. + +Usage examples: + +``` +//smake odd shrink +//smake even avg xz +//smake equal grow xy +//smake equal average +//smake equal zy x +``` + +### ``: odd/even/equal/factor + +|Value | Description | +| --- | --- | +odd: | round up or down, based on mode, all axes specified in `` to the nearest odd length relative to pos1 +even: | round up or down, based on mode, all axes specified in `` to the nearest even length relative to pos1 +equal: | set `` axes length equal to the length of `` axis if specified or to the length of the largest, smallest or average of the `` axes based on mode. + +### `:` grow/shrink/average + +#### *If `` == odd or even:* + +|Value | Description | +| --- | --- | +grow: | grow each axis specified in `` to the nearest odd/even number to itself +shrink: | shrink each axis specified in `` to the nearest odd/even number to itself +average/avg: | take the average of all axes specified in `` and then for each specified axis grow or shrink it, depending on weather it is less than or greater than the average, to the nearest odd/even number to itself + +#### *If `` == equal:* ^[1] + +|Value | Description | +| --- | --- | +grow: | grow each axis specified in `` to the length of the longest specified axis +shrink: | shrink each axis specified in `` to the length of the shortest specified axis +average/avg: | set each axis specified in `` to the average length of all the specified axes + +### Additional arguments: + +|Name | Description | +| --- | --- | +``: | Specify axes to perform operation on (default= xz)| +``: If `` == odd or even: | Does nothing +``: If `` == equal: | Overrides ``^[1] and sets all `` axes equal to itself + +^[1]: `` argument can be omitted and will not be parsed if present if `` is specified + ## `//sstack` Displays the contents of your per-user selection stack. This stack can be pushed to and popped from rather like a stack of plates. See also `//spush` (for pushing to the selection stack) and `//spop` (for popping from the selection stack). diff --git a/Cookbook.md b/Cookbook.md index 2835be5..da0595f 100644 --- a/Cookbook.md +++ b/Cookbook.md @@ -9,6 +9,7 @@ See also: ## Fix lighting + ``` //multi //1 //2 //outset 50 //fixlight //y ``` diff --git a/README.md b/README.md index 3f08f9b..f55fb03 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ _(Do you have a cool build that you used WorldEditAdditions to build? [Get in to The detailed explanations have moved! Check them out [here](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md), or click the links below. ### Geometry - - [`//ellipsoid `](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#ellipsoid-rx-ry-rz-node_name) + - [`//ellipsoid [h[ollow]]`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#ellipsoid-rx-ry-rz-node_name-hollow) - [`//hollowellipsoid `](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#hollowellipsoid-rx-ry-rz-node_name) - [`//torus [ [h[ollow]]]`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#torus-major_radius-minor_radius-node_name-axesxy-hollow) - [`//hollowtorus []`](https://github.com/sbrl/Minetest-WorldEditAdditions/blob/main/Chat-Command-Reference.md#hollowtorus-major_radius-minor_radius-node_name-axesxy) diff --git a/worldeditadditions/utils/parse/init.lua b/worldeditadditions/utils/parse/init.lua index 27c9336..ae8d8b4 100644 --- a/worldeditadditions/utils/parse/init.lua +++ b/worldeditadditions/utils/parse/init.lua @@ -1,3 +1,9 @@ +-- ██████ █████ ██████ ███████ ███████ +-- ██ ██ ██ ██ ██ ██ ██ ██ +-- ██████ ███████ ██████ ███████ █████ +-- ██ ██ ██ ██ ██ ██ ██ +-- ██ ██ ██ ██ ██ ███████ ███████ + worldeditadditions.parse = {} dofile(worldeditadditions.modpath.."/utils/parse/chance.lua") diff --git a/worldeditadditions/utils/strings/init.lua b/worldeditadditions/utils/strings/init.lua index c62cc67..167ac70 100644 --- a/worldeditadditions/utils/strings/init.lua +++ b/worldeditadditions/utils/strings/init.lua @@ -1,2 +1,3 @@ dofile(worldeditadditions.modpath.."/utils/strings/split.lua") dofile(worldeditadditions.modpath.."/utils/strings/polyfill.lua") +dofile(worldeditadditions.modpath.."/utils/strings/tochars.lua") diff --git a/worldeditadditions/utils/strings/tochars.lua b/worldeditadditions/utils/strings/tochars.lua new file mode 100644 index 0000000..e8e34aa --- /dev/null +++ b/worldeditadditions/utils/strings/tochars.lua @@ -0,0 +1,27 @@ +--- Split into table of characters. +-- @param text string The string to iterate over +-- @param sort bool Sort characters +-- @param rem_dups bool Remove duplicate characters +-- @returns table A sequence table containing the substrings +function worldeditadditions.tochars(text,sort,rem_dups) + local t, set = {}, {} + if rem_dups then + text:gsub(".",function(c) set[c] = true end) + for k,v in pairs(set) do table.insert(t,k) end + else + text:gsub(".",function(c) table.insert(t,c) end) + end + + if sort then table.sort(t) end + + return t +end + +--- Split into a set of characters. +-- @param text string The string to iterate over +-- @returns table A sequence set table containing the substrings +function worldeditadditions.tocharset(text) + local t = {} + text:gsub(".",function(c) t[c] = true end) + return t +end diff --git a/worldeditadditions/utils/tables/init.lua b/worldeditadditions/utils/tables/init.lua index 9a9dd1c..2536672 100644 --- a/worldeditadditions/utils/tables/init.lua +++ b/worldeditadditions/utils/tables/init.lua @@ -9,12 +9,14 @@ -- extensive collection of functions :P -- TODO: Refactor into its own worldeditadditions.tables namespace. +worldeditadditions.tables = {} +dofile(worldeditadditions.modpath.."/utils/tables/sets.lua") dofile(worldeditadditions.modpath.."/utils/tables/shallowcopy.lua") dofile(worldeditadditions.modpath.."/utils/tables/table_apply.lua") -dofile(worldeditadditions.modpath.."/utils/tables/table_unpack.lua") -dofile(worldeditadditions.modpath.."/utils/tables/table_get_last.lua") -dofile(worldeditadditions.modpath.."/utils/tables/table_tostring.lua") -dofile(worldeditadditions.modpath.."/utils/tables/table_map.lua") dofile(worldeditadditions.modpath.."/utils/tables/table_filter.lua") +dofile(worldeditadditions.modpath.."/utils/tables/table_get_last.lua") +dofile(worldeditadditions.modpath.."/utils/tables/table_map.lua") +dofile(worldeditadditions.modpath.."/utils/tables/table_tostring.lua") dofile(worldeditadditions.modpath.."/utils/tables/table_unique.lua") +dofile(worldeditadditions.modpath.."/utils/tables/table_unpack.lua") diff --git a/worldeditadditions/utils/tables/sets.lua b/worldeditadditions/utils/tables/sets.lua new file mode 100644 index 0000000..8074991 --- /dev/null +++ b/worldeditadditions/utils/tables/sets.lua @@ -0,0 +1,9 @@ +--- Creates a table that stores data in keys. +-- @source https://riptutorial.com/lua/example/13407/search-for-an-item-in-a-list +-- @param list table The table of values to convert to keys. +-- @return table The table of (key,true) pairs. +function worldeditadditions.makeset (list) + local set = {} + for _, l in ipairs(list) do set[l] = true end + return set +end diff --git a/worldeditadditions/utils/vector.lua b/worldeditadditions/utils/vector.lua index ebcf4c1..4c767b4 100644 --- a/worldeditadditions/utils/vector.lua +++ b/worldeditadditions/utils/vector.lua @@ -5,7 +5,7 @@ function worldeditadditions.vector.tostring(v) return "(" .. v.x ..", " .. v.y ..", " .. v.z ..")" end --- Calculates the length squared of the given vector. +--- Calculates the length squared of the given vector. -- @param v Vector The vector to operate on -- @return number The length of the given vector squared function worldeditadditions.vector.lengthsquared(v) @@ -53,6 +53,17 @@ function worldeditadditions.vector.ceil(v) if v.z then v.z = math.ceil(v.z) end end +--- Sets the values in a vector to their absolute values. +-- Warning: This MUTATES the given vector! +-- @param v Vector The vector to operate on +function worldeditadditions.vector.abs(v) + if v.x then v.x = math.abs(v.x) end + -- Some vectors are 2d, but on the x / z axes + if v.y then v.y = math.abs(v.y) end + -- Some vectors are 2d + if v.z then v.z = math.abs(v.z) end +end + --- Determines if the target point is contained within the defined worldedit region. -- @param pos1 Vector pos1 of the defined region. -- @param pos2 Vector pos2 of the defined region. @@ -93,3 +104,19 @@ end function worldeditadditions.vector.mean(pos1, pos2) return vector.new((pos1.x + pos2.x)/2, (pos1.y + pos2.y)/2, (pos1.z + pos2.z)/2) end + +--- Returns a vector of the min values of 2 positions. +-- @param pos1 Vector pos1 of the defined region. +-- @param pos2 Vector pos2 of the defined region. +-- @return Vector Min values from input vectors. +function worldeditadditions.vector.min(pos1, pos2) + return vector.new(math.min(pos1.x + pos2.x), math.min(pos1.y + pos2.y), math.min(pos1.z + pos2.z)) +end + +--- Returns a vector of the max values of 2 positions. +-- @param pos1 Vector pos1 of the defined region. +-- @param pos2 Vector pos2 of the defined region. +-- @return Vector Max values from input vectors. +function worldeditadditions.vector.max(pos1, pos2) + return vector.new(math.max(pos1.x + pos2.x), math.max(pos1.y + pos2.y), math.max(pos1.z + pos2.z)) +end diff --git a/worldeditadditions_commands/commands/ellipsoid.lua b/worldeditadditions_commands/commands/ellipsoid.lua index 47e1b25..c08e6c7 100644 --- a/worldeditadditions_commands/commands/ellipsoid.lua +++ b/worldeditadditions_commands/commands/ellipsoid.lua @@ -3,35 +3,35 @@ -- █████ ██ ██ ██ ██████ ███████ ██ ██ ██ ██ ██ -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ███████ ███████ ███████ ██ ██ ███████ ██████ ██ ██████ - +local wea = worldeditadditions local function parse_params_ellipsoid(params_text) - local found, _, radius_x, radius_y, radius_z, replace_node = params_text:find("([0-9]+)%s+([0-9]+)%s+([0-9]+)%s+([a-z:_\\-]+)") + local parts = wea.split(params_text, "%s+", false) - if found == nil then - return false, "Invalid syntax" + if #parts < 4 then + return false, "Error: Not enough arguments. Expected \" [h[ollow]]\"." end - local radius = { - x = tonumber(radius_x), - y = tonumber(radius_y), - z = tonumber(radius_z) - } + local radius = minetest.string_to_pos(parts[1].." "..parts[2].." "..parts[3]) + if not radius then + return false, "Error: 3 radii must be specified." + end + wea.vector.abs(radius) - replace_node = worldedit.normalize_nodename(replace_node) + local replace_node = worldedit.normalize_nodename(parts[4]) if not replace_node then - worldedit.player_notify(name, "Error: Invalid node name.") - return false - end - if not radius.x or not radius.y or not radius.z then - worldedit.player_notify(name, "Error: Invalid radius(es).") - return false + return false, "Error: Invalid replace_node specified." end - return true, replace_node, radius + local hollow = false + if parts[5] == "hollow" or parts[5] == "h" then + hollow = true + end + + return true, replace_node, radius, hollow end worldedit.register_command("ellipsoid", { - params = " ", + params = " [h[ollow]]", description = "Creates a 3D ellipsoid with a radius of (rx, ry, rz) at pos1, filled with .", privs = { worldedit = true }, require_pos = 1, @@ -42,9 +42,9 @@ worldedit.register_command("ellipsoid", { nodes_needed = function(name, target_node, radius) return math.ceil(4/3 * math.pi * radius.x * radius.y * radius.z) end, - func = function(name, target_node, radius) + func = function(name, target_node, radius, hollow) local start_time = worldeditadditions.get_ms_time() - local replaced = worldeditadditions.ellipsoid(worldedit.pos1[name], radius, target_node, false) + local replaced = worldeditadditions.ellipsoid(worldedit.pos1[name], radius, target_node, hollow) local time_taken = worldeditadditions.get_ms_time() - start_time minetest.log("action", name .. " used //ellipsoid at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. replaced .. " nodes in " .. time_taken .. "s") diff --git a/worldeditadditions_commands/commands/selectors/smake.lua b/worldeditadditions_commands/commands/selectors/smake.lua new file mode 100644 index 0000000..6dcab3f --- /dev/null +++ b/worldeditadditions_commands/commands/selectors/smake.lua @@ -0,0 +1,146 @@ +-- ███████ ███ ███ █████ ██ ██ ███████ +-- ██ ████ ████ ██ ██ ██ ██ ██ +-- ███████ ██ ████ ██ ███████ █████ █████ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ███████ ██ ██ ██ ██ ██ ██ ███████ +local wea = worldeditadditions +worldedit.register_command("smake", { + params = " [ []]", + description = "Make one or more axes of the current selection odd, even, or equal to another.", + privs = { worldedit = true }, + require_pos = 2, + parse = function(params_text) + -- Split params_text, check for missing arguments and fill in empty spots + local parts = wea.split(params_text, "%s+", false) + if #parts < 2 then + return false, "Error: Not enough arguments. Expected \" [ []]\"." + else + for i=3,4 do if not parts[i] then parts[i] = false end end + end + + -- Initialze local variables and sets + local oper, mode, targ, base = unpack(parts) + local operSet, modeSet = wea.makeset {"equal", "odd", "even"}, wea.makeset {"grow", "shrink", "avg"} + + -- Main Logic + -- Check base if base is present and if so valid. + if base then + if base:match("[xyz]") then -- ensure correct base syntax + base = base:match("[xyz]") + else + return false, "Error: Invalid base \""..base.."\". Expected \"x\", \"y\" or \"z\"." + end + end + + -- Resolve target then mode (in that order incase mode is target). + 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.tochars(targ:match("[xyz]+"),true,true)) + else + return false, "Error: Invalid \""..targ.."\". Expected \"x\" and or \"y\" and or \"z\"." + end + + if mode == "average" then -- If mode is average set to avg + mode = "avg" + elseif mode:match("[xyz]+") then -- If target is actually base set vars to correct values. + base, targ, mode = targ:sub(1,1), table.concat(wea.tochars(mode:match("[xyz]+"),true,true)), false + elseif not modeSet[mode] and not base then -- If mode is invalid and base isn't present throw error + return false, "Error: Invalid \""..mode.."\". Expected \"grow\", \"shrink\", or \"average\"/\"avg\"." + end + + if base then + if oper ~= "equal" then base = false -- If operation isn't equalize we don't need + elseif targ:match(base) then -- Else check that base is not in target and return error if it is + return false, "Error: ("..base..") cannot be included in ("..targ..")." + end + end + + -- Check if operator is valid + if not operSet[oper] then + return false, "Error: Invalid operator \""..oper.."\". Expected \"odd\", \"even\" or \"equal\"." + end + + if false then -- Argument test + return false, ": " .. oper .. ", : " .. tostring(mode) .. ", : " .. tostring(targ) .. ", : " .. tostring(base) + end + return true, oper, mode, targ, base + end, + func = function(name, oper, mode, targ, base) + local p1, p2 = vector.new(worldedit.pos1[name]), vector.new(worldedit.pos2[name]) + local eval = function() end -- Declare eval placeholder function to edit later + + local delta = vector.subtract(p2,p1) -- 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.tocharset(targ) -- Break up targ string into set table + local _m = 0 -- _m is the container to hold the max, min or average (depending on the mode) of the axes in targ targ + + -- set _m to the max, min or mean of the target axes depending on mode or base if it exists + if base then _m = delta[base] + elseif mode == "avg" then + for k,v in pairs(targ) do _m = _m + math.abs(delta[k]) end + _m = _m / _tl + elseif mode == "grow" then + for k,v in pairs(targ) do if math.abs(delta[k]) > _m then _m = math.abs(delta[k]) end end + else + -- Take output of next(targ), put it in a table, wrap the table in brackets to force evlauation so that + -- we can call the first element of that table to serve as the key for a call to delta. + _m = delta[({next(targ)})[1]] + for k,v in pairs(targ) do if math.abs(delta[k]) < _m then _m = math.abs(delta[k]) end end + end + + if oper == "even" then + eval = function(int) + local tmp, abs, neg = int / 2, math.abs(int), int < 0 + if math.floor(tmp) ~= tmp then + if mode == "avg" then + if int > _m then int = abs - 1 + else int = abs + 1 end + elseif mode == "shrink" and abs > 0 then int = abs - 1 + else int = abs + 1 end + end + if neg then int = int * -1 end + return int + end + elseif oper == "odd" then + eval = function(int) + local tmp, abs, neg = int / 2, math.abs(int), int < 0 + if math.floor(tmp) == tmp then + if mode == "avg" then + if int > _m then int = abs - 1 + else int = abs + 1 end + elseif mode == "shrink" and abs > 0 then int = abs - 1 + else int = abs + 1 end + end + if neg then int = int * -1 end + return int + end + else -- Case: oper == "equal" + eval = function(int) + -- Bug: shrink sets pos2 to pos1 + if int < 0 then return _m * -1 + else return _m end + end + -- return false, "Case \"equal\" not handled." + end + + --- Test: + if false then + local brk = "" + for k,v in pairs(targ) do + brk = brk..k..": "..delta[k]..", " + delta[k] = eval(delta[k]) + brk = brk..k..": "..delta[k]..", " + end + return false, brk + end + -- //multi //fp set1 589 2 -82 //fp set2 615 2 -53 + -- //smake even shrink + + for k,v in pairs(targ) do delta[k] = eval(delta[k]) end + + worldedit.pos2[name] = vector.add(p1,delta) + worldedit.mark_pos2(name) + return true, "position 2 set to " .. minetest.pos_to_string(p2) + end +}) diff --git a/worldeditadditions_commands/commands/torus.lua b/worldeditadditions_commands/commands/torus.lua index 1593b2d..973944f 100644 --- a/worldeditadditions_commands/commands/torus.lua +++ b/worldeditadditions_commands/commands/torus.lua @@ -90,7 +90,7 @@ worldedit.register_command("hollowtorus", { local values = {parse_params_torus(params_text)} return unpack(values) end, - nodes_needed = function(name, target_node, major_radius, minor_radius, axes) + nodes_needed = function(name, target_node, major_radius, minor_radius) return math.ceil(2 * math.pi*math.pi * major_radius * minor_radius*minor_radius) end, func = function(name, target_node, major_radius, minor_radius, axes) diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index e1b9eb0..a03833d 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -46,6 +46,7 @@ dofile(we_c.modpath.."/commands/selectors/scentre.lua") dofile(we_c.modpath.."/commands/selectors/scloud.lua") dofile(we_c.modpath.."/commands/selectors/scol.lua") dofile(we_c.modpath.."/commands/selectors/scube.lua") +dofile(we_c.modpath.."/commands/selectors/smake.lua") dofile(we_c.modpath.."/commands/selectors/spop.lua") dofile(we_c.modpath.."/commands/selectors/spush.lua") dofile(we_c.modpath.."/commands/selectors/srect.lua")