├── .docs ├── .eleventy.js ├── .eleventyignore ├── Changelog.11tydata.json ├── Changelog.md ├── Cookbook.11tydata.json ├── Cookbook.md ├── Reference.11tydata.js ├── Reference.html ├── Tutorial.md ├── _data │ └── contributors.js ├── _includes │ ├── content-generic.njk │ └── theme.njk ├── css.njk ├── css │ ├── gallerybox.css │ ├── patterns.css │ ├── prism-custom.css │ ├── smallscreens.css │ └── theme.css ├── grammars │ └── axes.bnf ├── images │ ├── banner-main.jpeg │ ├── clouds-night.png │ ├── clouds.png │ ├── contentdb.png │ ├── conv-layers.png │ ├── forest.png │ ├── gallery-a.jpeg │ ├── gallery-b.jpeg │ ├── gallery-c.jpeg │ ├── gallery-d.jpeg │ ├── gallery-e.jpeg │ ├── gallery-f.jpeg │ ├── github.svg │ ├── maze2d-alt.png │ ├── maze2d.png │ ├── reference │ │ └── spline.jpeg │ ├── torus-bonemeal.png │ └── tutorial_pos1_2.jpeg ├── img2brush │ ├── img2brush.js │ └── index.html ├── index.html ├── lib │ ├── Ansi.js │ ├── FileFetcher.js │ ├── HTMLPicture.js │ └── parse_sections.js ├── package-lock.json └── package.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── test.yml ├── .gitignore ├── .luacheckrc ├── .tests ├── Vector3 │ ├── abs.test.lua │ ├── add.test.lua │ ├── area.test.lua │ ├── ceil.test.lua │ ├── clamp.test.lua │ ├── clone.test.lua │ ├── concat.test.lua │ ├── divide.test.lua │ ├── dot.test.lua │ ├── equal.test.lua │ ├── expand_region.test.lua │ ├── floor.test.lua │ ├── is_contained.test.lua │ ├── length.test.lua │ ├── length_squared.test.lua │ ├── limit_to.test.lua │ ├── max.test.lua │ ├── max_component.test.lua │ ├── mean.test.lua │ ├── min.test.lua │ ├── min_component.test.lua │ ├── move_towards.test.lua │ ├── multiply.test.lua │ ├── new.test.lua │ ├── rotate3d.test.lua │ ├── round.test.lua │ ├── set_to.test.lua │ ├── snap_to.test.lua │ ├── sort_pos.test.lua │ ├── sqrt.test.lua │ ├── subtract.test.lua │ ├── tostring.test.lua │ ├── unit.test.lua │ └── volume.test.lua ├── parse │ ├── axes │ │ ├── axes_parser.test.lua │ │ ├── include_facing_dirs.lua │ │ ├── parse_abs_axis_name.test.lua │ │ ├── parse_axes.test.lua │ │ └── parse_relative_axis_name.test.lua │ ├── chance.test.lua │ ├── map.test.lua │ ├── seed.test.lua │ └── table │ │ ├── makeset.test.lua │ │ ├── table_apply.test.lua │ │ └── table_contains.test.lua └── strings │ ├── split_shell.test.lua │ ├── str_ends.test.lua │ ├── str_padend.test.lua │ ├── str_padstart.test.lua │ ├── str_starts.test.lua │ └── trim.test.lua ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.tsv ├── Chat-Command-Reference.md ├── Cookbook.md ├── FUNDING.yml ├── LICENSE ├── README.md ├── bresenham.lua ├── build.sh ├── modpack.conf ├── modpack.txt ├── rotate.js ├── screenshot.png ├── screenshot2.png ├── settingtypes.txt ├── test_count_chunks.lua ├── tests.sh ├── worldeditadditions-64.png ├── worldeditadditions.png ├── worldeditadditions ├── init.lua ├── lib │ ├── airapply.lua │ ├── bonemeal.lua │ ├── compat │ │ └── saplingnames.lua │ ├── conv │ │ ├── conv.lua │ │ ├── convolve.lua │ │ ├── kernel_gaussian.lua │ │ └── kernels.lua │ ├── copy.lua │ ├── count.lua │ ├── dome.lua │ ├── ellipsoid.lua │ ├── ellipsoid2.lua │ ├── ellipsoidapply.lua │ ├── erode │ │ ├── erode.lua │ │ ├── river.lua │ │ └── snowballs.lua │ ├── fillcaves.lua │ ├── floodfill.lua │ ├── forest.lua │ ├── hollow.lua │ ├── layers.lua │ ├── line.lua │ ├── maze2d.lua │ ├── maze3d.lua │ ├── metaballs │ │ ├── init.lua │ │ ├── playerdata.lua │ │ └── render.lua │ ├── move.lua │ ├── noise │ │ ├── apply_2d.lua │ │ ├── engines │ │ │ ├── infrared.lua │ │ │ ├── init.lua │ │ │ ├── perlin.lua │ │ │ ├── perlinmt.lua │ │ │ ├── red.lua │ │ │ ├── sin.lua │ │ │ └── white.lua │ │ ├── init.lua │ │ ├── make_2d.lua │ │ ├── params_apply_default.lua │ │ └── run2d.lua │ ├── noiseapply2d.lua │ ├── overlay.lua │ ├── replacemix.lua │ ├── revolve.lua │ ├── scale.lua │ ├── scale_down.lua │ ├── scale_up.lua │ ├── sculpt │ │ ├── apply.lua │ │ ├── apply_heightmap.lua │ │ ├── brushes │ │ │ ├── __gaussian.lua │ │ │ ├── circle.lua │ │ │ ├── circle_soft1.lua │ │ │ ├── ellipse.brush.tsv │ │ │ ├── gaussian.lua │ │ │ ├── gaussian_hard.lua │ │ │ ├── gaussian_soft.lua │ │ │ └── square.lua │ │ ├── import_static.lua │ │ ├── init.lua │ │ ├── make_brush.lua │ │ ├── make_preview.lua │ │ ├── parse_static.lua │ │ ├── preview_brush.lua │ │ └── scan_static.lua │ ├── selection │ │ ├── init.lua │ │ ├── selection.lua │ │ └── stack.lua │ ├── spiral_circle.lua │ ├── spiral_square.lua │ ├── spline.lua │ ├── subdivide.lua │ ├── torus.lua │ ├── walls.lua │ └── wireframe │ │ ├── corner_set.lua │ │ ├── make_compass.lua │ │ └── wire_box.lua └── mod.conf ├── worldeditadditions_commands ├── aliases.lua ├── commands │ ├── bonemeal.lua │ ├── convolve.lua │ ├── copy.lua │ ├── count.lua │ ├── dome.lua │ ├── ellipsoid.lua │ ├── ellipsoid2.lua │ ├── erode.lua │ ├── extra │ │ ├── basename.lua │ │ ├── saplingaliases.lua │ │ └── sculptlist.lua │ ├── fillcaves.lua │ ├── floodfill.lua │ ├── forest.lua │ ├── hollow.lua │ ├── layers.lua │ ├── line.lua │ ├── maze.lua │ ├── measure │ │ ├── init.lua │ │ ├── mface.lua │ │ ├── midpos.lua │ │ ├── msize.lua │ │ └── mtrig.lua │ ├── meta │ │ ├── airapply.lua │ │ ├── ellipsoidapply.lua │ │ ├── for.lua │ │ ├── init.lua │ │ ├── listentities.lua │ │ ├── macro.lua │ │ ├── many.lua │ │ ├── multi.lua │ │ ├── noiseapply2d.lua │ │ └── subdivide.lua │ ├── metaball.lua │ ├── move.lua │ ├── noise2d.lua │ ├── overlay.lua │ ├── replacemix.lua │ ├── revolve.lua │ ├── scale.lua │ ├── sculpt.lua │ ├── selectors │ │ ├── init.lua │ │ ├── mark.lua │ │ ├── pos1-2.lua │ │ ├── reset.lua │ │ ├── scentre.lua │ │ ├── scloud.lua │ │ ├── scol.lua │ │ ├── scube.lua │ │ ├── sfactor.lua │ │ ├── smake.lua │ │ ├── spop.lua │ │ ├── spush.lua │ │ ├── srect.lua │ │ ├── srel.lua │ │ ├── sshift.lua │ │ ├── sstack.lua │ │ └── unmark.lua │ ├── spiral2.lua │ ├── spline.lua │ ├── torus.lua │ ├── walls.lua │ └── wireframe │ │ ├── init.lua │ │ ├── wbox.lua │ │ ├── wcompass.lua │ │ └── wcorner.lua ├── doc │ ├── init.lua │ └── parse_reference.lua ├── init.lua ├── mod.conf └── player_notify_suppress.lua ├── worldeditadditions_core ├── README.md ├── core │ ├── entities │ │ ├── init.lua │ │ ├── pos_marker.lua │ │ └── pos_marker_wall.lua │ ├── fetch_command_def.lua │ ├── integrations │ │ ├── noworldedit.lua │ │ └── worldedit.lua │ ├── pos.lua │ ├── pos_marker_manage.lua │ ├── pos_marker_wall_manage.lua │ ├── register_alias.lua │ ├── register_command.lua │ ├── run_command.lua │ └── safe_region.lua ├── init.lua ├── mod.conf ├── textures │ ├── worldeditadditions_core_bg.png │ ├── worldeditadditions_core_l0.png │ ├── worldeditadditions_core_l1.png │ ├── worldeditadditions_core_l2.png │ ├── worldeditadditions_core_l3.png │ ├── worldeditadditions_core_l4.png │ ├── worldeditadditions_core_l5.png │ ├── worldeditadditions_core_l6.png │ ├── worldeditadditions_core_l7.png │ ├── worldeditadditions_core_l8.png │ ├── worldeditadditions_core_l9.png │ ├── worldeditadditions_core_marker_wall.png │ ├── worldeditadditions_core_r0.png │ ├── worldeditadditions_core_r1.png │ ├── worldeditadditions_core_r2.png │ ├── worldeditadditions_core_r3.png │ ├── worldeditadditions_core_r4.png │ ├── worldeditadditions_core_r5.png │ ├── worldeditadditions_core_r6.png │ ├── worldeditadditions_core_r7.png │ ├── worldeditadditions_core_r8.png │ └── worldeditadditions_core_r9.png └── utils │ ├── EventEmitter.lua │ ├── bit.lua │ ├── chaikin.lua │ ├── format │ ├── array_2d.lua │ ├── human_size.lua │ ├── human_time.lua │ ├── init.lua │ ├── make_ascii_table.lua │ ├── map.lua │ └── node_distribution.lua │ ├── inspect.lua │ ├── io.lua │ ├── lru.lua │ ├── lru_benchmark.lua │ ├── mesh.lua │ ├── node_identification.lua │ ├── nodes.lua │ ├── numbers.lua │ ├── parse │ ├── axes.lua │ ├── axes_parser.lua │ ├── chance.lua │ ├── init.lua │ ├── key_instance.lua │ ├── map.lua │ ├── seed.lua │ ├── tokenise_commands.lua │ └── weighted_nodes.lua │ ├── player.lua │ ├── queue.lua │ ├── raycast_adv.lua │ ├── set.lua │ ├── setting_handler.lua │ ├── strings │ ├── init.lua │ ├── polyfill.lua │ ├── split.lua │ ├── split_shell.lua │ ├── to_boolean.lua │ └── tochars.lua │ ├── table │ ├── deepcopy.lua │ ├── init.lua │ ├── makeset.lua │ ├── shallowcopy.lua │ ├── table_apply.lua │ ├── table_contains.lua │ ├── table_filter.lua │ ├── table_get_last.lua │ ├── table_map.lua │ ├── table_reduce.lua │ ├── table_tostring.lua │ ├── table_unique.lua │ └── table_unpack.lua │ ├── terrain │ ├── apply_heightmap_changes.lua │ ├── calculate_normals.lua │ ├── calculate_slopes.lua │ ├── init.lua │ └── make_heightmap.lua │ └── vector3.lua └── worldeditadditions_farwand ├── edit ├── brush.piskel └── worldedit_wand.piskel ├── init.lua ├── lib ├── chatcommand.lua ├── cloudwand.lua ├── do_raycast.lua ├── farwand.lua ├── multiwand.lua └── settings.lua ├── mod.conf └── textures ├── worldeditadditions_chisel.png ├── worldeditadditions_cloudwand.png ├── worldeditadditions_farwand.png └── worldeditadditions_multiwand.png /.docs/.eleventyignore: -------------------------------------------------------------------------------- 1 | css/ 2 | -------------------------------------------------------------------------------- /.docs/Changelog.11tydata.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": "content-generic.njk", 3 | "title": "Changelog", 4 | "tags": "navigable", 5 | "date": "2002-01-01" 6 | } 7 | -------------------------------------------------------------------------------- /.docs/Changelog.md: -------------------------------------------------------------------------------- 1 | ../CHANGELOG.md -------------------------------------------------------------------------------- /.docs/Cookbook.11tydata.json: -------------------------------------------------------------------------------- 1 | { 2 | "layout": "content-generic.njk", 3 | "title": "Cookbook", 4 | "tags": "navigable", 5 | "date": "2006-01-01" 6 | } 7 | -------------------------------------------------------------------------------- /.docs/Cookbook.md: -------------------------------------------------------------------------------- 1 | ../Cookbook.md -------------------------------------------------------------------------------- /.docs/Reference.11tydata.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | 6 | const columnify = require("columnify"); 7 | const htmlentities = require("html-entities"); 8 | 9 | const a = require("./lib/Ansi.js"); 10 | const parse_sections = require("./lib/parse_sections.js"); 11 | 12 | let { sections, categories } = parse_sections(fs.readFileSync( 13 | path.resolve( 14 | __dirname, 15 | `../Chat-Command-Reference.md` 16 | ), 17 | "utf-8" 18 | )) 19 | 20 | sections = sections.sort((a, b) => a.title.replace(/^\/+/g, "").localeCompare( 21 | b.title.replace(/^\/+/g, ""))); 22 | 23 | 24 | console.log(`REFERENCE SECTION TITLES`) 25 | console.log(columnify(sections.map(s => { return { 26 | category: `${a.hicol}${a.fyellow}${s.category}${a.reset}`, 27 | command: `${a.hicol}${a.fmagenta}${htmlentities.decode(s.title)}${a.reset}` 28 | } }))); 29 | // console.log(sections 30 | // .map(s => `${a.fyellow}${a.hicol}${s.category}${a.reset}\t${a.fmagenta}${a.hicol}${s.title}${a.reset}`).join(`\n`)); 31 | console.log(`************************`); 32 | 33 | console.log(`REFERENCE SECTION COLOURS`); 34 | console.log(columnify(Array.from(categories).map(el => { return { 35 | category: el[0], 36 | colour: el[1] 37 | } }))); 38 | 39 | module.exports = { 40 | layout: "theme.njk", 41 | title: "Reference", 42 | tags: "navigable", 43 | date: "2001-01-01", 44 | section_intro: sections[0], 45 | sections_help: sections, // Remove the very beginning bit 46 | categories: [...categories.keys()].join("|") 47 | } 48 | -------------------------------------------------------------------------------- /.docs/_data/contributors.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const htmlentities = require("html-entities"); 4 | 5 | function read_contributors() { 6 | return fs.readFileSync(path.resolve(__dirname, "../../CONTRIBUTORS.tsv"), "utf-8") 7 | .split("\n") 8 | .slice(1) 9 | .filter(line => line.length > 0) 10 | .map(line => line.split(/\s+/)) 11 | .map(items => { return { 12 | handle: htmlentities.encode(items[0]), 13 | name: htmlentities.encode(items[1]), 14 | profile_url: `https://github.com/${encodeURIComponent(items[0])}`, 15 | avatar_url: `https://avatars.githubusercontent.com/${encodeURIComponent(items[0])}` 16 | } }); 17 | } 18 | 19 | const contributors = read_contributors(); 20 | 21 | console.log(`CONTRIBUTORS`, contributors); 22 | 23 | module.exports = contributors; 24 | -------------------------------------------------------------------------------- /.docs/_includes/content-generic.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: theme.njk 3 | --- 4 | 5 |
6 | {{ content | safe }} 7 |
8 | -------------------------------------------------------------------------------- /.docs/css.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: theme.css 3 | --- 4 | 5 | {% include "css/patterns.css" %} 6 | {% include "css/theme.css" %} 7 | {% include "css/gallerybox.css" %} 8 | {% include "css/smallscreens.css" %} 9 | {% include "css/prism-custom.css" %} 10 | 11 | {# {% fetch "https://unpkg.com/prismjs/themes/prism-okaidia.css" %} #} 12 | {# {% fetch "https://raw.githubusercontent.com/PrismJS/prism-themes/master/themes/prism-shades-of-purple.css" %} #} 13 | {# {% fetch "https://raw.githubusercontent.com/PrismJS/prism-themes/master/themes/prism-material-light.css" %} #} 14 | -------------------------------------------------------------------------------- /.docs/css/smallscreens.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 1200px) { 2 | nav > ul a { 3 | padding: 1.45em 0.5em; 4 | } 5 | } 6 | 7 | 8 | @media (max-width: 930px) { 9 | nav { 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | } 14 | 15 | nav > h1 { 16 | width: 100%; 17 | } 18 | 19 | .bigbox { 20 | padding: 0 5vmin; 21 | } 22 | } 23 | 24 | @media (max-width: 640px) { 25 | nav > ul { 26 | padding-top: 0.5em; 27 | justify-content: center; 28 | flex-wrap: wrap; 29 | } 30 | 31 | .bigbox h1 { 32 | font-size: 1.5em; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.docs/grammars/axes.bnf: -------------------------------------------------------------------------------- 1 | {% 2 | # Lists of axes 3 | 4 | In various commands such as `//copy+`, `//move+`, and others lists of axes are used. These are all underpinned by a single grammar and a single parser (located in `worldeditadditions/utils/parse/axes.lua`). While the parser itself requires pre-split tokens (see `split_shell` for that), the grammar which it parses is documented here. 5 | 6 | Examples: 7 | 8 | ``` 9 | front 3 left 10 y 77 x 30 back 99 10 | ``` 11 | 12 | %} 13 | 14 | ::= * 15 | 16 | ::= 17 | | 18 | 19 | ::= 20 | | 21 | 22 | ::= sym | symmetrical | mirror | mir | rev | reverse | true 23 | 24 | 25 | 26 | ::= 27 | | 28 | 29 | ::= 30 | | "-" 31 | 32 | ::= front | back | left | right | up | down | "?" 33 | 34 | ::= x | y | z | h | v 35 | 36 | 37 | 38 | ::= * 39 | 40 | ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0 41 | -------------------------------------------------------------------------------- /.docs/images/banner-main.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/banner-main.jpeg -------------------------------------------------------------------------------- /.docs/images/clouds-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/clouds-night.png -------------------------------------------------------------------------------- /.docs/images/clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/clouds.png -------------------------------------------------------------------------------- /.docs/images/contentdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/contentdb.png -------------------------------------------------------------------------------- /.docs/images/conv-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/conv-layers.png -------------------------------------------------------------------------------- /.docs/images/forest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/forest.png -------------------------------------------------------------------------------- /.docs/images/gallery-a.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/gallery-a.jpeg -------------------------------------------------------------------------------- /.docs/images/gallery-b.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/gallery-b.jpeg -------------------------------------------------------------------------------- /.docs/images/gallery-c.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/gallery-c.jpeg -------------------------------------------------------------------------------- /.docs/images/gallery-d.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/gallery-d.jpeg -------------------------------------------------------------------------------- /.docs/images/gallery-e.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/gallery-e.jpeg -------------------------------------------------------------------------------- /.docs/images/gallery-f.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/gallery-f.jpeg -------------------------------------------------------------------------------- /.docs/images/maze2d-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/maze2d-alt.png -------------------------------------------------------------------------------- /.docs/images/maze2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/maze2d.png -------------------------------------------------------------------------------- /.docs/images/reference/spline.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/reference/spline.jpeg -------------------------------------------------------------------------------- /.docs/images/torus-bonemeal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/torus-bonemeal.png -------------------------------------------------------------------------------- /.docs/images/tutorial_pos1_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/.docs/images/tutorial_pos1_2.jpeg -------------------------------------------------------------------------------- /.docs/img2brush/index.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: theme.njk 3 | title: Image to brush converter 4 | --- 5 | 6 |
7 |

Image to sculpting brush converter

8 | 9 |

Convert any image to a sculpting brush here!

10 |

11 | Use this channel to convert: 12 | 18 |

19 |

Only the selected channel is used to determine the weight of the brush - all other channels are ignored! Change the channel option before you drag + drop the image onto this page.

20 |
21 | 22 |
23 |

Input

24 |

Drop your image here.

25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 |
33 |

Output

34 | 35 |

Paste the output below into a text file in worldeditadditions/lib/sculpt/brushes with the file extension .brush.tsv and restart your Minetest server for the brush to be recognised.

36 | 37 |

38 | 39 |

40 | 41 |
(your output will appear here as soon as you drop an image above)
42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /.docs/lib/FileFetcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const path = require("path"); 4 | const fs = require("fs"); 5 | const os = require("os"); 6 | 7 | const phin = require("phin"); 8 | 9 | const a = require("./Ansi.js"); 10 | const pretty_ms = require("pretty-ms"); 11 | 12 | class FileFetcher { 13 | #cache = []; 14 | 15 | #pkg_obj = null; 16 | 17 | constructor() { 18 | 19 | } 20 | 21 | fetch_file(url) { 22 | let target_client = path.join(`/img`, path.basename(url)); 23 | 24 | if(this.#cache.includes(url)) return target_client; 25 | 26 | this.#cache.push(url); 27 | 28 | this.download_file(url); // Returns a promise! We fire-and-forget it though 'cause this function *must* be synchronous :-/ 29 | 30 | return target_client; 31 | } 32 | 33 | async download_file(url) { 34 | const time_start = new Date(); 35 | 36 | if(this.#pkg_obj === null) { 37 | this.#pkg_obj = JSON.parse(await fs.promises.readFile( 38 | path.join(path.dirname(__dirname), "package.json"), "utf8" 39 | )); 40 | } 41 | 42 | let target_download = path.join(`_site/img`, path.basename(url)); 43 | 44 | const response = await phin({ 45 | url, 46 | headers: { 47 | "user-agent": `WorldEditAdditionsStaticBuilder/${this.#pkg_obj.version} (Node.js/${process.version}; ${os.platform()} ${os.arch()}) eleventy/${this.#pkg_obj.dependencies["@11ty/eleventy"].replace(/\^/, "")}` 48 | }, 49 | followRedirects: true, 50 | parse: 'none' // Returns a Buffer 51 | // If we stream and pipe to a file, the build never ends :-/ 52 | }); 53 | 54 | await fs.promises.writeFile(target_download, response.body); 55 | 56 | console.log([ 57 | `${a.fred}${a.hicol}FETCH_FILE${a.reset}`, 58 | `${a.fyellow}${pretty_ms(new Date() - time_start)}${a.reset}`, 59 | `${a.fgreen}${url}${a.reset}` 60 | ].join("\t")); 61 | } 62 | 63 | } 64 | 65 | module.exports = FileFetcher; -------------------------------------------------------------------------------- /.docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worldeditadditions", 3 | "version": "1.14.5", 4 | "description": "Documentation website for WorldEditAdditions", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "test": "echo \"No tests have been implemented yet\"", 9 | "start": "npx @11ty/eleventy --serve", 10 | "build": "npx @11ty/eleventy" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/sbrl/Minetest-WorldEditAdditions.git" 15 | }, 16 | "author": "Starbeamrainbowlabs", 17 | "license": "MPL-2.0", 18 | "bugs": { 19 | "url": "https://github.com/sbrl/Minetest-WorldEditAdditions/issues" 20 | }, 21 | "homepage": "https://github.com/sbrl/Minetest-WorldEditAdditions#readme", 22 | "dependencies": { 23 | "@11ty/eleventy": "^2.0.1", 24 | "chroma-js": "^2.4.2", 25 | "clean-css": "^5.3.2", 26 | "columnify": "^1.6.0", 27 | "debug": "^4.3.4", 28 | "html-entities": "^2.4.0", 29 | "html-minifier-terser": "^7.0.0-beta.0", 30 | "imagickal": "^5.0.1", 31 | "markdown-it-prism": "^2.3.0", 32 | "p-memoize": "^7.1.1", 33 | "p-queue": "^7.3.4", 34 | "phin": "^3.7.0", 35 | "pretty-ms": "^7.0.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | What's the bug? Be clear and concise in our explanation. Don't forget to include any context, error messages, logs, and screenshots required to understand the issue if applicable. 12 | 13 | **Reproduction steps:** 14 | Steps to reproduce the behaviour: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Enter this command to '....' 18 | 4. See error 19 | 20 | **System information (please complete the following information):** 21 | - **Operating system and version:** [e.g. iOS] 22 | - **Minetest version:** [e.g. 5.8.0] 23 | - **WorldEdit version:** 24 | - **WorldEditAdditions version:** 25 | 26 | Please add any other additional specific system information here too if you think it would help. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Problem 11 | A clear and concise description of what the problem you want to solve is. e.g. I'm always frustrated when [...] 12 | 13 | ## Solution 14 | Describe clearly the solution you'd like. 15 | 16 | ## Alternatives 17 | A clear and concise description of any alternative solutions or features you've considered, and how they relate to your proposed feature. 18 | 19 | ## Additional context 20 | Add any other context or screenshots about the feature request here. 21 | 22 | Don't forget to remove replace descriptions, but keep the headers. 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "CI Tests" 2 | on: [push] 3 | jobs: 4 | Syntax-Check: 5 | runs-on: ubuntu-latest 6 | steps: 7 | # Checkout the git repo 8 | - name: Checkout 9 | uses: actions/checkout@v2 10 | 11 | - name: Install apt dependencies 12 | run: sudo apt-get --quiet install lua5.1 13 | 14 | - name: Environment info 15 | run: uname -a; lua -v 16 | 17 | - name: Perform Check 18 | run: find . -type f -name '*.lua' -not -path '*luarocks*' -not -path '*.git/*' -print0 | xargs -0 -n1 -P "$(nproc)" luac -p; 19 | Busted: 20 | runs-on: ubuntu-latest 21 | steps: 22 | # Checkout the git repo 23 | - name: Checkout 24 | uses: actions/checkout@v2 25 | 26 | - name: Install apt dependencies 27 | run: sudo apt-get --quiet install lua5.1 luarocks 28 | 29 | - name: Environment info 30 | run: uname -a; lua -v 31 | 32 | - name: Set up tests 33 | run: ./tests.sh run 34 | 35 | - name: Run Tests 36 | run: ./tests.sh run 37 | LuaCheck: 38 | runs-on: ubuntu-latest 39 | steps: 40 | # Checkout the git repo 41 | - name: Checkout 42 | uses: actions/checkout@v2 43 | 44 | - name: Install apt dependencies 45 | run: sudo apt-get --quiet install lua5.1 lua-check 46 | 47 | - name: Environment info 48 | run: uname -a; lua -v 49 | 50 | - name: Run luacheck 51 | run: luacheck . 52 | 53 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | quiet = 1 2 | codes = true 3 | 4 | exclude_files = { 5 | ".luarocks/*", 6 | "worldeditadditions_core/utils/bit.lua" 7 | } 8 | 9 | files["worldeditadditions_core/register/check.lua"] = { read_globals = { "table" } } 10 | 11 | ignore = { 12 | "631", "61[124]", 13 | "542", 14 | "412", 15 | "321/bit", 16 | "21[123]" 17 | } 18 | 19 | -- Read-write globals (i.e. they can be defined) 20 | globals = { 21 | "worldedit", 22 | "worldeditadditions", 23 | "worldeditadditions_commands", 24 | "worldeditadditions_core" 25 | } 26 | -- Read-only globals 27 | read_globals = { 28 | "minetest", 29 | "vector", 30 | "assert", 31 | "bit", 32 | "it", 33 | "describe", 34 | "bonemeal", 35 | "dofile", 36 | "PerlinNoise", 37 | "Settings" 38 | } 39 | std = "max" 40 | -------------------------------------------------------------------------------- /.tests/Vector3/abs.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.abs", function() 4 | it("should work with a positive vector", function() 5 | local a = Vector3.new(16, 64, 16) 6 | assert.are.same( 7 | Vector3.new(16, 64, 16), 8 | a:abs() 9 | ) 10 | end) 11 | it("should abs another positive vector", function() 12 | local a = Vector3.new(9, 16, 25) 13 | assert.are.same( 14 | Vector3.new(9, 16, 25), 15 | a:abs() 16 | ) 17 | end) 18 | it("should abs a negative vector", function() 19 | local a = Vector3.new(-9, -16, -25) 20 | assert.are.same( 21 | Vector3.new(9, 16, 25), 22 | a:abs() 23 | ) 24 | end) 25 | it("should return a new Vector3 instance", function() 26 | local a = Vector3.new(9, -16, 25) 27 | 28 | local result = a:abs() 29 | assert.are.same( 30 | Vector3.new(9, 16, 25), 31 | result 32 | ) 33 | assert.are_not.equal(result, a) 34 | end) 35 | end) 36 | -------------------------------------------------------------------------------- /.tests/Vector3/add.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.add", function() 4 | it("should add 2 positive vectors", function() 5 | local a = Vector3.new(3, 4, 5) 6 | local b = Vector3.new(1, 1, 1) 7 | assert.are.same( 8 | Vector3.new(4, 5, 6), 9 | a:add(b) 10 | ) 11 | end) 12 | it("should support the add operator", function() 13 | local a = Vector3.new(3, 4, 5) 14 | local b = Vector3.new(1, 1, 1) 15 | assert.are.same( 16 | Vector3.new(4, 5, 6), 17 | a + b 18 | ) 19 | end) 20 | it("should work with floats", function() 21 | local a = Vector3.new(3.5, 4.5, 5.5) 22 | local b = Vector3.new(1.1, 1.1, 1.1) 23 | assert.are.same( 24 | Vector3.new(4.6, 5.6, 6.6), 25 | a + b 26 | ) 27 | end) 28 | it("should work with scalar a", function() 29 | local a = 2 30 | local b = Vector3.new(6, 7, 8) 31 | assert.are.same( 32 | Vector3.new(8, 9, 10), 33 | a + b 34 | ) 35 | end) 36 | it("should work with scalar b", function() 37 | local a = Vector3.new(6, 7, 8) 38 | local b = 2 39 | assert.are.same( 40 | Vector3.new(8, 9, 10), 41 | a + b 42 | ) 43 | end) 44 | it("should handle negative b", function() 45 | local a = Vector3.new(3, 4, 5) 46 | local b = Vector3.new(-1, -1, -1) 47 | assert.are.same( 48 | Vector3.new(2, 3, 4), 49 | a + b 50 | ) 51 | end) 52 | it("should handle negative a", function() 53 | local a = Vector3.new(-3, -4, -5) 54 | local b = Vector3.new(1, 1, 1) 55 | assert.are.same( 56 | Vector3.new(-2, -3, -4), 57 | a + b 58 | ) 59 | end) 60 | it("should handle negative a and b", function() 61 | local a = Vector3.new(-3, -4, -5) 62 | local b = Vector3.new(-1, -1, -1) 63 | assert.are.same( 64 | Vector3.new(-4, -5, -6), 65 | a + b 66 | ) 67 | end) 68 | it("should return a new Vector3 instance", function() 69 | local a = Vector3.new(3, 4, 5) 70 | local b = Vector3.new(1, 1, 1) 71 | 72 | local result = a + b 73 | assert.are.same( 74 | Vector3.new(4, 5, 6), 75 | result 76 | ) 77 | assert.are_not.equal(result, a) 78 | assert.are_not.equal(result, b) 79 | end) 80 | end) 81 | -------------------------------------------------------------------------------- /.tests/Vector3/area.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.area", function() 4 | it("should work with a positive vector", function() 5 | local a = Vector3.new(3, 3, 3) 6 | assert.are.equal( 7 | 27, 8 | a:area() 9 | ) 10 | end) 11 | it("should work with a negative vector", function() 12 | local a = Vector3.new(-4, -4, -4) 13 | assert.are.equal( 14 | -64, 15 | a:area() 16 | ) 17 | end) 18 | it("should work with a mixed vector", function() 19 | local a = Vector3.new(-3, 3, -3) 20 | assert.are.equal( 21 | 27, 22 | a:area() 23 | ) 24 | end) 25 | end) 26 | -------------------------------------------------------------------------------- /.tests/Vector3/ceil.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.ceil", function() 4 | it("should ceil a positive vector", function() 5 | local a = Vector3.new(3.1, 4.2, 5.8) 6 | assert.are.same( 7 | Vector3.new(4, 5, 6), 8 | a:ceil() 9 | ) 10 | end) 11 | it("should ceil a negative vector", function() 12 | local a = Vector3.new(-3.1, -4.2, -5.3) 13 | assert.are.same( 14 | Vector3.new(-3, -4, -5), 15 | a:ceil() 16 | ) 17 | end) 18 | it("should work with integers", function() 19 | local a = Vector3.new(3, 4, 5) 20 | assert.are.same( 21 | a:ceil(), 22 | Vector3.new(3, 4, 5) 23 | ) 24 | end) 25 | it("should return a new Vector3 instance", function() 26 | local a = Vector3.new(3.1, 4.7, 5.99999) 27 | 28 | local result = a:ceil() 29 | assert.are.same( 30 | Vector3.new(4, 5, 6), 31 | result 32 | ) 33 | assert.are_not.equal(result, a) 34 | end) 35 | end) 36 | -------------------------------------------------------------------------------- /.tests/Vector3/clone.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.clone", function() 4 | it("should return a new Vector3 instance", function() 5 | local a = Vector3.new(3, 4, 5) 6 | 7 | local result = a:clone() 8 | result.x = 4 9 | assert.are.same(Vector3.new(3, 4, 5), a) 10 | assert.are.same(Vector3.new(4, 4, 5), result) 11 | end) 12 | it("should return a new Vector3 instance for a different vector", function() 13 | local a = Vector3.new(-99, 66, 88) 14 | 15 | local result = a:clone() 16 | result.y = -44 17 | assert.are.same(Vector3.new(-99, 66, 88), a) 18 | assert.are.same(Vector3.new(-99, -44, 88), result) 19 | end) 20 | end) 21 | -------------------------------------------------------------------------------- /.tests/Vector3/concat.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.__concat", function() 4 | it("should work when concatenating a Vector3 + Vector3", function() 5 | local a = Vector3.new(3, 4, 5) 6 | local b = Vector3.new(6, 7, 8) 7 | 8 | assert.are.is_true( 9 | type(tostring(a .. b)) == "string" 10 | ) 11 | end) 12 | it("should work when concatenating a string + Vector3", function() 13 | local a = "yay" 14 | local b = Vector3.new(6, 7, 8) 15 | 16 | assert.are.is_true( 17 | type(tostring(a .. b)) == "string" 18 | ) 19 | end) 20 | it("should work when concatenating a Vector3 + string", function() 21 | local a = Vector3.new(3, 4, 5) 22 | local b = "yay" 23 | 24 | assert.are.is_true( 25 | type(tostring(a .. b)) == "string" 26 | ) 27 | end) 28 | end) 29 | -------------------------------------------------------------------------------- /.tests/Vector3/dot.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.dot", function() 4 | it("should work with a positive vector", function() 5 | local a = Vector3.new(3, 3, 3) 6 | local b = Vector3.new(4, 5, 6) 7 | assert.are.equal( 8 | 45, 9 | a:dot(b) 10 | ) 11 | end) 12 | it("should work with a negative vector", function() 13 | local a = Vector3.new(-4, -4, -4) 14 | local b = Vector3.new(4, 5, 6) 15 | assert.are.equal( 16 | -60, 17 | a:dot(b) 18 | ) 19 | end) 20 | it("should work with a mixed vector", function() 21 | local a = Vector3.new(-3, 3, -3) 22 | local b = Vector3.new(7, 8, 9) 23 | assert.are.equal( 24 | -24, 25 | a:dot(b) 26 | ) 27 | end) 28 | it("should work with the dot_product alias", function() 29 | local a = Vector3.new(-3, 3, -3) 30 | local b = Vector3.new(7, 8, 9) 31 | assert.are.equal( 32 | -24, 33 | a:dot_product(b) 34 | ) 35 | end) 36 | end) 37 | -------------------------------------------------------------------------------- /.tests/Vector3/equal.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.equals", function() 4 | it("should return true when identical", function() 5 | local a = Vector3.new(3, 4, 5) 6 | local b = Vector3.new(3, 4, 5) 7 | 8 | assert.are.same( 9 | true, 10 | a:equals(b) 11 | ) 12 | end) 13 | it("should return false when not identical", function() 14 | local a = Vector3.new(3, 4, 5) 15 | local b = Vector3.new(6, 7, 8) 16 | 17 | assert.are.same( 18 | false, 19 | a:equals(b) 20 | ) 21 | end) 22 | it("should return false when not identical x", function() 23 | local a = Vector3.new(3, 4, 5) 24 | local b = Vector3.new(4, 4, 5) 25 | 26 | assert.are.same( 27 | false, 28 | a:equals(b) 29 | ) 30 | end) 31 | it("should return false when not identical y", function() 32 | local a = Vector3.new(3, 4, 5) 33 | local b = Vector3.new(3, 5, 5) 34 | 35 | assert.are.same( 36 | false, 37 | a:equals(b) 38 | ) 39 | end) 40 | end) 41 | -------------------------------------------------------------------------------- /.tests/Vector3/expand_region.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.expand_region", function() 4 | it("should work with positive vectors", function() 5 | local a = Vector3.new(16, 64, 16) 6 | local b = Vector3.new(1, 4, 6) 7 | local target = Vector3.new(99, 99, 99) 8 | 9 | local result_a, result_b = target:expand_region(a, b) 10 | assert.are.same(Vector3.new(1, 4, 6), result_a) 11 | assert.are.same(Vector3.new(99, 99, 99), result_b) 12 | end) 13 | it("should work with mixed components", function() 14 | local a = Vector3.new(16, 1, 16) 15 | local b = Vector3.new(1, 4, 60) 16 | local target = Vector3.new(-99, -99, -99) 17 | 18 | local result_a, result_b = target:expand_region(a, b) 19 | assert.are.same(Vector3.new(-99, -99, -99), result_a) 20 | assert.are.same(Vector3.new(16, 4, 60), result_b) 21 | end) 22 | it("should work with negative vectors", function() 23 | local a = Vector3.new(-9, -16, -25) 24 | local b = Vector3.new(-3, -6, -2) 25 | local target = Vector3.new(-99, -99, -99) 26 | 27 | local result_a, result_b = target:expand_region(a, b) 28 | assert.are.same(Vector3.new(-99, -99, -99), result_a) 29 | assert.are.same(Vector3.new(-3, -6, -2), result_b) 30 | end) 31 | it("should return new Vector3 instances", function() 32 | local a = Vector3.new(16, 1, 16) 33 | local b = Vector3.new(1, 4, 60) 34 | local target = Vector3.new(99, 99, 99) 35 | 36 | local result_a, result_b = target:expand_region(a, b) 37 | assert.are.same(Vector3.new(1, 1, 16), result_a) 38 | assert.are.same(Vector3.new(99, 99, 99), result_b) 39 | 40 | result_a.y = 999 41 | result_b.y = 999 42 | 43 | assert.are.same(Vector3.new(16, 1, 16), a) 44 | assert.are.same(Vector3.new(1, 4, 60), b) 45 | assert.are.same(Vector3.new(1, 999, 16), result_a) 46 | assert.are.same(Vector3.new(99, 999, 99), result_b) 47 | end) 48 | end) 49 | -------------------------------------------------------------------------------- /.tests/Vector3/floor.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.floor", function() 4 | it("should floor a positive vector", function() 5 | local a = Vector3.new(3.1, 4.75, 5.9) 6 | assert.are.same( 7 | Vector3.new(3, 4, 5), 8 | a:floor() 9 | ) 10 | end) 11 | it("should floor a negative vector", function() 12 | local a = Vector3.new(-3.1, -4.2, -5.3) 13 | assert.are.same( 14 | Vector3.new(-4, -5, -6), 15 | a:floor() 16 | ) 17 | end) 18 | it("should work with integers", function() 19 | local a = Vector3.new(3, 4, 5) 20 | assert.are.same( 21 | Vector3.new(3, 4, 5), 22 | a:floor() 23 | ) 24 | end) 25 | it("should return a new Vector3 instance", function() 26 | local a = Vector3.new(3.1, 4.7, 5.99999) 27 | 28 | local result = a:floor() 29 | assert.are.same( 30 | Vector3.new(3, 4, 5), 31 | result 32 | ) 33 | assert.are_not.equal(result, a) 34 | end) 35 | end) 36 | -------------------------------------------------------------------------------- /.tests/Vector3/is_contained.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.is_contained", function() 4 | it("should return true when inside", function() 5 | local a = Vector3.new(3, 4, 5) 6 | local b = Vector3.new(30, 40, 50) 7 | local target = Vector3.new(6, 6, 6) 8 | 9 | assert.are.same( 10 | true, 11 | target:is_contained(a, b) 12 | ) 13 | end) 14 | it("should return false when outside x", function() 15 | local a = Vector3.new(3, 4, 5) 16 | local b = Vector3.new(30, 40, 50) 17 | local target = Vector3.new(60, 6, 6) 18 | 19 | assert.are.same( 20 | false, 21 | target:is_contained(a, b) 22 | ) 23 | end) 24 | it("should return false when outside y", function() 25 | local a = Vector3.new(3, 4, 5) 26 | local b = Vector3.new(30, 40, 50) 27 | local target = Vector3.new(6, 60, 6) 28 | 29 | assert.are.same( 30 | false, 31 | target:is_contained(a, b) 32 | ) 33 | end) 34 | it("should return false when outside z", function() 35 | local a = Vector3.new(3, 4, 5) 36 | local b = Vector3.new(30, 40, 50) 37 | local target = Vector3.new(6, 6, 60) 38 | 39 | assert.are.same( 40 | false, 41 | target:is_contained(a, b) 42 | ) 43 | end) 44 | end) 45 | -------------------------------------------------------------------------------- /.tests/Vector3/length.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | -- To find these numbers, in Javascript: 4 | -- function t(x) { return Math.sqrt((x*x)*3); } 5 | -- for(let i = 0; i < 1000000000; i++) { let r = t(i); if(Math.floor(r) === r) console.log(`i ${i}, r ${r}`); } 6 | 7 | 8 | describe("Vector3.length", function() 9 | it("should work with a positive vector", function() 10 | local a = Vector3.new(80198051, 80198051, 80198051) 11 | assert.are.equal( 12 | 138907099, 13 | a:length() 14 | ) 15 | end) 16 | it("should work with a negative vector", function() 17 | local a = Vector3.new(-189750626, -189750626, -189750626) 18 | assert.are.equal( 19 | 328657725, 20 | a:length() 21 | ) 22 | end) 23 | it("should work with a mixed vector", function() 24 | local a = Vector3.new(-371635731, 371635731, -371635731) 25 | assert.are.equal( 26 | 643691968, 27 | a:length() 28 | ) 29 | end) 30 | end) 31 | -------------------------------------------------------------------------------- /.tests/Vector3/length_squared.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.length_squared", function() 4 | it("should work with a positive vector", function() 5 | local a = Vector3.new(3, 3, 3) 6 | assert.are.equal( 7 | 27, 8 | a:length_squared() 9 | ) 10 | end) 11 | it("should work with a negative vector", function() 12 | local a = Vector3.new(-4, -4, -4) 13 | assert.are.equal( 14 | 48, 15 | a:length_squared() 16 | ) 17 | end) 18 | it("should work with a mixed vector", function() 19 | local a = Vector3.new(-3, 3, -3) 20 | assert.are.equal( 21 | 27, 22 | a:length_squared() 23 | ) 24 | end) 25 | end) 26 | -------------------------------------------------------------------------------- /.tests/Vector3/limit_to.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.limit_to", function() 4 | it("should limit_to a positive vector", function() 5 | local a = Vector3.new(801980510, 801980510, 801980510) 6 | assert.are.same( 7 | Vector3.new(80198051, 80198051, 80198051), 8 | a:limit_to(138907099) 9 | ) 10 | end) 11 | it("should limit_to a negative vector", function() 12 | local a = Vector3.new(-1897506260, -1897506260, -1897506260) 13 | assert.are.same( 14 | Vector3.new(-189750626, -189750626, -189750626), 15 | a:limit_to(328657725) 16 | ) 17 | end) 18 | it("should work if the length is borderline", function() 19 | local a = Vector3.new(80198051, 80198051, 80198051) 20 | assert.are.same( 21 | Vector3.new(80198051, 80198051, 80198051), 22 | a:limit_to(138907099) 23 | ) 24 | end) 25 | it("should not change anything if the length is smaller", function() 26 | local a = Vector3.new(3, 4, 5) 27 | assert.are.same( 28 | Vector3.new(3, 4, 5), 29 | a:limit_to(100) 30 | ) 31 | end) 32 | it("should return a new Vector3 instance", function() 33 | local a = Vector3.new(801980510, 801980510, 801980510) 34 | 35 | local result = a:limit_to(138907099) 36 | assert.are.same( 37 | Vector3.new(80198051, 80198051, 80198051), 38 | result 39 | ) 40 | a.x = 4 41 | assert.are.same(Vector3.new(4, 801980510, 801980510), a) 42 | assert.are.same(Vector3.new(80198051, 80198051, 80198051), result) 43 | end) 44 | it("should return a new Vector3 instance if the length is smaller", function() 45 | local a = Vector3.new(3, 4, 5) 46 | 47 | local result = a:limit_to(101) 48 | assert.are.same( 49 | Vector3.new(3, 4, 5), 50 | result 51 | ) 52 | a.x = 40 53 | assert.are.same(Vector3.new(40, 4, 5), a) 54 | assert.are.same(Vector3.new(3, 4, 5), result) 55 | end) 56 | end) 57 | -------------------------------------------------------------------------------- /.tests/Vector3/max.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.max", function() 4 | it("should work with positive vectors", function() 5 | local a = Vector3.new(16, 64, 16) 6 | local b = Vector3.new(1, 4, 6) 7 | 8 | assert.are.same( 9 | Vector3.new(16, 64, 16), 10 | Vector3.max(a, b) 11 | ) 12 | end) 13 | it("should work with mixed components", function() 14 | local a = Vector3.new(16, 1, 16) 15 | local b = Vector3.new(1, 4, 60) 16 | 17 | assert.are.same( 18 | Vector3.new(16, 4, 60), 19 | Vector3.max(a, b) 20 | ) 21 | end) 22 | it("should work with scalar numbers", function() 23 | local a = Vector3.new(16, 1, 16) 24 | local b = 2 25 | 26 | assert.are.same( 27 | Vector3.new(16, 2, 16), 28 | Vector3.max(a, b) 29 | ) 30 | end) 31 | it("should work with scalar numbers both ways around", function() 32 | local a = Vector3.new(16, 1, 16) 33 | local b = 2 34 | 35 | assert.are.same( 36 | Vector3.new(16, 2, 16), 37 | Vector3.max(b, a) 38 | ) 39 | end) 40 | it("should work with negative vectors", function() 41 | local a = Vector3.new(-9, -16, -25) 42 | local b = Vector3.new(-3, -6, -2) 43 | 44 | assert.are.same( 45 | Vector3.new(-3, -6, -2), 46 | Vector3.max(a, b) 47 | ) 48 | end) 49 | it("should return new Vector3 instances", function() 50 | local a = Vector3.new(16, 1, 16) 51 | local b = Vector3.new(1, 4, 60) 52 | 53 | local result = Vector3.max(a, b) 54 | assert.are.same(Vector3.new(16, 4, 60), result) 55 | 56 | result.y = 999 57 | 58 | assert.are.same(Vector3.new(16, 1, 16), a) 59 | assert.are.same(Vector3.new(1, 4, 60), b) 60 | assert.are.same(Vector3.new(16, 999, 60), result) 61 | end) 62 | end) 63 | -------------------------------------------------------------------------------- /.tests/Vector3/max_component.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.max_component", function() 4 | it("should work with a positive vector x", function() 5 | local a = Vector3.new(30, 4, 5) 6 | assert.are.equal( 7 | 30, 8 | a:max_component() 9 | ) 10 | end) 11 | it("should work with a positive vector y", function() 12 | local a = Vector3.new(3, 10, 5) 13 | assert.are.equal( 14 | 10, 15 | a:max_component() 16 | ) 17 | end) 18 | it("should work with a positive vector z", function() 19 | local a = Vector3.new(3, 1, 50.5) 20 | assert.are.equal( 21 | 50.5, 22 | a:max_component() 23 | ) 24 | end) 25 | it("should work with a negative vector", function() 26 | local a = Vector3.new(-4, -5, -1) 27 | assert.are.equal( 28 | -1, 29 | a:max_component() 30 | ) 31 | end) 32 | it("should work with a mixed vector", function() 33 | local a = Vector3.new(-30, 3, -3) 34 | assert.are.equal( 35 | 3, 36 | a:max_component() 37 | ) 38 | end) 39 | end) 40 | -------------------------------------------------------------------------------- /.tests/Vector3/mean.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.mean", function() 4 | it("should work with a positive vector", function() 5 | local a = Vector3.new(2, 2, 2) 6 | local b = Vector3.new(4, 4, 4) 7 | assert.are.same( 8 | Vector3.new(3, 3, 3), 9 | a:mean(b) 10 | ) 11 | end) 12 | it("should work with a positive vector the other way around", function() 13 | local a = Vector3.new(2, 2, 2) 14 | local b = Vector3.new(4, 4, 4) 15 | assert.are.same( 16 | Vector3.new(3, 3, 3), 17 | b:mean(a) 18 | ) 19 | end) 20 | it("should mean another positive vector", function() 21 | local a = Vector3.new(6, 6, 6) 22 | local b = Vector3.new(10, 10, 10) 23 | assert.are.same( 24 | Vector3.new(8, 8, 8), 25 | a:mean(b) 26 | ) 27 | end) 28 | it("should mean a negative vector", function() 29 | local a = Vector3.new(-2, -2, -2) 30 | local b = Vector3.new(0, 0, 0) 31 | assert.are.same( 32 | Vector3.new(-1, -1, -1), 33 | a:mean(b) 34 | ) 35 | end) 36 | it("should return a new Vector3 instance", function() 37 | local a = Vector3.new(6, 6, 6) 38 | local b = Vector3.new(10, 10, 10) 39 | assert.are.same( 40 | Vector3.new(8, 8, 8), 41 | a:mean(b) 42 | ) 43 | 44 | assert.are.same(Vector3.new(6, 6, 6), a) 45 | assert.are.same(Vector3.new(10, 10, 10), b) 46 | end) 47 | end) 48 | -------------------------------------------------------------------------------- /.tests/Vector3/min.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.min", function() 4 | it("should work with positive vectors", function() 5 | local a = Vector3.new(16, 64, 16) 6 | local b = Vector3.new(1, 4, 6) 7 | 8 | assert.are.same( 9 | Vector3.new(1, 4, 6), 10 | Vector3.min(a, b) 11 | ) 12 | end) 13 | it("should work with mixed components", function() 14 | local a = Vector3.new(16, 1, 16) 15 | local b = Vector3.new(1, 4, 60) 16 | 17 | assert.are.same( 18 | Vector3.new(1, 1, 16), 19 | Vector3.min(a, b) 20 | ) 21 | end) 22 | it("should work with scalar numbers", function() 23 | local a = Vector3.new(16, 1, 16) 24 | local b = 2 25 | 26 | assert.are.same( 27 | Vector3.new(2, 1, 2), 28 | Vector3.min(a, b) 29 | ) 30 | end) 31 | it("should work with scalar numbers both ways around", function() 32 | local a = Vector3.new(16, 1, 16) 33 | local b = 2 34 | 35 | assert.are.same( 36 | Vector3.new(2, 1, 2), 37 | Vector3.min(b, a) 38 | ) 39 | end) 40 | it("should work with negative vectors", function() 41 | local a = Vector3.new(-9, -16, -25) 42 | local b = Vector3.new(-3, -6, -2) 43 | 44 | assert.are.same( 45 | Vector3.new(-9, -16, -25), 46 | Vector3.min(a, b) 47 | ) 48 | end) 49 | it("should return new Vector3 instances", function() 50 | local a = Vector3.new(16, 1, 16) 51 | local b = Vector3.new(1, 4, 60) 52 | 53 | local result = Vector3.min(a, b) 54 | assert.are.same(Vector3.new(1, 1, 16), result) 55 | 56 | result.y = 999 57 | 58 | assert.are.same(Vector3.new(16, 1, 16), a) 59 | assert.are.same(Vector3.new(1, 4, 60), b) 60 | assert.are.same(Vector3.new(1, 999, 16), result) 61 | end) 62 | end) 63 | -------------------------------------------------------------------------------- /.tests/Vector3/min_component.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.min_component", function() 4 | it("should work with a positive vector x", function() 5 | local a = Vector3.new(3, 4, 5) 6 | assert.are.equal( 7 | 3, 8 | a:min_component() 9 | ) 10 | end) 11 | it("should work with a positive vector y", function() 12 | local a = Vector3.new(3, 1, 5) 13 | assert.are.equal( 14 | 1, 15 | a:min_component() 16 | ) 17 | end) 18 | it("should work with a positive vector z", function() 19 | local a = Vector3.new(3, 1, 0.5) 20 | assert.are.equal( 21 | 0.5, 22 | a:min_component() 23 | ) 24 | end) 25 | it("should work with a negative vector", function() 26 | local a = Vector3.new(-4, -5, -46) 27 | assert.are.equal( 28 | -46, 29 | a:min_component() 30 | ) 31 | end) 32 | it("should work with a mixed vector", function() 33 | local a = Vector3.new(-30, 3, -3) 34 | assert.are.equal( 35 | -30, 36 | a:min_component() 37 | ) 38 | end) 39 | end) 40 | -------------------------------------------------------------------------------- /.tests/Vector3/move_towards.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | -- To find these numbers, in Javascript: 4 | -- function t(x) { return Math.sqrt((x*x)*3); } 5 | -- for(let i = 0; i < 1000000000; i++) { let r = t(i); if(Math.floor(r) === r) console.log(`i ${i}, r ${r}`); } 6 | 7 | 8 | describe("Vector3.move_towards", function() 9 | it("should work with a positive vector", function() 10 | local a = Vector3.new(3, 4, 5) 11 | local b = Vector3.new(10, 10, 10) 12 | assert.are.same( 13 | Vector3.new(5.0022714374157439821, 5.7162326606420661435, 6.4301938838683883048), 14 | a:move_towards(b, 3) 15 | ) 16 | end) 17 | end) 18 | -------------------------------------------------------------------------------- /.tests/Vector3/multiply.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.multiply", function() 4 | it("should multiply 2 positive vectors", function() 5 | local a = Vector3.new(3, 4, 5) 6 | local b = Vector3.new(2, 2, 2) 7 | assert.are.same( 8 | Vector3.new(6, 8, 10), 9 | a:multiply(b) 10 | ) 11 | end) 12 | it("should work with the mul alias", function() 13 | local a = Vector3.new(3, 4, 5) 14 | local b = Vector3.new(2, 2, 2) 15 | assert.are.same( 16 | Vector3.new(6, 8, 10), 17 | a:mul(b) 18 | ) 19 | end) 20 | it("should work with scalar a", function() 21 | local a = 2 22 | local b = Vector3.new(6, 7, 8) 23 | assert.are.same( 24 | Vector3.new(12, 14, 16), 25 | a * b 26 | ) 27 | end) 28 | it("should work with scalar b", function() 29 | local a = Vector3.new(6, 7, 8) 30 | local b = 2 31 | assert.are.same( 32 | Vector3.new(12, 14, 16), 33 | a * b 34 | ) 35 | end) 36 | it("should support the multiply operator", function() 37 | local a = Vector3.new(3, 4, 5) 38 | local b = Vector3.new(2, 2, 2) 39 | assert.are.same( 40 | Vector3.new(6, 8, 10), 41 | a * b 42 | ) 43 | end) 44 | it("should handle negative b", function() 45 | local a = Vector3.new(3, 4, 5) 46 | local b = Vector3.new(-1, -1, -1) 47 | assert.are.same( 48 | Vector3.new(-3, -4, -5), 49 | a * b 50 | ) 51 | end) 52 | it("should handle negative a", function() 53 | local a = Vector3.new(-3, -4, -5) 54 | local b = Vector3.new(2, 2, 2) 55 | assert.are.same( 56 | Vector3.new(-6, -8, -10), 57 | a * b 58 | ) 59 | end) 60 | it("should handle negative a and b", function() 61 | local a = Vector3.new(-3, -4, -5) 62 | local b = Vector3.new(-2, -2, -2) 63 | assert.are.same( 64 | Vector3.new(6, 8, 10), 65 | a * b 66 | ) 67 | end) 68 | it("should return a new Vector3 instance", function() 69 | local a = Vector3.new(3, 4, 5) 70 | local b = Vector3.new(3, 3, 3) 71 | 72 | local result = a * b 73 | assert.are.same( 74 | Vector3.new(9, 12, 15), 75 | result 76 | ) 77 | assert.are_not.equal(result, a) 78 | assert.are_not.equal(result, b) 79 | end) 80 | end) 81 | -------------------------------------------------------------------------------- /.tests/Vector3/new.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.add", function() 4 | it("should create a new Vector3", function() 5 | assert.are.same( 6 | { x = 3, y = 4, z = 5 }, 7 | Vector3.new(3, 4, 5) 8 | ) 9 | end) 10 | it("should default to (0, 0, 0)", function() 11 | assert.are.same( 12 | { x = 0, y = 0, z = 0 }, 13 | Vector3.new() 14 | ) 15 | end) 16 | it("should not throw an error on invalid x", function() 17 | assert.has_no.errors(function() 18 | Vector3.new("cheese", 4, 5) 19 | end) 20 | end) 21 | it("should not throw an error on invalid y", function() 22 | assert.has_no.errors(function() 23 | Vector3.new(4, "cheese", 5) 24 | end) 25 | end) 26 | it("should not throw an error on invalid z", function() 27 | assert.has_no.errors(function() 28 | Vector3.new(66, 2, "cheese") 29 | end) 30 | end) 31 | end) 32 | -------------------------------------------------------------------------------- /.tests/Vector3/round.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.round", function() 4 | it("should round a positive vector", function() 5 | local a = Vector3.new(3.1, 4.75, 5.9) 6 | assert.are.same( 7 | Vector3.new(3, 5, 6), 8 | a:round() 9 | ) 10 | end) 11 | it("should round a negative vector", function() 12 | local a = Vector3.new(-3.1, -4.2, -5.3) 13 | assert.are.same( 14 | Vector3.new(-3, -4, -5), 15 | a:round() 16 | ) 17 | end) 18 | it("should work with integers", function() 19 | local a = Vector3.new(3, 4, 5) 20 | assert.are.same( 21 | Vector3.new(3, 4, 5), 22 | a:round() 23 | ) 24 | end) 25 | it("should return a new Vector3 instance", function() 26 | local a = Vector3.new(3.1, 4.7, 5.99999) 27 | 28 | local result = a:round() 29 | assert.are.same( 30 | Vector3.new(3, 5, 6), 31 | result 32 | ) 33 | assert.are_not.equal(result, a) 34 | end) 35 | end) 36 | -------------------------------------------------------------------------------- /.tests/Vector3/set_to.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.set_to", function() 4 | it("should set_to a positive vector", function() 5 | local a = Vector3.new(801980510, 801980510, 801980510) 6 | assert.are.same( 7 | Vector3.new(80198051, 80198051, 80198051), 8 | a:set_to(138907099) 9 | ) 10 | end) 11 | it("should set_to a negative vector", function() 12 | local a = Vector3.new(-1897506260, -1897506260, -1897506260) 13 | assert.are.same( 14 | Vector3.new(-189750626, -189750626, -189750626), 15 | a:set_to(328657725) 16 | ) 17 | end) 18 | it("should work if the length is borderline", function() 19 | local a = Vector3.new(80198051, 80198051, 80198051) 20 | assert.are.same( 21 | Vector3.new(80198051, 80198051, 80198051), 22 | a:set_to(138907099) 23 | ) 24 | end) 25 | it("should work if the length is smaller", function() 26 | local a = Vector3.new(80198051, 80198051, 80198051) 27 | assert.are.same( 28 | Vector3.new(109552575, 109552575, 109552575), 29 | a:set_to(189750626):floor() -- Hack to ignore flating-point errors. In theory we should really use epsilon here instead 30 | ) 31 | end) 32 | it("should return a new Vector3 instance", function() 33 | local a = Vector3.new(801980510, 801980510, 801980510) 34 | 35 | local result = a:set_to(138907099) 36 | assert.are.same( 37 | Vector3.new(80198051, 80198051, 80198051), 38 | result 39 | ) 40 | assert.are_not.equal(result, a) 41 | end) 42 | it("should return a new Vector3 instance if the length is smaller", function() 43 | local a = Vector3.new(80198051, 80198051, 80198051) 44 | 45 | local result = a:set_to(189750626):floor() 46 | assert.are.same( 47 | Vector3.new(109552575, 109552575, 109552575), 48 | result 49 | ) 50 | assert.are_not.equal(result, a) 51 | end) 52 | end) 53 | -------------------------------------------------------------------------------- /.tests/Vector3/snap_to.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.snap_to", function() 4 | it("should snap_to a positive vector", function() 5 | local a = Vector3.new(3.1, 4.75, 5.9) 6 | assert.are.same( 7 | Vector3.new(0, 0, 10), 8 | a:snap_to(10) 9 | ) 10 | end) 11 | it("should snap_to a negative vector", function() 12 | local a = Vector3.new(-2.5, -4.2, -5.3) 13 | assert.are.same( 14 | Vector3.new(0, -6, -6), 15 | a:snap_to(6) 16 | ) 17 | end) 18 | it("should work with integers", function() 19 | local a = Vector3.new(3, 4, 5) 20 | assert.are.same( 21 | Vector3.new(3, 3, 6), 22 | a:snap_to(3) 23 | ) 24 | end) 25 | it("should return a new Vector3 instance", function() 26 | local a = Vector3.new(3.1, 4.7, 5.99999) 27 | 28 | local result = a:snap_to(3) 29 | assert.are.same( 30 | Vector3.new(3, 6, 6), 31 | result 32 | ) 33 | assert.are_not.equal(result, a) 34 | end) 35 | end) 36 | -------------------------------------------------------------------------------- /.tests/Vector3/sort_pos.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.sort", function() 4 | it("should work with positive vectors", function() 5 | local a = Vector3.new(16, 64, 16) 6 | local b = Vector3.new(1, 4, 6) 7 | 8 | local result_a, result_b = Vector3.sort(a, b) 9 | assert.are.same(Vector3.new(1, 4, 6), result_a) 10 | assert.are.same(Vector3.new(16, 64, 16), result_b) 11 | end) 12 | it("should work with mixed components", function() 13 | local a = Vector3.new(16, 1, 16) 14 | local b = Vector3.new(1, 4, 60) 15 | 16 | local result_a, result_b = Vector3.sort(a, b) 17 | assert.are.same(Vector3.new(1, 1, 16), result_a) 18 | assert.are.same(Vector3.new(16, 4, 60), result_b) 19 | end) 20 | it("should work with negative vectors", function() 21 | local a = Vector3.new(-9, -16, -25) 22 | local b = Vector3.new(-3, -6, -2) 23 | 24 | local result_a, result_b = Vector3.sort(a, b) 25 | assert.are.same(Vector3.new(-9, -16, -25), result_a) 26 | assert.are.same(Vector3.new(-3, -6, -2), result_b) 27 | end) 28 | it("should return new Vector3 instances", function() 29 | local a = Vector3.new(16, 1, 16) 30 | local b = Vector3.new(1, 4, 60) 31 | 32 | local result_a, result_b = Vector3.sort(a, b) 33 | assert.are.same(Vector3.new(1, 1, 16), result_a) 34 | assert.are.same(Vector3.new(16, 4, 60), result_b) 35 | 36 | result_a.y = 999 37 | result_b.y = 999 38 | 39 | assert.are.same(Vector3.new(16, 1, 16), a) 40 | assert.are.same(Vector3.new(1, 4, 60), b) 41 | assert.are.same(Vector3.new(1, 999, 16), result_a) 42 | assert.are.same(Vector3.new(16, 999, 60), result_b) 43 | end) 44 | end) 45 | -------------------------------------------------------------------------------- /.tests/Vector3/sqrt.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.sqrt", function() 4 | it("should sqrt a positive vector", function() 5 | local a = Vector3.new(16, 64, 16) 6 | assert.are.same( 7 | Vector3.new(4, 8, 4), 8 | a:sqrt() 9 | ) 10 | end) 11 | it("should sqrt another positive vector", function() 12 | local a = Vector3.new(9, 16, 25) 13 | assert.are.same( 14 | Vector3.new(3, 4, 5), 15 | a:sqrt() 16 | ) 17 | end) 18 | it("should return a new Vector3 instance", function() 19 | local a = Vector3.new(9, 16, 25) 20 | 21 | local result = a:sqrt() 22 | assert.are.same( 23 | Vector3.new(3, 4, 5), 24 | result 25 | ) 26 | assert.are_not.equal(result, a) 27 | end) 28 | end) 29 | -------------------------------------------------------------------------------- /.tests/Vector3/subtract.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.subtract", function() 4 | it("should subtract 2 positive vectors", function() 5 | local a = Vector3.new(3, 4, 5) 6 | local b = Vector3.new(1, 1, 1) 7 | assert.are.same( 8 | Vector3.new(2, 3, 4), 9 | a:subtract(b) 10 | ) 11 | end) 12 | it("should work with the sub alias", function() 13 | local a = Vector3.new(3, 4, 5) 14 | local b = Vector3.new(1, 1, 1) 15 | assert.are.same( 16 | Vector3.new(2, 3, 4), 17 | a:sub(b) 18 | ) 19 | end) 20 | it("should work with scalar a", function() 21 | local a = 2 22 | local b = Vector3.new(6, 7, 8) 23 | assert.are.same( 24 | Vector3.new(4, 5, 6), 25 | a - b 26 | ) 27 | end) 28 | it("should work with scalar b", function() 29 | local a = Vector3.new(6, 7, 8) 30 | local b = 2 31 | assert.are.same( 32 | Vector3.new(4, 5, 6), 33 | a - b 34 | ) 35 | end) 36 | it("should support the subtract operator", function() 37 | local a = Vector3.new(3, 4, 5) 38 | local b = Vector3.new(1, 1, 1) 39 | assert.are.same( 40 | Vector3.new(2, 3, 4), 41 | a - b 42 | ) 43 | end) 44 | it("should handle negative b", function() 45 | local a = Vector3.new(3, 4, 5) 46 | local b = Vector3.new(-1, -1, -1) 47 | assert.are.same( 48 | Vector3.new(4, 5, 6), 49 | a - b 50 | ) 51 | end) 52 | it("should handle negative a", function() 53 | local a = Vector3.new(-3, -4, -5) 54 | local b = Vector3.new(1, 1, 1) 55 | assert.are.same( 56 | Vector3.new(-4, -5, -6), 57 | a - b 58 | ) 59 | end) 60 | it("should handle negative a and b", function() 61 | local a = Vector3.new(-3, -4, -5) 62 | local b = Vector3.new(-1, -1, -1) 63 | assert.are.same( 64 | Vector3.new(-2, -3, -4), 65 | a - b 66 | ) 67 | end) 68 | it("should return a new Vector3 instance", function() 69 | local a = Vector3.new(3, 4, 5) 70 | local b = Vector3.new(1, 1, 1) 71 | 72 | local result = a - b 73 | assert.are.same( 74 | Vector3.new(2, 3, 4), 75 | result 76 | ) 77 | assert.are_not.equal(result, a) 78 | assert.are_not.equal(result, b) 79 | end) 80 | end) 81 | -------------------------------------------------------------------------------- /.tests/Vector3/tostring.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.__tostring", function() 4 | it("should stringify a Vector3", function() 5 | local a = Vector3.new(3, 4, 5) 6 | assert.are.same( 7 | "(3, 4, 5)", 8 | a:__tostring() 9 | ) 10 | end) 11 | it("should implicitly stringify a Vector3", function() 12 | local a = Vector3.new(3, 4, 5) 13 | assert.are.same( 14 | "(3, 4, 5)", 15 | tostring(a) 16 | ) 17 | end) 18 | it("should implicitly stringify another Vector3", function() 19 | local a = Vector3.new(55, 77, 22) 20 | assert.are.same( 21 | "(55, 77, 22)", 22 | tostring(a) 23 | ) 24 | end) 25 | it("should handle negative numbers", function() 26 | local a = Vector3.new(-1, -2, -3) 27 | assert.are.same( 28 | "(-1, -2, -3)", 29 | tostring(a) 30 | ) 31 | end) 32 | it("should handle a mix of positive and negative numbers", function() 33 | local a = Vector3.new(-7, 2, -99) 34 | assert.are.same( 35 | "(-7, 2, -99)", 36 | tostring(a) 37 | ) 38 | end) 39 | end) 40 | -------------------------------------------------------------------------------- /.tests/Vector3/unit.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | -- To find these numbers, in Javascript: 4 | -- function t(x) { return Math.sqrt((x*x)*3); } 5 | -- for(let i = 0; i < 1000000000; i++) { let r = t(i); if(Math.floor(r) === r) console.log(`i ${i}, r ${r}`); } 6 | 7 | 8 | describe("Vector3.unit", function() 9 | it("should work with a positive vector", function() 10 | local a = Vector3.new(10, 10, 10) 11 | assert.are.same( 12 | Vector3.new(57735, 57735, 57735), 13 | a:unit():multiply(100000):floor() 14 | ) 15 | end) 16 | it("should work with a the normalise alias", function() 17 | local a = Vector3.new(10, 10, 10) 18 | assert.are.same( 19 | Vector3.new(57735, 57735, 57735), 20 | a:normalise():multiply(100000):floor() 21 | ) 22 | end) 23 | it("should work with a negative vector", function() 24 | local a = Vector3.new(10, 10, 10) 25 | assert.are.same( 26 | Vector3.new(57735, 57735, 57735), 27 | a:unit():multiply(100000):floor() 28 | ) 29 | end) 30 | it("should work with a mixed vector", function() 31 | local a = Vector3.new(-371635731, 371635731, -371635731) 32 | assert.are.same( 33 | Vector3.new(-57736, 57735, -57736), 34 | a:unit():multiply(100000):floor() 35 | ) 36 | end) 37 | end) 38 | -------------------------------------------------------------------------------- /.tests/Vector3/volume.test.lua: -------------------------------------------------------------------------------- 1 | local Vector3 = require("worldeditadditions_core.utils.vector3") 2 | 3 | describe("Vector3.volume", function() 4 | it("should work", function() 5 | local a = Vector3.new(4, 4, 4) 6 | local b = Vector3.new(8, 8, 8) 7 | assert.are.equal( 8 | 64, 9 | Vector3.volume(a, b) 10 | ) 11 | end) 12 | it("should work the other way around", function() 13 | local a = Vector3.new(4, 4, 4) 14 | local b = Vector3.new(8, 8, 8) 15 | assert.are.equal( 16 | 64, 17 | Vector3.volume(b, a) 18 | ) 19 | end) 20 | it("should work with negative values", function() 21 | local a = Vector3.new(-4, -4, -4) 22 | local b = Vector3.new(-8, -8, -8) 23 | assert.are.equal( 24 | 64, 25 | Vector3.volume(a, b) 26 | ) 27 | end) 28 | it("should work with different values", function() 29 | local a = Vector3.new(5, 6, 7) 30 | local b = Vector3.new(10, 10, 10) 31 | assert.are.equal( 32 | 60, 33 | Vector3.volume(a, b) 34 | ) 35 | end) 36 | it("should work with mixed values", function() 37 | local a = Vector3.new(10, 5, 8) 38 | local b = Vector3.new(5, 10, 2) 39 | assert.are.equal( 40 | 150, 41 | Vector3.volume(a, b) 42 | ) 43 | end) 44 | end) 45 | -------------------------------------------------------------------------------- /.tests/parse/chance.test.lua: -------------------------------------------------------------------------------- 1 | local parse_chance = require("worldeditadditions_core.utils.parse.chance") 2 | 3 | describe("parse.chance", function() 4 | it("should work in 1-in-n mode by default", function() 5 | local source = "50%" 6 | 7 | assert.are.equal( 8 | 2, 9 | parse_chance(source) 10 | ) 11 | end) 12 | it("should work with a different value in 1-in-n mode", function() 13 | local source = "25%" 14 | 15 | assert.are.equal( 16 | 4, 17 | parse_chance(source) 18 | ) 19 | end) 20 | it("should work in weight mode", function() 21 | local source = "50%" 22 | 23 | assert.are.equal( 24 | 2, 25 | parse_chance(source, "weight") 26 | ) 27 | end) 28 | it("should work in weight mode with different number", function() 29 | local source = "90%" 30 | 31 | assert.are.equal( 32 | 10, 33 | parse_chance(source, "weight") 34 | ) 35 | end) 36 | end) 37 | -------------------------------------------------------------------------------- /.tests/parse/seed.test.lua: -------------------------------------------------------------------------------- 1 | local parse_seed = require("worldeditadditions_core.utils.parse.seed") 2 | 3 | describe("parse.seed", function() 4 | it("should work", function() 5 | local source = "a test string" 6 | 7 | local result = parse_seed(source) 8 | 9 | assert.are.equal( 10 | "number", 11 | type(result) 12 | ) 13 | end) 14 | it("should work with a long string", function() 15 | local source = "If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction. If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction. If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction. If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction. If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction. If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction. If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction. If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction. If it looks like a duck and quacks like a duck but it needs batteries, you probably have the wrong abstraction." 16 | 17 | local result = parse_seed(source) 18 | 19 | assert.are.equal( 20 | "number", 21 | type(result) 22 | ) 23 | end) 24 | end) 25 | -------------------------------------------------------------------------------- /.tests/parse/table/makeset.test.lua: -------------------------------------------------------------------------------- 1 | local makeset = require("worldeditadditions_core.utils.table.makeset") 2 | 3 | describe("table.makeset", function() 4 | it("should work with a single item", function() 5 | local result = makeset({ "apples" }) 6 | assert.are.same( 7 | { apples = true }, 8 | result 9 | ) 10 | end) 11 | it("should work with 2 items", function() 12 | local result = makeset({ "apples", "orange" }) 13 | assert.are.same( 14 | { apples = true, orange = true }, 15 | result 16 | ) 17 | end) 18 | it("should work with duplicate items", function() 19 | local result = makeset({ "apples", "apples" }) 20 | assert.are.same( 21 | { apples = true }, 22 | result 23 | ) 24 | end) 25 | it("should work with duplicate items and non-duplicate items", function() 26 | local result = makeset({ "apples", "oranges", "apples" }) 27 | assert.are.same( 28 | { apples = true, oranges = true }, 29 | result 30 | ) 31 | end) 32 | end) 33 | -------------------------------------------------------------------------------- /.tests/parse/table/table_apply.test.lua: -------------------------------------------------------------------------------- 1 | local apply = require("worldeditadditions_core.utils.table.table_apply") 2 | 3 | describe("table.makeset", function() 4 | it("should work", function() 5 | local source = { apples = 4 } 6 | local target = { oranges = 3 } 7 | 8 | apply(source, target) 9 | 10 | assert.are.same( 11 | { apples = 4, oranges = 3 }, 12 | target 13 | ) 14 | end) 15 | it("should overwrite values in target", function() 16 | local source = { apples = 4 } 17 | local target = { apples = 3 } 18 | 19 | apply(source, target) 20 | 21 | assert.are.same( 22 | { apples = 4 }, 23 | target 24 | ) 25 | end) 26 | it("should work with strings", function() 27 | local source = { apples = "4" } 28 | local target = { oranges = "3" } 29 | 30 | apply(source, target) 31 | 32 | assert.are.same( 33 | { apples = "4", oranges = "3" }, 34 | target 35 | ) 36 | end) 37 | it("should overwrite values in target with strings", function() 38 | local source = { apples = "4" } 39 | local target = { apples = "3" } 40 | 41 | apply(source, target) 42 | 43 | assert.are.same( 44 | { apples = "4" }, 45 | target 46 | ) 47 | end) 48 | end) 49 | -------------------------------------------------------------------------------- /.tests/parse/table/table_contains.test.lua: -------------------------------------------------------------------------------- 1 | local contains = require("worldeditadditions_core.utils.table.table_contains") 2 | 3 | describe("table.makeset", function() 4 | it("should work with a string", function() 5 | assert.are.same( 6 | true, 7 | contains({ "apples" }, "apples") 8 | ) 9 | end) 10 | it("should work with a number", function() 11 | assert.are.same( 12 | true, 13 | contains({ 4 }, 4) 14 | ) 15 | end) 16 | it("should return false if a number doesn't exist", function() 17 | assert.are.same( 18 | false, 19 | contains({ 5 }, 4) 20 | ) 21 | end) 22 | it("should work with a string and multiple items in the table", function() 23 | assert.are.same( 24 | true, 25 | contains({ "yay", "apples", "cat" }, "apples") 26 | ) 27 | end) 28 | it("should return false if it doesn't exist", function() 29 | assert.are.same( 30 | false, 31 | contains({ "yay" }, "orange") 32 | ) 33 | end) 34 | it("should return false if it doesn't exist wiith multiple items", function() 35 | assert.are.same( 36 | false, 37 | contains({ "yay", "apples", "cat" }, "orange") 38 | ) 39 | end) 40 | end) 41 | -------------------------------------------------------------------------------- /.tests/strings/str_ends.test.lua: -------------------------------------------------------------------------------- 1 | local polyfill = require("worldeditadditions_core.utils.strings.polyfill") 2 | 3 | describe("str_ends", function() 4 | it("should return true for a single character", function() 5 | assert.are.equal( 6 | true, 7 | polyfill.str_ends("test", "t") 8 | ) 9 | end) 10 | it("should return true for a multiple characters", function() 11 | assert.are.equal( 12 | true, 13 | polyfill.str_ends("test", "st") 14 | ) 15 | end) 16 | it("should return true for identical strings", function() 17 | assert.are.equal( 18 | true, 19 | polyfill.str_ends("test", "test") 20 | ) 21 | end) 22 | it("should return false for a single character ", function() 23 | assert.are.equal( 24 | false, 25 | polyfill.str_ends("test", "y") 26 | ) 27 | end) 28 | it("should return false for a character present elsewherer", function() 29 | assert.are.equal( 30 | false, 31 | polyfill.str_ends("test", "e") 32 | ) 33 | end) 34 | it("should return false for another substring", function() 35 | assert.are.equal( 36 | false, 37 | polyfill.str_ends("test", "tes") 38 | ) 39 | end) 40 | end) 41 | -------------------------------------------------------------------------------- /.tests/strings/str_padend.test.lua: -------------------------------------------------------------------------------- 1 | local polyfill = require("worldeditadditions_core.utils.strings.polyfill") 2 | 3 | describe("str_padend", function() 4 | it("should pad a string", function() 5 | assert.are.equal( 6 | polyfill.str_padend("test", 5, " "), 7 | "test " 8 | ) 9 | end) 10 | it("should pad a different string", function() 11 | assert.are.equal( 12 | polyfill.str_padend("yay", 4, " "), 13 | "yay " 14 | ) 15 | end) 16 | it("should pad a string with multiple characters", function() 17 | assert.are.equal( 18 | polyfill.str_padend("test", 10, " "), 19 | "test " 20 | ) 21 | end) 22 | it("should not pad a long string", function() 23 | assert.are.equal( 24 | polyfill.str_padend("testtest", 5, " "), 25 | "testtest" 26 | ) 27 | end) 28 | it("should pad with other characters", function() 29 | assert.are.equal( 30 | polyfill.str_padend("1", 2, "0"), 31 | "10" 32 | ) 33 | end) 34 | it("should pad with multiple other characters", function() 35 | assert.are.equal( 36 | polyfill.str_padend("1", 5, "0"), 37 | "10000" 38 | ) 39 | end) 40 | end) 41 | -------------------------------------------------------------------------------- /.tests/strings/str_padstart.test.lua: -------------------------------------------------------------------------------- 1 | local polyfill = require("worldeditadditions_core.utils.strings.polyfill") 2 | 3 | describe("str_padstart", function() 4 | it("should pad a string", function() 5 | assert.are.equal( 6 | polyfill.str_padstart("test", 5, " "), 7 | " test" 8 | ) 9 | end) 10 | it("should pad a different string", function() 11 | assert.are.equal( 12 | polyfill.str_padstart("yay", 4, " "), 13 | " yay" 14 | ) 15 | end) 16 | it("should pad a string with multiple characters", function() 17 | assert.are.equal( 18 | polyfill.str_padstart("test", 10, " "), 19 | " test" 20 | ) 21 | end) 22 | it("should not pad a long string", function() 23 | assert.are.equal( 24 | polyfill.str_padstart("testtest", 5, " "), 25 | "testtest" 26 | ) 27 | end) 28 | it("should pad with other characters", function() 29 | assert.are.equal( 30 | polyfill.str_padstart("1", 2, "0"), 31 | "01" 32 | ) 33 | end) 34 | it("should pad with multiple other characters", function() 35 | assert.are.equal( 36 | polyfill.str_padstart("1", 5, "0"), 37 | "00001" 38 | ) 39 | end) 40 | end) 41 | -------------------------------------------------------------------------------- /.tests/strings/str_starts.test.lua: -------------------------------------------------------------------------------- 1 | local polyfill = require("worldeditadditions_core.utils.strings.polyfill") 2 | 3 | describe("str_starts", function() 4 | it("should return true for a single character", function() 5 | assert.are.equal( 6 | true, 7 | polyfill.str_starts("test", "t") 8 | ) 9 | end) 10 | it("should return true for a multiple characters", function() 11 | assert.are.equal( 12 | true, 13 | polyfill.str_starts("test", "te") 14 | ) 15 | end) 16 | it("should return true for identical strings", function() 17 | assert.are.equal( 18 | true, 19 | polyfill.str_starts("test", "test") 20 | ) 21 | end) 22 | it("should return false for a single character ", function() 23 | assert.are.equal( 24 | false, 25 | polyfill.str_starts("test", "y") 26 | ) 27 | end) 28 | it("should return false for a character present elsewherer", function() 29 | assert.are.equal( 30 | false, 31 | polyfill.str_starts("test", "e") 32 | ) 33 | end) 34 | it("should return false for another substring", function() 35 | assert.are.equal( 36 | false, 37 | polyfill.str_starts("test", "est") 38 | ) 39 | end) 40 | end) 41 | -------------------------------------------------------------------------------- /.tests/strings/trim.test.lua: -------------------------------------------------------------------------------- 1 | local polyfill = require("worldeditadditions_core.utils.strings.polyfill") 2 | 3 | describe("trim", function() 4 | it("work for a string that's already trimmed", function() 5 | assert.are.equal( 6 | polyfill.trim("test"), 7 | "test" 8 | ) 9 | end) 10 | it("trim from the start", function() 11 | assert.are.equal( 12 | polyfill.trim(" test"), 13 | "test" 14 | ) 15 | end) 16 | it("trim from the end", function() 17 | assert.are.equal( 18 | polyfill.trim("test "), 19 | "test" 20 | ) 21 | end) 22 | it("trim from both ends", function() 23 | assert.are.equal( 24 | polyfill.trim(" test "), 25 | "test" 26 | ) 27 | end) 28 | it("trim another string", function() 29 | assert.are.equal( 30 | polyfill.trim("yay "), 31 | "yay" 32 | ) 33 | end) 34 | it("trim tabs", function() 35 | assert.are.equal( 36 | polyfill.trim("//forest "), 37 | "//forest" 38 | ) 39 | end) 40 | it("avoid trimming spaces in the middle", function() 41 | assert.are.equal( 42 | polyfill.trim("te st "), 43 | "te st" 44 | ) 45 | end) 46 | end) 47 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Lua.diagnostics.disable": [ 3 | "trailing-space", 4 | "undefined-global", 5 | "lowercase-global", 6 | "cast-local-type" 7 | ], 8 | "cSpell.words": [ 9 | "ollow", 10 | "weacmd" 11 | ] 12 | } -------------------------------------------------------------------------------- /CONTRIBUTORS.tsv: -------------------------------------------------------------------------------- 1 | github_handle name 2 | sbrl Starbeamrainbowlabs 3 | VorTechnix VorTechnix 4 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | liberapay: sbrl 2 | -------------------------------------------------------------------------------- /modpack.conf: -------------------------------------------------------------------------------- 1 | name = worldeditadditions 2 | description = Extra tools and commands to extend WorldEdit. Currently has over 22 commands! 3 | 4 | depends = worldedit 5 | optional_depends = bonemeal,cool_trees,default,moretrees,ethereal 6 | min_minetest_version = 5.2 7 | -------------------------------------------------------------------------------- /modpack.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/modpack.txt -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/screenshot.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/screenshot2.png -------------------------------------------------------------------------------- /settingtypes.txt: -------------------------------------------------------------------------------- 1 | # https://github.com/minetest/minetest/blob/master/builtin/settingtypes.txt#L1 2 | 3 | # Will WorldEditAdditions override commands? 4 | worldeditadditions.override_commands (Override WorldEdit Commands) bool false 5 | 6 | # Will WorldEditAdditions wands be craftable? 7 | worldeditadditions.wand_craft (Craftable Wands) bool false 8 | -------------------------------------------------------------------------------- /worldeditadditions-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions-64.png -------------------------------------------------------------------------------- /worldeditadditions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions.png -------------------------------------------------------------------------------- /worldeditadditions/lib/conv/kernel_gaussian.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | -- Ported from Javascript by Starbeamrainbowlabs 3 | -- Original source: https://github.com/sidorares/gaussian-convolution-kernel/ 4 | -- From 5 | -- the code is taken from https://github.com/mattlockyer/iat455/blob/6493c882f1956703133c1bffa1d7ee9a83741cbe/assignment1/assignment/effects/blur-effect-dyn.js 6 | -- (c) Matt Lockyer, https://github.com/mattlockyer 7 | 8 | -- hypotenuse has moved to utils/numbers.lua 9 | 10 | --[[ 11 | * Generates a kernel used for the gaussian blur effect. 12 | * 13 | * @param dimension is an odd integer 14 | * @param sigma is the standard deviation used for our gaussian function. 15 | * 16 | * @returns an array with dimension^2 number of numbers, all less than or equal 17 | * to 1. Represents our gaussian blur kernel. 18 | ]]-- 19 | function worldeditadditions.conv.kernel_gaussian(dimension, sigma) 20 | if not (dimension % 2) or math.floor(dimension) ~= dimension or dimension < 3 then 21 | return false, "The dimension must be an odd integer greater than or equal to 3" 22 | end 23 | local kernel = {}; 24 | 25 | local two_sigma_square = 2 * sigma * sigma; 26 | local centre = (dimension - 1) / 2; 27 | 28 | local sum = 0 29 | for i = 0, dimension-1 do 30 | for j = 0, dimension-1 do 31 | local distance = wea_c.hypotenuse(i, j, centre, centre) 32 | 33 | -- The following is an algorithm that came from the gaussian blur 34 | -- wikipedia page [1]. 35 | -- 36 | -- http://en.wikipedia.org/w/index.php?title=Gaussian_blur&oldid=608793634#Mechanics 37 | local gaussian = (1 / math.sqrt( 38 | math.pi * two_sigma_square 39 | )) * math.exp((-1) * ((distance ^ 2) / two_sigma_square)); 40 | 41 | sum = sum + gaussian 42 | kernel[i*dimension + j] = gaussian 43 | end 44 | end 45 | 46 | -- Returns the unit vector of the kernel array. 47 | for k,v in pairs(kernel) do 48 | kernel[k] = kernel[k] / sum 49 | end 50 | 51 | return true, kernel 52 | end 53 | -------------------------------------------------------------------------------- /worldeditadditions/lib/hollow.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | -- ██ ██ ██████ ██ ██ ██████ ██ ██ 5 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 6 | -- ███████ ██ ██ ██ ██ ██ ██ ██ █ ██ 7 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ 8 | -- ██ ██ ██████ ███████ ███████ ██████ ███ ███ 9 | --- Hollows out the defined region, leaving a given number of nodes on the 10 | -- outside. 11 | -- (think of the bits of the outermost parts of the defined region as the 12 | -- 'walls' to a box) 13 | function worldeditadditions.hollow(pos1, pos2, wall_thickness) 14 | pos1, pos2 = Vector3.sort(pos1, pos2) 15 | -- pos2 will always have the highest co-ordinates now 16 | 17 | -- Fetch the nodes in the specified area 18 | local manip, area = worldedit.manip_helpers.init(pos1, pos2) 19 | local data = manip:get_data() 20 | 21 | local node_id_ignore = minetest.get_content_id("ignore") 22 | local node_id_air = minetest.get_content_id("air") 23 | 24 | -- minetest.log("action", "pos1: " ..pos1) 25 | -- minetest.log("action", "pos2: " .. pos2) 26 | 27 | local changes = { replaced = 0 } 28 | for z = pos2.z - wall_thickness, pos1.z + wall_thickness, -1 do 29 | for y = pos2.y - wall_thickness, pos1.y + wall_thickness, -1 do 30 | for x = pos2.x - wall_thickness, pos1.x + wall_thickness, -1 do 31 | local i = area:index(x, y, z) 32 | 33 | local is_air = wea_c.is_airlike(data[i]) 34 | local is_ignore = data[i] == node_id_ignore 35 | 36 | if not is_ignore and not is_air then 37 | data[i] = node_id_air 38 | changes.replaced = changes.replaced + 1 39 | end 40 | end 41 | end 42 | end 43 | 44 | -- Save the modified nodes back to disk & return 45 | worldedit.manip_helpers.finish(manip, data) 46 | 47 | return true, changes 48 | end 49 | -------------------------------------------------------------------------------- /worldeditadditions/lib/metaballs/init.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_m = wea.modpath .. "/lib/metaballs/" 3 | 4 | local playerdata = dofile(wea_m.."playerdata.lua") 5 | 6 | local metaballs_ns = { 7 | render = dofile(wea_m.."render.lua"), 8 | add = playerdata.add, 9 | remove = playerdata.remove, 10 | list = playerdata.list, 11 | list_pretty = playerdata.list_pretty, 12 | clear = playerdata.clear, 13 | volume = playerdata.volume 14 | } 15 | 16 | return metaballs_ns 17 | -------------------------------------------------------------------------------- /worldeditadditions/lib/noise/engines/infrared.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | local White = dofile(wea.modpath.."/lib/noise/engines/white.lua") 6 | 7 | local Infrared = {} 8 | Infrared.__index = Infrared 9 | 10 | 11 | function Infrared.new(seed) 12 | local result = { 13 | seed = seed or math.random(), 14 | white = White.new(seed), 15 | window = 2 16 | } 17 | setmetatable(result, Infrared) 18 | return result 19 | end 20 | 21 | function Infrared:noise( x, y, z ) 22 | local values = { } 23 | for nx=x-self.window,x+self.window do 24 | for ny=y-self.window,y+self.window do 25 | for nz=z-self.window,z+self.window do 26 | table.insert(values, self.white:noise(nx, ny, nz)) 27 | end 28 | end 29 | end 30 | return wea_c.average(values) 31 | end 32 | 33 | return Infrared 34 | -------------------------------------------------------------------------------- /worldeditadditions/lib/noise/engines/init.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | 3 | --- Noise generation algorithm engines. 4 | -- @namespace worldeditadditions.noise.engines 5 | return { 6 | available = { "perlin", "perlinmt", "sin", "white", "red", "infrared" }, 7 | Perlin = dofile(wea.modpath.."/lib/noise/engines/perlin.lua"), 8 | PerlinMT = dofile(wea.modpath.."/lib/noise/engines/perlinmt.lua"), 9 | Sin = dofile(wea.modpath.."/lib/noise/engines/sin.lua"), 10 | White = dofile(wea.modpath.."/lib/noise/engines/white.lua"), 11 | Red = dofile(wea.modpath.."/lib/noise/engines/red.lua"), 12 | Infrared = dofile(wea.modpath.."/lib/noise/engines/infrared.lua") 13 | -- TODO: Follow https://www.redblobgames.com/articles/noise/introduction.html and implement different colours of noise (*especially* red and pink noise) 14 | } 15 | -------------------------------------------------------------------------------- /worldeditadditions/lib/noise/engines/perlinmt.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | 5 | local PerlinMT = {} 6 | PerlinMT.__index = PerlinMT 7 | 8 | 9 | function PerlinMT.new(seed, params) 10 | if not seed then seed = 0 end 11 | local result = { 12 | -- Provided by Minetest 13 | engine = PerlinNoise({ 14 | offset = 0, 15 | scale = 1, 16 | spread = {x = 50, y = 50, z = 50}, 17 | seed = seed, 18 | octaves = 1, 19 | persistence = 0.63, 20 | lacunarity = 2.0, 21 | flags = "defaults,absvalue", 22 | }) 23 | } 24 | setmetatable(result, PerlinMT) 25 | return result 26 | end 27 | 28 | function PerlinMT:noise( x, y, z ) 29 | local value = self.engine:get_3d(Vector3.new(x, y, z)) 30 | return value 31 | end 32 | 33 | return PerlinMT 34 | -------------------------------------------------------------------------------- /worldeditadditions/lib/noise/engines/red.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | 4 | local White = dofile(wea.modpath.."/lib/noise/engines/white.lua") 5 | 6 | local Red = {} 7 | Red.__index = Red 8 | 9 | 10 | function Red.new(seed) 11 | local result = { 12 | seed = seed or math.random(), 13 | white = White.new(seed) 14 | } 15 | setmetatable(result, Red) 16 | return result 17 | end 18 | 19 | function Red:noise( x, y, z ) 20 | local values = { 21 | self.white:noise(x, y, z), 22 | self.white:noise(x + 1, y, z), 23 | self.white:noise(x, y + 1, z), 24 | self.white:noise(x, y, z + 1), 25 | self.white:noise(x - 1, y, z), 26 | self.white:noise(x, y - 1, z), 27 | self.white:noise(x, y, z - 1), 28 | self.white:noise(x, y - 1, z - 1), 29 | self.white:noise(x - 1, y, z - 1), 30 | self.white:noise(x - 1, y - 1, z), 31 | self.white:noise(x - 1, y - 1, z - 1), 32 | self.white:noise(x, y + 1, z + 1), 33 | self.white:noise(x + 1, y, z + 1), 34 | self.white:noise(x + 1, y + 1, z), 35 | self.white:noise(x + 1, y + 1, z + 1), 36 | } 37 | return wea_c.average(values) 38 | end 39 | 40 | return Red 41 | -------------------------------------------------------------------------------- /worldeditadditions/lib/noise/engines/sin.lua: -------------------------------------------------------------------------------- 1 | 2 | local Sin = {} 3 | Sin.__index = Sin 4 | 5 | 6 | function Sin.new() 7 | local result = {} 8 | setmetatable(result, Sin) 9 | return result 10 | end 11 | 12 | function Sin:noise( x, y, z ) 13 | -- local value = math.sin(x) 14 | local value = (math.sin(x) + math.sin(y) + math.sin(z)) / 3 15 | -- Rescale from -1 - +1 to 0 - 1 16 | return (value + 1) / 2 17 | end 18 | 19 | return Sin 20 | -------------------------------------------------------------------------------- /worldeditadditions/lib/noise/engines/white.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | local White = {} 4 | White.__index = White 5 | 6 | 7 | function White.new(seed) 8 | local result = { 9 | seed = seed or math.random() 10 | } 11 | setmetatable(result, White) 12 | return result 13 | end 14 | 15 | function White:noise( x, y, z ) 16 | if x == 0 then x = 1 end 17 | if y == 0 then y = 1 end 18 | if z == 0 then z = 1 end 19 | local seed = ((self.seed + (x * y * z)) * 1506359) % 1113883 20 | 21 | math.randomseed(seed) 22 | local value = math.random() 23 | return value 24 | end 25 | 26 | return White 27 | -------------------------------------------------------------------------------- /worldeditadditions/lib/noise/init.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | 3 | 4 | --- System to manipulate the world using noise generation functions. 5 | -- @namespace worldeditadditions.noise 6 | wea.noise = {} 7 | 8 | -- The command itself 9 | dofile(wea.modpath.."/lib/noise/run2d.lua") 10 | 11 | -- Dependencies 12 | dofile(wea.modpath.."/lib/noise/apply_2d.lua") 13 | dofile(wea.modpath.."/lib/noise/make_2d.lua") 14 | dofile(wea.modpath.."/lib/noise/params_apply_default.lua") 15 | 16 | -- Noise generation engines 17 | wea.noise.engines = dofile(wea.modpath.."/lib/noise/engines/init.lua") 18 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/brushes/__gaussian.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | --- Returns a smooth gaussian brush. 6 | -- @name make_gaussian 7 | -- @internal 8 | -- @param size Vector3 The target size of the brush. Note that the actual size of the brush will be different, as the gaussian function has some limitations. 9 | -- @param sigma=2 number The 'smoothness' of the brush. Higher values are more smooth. 10 | return function(size, sigma) 11 | size = math.min(size.x, size.y) 12 | if size % 2 == 0 then size = size - 1 end -- Gaussian runs on odd numbers 13 | if size < 1 then 14 | return false, "Error: Invalid brush size (brushes must be at least 1 node in size)." 15 | end 16 | 17 | local success, gaussian = wea.conv.kernel_gaussian(size, sigma) 18 | 19 | -- Normalise values to fill the range 0 - 1 20 | -- By default, wea.conv.kernel_gaussian values add up to 1 in total 21 | local max = wea_c.max(gaussian) 22 | for i=0,size*size-1 do 23 | gaussian[i] = gaussian[i] / max 24 | end 25 | 26 | return success, gaussian, Vector3.new(size, size, 0) 27 | end 28 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/brushes/circle.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | --- Makes a circle brush of a given size. 5 | -- @name circle 6 | -- @param size Vector3 The desired sizez of the brush (only X and Y are considered; Z is ignored). 7 | -- @returns bool,brush,Vector3 Success bool, then the brush, then finally the actual size of the brush generated. 8 | return function(size) 9 | local brush = {} 10 | 11 | local centre = (size / 2):floor() 12 | local minsize = math.floor(math.min(size.x, size.y) / 2) 13 | 14 | for y = size.y - 1, 0, -1 do 15 | for x = size.x - 1, 0, -1 do 16 | local i = y*size.x + x 17 | 18 | if math.floor((centre - Vector3.new(x, y, 0)):length()) < minsize then 19 | brush[i] = 1 20 | else 21 | brush[i] = 0 22 | end 23 | end 24 | end 25 | 26 | return true, brush, size 27 | end 28 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/brushes/circle_soft1.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | 6 | return function(size) 7 | local brush = {} 8 | 9 | local centre = (size / 2):floor() 10 | local minsize = math.floor(math.min(size.x, size.y) / 2) 11 | 12 | local border = 1 13 | local kernel_size = 3 14 | 15 | -- Make the circle 16 | -- We don't use 0 to 1 here, because we have to blur it and the existing convolutional 17 | -- system rounds values. 18 | for y = size.y - 1, 0, -1 do 19 | for x = size.x - 1, 0, -1 do 20 | local i = y*size.x + x 21 | 22 | if math.floor((centre - Vector3.new(x, y, 0)):length()) < minsize - border then 23 | brush[i] = 100000 24 | else 25 | brush[i] = 0 26 | end 27 | end 28 | end 29 | 30 | -- Make the kernel & blur it 31 | local success, kernel = wea.conv.kernel_gaussian(kernel_size, 2) 32 | if not success then return success, kernel end 33 | 34 | local success2, msg = wea.conv.convolve( 35 | brush, Vector3.new(size.x, 0, size.y), 36 | kernel, Vector3.new(kernel_size, 0, kernel_size) 37 | ) 38 | if not success2 then return success2, msg end 39 | 40 | -- Rescale to be between 0 and 1 41 | local max_value = wea_c.max(brush) 42 | for i,value in pairs(brush) do 43 | brush[i] = brush[i] / max_value 44 | end 45 | 46 | return true, brush, size 47 | end 48 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/brushes/gaussian.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | 3 | local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__gaussian.lua") 4 | 5 | return function(size) 6 | local success, brush, size_actual = __smooth(size, 3) 7 | return success, brush, size_actual 8 | end 9 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/brushes/gaussian_hard.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | 3 | local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__gaussian.lua") 4 | 5 | return function(size) 6 | local success, brush, size_actual = __smooth(size, 2) 7 | return success, brush, size_actual 8 | end 9 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/brushes/gaussian_soft.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | 3 | local __smooth = dofile(wea.modpath.."/lib/sculpt/brushes/__gaussian.lua") 4 | 5 | return function(size) 6 | local success, brush, size_actual = __smooth(size, 5) 7 | return success, brush, size_actual 8 | end 9 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/brushes/square.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Returns a simple square brush with 100% weight for every pixel. 3 | -- @name square 4 | -- @param size Vector3 The desired size of the brush. Only the x and y components are used; the z component is ignored. 5 | -- @returns bool,number[],Vector3 1: true, as this function always succeeds. 2: A simple square brush as a zero-indexed flat array. 3: The size of the resulting brush as a Vector3, using the x and y components. 6 | return function(size) 7 | local result = {} 8 | for y=0, size.y do 9 | for x=0, size.x do 10 | result[y*size.x + x] = 1 11 | end 12 | end 13 | return true, result, size 14 | end 15 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/import_static.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | 3 | local parse_static = dofile(wea.modpath.."/lib/sculpt/parse_static.lua") 4 | 5 | --- Reads and parses the brush stored in the specified file. 6 | -- @name import_static 7 | -- @internal 8 | -- @param filepath string The path to file that contains the static brush to read in. 9 | -- @returns true,table,Vector3|false,string A success boolean, followed either by an error message as a string or the brush (as a table) and it's size (as an X/Y Vector3) 10 | return function(filepath) 11 | local handle = io.open(filepath) 12 | 13 | if not handle then 14 | if handle ~= nil then handle:close() end 15 | return false, "Error: Failed to open the static brush file at '"..filepath.."'." 16 | end 17 | 18 | local data = handle:read("*all") 19 | handle:close() 20 | 21 | local success, brush, brush_size = parse_static(data) 22 | if not success then return success, brush end 23 | 24 | return true, brush, brush_size 25 | end 26 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/init.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | 3 | local sculpt = { 4 | brushes = { 5 | circle_soft1 = dofile(wea.modpath.."/lib/sculpt/brushes/circle_soft1.lua"), 6 | circle = dofile(wea.modpath.."/lib/sculpt/brushes/circle.lua"), 7 | square = dofile(wea.modpath.."/lib/sculpt/brushes/square.lua"), 8 | gaussian_hard = dofile(wea.modpath.."/lib/sculpt/brushes/gaussian_hard.lua"), 9 | gaussian = dofile(wea.modpath.."/lib/sculpt/brushes/gaussian.lua"), 10 | gaussian_soft = dofile(wea.modpath.."/lib/sculpt/brushes/gaussian_soft.lua"), 11 | }, 12 | make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua"), 13 | make_preview = dofile(wea.modpath.."/lib/sculpt/make_preview.lua"), 14 | preview_brush = dofile(wea.modpath.."/lib/sculpt/preview_brush.lua"), 15 | apply_heightmap = dofile(wea.modpath.."/lib/sculpt/apply_heightmap.lua"), 16 | apply = dofile(wea.modpath.."/lib/sculpt/apply.lua"), 17 | scan_static = dofile(wea.modpath.."/lib/sculpt/scan_static.lua"), 18 | import_static = dofile(wea.modpath.."/lib/sculpt/import_static.lua"), 19 | parse_static = dofile(wea.modpath.."/lib/sculpt/parse_static.lua") 20 | } 21 | 22 | -- scan_sculpt is called after everything is loaded in the main init file 23 | 24 | return sculpt 25 | 26 | -- TODO: Automatically find & register all text file based brushes in the brushes directory 27 | 28 | -- TODO: Implement automatic scaling of static brushes to the correct size. We have scale already, but we probably need to implement a proper 2d canvas scaling algorithm. Some options to consider: linear < [bi]cubic < nohalo/lohalo 29 | 30 | -- Note that we do NOT automatically find & register computed brushes because that's an easy way to execute arbitrary Lua code & cause a security issue unless handled very carefully 31 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/make_brush.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | 3 | --- Makes a sculpting brush that is as close to a target size as possible. 4 | -- @param brush_name string The name of the brush to create. 5 | -- @param target_size Vector3 The target size of the brush to create. 6 | -- @returns true,table,Vector3|false,string If the operation was successful, true followed by the brush in a 1D ZERO-indexed table followed by the actual size of the brush as a Vector3 (x & y components used only). If the operation was not successful, false and an error message string is returned instead. 7 | local function make_brush(brush_name, target_size) 8 | if brush_name == "default" then brush_name = "circle_soft1" end 9 | if not wea.sculpt.brushes[brush_name] then return false, "Error: That brush does not exist. Try using //sculptbrushes to list all available sculpting brushes." end 10 | 11 | local brush_def = wea.sculpt.brushes[brush_name] 12 | 13 | local success, brush, size_actual 14 | if type(brush_def) == "function" then 15 | success, brush, size_actual = brush_def(target_size) 16 | if not success then return success, brush end 17 | else 18 | brush = brush_def.brush 19 | size_actual = brush_def.size 20 | end 21 | 22 | return true, brush, size_actual 23 | end 24 | 25 | return make_brush 26 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/make_preview.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | 3 | local make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua") 4 | 5 | --- Generates a textual preview of a given brush. 6 | -- @param brush table The brush in question to preview. 7 | -- @param size Vector3 The size of the brush. 8 | -- @returns string A preview of the brush as a string. 9 | local function make_preview(brush, size, framed) 10 | if framed == nil then framed = true end 11 | 12 | -- Values to map brush pixel values to. 13 | -- Brush pixel values are first multiplied by 10 before comparing to these numbers 14 | local values = {} 15 | values["@"] = 9.5 16 | values["#"] = 8 17 | values["="] = 6 18 | values[":"] = 5 19 | values["-"] = 4 20 | values["."] = 1 21 | values[" "] = 0 22 | 23 | local frame_vertical = "+"..string.rep("-", math.max(0, size.x)).."+" 24 | 25 | local result = {} 26 | if framed then table.insert(result, frame_vertical) end 27 | 28 | for y = size.y-1, 0, -1 do 29 | local row = {} 30 | if framed then table.insert(row, "|") end 31 | for x = size.x-1, 0, -1 do 32 | local i = y*size.x + x 33 | local pixel = " " 34 | local threshold_cur = -1 35 | for value,threshold in pairs(values) do 36 | if brush[i] * 10 > threshold and threshold_cur < threshold then 37 | pixel = value 38 | threshold_cur = threshold 39 | end 40 | end 41 | table.insert(row, pixel) 42 | end 43 | if framed then table.insert(row, "|") end 44 | table.insert(result, table.concat(row)) 45 | end 46 | 47 | if framed then table.insert(result, frame_vertical) end 48 | 49 | 50 | return table.concat(result, "\n") 51 | end 52 | 53 | return make_preview 54 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/parse_static.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | --- Parses a static brush definition. 6 | -- @name parse_static 7 | -- @internal 8 | -- @param source string The source string that contains the static brush, formatted as TSV. 9 | -- @returns true,table,Vector3|false,string A success boolean, followed either by an error message as a string or the brush (as a table) and it's size (as an X/Y Vector3) 10 | return function(source) 11 | local width = -1 12 | local height 13 | local maxvalue, minvalue, range 14 | 15 | -- Parse out the TSV into a table of tables, while also parsing values as numbers 16 | -- Also keeps track of the maximum/minimum values found for rescaling later. 17 | local values = wea_c.table.map( 18 | wea_c.split(source, "\n", false), 19 | function(line) 20 | local row = wea_c.split(line, "%s+", false) 21 | width = math.max(width, #row) 22 | return wea_c.table.map( 23 | row, 24 | function(pixel) 25 | local value = tonumber(pixel) 26 | if not value then value = 0 end 27 | if maxvalue == nil or value > maxvalue then 28 | maxvalue = value 29 | end 30 | if minvalue == nil or value < minvalue then 31 | minvalue = value 32 | end 33 | return value 34 | end 35 | ) 36 | end 37 | ) 38 | 39 | height = #values 40 | range = maxvalue - minvalue 41 | 42 | local brush = {} 43 | for y,row in ipairs(values) do 44 | for x,value in ipairs(row) do 45 | local i = (y-1)*width + (x-1) 46 | brush[i] = (value - minvalue) / range 47 | end 48 | end 49 | 50 | return true, brush, Vector3.new(width, height, 0) 51 | end 52 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/preview_brush.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | local make_brush = dofile(wea.modpath.."/lib/sculpt/make_brush.lua") 6 | local make_preview = dofile(wea.modpath.."/lib/sculpt/make_preview.lua") 7 | 8 | --- Generates a textual preview of a given brush. 9 | -- @param brush_name string The name of the brush to create a preview for. 10 | -- @param target_size Vector3 The target size of the brush to create. Default: (10, 10, 0). 11 | -- @returns bool,string If the operation was successful, true followed by a preview of the brush as a string. If the operation was not successful, false and an error message string is returned instead. 12 | local function preview_brush(brush_name, target_size, framed) 13 | if framed == nil then framed = true end 14 | if not target_size then target_size = Vector3.new(10, 10, 0) end 15 | 16 | local success, brush, brush_size = make_brush(brush_name, target_size) 17 | if not success then return success, brush end 18 | 19 | return true, make_preview(brush, brush_size, framed) 20 | end 21 | 22 | return preview_brush 23 | -------------------------------------------------------------------------------- /worldeditadditions/lib/sculpt/scan_static.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | local import_static = dofile(wea.modpath.."/lib/sculpt/import_static.lua") 6 | 7 | local function import_filepath(filepath, name, overwrite_existing) 8 | if overwrite_existing and wea.sculpt.brushes[name] ~= nil then 9 | return false, "Error: A brush with the name '"..name.."' already exists." 10 | end 11 | 12 | local success, brush, brush_size = import_static(filepath) 13 | if not success then return success, "Error while reading from '"..filepath.."': "..brush end 14 | 15 | wea.sculpt.brushes[name] = { 16 | brush = brush, 17 | size = brush_size 18 | } 19 | 20 | return true 21 | end 22 | 23 | --- Scans the given directory and imports all static brushes found. 24 | -- Static brushes have the file extension ".brush.tsv" (without quotes). 25 | -- @name scan_static 26 | -- @internal 27 | -- @param dirpath string The path to directory that contains the static brushs to import. 28 | -- @returns bool,loaded,errors A success boolean, followed by the number of brushes loaded, followed by the number of errors encountered while loading brushes (errors are logged as warnings with Minetest) 29 | return function(dirpath, overwrite_existing) 30 | if overwrite_existing == nil then overwrite_existing = false end 31 | local files = wea_c.io.scandir_files(dirpath) 32 | 33 | local brushes_loaded = 0 34 | local errors = 0 35 | 36 | 37 | for i, filename in pairs(files) do 38 | if wea_c.str_ends(filename, ".brush.tsv") then 39 | local filepath = dirpath.."/"..filename 40 | local name = filename:gsub(".brush.tsv", "") 41 | 42 | local success, msg = import_filepath(filepath, name, overwrite_existing) 43 | if not success then 44 | minetest.log("warning", "[WorldEditAdditions:sculpt] Encountered error when loading brush from '"..filepath.."':"..msg) 45 | end 46 | brushes_loaded = brushes_loaded + 1 47 | end 48 | end 49 | 50 | return true, brushes_loaded, errors 51 | end 52 | -------------------------------------------------------------------------------- /worldeditadditions/lib/selection/init.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_m = wea.modpath .. "/lib/selection/" 3 | 4 | wea.add_pos = {} 5 | 6 | wea.selection = dofile(wea_m.."selection.lua") 7 | dofile(wea_m.."stack.lua") 8 | -------------------------------------------------------------------------------- /worldeditadditions/lib/walls.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | -- ██ ██ █████ ██ ██ ███████ 5 | -- ██ ██ ██ ██ ██ ██ ██ 6 | -- ██ █ ██ ███████ ██ ██ ███████ 7 | -- ██ ███ ██ ██ ██ ██ ██ ██ 8 | -- ███ ███ ██ ██ ███████ ███████ ███████ 9 | 10 | --- Creates vertical walls on the inside of the defined region. 11 | -- @apipath worldeditadditions.walls 12 | -- @param pos1 Vector Position 1 of the defined region, 13 | -- @param pos2 Vector Position 2 of the defined region. 14 | -- @param node_name string The name of the node to use to create the walls with. 15 | -- @param thickness number? The thickness of the walls to create. Default: 1 16 | function worldeditadditions.walls(pos1, pos2, node_name, thickness) 17 | pos1, pos2 = Vector3.sort(pos1, pos2) 18 | if not thickness then thickness = 1 end 19 | -- pos2 will always have the highest co-ordinates now 20 | 21 | -- Fetch the nodes in the specified area 22 | local manip, area = worldedit.manip_helpers.init(pos1, pos2) 23 | local data = manip:get_data() 24 | 25 | local node_id = minetest.get_content_id(node_name) 26 | 27 | -- z y x is the preferred loop order (because CPU cache I'd guess, since then we're iterating linearly through the data array) 28 | local count_replaced = 0 29 | for z = pos2.z, pos1.z, -1 do 30 | for y = pos2.y, pos1.y, -1 do 31 | for x = pos2.x, pos1.x, -1 do 32 | if math.abs(x - pos1.x) < thickness 33 | or math.abs(x - pos2.x) < thickness 34 | or math.abs(z - pos1.z) < thickness 35 | or math.abs(z - pos2.z) < thickness then 36 | data[area:index(x, y, z)] = node_id 37 | count_replaced = count_replaced + 1 38 | end 39 | end 40 | end 41 | end 42 | 43 | -- Save the modified nodes back to disk & return 44 | worldedit.manip_helpers.finish(manip, data) 45 | 46 | return true, count_replaced 47 | end 48 | -------------------------------------------------------------------------------- /worldeditadditions/lib/wireframe/corner_set.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | -- ██████ ██████ ██████ ███ ██ ███████ ██████ ███████ ███████ ████████ 5 | -- ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ 6 | -- ██ ██ ██ ██████ ██ ██ ██ █████ ██████ ███████ █████ ██ 7 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 8 | -- ██████ ██████ ██ ██ ██ ████ ███████ ██ ██ ███████ ███████ ██ 9 | 10 | --- Puts a node at each corner of selection box. 11 | -- @param pos1 Vector3 The 1st position defining the WorldEdit selection 12 | -- @param pos2 Vector3 The 2nd positioon defining the WorldEdit selection 13 | -- @param node string Name of the node to place 14 | function worldeditadditions.corner_set(pos1, pos2, node) 15 | 16 | -- z y x is the preferred loop order (because CPU cache I'd guess, since then we're iterating linearly through the data array) 17 | local counts = {replaced = 0} 18 | for i, z in pairs({pos1.z, pos2.z}) do 19 | for j, y in pairs({pos1.y, pos2.y}) do 20 | for k, x in pairs({pos1.x, pos2.x}) do 21 | minetest.set_node(Vector3.new(x, y, z), {name = node}) 22 | counts.replaced = counts.replaced + 1 23 | end 24 | end 25 | end 26 | 27 | return true, counts.replaced 28 | end 29 | -------------------------------------------------------------------------------- /worldeditadditions/lib/wireframe/make_compass.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | -- ███ ███ █████ ██ ██ ███████ ██████ ██████ ███ ███ ██████ █████ ███████ ███████ 5 | -- ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ 6 | -- ██ ████ ██ ███████ █████ █████ ██ ██ ██ ██ ████ ██ ██████ ███████ ███████ ███████ 7 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 8 | -- ██ ██ ██ ██ ██ ██ ███████ ██████ ██████ ██ ██ ██ ██ ██ ███████ ███████ 9 | 10 | --- Makes a compass with a bead pointing north (+Z). 11 | -- @param {Position} pos1 The 1st position defining the WorldEdit selection 12 | -- @param {string} node1 Name of the node to place 13 | -- @param {string} node2 Name of the node of the bead 14 | function worldeditadditions.make_compass(pos1,node1,node2) 15 | pos1 = Vector3.clone(pos1) 16 | minetest.set_node(pos1 + Vector3.new(0,1,3), { name = node2 }) 17 | local counts = { replaced = 1 } 18 | 19 | -- z y x is the preferred loop order (because CPU cache I'd guess, since then we're iterating linearly through the data array) 20 | for z = -3,3 do 21 | if z ~= 0 then 22 | for k,x in pairs({math.floor(-3/math.abs(z)),0,math.ceil(3/math.abs(z))}) do 23 | minetest.set_node(Vector3.new(pos1.x+x,pos1.y,pos1.z+z), {name=node1}) 24 | counts.replaced = counts.replaced + 1 25 | end 26 | else 27 | for x = -3,3 do 28 | minetest.set_node(Vector3.new(pos1.x+x,pos1.y,pos1.z), {name=node1}) 29 | counts.replaced = counts.replaced + 1 30 | end 31 | end 32 | end 33 | 34 | return true, counts.replaced 35 | end 36 | -------------------------------------------------------------------------------- /worldeditadditions/lib/wireframe/wire_box.lua: -------------------------------------------------------------------------------- 1 | local v3 = worldeditadditions_core.Vector3 2 | 3 | -- ██ ██ ██ ██████ ███████ ██████ ██████ ██ ██ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ██ █ ██ ██ ██████ █████ ██████ ██ ██ ███ 6 | -- ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 7 | -- ███ ███ ██ ██ ██ ███████ ██████ ██████ ██ ██ 8 | 9 | --- Fills the edges of the selection box with nodes. 10 | -- @param {Position} pos1 The 1st position defining the WorldEdit selection 11 | -- @param {Position} pos2 The 2nd positioon defining the WorldEdit selection 12 | -- @param {string} node Name of the node to place 13 | function worldeditadditions.wire_box(pos1,pos2,node) 14 | local node_id_replace = minetest.get_content_id(node) 15 | local ps1, ps2 = v3.sort(pos1,pos2) 16 | 17 | -- Fetch the nodes in the specified area 18 | local manip, area = worldedit.manip_helpers.init(pos1, pos2) 19 | local data = manip:get_data() 20 | 21 | -- Using three loops to reduce the number of nodes processed 22 | local counts = { replaced = 0 } 23 | 24 | for z = ps1.z,ps2.z do 25 | for _j,y in pairs({pos1.y,pos2.y}) do 26 | for _k,x in pairs({pos1.x,pos2.x}) do 27 | data[area:index(x, y, z)] = node_id_replace 28 | counts.replaced = counts.replaced + 1 29 | end 30 | end 31 | end 32 | if math.abs(ps2.y-ps1.y) > 1 then 33 | for _j,z in pairs({pos1.z,pos2.z}) do 34 | for y = pos1.y+1,pos2.y-1 do 35 | for _k,x in pairs({pos1.x,pos2.x}) do 36 | data[area:index(x, y, z)] = node_id_replace 37 | counts.replaced = counts.replaced + 1 38 | end 39 | end 40 | end 41 | end 42 | if math.abs(ps2.x-ps1.x) > 1 then 43 | for _j,z in pairs({pos1.z,pos2.z}) do 44 | for _k,y in pairs({pos1.y,pos2.y}) do 45 | for x = pos1.x+1,pos2.x-1 do 46 | data[area:index(x, y, z)] = node_id_replace 47 | counts.replaced = counts.replaced + 1 48 | end 49 | end 50 | end 51 | end 52 | -- Save the modified nodes back to disk & return 53 | worldedit.manip_helpers.finish(manip, data) 54 | 55 | return true, counts.replaced 56 | end 57 | -------------------------------------------------------------------------------- /worldeditadditions/mod.conf: -------------------------------------------------------------------------------- 1 | name = worldeditadditions 2 | description = Extra tools and commands to extend WorldEdit for Minetest 3 | depends = worldedit 4 | optional_depends = worldeditadditions_core, bonemeal, cool_trees, default, moretrees, ethereal 5 | -------------------------------------------------------------------------------- /worldeditadditions_commands/aliases.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | 4 | wea_c.register_alias("smoothadv", "convolve") 5 | wea_c.register_alias("conv", "convolve") 6 | 7 | wea_c.register_alias("naturalise", "layers") 8 | wea_c.register_alias("naturalize", "layers") 9 | 10 | wea_c.register_alias("flora", "bonemeal") 11 | 12 | -- Measure Tools 13 | wea_c.register_alias("mcount", "count") 14 | wea_c.register_alias("mfacing", "mface") 15 | 16 | 17 | --- Overrides to core WorldEdit commands that have been thoroughly tested 18 | -- These are now enabled by default, but if you find a bug please report and 19 | -- it will be fixed as a matter of priority. 20 | wea_c.register_alias("copy", "copy+", true) 21 | wea_c.register_alias("move", "move+", true) 22 | 23 | --- Overrides to core WorldEdit commands 24 | -- These are disabled by default for now, as they could be potentially dangerous to stability 25 | -- Thorough testing is required of our replacement commands before these are enabled by default 26 | local worldmt_settings = Settings(minetest.get_worldpath().."/world.mt") 27 | local should_override = worldmt_settings:get_bool("worldeditadditions_override_commands", false) 28 | if should_override then 29 | minetest.log("info", "[WorldEditAdditions] Enabling override aliases") 30 | wea_c.register_alias("replace", "replacemix", true) 31 | end 32 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/count.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local wea = worldeditadditions 3 | local Vector3 = wea_c.Vector3 4 | 5 | -- ██████ ██████ ██ ██ ███ ██ ████████ 6 | -- ██ ██ ██ ██ ██ ████ ██ ██ 7 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ 8 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ 9 | -- ██████ ██████ ██████ ██ ████ ██ 10 | worldeditadditions_core.register_command("count", { 11 | params = "", 12 | description = "Counts all the nodes in the defined region.", 13 | privs = { worldedit = true }, 14 | require_pos = 2, 15 | parse = function(params_text) 16 | return true 17 | end, 18 | nodes_needed = function(name) 19 | -- We don't actually modify anything, but without returning a 20 | -- number here safe_region doesn't work 21 | return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) 22 | end, 23 | func = function(name) 24 | local start_time = wea_c.get_ms_time() 25 | local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) 26 | local success, counts, total = wea.count( 27 | pos1, pos2, 28 | true 29 | ) 30 | if not success then return success, counts end 31 | 32 | local result = "\n"..wea_c.format.make_ascii_table(counts).."\n".. 33 | string.rep("=", 6 + #tostring(total) + 6).."\n".. 34 | "Total "..total.." nodes\n" 35 | 36 | local time_taken = wea_c.get_ms_time() - start_time 37 | 38 | 39 | minetest.log("action", name.." used //count at "..pos1.." - "..pos2..", counting "..total.." nodes in "..wea_c.format.human_time(time_taken)) 40 | return true, result 41 | end 42 | }) 43 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/ellipsoid2.lua: -------------------------------------------------------------------------------- 1 | -- ███████ ██ ██ ██ ██████ ███████ ██████ ██ ██████ 2 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 3 | -- █████ ██ ██ ██ ██████ ███████ ██ ██ ██ ██ ██ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ███████ ███████ ███████ ██ ██ ███████ ██████ ██ ██████ 6 | local wea_c = worldeditadditions_core 7 | local wea = worldeditadditions 8 | local Vector3 = wea_c.Vector3 9 | 10 | worldeditadditions_core.register_command("ellipsoid2", { 11 | params = "[ [h[ollow]]]", 12 | description = "Creates am optionally hollow 3D ellipsoid that fills the defined region, filled with .", 13 | privs = { worldedit = true }, 14 | require_pos = 2, 15 | parse = function(params_text) 16 | if not params_text or params_text == "" then 17 | params_text = "dirt" 18 | end 19 | 20 | local parts = wea_c.split_shell(params_text) 21 | 22 | 23 | local replace_node = worldedit.normalize_nodename(parts[1]) 24 | if not replace_node then 25 | return false, "Error: Invalid replace_node specified." 26 | end 27 | 28 | local hollow = false 29 | if parts[2] == "hollow" or parts[2] == "h" then 30 | hollow = true 31 | end 32 | 33 | return true, replace_node, hollow 34 | end, 35 | nodes_needed = function(name, target_node) 36 | local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) 37 | return math.ceil(4/3 * math.pi * (pos2.x - pos1.x)/2 * (pos2.y - pos1.y)/2 * (pos2.z - pos1.z)/2) 38 | end, 39 | func = function(name, target_node, radius, hollow) 40 | local start_time = wea_c.get_ms_time() 41 | local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) 42 | 43 | local replaced = wea.ellipsoid2( 44 | pos1, pos2, 45 | target_node, 46 | hollow 47 | ) 48 | local time_taken = wea_c.get_ms_time() - start_time 49 | 50 | minetest.log("action", name.." used //ellipsoid2 at "..pos1.." - "..pos2..", replacing "..replaced.." nodes in "..time_taken.."s") 51 | return true, replaced.." nodes replaced in "..wea_c.format.human_time(time_taken) 52 | end 53 | }) 54 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/extra/basename.lua: -------------------------------------------------------------------------------- 1 | -- ██████ █████ ███████ ███████ ███ ██ █████ ███ ███ ███████ 2 | -- ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ████ ████ ██ 3 | -- ██████ ███████ ███████ █████ ██ ██ ██ ███████ ██ ████ ██ █████ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ██████ ██ ██ ███████ ███████ ██ ████ ██ ██ ██ ██ ███████ 6 | worldeditadditions_core.register_command("basename", { 7 | params = "", 8 | description = "Returns the base name of nodes that use a given alias.", 9 | privs = {worldedit = true}, 10 | parse = function(params_text) 11 | if params_text == "" or not params_text then 12 | return false, "Node not specified." 13 | end 14 | return true, params_text 15 | end, 16 | func = function(name, params_text) 17 | if name == nil then return end 18 | return true, worldedit.normalize_nodename(params_text) or ('Error 404: "'..params_text..'" not found!') 19 | end 20 | }) 21 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/extra/saplingaliases.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | -- ███████ █████ ██████ ██ ██ ███ ██ ██████ █████ ██ ██ █████ ███████ ███████ ███████ 4 | -- ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ███████ ███████ ██████ ██ ██ ██ ██ ██ ██ ███ ███████ ██ ██ ███████ ███████ █████ ███████ 6 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 7 | -- ███████ ██ ██ ██ ███████ ██ ██ ████ ██████ ██ ██ ███████ ██ ██ ██ ███████ ███████ ███████ 8 | minetest.register_chatcommand("/saplingaliases", { 9 | params = "[aliases|all_saplings]", 10 | description = "Lists all the currently registered sapling aliases (default). A single argument is taken as the mode of operation. Current modes: aliases (default; as described previously), all_saplings (lists all node names with the group \"sapling\")", 11 | privs = { worldedit = true }, 12 | func = function(name, params_text) 13 | if name == nil then return end 14 | if params_text == "" or not params_text then 15 | params_text = "aliases" 16 | end 17 | 18 | local msg = {} 19 | 20 | if params_text == "aliases" then 21 | table.insert(msg, "Currently registered aliases:\n") 22 | local aliases = wea_c.get_all_sapling_aliases() 23 | local display = {} 24 | for node_name, alias in pairs(aliases) do 25 | table.insert(display, { node_name, alias }) 26 | end 27 | table.sort(display, function(a, b) return a[2] < b[2] end) 28 | table.insert(msg, wea_c.format.make_ascii_table(display)) 29 | elseif params_text == "all_saplings" then 30 | local results = wea_c.registered_nodes_by_group("sapling") 31 | table.insert(msg, "Sapling-like nodes:\n") 32 | local str = table.concat(results, "\n") 33 | table.insert(msg, str) 34 | else 35 | table.insert(msg, "Unknown mode '") 36 | table.insert(msg, params_text) 37 | table.insert(msg, "' (valid modes: aliases, all_saplings).") 38 | end 39 | worldedit.player_notify(name, table.concat(msg)) 40 | end 41 | }) 42 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/fillcaves.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | -- ███████ ██ ██ ██ ██████ █████ ██ ██ ███████ ███████ 5 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 6 | -- █████ ██ ██ ██ ██ ███████ ██ ██ █████ ███████ 7 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 8 | -- ██ ██ ███████ ███████ ██████ ██ ██ ████ ███████ ███████ 9 | worldeditadditions_core.register_command("fillcaves", { 10 | params = "[]", 11 | description = "Fills in all airlike nodes beneath the first non-airlike node detected in each column.", 12 | privs = { worldedit = true }, 13 | require_pos = 2, 14 | parse = function (params_text) 15 | if params_text == "" then 16 | params_text = "stone" 17 | end 18 | local replace_node = worldedit.normalize_nodename(params_text) 19 | if not replace_node then 20 | return false, "Error: Invalid node name." 21 | end 22 | 23 | return true, replace_node 24 | end, 25 | nodes_needed = function(name) 26 | return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) 27 | end, 28 | func = function(name, replace_node) 29 | local start_time = wea_c.get_ms_time() 30 | local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) 31 | 32 | local success, stats = worldeditadditions.fillcaves( 33 | pos1, pos2, 34 | replace_node 35 | ) 36 | if not success then return success, stats end 37 | 38 | local time_taken = wea_c.get_ms_time() - start_time 39 | 40 | minetest.log("action", name .. " used //fillcaves at "..pos1.." - "..pos2..", replacing "..stats.replaced.." nodes in "..time_taken.."s") 41 | return true, stats.replaced.." nodes replaced in "..wea_c.format.human_time(time_taken) 42 | end 43 | }) 44 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/measure/init.lua: -------------------------------------------------------------------------------- 1 | -- ███ ███ ███████ █████ ███████ ██ ██ ██████ ███████ 2 | -- ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ 3 | -- ██ ████ ██ █████ ███████ ███████ ██ ██ ██████ █████ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ██ ██ ███████ ██ ██ ███████ ██████ ██ ██ ███████ 6 | 7 | -- Chat commands that measure things. 8 | 9 | local subpath = worldeditadditions_commands.modpath .. "/commands/measure/" 10 | 11 | dofile(subpath.."mface.lua") 12 | dofile(subpath.."midpos.lua") 13 | dofile(subpath.."msize.lua") 14 | dofile(subpath.."mtrig.lua") 15 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/measure/mface.lua: -------------------------------------------------------------------------------- 1 | -- ███ ███ ███████ █████ ██████ ███████ 2 | -- ████ ████ ██ ██ ██ ██ ██ 3 | -- ██ ████ ██ █████ ███████ ██ █████ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ██ ██ ██ ██ ██ ██████ ███████ 6 | 7 | worldeditadditions_core.register_command("mface", { 8 | params = "", 9 | description = "Return player facing axis.", 10 | privs = { worldedit = true }, 11 | require_pos = 0, 12 | parse = function(params_text) 13 | return true 14 | end, 15 | func = function(name, params_text) 16 | local str = "You are facing " 17 | local dir = minetest.get_player_by_name(name):get_look_dir() 18 | 19 | if math.abs(dir.z) > math.abs(dir.x) then 20 | if dir.z < 0 then str = str.."-Z" 21 | else str = str.."Z" end 22 | if math.abs(dir.x) >= 0.35 then -- Alternate value: 1/3 23 | if dir.x < 0 then str = str.."-X" 24 | else str = str.."X" end 25 | end 26 | else 27 | if dir.x < 0 then str = str.."-X" 28 | else str = str.."X" end 29 | if math.abs(dir.z) >= 0.35 then -- Alternate value: 1/3 30 | if dir.z < 0 then str = str.."-Z" 31 | else str = str.."Z" end 32 | end 33 | end 34 | 35 | return true, str 36 | end, 37 | }) 38 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/measure/midpos.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | 5 | -- ███ ███ ██ ██████ ██████ ██████ ███████ 6 | -- ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ 7 | -- ██ ████ ██ ██ ██ ██ ██████ ██ ██ ███████ 8 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 9 | -- ██ ██ ██ ██████ ██ ██████ ███████ 10 | worldeditadditions_core.register_command("midpos", { 11 | params = "", 12 | description = "Return the mid point of current selection.", 13 | privs = { worldedit = true }, 14 | require_pos = 2, 15 | parse = function(params_text) 16 | return true 17 | end, 18 | func = function(name, params_text) 19 | local str = "The centre of the current selection is at " 20 | 21 | local pos1 = Vector3.clone(worldedit.pos1[name]) 22 | local pos2 = Vector3.clone(worldedit.pos2[name]) 23 | 24 | 25 | local vec = Vector3.mean(pos1, pos2) 26 | 27 | return true, str .. wea_c.table.tostring(vec) 28 | end, 29 | }) 30 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/measure/msize.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | 5 | -- ███ ███ ███████ ██ ███████ ███████ 6 | -- ████ ████ ██ ██ ███ ██ 7 | -- ██ ████ ██ ███████ ██ ███ █████ 8 | -- ██ ██ ██ ██ ██ ███ ██ 9 | -- ██ ██ ███████ ██ ███████ ███████ 10 | 11 | worldeditadditions_core.register_command("msize", { 12 | params = "", 13 | description = "Return the length of each axis of current selection.", 14 | privs = { worldedit = true }, 15 | require_pos = 2, 16 | parse = function(params_text) 17 | return true 18 | end, 19 | func = function(name, params_text) 20 | local str = "The dimensions of the current selection are " 21 | 22 | local pos1 = Vector3.clone(worldedit.pos1[name]) 23 | local pos2 = Vector3.clone(worldedit.pos2[name]) 24 | 25 | local vec = (pos2 - pos1):abs() 26 | 27 | return true, str .. "x: " .. vec.x .. ", y: " .. vec.y .. ", z: " .. vec.z 28 | end, 29 | }) 30 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/measure/mtrig.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | 5 | -- ███ ███ ████████ ██████ ██ ██████ 6 | -- ████ ████ ██ ██ ██ ██ ██ 7 | -- ██ ████ ██ ██ ██████ ██ ██ ███ 8 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ 9 | -- ██ ██ ██ ██ ██ ██ ██████ 10 | 11 | worldeditadditions_core.register_command("mtrig", { 12 | params = "", 13 | description = "Return the length of and angles of an imginary line between pos1 and pos2 in the selection.", 14 | privs = { worldedit = true }, 15 | require_pos = 2, 16 | parse = function(params_text) 17 | return true 18 | end, 19 | func = function(name, params_text) 20 | local str = "The measurements of the line from pos1 to pos2 are Length (D): " 21 | 22 | local pos1 = Vector3.clone(worldedit.pos2[name]) 23 | local pos2 = Vector3.clone(worldedit.pos1[name]) 24 | 25 | local vec = (pos2 - pos1):abs() 26 | local len = vec:length() 27 | 28 | str = str..wea_c.round(len, 4)..", ∠XZ: ".. 29 | wea_c.round(math.deg(math.atan(vec.z/vec.x)), 4).."°, ∠DY: ".. 30 | wea_c.round(math.deg(math.asin(vec.y/len)), 4).."°" 31 | 32 | return true, str 33 | end, 34 | }) 35 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/meta/init.lua: -------------------------------------------------------------------------------- 1 | -- ███ ███ ███████ ████████ █████ 2 | -- ████ ████ ██ ██ ██ ██ 3 | -- ██ ████ ██ █████ ██ ███████ 4 | -- ██ ██ ██ ██ ██ ██ ██ 5 | -- ██ ██ ███████ ██ ██ ██ 6 | 7 | -- Commands that work on other commands. 8 | 9 | local we_cmdpath = worldeditadditions_commands.modpath .. "/commands/meta/" 10 | 11 | dofile(we_cmdpath.."airapply.lua") 12 | dofile(we_cmdpath.."ellipsoidapply.lua") 13 | dofile(we_cmdpath.."for.lua") 14 | -- dofile(we_cm.."macro.lua") -- Async bug 15 | dofile(we_cmdpath.."many.lua") 16 | dofile(we_cmdpath.."multi.lua") 17 | dofile(we_cmdpath.."noiseapply2d.lua") 18 | dofile(we_cmdpath.."subdivide.lua") 19 | 20 | dofile(we_cmdpath.."listentities.lua") -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/meta/listentities.lua: -------------------------------------------------------------------------------- 1 | -- Lists all currently loaded entities. 2 | 3 | local weac = worldeditadditions_core 4 | 5 | minetest.register_chatcommand("/listentities", { 6 | params = "", 7 | description = 8 | "Lists all currently loaded entities. This is a command for debugging and development. You will not need this unless you are developing a mod.", 9 | privs = { worldedit = true }, 10 | func = function(name, params_text) 11 | local table_vals = { 12 | { "ID", "Name", "Position" }, 13 | { "------", "-------", "---------" }, 14 | } 15 | for id, obj in pairs(minetest.object_refs) do 16 | local obj_name = "[ObjectRef]" 17 | if obj.get_luaentity then 18 | local luaentity = obj:get_luaentity() 19 | if luaentity then 20 | obj_name = "[LuaEntity:"..luaentity.name.."]" 21 | else 22 | obj_name = "[LuaEntity:__UNKNOWN__]" 23 | end 24 | end 25 | local pos = weac.Vector3.clone(obj:get_pos()) 26 | table.insert(table_vals, { 27 | id, 28 | obj_name, 29 | tostring(pos) 30 | }) 31 | end 32 | worldedit.player_notify(name, table.concat({ 33 | "Currently loaded entities:", 34 | weac.format.make_ascii_table(table_vals), 35 | "", 36 | "Total "..tostring(#table_vals).." objects" 37 | }, "\n")) 38 | end 39 | }) 40 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/overlay.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local wea = worldeditadditions 3 | local Vector3 = wea_c.Vector3 4 | 5 | -- ██████ ██ ██ ███████ ██████ ██ █████ ██ ██ 6 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 7 | -- ██ ██ ██ ██ █████ ██████ ██ ███████ ████ 8 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 9 | -- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██ 10 | worldeditadditions_core.register_command("overlay", { 11 | params = " [] [] [ []] ...", 12 | description = "Places in the last contiguous air space encountered above the first non-air node. In other words, overlays all top-most nodes in the specified area with . Optionally supports a mix of nodes and chances, as in //mix and //replacemix.", 13 | privs = { worldedit = true }, 14 | require_pos = 2, 15 | parse = function(params_text) 16 | local success, node_list = wea_c.parse.weighted_nodes( 17 | wea_c.split_shell(params_text) 18 | ) 19 | return success, node_list 20 | end, 21 | nodes_needed = function(name) 22 | -- //overlay only modifies up to 1 node per column in the selected region 23 | local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name]) 24 | return (pos2.x - pos1.x) * (pos2.y - pos1.y) 25 | end, 26 | func = function(name, node_list) 27 | local start_time = wea_c.get_ms_time() 28 | local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) 29 | 30 | local changes = wea.overlay( 31 | pos1, pos2, 32 | node_list 33 | ) 34 | local time_taken = wea_c.get_ms_time() - start_time 35 | 36 | minetest.log("action", name .. " used //overlay at " .. pos1 .. " - "..pos2..", replacing " .. changes.updated .. " nodes and skipping " .. changes.skipped_columns .. " columns in " .. time_taken .. "s") 37 | return true, changes.updated .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped in " .. wea_c.format.human_time(time_taken) 38 | end 39 | }) 40 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/init.lua: -------------------------------------------------------------------------------- 1 | -- ███████ ███████ ██ ███████ ██████ ████████ ██████ ██████ ███████ 2 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 3 | -- ███████ █████ ██ █████ ██ ██ ██ ██ ██████ ███████ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ███████ ███████ ███████ ███████ ██████ ██ ██████ ██ ██ ███████ 6 | 7 | -- Chat commands that operate on selections. 8 | 9 | local wea_cmdpath = worldeditadditions_commands.modpath .. "/commands/selectors/" 10 | local weac = worldeditadditions_core 11 | 12 | dofile(wea_cmdpath.."srel.lua") 13 | dofile(wea_cmdpath.."scentre.lua") 14 | dofile(wea_cmdpath.."scloud.lua") 15 | dofile(wea_cmdpath.."scol.lua") 16 | dofile(wea_cmdpath.."scube.lua") 17 | dofile(wea_cmdpath.."sfactor.lua") 18 | dofile(wea_cmdpath.."smake.lua") 19 | dofile(wea_cmdpath.."spop.lua") 20 | dofile(wea_cmdpath.."spush.lua") 21 | dofile(wea_cmdpath.."srect.lua") 22 | dofile(wea_cmdpath.."sshift.lua") 23 | dofile(wea_cmdpath.."sstack.lua") 24 | 25 | dofile(wea_cmdpath.."unmark.lua") 26 | dofile(wea_cmdpath.."mark.lua") 27 | dofile(wea_cmdpath.."pos1-2.lua") 28 | dofile(wea_cmdpath.."reset.lua") 29 | 30 | -- Aliases 31 | weac.register_alias("sfac", "sfactor") 32 | 33 | weac.register_alias("1", "pos1", true) -- true = override target 34 | weac.register_alias("2", "pos2", true) -- true = override target 35 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/mark.lua: -------------------------------------------------------------------------------- 1 | local weac = worldeditadditions_core 2 | 3 | 4 | local function do_mark(name, params_text) 5 | -- TODO: Decide whether we need to hided the worldeditadditions marker here or not. 6 | -- Show the WorldEditAdditions marker 7 | weac.pos.mark(name) 8 | end 9 | 10 | if minetest.registered_chatcommands["/mark"] then 11 | minetest.override_chatcommand("/mark", { 12 | params = "", 13 | description = "Show the markers for the defined region (and any other positions) once more.", 14 | func = do_mark 15 | }) 16 | else 17 | minetest.register_chatcommand("/mark", { 18 | params = "", 19 | description = "Show the markers for the defined region (and any other positions) once more.", 20 | privs = { worldedit = true }, 21 | func = do_mark 22 | }) 23 | end -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/pos1-2.lua: -------------------------------------------------------------------------------- 1 | local weac = worldeditadditions_core 2 | local Vector3 = weac.Vector3 3 | 4 | 5 | local function do_set(name, i) 6 | local player = minetest.get_player_by_name(name) 7 | weac.pos.set(name, i, Vector3.round(player:get_pos())) 8 | end 9 | 10 | local function do_set1(name, params_text) 11 | do_set(name, 1) 12 | end 13 | local function do_set2(name, params_text) 14 | do_set(name, 2) 15 | end 16 | 17 | if minetest.registered_chatcommands["/pos1"] then 18 | minetest.override_chatcommand("/pos1", { 19 | params = "", 20 | description = 21 | "Sets pos1 to the current position of the calling player.", 22 | func = do_set1 23 | }) 24 | else 25 | minetest.register_chatcommand("/pos1", { 26 | params = "", 27 | description = 28 | "Sets pos1 to the current position of the calling player.", 29 | privs = { worldedit = true }, 30 | func = do_set1 31 | }) 32 | end 33 | if minetest.registered_chatcommands["/pos2"] then 34 | minetest.override_chatcommand("/pos2", { 35 | params = "", 36 | description = "Sets pos2 to the current position of the calling player.", 37 | func = do_set2 38 | }) 39 | else 40 | minetest.register_chatcommand("/pos2", { 41 | params = "", 42 | description = "Sets pos2 to the current position of the calling player.", 43 | privs = { worldedit = true }, 44 | func = do_set2 45 | }) 46 | end 47 | 48 | 49 | minetest.register_chatcommand("//pos", { 50 | params = "", 51 | description = "Sets position to the current position of the calling player.", 52 | privs = { worldedit = true }, 53 | func = function(name, params_text) 54 | local i = tonumber(params_text) 55 | if type(i) ~= "number" then 56 | worldedit.player_notify(name, "Error: Invalid index number given.") 57 | return 58 | end 59 | i = math.floor(i) 60 | 61 | do_set(name, i) 62 | end 63 | }) -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/reset.lua: -------------------------------------------------------------------------------- 1 | local weac = worldeditadditions_core 2 | 3 | 4 | local worldedit_reset 5 | if minetest.registered_chatcommands["/reset"] then 6 | worldedit_reset = minetest.registered_chatcommands["/reset"].func 7 | end 8 | 9 | local function do_reset(name, params_text) 10 | -- Hide the WorldEdit marker, if appropriate 11 | if type(worldedit_reset) == "function" then 12 | worldedit_reset(name, params_text) 13 | end 14 | 15 | -- Hide the WorldEditAdditions marker 16 | weac.pos.clear(name) 17 | end 18 | 19 | if minetest.registered_chatcommands["/reset"] then 20 | minetest.override_chatcommand("/reset", { 21 | params = "", 22 | description = "Clears all defined points and the currently defined region.", 23 | func = do_reset 24 | }) 25 | else 26 | minetest.register_chatcommand("/reset", { 27 | params = "", 28 | description = "Clears all defined points and the currently defined region.", 29 | privs = { worldedit = true }, 30 | func = do_reset 31 | }) 32 | end -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/scentre.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | -- ███████ ██████ ███████ ███ ██ ████████ ███████ ██████ 5 | -- ██ ██ ██ ████ ██ ██ ██ ██ ██ 6 | -- ███████ ██ █████ ██ ██ ██ ██ █████ ██████ 7 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 8 | -- ███████ ██████ ███████ ██ ████ ██ ███████ ██ ██ 9 | worldeditadditions_core.register_command("scentre", { 10 | params = "", 11 | description = "Set WorldEdit region positions 1 and 2 to the centre of the current selection.", 12 | privs = {worldedit=true}, 13 | require_pos = 2, 14 | parse = function(params_text) 15 | return true 16 | end, 17 | func = function(name) 18 | local mean = Vector3.mean( 19 | Vector3.clone(worldedit.pos1[name]), 20 | Vector3.clone(worldedit.pos2[name]) 21 | ) 22 | local pos1, pos2 = Vector3.clone(mean), Vector3.clone(mean) 23 | 24 | pos1 = pos1:floor() 25 | pos2 = pos2:ceil() 26 | 27 | worldedit.pos1[name], worldedit.pos2[name] = pos1, pos2 28 | worldedit.mark_pos1(name) 29 | worldedit.mark_pos2(name) 30 | 31 | return true, "position 1 set to "..pos1..", position 2 set to "..pos2 32 | end, 33 | }) 34 | 35 | -- lua print(vecs.mean.x..", "..vecs.mean.y..", "..vecs.mean.z) 36 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/scloud.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | -- ██████ ██████ ██ ██████ ██ ██ ██████ 6 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ 7 | -- ███████ ██ ██ ██ ██ ██ ██ ██ ██ 8 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ 9 | -- ██████ ██████ ███████ ██████ ██████ ██████ 10 | minetest.register_on_punchnode(function(pos, node, puncher) 11 | local name = puncher:get_player_name() 12 | if name ~= "" and wea.add_pos[name] ~= nil then 13 | if wea.add_pos[name] > 0 then 14 | wea.selection.add_point(name, pos) 15 | wea.add_pos[name] = wea.add_pos[name] - 1 16 | worldedit.player_notify(name, "You have "..wea.add_pos[name].." nodes left to punch") 17 | else wea.add_pos[name] = nil end 18 | end 19 | end) 20 | worldeditadditions_core.register_command("scloud", { 21 | params = "<0-6|stop|reset>", 22 | description = "Set and add to WorldEdit region by punching up to six nodes that define the maximums of your target", 23 | privs = {worldedit=true}, 24 | parse = function(param) 25 | return true, param 26 | end, 27 | func = function(name, param) 28 | local que = tonumber(param) 29 | if que then 30 | if que > 0 then 31 | wea.add_pos[name] = que < 7 and que or 6 32 | return true, "create or add to selection by punching "..wea.add_pos[name].." nodes" 33 | else 34 | wea.add_pos[name] = nil 35 | return true, "0 nodes to punch: operation canceled" 36 | end 37 | elseif param == "stop" then 38 | wea.add_pos[name] = nil 39 | return true, "selection operation stopped" 40 | elseif param == "reset" then 41 | wea.add_pos[name] = nil 42 | wea.selection.clear_points(name) 43 | return true, "selection cleared" 44 | else 45 | return false, (param == "" and "no input" or "invalid input: '"..param.."'").."! Allowed params are: 0-6, stop, or reset" 46 | end 47 | end, 48 | }) 49 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/scol.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | -- ███████ ██████ ██████ ██ 5 | -- ██ ██ ██ ██ ██ 6 | -- ███████ ██ ██ ██ ██ 7 | -- ██ ██ ██ ██ ██ 8 | -- ███████ ██████ ██████ ███████ 9 | worldeditadditions_core.register_command("scol", { 10 | params = "[] ", 11 | description = "Set WorldEdit region position 2 at a set distance along 1 axis.", 12 | privs = {worldedit=true}, 13 | require_pos = 1, 14 | parse = function(params_text) 15 | local vec, tmp = Vector3.new(0, 0, 0), {} 16 | local find = wea_c.split(params_text, "%s", false) 17 | local ax1, sn1, len = (tostring(find[1]):match('[xyz]') or "g"):sub(1,1), wea_c.getsign(find[1]), find[table.maxn(find)] 18 | 19 | tmp.len = tonumber(len) 20 | -- If len == nil cancel the operation 21 | if not tmp.len then return false, "No length specified." end 22 | -- If ax1 is bad send "get" order 23 | if ax1 == "g" then tmp.get = true 24 | else vec[ax1] = sn1 * tmp.len end 25 | 26 | return true, vec, tmp 27 | -- tmp carries: 28 | -- The length (len) arguement to the main function for use if "get" is invoked there 29 | -- The bool value "get" to tell the main function if it needs to populate missing information in vec 30 | end, 31 | func = function(name, vec, tmp) 32 | if tmp.get then 33 | local ax, dir = wea_c.player_axis2d(name) 34 | vec[ax] = tmp.len * dir 35 | end 36 | 37 | local pos2 = vec + Vector3.clone(worldedit.pos1[name]) 38 | worldedit.pos2[name] = pos2 39 | worldedit.mark_pos2(name) 40 | return true, "position 2 set to "..pos2 41 | end, 42 | }) 43 | 44 | -- Tests 45 | -- /multi //fp set1 -63 19 -20 //scol 5 46 | 47 | -- lua print(worldedit.player_axis(myname)) 48 | -- tonumber(('y1'):gsub('[xyz]',''):sub(1,2)) 49 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/spop.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local weac = worldeditadditions_core 3 | 4 | 5 | -- ███████ ██████ ██████ ██████ 6 | -- ██ ██ ██ ██ ██ ██ ██ 7 | -- ███████ ██████ ██ ██ ██████ 8 | -- ██ ██ ██ ██ ██ 9 | -- ███████ ██ ██████ ██ 10 | worldeditadditions_core.register_command("spop", { 11 | params = "", 12 | description = "Pops a region off your (per-user) selection stack.", 13 | privs = { worldedit = true }, 14 | parse = function(params_text) 15 | return true 16 | end, 17 | nodes_needed = function(name) 18 | return 0 19 | end, 20 | func = function(name) 21 | local success, pos1, pos2 = wea.spop(name) 22 | if not success then return success, pos1 end 23 | 24 | weac.pos.set1(name, pos1) 25 | weac.pos.set2(name, pos2) 26 | -- worldedit.pos1[name] = pos1 27 | -- worldedit.pos2[name] = pos2 28 | -- worldedit.marker_update(name) 29 | 30 | local new_count = wea.scount(name) 31 | local plural = "s are" 32 | if new_count == 1 then plural = " is" end 33 | 34 | local region_text = pos1.." - "..pos2 35 | 36 | minetest.log("action", name .. " used //spopped at "..region_text..". Stack height is now " .. new_count.." regions") 37 | return true, "Region "..region_text.." popped from selection stack; "..new_count.." region"..plural.." now in the stack" 38 | end 39 | }) 40 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/spush.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | -- ███████ ██████ ██ ██ ███████ ██ ██ 6 | -- ██ ██ ██ ██ ██ ██ ██ ██ 7 | -- ███████ ██████ ██ ██ ███████ ███████ 8 | -- ██ ██ ██ ██ ██ ██ ██ 9 | -- ███████ ██ ██████ ███████ ██ ██ 10 | worldeditadditions_core.register_command("spush", { 11 | params = "", 12 | description = "Pushes the currently defined region onto your (per-user) selection stack.", 13 | privs = { worldedit = true }, 14 | require_pos = 1, 15 | parse = function(params_text) 16 | return true 17 | end, 18 | nodes_needed = function(name) 19 | return 0 20 | end, 21 | func = function(name) 22 | local pos1 = Vector3.clone(worldedit.pos1[name]) 23 | local pos2 = Vector3.clone(worldedit.pos2[name]) 24 | local success, msg = wea.spush(name, pos1, pos2) 25 | if not success then 26 | return success, msg 27 | end 28 | 29 | local new_count = wea.scount(name) 30 | local plural = "s are" 31 | if new_count == 1 then plural = " is" end 32 | 33 | local region_text = pos1.." - "..pos2 34 | 35 | minetest.log("action", name .. " used //spush at "..region_text..". Stack height is now " .. new_count.." regions") 36 | return true, "Region "..region_text.." pushed onto selection stack; "..new_count.." region"..plural.." now in the stack" 37 | end 38 | }) 39 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/srel.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | 6 | -- ███████ ██████ ███████ ██ 7 | -- ██ ██ ██ ██ ██ 8 | -- ███████ ██████ █████ ██ 9 | -- ██ ██ ██ ██ ██ 10 | -- ███████ ██ ██ ███████ ███████ 11 | local function parse_with_name(name,args) 12 | local vec, tmp = Vector3.new(0, 0, 0), {} 13 | local find, _, i = {}, 0, 0 14 | repeat 15 | _, i, tmp.proc = args:find("([%l%s+-]+%d+)%s*", i) 16 | if tmp.proc:match("[xyz]") then 17 | tmp.ax = tmp.proc:match("[xyz]") 18 | tmp.dir = tonumber(tmp.proc:match("[+-]?%d+")) * (tmp.proc:match("-%l+") and -1 or 1) 19 | else 20 | tmp.ax, _ = wea_c.dir_to_xyz(name, tmp.proc:match("%l+")) 21 | if not tmp.ax then return false, _ end 22 | tmp.dir = tonumber(tmp.proc:match("[+-]?%d+")) * (tmp.proc:match("-%l+") and -1 or 1) * _ 23 | end 24 | vec[tmp.ax] = tmp.dir 25 | until not args:find("([%l%s+-]+%d+)%s*", i) 26 | return true, vec 27 | end 28 | worldeditadditions_core.register_command("srel", { 29 | params = " [ [ ]]", 30 | description = "Set WorldEdit region position 2 at set distances along 3 axes.", 31 | privs = { worldedit = true }, 32 | require_pos = 0, 33 | parse = function(params_text) 34 | if params_text:match("([%l%s+-]+%d+)") then return true, params_text 35 | else return false, "No acceptable params found" end 36 | end, 37 | func = function(name, params_text) 38 | local ret = "" 39 | local _, vec = parse_with_name(name,params_text) 40 | if not _ then return false, vec end 41 | 42 | if not worldedit.pos1[name] then 43 | local pos = wea_c.player_vector(name) + Vector3.new(0.5, -0.5, 0.5) 44 | pos = pos:floor() 45 | worldedit.pos1[name] = pos 46 | worldedit.mark_pos1(name) 47 | ret = "position 1 set to "..pos..", " 48 | end 49 | 50 | local p2 = vec + Vector3.clone(worldedit.pos1[name]) 51 | worldedit.pos2[name] = p2 52 | worldedit.mark_pos2(name) 53 | return true, ret.."position 2 set to "..p2 54 | end, 55 | }) 56 | 57 | -- Tests 58 | -- //srel front 5 left 3 y 2 59 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/sshift.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = worldeditadditions.Vector3 4 | 5 | -- ███████ ███████ ██ ██ ██ ███████ ████████ 6 | -- ██ ██ ██ ██ ██ ██ ██ 7 | -- ███████ ███████ ███████ ██ █████ ██ 8 | -- ██ ██ ██ ██ ██ ██ ██ 9 | -- ███████ ███████ ██ ██ ██ ██ ██ 10 | 11 | local function parse_with_name(name,args) 12 | local vec, tmp = Vector3.new(0, 0, 0), {} 13 | local find, _, i = {}, 0, 0 14 | repeat 15 | _, i, tmp.proc = args:find("([%l%s+-]+%d+)%s*", i) 16 | if tmp.proc:match("[xyz]") then 17 | tmp.ax = tmp.proc:match("[xyz]") 18 | tmp.dir = tonumber(tmp.proc:match("[+-]?%d+")) * (tmp.proc:match("-%l+") and -1 or 1) 19 | else 20 | tmp.ax, _ = wea_c.dir_to_xyz(name, tmp.proc:match("%l+")) 21 | if not tmp.ax then return false, _ end 22 | tmp.dir = tonumber(tmp.proc:match("[+-]?%d+")) * (tmp.proc:match("-%l+") and -1 or 1) * _ 23 | end 24 | vec[tmp.ax] = tmp.dir 25 | until not args:find("([%l%s+-]+%d+)%s*", i) 26 | return true, vec 27 | end 28 | 29 | worldeditadditions_core.register_command("sshift", { 30 | params = " [ [ ]]", 31 | description = "Shift the WorldEdit region in 3 dimensions.", 32 | privs = { worldedit = true }, 33 | require_pos = 2, 34 | parse = function(params_text) 35 | if params_text:match("([%l%s+-]+%d+)") then return true, params_text 36 | else return false, "No acceptable params found" end 37 | end, 38 | func = function(name, params_text) 39 | local _, vec = parse_with_name(name,params_text) 40 | if not _ then return false, vec end 41 | 42 | local pos1 = vec:add(worldedit.pos1[name]) 43 | worldedit.pos1[name] = pos1 44 | worldedit.mark_pos1(name) 45 | 46 | local pos2 = vec:add(worldedit.pos2[name]) 47 | worldedit.pos2[name] = pos2 48 | worldedit.mark_pos2(name) 49 | 50 | return true, "Region shifted by " .. (vec.x + vec.y + vec.z) .. " nodes." 51 | end, 52 | }) 53 | 54 | -- Tests 55 | -- //srel front 5 left 3 y 2 56 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/sstack.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | -- ███████ ███████ ████████ █████ ██████ ██ ██ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ███████ ███████ ██ ███████ ██ █████ 6 | -- ██ ██ ██ ██ ██ ██ ██ ██ 7 | -- ███████ ███████ ██ ██ ██ ██████ ██ ██ 8 | worldeditadditions_core.register_command("sstack", { 9 | params = "", 10 | description = "Displays the contents of your (per-user) selection stack.", 11 | privs = { worldedit = true }, 12 | parse = function(params_text) 13 | return true 14 | end, 15 | nodes_needed = function(name) 16 | return 0 17 | end, 18 | func = function(name) 19 | 20 | local result = {"Stack contents for user ", name, ":\n"} 21 | if not worldeditadditions.sstack[name] then 22 | table.insert(result, "(empty)") 23 | else 24 | for i,item in ipairs(worldeditadditions.sstack[name]) do 25 | -- TODO: Implement a volume command.... 26 | local volume = worldedit.volume(item[1], item[2]) 27 | local volume_text = wea_c.format.human_size(volume, 2) 28 | if volume > 1000 then volume_text = "~"..volume_text end 29 | 30 | table.insert(result, i) 31 | table.insert(result, ": ") 32 | 33 | table.insert(result, volume_text) 34 | table.insert(result, " nodes - ") 35 | table.insert(result, tostring(item[1])) -- Vector3 instance 36 | table.insert(result, " - ") 37 | table.insert(result, tostring(item[2])) -- Vector3 instance 38 | table.insert(result, "\n") 39 | end 40 | table.insert(result, "========================\nTotal ") 41 | table.insert(result, #worldeditadditions.sstack[name]) 42 | table.insert(result, " items") 43 | end 44 | return true, table.concat(result, "") 45 | end 46 | }) 47 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/selectors/unmark.lua: -------------------------------------------------------------------------------- 1 | local weac = worldeditadditions_core 2 | 3 | 4 | local worldedit_unmark 5 | if minetest.registered_chatcommands["/unmark"] then 6 | worldedit_unmark = minetest.registered_chatcommands["/unmark"].func 7 | end 8 | 9 | local function do_unmark(name, params_text) 10 | -- Hide the WorldEdit marker, if appropriate 11 | if type(worldedit_unmark) == "function" then 12 | worldedit_unmark(name, params_text) 13 | end 14 | 15 | -- Hide the WorldEditAdditions marker 16 | weac.pos.unmark(name) 17 | end 18 | 19 | if minetest.registered_chatcommands["/unmark"] then 20 | minetest.override_chatcommand("/unmark", { 21 | params = "", 22 | description = "Hide the markers for the defined region (and any other positions), but do not remove the points themselves.", 23 | func = do_unmark 24 | }) 25 | else 26 | minetest.register_chatcommand("/unmark", { 27 | params = "", 28 | description = "Hide the markers for the defined region (and any other positions), but do not remove the points themselves.", 29 | privs = { worldedit = true }, 30 | func = do_unmark 31 | }) 32 | end -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/wireframe/init.lua: -------------------------------------------------------------------------------- 1 | -- ██ ██ ██ ██████ ███████ ███████ ██████ █████ ███ ███ ███████ 2 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ 3 | -- ██ █ ██ ██ ██████ █████ █████ ██████ ███████ ██ ████ ██ █████ 4 | -- ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ███ ███ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ███████ 6 | 7 | -- 2d and 3d outlines of shapes. 8 | 9 | local wea_cmd = worldeditadditions_commands.modpath .. "/commands/wireframe/" 10 | 11 | dofile(wea_cmd.."wbox.lua") 12 | dofile(wea_cmd.."wcompass.lua") 13 | dofile(wea_cmd.."wcorner.lua") 14 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/wireframe/wbox.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | 6 | -- ██ ██ ██████ ██████ ██ ██ 7 | -- ██ ██ ██ ██ ██ ██ ██ ██ 8 | -- ██ █ ██ ██████ ██ ██ ███ 9 | -- ██ ███ ██ ██ ██ ██ ██ ██ ██ 10 | -- ███ ███ ██████ ██████ ██ ██ 11 | 12 | worldeditadditions_core.register_command("wbox", { 13 | params = "", 14 | description = "Sets the edges of the current selection to ", 15 | privs = {worldedit=true}, 16 | require_pos = 2, 17 | parse = function(params_text) 18 | if params_text == "" then 19 | return false, "Error: too few arguments! Expected: \"\"" 20 | end 21 | local node = worldedit.normalize_nodename(params_text) 22 | if not node then 23 | return false, "invalid node name: " .. params_text 24 | end 25 | return true, node 26 | end, 27 | nodes_needed = function(name) 28 | local delta = Vector3.subtract(worldedit.pos2[name], worldedit.pos1[name]):abs():add(1) 29 | local total, mult, axes = 1, 4, {"x","y","z"} 30 | for k,v in pairs(axes) do 31 | if worldedit.pos1[name] ~= worldedit.pos2[name] then total = total*2 32 | else mult = mult/2 end 33 | end 34 | for k,v in pairs(axes) do 35 | if delta[v] > 2 then total = total + (delta[v] - 2)*mult end 36 | end 37 | return total 38 | end, 39 | func = function(name, node) 40 | local start_time = wea_c.get_ms_time() 41 | 42 | local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) 43 | local success, count = wea.wire_box(pos1, pos2, node) 44 | if not success then return success, count end 45 | 46 | local time_taken = wea_c.format.human_time(wea_c.get_ms_time() - start_time) 47 | 48 | 49 | minetest.log("action", name.." used //wbox at "..pos1.." - "..pos2..", taking "..time_taken) 50 | return true, count .. " nodes set in "..time_taken 51 | end, 52 | }) 53 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/wireframe/wcompass.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | local Vector3 = wea_c.Vector3 4 | 5 | -- ██ ██ ██████ ██████ ███ ███ ██████ █████ ███████ ███████ 6 | -- ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ 7 | -- ██ █ ██ ██ ██ ██ ██ ████ ██ ██████ ███████ ███████ ███████ 8 | -- ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 9 | -- ███ ███ ██████ ██████ ██ ██ ██ ██ ██ ███████ ███████ 10 | 11 | worldeditadditions_core.register_command("wcompass", { 12 | params = " []", 13 | description = "Creates a compass around pos1 with a single node bead pointing north (+Z).", 14 | privs = {worldedit=true}, 15 | require_pos = 1, 16 | parse = function(params_text) 17 | local parts = wea_c.split(params_text," ",true) 18 | if not parts[1] then 19 | return false, "Error: too few arguments! Expected: \" []\"" 20 | elseif not parts[2] then 21 | parts[2] = parts[1] 22 | end 23 | local node1 = worldedit.normalize_nodename(parts[1]) 24 | local node2 = worldedit.normalize_nodename(parts[2]) 25 | if not node1 then 26 | return false, "Invalid : " .. parts[1] 27 | elseif not node2 then 28 | return false, "Invalid : " .. parts[2] 29 | end 30 | return true, node1, node2 31 | end, 32 | nodes_needed = function(name) 33 | return 26 34 | end, 35 | func = function(name, node1, node2) 36 | local start_time = wea_c.get_ms_time() 37 | 38 | local pos1 = Vector3.clone(worldedit.pos1[name]) 39 | local success, count = wea.make_compass(pos1, node1, node2) 40 | if not success then return success, count end 41 | 42 | local time_taken = wea_c.format.human_time(wea_c.get_ms_time() - start_time) 43 | 44 | 45 | minetest.log("action", name.." used //wcompass at "..pos1..", taking "..time_taken) 46 | return true, count .. " nodes set in "..time_taken 47 | 48 | end, 49 | }) 50 | -------------------------------------------------------------------------------- /worldeditadditions_commands/commands/wireframe/wcorner.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local wea = worldeditadditions 3 | local Vector3 = wea_c.Vector3 4 | 5 | -- ██ ██ ██████ ██████ ██████ ███ ██ ███████ ██████ 6 | -- ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ 7 | -- ██ █ ██ ██ ██ ██ ██████ ██ ██ ██ █████ ██████ 8 | -- ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 9 | -- ███ ███ ██████ ██████ ██ ██ ██ ████ ███████ ██ ██ 10 | 11 | worldeditadditions_core.register_command("wcorner", { 12 | params = "", 13 | description = "Set the corners of the current selection to ", 14 | privs = {worldedit=true}, 15 | require_pos = 2, 16 | parse = function(params_text) 17 | local node = worldedit.normalize_nodename(params_text) 18 | if not node then 19 | return false, "invalid node name: " .. params_text 20 | end 21 | return true, node 22 | end, 23 | nodes_needed = function(name) 24 | local p1, p2, total = worldedit.pos1[name], worldedit.pos2[name], 1 25 | for k,v in pairs({"x","y","z"}) do 26 | if p1[v] ~= p2[v] then total = total*2 end 27 | end 28 | return total 29 | end, 30 | func = function(name, node) 31 | 32 | local start_time = wea_c.get_ms_time() 33 | 34 | local pos1, pos2 = Vector3.sort(worldedit.pos1[name], worldedit.pos2[name]) 35 | local success, count = wea.corner_set(pos1, pos2, node) 36 | if not success then return success, count end 37 | 38 | local time_taken = wea_c.format.human_time(wea_c.get_ms_time() - start_time) 39 | 40 | 41 | 42 | minetest.log("action", name.." used //wcorner at "..pos1.." - "..pos2..", taking "..time_taken) 43 | return true, count .. " nodes set in "..time_taken 44 | end, 45 | }) 46 | -------------------------------------------------------------------------------- /worldeditadditions_commands/doc/init.lua: -------------------------------------------------------------------------------- 1 | -- ██████ ██████ ██████ ███████ 2 | -- ██ ██ ██ ██ ██ ██ 3 | -- ██ ██ ██ ██ ██ ███████ 4 | -- ██ ██ ██ ██ ██ ██ 5 | -- ██████ ██████ ██████ ███████ 6 | 7 | -- This directory contains support for the doc mod: 8 | -- https://content.minetest.net/packages/Wuzzy/doc/ 9 | -- API docs: https://repo.or.cz/minetest_doc.git/blob/HEAD:/API.md 10 | 11 | -- The strategy here is not to have duplicate content, but to pull data from 12 | -- existing sources. 13 | -- Long-form article descriptions: Chat-Command-Reference.md 14 | -- Short descriptions: Undecided, but maybe from the registered command definition? 15 | 16 | worldeditadditions.doc = { 17 | parse_reference = dofile(worldeditadditions_commands.modpath.."/doc/parse_reference.lua") 18 | } 19 | 20 | -------------------------------------------------------------------------------- /worldeditadditions_commands/doc/parse_reference.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | local function get_reference() 4 | local lines = {} 5 | for line in io.lines(wea_c.modpath.."/Chat-Command-Reference.md") do 6 | table.insert(lines, line) 7 | end 8 | return lines 9 | end 10 | 11 | local function group_by_heading(lines, max_level) 12 | local groups = {} 13 | local acc = {} 14 | 15 | for i,line in ipairs(lines) do 16 | if wea_c.str_starts(line, "#") then 17 | local _, _, heading, headingtext = line:find("(#+)%s*(.*)") 18 | if #heading <= max_level then 19 | table.insert(groups, { 20 | level = #heading, 21 | heading = headingtext, 22 | text = table.concat(acc, "\n") 23 | }) 24 | acc = {} 25 | end 26 | else 27 | table.insert(acc, line) 28 | end 29 | end 30 | return groups 31 | end 32 | 33 | local function parse_reference() 34 | local lines = get_reference() 35 | local headings = wea_c.table.filter( 36 | group_by_heading(lines, 2), 37 | function(item, i) return item.level ~= 2 end 38 | ) 39 | for i,value in ipairs(headings) do 40 | print(i, "level", value.level, "heading", value.heading, "text", value.text) 41 | end 42 | end 43 | 44 | return parse_reference 45 | -------------------------------------------------------------------------------- /worldeditadditions_commands/mod.conf: -------------------------------------------------------------------------------- 1 | name = worldeditadditions_commands 2 | description = worldeditadditions: chat command interfaces 3 | depends = worldeditadditions, worldeditadditions_core, worldedit_commands, worldedit_shortcommands, worldedit 4 | optional_depends = worldeditdebug, bonemeal 5 | -------------------------------------------------------------------------------- /worldeditadditions_commands/player_notify_suppress.lua: -------------------------------------------------------------------------------- 1 | -- Overrides worldedit.player_notify() to add the ability to 2 | -- suppress messages (used in //subdivide) 3 | 4 | local player_notify_suppressed = {} 5 | 6 | local orig_player_notify = worldedit.player_notify 7 | function worldedit.player_notify(name, message) 8 | if not player_notify_suppressed[name] then 9 | orig_player_notify(name, message) 10 | end 11 | end 12 | 13 | --- Disables sending worldedit messages to the player with the given name. 14 | function worldedit.player_notify_suppress(name) 15 | player_notify_suppressed[name] = true 16 | end 17 | --- Enables sending worldedit messages to the player with the given name. 18 | function worldedit.player_notify_unsuppress(name) 19 | player_notify_suppressed[name] = nil 20 | end 21 | -------------------------------------------------------------------------------- /worldeditadditions_core/README.md: -------------------------------------------------------------------------------- 1 | # worldeditadditions_core 2 | 3 | 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 4 | 5 | In short, `worldeditadditions_core` is a reimplementation of a number of underlying parts of the worldedit engine. Parts of `worldeditadditions_core` are likely to look very similar to parts of WorldEdit - this is because inspiration was taken from Uberi/WorldEdit when implementing `worldeditadditions_core`. 6 | -------------------------------------------------------------------------------- /worldeditadditions_core/core/entities/init.lua: -------------------------------------------------------------------------------- 1 | -- ███████ ███ ██ ████████ ██ ████████ ██ ███████ ███████ 2 | -- ██ ████ ██ ██ ██ ██ ██ ██ ██ 3 | -- █████ ██ ██ ██ ██ ██ ██ ██ █████ ███████ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ███████ ██ ████ ██ ██ ██ ██ ███████ ███████ 6 | 7 | --- Entities and functions to manage them. 8 | -- @namespace worldeditadditions_core.entities 9 | 10 | local wea_c = worldeditadditions_core 11 | 12 | return { 13 | pos_marker = dofile(wea_c.modpath.."/core/entities/pos_marker.lua"), 14 | pos_marker_wall = dofile(wea_c.modpath.."/core/entities/pos_marker_wall.lua") 15 | } -------------------------------------------------------------------------------- /worldeditadditions_core/core/fetch_command_def.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | --- Fetches the definition of a WorldEditAdditions or WorldEdit command 4 | -- Does not support fetching generic Minetest commands - check 5 | -- minetest.registered_chatcommands for this. 6 | -- @param cmdname string The name of the command to fetch the definition for. 7 | local function fetch_command_def(cmdname) 8 | local wea_c = worldeditadditions_core 9 | 10 | if wea_c.registered_commands[cmdname] then 11 | return wea_c.registered_commands[cmdname] 12 | end 13 | if minetest.global_exists("worldedit") and worldedit.registered_commands and worldedit.registered_commands[cmdname] then 14 | return worldedit.registered_commands[cmdname] 15 | end 16 | 17 | return nil 18 | end 19 | 20 | return fetch_command_def 21 | -------------------------------------------------------------------------------- /worldeditadditions_core/core/integrations/noworldedit.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | --- WorldEdit shim just in case WorldEdit doesn't exist. 4 | -- Eventually this will go away. 5 | worldedit = { 6 | -- Note that you want worldeditadditions_core.registered_commands, and not worldedit.registered_commands! This table is not guaranteed to contain all command definitions in the future, whereas worldedit command definitions are guaranteed to be imported into this worldeditadditions_core.registered_commands. 7 | registered_commands = { } 8 | } 9 | -------------------------------------------------------------------------------- /worldeditadditions_core/core/integrations/worldedit.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | 4 | for name,definition in pairs(worldedit.registered_commands) do 5 | -- This check should not be needed since worldeditadditions_commands 6 | -- depends on this mod (so it will overwrite any worldedit definitions, 7 | -- since worldedit is loaded first), but it's here just in case 8 | if not wea_c.registered_commands[name] then 9 | -- The Minetest chat command should already be imported here, so we 10 | -- just need to import worldedit chat command definition here 11 | wea_c.registered_commands[name] = definition 12 | else 13 | minetest.log("info", "Skipping registration of worldedit command "..name..", as it's already a registered worldeditadditions command") 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /worldeditadditions_core/core/register_alias.lua: -------------------------------------------------------------------------------- 1 | 2 | local wea_c = worldeditadditions_core 3 | 4 | local function register_alias(cmdname_target, cmdname_source, override) 5 | if override == nil then override = false end 6 | 7 | local def_source = wea_c.fetch_command_def(cmdname_source) 8 | 9 | if not def_source then 10 | minetest.log("error", "worldeditadditions_core: Failed to register alias for "..cmdname_source.." → "..cmdname_target..", as the source command doesn't exist.") 11 | return false 12 | end 13 | 14 | if wea_c.fetch_command_def(cmdname_target) and not override then 15 | minetest.log("error", "worldeditadditions_core: Failed to register alias for "..cmdname_source.." → "..cmdname_target..", as the target command exists and override wasn't set to true.") 16 | return false 17 | end 18 | 19 | -- print("DEBUG ALIAS source "..cmdname_source.." target "..cmdname_target) 20 | 21 | if minetest.registered_chatcommands["/" .. cmdname_target] then 22 | minetest.override_chatcommand( 23 | "/"..cmdname_target, 24 | minetest.registered_chatcommands["/" .. cmdname_source] 25 | ) 26 | else 27 | minetest.register_chatcommand( 28 | "/"..cmdname_target, 29 | minetest.registered_chatcommands["/" .. cmdname_source] 30 | ) 31 | end 32 | wea_c.registered_commands[cmdname_target] = wea_c.registered_commands[cmdname_source] 33 | 34 | if minetest.global_exists("worldedit") then 35 | worldedit.registered_commands[cmdname_target] = worldedit.registered_commands[cmdname_source] 36 | end 37 | end 38 | 39 | 40 | return register_alias 41 | -------------------------------------------------------------------------------- /worldeditadditions_core/mod.conf: -------------------------------------------------------------------------------- 1 | name = worldeditadditions_core 2 | description = worldeditadditions: core components 3 | optional_depends = worldedit, worldedit_commands, worldedit_shortcommands 4 | -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_bg.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l0.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l1.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l2.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l3.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l4.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l5.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l6.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l7.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l8.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_l9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_l9.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_marker_wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_marker_wall.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r0.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r1.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r2.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r3.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r4.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r5.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r6.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r7.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r8.png -------------------------------------------------------------------------------- /worldeditadditions_core/textures/worldeditadditions_core_r9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_core/textures/worldeditadditions_core_r9.png -------------------------------------------------------------------------------- /worldeditadditions_core/utils/format/array_2d.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | 4 | --- Prints a 2d array of numbers formatted like a JS TypedArray (e.g. like a manip node list or a convolutional kernel) 5 | -- In other words, the numbers should be formatted as a single flat array. 6 | -- @param tbl number[] The ZERO-indexed list of numbers 7 | -- @param width number The width of 2D array. 8 | local function format_array_2d(tbl, width) 9 | print("==== count: "..(#tbl+1)..", width:"..width.." ====") 10 | local display_width = 1 11 | for _i,value in pairs(tbl) do 12 | display_width = math.max(display_width, #tostring(value)) 13 | end 14 | display_width = display_width + 2 15 | local next = {} 16 | for i=0, #tbl do 17 | table.insert(next, wea_c.str_padstart(tostring(tbl[i]), display_width)) 18 | if #next == width then 19 | print(table.concat(next, "")) 20 | next = {} 21 | end 22 | end 23 | end 24 | 25 | return format_array_2d -------------------------------------------------------------------------------- /worldeditadditions_core/utils/format/human_size.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Formats (usually large) numbers as human-readable strings. 3 | -- Ported from PHP: https://github.com/sbrl/Pepperminty-Wiki/blob/0a81c940c5803856db250b29f54658476bc81e21/core/05-functions.php#L67 4 | -- @param n number The number to format. 5 | -- @param decimals number The number of decimal places to show. 6 | -- @return string A formatted string that represents the given input number. 7 | local function format_human_size(n, decimals) 8 | local sizes = { "", "K", "M", "G", "T", "P", "E", "Y", "Z" } 9 | local factor = math.floor((#tostring(n) - 1) / 3) 10 | local multiplier = 10^(decimals or 0) 11 | local result = math.floor(0.5 + (n / (1000 ^ factor)) * multiplier) / multiplier 12 | return result .. sizes[factor+1] 13 | end 14 | 15 | return format_human_size -------------------------------------------------------------------------------- /worldeditadditions_core/utils/format/human_time.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Converts a float milliseconds into a human-readable string. 3 | -- Ported from PHP human_time from Pepperminty Wiki: https://github.com/sbrl/Pepperminty-Wiki/blob/fa81f0d/core/05-functions.php#L82-L104 4 | -- @param ms float The number of milliseconds to convert. 5 | -- @return string A human-readable string representing the input ms. 6 | local function format_human_time(ms) 7 | if type(ms) ~= "number" then return "unknown" end 8 | local tokens = { 9 | { 31536000 * 1000, 'year' }, 10 | { 2592000 * 1000, 'month' }, 11 | { 604800 * 1000, 'week' }, 12 | { 86400 * 1000, 'day' }, 13 | { 3600 * 1000, 'hr' }, 14 | { 60 * 1000, 'min' }, 15 | { 1 * 1000, 's' }, 16 | { 1, 'ms'} 17 | } 18 | 19 | for _,pair in pairs(tokens) do 20 | if ms > pair[1] or pair[2] == "ms" then 21 | local unit = pair[2] 22 | if ms > 60 * 1000 and math.floor(ms / pair[1]) > 1 then 23 | unit = unit.."s" 24 | end 25 | return string.format("%.2f", ms / pair[1])..unit 26 | end 27 | end 28 | end 29 | 30 | 31 | return format_human_time -------------------------------------------------------------------------------- /worldeditadditions_core/utils/format/init.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | 4 | wea_c.format = { 5 | array_2d = dofile(wea_c.modpath.."/utils/format/array_2d.lua"), 6 | human_size = dofile(wea_c.modpath.."/utils/format/human_size.lua"), 7 | human_time = dofile(wea_c.modpath.."/utils/format/human_time.lua"), 8 | node_distribution = dofile(wea_c.modpath.."/utils/format/node_distribution.lua"), 9 | make_ascii_table = dofile(wea_c.modpath.."/utils/format/make_ascii_table.lua"), 10 | map = dofile(wea_c.modpath.."/utils/format/map.lua"), 11 | } 12 | 13 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/format/make_ascii_table.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | --- Makes a human-readable table of data. 4 | -- Data should be a 2D array - i.e. a table of tables. The nested tables should 5 | -- contain a list of items for a single row. 6 | -- If total is specified, then a grand total is printed at the bottom - this is 7 | -- useful when you want to print a node list. 8 | -- @param data table[] A table of tables. Each subtable is a single row of the tabulated output. 9 | -- @returns string The input table of tables formatted into a nice ASCII table. 10 | local function format_make_ascii_table(data) 11 | local extra_padding = 2 12 | local result = {} 13 | local max_lengths = {} 14 | for y = 1, #data, 1 do 15 | for x = 1, #data[y], 1 do 16 | if not max_lengths[x] then 17 | max_lengths[x] = 0 18 | end 19 | max_lengths[x] = math.max(max_lengths[x], #tostring(data[y][x]) + extra_padding) 20 | end 21 | end 22 | 23 | for _key, row in ipairs(data) do 24 | local row_result = {} 25 | for i = 1, #row, 1 do 26 | row_result[#row_result + 1] = wea_c.str_padend(tostring(row[i]), max_lengths[i], " ") 27 | end 28 | result[#result+1] = table.concat(row_result, "") 29 | end 30 | 31 | -- TODO: Add multi-column support here 32 | return table.concat(result, "\n") 33 | end 34 | 35 | return format_make_ascii_table -------------------------------------------------------------------------------- /worldeditadditions_core/utils/format/map.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Formats a key-value table of values as a string. 3 | -- @param map table The table of key-value pairs to format. 4 | -- @returns string The given table of key-value pairs formatted as a string. 5 | local function format_map(map) 6 | local result = {} 7 | for key, value in pairs(map) do 8 | table.insert(result, key.."\t"..tostring(value)) 9 | end 10 | return table.concat(result, "\n") 11 | end 12 | 13 | return format_map -------------------------------------------------------------------------------- /worldeditadditions_core/utils/format/node_distribution.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | --- Turns an associative node_id → count table into a human-readable list. 4 | -- @param distribution table A node distribution in the format { node_name = count, .... }. 5 | -- @param nodes_total number The total number of nodes in the distribution. 6 | -- @param add_total bool Whether to add a grand total to the bottom or not. Default: no 7 | local function format_node_distribution(distribution, nodes_total, add_total) 8 | local distribution_data = {} 9 | for node_id, count in pairs(distribution) do 10 | table.insert(distribution_data, { 11 | count, 12 | tostring(wea_c.round((count / nodes_total) * 100, 2)).."%", 13 | minetest.get_name_from_content_id(node_id) 14 | }) 15 | end 16 | local result = wea_c.format.make_ascii_table(distribution_data) 17 | 18 | if add_total == true then 19 | result = result.."\n"..string.rep("=", 6 + #tostring(nodes_total) + 6).."\n".. 20 | "Total "..nodes_total.." nodes\n" 21 | end 22 | 23 | return result 24 | end 25 | 26 | return format_node_distribution -------------------------------------------------------------------------------- /worldeditadditions_core/utils/inspect.lua: -------------------------------------------------------------------------------- 1 | --- Serialises an arbitrary value to a string. 2 | -- Note that although the resulting table *looks* like valid Lua, it isn't. 3 | -- Completely arbitrarily, if a table (or it's associated metatable) has the 4 | -- key __name then it is conidered the name of the parent metatable. This can 5 | -- be useful for identifying custom table-based types. 6 | -- Should anyone come across a 'proper' way to obtain the name of a metatable 7 | -- in pure vanilla Lua, I will update this to follow that standard instead. 8 | -- @param item any Input item to serialise. 9 | -- @param sep string key value seperator 10 | -- @param new_line string key value pair delimiter 11 | -- @return string concatenated table pairs 12 | local function inspect(item, maxdepth) 13 | if not maxdepth then maxdepth = 3 end 14 | if type(item) ~= "table" then 15 | if type(item) == "string" then return "\""..item.."\"" end 16 | return tostring(item) 17 | end 18 | if maxdepth < 1 then return "[truncated]" end 19 | 20 | local result = { } 21 | -- Consider our (arbitrarily decided) property __name to the type of this item 22 | -- Remember that this implicitly checks the metatable so long as __index is set. 23 | if type(item.__name) == "string" then 24 | table.insert(result, "("..item.__name..") ") 25 | end 26 | table.insert(result, "{\n") 27 | for key,value in pairs(item) do 28 | local value_text = inspect(value, maxdepth - 1) 29 | :gsub("\n", "\n\t") 30 | table.insert(result, "\t"..tostring(key).." = ".."("..type(value)..") "..value_text.."\n") 31 | end 32 | table.insert(result, "}") 33 | return table.concat(result,"") 34 | end 35 | 36 | -- local test = { 37 | -- a = { x = 5, y = 7, z = -6 }, 38 | -- http = { 39 | -- port = 80, 40 | -- protocol = "http" 41 | -- }, 42 | -- mode = "do_stuff", 43 | -- apple = false, 44 | -- deepa = { deepb = { deepc = { yay = "Happy Birthday!" } }} 45 | -- } 46 | -- print(inspect(test)) 47 | 48 | return inspect 49 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/io.lua: -------------------------------------------------------------------------------- 1 | local io = { 2 | -- Ref https://minetest.gitlab.io/minetest/minetest-namespace-reference/#utilities 3 | scandir = function(dirpath) 4 | return minetest.get_dir_list(dirpath, nil) 5 | end, 6 | scandir_files = function(dirpath) 7 | return minetest.get_dir_list(dirpath, false) 8 | end, 9 | scandir_dirs = function(dirpath) 10 | return minetest.get_dir_list(dirpath, true) 11 | end, 12 | } 13 | 14 | return io 15 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/parse/chance.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Parses a chance value, and returns the 1-in-N value thereof. 3 | -- @param str string The string to parse. 4 | -- @param mode string The operation mode. Valid modes: "1-in-n" (default), "weight". "1-in-n" refers to a 1-in-N chance of something happening (lower numbers mean greater likelihood). "weight", on the other hand, is instead a weighting that something will happen (higher numbers mean a greater likelihood). 5 | -- @returns number|nil The 1-in-N chance if parsing was successful, otherwise nil. 6 | local function parse_chance(str, mode) 7 | if not mode then mode = "1-in-n" end 8 | if tonumber(str) ~= nil then return tonumber(str) end 9 | if str:sub(#str) == "%" then 10 | local result = tonumber(str:sub(1, #str-1)) 11 | if not result then return nil end 12 | if mode == "weight" then result = 100 - result end 13 | return 1 / (result / 100) -- Convert percentage to 1-in-N chance 14 | end 15 | return nil 16 | end 17 | 18 | 19 | return parse_chance -------------------------------------------------------------------------------- /worldeditadditions_core/utils/parse/init.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | -- ██████ █████ ██████ ███████ ███████ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ██████ ███████ ██████ ███████ █████ 6 | -- ██ ██ ██ ██ ██ ██ ██ 7 | -- ██ ██ ██ ██ ██ ███████ ███████ 8 | 9 | -- Unified Axes Keyword Parser 10 | local uak_parse = dofile(wea_c.modpath.."/utils/parse/axes_parser.lua") 11 | -- Old axis parsing functions 12 | local axes = dofile(wea_c.modpath.."/utils/parse/axes.lua") 13 | 14 | wea_c.parse = { 15 | direction_keyword = uak_parse.keyword, 16 | directions = uak_parse.keytable, 17 | -- Old parse functions (marked for deprecation). 18 | -- Use parse.keytable or parse.keyword instead 19 | axes = axes.parse_axes, 20 | axis_name = axes.parse_axis_name, 21 | 22 | seed = dofile(wea_c.modpath.."/utils/parse/seed.lua"), 23 | chance = dofile(wea_c.modpath.."/utils/parse/chance.lua"), 24 | map = dofile(wea_c.modpath.."/utils/parse/map.lua"), 25 | weighted_nodes = dofile(wea_c.modpath.."/utils/parse/weighted_nodes.lua") 26 | } 27 | 28 | dofile(wea_c.modpath.."/utils/parse/tokenise_commands.lua") 29 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/parse/map.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: cast-local-type 2 | local wea_c = worldeditadditions_core 3 | 4 | --- Parses a map of key-value pairs into a table. 5 | -- For example, "count 25000 speed 0.8 rate_erosion 0.006 doawesome true" would be parsed into 6 | -- the following table: { count = 25000, speed = 0.8, rate_erosion = 0.006, doawesome = true }. 7 | -- @param params_text string The string to parse. 8 | -- @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. 9 | -- @returns table A table of key-value pairs parsed out from the given string. 10 | local function parse_map(params_text, keywords) 11 | if not keywords then keywords = {} end 12 | local result = {} 13 | local parts = wea_c.split(params_text, "%s+", false) 14 | 15 | local last_key = nil 16 | local mode = "KEY" 17 | for i, part in ipairs(parts) do 18 | if mode == "VALUE" then 19 | -- Try converting to a number to see if it works 20 | local part_converted = tonumber(part) 21 | if part_converted == nil then part_converted = part end 22 | -- Look for bools 23 | if part_converted == "true" then part_converted = true end 24 | if part_converted == "false" then part_converted = false end 25 | result[last_key] = part_converted 26 | mode = "KEY" 27 | else 28 | last_key = part 29 | -- Keyword support 30 | if wea_c.table.contains(keywords, last_key) then 31 | result[last_key] = true 32 | else 33 | mode = "VALUE" 34 | end 35 | end 36 | end 37 | return true, result 38 | end 39 | 40 | return parse_map -------------------------------------------------------------------------------- /worldeditadditions_core/utils/parse/seed.lua: -------------------------------------------------------------------------------- 1 | --- Makes a seed from a string. 2 | -- If the input is a number, it is returned as-is. 3 | -- If the input is a string and can be converted to a number with tonumber(), 4 | -- the output of tonumber() is returned. 5 | -- Otherwise, the string is converted to a number via a simple hashing algorithm 6 | -- (caution: certainlly NOT crypto-secure!). 7 | -- @param {string} str The string to convert. 8 | -- @source https://stackoverflow.com/a/2624210/1460422 The idea came from here 9 | local function parse_seed(str) 10 | if type(str) == "number" then return str end 11 | if tonumber(str) ~= nil then return tonumber(str) end 12 | local result = 0 13 | for i = 1, #str do 14 | result = (result*91) + (string.byte(str:sub(i, i)) * 31) 15 | end 16 | return result 17 | end 18 | 19 | 20 | return parse_seed -------------------------------------------------------------------------------- /worldeditadditions_core/utils/setting_handler.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Initialize settings container 3 | local wea_c = worldeditadditions_core 4 | wea_c.settings = {} 5 | 6 | -- Initialize wea world folder if not already existing 7 | local path = minetest.get_worldpath() .. "/worldeditadditions" 8 | minetest.mkdir(path) 9 | 10 | --- A wrapper to simultaniously handle global and world settings. 11 | -- @namespace worldeditadditions_core.setting_handler 12 | local setting_handler = {} 13 | 14 | --- Reads world settings into WEA core settings object 15 | setting_handler.read = function() 16 | local file, err = io.open(path .. "/settings.conf", "rb") 17 | if err then return false end 18 | -- Split by newline 19 | -- local settings = wea_c.split(file:read("*a"),"[\n\r]+") 20 | file:close() 21 | end 22 | 23 | --- Write setting to world settings 24 | setting_handler.write = function(setting, state) 25 | local writer, err = io.open(path .. "/settings.conf", "ab") 26 | if not writer then 27 | return false 28 | elseif setting == "" and not state then 29 | writer:write("") 30 | else 31 | writer:write("worldeditadditions_" .. setting .. " = " .. state .. "\n") 32 | end 33 | writer:flush() 34 | writer:close() 35 | return true 36 | end 37 | 38 | -- Test for world settings and generate file if none 39 | if not setting_handler.read() then 40 | setting_handler.write("") 41 | end 42 | 43 | return setting_handler -------------------------------------------------------------------------------- /worldeditadditions_core/utils/strings/init.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | local polyfills = dofile(wea_c.modpath.."/utils/strings/polyfill.lua") 4 | for key, value in pairs(polyfills) do 5 | wea_c[key] = value 6 | end 7 | 8 | dofile(wea_c.modpath.."/utils/strings/tochars.lua") 9 | wea_c.split = dofile(wea_c.modpath.."/utils/strings/split.lua") 10 | wea_c.split_shell = dofile(wea_c.modpath.."/utils/strings/split_shell.lua") 11 | wea_c.to_boolean = dofile(wea_c.modpath.."/utils/strings/to_boolean.lua") 12 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/strings/polyfill.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Functions that operate on strings that *really* should be present in the Lua 3 | standard library, but somehow aren't. 4 | A good rule of thumb is to ask "does Javascript have a built-in function for 5 | this?". If yes, then your implementation probably belongs here. 6 | ]]-- 7 | 8 | --- Pads str to length len with char from right 9 | -- @source https://snipplr.com/view/13092/strlpad--pad-string-to-the-left 10 | local function str_padend(str, len, char) 11 | if char == nil then char = ' ' end 12 | return str .. string.rep(char, len - #str) 13 | end 14 | --- Pads str to length len with char from left 15 | -- Adapted from the above 16 | local function str_padstart(str, len, char) 17 | if char == nil then char = ' ' end 18 | return string.rep(char, len - #str) .. str 19 | end 20 | 21 | --- Equivalent to string.startsWith in JS 22 | -- @param str string The string to operate on 23 | -- @param start number The start string to look for 24 | -- @returns bool Whether start is present at the beginning of str 25 | local function str_starts(str, start) 26 | return string.sub(str, 1, string.len(start)) == start 27 | end 28 | 29 | --- Equivalent to string.endsWith in JS 30 | -- @param str string The string to operate on 31 | -- @param substr_end number The ending string to look for 32 | -- @returns bool Whether substr_end is present at the end of str 33 | local function str_ends(str, substr_end) 34 | return string.sub(str, -string.len(substr_end)) == substr_end 35 | end 36 | 37 | --- Trims whitespace from a string from the beginning and the end. 38 | -- From http://lua-users.org/wiki/StringTrim 39 | -- @param str string The string to trim the whitespace from. 40 | -- @returns string A copy of the original string with the whitespace trimmed. 41 | local function trim(str) 42 | return (str:gsub("^%s*(.-)%s*$", "%1")) 43 | end 44 | 45 | 46 | return { 47 | str_padend = str_padend, 48 | str_padstart = str_padstart, 49 | str_starts = str_starts, 50 | str_ends = str_ends, 51 | trim = trim 52 | } 53 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/strings/to_boolean.lua: -------------------------------------------------------------------------------- 1 | --- Converts input to a value of type Boolean. 2 | -- @param arg any Input to convert 3 | -- @returns boolean 4 | local function to_boolean(arg) 5 | local typ = type(arg) 6 | if typ == "boolean" then return arg 7 | elseif typ == "number" and arg > 0 then return true 8 | elseif arg == "false" or arg == "no" then return false 9 | elseif typ ~= "nil" then return true 10 | else return false end 11 | end 12 | return to_boolean 13 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/strings/tochars.lua: -------------------------------------------------------------------------------- 1 | --- Split into table of characters. 2 | -- @param text string The string to iterate over 3 | -- @param sort bool Sort characters 4 | -- @param rem_dups bool Remove duplicate characters 5 | -- @returns table A sequence table containing the substrings 6 | function worldeditadditions_core.tochars(text,sort,rem_dups) 7 | local t, set = {}, {} 8 | if rem_dups then 9 | text:gsub(".",function(c) set[c] = true end) 10 | for k,v in pairs(set) do table.insert(t,k) end 11 | else 12 | text:gsub(".",function(c) table.insert(t,c) end) 13 | end 14 | 15 | if sort then table.sort(t) end 16 | 17 | return t 18 | end 19 | 20 | --- Split into a set of characters. 21 | -- @param text string The string to iterate over 22 | -- @returns table A sequence set table containing the substrings 23 | function worldeditadditions_core.tocharset(text) 24 | local t = {} 25 | text:gsub(".",function(c) t[c] = true end) 26 | return t 27 | end 28 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/deepcopy.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | -- 4. Supporting recursive structures. 4 | -- 5 | -- The issue here is that the following code will call itself 6 | -- indefinitely and ultimately cause a stack overflow: 7 | -- 8 | -- local my_t = {} 9 | -- my_t.a = my_t 10 | -- local t_copy = copy2(my_t) 11 | -- 12 | -- This happens to both copy1 and copy2, which each try to make 13 | -- a copy of my_t.a, which involves making a copy of my_t.a.a, 14 | -- which involves making a copy of my_t.a.a.a, etc. The 15 | -- recursive table my_t is perfectly legal, and it's possible to 16 | -- make a deep_copy function that can handle this by tracking 17 | -- which tables it has already started to copy. 18 | -- 19 | -- Thanks to @mnemnion for pointing out that we should not call 20 | -- setmetatable() until we're doing copying values; otherwise we 21 | -- may accidentally trigger a custom __index() or __newindex()! 22 | 23 | --- Deep clones a given table. 24 | -- @source https://gist.github.com/tylerneylon/81333721109155b2d244 25 | -- @param obj table The table to clone. 26 | -- @returns table A deep copy of the given table. 27 | local function deepcopy(obj, seen) 28 | -- Handle non-tables and previously-seen tables. 29 | if type(obj) ~= 'table' then return obj end 30 | if seen and seen[obj] then return seen[obj] end 31 | 32 | -- New table; mark it as seen and copy recursively. 33 | local s = seen or {} 34 | local res = {} 35 | s[obj] = res 36 | for k, v in pairs(obj) do res[deepcopy(k, s)] = deepcopy(v, s) end 37 | return setmetatable(res, getmetatable(obj)) 38 | end 39 | 40 | return deepcopy 41 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/init.lua: -------------------------------------------------------------------------------- 1 | -- ████████ █████ ██████ ██ ███████ ███████ 2 | -- ██ ██ ██ ██ ██ ██ ██ ██ 3 | -- ██ ███████ ██████ ██ █████ ███████ 4 | -- ██ ██ ██ ██ ██ ██ ██ ██ 5 | -- ██ ██ ██ ██████ ███████ ███████ ███████ 6 | 7 | -- Functions that operate on tables. 8 | -- Lua doesn't exactly come with batteries included, so this is quite an 9 | -- extensive collection of functions :P 10 | 11 | local wea_c = worldeditadditions_core 12 | 13 | wea_c.table = { 14 | apply = dofile(wea_c.modpath.."/utils/table/table_apply.lua"), 15 | contains = dofile(wea_c.modpath.."/utils/table/table_contains.lua"), 16 | deepcopy = dofile(wea_c.modpath.."/utils/table/deepcopy.lua"), 17 | filter = dofile(wea_c.modpath.."/utils/table/table_filter.lua"), 18 | get_last = dofile(wea_c.modpath.."/utils/table/table_get_last.lua"), 19 | makeset = dofile(wea_c.modpath.."/utils/table/makeset.lua"), 20 | map = dofile(wea_c.modpath.."/utils/table/table_map.lua"), 21 | reduce = dofile(wea_c.modpath.."/utils/table/table_reduce.lua"), 22 | shallowcopy = dofile(wea_c.modpath.."/utils/table/shallowcopy.lua"), 23 | tostring = dofile(wea_c.modpath.."/utils/table/table_tostring.lua"), 24 | unique = dofile(wea_c.modpath.."/utils/table/table_unique.lua"), 25 | unpack = dofile(wea_c.modpath.."/utils/table/table_unpack.lua"), 26 | } 27 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/makeset.lua: -------------------------------------------------------------------------------- 1 | --- Creates a table that stores data in keys. 2 | -- @source https://riptutorial.com/lua/example/13407/search-for-an-item-in-a-list 3 | -- @param list table The table of values to convert to keys. 4 | -- @return table The table of (key => true) pairs. 5 | local function makeset(list) 6 | local set = {} 7 | for _, l in ipairs(list) do 8 | set[l] = true 9 | end 10 | return set 11 | end 12 | 13 | return makeset 14 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/shallowcopy.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Shallow clones a table. 3 | -- @source http://lua-users.org/wiki/CopyTable 4 | -- @param orig table The table to clone. 5 | -- @return table The cloned table. 6 | local function shallowcopy(orig) 7 | local orig_type = type(orig) 8 | local copy 9 | if orig_type == 'table' then 10 | copy = {} 11 | for orig_key, orig_value in pairs(orig) do 12 | copy[orig_key] = orig_value 13 | end 14 | else -- number, string, boolean, etc 15 | copy = orig 16 | end 17 | return copy 18 | end 19 | 20 | return shallowcopy 21 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/table_apply.lua: -------------------------------------------------------------------------------- 1 | --- SHALLOW ONLY - applies the values in source to overwrite the equivalent keys in target. 2 | -- Warning: This function mutates target! 3 | -- @param source table The source to take values from 4 | -- @param target table The target to write values to 5 | local function table_apply(source, target) 6 | for key, value in pairs(source) do 7 | target[key] = value 8 | end 9 | end 10 | 11 | return table_apply 12 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/table_contains.lua: -------------------------------------------------------------------------------- 1 | 2 | --- Looks to see whether a given table contains a given value. 3 | -- @param tbl table The table to look in. 4 | -- @param target any The target to look for. 5 | -- @returns bool Whether the table contains the given target or not. 6 | local function table_contains(tbl, target) 7 | for key, value in ipairs(tbl) do 8 | if value == target then return true end 9 | end 10 | return false 11 | end 12 | 13 | return table_contains 14 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/table_filter.lua: -------------------------------------------------------------------------------- 1 | --- Filters the items in the given table using the given function. 2 | -- The function is executed for each item in the list. If it returns true, the 3 | -- item is kept. If it returns false, the item is discarded. 4 | -- Arguments passed to the function: item, i 5 | -- ...where item is the item to filter, and i is the index in the table the item 6 | -- is located at. 7 | -- @param tbl table The table of values to filter. 8 | -- @param func function:bool The filter function to execute - should return a boolean value indicating whether the item provided as the first argument should be kept 9 | -- @returns table A new table containing the values that the given function returned true for. 10 | local function table_filter(tbl, func) 11 | local result = {} 12 | for i,value in ipairs(tbl) do 13 | if func(value, i) then 14 | table.insert(result, value) 15 | end 16 | end 17 | return result 18 | end 19 | 20 | return table_filter 21 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/table_get_last.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | local table_unpack = dofile(wea_c.modpath.."/utils/table/table_unpack.lua") 4 | 5 | 6 | --- Returns only the last count items in a given numerical table-based list. 7 | -- @param tbl table The table to fetch items from. 8 | -- @param count number The number of items to fetch from the end of the table. 9 | -- @returns table A table containing the last count items from the given table. 10 | local function table_get_last(tbl, count) 11 | return {table_unpack( 12 | tbl, 13 | math.max(0, (#tbl) - (count - 1)) 14 | )} 15 | end 16 | 17 | return table_get_last 18 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/table_map.lua: -------------------------------------------------------------------------------- 1 | --- Executes the given function on every item in the given table. 2 | -- Ignores return values that are nil and doesn't insert them into the table. 3 | -- @param tbl table The table to operate on. 4 | -- @param func function:any|nil The function to execute on every item in the table. 5 | -- @returns table A new table containing the return values of the function. 6 | local function table_map(tbl, func) 7 | local result = {} 8 | for i,value in ipairs(tbl) do 9 | local newval = func(value, i) 10 | if newval ~= nil then table.insert(result, newval) end 11 | end 12 | return result 13 | end 14 | 15 | return table_map 16 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/table_reduce.lua: -------------------------------------------------------------------------------- 1 | 2 | 3 | --- Lua implementation of array.reduce() from Javascript. 4 | -- @param tbl The table to iterate over. 5 | -- @param func The function to call for every element in tbl. Will be passed the following arguments: accumulator, value, index, table. Of course, the provided function need not take this many arguments. 6 | -- @param initial_value The initial value of the accumulator. 7 | local function table_reduce(tbl, func, initial_value) 8 | local acc = initial_value 9 | for key, value in pairs(tbl) do 10 | acc = func(acc, value, key, tbl) 11 | end 12 | return acc 13 | end 14 | 15 | return table_reduce 16 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/table_tostring.lua: -------------------------------------------------------------------------------- 1 | --- Returns the key value pairs in a table as a single string 2 | -- @param tbl table input table 3 | -- @param sep string key value seperator 4 | -- @param new_line string key value pair delimiter 5 | -- @param max_depth number max recursion depth (optional) 6 | -- @return string concatenated table pairs 7 | local function table_tostring(tbl, sep, new_line, max_depth) 8 | if type(sep) ~= "string" then sep = ": " end 9 | if type(new_line) ~= "string" then new_line = ", " end 10 | if type(max_depth) == "number" then max_depth = {depth=0,max=max_depth} 11 | elseif type(max_depth) ~= "table" then max_depth = {depth=0,max=5} end 12 | local ret = {} 13 | if type(tbl) ~= "table" then return "Error: input not table!" end 14 | for key,value in pairs(tbl) do 15 | if type(value) == "table" and max_depth.depth < max_depth.max then 16 | table.insert(ret,tostring(key) .. sep .. 17 | "{" .. table_tostring(value,sep,new_line,{max_depth.depth+1,max_depth.max}) .. "}") 18 | else 19 | table.insert(ret,tostring(key) .. sep .. tostring(value)) 20 | end 21 | end 22 | return table.concat(ret,new_line) 23 | end 24 | 25 | -- Test: 26 | -- /lua v1 = { x= 0.335, facing= { axis= "z", sign= -1 } }; print(worldeditadditions.table.tostring(v1)) 27 | 28 | return table_tostring 29 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/table_unique.lua: -------------------------------------------------------------------------------- 1 | --- Builds a new table with the elements of the given table appearing at most once. 2 | -- @param tbl table The table of values to make unique. 3 | -- @returns table A new table containing the values of the given table appearing at most once. 4 | local function table_unique(tbl) 5 | local newtbl = {} 6 | for i,value in ipairs(tbl) do 7 | local seen = false 8 | for j,seenvalue in ipairs(newtbl) do 9 | if value == seenvalue then 10 | seen = true 11 | break 12 | end 13 | end 14 | if not seen then 15 | table.insert(newtbl, value) 16 | end 17 | end 18 | return newtbl 19 | end 20 | 21 | return table_unique 22 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/table/table_unpack.lua: -------------------------------------------------------------------------------- 1 | --- Polyfill for unpack / table.unpack. 2 | -- Calls unpack when available, and looks for table.unpack if unpack() isn't 3 | -- found. 4 | -- This is needed because in Lua 5.1 it's the global unpack(), but in Lua 5.4 5 | -- it's moved to table.unpack(). 6 | local function table_unpack(tbl, offset, count) 7 | ---@diagnostic disable-next-line: deprecated 8 | if type(unpack) == "function" then 9 | ---@diagnostic disable-next-line: deprecated 10 | return unpack(tbl, offset, count) 11 | else 12 | return table.unpack(tbl, offset, count) 13 | end 14 | end 15 | 16 | return table_unpack 17 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/terrain/calculate_slopes.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | 5 | --- Converts a 2d heightmap into slope values in radians. 6 | -- Convert a radians to degrees by doing (radians*math.pi) / 180 for display, 7 | -- but it is STRONGLY recommended to keep all internal calculations in radians. 8 | -- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.terrain.make_heightmap(). 9 | -- @param heightmap_size int[] The size of the heightmap in the form [ z, x ] 10 | -- @return Vector[] The calculated slope map, in the same form as the input heightmap. Each element of the array is a (floating-point) number representing the slope in that cell in radians. 11 | local function calculate_slopes(heightmap, heightmap_size) 12 | local normals = wea_c.terrain.calculate_normals(heightmap, heightmap_size) 13 | local slopes = { } 14 | 15 | local up = Vector3.new(0, 1, 0) -- Z & Y are flipped 16 | 17 | for z = heightmap_size.z-1, 0, -1 do 18 | for x = heightmap_size.x-1, 0, -1 do 19 | local hi = z*heightmap_size.x + x 20 | 21 | -- Ref https://stackoverflow.com/a/16669463/1460422 22 | -- slopes[hi] = wea.Vector3.dot_product(normals[hi], up) 23 | slopes[hi] = math.acos(normals[hi].y) 24 | end 25 | end 26 | 27 | return slopes 28 | end 29 | 30 | return calculate_slopes 31 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/terrain/init.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | 3 | local terrain = { 4 | make_heightmap = dofile(wea_c.modpath.."/utils/terrain/make_heightmap.lua"), 5 | calculate_normals = dofile(wea_c.modpath.."/utils/terrain/calculate_normals.lua"), 6 | calculate_slopes = dofile(wea_c.modpath.."/utils/terrain/calculate_slopes.lua"), 7 | apply_heightmap_changes = dofile(wea_c.modpath.."/utils/terrain/apply_heightmap_changes.lua") 8 | } 9 | 10 | return terrain 11 | -------------------------------------------------------------------------------- /worldeditadditions_core/utils/terrain/make_heightmap.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | local Vector3 = wea_c.Vector3 3 | 4 | 5 | --- Given a manip object and associates, generates a 2D x/z heightmap. 6 | -- Note that pos1 and pos2 should have already been pushed through 7 | -- worldedit.sort_pos(pos1, pos2) before passing them to this function. 8 | -- @param pos1 Vector Position 1 of the region to operate on 9 | -- @param pos2 Vector Position 2 of the region to operate on 10 | -- @param manip VoxelManip The VoxelManip object. 11 | -- @param area area The associated area object. 12 | -- @param data table The associated data object. 13 | -- @return table,table The ZERO-indexed heightmap data (as 1 single flat array), followed by the size of the heightmap in the form { z = size_z, x = size_x }. 14 | local function make_heightmap(pos1, pos2, manip, area, data) 15 | -- z y x (in reverse for little-endian machines) is the preferred loop order, but that isn't really possible here 16 | 17 | local heightmap = {} 18 | local hi = 0 19 | local changes = { updated = 0, skipped_columns = 0 } 20 | for z = pos1.z, pos2.z, 1 do 21 | for x = pos1.x, pos2.x, 1 do 22 | local found_node = false 23 | -- Scan each column top to bottom 24 | for y = pos2.y+1, pos1.y, -1 do 25 | local i = area:index(x, y, z) 26 | if not (wea_c.is_airlike(data[i]) and not wea_c.is_liquidlike(data[i])) then 27 | -- It's the first non-airlike node in this column 28 | -- Start heightmap values from 1 (i.e. there's at least 1 node in the column) 29 | heightmap[hi] = (y - pos1.y) + 1 30 | found_node = true 31 | break 32 | end 33 | end 34 | 35 | if not found_node then heightmap[hi] = -1 end 36 | hi = hi + 1 37 | end 38 | end 39 | 40 | local heightmap_size = Vector3.new( 41 | (pos2.x - pos1.x) + 1, -- x 42 | 0, -- y 43 | (pos2.z - pos1.z) + 1 -- z 44 | ) 45 | 46 | return heightmap, heightmap_size 47 | end 48 | 49 | 50 | return make_heightmap 51 | -------------------------------------------------------------------------------- /worldeditadditions_farwand/edit/brush.piskel: -------------------------------------------------------------------------------- 1 | {"modelVersion":2,"piskel":{"name":"New Piskel","description":"","fps":0,"height":16,"width":16,"layers":["{\"name\":\"bg\",\"opacity\":1,\"frameCount\":2,\"chunks\":[{\"layout\":[[0],[1]],\"base64PNG\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAAArklEQVRIS2NkGGDAOMD2M4w6YDQERkNg+ITAz9df/4PKFHZRbpI8RZJifIUWyAHTWo8xFE5wJclMkhTjc0BVyIb/AqJcDGXT3cBm2m9z+i/EK8Sw3nYNXjuo4gCQ5SBLQQ4AgbVmDWCaQ5wDTB/02ofTHqo4INtz7n9+bmF4AIEcgu4IXA6h2AGx9h3/+bhEwZbDHIEcFebzrcChAwInE49h2EexA0ipTWGOQXYIAGP4KxGpw5fjAAAAAElFTkSuQmCC\"}]}","{\"name\":\"main\",\"opacity\":1,\"frameCount\":2,\"chunks\":[{\"layout\":[[0],[1]],\"base64PNG\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAABJ0lEQVRIS2PcWav7n4GBgcG9+TIjiCYVUKqfcUGOOtgBkoJsZDmCUv1gX7dHK/4HOYBch1CiHx7sIENgDiAnNMjVD3dAnpc0igNIdQS5+lESHrIhsNBImHKT6MRJjn4Mw2GGwBxgpMRDUuIkVT9W34VaioITJTImJZuSoh9n8JJiCLayg1j9eOOXWENwFV7E6CeYwDwMhf6rSXLCo4OURAlyGCH9BB0AMiTBUeI/HyczQ0q6EQOPmAHYw0qWzUTpJaSfaEPOLDP9z8YtAbYc5AhSHADSg0s/0Q7YOlnvv7gwO9gBIIeQ6ghk/dk/WRk4xDkYDnrtYyTaASCLQYaAaJBDQI7Q899Msv6ie3/AloPwycRjpDkAltphviHHESAz1Au1/sMcAQADxIMgEWtmUwAAAABJRU5ErkJggg==\"}]}","{\"name\":\"overlay\",\"opacity\":1,\"frameCount\":2,\"chunks\":[{\"layout\":[[0],[1]],\"base64PNG\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAQCAYAAAB3AH1ZAAAAIklEQVRIS2NkGGDAOMD2M4w6YDQERkNgNARGQ2A0BEZDAABMmAARhe0y0gAAAABJRU5ErkJggg==\"}]}"]}} -------------------------------------------------------------------------------- /worldeditadditions_farwand/edit/worldedit_wand.piskel: -------------------------------------------------------------------------------- 1 | {"modelVersion":2,"piskel":{"name":"worldedit_wand","description":"","fps":0,"height":16,"width":16,"layers":["{\"name\":\"Layer 1\",\"opacity\":1,\"frameCount\":5,\"chunks\":[{\"layout\":[[0],[1],[2],[3],[4]],\"base64PNG\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAQCAYAAACBSfjBAAADB0lEQVRYR+2WXUhTYRjH/wd1btacgZmaeZFpRgrZF4GQIEQE9gERCEKF9HWpN1oXIlIkGRgUCHVVlGBlUErRRdEHDEqXXZiRLUdipubMsx2348623niPvGsz3c57CmTgA7s4O/yf53l/z8d5BQD49SybCA4JQY+ExFoI9D+tlnk6maTkGJC28hXEmTI4GiQuPYtDCCFjEzPweAPIX7+K24flXirxvwnA2+rl1oaftbLqGKHPHe23NPkRSJeZBF1+JMhJKkAoQGK9dogVt4eIKM2oOXh9QfTVbtUUODzp7+MSSU01we2W8VOUUVSYodmHs7yciP39KDkngwL8tssGZdCF7Oulmn2wXJoaW1R4zBqb6mL6EMgNc0ikF6B9ahv8UhAJSc9xtHs3GqyzMQOHJ2p3TJMVKYm6uu9HQQH56HSi7LIJjuFRmBPeY9bmRG73Hq4cGLz6szURw2c0GSL80A7dtGEzNhbl4VDFfgiBK/hDXQGau4xcAEo6MohP8iPoIVCcd1H94iCXno7uwOAkkg1JGB1zQhSn0X9RexFOPRomtlEZPb5CvHYA1j6+/CmtWVkJMbjZficEMM1iQeWRw1ELob4MXJqDSOFtzzLANqZwQaDa86VG1Qev/qXVTtZmpcOn+PFl6KsKcKRNexHoCvk8MoEDV9tgyevkjt9x/4Gat+hygQKjNvhhKKILo43yX3QpCF4I4dF49Q8f9xAK7XjVXkFvEVpza8g7zye0Tz1VffDmP3/30fOsWZceOtaZE9WLduGCL/Qk8S8Q53/xlyI+7UTahcy2FBerXUl/0cZ4UbJLcYilLgKFyMb4rbVPTWfnwAUkZ7lRfm3hm0nUBUkhZlpMGHfJ3DuR7cV41LORzu5tViHmpAQXhRjzU88g2idltPTyXU/CIcajvm6HkeSvNkWFGBMgVTNHeiHEsz4WRE0AlyEaidkA0HVEx5navk63yk4zwGWIcxDZXVcXwGWIkZ1IIXJ1ILtmxPNO+59NcPLJtD6A4UlQJ/MvwlqeWRHiVc/O+Bv+1GgX7gdMgAAAAABJRU5ErkJggg==\"}]}"]}} -------------------------------------------------------------------------------- /worldeditadditions_farwand/init.lua: -------------------------------------------------------------------------------- 1 | worldeditadditions.farwand = { 2 | player_data = {} 3 | } 4 | 5 | local modpath = minetest.get_modpath("worldeditadditions_farwand") 6 | 7 | dofile(modpath.."/lib/do_raycast.lua") 8 | dofile(modpath.."/lib/farwand.lua") 9 | dofile(modpath.."/lib/cloudwand.lua") 10 | dofile(modpath.."/lib/multiwand.lua") 11 | dofile(modpath.."/lib/chatcommand.lua") 12 | dofile(modpath.."/lib/settings.lua") 13 | -------------------------------------------------------------------------------- /worldeditadditions_farwand/lib/cloudwand.lua: -------------------------------------------------------------------------------- 1 | local wea = worldeditadditions 2 | local wea_c = worldeditadditions_core 3 | 4 | minetest.register_tool(":worldeditadditions:cloudwand", { 5 | description = "WorldEditAdditions far-reaching additive selector wand", 6 | inventory_image = "worldeditadditions_cloudwand.png", 7 | 8 | on_place = function(itemstack, player, pointed_thing) 9 | local name = player:get_player_name() 10 | -- print("[farwand] on_place", name) 11 | -- Right click when pointing at something 12 | -- Pointed thing: https://rubenwardy.com/minetest_modding_book/lua_api.html#pointed_thing 13 | wea.selection.clear_points(name) 14 | end, 15 | 16 | on_use = function(itemstack, player, pointed_thing) 17 | local name = player:get_player_name() 18 | -- print("[farwand] on_use", name) 19 | local looking_pos, node_id = worldeditadditions.farwand.do_raycast(player) 20 | wea.selection.add_point(name, looking_pos) 21 | -- Left click when pointing at something or nothing 22 | end, 23 | 24 | on_secondary_use = function(itemstack, player, pointed_thing) 25 | local name = player:get_player_name() 26 | -- Right click when pointing at nothing 27 | -- print("[farwand] on_secondary_use", name) 28 | 29 | -- TODO: Move over to wea_c.pos completely 30 | wea.selection.clear_points(name) 31 | wea_c.pos.clear(name) 32 | end 33 | }) 34 | -------------------------------------------------------------------------------- /worldeditadditions_farwand/lib/do_raycast.lua: -------------------------------------------------------------------------------- 1 | local wea_c = worldeditadditions_core 2 | --- worldeditadditions.raycast() wrapper 3 | function worldeditadditions.farwand.do_raycast(player) 4 | if player == nil then return nil end 5 | local player_name = player:get_player_name() 6 | 7 | if worldeditadditions.farwand.player_data[player_name] == nil then 8 | worldeditadditions.farwand.player_data[player_name] = { maxdist = 1000, skip_liquid = true } 9 | end 10 | 11 | local looking_pos, node_id = wea_c.raycast( 12 | player, 13 | worldeditadditions.farwand.player_data[player_name].maxdist, 14 | worldeditadditions.farwand.player_data[player_name].skip_liquid 15 | ) 16 | return looking_pos, node_id 17 | end 18 | -------------------------------------------------------------------------------- /worldeditadditions_farwand/lib/settings.lua: -------------------------------------------------------------------------------- 1 | --- If the settings object for the given player name doesn't exist, it is created. 2 | -- @param name The name of the player to ensure has a settings object. 3 | local function settings_init(name) 4 | if worldeditadditions.farwand.player_data[name] == nil then 5 | minetest.log("INFO", "Initialising settings for "..name) 6 | worldeditadditions.farwand.player_data[name] = { 7 | maxdist = 1000, 8 | skip_liquid = true 9 | } 10 | end 11 | end 12 | 13 | --- Gets a given farwand setting for the given player name. 14 | -- @param string name The name of the player to get the setting for. 15 | -- @param string setting_name The name of the setting to fetch. 16 | -- @return any The value of the setting. 17 | function worldeditadditions.farwand.setting_get(name, setting_name) 18 | if setting_name == nil then return nil end 19 | settings_init(name) 20 | return worldeditadditions.farwand.player_data[name][setting_name] 21 | end 22 | 23 | --- Sets a given farwand setting for the given player name to the given value. 24 | -- @param string name The name of the player to set the setting for. 25 | -- @param string setting_name The name of the setting to set. 26 | -- @param any setting_value The value to set the setting to. 27 | -- @return bool Whether setting the setting was successful or not. 28 | function worldeditadditions.farwand.setting_set(name, setting_name, setting_value) 29 | if setting_name == nil then return false end 30 | settings_init(name) 31 | worldeditadditions.farwand.player_data[name][setting_name] = setting_value 32 | return true 33 | end 34 | -------------------------------------------------------------------------------- /worldeditadditions_farwand/mod.conf: -------------------------------------------------------------------------------- 1 | name = worldeditadditions_farwand 2 | description = worldeditadditions: convenient tool items 3 | depends = worldedit, worldeditadditions 4 | -------------------------------------------------------------------------------- /worldeditadditions_farwand/textures/worldeditadditions_chisel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_farwand/textures/worldeditadditions_chisel.png -------------------------------------------------------------------------------- /worldeditadditions_farwand/textures/worldeditadditions_cloudwand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_farwand/textures/worldeditadditions_cloudwand.png -------------------------------------------------------------------------------- /worldeditadditions_farwand/textures/worldeditadditions_farwand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_farwand/textures/worldeditadditions_farwand.png -------------------------------------------------------------------------------- /worldeditadditions_farwand/textures/worldeditadditions_multiwand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrl/Minetest-WorldEditAdditions/e89d61034fcbf8f2ce9479efc6101155f13cee18/worldeditadditions_farwand/textures/worldeditadditions_multiwand.png --------------------------------------------------------------------------------