From 75a17ed64f36ec5dd70324a34f64db1f4b2ff8f9 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Thu, 11 Jun 2020 00:38:16 +0100 Subject: [PATCH] Add //layers command Also add aliases //naturalise and //naturalize --- README.md | 2 +- worldeditadditions/init.lua | 1 + worldeditadditions/lib/layers.lua | 58 +++++++++++++++++++ worldeditadditions/lib/overlay.lua | 3 +- worldeditadditions/utils/nodes.lua | 25 ++++++-- worldeditadditions/utils/strings.lua | 18 ++++-- .../commands/convolve.lua | 2 +- .../commands/layers.lua | 36 ++++++++++++ worldeditadditions_commands/init.lua | 1 + 9 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 worldeditadditions/lib/layers.lua create mode 100644 worldeditadditions_commands/commands/layers.lua diff --git a/README.md b/README.md index 5d770ae..66e2b7d 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ Here are all the above examples together: ``` ### `//convolve [[,]] []` -Advanced version of `//smooth` from we_env, and so far the _only_ WorldEditAdditions command to have any aliases (`//smoothadv` and `//conv`). +Advanced version of `//smooth` from we_env, and one of the few WorldEditAdditions commands to have any aliases (`//smoothadv` and `//conv`). Extracts a heightmap from the defined region and then proceeds to [convolve](https://en.wikipedia.org/wiki/Kernel_(image_processing)) over it with the specified kernel. The kernel can be thought of as the filter that will be applied to the heightmap. Once done, the newly convolved heightmap is applied to the terrain. diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 42a0d2b..cef29a9 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -15,6 +15,7 @@ dofile(worldeditadditions.modpath.."/utils/tables.lua") dofile(worldeditadditions.modpath.."/utils.lua") dofile(worldeditadditions.modpath.."/lib/floodfill.lua") dofile(worldeditadditions.modpath.."/lib/overlay.lua") +dofile(worldeditadditions.modpath.."/lib/layers.lua") dofile(worldeditadditions.modpath.."/lib/ellipsoid.lua") dofile(worldeditadditions.modpath.."/lib/torus.lua") dofile(worldeditadditions.modpath.."/lib/walls.lua") diff --git a/worldeditadditions/lib/layers.lua b/worldeditadditions/lib/layers.lua new file mode 100644 index 0000000..63b7a4b --- /dev/null +++ b/worldeditadditions/lib/layers.lua @@ -0,0 +1,58 @@ +--- Overlap command. Places a specified node on top of each column. +-- @module worldeditadditions.overlay + +function worldeditadditions.layers(pos1, pos2, node_weights) + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + -- pos2 will always have the highest co-ordinates now + + -- Fetch the nodes in the specified area + local manip, area = worldedit.manip_helpers.init(pos1, pos2) + local data = manip:get_data() + + local node_id_ignore = minetest.get_content_id("ignore") + + local node_ids, node_ids_count = worldeditadditions.unwind_node_list(node_weights) + + -- minetest.log("action", "pos1: " .. worldeditadditions.vector.tostring(pos1)) + -- minetest.log("action", "pos2: " .. worldeditadditions.vector.tostring(pos2)) + for i,v in ipairs(node_ids) do + print("[layer] i", i, "node id", v) + end + -- z y x is the preferred loop order, but that isn't really possible here + + local changes = { replaced = 0, skipped_columns = 0 } + for z = pos2.z, pos1.z, -1 do + for x = pos2.x, pos1.x, -1 do + local next_index = 1 -- We use table.insert() in make_weighted + local placed_node = false + + for y = pos2.y, pos1.y, -1 do + local i = area:index(x, y, z) + + local is_air = worldeditadditions.is_airlike(data[i]) + local is_ignore = data[i] == node_id_ignore + + 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 + + if not placed_node then + changes.skipped_columns = changes.skipped_columns + 1 + end + end + end + + -- Save the modified nodes back to disk & return + worldedit.manip_helpers.finish(manip, data) + + return changes +end diff --git a/worldeditadditions/lib/overlay.lua b/worldeditadditions/lib/overlay.lua index ff34311..9730616 100644 --- a/worldeditadditions/lib/overlay.lua +++ b/worldeditadditions/lib/overlay.lua @@ -9,7 +9,6 @@ function worldeditadditions.overlay(pos1, pos2, node_weights) local manip, area = worldedit.manip_helpers.init(pos1, pos2) local data = manip:get_data() - local node_id_air = minetest.get_content_id("air") local node_id_ignore = minetest.get_content_id("ignore") local node_ids, node_ids_count = worldeditadditions.make_weighted(node_weights) @@ -28,7 +27,7 @@ function worldeditadditions.overlay(pos1, pos2, node_weights) for y = pos2.y, pos1.y, -1 do local i = area:index(x, y, z) - local is_air = data[i] == node_id_air + local is_air = worldeditadditions.is_airlike(data[i]) if not is_air then -- wielded_light nodes are airlike too local this_node_name = minetest.get_name_from_content_id(data[i]) is_air = is_air or worldeditadditions.string_starts(this_node_name, "wielded_light") diff --git a/worldeditadditions/utils/nodes.lua b/worldeditadditions/utils/nodes.lua index 41ec5b5..336ebc5 100644 --- a/worldeditadditions/utils/nodes.lua +++ b/worldeditadditions/utils/nodes.lua @@ -6,10 +6,23 @@ function worldeditadditions.make_weighted(tbl) local next_id = minetest.get_content_id(node_name) print("[make_weighted] seen "..node_name.." @ weight "..weight.." → id "..next_id) for i = 1, weight do - table.insert( - result, - next_id - ) + table.insert(result, next_id) + end + end + return result, #result +end + +--- Unwinds a list of { node = string, weight = number } tables into a list of node ids. +-- The node ids will be repeated multiple times according to their weights +-- (e.g. an entry with a weight of 2 will be repeated twice). +-- @param list table[] The list to unwind. +-- @return number[],number The unwound list of node ids, follows by the number of node ids in total. +function worldeditadditions.unwind_node_list(list) + local result = {} + for i,item in ipairs(list) do + local node_id = minetest.get_content_id(item.node) + for i = 1, item.weight do + table.insert(result, node_id) end end return result, #result @@ -41,6 +54,10 @@ function worldeditadditions.is_airlike(id) return true end -- Just in case + if worldeditadditions.string_starts(this_node_name, "wielded_light") then + return true + end + -- Just in case return false end diff --git a/worldeditadditions/utils/strings.lua b/worldeditadditions/utils/strings.lua index 94fda4b..9c05e4f 100644 --- a/worldeditadditions/utils/strings.lua +++ b/worldeditadditions/utils/strings.lua @@ -155,8 +155,10 @@ end --- Parses a list of strings as a list of weighted nodes - e.g. like in the //mix command. -- @param parts string[] The list of strings to parse (try worldeditadditions.split) +-- @param as_list bool If true, then table.insert() successive { node = string, weight = number } subtables when parsing instead of populating as an associative array. -- @returns table A table in the form node_name => weight. -function worldeditadditions.parse_weighted_nodes(parts) +function worldeditadditions.parse_weighted_nodes(parts, as_list) + if as_list == nil then as_list = false end local MODE_EITHER = 1 local MODE_NODE = 2 @@ -177,23 +179,31 @@ function worldeditadditions.parse_weighted_nodes(parts) print("mode: either"); local chance = tonumber(part) if not chance then + print("not a chance, trying a node name") local node_name = worldedit.normalize_nodename(part) if not node_name then return false, "Error: Invalid number '"..chance.."'" end if last_node_name then - result[last_node_name] = 1 + if as_list then table.insert(result, { node = last_node_name, weight = 1 }) + else result[last_node_name] = 1 end end last_node_name = node_name mode = MODE_EITHER else - result[last_node_name] = math.floor(chance) + print("it's a chance: ", chance, "for", last_node_name) + chance = math.floor(chance) + if as_list then table.insert(result, { node = last_node_name, weight = chance }) + else result[last_node_name] = chance end + last_node_name = nil mode = MODE_NODE end end end if last_node_name then - result[last_node_name] = 1 + print("caught trailing node name: ", last_node_name) + if as_list then table.insert(result, { node = last_node_name, weight = 1 }) + else result[last_node_name] = 1 end end return true, result diff --git a/worldeditadditions_commands/commands/convolve.lua b/worldeditadditions_commands/commands/convolve.lua index 9f50076..29f35b5 100644 --- a/worldeditadditions_commands/commands/convolve.lua +++ b/worldeditadditions_commands/commands/convolve.lua @@ -69,7 +69,7 @@ worldedit.register_command("convolve", { minetest.log("action", name.." used //convolve at "..worldeditadditions.vector.tostring(worldedit.pos1[name]).." - "..worldeditadditions.vector.tostring(worldedit.pos2[name])..", adding "..stats.added.." nodes and removing "..stats.removed.." nodes in "..time_taken.."s") - return true, "Added "..stats.added.." and removed "..stats.removed.." nodes" + return true, "Added "..stats.added.." and removed "..stats.removed.." nodes in " .. time_taken .. "s" end }) diff --git a/worldeditadditions_commands/commands/layers.lua b/worldeditadditions_commands/commands/layers.lua new file mode 100644 index 0000000..fd8969a --- /dev/null +++ b/worldeditadditions_commands/commands/layers.lua @@ -0,0 +1,36 @@ +-- ██████ ██ ██ ███████ ██████ ██ █████ ██ ██ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ██ ██ ██ ██ █████ ██████ ██ ███████ ████ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██ +worldedit.register_command("layers", { + params = "[ []] [ []] ...", + 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", + privs = { worldedit = true }, + require_pos = 2, + parse = function(params_text) + if not params_text or params_text == "" then + params_text = "dirt_with_grass dirt 3" + end + + local success, node_list = worldeditadditions.parse_weighted_nodes( + worldeditadditions.split(params_text, "%s+", false), + true + ) + return success, node_list + end, + nodes_needed = function(name) + return worldedit.volume(worldedit.pos1[name], worldedit.pos2[name]) + end, + func = function(name, node_list) + local start_time = os.clock() + local changes = worldeditadditions.layers(worldedit.pos1[name], worldedit.pos2[name], node_list) + local time_taken = os.clock() - 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") + return true, changes.replaced .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped in " .. time_taken .. "s" + end +}) + +worldedit.alias_command("naturalise", "layers") +worldedit.alias_command("naturalize", "layers") diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index 3104fa3..53d880d 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -19,6 +19,7 @@ dofile(we_c.modpath.."/multi.lua") dofile(we_c.modpath.."/commands/floodfill.lua") dofile(we_c.modpath.."/commands/overlay.lua") +dofile(we_c.modpath.."/commands/layers.lua") dofile(we_c.modpath.."/commands/ellipsoid.lua") dofile(we_c.modpath.."/commands/torus.lua") dofile(we_c.modpath.."/commands/walls.lua")