mirror of
https://github.com/sbrl/Minetest-WorldEditAdditions.git
synced 2024-08-21 05:54:06 +02:00
Merge branch 'VorTechnix' of https://github.com/sbrl/Minetest-WorldEditAdditions into VorTechnix
This commit is contained in:
commit
46bd5d4fce
@ -1,12 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
const os = require("os");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const debug = require("debug");
|
||||
const htmlentities = require("html-entities");
|
||||
const phin = require("phin");
|
||||
|
||||
const Image = require("@11ty/eleventy-img");
|
||||
|
||||
const HTMLPicture = require("./lib/HTMLPicture.js");
|
||||
|
||||
var nextid = 0;
|
||||
|
||||
@ -16,46 +18,22 @@ const image_filename_format = (_id, src, width, format, _options) => {
|
||||
return `${name}-${width}w.${format}`;
|
||||
};
|
||||
|
||||
function image_metadata_log(metadata, source) {
|
||||
for(let format in metadata) {
|
||||
for(let img of metadata[format]) {
|
||||
console.log(`${source.padEnd(10)} ${format.padEnd(5)} ${`${img.width}x${img.height}`.padEnd(10)} ${img.outputPath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function shortcode_image(src, alt, classes = "") {
|
||||
let metadata = await Image(src, {
|
||||
widths: [300, null],
|
||||
formats: ["avif", "jpeg"],
|
||||
outputDir: `./_site/img/`,
|
||||
filenameFormat: image_filename_format
|
||||
});
|
||||
image_metadata_log(metadata, `IMAGE`);
|
||||
async function shortcode_image(src, alt) {
|
||||
|
||||
let imageAttributes = {
|
||||
class: classes,
|
||||
alt: htmlentities.encode(alt),
|
||||
sizes: Object.values(metadata)[0].map((el) => `${el.width}w`).join(" "),
|
||||
loading: "lazy",
|
||||
decoding: "async",
|
||||
};
|
||||
|
||||
// You bet we throw an error on missing alt in `imageAttributes` (alt="" works okay)
|
||||
return Image.generateHTML(metadata, imageAttributes);
|
||||
return HTMLPicture(
|
||||
src, alt,
|
||||
`./_site/img`, `/img`
|
||||
);
|
||||
}
|
||||
|
||||
async function shortcode_image_url(src) {
|
||||
let metadata = await Image(src, {
|
||||
widths: [ null ],
|
||||
formats: [ "jpeg" ],
|
||||
outputDir: `./_site/img/`,
|
||||
filenameFormat: image_filename_format
|
||||
});
|
||||
image_metadata_log(metadata, `IMAGE_URL`);
|
||||
const src_parsed = path.parse(src);
|
||||
const target = path.join(`./_site/img`, src_parsed.base);
|
||||
if(!fs.existsSync(path.dirname(target)))
|
||||
await fs.promises.mkdir(target_dir, { recursive: true });
|
||||
await fs.promises.copyFile(src, target);
|
||||
|
||||
let data = metadata.jpeg[metadata.jpeg.length - 1];
|
||||
return data.url;
|
||||
return path.join(`/img`, src_parsed.base);
|
||||
}
|
||||
|
||||
async function shortcode_image_urlpass(src) {
|
||||
@ -82,14 +60,14 @@ async function shortcode_gallerybox(content, src, idthis, idprev, idnext) {
|
||||
}
|
||||
|
||||
async function fetch(url) {
|
||||
let package = JSON.parse(await fs.promises.readFile(
|
||||
const pkg_obj = 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(/\^/, "")}`
|
||||
"user-agent": `WorldEditAdditionsStaticBuilder/${pkg_obj.version} (Node.js/${process.version}; ${os.platform()} ${os.arch()}) eleventy/${pkg_obj.devDependencies["@11ty/eleventy"].replace(/\^/, "")}`
|
||||
},
|
||||
followRedirects: true,
|
||||
parse: "string"
|
||||
|
@ -1,5 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const columnify = require("columnify");
|
||||
const htmlentities = require("html-entities");
|
||||
|
||||
const a = require("./lib/Ansi.js");
|
||||
const parse_sections = require("./lib/parse_sections.js");
|
||||
|
||||
let { sections, categories } = parse_sections(fs.readFileSync(
|
||||
@ -15,11 +22,20 @@ sections = sections.slice(1).sort((a, b) => a.title.replace(/^\/+/g, "").localeC
|
||||
|
||||
|
||||
console.log(`REFERENCE SECTION TITLES`)
|
||||
console.log(sections
|
||||
.map(s => [s.category, s.title].join(`\t`)).join(`\n`));
|
||||
console.log(columnify(sections.map(s => { return {
|
||||
category: `${a.hicol}${a.fyellow}${s.category}${a.reset}`,
|
||||
command: `${a.hicol}${a.fmagenta}${htmlentities.decode(s.title)}${a.reset}`
|
||||
} })));
|
||||
// console.log(sections
|
||||
// .map(s => `${a.fyellow}${a.hicol}${s.category}${a.reset}\t${a.fmagenta}${a.hicol}${s.title}${a.reset}`).join(`\n`));
|
||||
console.log(`************************`);
|
||||
|
||||
console.log(`REFERENCE SECTION COLOURS`, categories);
|
||||
console.log(`REFERENCE SECTION COLOURS`);
|
||||
console.log(columnify(Array.from(categories).map(el => { return {
|
||||
category: el[0],
|
||||
colour: el[1]
|
||||
} })));
|
||||
|
||||
module.exports = {
|
||||
layout: "theme.njk",
|
||||
title: "Reference",
|
||||
|
@ -14,11 +14,11 @@
|
||||
|
||||
<section id="filter" class="panel-generic">
|
||||
<div class="form-item bigsearch">
|
||||
<label for="input-filter">Filter:</label>
|
||||
<input type="search" id="input-filter" />
|
||||
<label for="input-filter" >Filter:</label>
|
||||
<input type="search" id="input-filter" placeholder="Start typing to filter." />
|
||||
</div>
|
||||
<div class="form-item centre checkbox">
|
||||
<input type="checkbox" id="input-searchall" placeholder="Start typing to filter the sections." />
|
||||
<input type="checkbox" id="input-searchall" />
|
||||
<label for="input-searchall" title="If unchecked, only the title will be searched.">Search content</label>
|
||||
</div>
|
||||
</section>
|
||||
@ -128,6 +128,7 @@
|
||||
function do_filter() {
|
||||
let el_search = document.querySelector("#input-filter");
|
||||
let el_searchall = document.querySelector("#input-searchall");
|
||||
localStorage.setItem("commandlist-searchall", el_searchall.checked);
|
||||
/* Filterable items
|
||||
- Sections
|
||||
- Commands in the command list
|
||||
@ -165,6 +166,22 @@
|
||||
el_next.classList.remove("visible", "hidden");
|
||||
el_next.classList.add(show ? "visible" : "hidden");
|
||||
}
|
||||
|
||||
let commandlist_is_categorical = document.querySelector("button.active[data-mode=categorical]") !== null;
|
||||
if(commandlist_is_categorical) {
|
||||
let container = document.querySelector(".command-container");
|
||||
for(let i = 0; i < container.children.length; i++) {
|
||||
if(container.children[i].nodeName.toLowerCase() === "ul") {
|
||||
let class_name = "visible";
|
||||
if(container.children[i].querySelector("li.visible") === null)
|
||||
class_name = "hidden";
|
||||
if(i > 0) {
|
||||
container.children[i-1].classList.remove("visible", "hidden");
|
||||
container.children[i-1].classList.add(class_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", (_event) => {
|
||||
@ -181,6 +198,9 @@
|
||||
els_cats[i].addEventListener("touchend", handle_display_mode);
|
||||
}
|
||||
|
||||
if(localStorage.getItem("commandlist-searchall") !== null)
|
||||
el_searchall.checked = localStorage.getItem("commandlist-searchall") === "true";
|
||||
|
||||
if(localStorage.getItem("commandlist-displaymode") !== null)
|
||||
set_display_mode(localStorage.getItem("commandlist-displaymode"))
|
||||
});
|
||||
|
88
.docs/lib/Ansi.js
Normal file
88
.docs/lib/Ansi.js
Normal file
@ -0,0 +1,88 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Generates various VT100 ANSI escape sequences.
|
||||
* Ported from C#.
|
||||
* @licence MPL-2.0 <https://www.mozilla.org/en-US/MPL/2.0/>
|
||||
* @source https://gist.github.com/a4edd3204a03f4eedb79785751efb0f3#file-ansi-cs
|
||||
* @author Starbeamrainbowlabs
|
||||
* GitHub: @sbrl | Twitter: @SBRLabs | Reddit: u/Starbeamrainbowlabs
|
||||
***** Changelog *****
|
||||
* 27th March 2019:
|
||||
* - Initial public release
|
||||
* 9th March 2020:
|
||||
* - Add Italics (\u001b[3m])
|
||||
* - Export a new instance of it by default (makes it so that there's 1 global instance)
|
||||
* 5th September 2020:
|
||||
* - Add support for NO_COLOR environment variable <https://no-color.org/>
|
||||
*/
|
||||
class Ansi {
|
||||
constructor() {
|
||||
/**
|
||||
* Whether we should *actually* emit ANSI escape codes or not.
|
||||
* Useful when we want to output to a log file, for example
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this.enabled = true;
|
||||
|
||||
this.escape_codes();
|
||||
}
|
||||
|
||||
escape_codes() {
|
||||
if(typeof process !== "undefined" && typeof process.env.NO_COLOR == "string") {
|
||||
this.enabled = false;
|
||||
return;
|
||||
}
|
||||
// Solution on how to output ANSI escape codes in C# from here:
|
||||
// https://www.jerriepelser.com/blog/using-ansi-color-codes-in-net-console-apps
|
||||
this.reset = this.enabled ? "\u001b[0m" : "";
|
||||
this.hicol = this.enabled ? "\u001b[1m" : "";
|
||||
this.locol = this.enabled ? "\u001b[2m" : "";
|
||||
this.italics = this.enabled ? "\u001b[3m" : "";
|
||||
this.underline = this.enabled ? "\u001b[4m" : "";
|
||||
this.inverse = this.enabled ? "\u001b[7m" : "";
|
||||
this.fblack = this.enabled ? "\u001b[30m" : "";
|
||||
this.fred = this.enabled ? "\u001b[31m" : "";
|
||||
this.fgreen = this.enabled ? "\u001b[32m" : "";
|
||||
this.fyellow = this.enabled ? "\u001b[33m" : "";
|
||||
this.fblue = this.enabled ? "\u001b[34m" : "";
|
||||
this.fmagenta = this.enabled ? "\u001b[35m" : "";
|
||||
this.fcyan = this.enabled ? "\u001b[36m" : "";
|
||||
this.fwhite = this.enabled ? "\u001b[37m" : "";
|
||||
this.bblack = this.enabled ? "\u001b[40m" : "";
|
||||
this.bred = this.enabled ? "\u001b[41m" : "";
|
||||
this.bgreen = this.enabled ? "\u001b[42m" : "";
|
||||
this.byellow = this.enabled ? "\u001b[43m" : "";
|
||||
this.bblue = this.enabled ? "\u001b[44m" : "";
|
||||
this.bmagenta = this.enabled ? "\u001b[45m" : "";
|
||||
this.bcyan = this.enabled ? "\u001b[46m" : "";
|
||||
this.bwhite = this.enabled ? "\u001b[47m" : "";
|
||||
}
|
||||
|
||||
// Thanks to http://ascii-table.com/ansi-escape-sequences.php for the following ANSI escape sequences
|
||||
up(lines = 1) {
|
||||
return this.enabled ? `\u001b[${lines}A` : "";
|
||||
}
|
||||
down(lines = 1) {
|
||||
return this.enabled ? `\u001b[${lines}B` : "";
|
||||
}
|
||||
right(lines = 1) {
|
||||
return this.enabled ? `\u001b[${lines}C` : "";
|
||||
}
|
||||
left(lines = 1) {
|
||||
return this.enabled ? `\u001b[${lines}D` : "";
|
||||
}
|
||||
|
||||
jump_to(x, y) {
|
||||
return this.enabled ? `\u001b[${y};${x}H` : "";
|
||||
}
|
||||
|
||||
cursor_pos_save() {
|
||||
return this.enabled ? `\u001b[s` : "";
|
||||
}
|
||||
cursor_pos_restore() {
|
||||
return this.enabled ? `\u001b[u` : "";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Ansi();
|
155
.docs/lib/HTMLPicture.js
Normal file
155
.docs/lib/HTMLPicture.js
Normal file
@ -0,0 +1,155 @@
|
||||
"use strict";
|
||||
|
||||
const os = require(`os`);
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const pretty_ms = require("pretty-ms");
|
||||
const debug = require("debug")("image");
|
||||
const imagickal = require("imagickal");
|
||||
const htmlentities = require("html-entities");
|
||||
|
||||
const a = require("./Ansi.js");
|
||||
|
||||
function calculate_size(width, height, size_spec) {
|
||||
if(size_spec.indexOf("%") > -1) {
|
||||
// It's a percentage
|
||||
const multiplier = parseInt(size_spec.replace(/%/, ""), 10) / 100;
|
||||
return {
|
||||
width: Math.ceil(width * multiplier),
|
||||
height: Math.ceil(height * multiplier)
|
||||
};
|
||||
}
|
||||
else {
|
||||
// It's an absolute image width
|
||||
const new_width = parseInt(size_spec, 10);
|
||||
return {
|
||||
width: new_width,
|
||||
height: Math.ceil(new_width/width * height)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Main task list - we make sure it completes before exiting.
|
||||
var queue = null;
|
||||
var pMemoize = null;
|
||||
|
||||
async function make_queue() {
|
||||
// 1: Setup task queue
|
||||
const PQueue = (await import("p-queue")).default;
|
||||
let concurrency = os.cpus().length;
|
||||
if(process.env["MAX_CONCURRENT"])
|
||||
concurrency = parseInt(process.env["MAX_CONCURRENT"], 10);
|
||||
debug(`Image conversion queue concurrency: `, concurrency);
|
||||
queue = new PQueue({ concurrency });
|
||||
queue.on("idle", () => console.log(`IMAGE ${a.fcyan}all conversions complete${a.reset}`));
|
||||
process.on("exit", async () => {
|
||||
debug(`Waiting for image conversions to finish...`);
|
||||
await queue.onEmpty();
|
||||
debug(`All image conversions complete.`);
|
||||
});
|
||||
}
|
||||
|
||||
async function srcset(source_image, target_dir, urlpath, format = "__AUTO__", sizes = [ "25%", "50%", "100%" ], quality = 95, strip = true) {
|
||||
if(queue === null) await make_queue();
|
||||
|
||||
const source_parsed = path.parse(source_image);
|
||||
// ext contains the dot . already
|
||||
const target_format = format == "__AUTO__" ? source_parsed.ext.replace(/\./g, "") : format;
|
||||
|
||||
const source_size = await imagickal.dimensions(source_image);
|
||||
|
||||
debug(`SOURCE_SIZE`, source_size, `TARGET_FORMAT`, target_format);
|
||||
|
||||
let setitems = await Promise.all(sizes.map(async (size) => {
|
||||
let target_filename = `${source_parsed.name}_${size}.${target_format}`
|
||||
.replace(/%/, "pcent");
|
||||
let target_current = path.join(
|
||||
target_dir,
|
||||
target_filename
|
||||
);
|
||||
queue.add(async () => {
|
||||
const start = new Date();
|
||||
await imagickal.transform(source_image, target_current, {
|
||||
resize: { width: size },
|
||||
quality,
|
||||
strip
|
||||
});
|
||||
console.log(`IMAGE\t${a.fcyan}${queue.size}/${queue.pending} tasks${a.reset}\t${a.fyellow}${pretty_ms(new Date() - start)}${a.reset}\t${a.fgreen}${target_current}${a.reset}`);
|
||||
});
|
||||
// const size_target = await imagickal.dimensions(target_current);
|
||||
|
||||
const predict = calculate_size(source_size.width, source_size.height, size);
|
||||
// debug(`size spec:`, size, `size predicted: ${predict.width}x${predict.height} actual: ${size_target.width}x${size_target.height}`);
|
||||
return `${path.resolve(urlpath, target_filename)} ${predict.width}w`;
|
||||
}));
|
||||
|
||||
return setitems.join(", ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string of HTML for a <picture> element, converting images to the specified formats in the process.
|
||||
* @param {string} source_image The filepath to the source image.
|
||||
* @param {string} alt The alt (alternative) text. Automatically run though htmlentities.
|
||||
* @param {string} target_dir The target directory to save converted images to.
|
||||
* @param {string} urlpath The path to the aforementionoed target directory as a URL. Image paths in the HTML will be prefixed with this value.
|
||||
* @param {string} [formats="__AUTO__"] A list of formats to convert the source image to. Defaults to automatically determining the most optimal formats based on the input format. [must be lowercase]
|
||||
* @param {Array} [sizes=["25%","50%", "100%" ]] The sizes, as imagemagick size specs, to convert the source image to.
|
||||
* @param {Number} [quality=95] The quality value to use when converting images.
|
||||
* @param {Boolean} [strip=true] Whether to strip all metadata from images when converting them [saves some space]
|
||||
* @return {Promise<string>} A Promise that returns a generated string of HTML.
|
||||
*/
|
||||
async function picture(source_image, alt, target_dir, urlpath, formats = "__AUTO__", sizes = [ "25%", "50%", "100%" ], quality = 95, strip = true) {
|
||||
const source_parsed = path.parse(source_image);
|
||||
const source_format = source_parsed.ext.toLowerCase().replace(".", "");
|
||||
|
||||
if(formats == "__AUTO__") {
|
||||
switch(source_format) {
|
||||
case "png":
|
||||
case "gif": // * shudder *
|
||||
case "bmp":
|
||||
formats = [ "png" ];
|
||||
break;
|
||||
default:
|
||||
// jxl = JPEG XL <https://jpegxl.info/> - not currently supported by the old version of imagemagick shipped via apt :-/
|
||||
// Imagemagick v7+ does support it, but isn't shipped yet :-(
|
||||
formats = [ "jpeg", "webp", "avif", /*"jxl"*/ ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const target_original = path.join(target_dir, source_parsed.base);
|
||||
await fs.promises.copyFile(source_image, target_original);
|
||||
|
||||
const sources = await Promise.all(formats.map(async (format) => {
|
||||
debug(`${format} ${source_image}`);
|
||||
|
||||
return {
|
||||
mime: `image/${format}`,
|
||||
srcset: await srcset(
|
||||
source_image,
|
||||
target_dir, urlpath,
|
||||
format, sizes,
|
||||
quality, strip
|
||||
)
|
||||
};
|
||||
}));
|
||||
|
||||
let result = `<picture>\n\t`;
|
||||
result += sources.map(source => `<source srcset="${source.srcset}" type="${source.mime}" />`).join(`\n\t`);
|
||||
result += `\n\t<img loading="lazy" decoding="async" src="${urlpath}/${source_parsed.base}" alt="${htmlentities.encode(alt)}" />\n`;
|
||||
result += `</picture>\n`
|
||||
return result;
|
||||
}
|
||||
|
||||
var picture_memoize = null;
|
||||
|
||||
async function setup_memoize() {
|
||||
const pMemoize = (await import("p-memoize")).default;
|
||||
picture_memoize = pMemoize(picture);
|
||||
}
|
||||
|
||||
module.exports = async function(...args) {
|
||||
if(picture_memoize === null) await setup_memoize();
|
||||
return await picture_memoize(...args);
|
||||
};
|
@ -1,3 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const crypto = require("crypto");
|
||||
|
||||
const htmlentities = require("html-entities");
|
||||
@ -6,7 +8,6 @@ const markdown = require("markdown-it")({
|
||||
});
|
||||
const chroma = require("chroma-js");
|
||||
|
||||
|
||||
const markdown_prism = require("markdown-it-prism");
|
||||
markdown.use(markdown_prism, {
|
||||
init: (Prism) => {
|
||||
|
1776
.docs/package-lock.json
generated
1776
.docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,10 +21,15 @@
|
||||
"homepage": "https://github.com/sbrl/Minetest-WorldEditAdditions#readme",
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^0.12.1",
|
||||
"@11ty/eleventy-img": "^0.10.0",
|
||||
"chroma-js": "^2.1.2",
|
||||
"columnify": "^1.5.4",
|
||||
"debug": "^4.3.2",
|
||||
"imagickal": "^5.0.1",
|
||||
"markdown-it-prism": "^2.2.1",
|
||||
"phin": "^3.6.0"
|
||||
"p-memoize": "^6.0.1",
|
||||
"p-queue": "^7.1.0",
|
||||
"phin": "^3.6.0",
|
||||
"pretty-ms": "^7.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"html-entities": "^2.3.2"
|
||||
|
@ -6,6 +6,7 @@ exclude_files = {
|
||||
"worldeditadditions/utils/bit.lua"
|
||||
}
|
||||
|
||||
files["worldeditadditions_core/register/check.lua"] = { read_globals = { "table" } }
|
||||
|
||||
ignore = {
|
||||
"631", "61[124]",
|
||||
|
@ -12,7 +12,9 @@ Note to self: See the bottom of this file for the release template text.
|
||||
- Add `//sshift` (_selection shift_) - WorldEdit cuboid manipulator replacements implemented by @VorTechnix.
|
||||
- Add `//noise2d` for perturbing terrain with multiple different noise functions
|
||||
- Add `//noiseapply2d` for running commands on columns where a noise value is over a threshold
|
||||
- Use [luacheck](https://github.com/mpeterv/luacheck) to find and fix a large number of bugs and other issues
|
||||
- Add `//ellipsoid2` which creates an ellipsoid that fills the defined region
|
||||
- Add `//spiral2` for creating both square and circular spirals
|
||||
- Use [luacheck](https://github.com/mpeterv/luacheck) to find and fix a large number of bugs and other issues [code quality from now on will be significantly improved]
|
||||
- Multiple commands: Allow using quotes (`"thing"`, `'thing'`) to quote values when splitting
|
||||
- `//layers`: Add optional slope constraint (inspired by [WorldPainter](https://worldpainter.net/))
|
||||
- `//bonemeal`: Add optional node list constraint
|
||||
@ -31,6 +33,8 @@ Note to self: See the bottom of this file for the release template text.
|
||||
- Add `holly` ⇒ `hollytree:sapling`
|
||||
- `//replacemix`: Improve error handling to avoid crashes (thanks, Jonathon for reporting via Discord!)
|
||||
- Cloud wand: Improve chat message text
|
||||
- Fix `bonemeal` mod detection to look for the global `bonemeal`, not whether the `bonemeal` mod name has been loaded
|
||||
- `//walls`: Prevent crash if not parameters are specified by defaulting to `dirt` as the replace_node
|
||||
|
||||
|
||||
## v1.12: The selection tools update (26th June 2021)
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
This is the full chat command reference for WorldEditAdditions. Having trouble finding the section you want? Try the **[quick reference](https://github.com/sbrl/Minetest-WorldEditAdditions#quick-command-reference)** instead, which has links to back to sections of this document!
|
||||
|
||||
Check out the reference on the new website - it's even searchable: <https://worldeditadditions.mooncarrot.space/Reference/>
|
||||
**Check out the reference on the new website - it's even searchable: <https://worldeditadditions.mooncarrot.space/Reference/>**
|
||||
|
||||
Other useful links:
|
||||
|
||||
@ -42,6 +42,18 @@ Creates a hollow ellipsoid at position 1 with the radius `(rx, ry, rz)`. Works t
|
||||
//hollowellipsoid 21 11 41 stone
|
||||
```
|
||||
|
||||
### `//ellipsoid2 [<node_name:dirt> [h[ollow]]]`
|
||||
Creates an (optionally hollow) solid ellipsoid that fills the defined region.
|
||||
|
||||
```weacmd
|
||||
//ellipsoid2
|
||||
//ellipsoid2 ice
|
||||
//ellipsoid2 air
|
||||
//ellipsoid2 steelblock h
|
||||
//ellipsoid2 papyrus hollow
|
||||
```
|
||||
|
||||
|
||||
### `//torus <major_radius> <minor_radius> <node_name> [<axes=xy> [h[ollow]]]`
|
||||
Creates a solid torus at position 1 with the specified major and minor radii. The major radius is the distance from the centre of the torus to the centre of the circle bit, and the minor radius is the radius of the circle bit.
|
||||
|
||||
@ -134,10 +146,11 @@ Additional examples:
|
||||
```
|
||||
|
||||
|
||||
### `//walls <replace_node> [<thickness=1>]`
|
||||
Creates vertical walls of `<replace_node>` around the inside edges of the defined region, optionally specifying the thickness thereof.
|
||||
### `//walls [<replace_node=dirt> [<thickness=1>]]`
|
||||
Creates vertical walls of `<replace_node>` around the inside edges of the defined region, optionally specifying the thickness thereof. Defaults to a replace node of `dirt` and a wall thickness of 1.
|
||||
|
||||
```weacmd
|
||||
//walls
|
||||
//walls dirt
|
||||
//walls stone
|
||||
//walls goldblock
|
||||
@ -146,6 +159,19 @@ Creates vertical walls of `<replace_node>` around the inside edges of the define
|
||||
```
|
||||
|
||||
|
||||
### `//spiral2 [<circle|square>] [<replace_node=dirt> [<interval=3> [<acceleration=0>] ] ]`
|
||||
Generates both square and circular spiral shapes with the given `<replace_node>` - defaulting to square spirals. The interval defines the gap between the spiral in nodes, and the acceleration defines by how much the interval should be increased (a value of 1 means 1 node per revolution).
|
||||
|
||||
```
|
||||
//spiral2
|
||||
//spiral2 circle stone
|
||||
//spiral2 square
|
||||
//spiral2 circle
|
||||
//spiral2 glass 5
|
||||
//spiral2 square desert_sand 3 1
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Misc
|
||||
<!--
|
||||
@ -158,7 +184,7 @@ Creates vertical walls of `<replace_node>` around the inside edges of the define
|
||||
|
||||
|
||||
### `//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
|
||||
|
@ -29,6 +29,7 @@ The detailed explanations have moved! Check them out [here](https://github.com/s
|
||||
|
||||
### Geometry
|
||||
- [`//ellipsoid <rx> <ry> <rz> <node_name> [h[ollow]]`](https://worldeditadditions.mooncarrot.space/Reference/#ellipsoid)
|
||||
- [`//ellipsoid2 <node_name> [h[ollow]]`](https://worldeditadditions.mooncarrot.space/Reference/#ellipsoid2)
|
||||
- [`//hollowellipsoid <rx> <ry> <rz> <node_name>`](https://worldeditadditions.mooncarrot.space/Reference/#hollowellipsoid)
|
||||
- [`//torus <major_radius> <minor_radius> <node_name> [<axes=xy> [h[ollow]]]`](https://worldeditadditions.mooncarrot.space/Reference/#torus)
|
||||
- [`//hollowtorus <major_radius> <minor_radius> <node_name> [<axes=xy>]`](https://worldeditadditions.mooncarrot.space/Reference/#hollowtorus)
|
||||
@ -37,6 +38,7 @@ The detailed explanations have moved! Check them out [here](https://github.com/s
|
||||
- [`//hollow [<wall_thickness>]`](https://worldeditadditions.mooncarrot.space/Reference/#hollow)
|
||||
- [`//maze <replace_node> [<path_length> [<path_width> [<seed>]]]`](https://worldeditadditions.mooncarrot.space/Reference/#maze)
|
||||
- [`//maze3d <replace_node> [<path_length> [<path_width> [<path_depth> [<seed>]]]]`](https://worldeditadditions.mooncarrot.space/Reference/#maze3d)
|
||||
- [`//spiral2 [<circle|square>] [<replace_node=dirt> [<interval=3> [<acceleration=0>] ] ]`](https://worldeditadditions.mooncarrot.space/Reference/#spiral2)
|
||||
|
||||
### Misc
|
||||
- [`//replacemix <target_node> [<chance>] <replace_node_a> [<chance_a>] [<replace_node_b> [<chance_b>]] [<replace_node_N> [<chance_N>]] ....`](https://worldeditadditions.mooncarrot.space/Reference/#replacemix)
|
||||
|
@ -41,6 +41,7 @@ dofile(wea.modpath.."/lib/overlay.lua")
|
||||
dofile(wea.modpath.."/lib/layers.lua")
|
||||
dofile(wea.modpath.."/lib/fillcaves.lua")
|
||||
dofile(wea.modpath.."/lib/ellipsoid.lua")
|
||||
dofile(wea.modpath.."/lib/ellipsoid2.lua")
|
||||
dofile(wea.modpath.."/lib/torus.lua")
|
||||
dofile(wea.modpath.."/lib/line.lua")
|
||||
dofile(wea.modpath.."/lib/walls.lua")
|
||||
@ -51,10 +52,14 @@ dofile(wea.modpath.."/lib/hollow.lua")
|
||||
dofile(wea.modpath.."/lib/scale_up.lua")
|
||||
dofile(wea.modpath.."/lib/scale_down.lua")
|
||||
dofile(wea.modpath.."/lib/scale.lua")
|
||||
dofile(wea.modpath.."/lib/spiral_square.lua")
|
||||
dofile(wea.modpath.."/lib/spiral_circle.lua")
|
||||
dofile(wea.modpath.."/lib/conv/conv.lua")
|
||||
dofile(wea.modpath.."/lib/erode/erode.lua")
|
||||
dofile(wea.modpath.."/lib/noise/init.lua")
|
||||
|
||||
dofile(wea.modpath.."/lib/copy.lua")
|
||||
|
||||
dofile(wea.modpath.."/lib/count.lua")
|
||||
|
||||
dofile(wea.modpath.."/lib/bonemeal.lua")
|
||||
|
47
worldeditadditions/lib/copy.lua
Normal file
47
worldeditadditions/lib/copy.lua
Normal file
@ -0,0 +1,47 @@
|
||||
--- Copies a region to another location, potentially overwriting the exiting region.
|
||||
-- @module worldeditadditions.copy
|
||||
|
||||
local wea = worldeditadditions
|
||||
local Vector3 = wea.Vector3
|
||||
|
||||
-- ██████ ██████ ██████ ██ ██
|
||||
-- ██ ██ ██ ██ ██ ██ ██
|
||||
-- ██ ██ ██ ██████ ████
|
||||
-- ██ ██ ██ ██ ██
|
||||
-- ██████ ██████ ██ ██
|
||||
|
||||
function worldeditadditions.count(source_pos1, source_pos2, target_pos1, target_pos2)
|
||||
source_pos1, source_pos2 = Vector3.sort(source_pos1, source_pos2)
|
||||
target_pos1, target_pos2 = Vector3.sort(target_pos1, target_pos2)
|
||||
|
||||
local offset = source_pos1:subtract(target_pos1)
|
||||
-- {source,target}_pos2 will always have the highest co-ordinates now
|
||||
|
||||
-- Fetch the nodes in the source area
|
||||
local manip_source, area_source = worldedit.manip_helpers.init(source_pos1, source_pos2)
|
||||
local data_source = manip_source:get_data()
|
||||
|
||||
-- Fetch a manip for the target area
|
||||
local manip_target, area_target = worldedit.manip_helpers.init(target_pos1, target_pos2)
|
||||
local data_target = manip_target:get_data()
|
||||
|
||||
-- z y x is the preferred loop order (because CPU cache I'd guess, since then we're iterating linearly through the data array)
|
||||
|
||||
for z = source_pos2.z, source_pos1.z, -1 do
|
||||
for y = source_pos2.y, source_pos1.y, -1 do
|
||||
for x = source_pos2.x, source_pos1.x, -1 do
|
||||
local source = Vector3.new(x, y, z)
|
||||
local source_i = area_source:index(x, y, z)
|
||||
local target = source:subtract(offset)
|
||||
local target_i = area_target:index(target.x, target.y, target.z)
|
||||
|
||||
data_target[target_i] = data_source[source_i]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Save the modified nodes back to disk & return
|
||||
worldedit.manip_helpers.finish(manip_target, data_target)
|
||||
|
||||
return true, worldedit.volume(target_pos1, target_pos2)
|
||||
end
|
@ -4,6 +4,14 @@
|
||||
-- ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ███████ ███████ ██ ██ ███████ ███████
|
||||
|
||||
--- Fills an ellipsoidal area around the given position with the target node.
|
||||
-- The resulting ellipsoid may optionally be hollow (in which case
|
||||
-- nodes inside the ellipsoid are left untouched).
|
||||
-- @param position Vector3 The centre position of the ellipsoid.
|
||||
-- @param radius Vector3 The radius of the ellipsoid in all 3 dimensions.
|
||||
-- @param target_node string The name of the node to use to fill the ellipsoid.
|
||||
-- @param hollow bool Whether the ellipsoid should be hollow or not.
|
||||
-- @returns number The number of nodes filled to create the (optionally hollow) ellipsoid. This number will be lower with hollow ellipsoids, since the internals of an ellipsoid aren't altered.
|
||||
function worldeditadditions.ellipsoid(position, radius, target_node, hollow)
|
||||
-- position = { x, y, z }
|
||||
local hollow_inner_radius = {
|
||||
@ -18,7 +26,6 @@ function worldeditadditions.ellipsoid(position, radius, target_node, hollow)
|
||||
local data = manip:get_data()
|
||||
|
||||
local node_id = minetest.get_content_id(target_node)
|
||||
local node_id_air = minetest.get_content_id("air")
|
||||
|
||||
local stride_z, stride_y = area.zstride, area.ystride
|
||||
|
||||
|
75
worldeditadditions/lib/ellipsoid2.lua
Normal file
75
worldeditadditions/lib/ellipsoid2.lua
Normal file
@ -0,0 +1,75 @@
|
||||
local wea = worldeditadditions
|
||||
|
||||
-- ███████ ██ ██ ██ ██████ ███████ ███████
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- █████ ██ ██ ██ ██████ ███████ █████
|
||||
-- ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ███████ ███████ ██ ██ ███████ ███████
|
||||
|
||||
|
||||
function worldeditadditions.ellipsoid2(pos1, pos2, target_node, hollow)
|
||||
pos1, pos2 = wea.Vector3.sort(pos1, pos2)
|
||||
local volume = pos2:subtract(pos1)
|
||||
local volume_half = volume:divide(2)
|
||||
|
||||
local radius = pos2:subtract(pos1):divide(2)
|
||||
|
||||
print("DEBUG:ellipsoid2 | pos1: "..pos1..", pos2: "..pos2..", target_node: "..target_node)
|
||||
print("DEBUG:ellipsoid2 radius", radius)
|
||||
|
||||
-- position = { x, y, z }
|
||||
local hollow_inner_radius = {
|
||||
x = radius.x - 1,
|
||||
y = radius.y - 1,
|
||||
z = radius.z - 1
|
||||
}
|
||||
|
||||
-- Fetch the nodes in the specified area
|
||||
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
|
||||
local data = manip:get_data()
|
||||
|
||||
local node_id = minetest.get_content_id(target_node)
|
||||
|
||||
local count = 0 -- The number of nodes replaced
|
||||
|
||||
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 pos_relative = wea.Vector3.new(x, y, z):subtract(pos1)
|
||||
:subtract(volume_half)
|
||||
|
||||
print("DEBUG pos1", pos1, "pos2", pos2, "volume_half", volume_half, "pos_relative", pos_relative)
|
||||
|
||||
-- If we're inside the ellipse, then fill it in
|
||||
local comp = pos_relative:divide(radius)
|
||||
local ellipsoid_dist = comp:length_squared()
|
||||
if ellipsoid_dist <= 1 then
|
||||
local place_ok = not hollow;
|
||||
|
||||
if not place_ok then
|
||||
-- It must be hollow! Do some additional calculations.
|
||||
local hx_comp = x/hollow_inner_radius.x
|
||||
local hy_comp = y/hollow_inner_radius.y
|
||||
local hz_comp = z/hollow_inner_radius.z
|
||||
|
||||
-- It's only ok to place it if it's outside our inner ellipse
|
||||
place_ok = hx_comp*hx_comp + hy_comp*hy_comp + hz_comp*hz_comp >= 1
|
||||
end
|
||||
|
||||
if place_ok then
|
||||
data[area:index(x, y, z)] = node_id
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Save the modified nodes back to disk & return
|
||||
worldedit.manip_helpers.finish(manip, data)
|
||||
|
||||
return count
|
||||
end
|
86
worldeditadditions/lib/spiral_circle.lua
Normal file
86
worldeditadditions/lib/spiral_circle.lua
Normal file
@ -0,0 +1,86 @@
|
||||
local wea = worldeditadditions
|
||||
local Vector3 = wea.Vector3
|
||||
|
||||
-- ███████ ██████ ██ ██████ █████ ██
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ██████ ██ ██████ ███████ ██
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ██ ██ ██ ██ ██ ██ ███████
|
||||
--
|
||||
-- ██████ ██ ██████ ██████ ██ ███████
|
||||
-- ██ ██ ██ ██ ██ ██ ██
|
||||
-- ██ ██ ██████ ██ ██ █████
|
||||
-- ██ ██ ██ ██ ██ ██ ██
|
||||
-- ██████ ██ ██ ██ ██████ ███████ ███████
|
||||
|
||||
|
||||
--- Creates a circular spiral that fills the defined region.
|
||||
-- @param pos1 Vector3 The 1st position of the defined region.
|
||||
-- @param pos2 Vector3 The 2nd position of the defined region.
|
||||
-- @param target_node Vector3 The *normalised* name of the node to use to build the square spiral with.
|
||||
-- @param interval_initial number The distance between the walls of the spiral.
|
||||
-- @param acceleration=0 number Increate the interval by this number every time we hit a corner of the square spiral.
|
||||
-- @returns bool,number|string A success boolean value, followed by either the number of the nodes set or an error message string.
|
||||
function worldeditadditions.spiral_circle(pos1, pos2, target_node, interval_initial, acceleration)
|
||||
if not acceleration then acceleration = 0 end
|
||||
|
||||
pos1, pos2 = Vector3.sort(pos1, pos2)
|
||||
local volume = pos2:subtract(pos1)
|
||||
local volume_half = volume:divide(2)
|
||||
|
||||
print("DEBUG:spiral_square | pos1", pos1, "pos2", pos2, "target_node", target_node, "interval_initial:", interval_initial, "acceleration", acceleration)
|
||||
|
||||
interval_initial = interval_initial + 1
|
||||
|
||||
-- Fetch the nodes in the specified area
|
||||
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
|
||||
local data = manip:get_data()
|
||||
|
||||
local node_id = minetest.get_content_id(target_node)
|
||||
|
||||
local count = 0 -- The number of nodes replaced
|
||||
|
||||
local centre = pos2:subtract(pos1):floor():divide(2):add(pos1)
|
||||
|
||||
local pos_current = centre:clone():floor()
|
||||
local interval = interval_initial
|
||||
local radius = 1
|
||||
local angle = 0
|
||||
-- local sides_acc = 0
|
||||
|
||||
while pos_current:is_contained(pos1, pos2) do
|
||||
|
||||
for y = pos2.y, pos1.y, -1 do
|
||||
data[area:index(
|
||||
math.floor(pos_current.x),
|
||||
y,
|
||||
math.floor(pos_current.z)
|
||||
)] = node_id
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
-- print("DEBUG:spiral_circle centre", centre, "bearing", Vector3.fromBearing(angle, 0, radius))
|
||||
pos_current = centre:add(Vector3.fromBearing(angle, 0, radius))
|
||||
|
||||
local circumference_now = 2 * math.pi * radius
|
||||
local step = (math.pi*2)/(circumference_now*2)
|
||||
if angle < math.pi then step = step / 10 end
|
||||
angle = angle + (step)
|
||||
|
||||
local acceleration_constant = 0
|
||||
if angle > math.pi / 2 then
|
||||
acceleration_constant = (interval/angle * acceleration) * step
|
||||
end
|
||||
radius = 1 + math.floor(interval*(angle / (math.pi*2)), 0)
|
||||
interval = interval_initial + acceleration_constant
|
||||
|
||||
|
||||
print("DEBUG cpos", pos_current:multiply(1000):floor():divide(1000), "angle", math.deg(angle), "step", wea.round(math.deg(step), 3), "radius", wea.round(radius, 3), "interval", wea.round(interval, 3), "accel_const", acceleration_constant)
|
||||
|
||||
end
|
||||
|
||||
-- Save the modified nodes back to disk & return
|
||||
worldedit.manip_helpers.finish(manip, data)
|
||||
|
||||
return true, count
|
||||
end
|
92
worldeditadditions/lib/spiral_square.lua
Normal file
92
worldeditadditions/lib/spiral_square.lua
Normal file
@ -0,0 +1,92 @@
|
||||
local wea = worldeditadditions
|
||||
local Vector3 = wea.Vector3
|
||||
|
||||
-- ███████ ██████ ██ ██████ █████ ██
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ██████ ██ ██████ ███████ ██
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ██ ██ ██ ██ ██ ██ ███████
|
||||
--
|
||||
-- ███████ ██████ ██ ██ █████ ██████ ███████
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ██ ██ ██ ██ ███████ ██████ █████
|
||||
-- ██ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ██████ ██████ ██ ██ ██ ██ ███████
|
||||
-- ▀▀
|
||||
|
||||
--- Creates a square spiral that fills the defined region.
|
||||
-- @param pos1 Vector3 The 1st position of the defined region.
|
||||
-- @param pos2 Vector3 The 2nd position of the defined region.
|
||||
-- @param target_node Vector3 The *normalised* name of the node to use to build the square spiral with.
|
||||
-- @param interval number The distance between the walls of the spiral.
|
||||
-- @param acceleration=0 number Increate the interval by this number every time we hit a corner of the square spiral.
|
||||
-- @returns bool,number|string A success boolean value, followed by either the number of the nodes set or an error message string.
|
||||
function worldeditadditions.spiral_square(pos1, pos2, target_node, interval, acceleration)
|
||||
if not acceleration then acceleration = 0 end
|
||||
|
||||
pos1, pos2 = Vector3.sort(pos1, pos2)
|
||||
local volume = pos2:subtract(pos1)
|
||||
local volume_half = volume:divide(2)
|
||||
|
||||
print("DEBUG:spiral_square | pos1: "..pos1..", pos2: "..pos2..", target_node: "..target_node, "interval:"..interval..", acceleration: "..acceleration)
|
||||
|
||||
|
||||
-- Fetch the nodes in the specified area
|
||||
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
|
||||
local data = manip:get_data()
|
||||
|
||||
local node_id = minetest.get_content_id(target_node)
|
||||
|
||||
local count = 0 -- The number of nodes replaced
|
||||
|
||||
local centre = pos2:subtract(pos1):floor():divide(2):add(pos1)
|
||||
|
||||
local pos_current = centre:clone():floor()
|
||||
local side_length = 0
|
||||
local direction = Vector3.new(1, 0, 0)
|
||||
local side_length_max = interval + 1
|
||||
local sides_complete = 0
|
||||
-- local sides_acc = 0
|
||||
|
||||
while pos_current:is_contained(pos1, pos2) do
|
||||
|
||||
for y = pos2.y, pos1.y, -1 do
|
||||
data[area:index(pos_current.x, y, pos_current.z)] = node_id
|
||||
count = count + 1
|
||||
end
|
||||
|
||||
pos_current = pos_current:add(direction)
|
||||
side_length = side_length + 1
|
||||
|
||||
print("DEBUG cpos", pos_current, "side_length", side_length, "side_length_max", side_length_max, "direction", direction)
|
||||
|
||||
if side_length >= side_length_max then
|
||||
sides_complete = sides_complete + 1
|
||||
-- sides_acc = sides_acc + 1
|
||||
if sides_complete % 2 == 0 then
|
||||
-- sides_acc = 0
|
||||
side_length_max = side_length_max + interval + acceleration + 1
|
||||
end
|
||||
side_length = 0
|
||||
|
||||
if direction.x == 0 and direction.z == 1 then
|
||||
direction.x = 1
|
||||
direction.z = 0
|
||||
elseif direction.x == 1 and direction.z == 0 then
|
||||
direction.x = 0
|
||||
direction.z = -1
|
||||
elseif direction.x == 0 and direction.z == -1 then
|
||||
direction.x = -1
|
||||
direction.z = 0
|
||||
else
|
||||
direction.x = 0
|
||||
direction.z = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Save the modified nodes back to disk & return
|
||||
worldedit.manip_helpers.finish(manip, data)
|
||||
|
||||
return true, count
|
||||
end
|
@ -21,17 +21,17 @@ function worldeditadditions.wire_box(pos1,pos2,node)
|
||||
local counts = { replaced = 0 }
|
||||
|
||||
for z = ps1.z,ps2.z do
|
||||
for k,y in pairs({pos1.y,pos2.y}) do
|
||||
for k,x in pairs({pos1.x,pos2.x}) do
|
||||
for _j,y in pairs({pos1.y,pos2.y}) do
|
||||
for _k,x in pairs({pos1.x,pos2.x}) do
|
||||
data[area:index(x, y, z)] = node_id_replace
|
||||
counts.replaced = counts.replaced + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if math.abs(ps2.y-ps1.y) > 1 then
|
||||
for k,z in pairs({pos1.z,pos2.z}) do
|
||||
for _j,z in pairs({pos1.z,pos2.z}) do
|
||||
for y = pos1.y+1,pos2.y-1 do
|
||||
for k,x in pairs({pos1.x,pos2.x}) do
|
||||
for _k,x in pairs({pos1.x,pos2.x}) do
|
||||
data[area:index(x, y, z)] = node_id_replace
|
||||
counts.replaced = counts.replaced + 1
|
||||
end
|
||||
@ -39,8 +39,8 @@ function worldeditadditions.wire_box(pos1,pos2,node)
|
||||
end
|
||||
end
|
||||
if math.abs(ps2.x-ps1.x) > 1 then
|
||||
for k,z in pairs({pos1.z,pos2.z}) do
|
||||
for k,y in pairs({pos1.y,pos2.y}) do
|
||||
for _j,z in pairs({pos1.z,pos2.z}) do
|
||||
for _k,y in pairs({pos1.y,pos2.y}) do
|
||||
for x = pos1.x+1,pos2.x-1 do
|
||||
data[area:index(x, y, z)] = node_id_replace
|
||||
counts.replaced = counts.replaced + 1
|
||||
|
55
worldeditadditions/utils/parse/axes.lua
Normal file
55
worldeditadditions/utils/parse/axes.lua
Normal file
@ -0,0 +1,55 @@
|
||||
local wea = worldeditadditions
|
||||
local Vector3 = dofile(wea.modpath.."/utils/vector3.lua")
|
||||
-- BUG: This does not exist yet - need to merge @VorTechnix's branch first to get it
|
||||
-- TODO: Uncomment then once it's implemented
|
||||
-- local parse_axis = dofile(wea.modpath.."/utils/axis.lua")
|
||||
|
||||
--- Parses a token list of axes and counts into a Vector3.
|
||||
-- For example, "x 4" would become { x = 4, y = 0, z = 0 }, and "? 4 -z 10"
|
||||
-- might become { x = 4, y = 0, z = -10 }.
|
||||
-- Note that the input here needs to be *pre split*. wea.split_shell is
|
||||
-- recommended for this purpose.
|
||||
-- Uses wea.parse.axis for parsing axis names.
|
||||
-- @param token_list string[] A list of tokens to parse
|
||||
-- @returns Vector3 A Vector3 generated from parsing out the input token list.
|
||||
local function parse_axes(token_list)
|
||||
local vector_result = wea.Vector3.new()
|
||||
|
||||
if #token_list < 2 then
|
||||
return false, "Error: Not enough arguments (at least 2 are required)"
|
||||
end
|
||||
|
||||
local state = "AXIS"
|
||||
local current_axis = nil
|
||||
local success
|
||||
|
||||
for i,token in ipairs(token_list) do
|
||||
if state == "AXIS" then
|
||||
success, current_axis = parse_axis(token)
|
||||
if not success then return success, current_axis end
|
||||
state = "VALUE"
|
||||
elseif state == "VALUE" then
|
||||
|
||||
local offset_this = tonumber(token)
|
||||
if not offset_this then
|
||||
return false, "Error: Invalid count value for axis '"..current_axis.."'. Values may only be positive or negative integers."
|
||||
end
|
||||
|
||||
-- Handle negative axes
|
||||
if current_axis:sub(1, 1) == "-" then
|
||||
offset_this = -offset_this
|
||||
current_axis = current_axis:sub(2, 2)
|
||||
end
|
||||
|
||||
vector_result[current_axis] = vector_result[current_axis] + offset_this
|
||||
|
||||
state = "AXIS"
|
||||
else
|
||||
return false, "Error: Failed to parse input due to unknown state '"..tostring(state).."' (this is probably a bug - please report it!)"
|
||||
end
|
||||
end
|
||||
|
||||
return vector_result
|
||||
end
|
||||
|
||||
return parse_axes
|
@ -4,7 +4,9 @@
|
||||
-- ██ ██ ██ ██ ██ ██ ██
|
||||
-- ██ ██ ██ ██ ██ ███████ ███████
|
||||
|
||||
worldeditadditions.parse = {}
|
||||
worldeditadditions.parse = {
|
||||
axes = dofile(worldeditadditions.modpath.."/utils/parse/axes.lua")
|
||||
}
|
||||
|
||||
dofile(worldeditadditions.modpath.."/utils/parse/chance.lua")
|
||||
dofile(worldeditadditions.modpath.."/utils/parse/map.lua")
|
||||
|
@ -361,6 +361,22 @@ function Vector3.max(pos1, pos2)
|
||||
)
|
||||
end
|
||||
|
||||
--- Given 2 angles and a length, return a Vector3 pointing in that direction.
|
||||
-- Consider a sphere, with the 2 angles defining a point on the sphere's surface.
|
||||
-- This function returns that point as a Vector3.
|
||||
-- @source https://math.stackexchange.com/a/1881767/221181
|
||||
-- @param angle_x number The X angle.
|
||||
-- @param angle_y number The Y angle.
|
||||
-- @param length number The radius of the sphere in question.
|
||||
-- @returns Vector3 The point on the sphere defined by the aforementioned parameters.
|
||||
function Vector3.fromBearing(angle_x, angle_y, length)
|
||||
return Vector3.new( -- X and Y swapped
|
||||
length * math.cos(angle_x),
|
||||
length * math.sin(angle_x) * math.sin(angle_y),
|
||||
length * math.sin(angle_x) * math.cos(angle_y)
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- ██████ ██████ ███████ ██████ █████ ████████ ██████ ██████
|
||||
|
50
worldeditadditions_commands/commands/copy.lua
Normal file
50
worldeditadditions_commands/commands/copy.lua
Normal file
@ -0,0 +1,50 @@
|
||||
local wea = worldeditadditions
|
||||
-- ██████ ██████ ██████ ██ ██
|
||||
-- ██ ██ ██ ██ ██ ██ ██
|
||||
-- ██ ██ ██ ██████ ████
|
||||
-- ██ ██ ██ ██ ██
|
||||
-- ██████ ██████ ██ ██
|
||||
worldedit.register_command("copy", { -- TODO: Make this an override
|
||||
params = "<axis:x|y|z|-x|-y|-z|?|front|back|left|right|up|down> <count> [<axis> <count> [...]]",
|
||||
description = "Copies the defined region to another location - potentially on multiple axes at once.",
|
||||
privs = { worldedit = true },
|
||||
require_pos = 2,
|
||||
parse = function(params_text)
|
||||
if not params_text then params_text = "" end
|
||||
|
||||
local parts = wea.split_shell(params_text)
|
||||
|
||||
local copy_offset = wea.parse.axes(parts)
|
||||
|
||||
if copy_offset == wea.Vector3.new() then
|
||||
return false, "Refusing to copy region a distance of 0 nodes"
|
||||
end
|
||||
|
||||
return true, copy_offset:floor()
|
||||
end,
|
||||
nodes_needed = function(name)
|
||||
-- We don't actually modify anything, but without returning a
|
||||
-- number here safe_region doesn't work
|
||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||
end,
|
||||
func = function(name, copy_offset)
|
||||
local start_time = wea.get_ms_time()
|
||||
|
||||
local source_pos1 = wea.Vector3.clone(worldedit.pos1[name])
|
||||
local source_pos2 = wea.Vector3.clone(worldedit.pos2[name])
|
||||
|
||||
local target_pos1 = source_pos1:add(copy_offset)
|
||||
local target_pos2 = source_pos2:add(copy_offset)
|
||||
|
||||
local success, nodes_modified = wea.copy(
|
||||
source_pos1, source_pos2,
|
||||
target_pos1, target_pos2
|
||||
)
|
||||
|
||||
local time_taken = wea.get_ms_time() - start_time
|
||||
|
||||
|
||||
minetest.log("action", name.." used //copy from "..source_pos1.." - "..source_pos2.." to "..target_pos1.." - "..target_pos2..", modifying "..nodes_modified.." nodes in "..wea.format.human_time(time_taken))
|
||||
return true, nodes_modified.." nodes copied using offset "..copy_offset.." in "..wea.format.human_time(time_taken)
|
||||
end
|
||||
})
|
51
worldeditadditions_commands/commands/ellipsoid2.lua
Normal file
51
worldeditadditions_commands/commands/ellipsoid2.lua
Normal file
@ -0,0 +1,51 @@
|
||||
-- ███████ ██ ██ ██ ██████ ███████ ██████ ██ ██████
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- █████ ██ ██ ██ ██████ ███████ ██ ██ ██ ██ ██
|
||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||
-- ███████ ███████ ███████ ██ ██ ███████ ██████ ██ ██████
|
||||
local wea = worldeditadditions
|
||||
|
||||
worldedit.register_command("ellipsoid2", {
|
||||
params = "[<replace_node:dirt> [h[ollow]]]",
|
||||
description = "Creates am optionally hollow 3D ellipsoid that fills the defined region, filled with <replace_node>.",
|
||||
privs = { worldedit = true },
|
||||
require_pos = 2,
|
||||
parse = function(params_text)
|
||||
if not params_text or params_text == "" then
|
||||
params_text = "dirt"
|
||||
end
|
||||
|
||||
local parts = wea.split_shell(params_text)
|
||||
|
||||
|
||||
local replace_node = worldedit.normalize_nodename(parts[1])
|
||||
if not replace_node then
|
||||
return false, "Error: Invalid replace_node specified."
|
||||
end
|
||||
|
||||
local hollow = false
|
||||
if parts[2] == "hollow" or parts[2] == "h" then
|
||||
hollow = true
|
||||
end
|
||||
|
||||
return true, replace_node, hollow
|
||||
end,
|
||||
nodes_needed = function(name, target_node)
|
||||
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, target_node, radius, hollow)
|
||||
local start_time = wea.get_ms_time()
|
||||
local pos1, pos2 = wea.Vector3.sort(worldedit.pos1[name], worldedit.pos2[name])
|
||||
|
||||
local replaced = wea.ellipsoid2(
|
||||
pos1, pos2,
|
||||
target_node,
|
||||
hollow
|
||||
)
|
||||
local time_taken = wea.get_ms_time() - start_time
|
||||
|
||||
minetest.log("action", name .. " used //ellipsoid2 at "..pos1.." - "..pos2..", replacing " .. replaced .. " nodes in " .. time_taken .. "s")
|
||||
return true, replaced .. " nodes replaced in " .. wea.format.human_time(time_taken)
|
||||
end
|
||||
})
|
88
worldeditadditions_commands/commands/spiral2.lua
Normal file
88
worldeditadditions_commands/commands/spiral2.lua
Normal file
@ -0,0 +1,88 @@
|
||||
|
||||
local wea = worldeditadditions
|
||||
local Vector3 = wea.Vector3
|
||||
|
||||
worldedit.register_command("spiral2", {
|
||||
params = "[<circle|square>] [<replace_node=dirt> [<interval=3> [<acceleration=0>] ] ]",
|
||||
description = "Generates a spiral that fills the defined region using the specified replace node. The spiral is either square (default) or circular in shape. The interval specifies the distance between the walls of the spiral, and the acceleration specifies how quickly this value should increase.",
|
||||
privs = { worldedit = true },
|
||||
require_pos = 2,
|
||||
parse = function(params_text)
|
||||
if not params_text then params_text = "" end
|
||||
params_text = wea.trim(params_text)
|
||||
if params_text == "" then return true, "square", "dirt", 3, 0 end
|
||||
|
||||
local parts = wea.split_shell(params_text)
|
||||
|
||||
local mode = "square"
|
||||
local target_node = "dirt"
|
||||
local target_node_found = false
|
||||
local interval = 3
|
||||
local acceleration = 0
|
||||
|
||||
if parts[1] ~= "circle" and parts[1] ~= "square" then
|
||||
target_node = parts[1]
|
||||
target_node_found = true
|
||||
table.remove(parts, 1)
|
||||
else
|
||||
mode = parts[1]
|
||||
table.remove(parts, 1)
|
||||
end
|
||||
|
||||
if #parts >= 1 and not target_node_found then
|
||||
target_node = parts[1]
|
||||
table.remove(parts, 1)
|
||||
end
|
||||
if #parts >= 1 then
|
||||
interval = tonumber(parts[1])
|
||||
if not interval then
|
||||
return false, "Error: Invalid interval value '"..tostring(parts[1]).."'. Interval values must be integers."
|
||||
end
|
||||
table.remove(parts, 1)
|
||||
end
|
||||
if #parts >= 1 then
|
||||
acceleration = tonumber(parts[1])
|
||||
if not acceleration then
|
||||
return false, "Error: Invalid acceleration value '"..tostring(parts[1]).."'. Acceleration values must be integers."
|
||||
end
|
||||
table.remove(parts, 1)
|
||||
end
|
||||
|
||||
local target_node_norm = worldedit.normalize_nodename(target_node)
|
||||
if not target_node_norm then
|
||||
return false, "Error: Unknown node '"..tostring(target_node).."'."
|
||||
end
|
||||
|
||||
return true, mode, target_node_norm, interval, acceleration
|
||||
end,
|
||||
nodes_needed = function(name)
|
||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||
end,
|
||||
func = function(name, mode, target_node, interval, acceleration)
|
||||
local start_time = wea.get_ms_time()
|
||||
|
||||
local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name])
|
||||
|
||||
local success, count
|
||||
|
||||
if mode == "circle" then
|
||||
success, count = wea.spiral_circle(
|
||||
pos1, pos2,
|
||||
target_node,
|
||||
interval, acceleration
|
||||
)
|
||||
if not success then return success, count end
|
||||
else
|
||||
success, count = wea.spiral_square(
|
||||
pos1, pos2,
|
||||
target_node,
|
||||
interval, acceleration
|
||||
)
|
||||
if not success then return success, count end
|
||||
end
|
||||
local time_taken = wea.get_ms_time() - start_time
|
||||
|
||||
minetest.log("action", name .. " used //spiral at "..pos1.." - "..pos2..", adding " .. count .. " nodes in " .. time_taken .. "s")
|
||||
return true, count .. " nodes replaced in " .. wea.format.human_time(time_taken)
|
||||
end
|
||||
})
|
@ -4,11 +4,12 @@
|
||||
-- ██ ███ ██ ██ ██ ██ ██ ██
|
||||
-- ███ ███ ██ ██ ███████ ███████ ███████
|
||||
worldedit.register_command("walls", {
|
||||
params = "<replace_node> [<thickness=1>]",
|
||||
params = "[<replace_node=dirt> [<thickness=1>]]",
|
||||
description = "Creates vertical walls of <replace_node> around the inside edges of the defined region. Optionally specifies a thickness for the walls to be created (defaults to 1)",
|
||||
privs = { worldedit = true },
|
||||
require_pos = 2,
|
||||
parse = function(params_text)
|
||||
if not params_text or params_text == "" then params_text = "dirt" end
|
||||
local parts = worldeditadditions.split_shell(params_text)
|
||||
|
||||
local target_node
|
||||
|
@ -21,6 +21,7 @@ dofile(we_c.modpath.."/player_notify_suppress.lua")
|
||||
|
||||
dofile(we_c.modpath.."/commands/convolve.lua")
|
||||
dofile(we_c.modpath.."/commands/ellipsoid.lua")
|
||||
dofile(we_c.modpath.."/commands/ellipsoid2.lua")
|
||||
dofile(we_c.modpath.."/commands/erode.lua")
|
||||
dofile(we_c.modpath.."/commands/fillcaves.lua")
|
||||
dofile(we_c.modpath.."/commands/floodfill.lua")
|
||||
@ -34,6 +35,7 @@ dofile(we_c.modpath.."/commands/replacemix.lua")
|
||||
dofile(we_c.modpath.."/commands/scale.lua")
|
||||
dofile(we_c.modpath.."/commands/torus.lua")
|
||||
dofile(we_c.modpath.."/commands/walls.lua")
|
||||
dofile(we_c.modpath.."/commands/spiral2.lua")
|
||||
|
||||
dofile(we_c.modpath.."/commands/count.lua")
|
||||
|
||||
@ -53,7 +55,7 @@ dofile(we_c.modpath.."/commands/extra/saplingaliases.lua")
|
||||
dofile(we_c.modpath.."/commands/extra/basename.lua")
|
||||
|
||||
-- Don't registry the //bonemeal command if the bonemeal mod isn't present
|
||||
if minetest.get_modpath("bonemeal") then
|
||||
if minetest.global_exists("bonemeal") then
|
||||
dofile(we_c.modpath.."/commands/bonemeal.lua")
|
||||
dofile(we_c.modpath.."/commands/forest.lua")
|
||||
else
|
||||
|
3
worldeditadditions_core/README.md
Normal file
3
worldeditadditions_core/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# worldeditadditions_core
|
||||
|
||||
This mod's purpose is to provide a solid base upon which the rest of WorldEditAdditions can function. Once it is complete, we will be able to mark our dependency on `worldedit` itself optional. To get to that point though will still require a significant effort in implementing enhanced versions of all existing WorldEdit commands. If you've got some free time and a great idea for a command, please do open a pull request! :D
|
@ -1,5 +1,5 @@
|
||||
function worldeditadditions_core.chatcommand_handler(cmd_name, name, param)
|
||||
local def = assert(worldedit.registered_commands[cmd_name])
|
||||
local def = assert(worldedit.registered_commands[cmd_name], "Error: Failed to locate worldedit command definition for command '"..name.."' (this is probably a bug).")
|
||||
|
||||
if def.require_pos == 2 then
|
||||
local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
|
||||
|
@ -2,7 +2,7 @@ local we_c = worldeditadditions_core
|
||||
function we_c.override_command(name, def)
|
||||
local def = table.copy(def)
|
||||
local success, err = we_c.check_command(name, def)
|
||||
|
||||
|
||||
if not success then
|
||||
error(err)
|
||||
return false
|
||||
|
@ -2,7 +2,6 @@ local we_c = worldeditadditions_core
|
||||
function we_c.register_command(name, def)
|
||||
local def = table.copy(def)
|
||||
local success, err = we_c.check_command(name, def)
|
||||
|
||||
if not success then
|
||||
return false, err
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
--- WorldEdit dependencies for WorldEditAdditions
|
||||
--- WorldEdit shim just in case WorldEdit doesn't exist
|
||||
|
||||
worldedit = {
|
||||
registered_commands = {}
|
||||
registered_commands = { }
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user