Merge branch 'main' into VorTechnix

This commit is contained in:
VorTechnix 2021-07-15 13:27:30 -07:00
commit c57e8e6558
45 changed files with 1321 additions and 249 deletions

@ -1,9 +1,13 @@
const os = require("os");
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const htmlentities = require("html-entities"); const htmlentities = require("html-entities");
const phin = require("phin");
const Image = require("@11ty/eleventy-img"); const Image = require("@11ty/eleventy-img");
var nextid = 0; var nextid = 0;
const image_filename_format = (_id, src, width, format, _options) => { const image_filename_format = (_id, src, width, format, _options) => {
@ -77,7 +81,25 @@ async function shortcode_gallerybox(content, src, idthis, idprev, idnext) {
</figure>`; </figure>`;
} }
async function fetch(url) {
let package = JSON.parse(await fs.promises.readFile(
path.join(__dirname, "package.json"), "utf8"
));
return (await phin({
url,
headers: {
"user-agent": `WorldEditAdditionsStaticBuilder/${package.version} (Node.js/${process.version}; ${os.platform()} ${os.arch()}) eleventy/${package.devDependencies["@11ty/eleventy"].replace(/\^/, "")}`
},
followRedirects: true,
parse: "string"
})).body;
}
module.exports = function(eleventyConfig) { module.exports = function(eleventyConfig) {
eleventyConfig.addAsyncShortcode("fetch", fetch);
// eleventyConfig.addPassthroughCopy("images"); // eleventyConfig.addPassthroughCopy("images");
// eleventyConfig.addPassthroughCopy("css"); // eleventyConfig.addPassthroughCopy("css");
eleventyConfig.addShortcode("image", shortcode_image); eleventyConfig.addShortcode("image", shortcode_image);

@ -5,3 +5,9 @@ permalink: theme.css
{% include "css/theme.css" %} {% include "css/theme.css" %}
{% include "css/gallerybox.css" %} {% include "css/gallerybox.css" %}
{% include "css/smallscreens.css" %} {% include "css/smallscreens.css" %}
{% include "css/prism-custom.css" %}
{# {% fetch "https://unpkg.com/prismjs/themes/prism-okaidia.css" %} #}
{# {% fetch "https://raw.githubusercontent.com/PrismJS/prism-themes/master/themes/prism-shades-of-purple.css" %} #}
{# {% fetch "https://raw.githubusercontent.com/PrismJS/prism-themes/master/themes/prism-material-light.css" %} #}

159
.docs/css/prism-custom.css Normal file

@ -0,0 +1,159 @@
/* Ref https://github.com/jGleitz/markdown-it-prism/issues/1; we don't get have
line numbers because we don't have 1 span per line :-/*/
pre[class*='language-'] {
counter-reset: line-numbers;
border: 0;
}
span[class*='language-'] {
counter-increment: line-numbers;
}
span[class*='language-']::before {
content: counter(line-numbers);
}
/* PrismJS 1.16.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript&plugins=line-highlight+line-numbers+autolinker+data-uri-highlight+toolbar+previewers+autoloader+command-line+normalize-whitespace+show-language */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
/* color: #DD4A68; */
color: hsl(347, 87%, 44%);
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

@ -20,6 +20,7 @@
--bg-transcluscent-slight: rgba(255, 255, 255, 0.1); --bg-transcluscent-slight: rgba(255, 255, 255, 0.1);
--bg-transcluscent: rgba(255, 255, 255, 0.85); --bg-transcluscent: rgba(255, 255, 255, 0.85);
--bg-transcluscent-alt: hsla(226, 59%, 38%, 0.8); --bg-transcluscent-alt: hsla(226, 59%, 38%, 0.8);
--bg-transcluscent-alt-vdark: hsla(226, 59%, 8%, 0.8);
--bg-transcluscent-alt-slight: hsla(196, 91%, 62%, 0.23); --bg-transcluscent-alt-slight: hsla(196, 91%, 62%, 0.23);
/* --text-main: #3F57B4; */ /* --text-main: #3F57B4; */
@ -173,6 +174,9 @@ code {
border-radius: 0.25em; border-radius: 0.25em;
padding: 0.15em; padding: 0.15em;
} }
/* pre.language-weacmd {
background: var(--bg-transcluscent-alt-vdark);
} */
label { label {
font-weight: bold; font-weight: bold;

@ -3,6 +3,19 @@ const markdown = require("markdown-it")({
xhtmlOut: true xhtmlOut: true
}); });
const markdown_prism = require("markdown-it-prism");
markdown.use(markdown_prism, {
init: (Prism) => {
Prism.languages.weacmd = {
"string": /\<[^>]+?\>/,
"function": /^(?:\/\/\S+)\b/m,
"number": /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?%?/i,
"operator": /[<>:=\[\]|{}]/,
"keyword": /\b(?:[-+]?[zyx])\b/
}
}
});
module.exports = function parse_sections(source) { module.exports = function parse_sections(source) {
const lines = source.split(/\r?\n/gi); const lines = source.split(/\r?\n/gi);
const result = []; const result = [];

@ -12,7 +12,9 @@
"@11ty/eleventy": "^0.12.1", "@11ty/eleventy": "^0.12.1",
"@11ty/eleventy-img": "^0.9.0", "@11ty/eleventy-img": "^0.9.0",
"html-entities": "^2.3.2", "html-entities": "^2.3.2",
"htmlentities": "^1.0.0" "htmlentities": "^1.0.0",
"markdown-it-prism": "^2.1.8",
"phin": "^3.6.0"
} }
}, },
"node_modules/@11ty/dependency-tree": { "node_modules/@11ty/dependency-tree": {
@ -697,6 +699,12 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/centra": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/centra/-/centra-2.4.2.tgz",
"integrity": "sha512-f1RaP0V1HqVNEXfLfjNBthB2yy3KnSGnPCnOPCFLUk9e/Z4rNJ8nBaJNnghflnp88mi1IT8mfmW+HlMS1/H+bg==",
"dev": true
},
"node_modules/chalk": { "node_modules/chalk": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
@ -2479,6 +2487,18 @@
"markdown-it": "bin/markdown-it.js" "markdown-it": "bin/markdown-it.js"
} }
}, },
"node_modules/markdown-it-prism": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/markdown-it-prism/-/markdown-it-prism-2.1.8.tgz",
"integrity": "sha512-PBiqlX3zGPQnOk7q7TkeveQfXlqzhjfHg2zSwntDNauYY7KFhg2FzO6O+1boillQptEBcIaEAO9gwKq/tXGHUQ==",
"dev": true,
"dependencies": {
"prismjs": "1.24.1"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/maximatch": { "node_modules/maximatch": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz",
@ -3022,6 +3042,18 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/phin": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/phin/-/phin-3.6.0.tgz",
"integrity": "sha512-nYY4Qh/yGCoxcwOAS2UfCM8+nVJcbI4f9NC4M4zPAsuswnIIS2aB14uYAbvdxP/4DqzXfrpadT2WQOx9mLDyTA==",
"dev": true,
"dependencies": {
"centra": "^2.4.2"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@ -3144,6 +3176,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/prismjs": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.1.tgz",
"integrity": "sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow==",
"dev": true
},
"node_modules/process-nextick-args": { "node_modules/process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -5178,6 +5216,12 @@
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true "dev": true
}, },
"centra": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/centra/-/centra-2.4.2.tgz",
"integrity": "sha512-f1RaP0V1HqVNEXfLfjNBthB2yy3KnSGnPCnOPCFLUk9e/Z4rNJ8nBaJNnghflnp88mi1IT8mfmW+HlMS1/H+bg==",
"dev": true
},
"chalk": { "chalk": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
@ -6604,6 +6648,15 @@
"uc.micro": "^1.0.5" "uc.micro": "^1.0.5"
} }
}, },
"markdown-it-prism": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/markdown-it-prism/-/markdown-it-prism-2.1.8.tgz",
"integrity": "sha512-PBiqlX3zGPQnOk7q7TkeveQfXlqzhjfHg2zSwntDNauYY7KFhg2FzO6O+1boillQptEBcIaEAO9gwKq/tXGHUQ==",
"dev": true,
"requires": {
"prismjs": "1.24.1"
}
},
"maximatch": { "maximatch": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz", "resolved": "https://registry.npmjs.org/maximatch/-/maximatch-0.1.0.tgz",
@ -7011,6 +7064,15 @@
"integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=",
"dev": true "dev": true
}, },
"phin": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/phin/-/phin-3.6.0.tgz",
"integrity": "sha512-nYY4Qh/yGCoxcwOAS2UfCM8+nVJcbI4f9NC4M4zPAsuswnIIS2aB14uYAbvdxP/4DqzXfrpadT2WQOx9mLDyTA==",
"dev": true,
"requires": {
"centra": "^2.4.2"
}
},
"picomatch": { "picomatch": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@ -7099,6 +7161,12 @@
"parse-ms": "^0.1.0" "parse-ms": "^0.1.0"
} }
}, },
"prismjs": {
"version": "1.24.1",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.24.1.tgz",
"integrity": "sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow==",
"dev": true
},
"process-nextick-args": { "process-nextick-args": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",

@ -23,6 +23,8 @@
"@11ty/eleventy": "^0.12.1", "@11ty/eleventy": "^0.12.1",
"@11ty/eleventy-img": "^0.9.0", "@11ty/eleventy-img": "^0.9.0",
"html-entities": "^2.3.2", "html-entities": "^2.3.2",
"htmlentities": "^1.0.0" "htmlentities": "^1.0.0",
"markdown-it-prism": "^2.1.8",
"phin": "^3.6.0"
} }
} }

@ -0,0 +1,28 @@
local Vector3 = require("worldeditadditions.utils.vector3")
describe("Vector3.__concat", function()
it("should work when concatenating a Vector3 + Vector3", function()
local a = Vector3.new(3, 4, 5)
local b = Vector3.new(6, 7, 8)
assert.are.is_true(
type(tostring(a .. b)) == "string"
)
end)
it("should work when concatenating a string + Vector3", function()
local a = "yay"
local b = Vector3.new(6, 7, 8)
assert.are.is_true(
type(tostring(a .. b)) == "string"
)
end)
it("should work when concatenating a Vector3 + string", function()
local a = Vector3.new(3, 4, 5)
local b = "yay"
assert.are.is_true(
type(tostring(a .. b)) == "string"
)
end)
end)

@ -7,18 +7,18 @@ describe("Vector3.add", function()
Vector3.new(3, 4, 5) Vector3.new(3, 4, 5)
) )
end) end)
it("should throw an error on invalid x", function() it("should not throw an error on invalid x", function()
assert.has.errors(function() assert.has_no.errors(function()
Vector3.new("cheese", 4, 5) Vector3.new("cheese", 4, 5)
end) end)
end) end)
it("should throw an error on invalid y", function() it("should not throw an error on invalid y", function()
assert.has.errors(function() assert.has_no.errors(function()
Vector3.new(4, "cheese", 5) Vector3.new(4, "cheese", 5)
end) end)
end) end)
it("should throw an error on invalid z", function() it("should not throw an error on invalid z", function()
assert.has.errors(function() assert.has_no.errors(function()
Vector3.new(66, 2, "cheese") Vector3.new(66, 2, "cheese")
end) end)
end) end)

@ -15,7 +15,7 @@ Other useful links:
## `//floodfill [<replace_node> [<radius>]]` ## `//floodfill [<replace_node> [<radius>]]`
Floods all connected nodes of the same type starting at _pos1_ with <replace_node> (which defaults to `water_source`), in a sphere with a radius of <radius> (which defaults to 50). Floods all connected nodes of the same type starting at _pos1_ with <replace_node> (which defaults to `water_source`), in a sphere with a radius of <radius> (which defaults to 50).
``` ```weacmd
//floodfill //floodfill
//floodfill water_source 50 //floodfill water_source 50
//floodfill glass 25 //floodfill glass 25
@ -30,7 +30,7 @@ Note that all-air columns are skipped - so if you experience issues with it not
Note also that columns without any air nodes in them at all are also skipped, so try `//expand y 1` to add an extra layer to your defined region. Note also that columns without any air nodes in them at all are also skipped, so try `//expand y 1` to add an extra layer to your defined region.
``` ```weacmd
//overlay grass //overlay grass
//overlay glass //overlay glass
//overlay grass_with_dirt //overlay grass_with_dirt
@ -45,7 +45,7 @@ Finds the first non-air node in each column and works downwards, replacing non-a
The list of nodes has a form similar to that of a chance list you might find in `//replacemix`, `//overlay`, or `//mix` - see the examples below. If the numberr of layers isn't specified, `1` is assumed (i.e. a single layer). The list of nodes has a form similar to that of a chance list you might find in `//replacemix`, `//overlay`, or `//mix` - see the examples below. If the numberr of layers isn't specified, `1` is assumed (i.e. a single layer).
``` ```weacmd
//layers dirt_with_grass dirt 3 //layers dirt_with_grass dirt 3
//layers sand 5 sandstone 4 desert_stone 2 //layers sand 5 sandstone 4 desert_stone 2
//layers brick stone 3 //layers brick stone 3
@ -74,13 +74,14 @@ If you like, you can also reference the full name of a sapling node instead. The
For example, the first 2 examples below are functionally equivalent. For example, the first 2 examples below are functionally equivalent.
``` ```weacmd
//forest aspen //forest aspen
//forest default:aspen_sapling //forest default:aspen_sapling
//forest 2 oak 3 aspen pine //forest 2 oak 3 aspen pine
//forest 0.5 acacia //forest 0.5 acacia
``` ```
## `//saplingaliases [aliases|all_saplings]` ## `//saplingaliases [aliases|all_saplings]`
Lists all the currently registered sapling aliases in alphabetical order. These aliases can be used in the `//forest` subcommand. Lists all the currently registered sapling aliases in alphabetical order. These aliases can be used in the `//forest` subcommand.
@ -91,7 +92,7 @@ Mode | Description
`aliases` | The default. Lists all the currently registered sapling aliases in alphabetical order. `aliases` | The default. Lists all the currently registered sapling aliases in alphabetical order.
`all_saplings` | Spins through all the nodes currently registered in Minetest, and lists all the nodes that have the `sapling` group. `all_saplings` | Spins through all the nodes currently registered in Minetest, and lists all the nodes that have the `sapling` group.
``` ```weacmd
//saplingaliases //saplingaliases
//saplingaliases all_saplings //saplingaliases all_saplings
//saplingaliases aliases //saplingaliases aliases
@ -103,16 +104,17 @@ Fills in all airlike nodes beneath non airlike nodes, which gives the effect of
Note that the *entire* cave you want filling must be selected, as `//fillcaves` only operates within the defined region (ref #50). Note that the *entire* cave you want filling must be selected, as `//fillcaves` only operates within the defined region (ref #50).
``` ```weacmd
//fillcaves //fillcaves
//fillcaves dirt //fillcaves dirt
//fillcaves brick //fillcaves brick
``` ```
## `//ellipsoid <rx> <ry> <rz> <node_name> [h[ollow]]` ## `//ellipsoid <rx> <ry> <rz> <node_name> [h[ollow]]`
Creates a solid ellipsoid at position 1 with the radius `(rx, ry, rz)`. Creates a solid ellipsoid at position 1 with the radius `(rx, ry, rz)`.
``` ```weacmd
//ellipsoid 10 5 15 ice //ellipsoid 10 5 15 ice
//ellipsoid 3 5 10 dirt //ellipsoid 3 5 10 dirt
//ellipsoid 20 10 40 air //ellipsoid 20 10 40 air
@ -123,7 +125,7 @@ Creates a solid ellipsoid at position 1 with the radius `(rx, ry, rz)`.
## `//hollowellipsoid <rx> <ry> <rz> <node_name>` ## `//hollowellipsoid <rx> <ry> <rz> <node_name>`
Creates a hollow ellipsoid at position 1 with the radius `(rx, ry, rz)`. Works the same way as `//ellipsoid` does. Creates a hollow ellipsoid at position 1 with the radius `(rx, ry, rz)`. Works the same way as `//ellipsoid` does.
``` ```weacmd
//hollowellipsoid 10 5 15 glass //hollowellipsoid 10 5 15 glass
//hollowellipsoid 21 11 41 stone //hollowellipsoid 21 11 41 stone
``` ```
@ -133,7 +135,7 @@ Creates a solid torus at position 1 with the specified major and minor radii. Th
The optional axes sets the axes upon which the torus will lay flat. Possible values: `xy` (the default), `xz`, `yz`. A single axis may also be specified (i.e. `x`, `y`, or `z`) - this will be interpreted as the axis that runs through the hole in the middle of the torus. The optional axes sets the axes upon which the torus will lay flat. Possible values: `xy` (the default), `xz`, `yz`. A single axis may also be specified (i.e. `x`, `y`, or `z`) - this will be interpreted as the axis that runs through the hole in the middle of the torus.
``` ```weacmd
//torus 15 5 stone //torus 15 5 stone
//torus 5 3 meselamp //torus 5 3 meselamp
//torus 10 6 sandstone xz //torus 10 6 sandstone xz
@ -144,7 +146,7 @@ The optional axes sets the axes upon which the torus will lay flat. Possible val
## `//hollowtorus <major_radius> <minor_radius> <node_name> [<axes=xy>]` ## `//hollowtorus <major_radius> <minor_radius> <node_name> [<axes=xy>]`
Creates a hollow torus at position 1 with the radius major and minor radii. Works the same way as `//torus` does. Creates a hollow torus at position 1 with the radius major and minor radii. Works the same way as `//torus` does.
``` ```weacmd
//hollowtorus 10 5 glass //hollowtorus 10 5 glass
//hollowtorus 21 11 stone //hollowtorus 21 11 stone
//hollowtorus 18 6 dirt xz //hollowtorus 18 6 dirt xz
@ -157,7 +159,7 @@ The radius can be thought of as the thickness of the line, and is defined as the
Floating-point values are fully supported for the radius. Floating-point values are fully supported for the radius.
``` ```weacmd
//line //line
//line stone //line stone
//line sandstone 3 //line sandstone 3
@ -169,7 +171,7 @@ Replaces nodes inside the defined region with air, but leaving a given number of
Note that all air-like nodes are also left alone. Note that all air-like nodes are also left alone.
``` ```weacmd
//hollow //hollow
//hollow 2 //hollow 2
``` ```
@ -189,7 +191,7 @@ Note also that since WorldEditAdditions v1.10, the seed doesn't have to be a num
The last example below shows how to set the path length and width: The last example below shows how to set the path length and width:
``` ```weacmd
//maze ice //maze ice
//maze stone 2 1 1234 //maze stone 2 1 1234
//maze dirt 4 2 56789 //maze dirt 4 2 56789
@ -203,7 +205,7 @@ The optional `path_depth` parameter defaults to `1` and allows customisation of
To get a better look at the generated maze, try inverting it like so: To get a better look at the generated maze, try inverting it like so:
``` ```weacmd
//maze3d stone //maze3d stone
//replace air dirt //replace air dirt
//replace stone air //replace stone air
@ -211,7 +213,7 @@ To get a better look at the generated maze, try inverting it like so:
Additional examples: Additional examples:
``` ```weacmd
//maze3d glass //maze3d glass
//maze3d bush_leaves 2 1 1 12345 //maze3d bush_leaves 2 1 1 12345
//maze3d dirt 4 2 2 //maze3d dirt 4 2 2
@ -233,7 +235,7 @@ For example, a chance number of 2 would mean a 50% chance that any given eligibl
Since WorldEditAdditions v1.12, a percentage chance is also supported. This is denoted by suffixing a number with a percent sign (e.g. `//bonemeal 1 25%`). Since WorldEditAdditions v1.12, a percentage chance is also supported. This is denoted by suffixing a number with a percent sign (e.g. `//bonemeal 1 25%`).
``` ```weacmd
//bonemeal //bonemeal
//bonemeal 3 25 //bonemeal 3 25
//bonemeal 4 //bonemeal 4
@ -245,7 +247,7 @@ Since WorldEditAdditions v1.12, a percentage chance is also supported. This is d
## `//walls <replace_node>` ## `//walls <replace_node>`
Creates vertical walls of `<replace_node>` around the inside edges of the defined region. Creates vertical walls of `<replace_node>` around the inside edges of the defined region.
``` ```weacmd
//walls dirt //walls dirt
//walls stone //walls stone
//walls goldblock //walls goldblock
@ -274,25 +276,25 @@ With this in mind, there are 3 forms that you can tell `//scale` how you want to
### Single Axis ### Single Axis
If you just need to scale a single axis, you can tell `//scale` that like so: If you just need to scale a single axis, you can tell `//scale` that like so:
``` ```weacmd
//scale <axis> <scale_factor> //scale <axis> <scale_factor>
``` ```
To give a concrete example: To give a concrete example:
``` ```weacmd
//scale y 2 //scale y 2
``` ```
The above will scale the defined region in the positive y direction by 2 times, doubling the height. If you want to scale in the opposite direction, do this: The above will scale the defined region in the positive y direction by 2 times, doubling the height. If you want to scale in the opposite direction, do this:
``` ```weacmd
//scale -y 2 //scale -y 2
``` ```
This will scale in the _negative_ y direction by 2 times (again, doubling the height). Some more examples: This will scale in the _negative_ y direction by 2 times (again, doubling the height). Some more examples:
``` ```weacmd
//scale z 50% //scale z 50%
//scale -x 1/3 //scale -x 1/3
``` ```
@ -300,7 +302,7 @@ This will scale in the _negative_ y direction by 2 times (again, doubling the he
### All axes ### All axes
To scale on all axes at once, `//scale` takes the shortcut syntax of specifying a single scale factor: To scale on all axes at once, `//scale` takes the shortcut syntax of specifying a single scale factor:
``` ```weacmd
//scale 2 //scale 2
//scale 200% //scale 200%
``` ```
@ -310,13 +312,13 @@ Both of the above will scale the defined region up by 2 times in all directions.
### Multiple scale factors ### Multiple scale factors
If you want to specify different scale factors for difference axes, then `//scale` also supports a third syntax. Here's an example: If you want to specify different scale factors for difference axes, then `//scale` also supports a third syntax. Here's an example:
``` ```weacmd
//scale 2 3 4 //scale 2 3 4
``` ```
This will scale the defined region by 2x in the positive x, 3x in the positive y, and 4x in the positive z. As these are all scale factors, we can also use the syntax described above to scale up and down in the same operation: This will scale the defined region by 2x in the positive x, 3x in the positive y, and 4x in the positive z. As these are all scale factors, we can also use the syntax described above to scale up and down in the same operation:
``` ```weacmd
//scale 50% 2 1/4 //scale 50% 2 1/4
``` ```
@ -324,7 +326,7 @@ This will first scale in the positive y by 2x. Once that operation is completed,
If you want to change the anchor point of the scaling operation too, `//scale` supports a final syntax like so: If you want to change the anchor point of the scaling operation too, `//scale` supports a final syntax like so:
``` ```weacmd
//scale 50% 2 1/4 1 -1 1 //scale 50% 2 1/4 1 -1 1
``` ```
@ -338,7 +340,7 @@ Replaces a given node with a random mix of other nodes. Functions like `//mix`.
This command is best explained with examples: This command is best explained with examples:
``` ```weacmd
//replacemix dirt stone //replacemix dirt stone
``` ```
@ -346,19 +348,19 @@ The above functions just like `//replace` - nothing special going on here. It re
Let's make it more interesting: Let's make it more interesting:
``` ```weacmd
//replacemix dirt 5 stone //replacemix dirt 5 stone
``` ```
The above replaces 1 in every 5 `dirt` nodes with `stone`. Let's get even fancier: The above replaces 1 in every 5 `dirt` nodes with `stone`. Let's get even fancier:
``` ```weacmd
//replacemix stone stone_with_diamond stone_with_gold //replacemix stone stone_with_diamond stone_with_gold
``` ```
The above replaces `stone` nodes with a random mix of `stone_with_diamond` and `stone_with_gold` nodes. But wait - there's more! The above replaces `stone` nodes with a random mix of `stone_with_diamond` and `stone_with_gold` nodes. But wait - there's more!
``` ```weacmd
//replacemix stone stone_with_diamond stone_with_gold 4 //replacemix stone stone_with_diamond stone_with_gold 4
``` ```
@ -366,7 +368,7 @@ The above replaces `stone` nodes with a random mix of `stone_with_diamond` and `
If we wanted to put all of the above features together into a single command, then we might do this: If we wanted to put all of the above features together into a single command, then we might do this:
``` ```weacmd
//replacemix dirt 3 sandstone 10 dry_dirt cobble 2 //replacemix dirt 3 sandstone 10 dry_dirt cobble 2
``` ```
@ -374,7 +376,7 @@ The above replaces 1 in 3 `dirt` nodes with a mix of `sandstone`, `dry_dirt`, an
Since WorldEditAdditions v1.12, you can also use percentages: Since WorldEditAdditions v1.12, you can also use percentages:
``` ```weacmd
//replacemix dirt 33% sandstone 75% dry_dirt 10% cobble 15% //replacemix dirt 33% sandstone 75% dry_dirt 10% cobble 15%
``` ```
@ -382,7 +384,7 @@ Note though that the percentages are internally converted to a 1-in-N chance and
Here are all the above examples together: Here are all the above examples together:
``` ```weacmd
//replacemix dirt stone //replacemix dirt stone
//replacemix dirt 5 stone //replacemix dirt 5 stone
//replacemix stone stone_with_diamond stone_with_gold //replacemix stone stone_with_diamond stone_with_gold
@ -409,7 +411,7 @@ The width and height (if specified) refer to the dimensions of the kernel and mu
The sigma value is only applicable to the `gaussian` kernel, and can be thought of as the 'smoothness' to apply. Greater values result in more smoothing. Default: 2. See the [Gaussian blur](https://en.wikipedia.org/wiki/Gaussian_blur) page on Wikipedia for some pictures showing the effect of the sigma value. The sigma value is only applicable to the `gaussian` kernel, and can be thought of as the 'smoothness' to apply. Greater values result in more smoothing. Default: 2. See the [Gaussian blur](https://en.wikipedia.org/wiki/Gaussian_blur) page on Wikipedia for some pictures showing the effect of the sigma value.
``` ```weacmd
//convolve //convolve
//convolve box 7 //convolve box 7
//convolve pascal 11,3 //convolve pascal 11,3
@ -432,7 +434,7 @@ Algorithm | Mode | Description
Usage examples: Usage examples:
``` ```weacmd
//erode //erode
//erode snowballs //erode snowballs
//erode snowballs count 25000 //erode snowballs count 25000
@ -459,7 +461,7 @@ noconv | any | n/a | When set to any value, disables to automatic 3x3 gau
Usage examples: Usage examples:
``` ```weacmd
//erode //erode
//erode snowballs //erode snowballs
//erode snowballs count 50000 //erode snowballs count 50000
@ -480,21 +482,87 @@ dolower | `boolean` | true | Whether to lower columns in height. If false,
Usage examples: Usage examples:
``` ```weacmd
//erode river //erode river
//erode river steps 10 //erode river steps 10
``` ```
## `//noise2d [<key_1> [<value_1>]] [<key_2> [<value_2>]] ...]`
Applies 2D noise to the terrain in the defined region. Like `//erode`, this command accepts a number of different key-value parameters and provides a number of different underlying algorithms.
Parameter | Type | Default Value | Description
------------|-----------|---------------|-----------------------
algorithm | `string` | perlin | The 2D noise algorithm to apply - see below.
apply | `string|integer` | 5 | How to apply the noise to the terrain - see below.
scalex | `float` | 1 | The scale of the noise on the x axis.
scaley | `float` | 1 | The scale of the noise on the y axis.
scalez | `float` | 1 | The scale of the noise on the z axis.
offsetx | `float` | 1 | The offset to add to the x axis before calling the noise function.
offsety | `float` | 0 | The offset to add to the y axis before calling the noise function.
offsetz | `float` | 0 | The offset to add to the z axis before calling the noise function.
exponent | `float` | 0 | Raise the generated noise value (with a range of 0 to 1) to this power. Generally results in sharper peaks.
multiply | `float` | 1 | Multiply the generated noise value by this number
add | `float` | 0 | Add this number to the generated noise value.
Different values of the `apply` parameter result in the generated noise values being applied in different ways:
- An integer indicates that the noise should be rescaled to a given amplitude (equal parts of the range above and below 0) before being added to the terrain heightmap.`
- The exact string `add`: Noise values are added to each heightmap pixel.
- The exact string `multiply`: Each heightmap pixel is multiplied by the corresponding noise value.
The following algorithms are currently available:
Algorithm | Description
------------|--------------------------
`perlin` | Perlin noise. Functional, but currently contains artefacts I'm having difficulty tracking down.
`sin` | A sine wave created with `math.sin()`.
`white` | Random white noise.
`red` | Red noise - has a lower frequency than white noise. Ref [Noise Functions and Map Generation by Red Blob Games](https://www.redblobgames.com/articles/noise/introduction.html).
`infrared` | Even smoother than red noise. Tends to also be quite flat unless you use a slightly higher `apply` value (e.g. `20`).
When specifying algorithm names, the `algorithm` parameter name is optional. For example, the following are both equivalent:
```weacmd
//noise2d offsetx 4 perlin scale 0.2
//noise2d offsetx 4 algorithm perlin scale 0.2
```
Example invocations:
```weacmd
//noise2d sin scale 0.5
//noise2d offsetx 20 perlin
//noise2d sin exponent 4
```
## `//count` ## `//count`
Counts all the nodes in the defined region and returns the result along with calculated percentages (note that if the chat window used a monospace font, the returned result would be a perfect table. If someone has a ~~hack~~ solution to make the columns line up neatly, please [open an issue](https://github.com/sbrl/Minetest-WorldEditAdditions/issues/new) :D) Counts all the nodes in the defined region and returns the result along with calculated percentages (note that if the chat window used a monospace font, the returned result would be a perfect table. If someone has a ~~hack~~ solution to make the columns line up neatly, please [open an issue](https://github.com/sbrl/Minetest-WorldEditAdditions/issues/new) :D)
**Note:** The output of `//count` can be rather long sometimes, and Minetest by default only shows the last few lines of chat. Press <kbd>F10</kbd> to show the full chat window that you can then scroll through to inspect the full output. **Note:** The output of `//count` can be rather long sometimes, and Minetest by default only shows the last few lines of chat. Press <kbd>F10</kbd> to show the full chat window that you can then scroll through to inspect the full output.
``` ```weacmd
//count //count
``` ```
## `//basename <name>`
Returns the absolute canonical name of a node, given an alias or partial node name. For example:
```weacmd
//basename dirt
```
...will return `default:dirt`. Uses `worldedit.normalize_nodename(string)` under the hood.
```weacmd
//basename stonebrick
//basename glass
```
## `//subdivide <size_x> <size_y> <size_z> <cmd_name> <args>` ## `//subdivide <size_x> <size_y> <size_z> <cmd_name> <args>`
Splits the current WorldEdit region into `(<size_x>, <size_y>, <size_z>)` sized chunks, and run `//<cmd_name> <args>` over each chunk. Splits the current WorldEdit region into `(<size_x>, <size_y>, <size_z>)` sized chunks, and run `//<cmd_name> <args>` over each chunk.
@ -508,7 +576,7 @@ While other server commands can be executed while a `//subdivide` is running, `/
**Warning:** Once started, this command cannot be stopped without restarting your server! This is the case with all WorldEdit commands, but it's worth a special mention here. **Warning:** Once started, this command cannot be stopped without restarting your server! This is the case with all WorldEdit commands, but it's worth a special mention here.
``` ```weacmd
//subdivide 10 10 10 set dirt //subdivide 10 10 10 set dirt
//subdivice 25 25 25 fixlight //subdivice 25 25 25 fixlight
``` ```
@ -517,13 +585,13 @@ While other server commands can be executed while a `//subdivide` is running, `/
## `//multi <command_a> <command_b> <command_c> .....` ## `//multi <command_a> <command_b> <command_c> .....`
Executes multi chat commands in sequence. Intended for _WorldEdit_ commands, but does work with others too. Don't forget a space between commands! Executes multi chat commands in sequence. Intended for _WorldEdit_ commands, but does work with others too. Don't forget a space between commands!
``` ```weacmd
//multi //set dirt //shift x 10 //set glass //multi //set dirt //shift x 10 //set glass
``` ```
Since WorldEditAdditions v1.12, curly brace syntax has also been introduced to allow nesting of commands: Since WorldEditAdditions v1.12, curly brace syntax has also been introduced to allow nesting of commands:
``` ```weacmd
//multi //fixlight {//many 5 //bonemeal 3 100} //multi //fixlight {//many 5 //bonemeal 3 100}
``` ```
@ -531,7 +599,7 @@ This syntax can also be nested arbitrarily in arbitrarily complex combinations,
In addition, this also allows for including a double forward slash in the argument list for a command, should you need to do so (e.g. `//multi //example {//bar //baz} //example` will be executed as 3 commands: `/example`, then `/bar` with an argument of `//baz`, then finally `/example`). In addition, this also allows for including a double forward slash in the argument list for a command, should you need to do so (e.g. `//multi //example {//bar //baz} //example` will be executed as 3 commands: `/example`, then `/bar` with an argument of `//baz`, then finally `/example`).
``` ```weacmd
//multi //1 //2 //shift z -10 //sphere 5 sand //shift z 20 //ellipsoid 5 3 5 ice //multi //1 //2 //shift z -10 //sphere 5 sand //shift z 20 //ellipsoid 5 3 5 ice
//multi //1 //hollowtorus 30 5 stone //hollowtorus 20 3 dirt //torus 10 2 dirt_with_grass //multi //1 //hollowtorus 30 5 stone //hollowtorus 20 3 dirt //torus 10 2 dirt_with_grass
//multi /time 7:00 //1 //outset h 20 //outset v 5 //overlay dirt_with_grass //1 //2 //sphere 8 air //shift down 1 //floodfill //reset //multi /time 7:00 //1 //outset h 20 //outset v 5 //overlay dirt_with_grass //1 //2 //sphere 8 air //shift down 1 //floodfill //reset
@ -543,7 +611,7 @@ Executes a single chat command many times in a row. Uses `minetest.after()` to y
Note that this isn't necessarily limited to executing WorldEdit / WorldEditAdditions commands. Combine with `//multi` (see above) execute multiple commands at once for even more power and flexibility! Note that this isn't necessarily limited to executing WorldEdit / WorldEditAdditions commands. Combine with `//multi` (see above) execute multiple commands at once for even more power and flexibility!
``` ```weacmd
//many 10 //bonemeal 3 100 //many 10 //bonemeal 3 100
//many 100 //multi //1 //2 //outset 20 //set dirt //many 100 //multi //1 //2 //outset 20 //set dirt
``` ```
@ -552,7 +620,7 @@ Note that this isn't necessarily limited to executing WorldEdit / WorldEditAddit
## `//ellipsoidapply <command_name> <args>` ## `//ellipsoidapply <command_name> <args>`
Executes the given command, and then clips the result to the largest ellipsoid that will fit inside the defined region. The specified command must obviously take 2 positions - so for example `//set`, `//replacemix`, and `//maze3d` will work, but `//sphere`, `//torus`, and `//floodfill` won't. Executes the given command, and then clips the result to the largest ellipsoid that will fit inside the defined region. The specified command must obviously take 2 positions - so for example `//set`, `//replacemix`, and `//maze3d` will work, but `//sphere`, `//torus`, and `//floodfill` won't.
``` ```weacmd
//ellipsoidapply set dirt //ellipsoidapply set dirt
//ellipsoidapply maze3d dirt 4 2 2 //ellipsoidapply maze3d dirt 4 2 2
//ellipsoidapply erode //ellipsoidapply erode
@ -564,7 +632,7 @@ Executes the given command, and then clips the result to the largest ellipsoid t
## `//scol [<axis1> ] <length>` ## `//scol [<axis1> ] <length>`
Short for _select column_. Sets the pos2 at a set distance along 1 axis from pos1. If the axis isn't specified, defaults the direction you are facing. Implementation thanks to @VorTechnix. Short for _select column_. Sets the pos2 at a set distance along 1 axis from pos1. If the axis isn't specified, defaults the direction you are facing. Implementation thanks to @VorTechnix.
``` ```weacmd
//scol 10 //scol 10
//scol x 3 //scol x 3
``` ```
@ -573,7 +641,7 @@ Short for _select column_. Sets the pos2 at a set distance along 1 axis from pos
## `//srect [<axis1> [<axis2>]] <length>` ## `//srect [<axis1> [<axis2>]] <length>`
Short for _select rectangle_. Sets the pos2 at a set distance along 2 axes from pos1. If the axes aren't specified, defaults to positive y and the direction you are facing. Implementation thanks to @VorTechnix. Short for _select rectangle_. Sets the pos2 at a set distance along 2 axes from pos1. If the axes aren't specified, defaults to positive y and the direction you are facing. Implementation thanks to @VorTechnix.
``` ```weacmd
//srect x z 10 //srect x z 10
//srect 3 //srect 3
//srect -z y 25 //srect -z y 25
@ -583,7 +651,7 @@ Short for _select rectangle_. Sets the pos2 at a set distance along 2 axes from
## `//scube [<axis1> [<axis2> [<axis3>]]] <length>` ## `//scube [<axis1> [<axis2> [<axis3>]]] <length>`
Short for _select cube_. Sets the pos2 at a set distance along 3 axes from pos1. If the axes aren't specified, defaults to positive y, the direction you are facing and the axis to the left of facing. Implementation thanks to @VorTechnix. Short for _select cube_. Sets the pos2 at a set distance along 3 axes from pos1. If the axes aren't specified, defaults to positive y, the direction you are facing and the axis to the left of facing. Implementation thanks to @VorTechnix.
``` ```weacmd
//scube 5 //scube 5
//scube z a y 12 //scube z a y 12
//scube x z 3 //scube x z 3
@ -594,7 +662,7 @@ Short for _select cube_. Sets the pos2 at a set distance along 3 axes from pos1.
## `//scloud <0-6|stop|reset>` ## `//scloud <0-6|stop|reset>`
Short for _select point cloud_. Sets pos1 and pos2 to include the nodes you punch. Numbers 1-6 designate how many nodes you want to punch before the operation ends. 0 or stop terminate the operation so that any further nodes you punch won't be added to selection. Reset terminates operation if one is running and resets the selection area. Short for _select point cloud_. Sets pos1 and pos2 to include the nodes you punch. Numbers 1-6 designate how many nodes you want to punch before the operation ends. 0 or stop terminate the operation so that any further nodes you punch won't be added to selection. Reset terminates operation if one is running and resets the selection area.
``` ```weacmd
//scloud 6 //scloud 6
//scloud 5 //scloud 5
//scloud stop //scloud stop
@ -604,7 +672,7 @@ Short for _select point cloud_. Sets pos1 and pos2 to include the nodes you punc
## `//scentre` ## `//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 thanks to @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.
``` ```weacmd
//scentre //scentre
``` ```
@ -612,7 +680,7 @@ Short for _select center_. Sets pos1 and pos2 to the centre point(s) of the curr
## `//srel <axis1> <length1> [<axis2> <length2> [<axis3> <length3>]]` ## `//srel <axis1> <length1> [<axis2> <length2> [<axis3> <length3>]]`
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. 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 front 5
//srel y 12 right -2 //srel y 12 right -2
//srel left 3 up 5 -front 7 //srel left 3 up 5 -front 7
@ -625,7 +693,7 @@ Short for _selection make_. Modifies existing selection by moving pos2. Allows y
Usage examples: Usage examples:
``` ```weacmd
//smake odd shrink //smake odd shrink
//smake even avg xz //smake even avg xz
//smake equal grow xy //smake equal grow xy
@ -674,7 +742,7 @@ Short for _selection factor_; alias: `//sfac`. Built specifically for use with `
Usage examples: Usage examples:
``` ```weacmd
//sfac grow 5 //sfac grow 5
//sfac avg 3 xy //sfac avg 3 xy
``` ```
@ -690,7 +758,7 @@ Value | Description
## `//sstack` ## `//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). 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).
``` ```weacmd
//sstack //sstack
``` ```
@ -701,14 +769,14 @@ If the stack is full (currently the limit is set to 100 regions in the stack), t
Note that pos2 does _not_ need to be defined in order to use this. if it isn't defined, then a pos2 of `nil` will be pushed onto the stack instead. Note that pos2 does _not_ need to be defined in order to use this. if it isn't defined, then a pos2 of `nil` will be pushed onto the stack instead.
``` ```weacmd
//spush //spush
``` ```
## `//spop` ## `//spop`
Pops a selection off your per-user selection stack and applies it to the currently defined region. If pos2 from the item popped from the stack is nil, then pos2 is explicitly unset. If the stack is empty, this has no effect. Pops a selection off your per-user selection stack and applies it to the currently defined region. If pos2 from the item popped from the stack is nil, then pos2 is explicitly unset. If the stack is empty, this has no effect.
``` ```weacmd
//spop //spop
``` ```
@ -752,7 +820,7 @@ Confirms the execution of a command if it could potentially affect a large numbe
<!-- Equivalent to _WorldEdit_'s `//y`, but because of security sandboxing issues it's not really possible to hook into WorldEdit's existing command. --> <!-- Equivalent to _WorldEdit_'s `//y`, but because of security sandboxing issues it's not really possible to hook into WorldEdit's existing command. -->
``` ```weacmd
//y //y
``` ```
@ -761,7 +829,7 @@ Prevents the execution of a command if it could potentially affect a large numbe
<!-- Equivalent to _WorldEdit_'s `//y`, but because of security sandboxing issues it's not really possible to hook into WorldEdit's existing command. --> <!-- Equivalent to _WorldEdit_'s `//y`, but because of security sandboxing issues it's not really possible to hook into WorldEdit's existing command. -->
``` ```weacmd
//n //n
``` ```
@ -774,20 +842,20 @@ It functions very similarly to the regular WorldEdit wand, except that it has a
## `//farwand skip_liquid (true|false) | maxdist <number>` ## `//farwand skip_liquid (true|false) | maxdist <number>`
This command helps control the behaviour of the [WorldEditAdditions far wand](#far-wand). Calling it without any arguments shows the current status: This command helps control the behaviour of the [WorldEditAdditions far wand](#far-wand). Calling it without any arguments shows the current status:
``` ```weacmd
//farwand //farwand
``` ```
You can decide whether you can select liquids or not like so: You can decide whether you can select liquids or not like so:
``` ```weacmd
//farwand skip_liquid true //farwand skip_liquid true
//farwand skip_liquid false //farwand skip_liquid false
``` ```
You can change the maximum range with the `maxdist` subcommand: You can change the maximum range with the `maxdist` subcommand:
``` ```weacmd
//farwand maxdist 1000 //farwand maxdist 1000
//farwand maxdist 200 //farwand maxdist 200
//farwand maxdist 9999 //farwand maxdist 9999

@ -12,6 +12,9 @@ worldeditadditions.Vector3 = dofile(worldeditadditions.modpath.."/utils/vector3.
worldeditadditions.Mesh, worldeditadditions.Mesh,
worldeditadditions.Face = dofile(worldeditadditions.modpath.."/utils/mesh.lua") worldeditadditions.Face = dofile(worldeditadditions.modpath.."/utils/mesh.lua")
worldeditadditions.Queue = dofile(worldeditadditions.modpath.."/utils/queue.lua")
worldeditadditions.LRU = dofile(worldeditadditions.modpath.."/utils/lru.lua")
dofile(worldeditadditions.modpath.."/utils/strings/init.lua") dofile(worldeditadditions.modpath.."/utils/strings/init.lua")
dofile(worldeditadditions.modpath.."/utils/format/init.lua") dofile(worldeditadditions.modpath.."/utils/format/init.lua")
@ -44,6 +47,7 @@ dofile(worldeditadditions.modpath.."/lib/scale_down.lua")
dofile(worldeditadditions.modpath.."/lib/scale.lua") dofile(worldeditadditions.modpath.."/lib/scale.lua")
dofile(worldeditadditions.modpath.."/lib/conv/conv.lua") dofile(worldeditadditions.modpath.."/lib/conv/conv.lua")
dofile(worldeditadditions.modpath.."/lib/erode/erode.lua") dofile(worldeditadditions.modpath.."/lib/erode/erode.lua")
dofile(worldeditadditions.modpath.."/lib/noise/init.lua")
dofile(worldeditadditions.modpath.."/lib/count.lua") dofile(worldeditadditions.modpath.."/lib/count.lua")
@ -51,6 +55,7 @@ dofile(worldeditadditions.modpath.."/lib/bonemeal.lua")
dofile(worldeditadditions.modpath.."/lib/forest.lua") dofile(worldeditadditions.modpath.."/lib/forest.lua")
dofile(worldeditadditions.modpath.."/lib/ellipsoidapply.lua") dofile(worldeditadditions.modpath.."/lib/ellipsoidapply.lua")
dofile(worldeditadditions.modpath.."/lib/airapply.lua")
dofile(worldeditadditions.modpath.."/lib/subdivide.lua") dofile(worldeditadditions.modpath.."/lib/subdivide.lua")
dofile(worldeditadditions.modpath.."/lib/selection/stack.lua") dofile(worldeditadditions.modpath.."/lib/selection/stack.lua")

@ -0,0 +1,57 @@
-- █████ ██ ██████
-- ██ ██ ██ ██ ██
-- ███████ ██ ██████
-- ██ ██ ██ ██ ██
-- ██ ██ ██ ██ ██
--
-- █████ ██████ ██████ ██ ██ ██
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ███████ ██████ ██████ ██ ████
-- ██ ██ ██ ██ ██ ██
-- ██ ██ ██ ██ ███████ ██
--- Similar to cubeapply, except that it takes 2 positions and only keeps an ellipsoid-shaped area defined by the boundaries of the defined region.
-- Takes a backup copy of the defined region, runs the given function, and then
-- restores the bits around the edge that aren't inside the largest ellipsoid that will fit inside the defined region.
-- @param {Position} pos1 The 1st position defining the region boundary
-- @param {Position} pos2 The 2nd positioon defining the region boundary
-- @param {Function} func The function to call that performs the action in question. It is expected that the given function will accept no arguments.
function worldeditadditions.airapply(pos1, pos2, func)
local time_taken_all = worldeditadditions.get_ms_time()
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
-- pos2 will always have the highest co-ordinates now
-- Fetch the nodes in the specified area
local manip_before, area_before = worldedit.manip_helpers.init(pos1, pos2)
local data_before = manip_before:get_data()
local time_taken_fn = worldeditadditions.get_ms_time()
func()
time_taken_fn = worldeditadditions.get_ms_time() - time_taken_fn
local manip_after, area_after = worldedit.manip_helpers.init(pos1, pos2)
local data_after = manip_after:get_data()
for z = pos2.z, pos1.z, -1 do
for y = pos2.y, pos1.y, -1 do
for x = pos2.x, pos1.x, -1 do
local i_before = area_before:index(x, y, z)
local i_after = area_after:index(x, y, z)
local old_is_airlike = worldeditadditions.is_airlike(data_before[i_before])
-- Roll everything that replaces nodes that aren't airlike
if not old_is_airlike then
data_after[i_after] = data_before[i_before]
end
end
end
end
-- Save the modified nodes back to disk & return
-- No need to save - this function doesn't actually change anything
worldedit.manip_helpers.finish(manip_after, data_after)
time_taken_all = worldeditadditions.get_ms_time() - time_taken_all
return true, { all = time_taken_all, fn = time_taken_fn }
end

@ -1,47 +1,7 @@
--- Flood-fill command for complex lakes etc. --- Flood-fill command for complex lakes etc.
-- @module worldeditadditions.floodfill -- @module worldeditadditions.floodfill
------------------------------------------------------------------------------- local wea = worldeditadditions
--- A Queue implementation, taken & adapted from https://www.lua.org/pil/11.4.html
-- @submodule worldeditadditions.utils.queue
local Queue = {}
function Queue.new()
return { first = 0, last = -1 }
end
function Queue.enqueue(queue, value)
local new_last = queue.last + 1
queue.last = new_last
queue[new_last] = value
end
function Queue.contains(queue, value)
for i=queue.first,queue.last do
if queue[i] == value then
return true
end
end
return false
end
function Queue.is_empty(queue)
return queue.first > queue.last
end
function Queue.dequeue(queue)
local first = queue.first
if Queue.is_empty(queue) then
error("Error: The queue is empty!")
end
local value = queue[first]
queue[first] = nil -- Help the garbage collector out
queue.first = first + 1
return value
end
-------------------------------------------------------------------------------
function worldeditadditions.floodfill(start_pos, radius, replace_node) function worldeditadditions.floodfill(start_pos, radius, replace_node)
-- Calculate the area we want to modify -- Calculate the area we want to modify
@ -67,12 +27,12 @@ function worldeditadditions.floodfill(start_pos, radius, replace_node)
end end
local count = 0 local count = 0
local remaining_nodes = Queue.new() local remaining_nodes = wea.Queue.new()
Queue.enqueue(remaining_nodes, start_pos_index) remaining_nodes:enqueue(start_pos_index)
-- Do the floodfill -- Do the floodfill
while Queue.is_empty(remaining_nodes) == false do while remaining_nodes:is_empty() == false do
local cur = Queue.dequeue(remaining_nodes) local cur = remaining_nodes:dequeue()
-- Replace this node -- Replace this node
data[cur] = replace_id data[cur] = replace_id
@ -83,37 +43,37 @@ function worldeditadditions.floodfill(start_pos, radius, replace_node)
local xplus = cur + 1 -- +X local xplus = cur + 1 -- +X
if data[xplus] == search_id and if data[xplus] == search_id and
worldeditadditions.vector.lengthsquared(vector.subtract(area:position(xplus), start_pos)) < radius_sq and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(xplus), start_pos)) < radius_sq and
not Queue.contains(remaining_nodes, xplus) then not remaining_nodes:contains(xplus) then
-- minetest.log("action", "[floodfill] [+X] index " .. xplus .. " is a " .. data[xplus] .. ", searching for a " .. search_id) -- minetest.log("action", "[floodfill] [+X] index " .. xplus .. " is a " .. data[xplus] .. ", searching for a " .. search_id)
Queue.enqueue(remaining_nodes, xplus) remaining_nodes:enqueue(xplus)
end end
local xminus = cur - 1 -- -X local xminus = cur - 1 -- -X
if data[xminus] == search_id and if data[xminus] == search_id and
worldeditadditions.vector.lengthsquared(vector.subtract(area:position(xminus), start_pos)) < radius_sq and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(xminus), start_pos)) < radius_sq and
not Queue.contains(remaining_nodes, xminus) then not remaining_nodes:contains(xminus) then
-- minetest.log("action", "[floodfill] [-X] index " .. xminus .. " is a " .. data[xminus] .. ", searching for a " .. search_id) -- minetest.log("action", "[floodfill] [-X] index " .. xminus .. " is a " .. data[xminus] .. ", searching for a " .. search_id)
Queue.enqueue(remaining_nodes, xminus) remaining_nodes:enqueue(xminus)
end end
local zplus = cur + area.zstride -- +Z local zplus = cur + area.zstride -- +Z
if data[zplus] == search_id and if data[zplus] == search_id and
worldeditadditions.vector.lengthsquared(vector.subtract(area:position(zplus), start_pos)) < radius_sq and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(zplus), start_pos)) < radius_sq and
not Queue.contains(remaining_nodes, zplus) then not remaining_nodes:contains(zplus) then
-- minetest.log("action", "[floodfill] [+Z] index " .. zplus .. " is a " .. data[zplus] .. ", searching for a " .. search_id) -- minetest.log("action", "[floodfill] [+Z] index " .. zplus .. " is a " .. data[zplus] .. ", searching for a " .. search_id)
Queue.enqueue(remaining_nodes, zplus) remaining_nodes:enqueue(zplus)
end end
local zminus = cur - area.zstride -- -Z local zminus = cur - area.zstride -- -Z
if data[zminus] == search_id and if data[zminus] == search_id and
worldeditadditions.vector.lengthsquared(vector.subtract(area:position(zminus), start_pos)) < radius_sq and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(zminus), start_pos)) < radius_sq and
not Queue.contains(remaining_nodes, zminus) then not remaining_nodes:contains(zminus) then
-- minetest.log("action", "[floodfill] [-Z] index " .. zminus .. " is a " .. data[zminus] .. ", searching for a " .. search_id) -- minetest.log("action", "[floodfill] [-Z] index " .. zminus .. " is a " .. data[zminus] .. ", searching for a " .. search_id)
Queue.enqueue(remaining_nodes, zminus) remaining_nodes:enqueue(zminus)
end end
local yminus = cur - area.ystride -- -Y local yminus = cur - area.ystride -- -Y
if data[yminus] == search_id and if data[yminus] == search_id and
worldeditadditions.vector.lengthsquared(vector.subtract(area:position(yminus), start_pos)) < radius_sq and worldeditadditions.vector.lengthsquared(vector.subtract(area:position(yminus), start_pos)) < radius_sq and
not Queue.contains(remaining_nodes, yminus) then not remaining_nodes:contains(yminus) then
-- minetest.log("action", "[floodfill] [-Y] index " .. yminus .. " is a " .. data[yminus] .. ", searching for a " .. search_id) -- minetest.log("action", "[floodfill] [-Y] index " .. yminus .. " is a " .. data[yminus] .. ", searching for a " .. search_id)
Queue.enqueue(remaining_nodes, yminus) remaining_nodes:enqueue(yminus)
end end
count = count + 1 count = count + 1

@ -66,7 +66,7 @@ function worldeditadditions.forest(pos1, pos2, density_multiplier, sapling_weigh
end end
end end
if not did_grow then if not did_grow then
print("[//forest] Failed to grow sapling, detected node id", new_id_at_pos, "name", new_name_at_pos, "was originally", minetest.get_name_from_content_id(node_id)) -- print("[//forest] Failed to grow sapling, detected node id", new_id_at_pos, "name", new_name_at_pos, "was originally", minetest.get_name_from_content_id(node_id))
-- We can't set it to air here because then when we save back we would overwrite all the newly grown trees -- We can't set it to air here because then when we save back we would overwrite all the newly grown trees
stats.failures = stats.failures + 1 stats.failures = stats.failures + 1
end end

@ -14,7 +14,6 @@ function worldeditadditions.line(pos1, pos2, thickness, node_name)
pos2 = vector.new(pos2) pos2 = vector.new(pos2)
local node_id_replace = minetest.get_content_id(node_name) local node_id_replace = minetest.get_content_id(node_name)
print("thickness", thickness, "node_name", node_name, "node_id_replace", node_id_replace)
-- Fetch the nodes in the specified area -- Fetch the nodes in the specified area
local manip, area = worldedit.manip_helpers.init(pos1, pos2) local manip, area = worldedit.manip_helpers.init(pos1, pos2)
@ -37,7 +36,6 @@ function worldeditadditions.line(pos1, pos2, thickness, node_name)
local distance = vector.length(vector.subtract(here, closest_on_line)) local distance = vector.length(vector.subtract(here, closest_on_line))
if distance < thickness then if distance < thickness then
print("[line] vector", closest_on_line.x, closest_on_line.y, closest_on_line.z, "length", distance)
data[area:index(x, y, z)] = node_id_replace data[area:index(x, y, z)] = node_id_replace
counts.replaced = counts.replaced + 1 counts.replaced = counts.replaced + 1
end end

@ -0,0 +1,61 @@
local wea = worldeditadditions
--- Applies the given noise field to the given heightmap.
-- Mutates the given heightmap.
-- @param heightmap number[] A table of ZERO indexed numbers representing the heghtmap - see worldeditadditions.make_heightmap().
-- @param noise number[] An table identical in structure to the heightmap containing the noise values to apply.
-- @param heightmap_size {x:number,z:number} A 2d vector representing the size of the heightmap.
-- @param region_height number The height of the defined region.
-- @param apply_mode string The apply mode to use to apply the noise to the heightmap.
-- @returns bool[,string] A boolean value representing whether the application was successful or not. If false, then an error message as a string is also returned describing the error that occurred.
function worldeditadditions.noise.apply_2d(heightmap, noise, heightmap_size, pos1, pos2, apply_mode)
if type(apply_mode) ~= "string" and type(apply_mode) ~= "number" then
return false, "Error: Expected value of type string or number for apply_mode, but received value of type "..type(apply_mode)
end
local region_height = pos2.y - pos1.y
print("NOISE APPLY_2D\n")
worldeditadditions.format.array_2d(noise, heightmap_size.x)
local height = tonumber(apply_mode)
print("DEBUG apply_mode", apply_mode, "as height", height)
for z = heightmap_size.z - 1, 0, -1 do
for x = heightmap_size.x - 1, 0, -1 do
local i = (z * heightmap_size.x) + x
if apply_mode == "add" then
heightmap[i] = wea.round(heightmap[i] + noise[i])
elseif apply_mode == "multiply" then
heightmap[i] = wea.round(heightmap[i] * noise[i])
elseif height then
-- Rescale from 0 - 1 to -1 - +1
local rescaled = (noise[i] * 2) - 1
-- Rescale to match the height specified
rescaled = rescaled * height
rescaled = math.floor(wea.clamp(
heightmap[i] + rescaled,
0, region_height
))
heightmap[i] = rescaled
else
return false, "Error: Unknown apply mode '"..apply_mode.."'"
end
end
end
-- for z = heightmap_size.z - 1, 0, -1 do
-- x = 0
-- heightmap[(z * heightmap_size.x) + x] = z
-- end
print("HEIGHTMAP\n")
worldeditadditions.format.array_2d(heightmap, heightmap_size.x)
return true
end

@ -0,0 +1,31 @@
local wea = worldeditadditions
local White = dofile(wea.modpath.."/lib/noise/engines/white.lua")
local Infrared = {}
Infrared.__index = Infrared
function Infrared.new(seed)
local result = {
seed = seed or math.random(),
white = White.new(seed),
window = 2
}
setmetatable(result, Infrared)
return result
end
function Infrared:noise( x, y, z )
local values = { }
for nx=x-self.window,x+self.window do
for ny=y-self.window,y+self.window do
for nz=z-self.window,z+self.window do
table.insert(values, self.white:noise(nx, ny, nz))
end
end
end
return wea.average(values)
end
return Infrared

@ -0,0 +1,12 @@
local wea = worldeditadditions
return {
available = { "perlin", "sin", "white", "red", "infrared" },
Perlin = dofile(wea.modpath.."/lib/noise/engines/perlin.lua"),
Sin = dofile(wea.modpath.."/lib/noise/engines/sin.lua"),
White = dofile(wea.modpath.."/lib/noise/engines/white.lua"),
Red = dofile(wea.modpath.."/lib/noise/engines/red.lua"),
Infrared = dofile(wea.modpath.."/lib/noise/engines/infrared.lua")
-- TODO: Follow https://www.redblobgames.com/articles/noise/introduction.html and implement different colours of noise (*especially* red and pink noise)
}

@ -1,38 +1,14 @@
local wea = worldeditadditions local wea = worldeditadditions
-- original code by Ken Perlin: http://mrl.nyu.edu/~perlin/noise/ --- Perlin noise generation engine.
-- Original code by Ken Perlin: http://mrl.nyu.edu/~perlin/noise/
-- Port from this StackOverflow answer: https://stackoverflow.com/a/33425812/1460422 -- Port from this StackOverflow answer: https://stackoverflow.com/a/33425812/1460422
-- @class
local Perlin = {}
Perlin.__index = Perlin
Perlin.p = {}
local function BitAND(a, b) --Bitwise and Perlin.permutation = {
local p, c = 1, 0
while a > 0 and b > 0 do
local ra, rb = a%2, b%2
if ra + rb > 1 then c = c + p end
a, b, p = (a - ra) / 2, (b - rb) / 2, p * 2
end
return c
end
local function fade(t)
return t * t * t * (t * (t * 6 - 15) + 10)
end
local function lerp(t, a, b)
return a + t * (b - a)
end
local function grad(hash, x, y, z)
local h = BitAND(hash, 15)
local u = h < 8 and x or y
local v = h < 4 and y or ((h == 12 or h == 14) and x or z)
return ((h and 1) == 0 and u or - u) + ((h and 2) == 0 and v or - v)
end
wea.noise.perlin = {}
wea.noise.perlin.p = {}
wea.noise.perlin.permutation = {
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140,
36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120,
234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33,
@ -50,51 +26,114 @@ wea.noise.perlin.permutation = {
115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29,
24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
} }
wea.noise.perlin.size = 256 Perlin.size = 256
wea.noise.perlin.gx = {} Perlin.gx = {}
wea.noise.perlin.gy = {} Perlin.gy = {}
wea.noise.perlin.randMax = 256 Perlin.randMax = 256
--- Creates a new perlin instance. --- Creates a new Perlin instance.
-- @return perlin A new perlin instance. -- @return Perlin A new perlin instance.
function wea.noise.perlin.new() function Perlin.new()
local instance = {} local instance = {}
setmetatable(instance, wea.noise.perlin) setmetatable(instance, Perlin)
instance:load() instance:load()
return instance return instance
end end
function wea.noise.perlin:load() --- Initialises this Perlin instance.
-- Called automatically by the constructor - you do NOT need to call this
-- yourself.
function Perlin:load()
for i = 1, self.size do for i = 1, self.size do
self.p[i] = self.permutation[i] self.p[i - 1] = self.permutation[i]
self.p[255 + i] = self.p[i] self.p[i + 255] = self.permutation[i]
end end
end end
function wea.noise.perlin:noise( x, y, z ) --- Returns a noise value for a given point in 3D space.
local X = BitAND(math.floor(x), 255) + 1 -- @param x number The x co-ordinate.
local Y = BitAND(math.floor(y), 255) + 1 -- @param y number The y co-ordinate.
local Z = BitAND(math.floor(z), 255) + 1 -- @param z number The z co-ordinate.
-- @returns number The calculated noise value.
function Perlin:noise( x, y, z )
y = y or 0
z = z or 0
local xi = self:BitAND(math.floor(x), 255)
local yi = self:BitAND(math.floor(y), 255)
local zi = self:BitAND(math.floor(z), 255)
-- print("x", x, "y", y, "z", z, "xi", xi, "yi", yi, "zi", zi)
-- print("p[xi]", self.p[xi])
x = x - math.floor(x) x = x - math.floor(x)
y = y - math.floor(y) y = y - math.floor(y)
z = z - math.floor(z) z = z - math.floor(z)
local u = fade(x) local u = self:fade(x)
local v = fade(y) local v = self:fade(y)
local w = fade(z) local w = self:fade(z)
local A = self.p[X] + Y local A = self.p[xi] + yi
local AA = self.p[A] + Z local AA = self.p[A] + zi
local AB = self.p[A + 1] + Z local AB = self.p[A + 1] + zi
local B = self.p[X + 1] + Y local AAA = self.p[AA]
local BA = self.p[B] + Z local ABA = self.p[AB]
local BB = self.p[B + 1] + Z local AAB = self.p[AA + 1]
local ABB = self.p[AB + 1]
return lerp(w, lerp(v, lerp(u, grad(self.p[AA ], x, y, z ), local B = self.p[xi + 1] + yi
grad(self.p[BA ], x - 1, y, z )), local BA = self.p[B] + zi
lerp(u, grad(self.p[AB ], x, y - 1, z ), local BB = self.p[B + 1] + zi
grad(self.p[BB ], x - 1, y - 1, z ))), local BAA = self.p[BA]
lerp(v, lerp(u, grad(self.p[AA + 1], x, y, z - 1), local BBA = self.p[BB]
grad(self.p[BA + 1], x - 1, y, z - 1)), local BAB = self.p[BA + 1]
lerp(u, grad(self.p[AB + 1], x, y - 1, z - 1), local BBB = self.p[BB + 1]
grad(self.p[BB + 1], x - 1, y - 1, z - 1))))
-- Add 0.5 to rescale to 0 - 1 instead of -0.5 - +0.5
return 0.5 + self:lerp(w,
self:lerp(v,
self:lerp(u,
self:grad(AAA,x,y,z),
self:grad(BAA,x-1,y,z)
),
self:lerp(u,
self:grad(ABA,x,y-1,z),
self:grad(BBA,x-1,y-1,z)
)
),
self:lerp(v,
self:lerp(u,
self:grad(AAB,x,y,z-1), self:grad(BAB,x-1,y,z-1)
),
self:lerp(u,
self:grad(ABB,x,y-1,z-1), self:grad(BBB,x-1,y-1,z-1)
)
)
)
end end
function Perlin:BitAND(a, b) --Bitwise and
local p, c = 1, 0
while a > 0 and b > 0 do
local ra, rb = a%2, b%2
if ra + rb > 1 then c = c + p end
a, b, p = (a - ra) / 2, (b - rb) / 2, p * 2
end
return c
end
function Perlin:fade(t)
return t * t * t * (t * (t * 6 - 15) + 10)
end
function Perlin:lerp(t, a, b)
return a + t * (b - a)
end
function Perlin:grad(hash, x, y, z)
local h = self:BitAND(hash, 15)
local u = h < 8 and x or y
local v = h < 4 and y or ((h == 12 or h == 14) and x or z)
return ((h and 1) == 0 and u or - u) + ((h and 2) == 0 and v or - v)
end
return Perlin

@ -0,0 +1,39 @@
local wea = worldeditadditions
local White = dofile(wea.modpath.."/lib/noise/engines/white.lua")
local Red = {}
Red.__index = Red
function Red.new(seed)
local result = {
seed = seed or math.random(),
white = White.new(seed)
}
setmetatable(result, Red)
return result
end
function Red:noise( x, y, z )
local values = {
self.white:noise(x, y, z),
self.white:noise(x + 1, y, z),
self.white:noise(x, y + 1, z),
self.white:noise(x, y, z + 1),
self.white:noise(x - 1, y, z),
self.white:noise(x, y - 1, z),
self.white:noise(x, y, z - 1),
self.white:noise(x, y - 1, z - 1),
self.white:noise(x - 1, y, z - 1),
self.white:noise(x - 1, y - 1, z),
self.white:noise(x - 1, y - 1, z - 1),
self.white:noise(x, y + 1, z + 1),
self.white:noise(x + 1, y, z + 1),
self.white:noise(x + 1, y + 1, z),
self.white:noise(x + 1, y + 1, z + 1),
}
return wea.average(values)
end
return Red

@ -0,0 +1,21 @@
local wea = worldeditadditions
local Sin = {}
Sin.__index = Sin
function Sin.new()
local result = {}
setmetatable(result, Sin)
return result
end
function Sin:noise( x, y, z )
-- local value = math.sin(x)
local value = (math.sin(x) + math.sin(y) + math.sin(z)) / 3
-- Rescale from -1 - +1 to 0 - 1
return (value + 1) / 2
end
return Sin

@ -0,0 +1,27 @@
local wea = worldeditadditions
local White = {}
White.__index = White
function White.new(seed)
local result = {
seed = seed or math.random()
}
setmetatable(result, White)
return result
end
function White:noise( x, y, z )
if x == 0 then x = 1 end
if y == 0 then y = 1 end
if z == 0 then z = 1 end
local seed = ((self.seed + (x * y * z)) * 1506359) % 1113883
math.randomseed(seed)
local value = math.random()
return value
end
return White

@ -0,0 +1,14 @@
local wea = worldeditadditions
wea.noise = {}
-- The command itself
dofile(wea.modpath.."/lib/noise/run2d.lua")
-- Dependencies
dofile(wea.modpath.."/lib/noise/apply_2d.lua")
dofile(wea.modpath.."/lib/noise/make_2d.lua")
dofile(wea.modpath.."/lib/noise/params_apply_default.lua")
-- Noise generation engines
wea.noise.engines = dofile(wea.modpath.."/lib/noise/engines/init.lua")

@ -4,33 +4,57 @@
-- ██ ████ ██ ███████ █████ █████ █████ ██ ██ -- ██ ████ ██ ███████ █████ █████ █████ ██ ██
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ██ ██ ██ ██ ███████ ███████ ███████ ██████ -- ██ ██ ██ ██ ██ ██ ███████ ███████ ███████ ██████
local wea = worldeditadditions
-- Generate a flat array of 2D noise. -- Generate a flat array of 2D noise.
-- Written with help from https://www.redblobgames.com/maps/terrain-from-noise/ -- Written with help from https://www.redblobgames.com/maps/terrain-from-noise/
-- @param size Vector An x/y vector representing the size of the noise area to generate. -- @param size Vector An x/y vector representing the size of the noise area to generate.
-- @param params table|table<table> A table of noise params to use to generate the noise. Values that aren't specified are filled in automatically. If a table of tables is specified, it is interpreted as multiple octaves of noise to apply in sequence. -- @param params table|table<table> A table of noise params to use to generate the noise. Values that aren't specified are filled in automatically. If a table of tables is specified, it is interpreted as multiple octaves of noise to apply in sequence.
function worldeditadditions.noise.make_2d(size, params) function worldeditadditions.noise.make_2d(size, start_pos, params)
params = worldeditadditions.noise.params_apply_default(params)
local result = {} local result = {}
local generator; for layer_i, layer in ipairs(params) do
if params.algorithm == "perlin" then local generator
generator = worldeditadditions.noise.perlin.new() if layer.algorithm == "perlin" then
else -- We don't have any other generators just yet generator = wea.noise.engines.Perlin.new()
return false, "Error: Unknown noise algorithm '"..params.."'." elseif layer.algorithm == "sin" then
end generator = wea.noise.engines.Sin.new()
elseif layer.algorithm == "white" then
for x=params.offset.x, params.offset.x+size.x do generator = wea.noise.engines.White.new()
for y=params.offset.y, params.offset.y+size.y do elseif layer.algorithm == "red" then
local result = 0 generator = wea.noise.engines.Red.new()
for i,params_octave in ipairs(params) do elseif layer.algorithm == "infrared" then
result = result + (generator:noise(x * scale.x, y * scale.y, 0) ^ params.exponent) * params.multiply + params.add generator = wea.noise.engines.Infrared.new()
end else -- We don't have any other generators just yet
result[y*size.x + x] = result return false, "Error: Unknown noise algorithm '"..tostring(layer.algorithm).."' in layer "..layer_i.." of "..#params.." (available algorithms: "..table.concat(wea.noise.engines.available, ", ")..")."
end end
-- TODO: Optimise performance by making i count backwards in sequence
for x = 0, size.x - 1 do
for y = 0, size.z - 1 do
local i = y*size.x + x
local noise_x = (x + 100000+start_pos.x+layer.offset.x) * layer.scale.x
local noise_y = (y + 100000+start_pos.z+layer.offset.z) * layer.scale.z
local noise_value = generator:noise(noise_x, noise_y, 0)
if type(result[i]) ~= "number" then result[i] = 0 end
local result_before = result[i]
result[i] = result[i] + (noise_value ^ layer.exponent) * layer.multiply + layer.add
-- if layer_i == 1 and result[i] > 1 then
-- print("NOISE TOO BIG noise_value", noise_value, "noise_x", noise_x, "noise_y", noise_y, "i", i, "result[i]: BEFORE", result_before, "AFTER", result[i])
-- end
end
end
end end
return result
print("NOISE MAKE_2D\n")
worldeditadditions.format.array_2d(result, size.x)
-- We don't round here, because otherwise when we apply it it'll be inaccurate
return true, result
end end

@ -1,5 +0,0 @@
worldeditadditions.noise = {}
dofile(worldeditadditions.modpath.."/lib/noise/noise_params.lua")
dofile(worldeditadditions.modpath.."/lib/noise/make_2d.lua")
dofile(worldeditadditions.modpath.."/lib/noise/engines/perlin.lua")

@ -1,14 +1,23 @@
local wea = worldeditadditions
--- Makes sure that the default settings are all present in the given params table. --- Makes sure that the default settings are all present in the given params table.
-- This way not all params have to be specified by the user. -- This way not all params have to be specified by the user.
-- @param params table The params table generated from the user's input. -- @param params table The params table generated from the user's input.
-- @return table A NEW table with all the missing properties filled in with the default values. -- @return table A NEW table with all the missing properties filled in with the default values.
function worldeditadditions.noise.params_apply_default(params) function worldeditadditions.noise.params_apply_default(params)
local params_default = { local params_default = {
-- How to apply the noise to the heightmap. Different values result in
-- different effects:
-- - The exact string "add": Noise values are added to each heightmap pixel.
-- - The exact string "multiply": Each heightmap pixel is multiplied by the corresponding noise value.
-- - A string in the form of digits followed, then the noise will is remapped from the range 0 - 1 to the range -1 - +1 and multiplied by this number / 2, and then for each pixel in the heightmap the corresponding noise value will be added to it.
apply = 5,
-- The backend noise algorithm to use
algorithm = "perlin", algorithm = "perlin",
-- Zooms in and out -- Zooms in and out
scale = vector.new(1, 1, 1), scale = wea.Vector3.new(1, 1, 1),
-- Offset the generated noise by this vector. -- Offset the generated noise by this vector.
offset = vector.new(0, 0, 0), offset = wea.Vector3.new(0, 0, 0),
-- Apply this exponent to the resulting noise value -- Apply this exponent to the resulting noise value
exponent = 1, exponent = 1,
-- Multiply the resulting noise value by this number. Changes the "strength" of the noise -- Multiply the resulting noise value by this number. Changes the "strength" of the noise
@ -23,10 +32,19 @@ function worldeditadditions.noise.params_apply_default(params)
if not params[1] then params = { params } end if not params[1] then params = { params } end
-- If params[1] is thing, this is a list of params -- If params[1] is thing, this is a list of params
-- This might be a thing if we're dealingw ith multiple octaves -- This might be a thing if we're dealing with multiple octaves
for i,params_el in ipairs(params) do for i,params_el in ipairs(params) do
local default_copy = worldeditadditions.table.shallowcopy(params_default) local default_copy = wea.table.shallowcopy(params_default)
worldeditadditions.table.apply(
-- Keyword support
for i, keyword in ipairs(wea.noise.engines.available) do
if params_el[keyword] ~= nil then
params_el.algorithm = keyword
end
end
-- Apply this table to fill in the gaps
wea.table.apply(
params_el, params_el,
default_copy default_copy
) )

@ -1,6 +1,8 @@
--- Applies a layer of 2D noise over the terrain in the defined region. --- Applies a layer of 2D noise over the terrain in the defined region.
-- @module worldeditadditions.noise2d -- @module worldeditadditions.noise2d
local wea = worldeditadditions
-- ███ ██ ██████ ██ ███████ ███████ ██████ ██████ -- ███ ██ ██████ ██ ███████ ███████ ██████ ██████
-- ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ████ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ██ ██ ██ ██ ██ ██ ███████ █████ █████ ██ ██ -- ██ ██ ██ ██ ██ ██ ███████ █████ █████ ██ ██
@ -9,11 +11,16 @@
--- Applies a layer of 2d noise over the terrain in the defined region. --- Applies a layer of 2d noise over the terrain in the defined region.
-- @param pos1 Vector pos1 of the defined region -- @param pos1 Vector pos1 of the defined region
-- @param pos2 Vector pos2 of the defined region -- @param pos2 Vector pos2 of the defined region
-- @param noise_params table A noise parameters table. Will be passed unmodified to PerlinNoise() from the Minetest API. -- @param noise_params table A noise parameters table.
function worldeditadditions.noise2d(pos1, pos2, noise_params) function worldeditadditions.noise.run2d(pos1, pos2, noise_params)
pos1, pos2 = worldedit.sort_pos(pos1, pos2) pos1, pos2 = worldedit.sort_pos(pos1, pos2)
-- pos2 will always have the highest co-ordinates now -- pos2 will always have the highest co-ordinates now
-- Fill in the default params
-- print("DEBUG noise_params_custom ", wea.format.map(noise_params))
noise_params = worldeditadditions.noise.params_apply_default(noise_params)
-- print("DEBUG noise_params[1] ", wea.format.map(noise_params[1]))
-- Fetch the nodes in the specified area -- Fetch the nodes in the specified area
local manip, area = worldedit.manip_helpers.init(pos1, pos2) local manip, area = worldedit.manip_helpers.init(pos1, pos2)
local data = manip:get_data() local data = manip:get_data()
@ -25,14 +32,32 @@ function worldeditadditions.noise2d(pos1, pos2, noise_params)
) )
local heightmap_new = worldeditadditions.table.shallowcopy(heightmap_old) local heightmap_new = worldeditadditions.table.shallowcopy(heightmap_old)
local perlin_map = PerlinNoiseMap(noise_params, heightmap_size) local success, noisemap = wea.noise.make_2d(
heightmap_size,
pos1,
noise_params)
if not success then return success, noisemap end
-- TODO: apply the perlin noise map here local message
success, message = wea.noise.apply_2d(
heightmap_new,
noisemap,
heightmap_size,
pos1, pos2,
noise_params[1].apply
)
if not success then return success, message end
local stats = { added = 0, removed = 0 }
local success, stats = wea.apply_heightmap_changes(
pos1, pos2,
area, data,
heightmap_old, heightmap_new,
heightmap_size
)
if not success then return success, stats end
-- Save the modified nodes back to disk & return -- Save the modified nodes back to disk & return
-- No need to save - this function doesn't actually change anything
worldedit.manip_helpers.finish(manip, data) worldedit.manip_helpers.finish(manip, data)
return true, stats return true, stats

@ -25,7 +25,6 @@ function worldeditadditions.scale_down(pos1, pos2, scale, anchor)
z = math.floor(1 / scale.z) z = math.floor(1 / scale.z)
} }
local size = vector.subtract(pos2, pos1) local size = vector.subtract(pos2, pos1)
print("[DEBUG] scale_down", worldeditadditions.vector.tostring(scale_down), "size", size)
if size.x < scale_down.x or size.y < scale_down.y or size.z < scale.z then if size.x < scale_down.x or size.y < scale_down.y or size.z < scale.z then
return false, "Error: Area isn't big enough to apply scale down by "..worldeditadditions.vector.tostring(scale).."." return false, "Error: Area isn't big enough to apply scale down by "..worldeditadditions.vector.tostring(scale).."."

@ -4,7 +4,7 @@
function worldeditadditions.format.map(map) function worldeditadditions.format.map(map)
local result = {} local result = {}
for key, value in pairs(map) do for key, value in pairs(map) do
table.insert(result, key.."\t"..value) table.insert(result, key.."\t"..tostring(value))
end end
return table.concat(result, "\n") return table.concat(result, "\n")
end end

@ -0,0 +1,80 @@
local Queue
if worldeditadditions then
Queue = dofile(worldeditadditions.modpath.."/utils/queue.lua")
else
Queue = require("queue")
end
--- A least-recently-used cache implementation.
-- @class
local LRU = {}
LRU.__index = LRU
--- Creates a new LRU cache.
-- Optimal sizes: 8, 16, 32, 64 or any value above
-- @param max_size=32 number The maximum number of items to store in the cache.
-- @returns LRU A new LRU cache.
function LRU.new(max_size)
if not max_size then max_size = 32 end
local result = {
max_size = max_size,
cache = { },
size = 0,
queue = Queue.new()
}
setmetatable(result, LRU)
return result
end
--- Determines whether the given key is present in this cache object.
-- Does NOT update the most recently used status of said key.
-- @param key string The key to check.
-- @returns bool Whether the given key exists in the cache or not.
function LRU:has(key)
return self.cache[key] ~= nil
end
--- Gets the value associated with the given key.
-- @param key string The key to retrieve the value for.
-- @returns any|nil The value associated with the given key, or nil if it doesn't exist in this cache.
function LRU:get(key)
if not self.cache[key] then return nil end
-- Put it to the end of the queue
self.queue:remove_index(self.cache[key].index)
self.cache[key].index = self.queue:enqueue(key)
return self.cache[key].value
end
--- Adds a given key-value pair to this cache.
-- Note that this might (or might not) result in the eviction of another item.
-- @param key string The key of the item to add.
-- @param value any The value to associate with the given key.
-- @returns nil
function LRU:set(key, value)
if self.cache[key] ~= nil then
-- It's already present in the cache - update it
-- Put it to the end of the queue
self.queue:remove_index(self.cache[key].index)
local new_index = self.queue:enqueue(key)
-- Update the cache entry
self.cache[key] = {
value = value,
index = new_index
}
else
-- It's not in the cache -- add it
self.cache[key] = { value = value, index = self.queue:enqueue(key) }
self.size = self.size + 1
if self.size > self.max_size then
-- The cache is full, delete the oldest item
local oldest_key = self.queue:dequeue()
self.cache[oldest_key] = nil
self.size = self.size - 1
end
end
end
return LRU

@ -0,0 +1,81 @@
local LRU = require("lru")
local operations = 100000
local values = {
"Abiu", "Açaí", "Acerola", "Ackee", "African cucumber", "Apple", "Apricot",
"Avocado", "Banana", "Bilberry", "Blackberry", "Blackcurrant",
"Black sapote", "Blueberry", "Boysenberry", "Breadfruit", "Cactus pear",
"Canistel", "Cempedak", "Cherimoya", "Cherry", "Chico fruit", "Cloudberry",
"Coco De Mer", "Coconut", "Crab apple", "Cranberry", "Currant", "Damson",
"Date", "Dragonfruit (or Pitaya)", "Durian", "Egg Fruit", "Elderberry",
"Feijoa", "Fig", "Finger Lime", "Goji berry", "Gooseberry", "Grape",
"Raisin", "Grapefruit", "Grewia asiatica", "Guava", "Hala Fruit",
"Honeyberry", "Huckleberry", "Jabuticaba", "Jackfruit", "Jambul",
"Japanese plum", "Jostaberry", "Jujube", "Juniper berry", "Kaffir Lime",
"Kiwano", "Kiwifruit", "Kumquat", "Lemon", "Lime", "Loganberry", "Longan",
"Loquat"
}
-- From http://lua-users.org/wiki/SimpleRound
function round(num, numDecimalPlaces)
local mult = 10^(numDecimalPlaces or 0)
return math.floor(num * mult + 0.5) / mult
end
--- Pads str to length len with char from right
-- @source https://snipplr.com/view/13092/strlpad--pad-string-to-the-left
local function str_padend(str, len, char)
if char == nil then char = ' ' end
return str .. string.rep(char, len - #str)
end
--- Pads str to length len with char from left
-- Adapted from the above
local function str_padstart(str, len, char)
if char == nil then char = ' ' end
return string.rep(char, len - #str) .. str
end
local values_count = #values
function test(size)
local cpu_start = os.clock()
local lru = LRU.new(size)
local hits = 0
local misses = 0
for i=1,operations do
local key = values[math.random(1, values_count)]
if math.random() >= 0.5 then
-- set
lru:set(key, math.random())
else
-- get
if lru:get(key) == nil then
misses = misses + 1
lru:set(key, math.random())
else
hits = hits + 1
end
end
end
local cpu_end = os.clock()
print(
size,
str_padend(tostring(round(cpu_end - cpu_start, 6)), 9),
hits, misses,
((hits/operations)*100)
)
end
io.stderr:write("OPERATIONS\t"..operations.."\n")
print("size", "cpu time", "hits", "misses", "hit ratio (%)")
for i=3,256 do
io.stderr:write("size "..i.."\r")
test(i)
end

@ -8,6 +8,7 @@ local wea = worldeditadditions
-- ██ ██ ██ ██████ ███████ -- ██ ██ ██ ██████ ███████
--- A single face of a Mesh. --- A single face of a Mesh.
-- @class
local Face = {} local Face = {}
Face.__index = Face Face.__index = Face
@ -41,6 +42,7 @@ function Face.__eq(a, b) return Face.equal(a, b) end
-- ██ ██ ███████ ███████ ██ ██ -- ██ ██ ███████ ███████ ██ ██
--- A mesh of faces. --- A mesh of faces.
-- @class
local Mesh = {} local Mesh = {}
Mesh.__index = Mesh Mesh.__index = Mesh

@ -76,6 +76,11 @@ end
local sapling_aliases = {} local sapling_aliases = {}
--- Register a new sapling alias.
-- @param sapling_node_name string The canonical name of the sapling.
-- @param alias string The alias name of the sapling.
-- @returns bool[,string] Whether the alias registration was successful or not. If false, then an error message as a string is also returned as the second value.
function worldeditadditions.register_sapling_alias(sapling_node_name, alias) function worldeditadditions.register_sapling_alias(sapling_node_name, alias)
if sapling_aliases[sapling_node_name] ~= nil then if sapling_aliases[sapling_node_name] ~= nil then
return false, "Error: An alias against the node name '"..sapling_node_name.."' already exists." return false, "Error: An alias against the node name '"..sapling_node_name.."' already exists."
@ -83,6 +88,9 @@ function worldeditadditions.register_sapling_alias(sapling_node_name, alias)
sapling_aliases[alias] = sapling_node_name sapling_aliases[alias] = sapling_node_name
return true return true
end end
--- Convenience function to register many sapling aliases at once.
-- @param tbl [string, string][] A list of tables containing exactly 2 strings in the form { sapling_node_name, alias }.
-- @returns bool[,string] Whether the alias registrations were successful or not. If false, then an error message as a string is also returned as the second value.
function worldeditadditions.register_sapling_alias_many(tbl) function worldeditadditions.register_sapling_alias_many(tbl)
for i, next in ipairs(tbl) do for i, next in ipairs(tbl) do
local success, msg = worldeditadditions.register_sapling_alias( local success, msg = worldeditadditions.register_sapling_alias(
@ -91,6 +99,7 @@ function worldeditadditions.register_sapling_alias_many(tbl)
) )
if not success then return success, msg end if not success then return success, msg end
end end
return true
end end
--- Returns the current key ⇒ value table of sapling names and aliases. --- Returns the current key ⇒ value table of sapling names and aliases.
-- @return table -- @return table

@ -87,6 +87,17 @@ function worldeditadditions.getsign(src)
else return src:match('-') and -1 or 1 end else return src:match('-') and -1 or 1 end
end end
--- Clamp a number to ensure it falls within a given range.
-- @param value number The value to clamp.
-- @param min number The minimum allowed value.
-- @param max number The maximum allowed value.
-- @returns number The clamped number.
function worldeditadditions.clamp(value, min, max)
if value < min then return min end
if value > max then return max end
return value
end
-- For Testing: -- For Testing:
-- worldeditadditions = {} -- worldeditadditions = {}
-- print(worldeditadditions.getsign('-y')) -- print(worldeditadditions.getsign('-y'))

@ -1,25 +1,36 @@
local wea = worldeditadditions
--- Parses a map of key-value pairs into a table. --- Parses a map of key-value pairs into a table.
-- For example, "count 25000 speed 0.8 rate_erosion 0.006 doawesome true" would be parsed into -- For example, "count 25000 speed 0.8 rate_erosion 0.006 doawesome true" would be parsed into
-- the following table: { count = 25000, speed = 0.8, rate_erosion = 0.006, doawesome = true }. -- the following table: { count = 25000, speed = 0.8, rate_erosion = 0.006, doawesome = true }.
-- @param params_text string The string to parse. -- @param params_text string The string to parse.
-- @param keywords string[]? Optional. A list of keywords. Keywords can be present on their own without a value. If found, their value will be automatically set to bool true.
-- @returns table A table of key-value pairs parsed out from the given string. -- @returns table A table of key-value pairs parsed out from the given string.
function worldeditadditions.parse.map(params_text) function worldeditadditions.parse.map(params_text, keywords)
if not keywords then keywords = {} end
local result = {} local result = {}
local parts = worldeditadditions.split(params_text, "%s+", false) local parts = wea.split(params_text, "%s+", false)
local last_key = nil local last_key = nil
local mode = "KEY"
for i, part in ipairs(parts) do for i, part in ipairs(parts) do
if i % 2 == 0 then -- Lua starts at 1 :-/ if mode == "VALUE" then
-- Try converting to a number to see if it works -- Try converting to a number to see if it works
local part_converted = tonumber(part) local part_converted = tonumber(part)
if as_number == nil then part_converted = part end if part_converted == nil then part_converted = part end
-- Look for bools -- Look for bools
if part_converted == "true" then part_converted = true end if part_converted == "true" then part_converted = true end
if part_converted == "false" then part_converted = false end if part_converted == "false" then part_converted = false end
result[last_key] = part result[last_key] = part
mode = "KEY"
else else
last_key = part last_key = part
-- Keyword support
if wea.table.contains(keywords, last_key) then
result[last_key] = true
else
mode = "VALUE"
end
end end
end end
return true, result return true, result

@ -0,0 +1,56 @@
-------------------------------------------------------------------------------
--- A Queue implementation, taken & adapted from https://www.lua.org/pil/11.4.html
-- @submodule worldeditadditions.utils.queue
local Queue = {}
Queue.__index = Queue
function Queue.new()
result = { first = 0, last = -1, items = {} }
setmetatable(result, Queue)
return result
end
function Queue:enqueue(value)
local new_last = self.last + 1
self.last = new_last
self.items[new_last] = value
return new_last
end
function Queue:contains(value)
for i=self.first,self.last do
if self.items[i] == value then
return true
end
end
return false
end
function Queue:is_empty()
return self.first > self.last
end
function Queue:remove_index(index)
self.items[index] = nil
end
function Queue:dequeue()
if Queue.is_empty(self) then
error("Error: The self is empty!")
end
local first = self.first
-- Find the next non-nil item
local value
while value == nil do
if first >= self.last then return nil end
value = self.items[first]
self.items[first] = nil -- Help the garbage collector out
first = first + 1
end
self.first = first
return value
end
return Queue

@ -3,7 +3,7 @@
-- @param tbl table The table to look in. -- @param tbl table The table to look in.
-- @param target any The target to look for. -- @param target any The target to look for.
-- @returns bool Whether the table contains the given target or not. -- @returns bool Whether the table contains the given target or not.
local function contains(tbl, target) local function table_contains(tbl, target)
for key, value in ipairs(tbl) do for key, value in ipairs(tbl) do
if value == target then return true end if value == target then return true end
end end

@ -92,14 +92,14 @@ end
-- @param heightmap_old number[] The original heightmap from worldeditadditions.make_heightmap. -- @param heightmap_old number[] The original heightmap from worldeditadditions.make_heightmap.
-- @param heightmap_new number[] The new heightmap containing the altered updated values. It is expected that worldeditadditions.table.shallowcopy be used to make a COPY of the data worldeditadditions.make_heightmap for this purpose. Both heightmap_old AND heightmap_new are REQUIRED in order for this function to work. -- @param heightmap_new number[] The new heightmap containing the altered updated values. It is expected that worldeditadditions.table.shallowcopy be used to make a COPY of the data worldeditadditions.make_heightmap for this purpose. Both heightmap_old AND heightmap_new are REQUIRED in order for this function to work.
-- @param heightmap_size vector The x / z size of the heightmap. Any y value set in the vector is ignored. -- @param heightmap_size vector The x / z size of the heightmap. Any y value set in the vector is ignored.
-- -- @returns bool, string|{ added: number, removed: number } A bool indicating whether the operation was successful or not, followed by either an error message as a string (if it was not successful) or a table of statistics (if it was successful).
function worldeditadditions.apply_heightmap_changes(pos1, pos2, area, data, heightmap_old, heightmap_new, heightmap_size) function worldeditadditions.apply_heightmap_changes(pos1, pos2, area, data, heightmap_old, heightmap_new, heightmap_size)
local stats = { added = 0, removed = 0 } local stats = { added = 0, removed = 0 }
local node_id_air = minetest.get_content_id("air") local node_id_air = minetest.get_content_id("air")
local node_id_ignore = minetest.get_content_id("ignore") local node_id_ignore = minetest.get_content_id("ignore")
for z = heightmap_size.z, 0, -1 do for z = heightmap_size.z - 1, 0, -1 do
for x = heightmap_size.x, 0, -1 do for x = heightmap_size.x - 1, 0, -1 do
local hi = z*heightmap_size.x + x local hi = z*heightmap_size.x + x
local height_old = heightmap_old[hi] local height_old = heightmap_old[hi]

@ -1,17 +1,16 @@
--- A 3-dimensional vector. --- A 3-dimensional vector.
-- @class
local Vector3 = {} local Vector3 = {}
Vector3.__index = Vector3 Vector3.__index = Vector3
--- Creates a new Vector3 instance.
-- @param x number The x co-ordinate value.
-- @param y number The y co-ordinate value.
-- @param z number The z co-ordinate value.
function Vector3.new(x, y, z) function Vector3.new(x, y, z)
if type(x) ~= "number" then x = x or 0
error("Error: Expected number for the value of x, but received argument of type "..type(x)..".") y = y or 0
end z = z or 0
if type(y) ~= "number" then
error("Error: Expected number for the value of y, but received argument of type "..type(y)..".")
end
if type(z) ~= "number" then
error("Error: Expected number for the value of z, but received argument of type "..type(z)..".")
end
local result = { local result = {
x = x, x = x,
@ -390,5 +389,11 @@ function Vector3.__tostring(a)
return "("..a.x..", "..a.y..", "..a.z..")" return "("..a.x..", "..a.y..", "..a.z..")"
end end
function Vector3.__concat(a, b)
if type(a) ~= "string" then a = tostring(a) end
if type(b) ~= "string" then b = tostring(b) end
return a .. b
end
return Vector3 return Vector3

@ -28,7 +28,6 @@ minetest.register_chatcommand("/saplingaliases", {
local results = worldeditadditions.registered_nodes_by_group("sapling") local results = worldeditadditions.registered_nodes_by_group("sapling")
table.insert(msg, "Sapling-like nodes:\n") table.insert(msg, "Sapling-like nodes:\n")
local str = table.concat(results, "\n") local str = table.concat(results, "\n")
print(str)
table.insert(msg, str) table.insert(msg, str)
else else
table.insert(msg, "Unknown mode '") table.insert(msg, "Unknown mode '")

@ -0,0 +1,69 @@
-- ███████ ██ ██ ██ ██████ ███████ ███████ █████ ██████ ██████ ██ ██ ██
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- █████ ██ ██ ██ ██████ ███████ █████ ███████ ██████ ██████ ██ ████
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
-- ███████ ███████ ███████ ██ ██ ███████ ███████ ██ ██ ██ ██ ███████ ██
worldedit.register_command("airapply", {
params = "<command_name> <args>",
description = "Executes the given command (automatically prepending '//'), but only on non-air nodes within the defined region.",
privs = { worldedit = true },
require_pos = 2,
parse = function(params_text)
if params_text == "" then return false, "Error: No command specified." end
local cmd_name, args_text = params_text:match("([^%s]+)%s+(.+)")
if not cmd_name then
cmd_name = params_text
args_text = ""
end
-- Note that we search the worldedit commands here, not the minetest ones
local cmd_we = worldedit.registered_commands[cmd_name]
if cmd_we == nil then
return false, "Error: "..cmd_name.." isn't a valid command."
end
if cmd_we.require_pos ~= 2 then
return false, "Error: The command "..cmd_name.." exists, but doesn't take 2 positions and so can't be used with //airapply."
end
-- Run parsing of target command
-- Lifted from cubeapply in WorldEdit
local args_parsed = {cmd_we.parse(args_text)}
if not table.remove(args_parsed, 1) then
return false, args_parsed[1]
end
return true, cmd_we, args_parsed
end,
nodes_needed = function(name)
local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
return math.ceil(4/3 * math.pi * (pos2.x - pos1.x)/2 * (pos2.y - pos1.y)/2 * (pos2.z - pos1.z)/2)
end,
func = function(name, cmd, args_parsed)
if not minetest.check_player_privs(name, cmd.privs) then
return false, "Your privileges are insufficient to execute the command '"..cmd_name.."'."
end
local pos1, pos2 = worldeditadditions.Vector3.sort(
worldedit.pos1[name],
worldedit.pos2[name]
)
local success, stats_time = worldeditadditions.airapply(
pos1, pos2,
function()
cmd.func(name, worldeditadditions.table.unpack(args_parsed))
end, args
)
local time_overhead = 100 - worldeditadditions.round((stats_time.fn / stats_time.all) * 100, 3)
local text_time_all = worldeditadditions.format.human_time(stats_time.all)
local text_time_fn = worldeditadditions.format.human_time(stats_time.fn)
minetest.log("action", name.." used //airapply at "..pos1.." - "..pos2.." in "..text_time_all)
return true, "Complete in "..text_time_all.." ("..text_time_fn.." fn, "..time_overhead.."% airapply overhead)"
end
})

@ -45,7 +45,7 @@ minetest.register_chatcommand("/multi", {
if not success then return success, commands end if not success then return success, commands end
for i, command in ipairs(commands) do for i, command in ipairs(commands) do
print("[DEBUG] i", i, "command: '"..command.."'") -- print("[DEBUG] i", i, "command: '"..command.."'")
local start_time = worldeditadditions.get_ms_time() local start_time = worldeditadditions.get_ms_time()
local found, _, command_name, args = command:find("^([^%s]+)%s(.+)$") local found, _, command_name, args = command:find("^([^%s]+)%s(.+)$")
@ -53,7 +53,7 @@ minetest.register_chatcommand("/multi", {
-- Things start at 1, not 0 in Lua :-( -- Things start at 1, not 0 in Lua :-(
command_name = worldeditadditions.trim(command_name):sub(2) -- Strip the leading / command_name = worldeditadditions.trim(command_name):sub(2) -- Strip the leading /
if not args then args = "" end if not args then args = "" end
print("command_name", command_name) -- print("command_name", command_name)
worldedit.player_notify(name, "#"..i..": "..command) worldedit.player_notify(name, "#"..i..": "..command)

@ -0,0 +1,52 @@
local wea = worldeditadditions
worldedit.register_command("noise2d", {
params = "[<key_1> [<value_1>]] [<key_2> [<value_2>]] ...]",
description = "Applies 2d random noise to the terrain as a 2d heightmap in the defined region. Optionally takes an arbitrary set of key - value pairs representing parameters that control the properties of the noise and how it's applied. See the full documentation for details of these parameters and what they do.",
privs = { worldedit = true },
require_pos = 2,
parse = function(params_text)
if not params_text then return true, {} end
params_text = wea.trim(params_text)
if params_text == "" then return true, {} end
local success, map = worldeditadditions.parse.map(params_text,
wea.noise.engines.available) -- Keywords
if not success then return success, map end
if map.scale then
map.scale = tonumber(map.scale)
map.scale = wea.Vector3.new(map.scale, map.scale, map.scale)
elseif map.scalex or map.scaley or map.scalez then
map.scalex = tonumber(map.scalex) or 1
map.scaley = tonumber(map.scaley) or 1
map.scalez = tonumber(map.scalez) or 1
map.scale = wea.Vector3.new(map.scalex, map.scaley, map.scalez)
end
if map.offsetx or map.offsety or map.offsetz then
map.offsetx = tonumber(map.offsetx) or 0
map.offsety = tonumber(map.offsety) or 0
map.offsetz = tonumber(map.offsetz) or 0
map.offset = wea.Vector3.new(map.offsetx, map.offsety, map.offsetz)
end
return true, map
end,
nodes_needed = function(name)
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
end,
func = function(name, params)
local start_time = worldeditadditions.get_ms_time()
local success, stats = worldeditadditions.noise.run2d(
worldedit.pos1[name], worldedit.pos2[name],
params
)
if not success then return success, stats end
local time_taken = worldeditadditions.get_ms_time() - start_time
minetest.log("action", name .. " used //noise2d at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", adding " .. stats.added .. " nodes and removing " .. stats.removed .. " nodes in " .. time_taken .. "s")
return true, stats.added .. " nodes added and " .. stats.removed .. " nodes removed in " .. worldeditadditions.format.human_time(time_taken)
end
})

@ -102,7 +102,7 @@ worldedit.register_command("scale", {
return volume * factor return volume * factor
end, end,
func = function(name, scale, anchor) func = function(name, scale, anchor)
print("initial scale: "..worldeditadditions.vector.tostring(scale)..", anchor: "..worldeditadditions.vector.tostring(anchor)) -- print("initial scale: "..worldeditadditions.vector.tostring(scale)..", anchor: "..worldeditadditions.vector.tostring(anchor))
local start_time = worldeditadditions.get_ms_time() local start_time = worldeditadditions.get_ms_time()

@ -28,6 +28,7 @@ dofile(we_c.modpath.."/commands/hollow.lua")
dofile(we_c.modpath.."/commands/layers.lua") dofile(we_c.modpath.."/commands/layers.lua")
dofile(we_c.modpath.."/commands/line.lua") dofile(we_c.modpath.."/commands/line.lua")
dofile(we_c.modpath.."/commands/maze.lua") dofile(we_c.modpath.."/commands/maze.lua")
dofile(we_c.modpath.."/commands/noise2d.lua")
dofile(we_c.modpath.."/commands/overlay.lua") dofile(we_c.modpath.."/commands/overlay.lua")
dofile(we_c.modpath.."/commands/replacemix.lua") dofile(we_c.modpath.."/commands/replacemix.lua")
dofile(we_c.modpath.."/commands/scale.lua") dofile(we_c.modpath.."/commands/scale.lua")
@ -40,6 +41,7 @@ dofile(we_c.modpath.."/commands/meta/multi.lua")
dofile(we_c.modpath.."/commands/meta/many.lua") dofile(we_c.modpath.."/commands/meta/many.lua")
dofile(we_c.modpath.."/commands/meta/subdivide.lua") dofile(we_c.modpath.."/commands/meta/subdivide.lua")
dofile(we_c.modpath.."/commands/meta/ellipsoidapply.lua") dofile(we_c.modpath.."/commands/meta/ellipsoidapply.lua")
dofile(we_c.modpath.."/commands/meta/airapply.lua")
-- Selection Tools -- Selection Tools
dofile(we_c.modpath.."/commands/selectors/init.lua") dofile(we_c.modpath.."/commands/selectors/init.lua")