mirror of
https://github.com/sbrl/Minetest-WorldEditAdditions.git
synced 2024-11-22 15:33:00 +00:00
Merge branch 'main' into VorTechnix
This commit is contained in:
commit
cff58792bf
17 changed files with 397 additions and 96 deletions
|
@ -11,21 +11,6 @@
|
||||||
<p>After the contents, there is a <a href="#filter">filter box</a> for filtering the detailed explanations to quickly find the one you're after.</p>
|
<p>After the contents, there is a <a href="#filter">filter box</a> for filtering the detailed explanations to quickly find the one you're after.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel-generic">
|
|
||||||
<h2 id="contents" class="linked-section-heading">
|
|
||||||
<a class="section-link" href="#{{ section.slug }}">🔗 <!-- Link Symbol --></a>
|
|
||||||
<span>Contents</span>
|
|
||||||
</h2>
|
|
||||||
<p>TODO: Group commands here by category (*especially* the meta commands)</p>
|
|
||||||
<ul class="command-list">
|
|
||||||
{% for section in sections_help %}
|
|
||||||
<li><a href="#{{ section.slug }}">
|
|
||||||
<code>{{ section.title }}</code>
|
|
||||||
</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="filter" class="panel-generic">
|
<section id="filter" class="panel-generic">
|
||||||
<div class="form-item bigsearch">
|
<div class="form-item bigsearch">
|
||||||
<label for="input-filter">Filter:</label>
|
<label for="input-filter">Filter:</label>
|
||||||
|
@ -37,6 +22,21 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="panel-generic">
|
||||||
|
<h2 id="contents" class="linked-section-heading">
|
||||||
|
<a class="section-link" href="#{{ section.slug }}">🔗 <!-- Link Symbol --></a>
|
||||||
|
<span>Contents</span>
|
||||||
|
</h2>
|
||||||
|
<p>TODO: Group commands here by category (*especially* the meta commands)</p>
|
||||||
|
<ul class="command-list">
|
||||||
|
{% for section in sections_help %}
|
||||||
|
<li data-filtermode-force="all"><a href="#{{ section.slug }}">
|
||||||
|
<code>{{ section.title }}</code>
|
||||||
|
</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function search_text(query, text) {
|
function search_text(query, text) {
|
||||||
return text.toLocaleLowerCase().includes(query);
|
return text.toLocaleLowerCase().includes(query);
|
||||||
|
@ -45,19 +45,27 @@
|
||||||
function do_filter() {
|
function do_filter() {
|
||||||
let el_search = document.querySelector("#input-filter");
|
let el_search = document.querySelector("#input-filter");
|
||||||
let el_searchall = document.querySelector("#input-searchall");
|
let el_searchall = document.querySelector("#input-searchall");
|
||||||
let els_sections = document.querySelectorAll("section.filterable");
|
/* Filterable items
|
||||||
|
- Sections
|
||||||
|
- Commands in the command list
|
||||||
|
*/
|
||||||
|
let els_filterable = document.querySelectorAll("section.filterable, .command-list > li");
|
||||||
|
|
||||||
let query = el_search.value.toLocaleLowerCase();
|
let query = el_search.value.toLocaleLowerCase();
|
||||||
|
|
||||||
let mode = el_searchall.checked ? "all" : "header";
|
let mode = el_searchall.checked ? "all" : "header";
|
||||||
console.log(`SEARCH | mode`, mode, `query`, query);
|
console.log(`SEARCH | mode`, mode, `query`, query);
|
||||||
|
|
||||||
for(let i = 0; i < els_sections.length; i++) {
|
for(let i = 0; i < els_filterable.length; i++) {
|
||||||
let el_next = els_sections[i];
|
let el_next = els_filterable[i];
|
||||||
|
|
||||||
|
let mode_this = mode;
|
||||||
|
if(typeof el_next.dataset.filtermodeForce == "string")
|
||||||
|
mode_this = el_next.dataset.filtermodeForce;
|
||||||
|
|
||||||
let show = true;
|
let show = true;
|
||||||
if(query.length > 0) {
|
if(query.length > 0) {
|
||||||
switch(mode) {
|
switch(mode_this) {
|
||||||
case "all":
|
case "all":
|
||||||
show = search_text(query,
|
show = search_text(query,
|
||||||
el_next.textContent
|
el_next.textContent
|
||||||
|
|
|
@ -19,6 +19,24 @@ describe("Vector3.max", function()
|
||||||
Vector3.max(a, b)
|
Vector3.max(a, b)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
it("should work with scalar numbers", function()
|
||||||
|
local a = Vector3.new(16, 1, 16)
|
||||||
|
local b = 2
|
||||||
|
|
||||||
|
assert.are.same(
|
||||||
|
Vector3.new(16, 2, 16),
|
||||||
|
Vector3.max(a, b)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
it("should work with scalar numbers both ways around", function()
|
||||||
|
local a = Vector3.new(16, 1, 16)
|
||||||
|
local b = 2
|
||||||
|
|
||||||
|
assert.are.same(
|
||||||
|
Vector3.new(16, 2, 16),
|
||||||
|
Vector3.max(b, a)
|
||||||
|
)
|
||||||
|
end)
|
||||||
it("should work with negative vectors", function()
|
it("should work with negative vectors", function()
|
||||||
local a = Vector3.new(-9, -16, -25)
|
local a = Vector3.new(-9, -16, -25)
|
||||||
local b = Vector3.new(-3, -6, -2)
|
local b = Vector3.new(-3, -6, -2)
|
||||||
|
|
|
@ -19,6 +19,24 @@ describe("Vector3.min", function()
|
||||||
Vector3.min(a, b)
|
Vector3.min(a, b)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
it("should work with scalar numbers", function()
|
||||||
|
local a = Vector3.new(16, 1, 16)
|
||||||
|
local b = 2
|
||||||
|
|
||||||
|
assert.are.same(
|
||||||
|
Vector3.new(2, 1, 2),
|
||||||
|
Vector3.min(a, b)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
it("should work with scalar numbers both ways around", function()
|
||||||
|
local a = Vector3.new(16, 1, 16)
|
||||||
|
local b = 2
|
||||||
|
|
||||||
|
assert.are.same(
|
||||||
|
Vector3.new(2, 1, 2),
|
||||||
|
Vector3.min(b, a)
|
||||||
|
)
|
||||||
|
end)
|
||||||
it("should work with negative vectors", function()
|
it("should work with negative vectors", function()
|
||||||
local a = Vector3.new(-9, -16, -25)
|
local a = Vector3.new(-9, -16, -25)
|
||||||
local b = Vector3.new(-3, -6, -2)
|
local b = Vector3.new(-3, -6, -2)
|
||||||
|
|
|
@ -7,6 +7,12 @@ describe("Vector3.add", function()
|
||||||
Vector3.new(3, 4, 5)
|
Vector3.new(3, 4, 5)
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
it("should default to (0, 0, 0)", function()
|
||||||
|
assert.are.same(
|
||||||
|
{ x = 0, y = 0, z = 0 },
|
||||||
|
Vector3.new()
|
||||||
|
)
|
||||||
|
end)
|
||||||
it("should not throw an error on invalid x", function()
|
it("should not throw an error on invalid x", function()
|
||||||
assert.has_no.errors(function()
|
assert.has_no.errors(function()
|
||||||
Vector3.new("cheese", 4, 5)
|
Vector3.new("cheese", 4, 5)
|
||||||
|
|
|
@ -12,6 +12,9 @@ Note to self: See the bottom of this file for the release template text.
|
||||||
- Add `//sshift` (_selection shift_) - WorldEdit cuboid manipulator replacements implemented by @VorTechnix.
|
- Add `//sshift` (_selection shift_) - WorldEdit cuboid manipulator replacements implemented by @VorTechnix.
|
||||||
- Use [luacheck](https://github.com/mpeterv/luacheck) to find and fix a large number of bugs and other issues
|
- Use [luacheck](https://github.com/mpeterv/luacheck) to find and fix a large number of bugs and other issues
|
||||||
- Multiple commands: Allow using quotes (`"thing"`, `'thing'`) to quote values when splitting
|
- Multiple commands: Allow using quotes (`"thing"`, `'thing'`) to quote values when splitting
|
||||||
|
- `//layers`: Add optional slope constraint (inspired by [WorldPainter](https://worldpainter.net/))
|
||||||
|
- `//bonemeal`: Add optional node list constraint
|
||||||
|
- `//walls`: Add optional thickness argument
|
||||||
|
|
||||||
|
|
||||||
## v1.12: The selection tools update (26th June 2021)
|
## v1.12: The selection tools update (26th June 2021)
|
||||||
|
|
|
@ -40,16 +40,21 @@ Note also that columns without any air nodes in them at all are also skipped, so
|
||||||
//overlay dirt 90% stone 10%
|
//overlay dirt 90% stone 10%
|
||||||
```
|
```
|
||||||
|
|
||||||
## `//layers [<node_name_1> [<layer_count_1>]] [<node_name_2> [<layer_count_2>]] ...`
|
## `//layers [<max_slope|min_slope..max_slope>] [<node_name_1> [<layer_count_1>]] [<node_name_2> [<layer_count_2>]] ...`
|
||||||
Finds the first non-air node in each column and works downwards, replacing non-air nodes with a defined list of nodes in sequence. Like WorldEdit for Minecraft's `//naturalize` command, and also similar to [`we_env`'s `//populate`](https://github.com/sfan5/we_env). Speaking of, this command has `//naturalise` and `//naturalize` as aliases. Defaults to 1 layer of grass followed by 3 layers of dirt.
|
Finds the first non-air node in each column and works downwards, replacing non-air nodes with a defined list of nodes in sequence. Like WorldEdit for Minecraft's `//naturalize` command, and also similar to [`we_env`'s `//populate`](https://github.com/sfan5/we_env). Speaking of, this command has `//naturalise` and `//naturalize` as aliases. Defaults to 1 layer of grass followed by 3 layers of dirt.
|
||||||
|
|
||||||
The list of nodes has a form similar to that of a chance list you might find in `//replacemix`, `//overlay`, or `//mix` - see the examples below. If the numberr of layers isn't specified, `1` is assumed (i.e. a single layer).
|
Since WorldEditAdditions v1.13, a maximum and minimum slope is optionally accepted, and constrains the columns in the defined region that `//layers` will operate on. For example, specifying a value of `20` would mean that only columns with a slop less than or equal to 20° (degrees, not radians) will be operated on. A value of `45..60` would mean that only columns with a slope between 45° and 60° will be operated on.
|
||||||
|
|
||||||
|
The list of nodes has a form similar to that of a chance list you might find in `//replacemix`, `//overlay`, or `//mix` - see the examples below. If the number of layers isn't specified, `1` is assumed (i.e. a single layer).
|
||||||
|
|
||||||
|
|
||||||
```weacmd
|
```weacmd
|
||||||
//layers dirt_with_grass dirt 3
|
//layers dirt_with_grass dirt 3
|
||||||
//layers sand 5 sandstone 4 desert_stone 2
|
//layers sand 5 sandstone 4 desert_stone 2
|
||||||
//layers brick stone 3
|
//layers brick stone 3
|
||||||
//layers cobble 2 dirt
|
//layers cobble 2 dirt
|
||||||
|
//layers 45..60 dirt_with_snow dirt 2
|
||||||
|
//layers 30 snowblock dirt_with_snow dirt 2
|
||||||
```
|
```
|
||||||
|
|
||||||
## `//forest [<density>] <sapling_a> [<chance_a>] <sapling_b> [<chance_b>] [<sapling_N> [<chance_N>]] ...`
|
## `//forest [<density>] <sapling_a> [<chance_a>] <sapling_b> [<chance_b>] [<sapling_N> [<chance_N>]] ...`
|
||||||
|
@ -220,7 +225,7 @@ Additional examples:
|
||||||
//maze3d stone 6 3 3 54321
|
//maze3d stone 6 3 3 54321
|
||||||
```
|
```
|
||||||
|
|
||||||
## `//bonemeal [<strength> [<chance>]]`
|
## `//bonemeal [<strength> [<chance> [<node_name> [<node_name> ...]]]]`
|
||||||
Requires the [`bonemeal`](https://content.minetest.net/packages/TenPlus1/bonemeal/) ([repo](https://notabug.org/TenPlus1/bonemeal/)) mod (otherwise _WorldEditAdditions_ will not register this command and output a message to the server log). Alias: `//flora`.
|
Requires the [`bonemeal`](https://content.minetest.net/packages/TenPlus1/bonemeal/) ([repo](https://notabug.org/TenPlus1/bonemeal/)) mod (otherwise _WorldEditAdditions_ will not register this command and output a message to the server log). Alias: `//flora`.
|
||||||
|
|
||||||
Bonemeals all eligible nodes in the current region. An eligible node is one that has an air node directly above it - note that just because a node is eligible doesn't mean to say that something will actually happen when the `bonemeal` mod bonemeals it.
|
Bonemeals all eligible nodes in the current region. An eligible node is one that has an air node directly above it - note that just because a node is eligible doesn't mean to say that something will actually happen when the `bonemeal` mod bonemeals it.
|
||||||
|
@ -235,6 +240,9 @@ For example, a chance number of 2 would mean a 50% chance that any given eligibl
|
||||||
|
|
||||||
Since WorldEditAdditions v1.12, a percentage chance is also supported. This is denoted by suffixing a number with a percent sign (e.g. `//bonemeal 1 25%`).
|
Since WorldEditAdditions v1.12, a percentage chance is also supported. This is denoted by suffixing a number with a percent sign (e.g. `//bonemeal 1 25%`).
|
||||||
|
|
||||||
|
Since WorldEditAdditions v1.13, a list of node names is also optionally supported. This will constrain bonemeal operations to be performed only on the node names listed.
|
||||||
|
|
||||||
|
|
||||||
```weacmd
|
```weacmd
|
||||||
//bonemeal
|
//bonemeal
|
||||||
//bonemeal 3 25
|
//bonemeal 3 25
|
||||||
|
@ -242,15 +250,19 @@ Since WorldEditAdditions v1.12, a percentage chance is also supported. This is d
|
||||||
//bonemeal 1 10
|
//bonemeal 1 10
|
||||||
//bonemeal 2 15
|
//bonemeal 2 15
|
||||||
//bonemeal 2 10%
|
//bonemeal 2 10%
|
||||||
|
//bonemeal 2 10% dirt
|
||||||
|
//bonemeal 4 50 ethereal:grove_dirt
|
||||||
```
|
```
|
||||||
|
|
||||||
## `//walls <replace_node>`
|
## `//walls <replace_node> [<thickness=1>]`
|
||||||
Creates vertical walls of `<replace_node>` around the inside edges of the defined region.
|
Creates vertical walls of `<replace_node>` around the inside edges of the defined region, optionally specifying the thickness thereof.
|
||||||
|
|
||||||
```weacmd
|
```weacmd
|
||||||
//walls dirt
|
//walls dirt
|
||||||
//walls stone
|
//walls stone
|
||||||
//walls goldblock
|
//walls goldblock
|
||||||
|
//walls sandstone 2
|
||||||
|
//walls glass 4
|
||||||
```
|
```
|
||||||
|
|
||||||
## `//wbox <replace_node>`
|
## `//wbox <replace_node>`
|
||||||
|
|
|
@ -16,6 +16,7 @@ wea.Mesh, wea.Face = dofile(wea.modpath.."/utils/mesh.lua")
|
||||||
|
|
||||||
wea.Queue = dofile(wea.modpath.."/utils/queue.lua")
|
wea.Queue = dofile(wea.modpath.."/utils/queue.lua")
|
||||||
wea.LRU = dofile(wea.modpath.."/utils/lru.lua")
|
wea.LRU = dofile(wea.modpath.."/utils/lru.lua")
|
||||||
|
wea.inspect = dofile(wea.modpath.."/utils/inspect.lua")
|
||||||
|
|
||||||
wea.bit = dofile(wea.modpath.."/utils/bit.lua")
|
wea.bit = dofile(wea.modpath.."/utils/bit.lua")
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
-- strength The strength to apply - see bonemeal:on_use
|
-- strength The strength to apply - see bonemeal:on_use
|
||||||
-- chance Positive integer that represents the chance bonemealing will occur
|
-- chance Positive integer that represents the chance bonemealing will occur
|
||||||
function worldeditadditions.bonemeal(pos1, pos2, strength, chance)
|
function worldeditadditions.bonemeal(pos1, pos2, strength, chance, nodename_list)
|
||||||
|
if not nodename_list then nodename_list = {} end
|
||||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||||
-- pos2 will always have the highest co-ordinates now
|
-- pos2 will always have the highest co-ordinates now
|
||||||
|
|
||||||
|
@ -14,6 +15,12 @@ function worldeditadditions.bonemeal(pos1, pos2, strength, chance)
|
||||||
return false, "Bonemeal mod not loaded"
|
return false, "Bonemeal mod not loaded"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local node_list = worldeditadditions.table.map(nodename_list, function(nodename)
|
||||||
|
return minetest.get_content_id(nodename)
|
||||||
|
end)
|
||||||
|
local node_list_count = #nodename_list
|
||||||
|
|
||||||
|
|
||||||
-- Fetch the nodes in the specified area
|
-- Fetch the nodes in the specified area
|
||||||
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
|
local manip, area = worldedit.manip_helpers.init(pos1, pos2)
|
||||||
local data = manip:get_data()
|
local data = manip:get_data()
|
||||||
|
@ -26,10 +33,17 @@ function worldeditadditions.bonemeal(pos1, pos2, strength, chance)
|
||||||
for z = pos2.z, pos1.z, -1 do
|
for z = pos2.z, pos1.z, -1 do
|
||||||
for x = pos2.x, pos1.x, -1 do
|
for x = pos2.x, pos1.x, -1 do
|
||||||
for y = pos2.y, pos1.y, -1 do
|
for y = pos2.y, pos1.y, -1 do
|
||||||
if not worldeditadditions.is_airlike(data[area:index(x, y, z)]) then
|
local i = area:index(x, y, z)
|
||||||
|
if not worldeditadditions.is_airlike(data[i]) then
|
||||||
|
local should_bonemeal = true
|
||||||
|
if node_list_count > 0 and not worldeditadditions.table.contains(node_list, data[i]) then
|
||||||
|
should_bonemeal = false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- It's not an air node, so let's try to bonemeal it
|
-- It's not an air node, so let's try to bonemeal it
|
||||||
|
|
||||||
if math.random(0, chance - 1) == 0 then
|
if should_bonemeal and math.random(0, chance - 1) == 0 then
|
||||||
bonemeal:on_use(
|
bonemeal:on_use(
|
||||||
{ x = x, y = y, z = z },
|
{ x = x, y = y, z = z },
|
||||||
strength,
|
strength,
|
||||||
|
|
|
@ -1,8 +1,30 @@
|
||||||
--- Overlap command. Places a specified node on top of each column.
|
-- ██ █████ ██ ██ ███████ ██████ ███████
|
||||||
-- @module worldeditadditions.layers
|
-- ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||||
|
-- ██ ███████ ████ █████ ██████ ███████
|
||||||
|
-- ██ ██ ██ ██ ██ ██ ██ ██
|
||||||
|
-- ███████ ██ ██ ██ ███████ ██ ██ ███████
|
||||||
|
|
||||||
function worldeditadditions.layers(pos1, pos2, node_weights)
|
local wea = worldeditadditions
|
||||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
|
||||||
|
local function print_slopes(slopemap, width)
|
||||||
|
local copy = wea.table.shallowcopy(slopemap)
|
||||||
|
for key,value in pairs(copy) do
|
||||||
|
copy[key] = wea.round(math.deg(value), 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
worldeditadditions.format.array_2d(copy, width)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Replaces the non-air nodes in each column with a list of nodes from top to bottom.
|
||||||
|
-- @param pos1 Vector Position 1 of the region to operate on
|
||||||
|
-- @param pos2 Vector Position 2 of the region to operate on
|
||||||
|
-- @param node_weights string[]
|
||||||
|
-- @param min_slope number?
|
||||||
|
-- @param max_slope number?
|
||||||
|
function worldeditadditions.layers(pos1, pos2, node_weights, min_slope, max_slope)
|
||||||
|
pos1, pos2 = wea.Vector3.sort(pos1, pos2)
|
||||||
|
if not min_slope then min_slope = math.rad(0) end
|
||||||
|
if not max_slope then max_slope = math.rad(180) end
|
||||||
-- pos2 will always have the highest co-ordinates now
|
-- pos2 will always have the highest co-ordinates now
|
||||||
|
|
||||||
-- Fetch the nodes in the specified area
|
-- Fetch the nodes in the specified area
|
||||||
|
@ -11,38 +33,58 @@ function worldeditadditions.layers(pos1, pos2, node_weights)
|
||||||
|
|
||||||
local node_id_ignore = minetest.get_content_id("ignore")
|
local node_id_ignore = minetest.get_content_id("ignore")
|
||||||
|
|
||||||
local node_ids, node_ids_count = worldeditadditions.unwind_node_list(node_weights)
|
local node_ids, node_ids_count = wea.unwind_node_list(node_weights)
|
||||||
|
|
||||||
-- minetest.log("action", "pos1: " .. worldeditadditions.vector.tostring(pos1))
|
local heightmap, heightmap_size = wea.make_heightmap(
|
||||||
-- minetest.log("action", "pos2: " .. worldeditadditions.vector.tostring(pos2))
|
pos1, pos2,
|
||||||
|
manip, area, data
|
||||||
|
)
|
||||||
|
local slopemap = wea.calculate_slopes(heightmap, heightmap_size)
|
||||||
|
-- worldeditadditions.format.array_2d(heightmap, heightmap_size.x)
|
||||||
|
-- print_slopes(slopemap, heightmap_size.x)
|
||||||
|
--luacheck:ignore 311
|
||||||
|
heightmap = nil -- Just in case Lua wants to garbage collect it
|
||||||
|
|
||||||
|
|
||||||
|
-- minetest.log("action", "pos1: " .. wea.vector.tostring(pos1))
|
||||||
|
-- minetest.log("action", "pos2: " .. wea.vector.tostring(pos2))
|
||||||
-- for i,v in ipairs(node_ids) do
|
-- for i,v in ipairs(node_ids) do
|
||||||
-- print("[layer] i", i, "node id", v)
|
-- print("[layer] i", i, "node id", v)
|
||||||
-- end
|
-- end
|
||||||
-- z y x is the preferred loop order, but that isn't really possible here
|
-- z y x is the preferred loop order, but that isn't really possible here
|
||||||
|
|
||||||
local changes = { replaced = 0, skipped_columns = 0 }
|
local changes = { replaced = 0, skipped_columns = 0, skipped_columns_slope = 0 }
|
||||||
for z = pos2.z, pos1.z, -1 do
|
for z = pos2.z, pos1.z, -1 do
|
||||||
for x = pos2.x, pos1.x, -1 do
|
for x = pos2.x, pos1.x, -1 do
|
||||||
local next_index = 1 -- We use table.insert() in make_weighted
|
local next_index = 1 -- We use table.insert() in make_weighted
|
||||||
local placed_node = false
|
local placed_node = false
|
||||||
|
|
||||||
for y = pos2.y, pos1.y, -1 do
|
local hi = (z-pos1.z)*heightmap_size.x + (x-pos1.x)
|
||||||
local i = area:index(x, y, z)
|
|
||||||
|
|
||||||
local is_air = worldeditadditions.is_airlike(data[i])
|
-- print("DEBUG hi", hi, "x", x, "z", z, "slope", slopemap[hi], "as deg", math.deg(slopemap[hi]))
|
||||||
local is_ignore = data[i] == node_id_ignore
|
|
||||||
|
|
||||||
if not is_air and not is_ignore then
|
-- Again, Lua 5.1 doesn't have a continue statement :-/
|
||||||
-- It's not an airlike node or something else odd
|
if slopemap[hi] >= min_slope and slopemap[hi] <= max_slope then
|
||||||
data[i] = node_ids[next_index]
|
for y = pos2.y, pos1.y, -1 do
|
||||||
next_index = next_index + 1
|
local i = area:index(x, y, z)
|
||||||
changes.replaced = changes.replaced + 1
|
|
||||||
|
|
||||||
-- If we're done replacing nodes in this column, move to the next one
|
local is_air = wea.is_airlike(data[i])
|
||||||
if next_index > #node_ids then
|
local is_ignore = data[i] == node_id_ignore
|
||||||
break
|
|
||||||
|
if not is_air and not is_ignore then
|
||||||
|
-- It's not an airlike node or something else odd
|
||||||
|
data[i] = node_ids[next_index]
|
||||||
|
next_index = next_index + 1
|
||||||
|
changes.replaced = changes.replaced + 1
|
||||||
|
|
||||||
|
-- If we're done replacing nodes in this column, move to the next one
|
||||||
|
if next_index > #node_ids then
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
changes.skipped_columns_slope = changes.skipped_columns_slope + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if not placed_node then
|
if not placed_node then
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
--- Creates vertical walls on the inside of the defined region.
|
|
||||||
-- @module worldeditadditions.walls
|
|
||||||
|
|
||||||
-- ██ ██ █████ ██ ██ ███████
|
-- ██ ██ █████ ██ ██ ███████
|
||||||
-- ██ ██ ██ ██ ██ ██ ██
|
-- ██ ██ ██ ██ ██ ██ ██
|
||||||
|
@ -7,8 +5,15 @@
|
||||||
-- ██ ███ ██ ██ ██ ██ ██ ██
|
-- ██ ███ ██ ██ ██ ██ ██ ██
|
||||||
-- ███ ███ ██ ██ ███████ ███████ ███████
|
-- ███ ███ ██ ██ ███████ ███████ ███████
|
||||||
|
|
||||||
function worldeditadditions.walls(pos1, pos2, node_name)
|
--- Creates vertical walls on the inside of the defined region.
|
||||||
|
-- @apipath worldeditadditions.walls
|
||||||
|
-- @param pos1 Vector Position 1 of the defined region,
|
||||||
|
-- @param pos2 Vector Position 2 of the defined region.
|
||||||
|
-- @param node_name string The name of the node to use to create the walls with.
|
||||||
|
-- @param thickness number? The thickness of the walls to create. Default: 1
|
||||||
|
function worldeditadditions.walls(pos1, pos2, node_name, thickness)
|
||||||
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
|
||||||
|
if not thickness then thickness = 1 end
|
||||||
-- pos2 will always have the highest co-ordinates now
|
-- pos2 will always have the highest co-ordinates now
|
||||||
|
|
||||||
-- Fetch the nodes in the specified area
|
-- Fetch the nodes in the specified area
|
||||||
|
@ -22,7 +27,10 @@ function worldeditadditions.walls(pos1, pos2, node_name)
|
||||||
for z = pos2.z, pos1.z, -1 do
|
for z = pos2.z, pos1.z, -1 do
|
||||||
for y = pos2.y, pos1.y, -1 do
|
for y = pos2.y, pos1.y, -1 do
|
||||||
for x = pos2.x, pos1.x, -1 do
|
for x = pos2.x, pos1.x, -1 do
|
||||||
if x == pos1.x or x == pos2.x or z == pos1.z or z == pos2.z then
|
if math.abs(x - pos1.x) < thickness
|
||||||
|
or math.abs(x - pos2.x) < thickness
|
||||||
|
or math.abs(z - pos1.z) < thickness
|
||||||
|
or math.abs(z - pos2.z) < thickness then
|
||||||
data[area:index(x, y, z)] = node_id
|
data[area:index(x, y, z)] = node_id
|
||||||
count_replaced = count_replaced + 1
|
count_replaced = count_replaced + 1
|
||||||
end
|
end
|
||||||
|
|
37
worldeditadditions/utils/inspect.lua
Normal file
37
worldeditadditions/utils/inspect.lua
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
--- Serialises an arbitrary value to a string.
|
||||||
|
-- Note that although the resulting table *looks* like valid Lua, it isn't.
|
||||||
|
-- @param item any Input item to serialise.
|
||||||
|
-- @param sep string key value seperator
|
||||||
|
-- @param new_line string key value pair delimiter
|
||||||
|
-- @return string concatenated table pairs
|
||||||
|
local function inspect(item, maxdepth)
|
||||||
|
if not maxdepth then maxdepth = 3 end
|
||||||
|
if type(item) ~= "table" then
|
||||||
|
if type(item) == "string" then return "\""..item.."\"" end
|
||||||
|
return tostring(item)
|
||||||
|
end
|
||||||
|
if maxdepth < 1 then return "[truncated]" end
|
||||||
|
|
||||||
|
local result = { "{\n" }
|
||||||
|
for key,value in pairs(item) do
|
||||||
|
local value_text = inspect(value, maxdepth - 1)
|
||||||
|
:gsub("\n", "\n\t")
|
||||||
|
table.insert(result, "\t"..tostring(key).." = ".."("..type(value)..") "..value_text.."\n")
|
||||||
|
end
|
||||||
|
table.insert(result, "}")
|
||||||
|
return table.concat(result,"")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- local test = {
|
||||||
|
-- a = { x = 5, y = 7, z = -6 },
|
||||||
|
-- http = {
|
||||||
|
-- port = 80,
|
||||||
|
-- protocol = "http"
|
||||||
|
-- },
|
||||||
|
-- mode = "do_stuff",
|
||||||
|
-- apple = false,
|
||||||
|
-- deepa = { deepb = { deepc = { yay = "Happy Birthday!" } }}
|
||||||
|
-- }
|
||||||
|
-- print(inspect(test))
|
||||||
|
|
||||||
|
return inspect
|
|
@ -1,16 +1,22 @@
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
--- A Queue implementation, taken & adapted from https://www.lua.org/pil/11.4.html
|
--- A Queue implementation
|
||||||
|
-- Taken & adapted from https://www.lua.org/pil/11.4.html
|
||||||
-- @submodule worldeditadditions.utils.queue
|
-- @submodule worldeditadditions.utils.queue
|
||||||
|
-- @class
|
||||||
local Queue = {}
|
local Queue = {}
|
||||||
Queue.__index = Queue
|
Queue.__index = Queue
|
||||||
|
|
||||||
|
--- Creates a new queue instance.
|
||||||
|
-- @returns Queue
|
||||||
function Queue.new()
|
function Queue.new()
|
||||||
local result = { first = 0, last = -1, items = {} }
|
local result = { first = 0, last = -1, items = {} }
|
||||||
setmetatable(result, Queue)
|
setmetatable(result, Queue)
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Adds a new value to the end of the queue.
|
||||||
|
-- @param value any The new value to add to the end of the queue.
|
||||||
|
-- @returns number The index of the value that was added to the queue.
|
||||||
function Queue:enqueue(value)
|
function Queue:enqueue(value)
|
||||||
local new_last = self.last + 1
|
local new_last = self.last + 1
|
||||||
self.last = new_last
|
self.last = new_last
|
||||||
|
@ -18,6 +24,9 @@ function Queue:enqueue(value)
|
||||||
return new_last
|
return new_last
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Determines whether a given value is present in this queue or not.
|
||||||
|
-- @param value any The value to check.
|
||||||
|
-- @returns bool Whether the given value exists in the queue or not.
|
||||||
function Queue:contains(value)
|
function Queue:contains(value)
|
||||||
for i=self.first,self.last do
|
for i=self.first,self.last do
|
||||||
if self.items[i] == value then
|
if self.items[i] == value then
|
||||||
|
@ -27,14 +36,22 @@ function Queue:contains(value)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Returns whether the queue is empty or not.
|
||||||
|
-- @returns bool Whether the queue is empty or not.
|
||||||
function Queue:is_empty()
|
function Queue:is_empty()
|
||||||
return self.first > self.last
|
return self.first > self.last
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Removes the item with the given index from the queue.
|
||||||
|
-- Item indexes do not change as the items in a queue are added and removed.
|
||||||
|
-- @param number The index of the item to remove from the queue.
|
||||||
|
-- @returns nil
|
||||||
function Queue:remove_index(index)
|
function Queue:remove_index(index)
|
||||||
self.items[index] = nil
|
self.items[index] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Dequeues an item from the front of the queue.
|
||||||
|
-- @returns any|nil Returns the item at the front of the queue, or nil if no items are currently enqueued.
|
||||||
function Queue:dequeue()
|
function Queue:dequeue()
|
||||||
if Queue.is_empty(self) then
|
if Queue.is_empty(self) then
|
||||||
error("Error: The self is empty!")
|
error("Error: The self is empty!")
|
||||||
|
@ -53,4 +70,5 @@ function Queue:dequeue()
|
||||||
return value
|
return value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return Queue
|
return Queue
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
local wea = worldeditadditions
|
||||||
|
|
||||||
--- Given a manip object and associates, generates a 2D x/z heightmap.
|
--- Given a manip object and associates, generates a 2D x/z heightmap.
|
||||||
-- Note that pos1 and pos2 should have already been pushed through
|
-- Note that pos1 and pos2 should have already been pushed through
|
||||||
|
@ -20,7 +21,7 @@ function worldeditadditions.make_heightmap(pos1, pos2, manip, area, data)
|
||||||
-- Scan each column top to bottom
|
-- Scan each column top to bottom
|
||||||
for y = pos2.y+1, pos1.y, -1 do
|
for y = pos2.y+1, pos1.y, -1 do
|
||||||
local i = area:index(x, y, z)
|
local i = area:index(x, y, z)
|
||||||
if not (worldeditadditions.is_airlike(data[i]) or worldeditadditions.is_liquidlike(data[i])) then
|
if not (wea.is_airlike(data[i]) or wea.is_liquidlike(data[i])) then
|
||||||
-- It's the first non-airlike node in this column
|
-- It's the first non-airlike node in this column
|
||||||
-- Start heightmap values from 1 (i.e. there's at least 1 node in the column)
|
-- Start heightmap values from 1 (i.e. there's at least 1 node in the column)
|
||||||
heightmap[hi] = (y - pos1.y) + 1
|
heightmap[hi] = (y - pos1.y) + 1
|
||||||
|
@ -48,7 +49,7 @@ end
|
||||||
-- will have the z and y values swapped.
|
-- will have the z and y values swapped.
|
||||||
-- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.make_heightmap().
|
-- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.make_heightmap().
|
||||||
-- @param heightmap_size int[] The size of the heightmap in the form [ z, x ]
|
-- @param heightmap_size int[] The size of the heightmap in the form [ z, x ]
|
||||||
-- @return Vector[] The calculated normal map, in the same form as the input heightmap. Each element of the array is a 3D Vector (i.e. { x, y, z }) representing a normal.
|
-- @return Vector[] The calculated normal map, in the same form as the input heightmap. Each element of the array is a Vector3 instance representing a normal.
|
||||||
function worldeditadditions.calculate_normals(heightmap, heightmap_size)
|
function worldeditadditions.calculate_normals(heightmap, heightmap_size)
|
||||||
-- print("heightmap_size: "..heightmap_size.x.."x"..heightmap_size.z)
|
-- print("heightmap_size: "..heightmap_size.x.."x"..heightmap_size.z)
|
||||||
local result = {}
|
local result = {}
|
||||||
|
@ -72,18 +73,43 @@ function worldeditadditions.calculate_normals(heightmap, heightmap_size)
|
||||||
-- print("[normals] LEFT | index", z*heightmap_size.x + (x-1), "x", x, "x-1", x - 1, "left", left, "limit", 0)
|
-- print("[normals] LEFT | index", z*heightmap_size.x + (x-1), "x", x, "x-1", x - 1, "left", left, "limit", 0)
|
||||||
-- print("[normals] RIGHT | index", z*heightmap_size.x + (x+1), "x", x, "x+1", x + 1, "right", right, "limit", heightmap_size.x-1)
|
-- print("[normals] RIGHT | index", z*heightmap_size.x + (x+1), "x", x, "x+1", x + 1, "right", right, "limit", heightmap_size.x-1)
|
||||||
|
|
||||||
result[hi] = worldeditadditions.vector.normalize({
|
result[hi] = wea.Vector3.new(
|
||||||
x = left - right,
|
left - right, -- x
|
||||||
y = 2, -- Z & Y are flipped
|
2, -- y - Z & Y are flipped
|
||||||
z = down - up
|
down - up -- z
|
||||||
})
|
):normalise()
|
||||||
|
|
||||||
-- print("[normals] at "..hi.." ("..x..", "..z..") normal "..worldeditadditions.vector.tostring(result[hi]))
|
-- print("[normals] at "..hi.." ("..x..", "..z..") normal "..result[hi])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Converts a 2d heightmap into slope values in radians.
|
||||||
|
-- Convert a radians to degrees by doing (radians*math.pi) / 180 for display,
|
||||||
|
-- but it is STRONGLY recommended to keep all internal calculations in radians.
|
||||||
|
-- @param heightmap table A ZERO indexed flat heightmap. See worldeditadditions.make_heightmap().
|
||||||
|
-- @param heightmap_size int[] The size of the heightmap in the form [ z, x ]
|
||||||
|
-- @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.
|
||||||
|
function worldeditadditions.calculate_slopes(heightmap, heightmap_size)
|
||||||
|
local normals = worldeditadditions.calculate_normals(heightmap, heightmap_size)
|
||||||
|
local slopes = { }
|
||||||
|
|
||||||
|
local up = wea.Vector3.new(0, 1, 0) -- Z & Y are flipped
|
||||||
|
|
||||||
|
for z = heightmap_size.z-1, 0, -1 do
|
||||||
|
for x = heightmap_size.x-1, 0, -1 do
|
||||||
|
local hi = z*heightmap_size.x + x
|
||||||
|
|
||||||
|
-- Ref https://stackoverflow.com/a/16669463/1460422
|
||||||
|
-- slopes[hi] = wea.Vector3.dot_product(normals[hi], up)
|
||||||
|
slopes[hi] = math.acos(normals[hi].y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return slopes
|
||||||
|
end
|
||||||
|
|
||||||
--- Applies changes to a heightmap to a Voxel Manipulator data block.
|
--- Applies changes to a heightmap to a Voxel Manipulator data block.
|
||||||
-- @param pos1 vector Position 1 of the defined region
|
-- @param pos1 vector Position 1 of the defined region
|
||||||
-- @param pos2 vector Position 2 of the defined region
|
-- @param pos2 vector Position 2 of the defined region
|
||||||
|
|
|
@ -256,7 +256,8 @@ end
|
||||||
--- Sorts the components of the given vectors.
|
--- Sorts the components of the given vectors.
|
||||||
-- pos1 will contain the minimum values, and pos2 the maximum values.
|
-- pos1 will contain the minimum values, and pos2 the maximum values.
|
||||||
-- Returns 2 new vectors.
|
-- Returns 2 new vectors.
|
||||||
-- Note that the vectors provided do not *have* to be instances of Vector3.
|
-- Note that for this specific function
|
||||||
|
-- the vectors provided do not *have* to be instances of Vector3.
|
||||||
-- It is only required that they have the keys x, y, and z.
|
-- It is only required that they have the keys x, y, and z.
|
||||||
-- Vector3 instances are always returned.
|
-- Vector3 instances are always returned.
|
||||||
-- This enables convenient ingesting of positions from outside.
|
-- This enables convenient ingesting of positions from outside.
|
||||||
|
@ -316,8 +317,8 @@ end
|
||||||
|
|
||||||
--- Returns the mean (average) of 2 positions.
|
--- Returns the mean (average) of 2 positions.
|
||||||
-- In other words, returns the centre of 2 points.
|
-- In other words, returns the centre of 2 points.
|
||||||
-- @param pos1 Vector3 pos1 of the defined region.
|
-- @param pos1 Vector3|number pos1 of the defined region.
|
||||||
-- @param pos2 Vector3 pos2 of the defined region.
|
-- @param pos2 Vector3|number pos2 of the defined region.
|
||||||
-- @param target Vector3 Centre coordinates.
|
-- @param target Vector3 Centre coordinates.
|
||||||
function Vector3.mean(pos1, pos2)
|
function Vector3.mean(pos1, pos2)
|
||||||
return (pos1 + pos2) / 2
|
return (pos1 + pos2) / 2
|
||||||
|
@ -325,10 +326,16 @@ end
|
||||||
|
|
||||||
|
|
||||||
--- Returns a vector of the min components of 2 vectors.
|
--- Returns a vector of the min components of 2 vectors.
|
||||||
-- @param pos1 Vector3 The first vector to operate on.
|
-- @param pos1 Vector3|number The first vector to operate on.
|
||||||
-- @param pos2 Vector3 The second vector to operate on.
|
-- @param pos2 Vector3|number The second vector to operate on.
|
||||||
-- @return Vector3 The minimum values from the input vectors
|
-- @return Vector3 The minimum values from the input vectors
|
||||||
function Vector3.min(pos1, pos2)
|
function Vector3.min(pos1, pos2)
|
||||||
|
if type(pos1) == "number" then
|
||||||
|
pos1 = Vector3.new(pos1, pos1, pos1)
|
||||||
|
end
|
||||||
|
if type(pos2) == "number" then
|
||||||
|
pos2 = Vector3.new(pos2, pos2, pos2)
|
||||||
|
end
|
||||||
return Vector3.new(
|
return Vector3.new(
|
||||||
math.min(pos1.x, pos2.x),
|
math.min(pos1.x, pos2.x),
|
||||||
math.min(pos1.y, pos2.y),
|
math.min(pos1.y, pos2.y),
|
||||||
|
@ -341,6 +348,12 @@ end
|
||||||
-- @param pos2 Vector3 The second vector to operate on.
|
-- @param pos2 Vector3 The second vector to operate on.
|
||||||
-- @return Vector3 The maximum values from the input vectors.
|
-- @return Vector3 The maximum values from the input vectors.
|
||||||
function Vector3.max(pos1, pos2)
|
function Vector3.max(pos1, pos2)
|
||||||
|
if type(pos1) == "number" then
|
||||||
|
pos1 = Vector3.new(pos1, pos1, pos1)
|
||||||
|
end
|
||||||
|
if type(pos2) == "number" then
|
||||||
|
pos2 = Vector3.new(pos2, pos2, pos2)
|
||||||
|
end
|
||||||
return Vector3.new(
|
return Vector3.new(
|
||||||
math.max(pos1.x, pos2.x),
|
math.max(pos1.x, pos2.x),
|
||||||
math.max(pos1.y, pos2.y),
|
math.max(pos1.y, pos2.y),
|
||||||
|
|
|
@ -6,7 +6,7 @@ local we_c = worldeditadditions_commands
|
||||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||||
-- ██████ ██████ ██ ████ ███████ ██ ██ ███████ ██ ██ ███████
|
-- ██████ ██████ ██ ████ ███████ ██ ██ ███████ ██ ██ ███████
|
||||||
worldedit.register_command("bonemeal", {
|
worldedit.register_command("bonemeal", {
|
||||||
params = "[<strength> [<chance>]]",
|
params = "[<strength> [<chance> [<node_name> [<node_name> ...]]]]",
|
||||||
description = "Bonemeals everything that's bonemeal-able that has an air node directly above it. Optionally takes a strength value to use (default: 1, maximum: 4), and a chance to actually bonemeal an eligible node (positive integer; nodes have a 1-in-<chance> chance to be bonemealed; higher values mean a lower chance; default: 1 - 100% chance).",
|
description = "Bonemeals everything that's bonemeal-able that has an air node directly above it. Optionally takes a strength value to use (default: 1, maximum: 4), and a chance to actually bonemeal an eligible node (positive integer; nodes have a 1-in-<chance> chance to be bonemealed; higher values mean a lower chance; default: 1 - 100% chance).",
|
||||||
privs = { worldedit = true },
|
privs = { worldedit = true },
|
||||||
require_pos = 2,
|
require_pos = 2,
|
||||||
|
@ -19,15 +19,16 @@ worldedit.register_command("bonemeal", {
|
||||||
|
|
||||||
local strength = 1
|
local strength = 1
|
||||||
local chance = 1
|
local chance = 1
|
||||||
|
local node_names = {} -- An empty table means all nodes
|
||||||
|
|
||||||
if #parts >= 1 then
|
if #parts >= 1 then
|
||||||
strength = tonumber(parts[1])
|
strength = tonumber(table.remove(parts, 1))
|
||||||
if not strength then
|
if not strength then
|
||||||
return false, "Invalid strength value (value must be an integer)"
|
return false, "Invalid strength value (value must be an integer)"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #parts >= 2 then
|
if #parts >= 2 then
|
||||||
chance = worldeditadditions.parse.chance(parts[2])
|
chance = worldeditadditions.parse.chance(table.remove(parts, 1))
|
||||||
if not chance then
|
if not chance then
|
||||||
return false, "Invalid chance value (must be a positive integer)"
|
return false, "Invalid chance value (must be a positive integer)"
|
||||||
end
|
end
|
||||||
|
@ -37,21 +38,33 @@ worldedit.register_command("bonemeal", {
|
||||||
return false, "Error: strength value out of bounds (value must be an integer between 1 and 4 inclusive)"
|
return false, "Error: strength value out of bounds (value must be an integer between 1 and 4 inclusive)"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
if #parts > 0 then
|
||||||
|
for _,nodename in pairs(parts) do
|
||||||
|
local normalised = worldedit.normalize_nodename(nodename)
|
||||||
|
if not normalised then return false, "Error: Unknown node name '"..nodename.."'." end
|
||||||
|
table.insert(node_names, normalised)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- We unconditionally math.floor here because when we tried to test for it directly it was unreliable
|
-- We unconditionally math.floor here because when we tried to test for it directly it was unreliable
|
||||||
return true, math.floor(strength), math.floor(chance)
|
return true, math.floor(strength), math.floor(chance), node_names
|
||||||
end,
|
end,
|
||||||
nodes_needed = function(name) -- strength, chance
|
nodes_needed = function(name) -- strength, chance
|
||||||
-- Since every node has to have an air block, in the best-case scenario
|
-- Since every node has to have an air block, in the best-case scenario
|
||||||
-- edit only half the nodes in the selected area
|
-- edit only half the nodes in the selected area
|
||||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) / 2
|
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) / 2
|
||||||
end,
|
end,
|
||||||
func = function(name, strength, chance)
|
func = function(name, strength, chance, node_names)
|
||||||
local start_time = worldeditadditions.get_ms_time()
|
local start_time = worldeditadditions.get_ms_time()
|
||||||
local success, nodes_bonemealed, candidates = worldeditadditions.bonemeal(worldedit.pos1[name], worldedit.pos2[name], strength, chance)
|
local success, nodes_bonemealed, candidates = worldeditadditions.bonemeal(
|
||||||
if not success then
|
worldedit.pos1[name], worldedit.pos2[name],
|
||||||
-- nodes_bonemealed is an error message here because success == false
|
strength, chance,
|
||||||
return success, nodes_bonemealed
|
node_names
|
||||||
end
|
)
|
||||||
|
-- nodes_bonemealed is an error message here if success == false
|
||||||
|
if not success then return success, nodes_bonemealed end
|
||||||
|
|
||||||
local percentage = worldeditadditions.round((nodes_bonemealed / candidates)*100, 2)
|
local percentage = worldeditadditions.round((nodes_bonemealed / candidates)*100, 2)
|
||||||
local time_taken = worldeditadditions.get_ms_time() - start_time
|
local time_taken = worldeditadditions.get_ms_time() - start_time
|
||||||
-- Avoid nan% - since if there aren't any candidates then nodes_bonemealed will be 0 too
|
-- Avoid nan% - since if there aren't any candidates then nodes_bonemealed will be 0 too
|
||||||
|
|
|
@ -1,11 +1,34 @@
|
||||||
|
local function parse_slope_range(text)
|
||||||
|
if string.match(text, "%.%.") then
|
||||||
|
-- It's in the form a..b
|
||||||
|
local parts = worldeditadditions.split(text, "..", true)
|
||||||
|
if not parts then return nil end
|
||||||
|
if #parts ~= 2 then return false, "Error: Exactly 2 numbers may be separated by a double dot '..' (e.g. 10..45)" end
|
||||||
|
local min_slope = tonumber(parts[1])
|
||||||
|
local max_slope = tonumber(parts[2])
|
||||||
|
if not min_slope then return false, "Error: Failed to parse the specified min_slope '"..tostring(min_slope).."' value as a number." end
|
||||||
|
if not max_slope then return false, "Error: Failed to parse the specified max_slope '"..tostring(max_slope).."' value as a number." end
|
||||||
|
|
||||||
|
-- math.rad converts degrees to radians
|
||||||
|
return true, math.rad(min_slope), math.rad(max_slope)
|
||||||
|
else
|
||||||
|
-- It's a single value
|
||||||
|
local max_slope = tonumber(text)
|
||||||
|
if not max_slope then return nil end
|
||||||
|
|
||||||
|
return true, 0, math.rad(max_slope)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
-- ██████ ██ ██ ███████ ██████ ██ █████ ██ ██
|
-- ██████ ██ ██ ███████ ██████ ██ █████ ██ ██
|
||||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||||
-- ██ ██ ██ ██ █████ ██████ ██ ███████ ████
|
-- ██ ██ ██ ██ █████ ██████ ██ ███████ ████
|
||||||
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
||||||
-- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██
|
-- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██
|
||||||
worldedit.register_command("layers", {
|
worldedit.register_command("layers", {
|
||||||
params = "[<node_name_1> [<layer_count_1>]] [<node_name_2> [<layer_count_2>]] ...",
|
params = "[<max_slope|min_slope..max_slope>] [<node_name_1> [<layer_count_1>]] [<node_name_2> [<layer_count_2>]] ...",
|
||||||
description = "Replaces the topmost non-airlike nodes with layers of the given nodes from top to bottom. Like WorldEdit for MC's //naturalize command. Default: dirt_with_grass dirt 3",
|
description = "Replaces the topmost non-airlike nodes with layers of the given nodes from top to bottom. Like WorldEdit for MC's //naturalize command. Optionally takes a maximum or minimum and maximum slope value. If a column's slope value falls outside the defined range, then it's skipped. Default: dirt_with_grass dirt 3",
|
||||||
privs = { worldedit = true },
|
privs = { worldedit = true },
|
||||||
require_pos = 2,
|
require_pos = 2,
|
||||||
parse = function(params_text)
|
parse = function(params_text)
|
||||||
|
@ -13,21 +36,42 @@ worldedit.register_command("layers", {
|
||||||
params_text = "dirt_with_grass dirt 3"
|
params_text = "dirt_with_grass dirt 3"
|
||||||
end
|
end
|
||||||
|
|
||||||
local success, node_list = worldeditadditions.parse.weighted_nodes(
|
local parts = worldeditadditions.split_shell(params_text)
|
||||||
worldeditadditions.split_shell(params_text),
|
local success, min_slope, max_slope
|
||||||
|
|
||||||
|
if #parts > 0 then
|
||||||
|
success, min_slope, max_slope = parse_slope_range(parts[1])
|
||||||
|
if success then
|
||||||
|
table.remove(parts, 1) -- Automatically shifts other values down
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not min_slope then min_slope = 0 end
|
||||||
|
if not max_slope then max_slope = 180 end
|
||||||
|
|
||||||
|
local node_list
|
||||||
|
success, node_list = worldeditadditions.parse.weighted_nodes(
|
||||||
|
parts,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
return success, node_list
|
return success, node_list, min_slope, max_slope
|
||||||
end,
|
end,
|
||||||
nodes_needed = function(name)
|
nodes_needed = function(name)
|
||||||
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name])
|
||||||
end,
|
end,
|
||||||
func = function(name, node_list)
|
func = function(name, node_list, min_slope, max_slope)
|
||||||
local start_time = worldeditadditions.get_ms_time()
|
local start_time = worldeditadditions.get_ms_time()
|
||||||
local changes = worldeditadditions.layers(worldedit.pos1[name], worldedit.pos2[name], node_list)
|
local changes = worldeditadditions.layers(
|
||||||
|
worldedit.pos1[name], worldedit.pos2[name],
|
||||||
|
node_list,
|
||||||
|
min_slope, max_slope
|
||||||
|
)
|
||||||
local time_taken = worldeditadditions.get_ms_time() - start_time
|
local time_taken = worldeditadditions.get_ms_time() - start_time
|
||||||
|
|
||||||
minetest.log("action", name .. " used //layers at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. changes.replaced .. " nodes and skipping " .. changes.skipped_columns .. " columns in " .. time_taken .. "s")
|
print("DEBUG min_slope", min_slope, "max_slope", max_slope)
|
||||||
return true, changes.replaced .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped in " .. worldeditadditions.format.human_time(time_taken)
|
print("DEBUG min_slope", math.deg(min_slope), "max_slope", math.deg(max_slope))
|
||||||
|
|
||||||
|
minetest.log("action", name .. " used //layers at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. changes.replaced .. " nodes and skipping " .. changes.skipped_columns .. " columns ("..changes.skipped_columns_slope.." due to slope constraints) in " .. time_taken .. "s")
|
||||||
|
return true, changes.replaced .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped ("..changes.skipped_columns_slope.." due to slope constraints) in " .. worldeditadditions.format.human_time(time_taken)
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,28 +4,48 @@
|
||||||
-- ██ ███ ██ ██ ██ ██ ██ ██
|
-- ██ ███ ██ ██ ██ ██ ██ ██
|
||||||
-- ███ ███ ██ ██ ███████ ███████ ███████
|
-- ███ ███ ██ ██ ███████ ███████ ███████
|
||||||
worldedit.register_command("walls", {
|
worldedit.register_command("walls", {
|
||||||
params = "<replace_node>",
|
params = "<replace_node> [<thickness=1>]",
|
||||||
description = "Creates vertical walls of <replace_node> around the inside edges of the defined region.",
|
description = "Creates vertical walls of <replace_node> around the inside edges of the defined region. Optionally specifies a thickness for the walls to be created (defaults to 1)",
|
||||||
privs = { worldedit = true },
|
privs = { worldedit = true },
|
||||||
require_pos = 2,
|
require_pos = 2,
|
||||||
parse = function(params_text)
|
parse = function(params_text)
|
||||||
local target_node = worldedit.normalize_nodename(params_text)
|
local parts = worldeditadditions.split_shell(params_text)
|
||||||
|
|
||||||
|
local target_node
|
||||||
|
local thickness = 1
|
||||||
|
|
||||||
|
local target_node_raw = table.remove(parts, 1)
|
||||||
|
target_node = worldedit.normalize_nodename(target_node_raw)
|
||||||
if not target_node then
|
if not target_node then
|
||||||
return false, "Error: Invalid node name"
|
return false, "Error: Invalid node name '"..target_node_raw.."'."
|
||||||
end
|
end
|
||||||
return true, target_node
|
|
||||||
|
if #parts > 0 then
|
||||||
|
local thickness_raw = table.remove(parts, 1)
|
||||||
|
thickness = tonumber(thickness_raw)
|
||||||
|
if not thickness then return false, "Error: Invalid thickness value '"..thickness_raw.."'. The thickness value must be a positive integer greater than or equal to 0." end
|
||||||
|
if thickness < 1 then return false, "Error: That thickness value '"..thickness_raw.."' is out of range. The thickness value must be a positive integer greater than or equal to 0." end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, target_node, math.floor(thickness)
|
||||||
end,
|
end,
|
||||||
nodes_needed = function(name)
|
nodes_needed = function(name, target_node, thickness)
|
||||||
-- //overlay only modifies up to 1 node per column in the selected region
|
-- //overlay only modifies up to 1 node per column in the selected region
|
||||||
local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
|
local pos1, pos2 = worldedit.sort_pos(worldedit.pos1[name], worldedit.pos2[name])
|
||||||
|
|
||||||
local pos3 = { x = pos2.x - 2, z = pos2.z - 2, y = pos2.y }
|
local pos3 = {
|
||||||
|
x = pos2.x - thickness*2,
|
||||||
|
z = pos2.z - thickness*2,
|
||||||
|
y = pos2.y }
|
||||||
|
|
||||||
return worldedit.volume(pos1, pos2) - worldedit.volume(pos1, pos3)
|
return worldedit.volume(pos1, pos2) - worldedit.volume(pos1, pos3)
|
||||||
end,
|
end,
|
||||||
func = function(name, target_node)
|
func = function(name, target_node, thickness)
|
||||||
local start_time = worldeditadditions.get_ms_time()
|
local start_time = worldeditadditions.get_ms_time()
|
||||||
local success, replaced = worldeditadditions.walls(worldedit.pos1[name], worldedit.pos2[name], target_node)
|
local success, replaced = worldeditadditions.walls(
|
||||||
|
worldedit.pos1[name], worldedit.pos2[name],
|
||||||
|
target_node, thickness
|
||||||
|
)
|
||||||
local time_taken = worldeditadditions.get_ms_time() - start_time
|
local time_taken = worldeditadditions.get_ms_time() - start_time
|
||||||
|
|
||||||
minetest.log("action", name .. " used //walls from "..worldeditadditions.vector.tostring(worldedit.pos1[name]).." to "..worldeditadditions.vector.tostring(worldedit.pos1[name])..", replacing " .. replaced .. " nodes in " .. time_taken .. "s")
|
minetest.log("action", name .. " used //walls from "..worldeditadditions.vector.tostring(worldedit.pos1[name]).." to "..worldeditadditions.vector.tostring(worldedit.pos1[name])..", replacing " .. replaced .. " nodes in " .. time_taken .. "s")
|
||||||
|
|
Loading…
Reference in a new issue