From e26d5b1580aa2375796d0c77809718c5a050e718 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Sun, 7 Jun 2020 20:46:46 +0100 Subject: [PATCH] Upgrade //overlay to support a mix of nodes --- README.md | 12 +++-- worldeditadditions/init.lua | 1 + worldeditadditions/lib/overlay.lua | 22 +++++++-- worldeditadditions/lib/replacemix.lua | 12 +---- worldeditadditions/utils/nodes.lua | 16 ++++++ worldeditadditions/utils/strings.lua | 49 +++++++++++++++++++ .../commands/overlay.lua | 17 +++---- 7 files changed, 102 insertions(+), 27 deletions(-) create mode 100644 worldeditadditions/utils/nodes.lua diff --git a/README.md b/README.md index 25ac2b4..7e0082c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ If you can dream of it, it probably belongs here! ## Quick Command Reference - [`//floodfill [ []]`](#floodfill-replace_node-radius-floodfill) - - [`//overlay `](#overlay-node_name) + - [`//overlay [] [] [ []] ...`](#overlay-node_name) - [`//ellipsoid `](#ellipsoid-rx-ry-rz-node_name) - [`//hollowellipsoid `](#hollowellipsoid-rx-ry-rz-node_name) - [`//torus `](#torus-major_radius-minor_radius-node_name) @@ -35,19 +35,25 @@ Floods all connected nodes of the same type starting at _pos1_ with ` -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 . +### `//overlay [] [] [ []] ...` +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 node names and chances, as `//mix` (WorldEdit) and `//replacemix` (WorldEditAdditions) does. Will also work in caves, as it scans columns of nodes from top to bottom, skipping every non-air node until it finds one - and only then will it start searching for a node to place the target node on top of. Note that all-air columns are skipped - so if you experience issues with it not overlaying correctly, try `//expand down 1` to add an extra node's space to your defined region. +Note also that columns without any air nodes in them at all are also skipped, so try `//expand y 1` to add an extra layer to your defined region. + ``` //overlay grass //overlay glass //overlay grass_with_dirt +//overlay grass_with_dirt 10 dirt +//overlay grass_with_dirt 10 dirt 2 sand 1 +//overlay sandstone dirt 2 sand 5 ``` + ### `//ellipsoid ` Creates a solid ellipsoid at position 1 with the radius `(rx, ry, rz)`. diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index 5ca38c1..98932be 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -9,6 +9,7 @@ worldeditadditions = {} worldeditadditions.modpath = minetest.get_modpath("worldeditadditions") dofile(worldeditadditions.modpath.."/utils/strings.lua") dofile(worldeditadditions.modpath.."/utils/numbers.lua") +dofile(worldeditadditions.modpath.."/utils/nodes.lua") dofile(worldeditadditions.modpath.."/utils.lua") dofile(worldeditadditions.modpath.."/lib/floodfill.lua") diff --git a/worldeditadditions/lib/overlay.lua b/worldeditadditions/lib/overlay.lua index 9c77348..ff34311 100644 --- a/worldeditadditions/lib/overlay.lua +++ b/worldeditadditions/lib/overlay.lua @@ -1,7 +1,7 @@ --- Overlap command. Places a specified node on top of each column. -- @module worldeditadditions.overlay -function worldeditadditions.overlay(pos1, pos2, target_node) +function worldeditadditions.overlay(pos1, pos2, node_weights) pos1, pos2 = worldedit.sort_pos(pos1, pos2) -- pos2 will always have the highest co-ordinates now @@ -10,7 +10,9 @@ function worldeditadditions.overlay(pos1, pos2, target_node) local data = manip:get_data() local node_id_air = minetest.get_content_id("air") - local node_id_target = minetest.get_content_id(target_node) + local node_id_ignore = minetest.get_content_id("ignore") + + local node_ids, node_ids_count = worldeditadditions.make_weighted(node_weights) -- minetest.log("action", "pos1: " .. worldeditadditions.vector.tostring(pos1)) -- minetest.log("action", "pos2: " .. worldeditadditions.vector.tostring(pos2)) @@ -24,16 +26,26 @@ function worldeditadditions.overlay(pos1, pos2, target_node) local placed_node = false for y = pos2.y, pos1.y, -1 do - if data[area:index(x, y, z)] ~= node_id_air then + local i = area:index(x, y, z) + + local is_air = data[i] == node_id_air + 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") + end + local is_ignore = data[i] == node_id_ignore + + if not is_air and not is_ignore then if found_air then + local new_id = node_ids[math.random(node_ids_count)] -- We've found an air block previously, so it must be above us -- Replace the node above us with the target block - data[area:index(x, y + 1, z)] = node_id_target + data[area:index(x, y + 1, z)] = new_id changes.updated = changes.updated + 1 placed_node = true break -- Move on to the next column end - else + elseif not is_ignore then found_air = true end end diff --git a/worldeditadditions/lib/replacemix.lua b/worldeditadditions/lib/replacemix.lua index 439494d..e8a556e 100644 --- a/worldeditadditions/lib/replacemix.lua +++ b/worldeditadditions/lib/replacemix.lua @@ -22,18 +22,10 @@ function worldeditadditions.replacemix(pos1, pos2, target_node, target_node_chan local distribution = {} -- Generate the list of node ids - local node_ids_replace = {} + local node_ids_replace, node_ids_replace_count = worldeditadditions.make_weighted(replacements) for node_name, weight in pairs(replacements) do - local next_id = minetest.get_content_id(node_name) - for i = 1, weight do - table.insert( - node_ids_replace, - next_id - ) - end - distribution[next_id] = 0 + distribution[minetest.get_content_id(node_name)] = 0 end - local node_ids_replace_count = #node_ids_replace for i in area:iterp(pos1, pos2) do diff --git a/worldeditadditions/utils/nodes.lua b/worldeditadditions/utils/nodes.lua new file mode 100644 index 0000000..8350d26 --- /dev/null +++ b/worldeditadditions/utils/nodes.lua @@ -0,0 +1,16 @@ +--- Makes an associative table of node_name => weight into a list of node ids. +-- Node names with a heigher weight are repeated more times. +function worldeditadditions.make_weighted(tbl) + local result = {} + for node_name, weight in pairs(tbl) do + 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 + ) + end + end + return result, #result +end diff --git a/worldeditadditions/utils/strings.lua b/worldeditadditions/utils/strings.lua index a57179c..844f45a 100644 --- a/worldeditadditions/utils/strings.lua +++ b/worldeditadditions/utils/strings.lua @@ -73,6 +73,10 @@ function worldeditadditions.str_padstart(str, len, char) return string.rep(char, len - #str) .. str end +function worldeditadditions.string_starts(str,start) + return string.sub(str,1,string.len(start))==start +end + --- Turns an associative node_id → count table into a human-readable list. -- Works well with worldeditadditions.make_ascii_table(). function worldeditadditions.node_distribution_to_list(distribution, nodes_total) @@ -122,3 +126,48 @@ function worldeditadditions.make_ascii_table(data, total) -- TODO: Add multi-column support here return table.concat(result, "\n") end + + + +function worldeditadditions.parse_weighted_nodes(parts) + local MODE_EITHER = 1 + local MODE_NODE = 2 + + local result = {} + local mode = MODE_NODE + local last_node_name = nil + for i, part in ipairs(parts) do + print("i: "..i..", part: "..part) + if mode == MODE_NODE then + print("mode: node"); + local next = worldedit.normalize_nodename(part) + if not next then + return false, "Error: Invalid node name '"..part.."'" + end + last_node_name = next + mode = MODE_EITHER + elseif mode == MODE_EITHER then + print("mode: either"); + local chance = tonumber(part) + if not chance then + 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 + end + last_node_name = node_name + mode = MODE_EITHER + else + result[last_node_name] = math.floor(chance) + mode = MODE_NODE + end + end + end + if last_node_name then + result[last_node_name] = 1 + end + + return true, result +end diff --git a/worldeditadditions_commands/commands/overlay.lua b/worldeditadditions_commands/commands/overlay.lua index c04afc7..23300e0 100644 --- a/worldeditadditions_commands/commands/overlay.lua +++ b/worldeditadditions_commands/commands/overlay.lua @@ -4,25 +4,24 @@ -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██ worldedit.register_command("overlay", { - params = "", - 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 .", + params = " [] [] [ []] ...", + 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.", privs = { worldedit = true }, require_pos = 2, parse = function(params_text) - local target_node = worldedit.normalize_nodename(params_text) - if not target_node then - return false, "Error: Invalid node name" - end - return true, target_node + local success, node_list = worldeditadditions.parse_weighted_nodes( + worldeditadditions.split(params_text, "%s+", false) + ) + return success, node_list end, nodes_needed = function(name) -- //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]) return (pos2.x - pos1.x) * (pos2.y - pos1.y) end, - func = function(name, target_node) + func = function(name, node_list) local start_time = os.clock() - local changes = worldeditadditions.overlay(worldedit.pos1[name], worldedit.pos2[name], target_node) + local changes = worldeditadditions.overlay(worldedit.pos1[name], worldedit.pos2[name], node_list) local time_taken = os.clock() - start_time minetest.log("action", name .. " used //overlay at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. changes.updated .. " nodes and skipping " .. changes.skipped_columns .. " columns in " .. time_taken .. "s")