diff --git a/worldeditadditions/lib/compat/saplingnames.lua b/worldeditadditions/lib/compat/saplingnames.lua index 7251da1..5898e42 100644 --- a/worldeditadditions/lib/compat/saplingnames.lua +++ b/worldeditadditions/lib/compat/saplingnames.lua @@ -25,14 +25,15 @@ if minetest.get_modpath("default") then { "default:emergent_jungle_sapling", "jungle_emergent" }, { "default:acacia_sapling", "acacia" }, { "default:acacia_bush_sapling", "acacia_bush" }, - { "default:blueberry_bush_sapling", "blueberry_bush" } + { "default:blueberry_bush_sapling", "blueberry_bush" }, + -- { "default:large_cactus_seedling", "cactus" } -- Can't be bonemealed yet, but I'd guess it will be implemented eventually. It's not in the sapling category, so we won't detect it for bonemealing anyway }) end if minetest.get_modpath("moretrees") then worldeditadditions.register_sapling_alias_many({ { "moretrees:spruce_sapling_ongen", "spruce" }, - { "moretrees:rubber_tree_sapling_ongen", "rubbe" }, + { "moretrees:rubber_tree_sapling_ongen", "rubber" }, { "moretrees:beech_sapling_ongen", "beech" }, { "moretrees:jungletree_sapling_ongen", "jungle_moretrees" }, { "moretrees:fir_sapling_ongen", "fir" }, @@ -41,7 +42,7 @@ if minetest.get_modpath("moretrees") then { "moretrees:poplar_small_sapling_ongen", "poplar_small" }, { "moretrees:apple_tree_sapling_ongen", "apple" }, { "moretrees:birch_sapling_ongen", "birch" }, - { "moretrees:palm_sapling_ongen", "palm" }, + { "moretrees:palm_sapling_ongen", "palm_moretrees" }, { "moretrees:date_palm_sapling_ongen", "palm_date" }, { "moretrees:sequoia_sapling_ongen", "sequoia" }, { "moretrees:oak_sapling_ongen", "oak_moretrees" }, @@ -57,7 +58,7 @@ end -- ██████ ██████ ██████ ███████ ███████ ██ ██ ██ ███████ ███████ ███████ if minetest.get_modpath("lemontree") then - worldeditadditions.register_sapling_alias("lemontree:sapling", "lemontree") + worldeditadditions.register_sapling_alias("lemontree:sapling", "lemon") end if minetest.get_modpath("pineapple") then worldeditadditions.register_sapling_alias("pineapple:sapling", "pineapple") @@ -72,10 +73,10 @@ if minetest.get_modpath("birch") then worldeditadditions.register_sapling_alias("birch:sapling", "birch") end if minetest.get_modpath("cherrytree") then - worldeditadditions.register_sapling_alias("cherrytree:sapling", "cherrytree") + worldeditadditions.register_sapling_alias("cherrytree:sapling", "cherry") end if minetest.get_modpath("clementinetree") then - worldeditadditions.register_sapling_alias("clementinetree:sapling", "clementinetree") + worldeditadditions.register_sapling_alias("clementinetree:sapling", "clementine") end if minetest.get_modpath("ebony") then worldeditadditions.register_sapling_alias("ebony:sapling", "ebony") @@ -102,5 +103,5 @@ if minetest.get_modpath("mahogany") then worldeditadditions.register_sapling_alias("mahogany:sapling", "mahogany") end if minetest.get_modpath("chestnuttree") then - worldeditadditions.register_sapling_alias("chestnuttree:sapling", "chestnuttree") + worldeditadditions.register_sapling_alias("chestnuttree:sapling", "chestnut") end diff --git a/worldeditadditions/lib/forest.lua b/worldeditadditions/lib/forest.lua index f7055e9..b9bd461 100644 --- a/worldeditadditions/lib/forest.lua +++ b/worldeditadditions/lib/forest.lua @@ -2,16 +2,23 @@ -- @module worldeditadditions.layers -function worldeditadditions.forest(pos1, pos2, sapling_weights) +function worldeditadditions.forest(pos1, pos2, density_multiplier, sapling_weights) pos1, pos2 = worldedit.sort_pos(pos1, pos2) + local weight_total = 0 + for _name,weight in pairs(sapling_weights) do + weight_total = weight_total + weight + end + + sapling_weights["air"] = math.ceil(weight_total * 100 * 1/density_multiplier) + -- This command requires the bonemeal mod to be installed -- We check here too because other mods might call this function directly and bypass the chat command system if not minetest.get_modpath("bonemeal") then return false, "Bonemeal mod not loaded" end - worldeditadditions.overlay(pos1, pos2, sapling_weights) + local overlay_stats = worldeditadditions.overlay(pos1, pos2, sapling_weights) -- Fetch the nodes in the specified area local manip, area = worldedit.manip_helpers.init(pos1, pos2) @@ -19,17 +26,20 @@ function worldeditadditions.forest(pos1, pos2, sapling_weights) local id_air = minetest.get_content_id("air") local group_cache = {} - local stats = { attempts = {}, failures = 0 } + local stats = { attempts = {}, failures = 0, placed = {} } for z = pos2.z, pos1.z, -1 do for x = pos2.x, pos1.x, -1 do for y = pos2.y, pos1.y, -1 do local i = area:index(x, y, z) - if not group_cache[data[i]] then - group_cache[data[i]] = worldeditadditions.is_sapling(data[i]) + local node_id = data[i] + if not group_cache[node_id] then + group_cache[node_id] = worldeditadditions.is_sapling(node_id) end - if group_cache[data[i]] then + if group_cache[node_id] then local did_grow = false + local new_id_at_pos + local new_name_at_pos for i=1,100 do bonemeal:on_use( { x = x, y = y, z = z }, @@ -37,17 +47,26 @@ function worldeditadditions.forest(pos1, pos2, sapling_weights) nil ) - local new_id_at_pos = minetest.get_content_id(minetest.get_node({ z = z, y = y, x = x }).name) + new_name_at_pos = minetest.get_node({ z = z, y = y, x = x }).name + new_id_at_pos = minetest.get_content_id(new_name_at_pos) if not group_cache[new_id_at_pos] then group_cache[new_id_at_pos] = worldeditadditions.is_sapling(new_id_at_pos) end if not group_cache[new_id_at_pos] then did_grow = true + -- Log the number of attempts it took to grow table.insert(stats.attempts, i) + -- Update the running total of saplings that grew + if not stats.placed[node_id] then + stats.placed[node_id] = 0 + end + stats.placed[node_id] = stats.placed[node_id] + 1 + print("incrementing id", node_id, "to", stats.placed[node_id]) break end end if not did_grow then + print("[//forest] Failed to grow sapling, detected node id", new_id_at_pos, "name", new_name_at_pos, "was originally", minetest.get_name_from_content_id(node_id)) -- We can't set it to air here because then when we save back we would overwrite all the newly grown trees stats.failures = stats.failures + 1 end @@ -69,14 +88,16 @@ function worldeditadditions.forest(pos1, pos2, sapling_weights) end if group_cache[data[i]] then - data[i] = air + data[i] = id_air end end end end + stats.successes = #stats.attempts + stats.attempts_avg = worldeditadditions.average(stats.attempts) -- Save the modified nodes back to disk & return - worldedit.manip_helpers.finish(manip, data) + --worldedit.manip_helpers.finish(manip, data) return true, stats end diff --git a/worldeditadditions/lib/overlay.lua b/worldeditadditions/lib/overlay.lua index 9730616..12a35f5 100644 --- a/worldeditadditions/lib/overlay.lua +++ b/worldeditadditions/lib/overlay.lua @@ -18,7 +18,7 @@ function worldeditadditions.overlay(pos1, pos2, node_weights) -- z y x is the preferred loop order, but that isn't really possible here - local changes = { updated = 0, skipped_columns = 0 } + local changes = { updated = 0, skipped_columns = 0, placed = {} } for z = pos2.z, pos1.z, -1 do for x = pos2.x, pos1.x, -1 do local found_air = false @@ -28,10 +28,8 @@ function worldeditadditions.overlay(pos1, pos2, node_weights) local i = area:index(x, y, z) 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") - end + -- wielded_light is now handled by worldeditadditions.is_airlike + local is_ignore = data[i] == node_id_ignore if not is_air and not is_ignore then @@ -42,6 +40,10 @@ function worldeditadditions.overlay(pos1, pos2, node_weights) data[area:index(x, y + 1, z)] = new_id changes.updated = changes.updated + 1 placed_node = true + if not changes.placed[new_id] then + changes.placed[new_id] = 0 + end + changes.placed[new_id] = changes.placed[new_id] + 1 break -- Move on to the next column end elseif not is_ignore then diff --git a/worldeditadditions/utils/node_identification.lua b/worldeditadditions/utils/node_identification.lua index 992c200..349f594 100644 --- a/worldeditadditions/utils/node_identification.lua +++ b/worldeditadditions/utils/node_identification.lua @@ -57,13 +57,13 @@ end -- @param id number The content/node id to check. -- @return bool Whther the given node/content id is a sapxling or not. function worldeditadditions.is_sapling(id) - local node_name = minetest.get_name_from_content_id(data[i]) + local node_name = minetest.get_name_from_content_id(id) return minetest.get_item_group(node_name, "sapling") ~= 0 end local sapling_aliases = {} function worldeditadditions.register_sapling_alias(sapling_node_name, alias) - if sapling_aliases[sapling_node_name] then + if sapling_aliases[sapling_node_name] ~= nil then return false, "Error: An alias against the node name '"..sapling_node_name.."' already exists." end sapling_aliases[alias] = sapling_node_name diff --git a/worldeditadditions/utils/nodes.lua b/worldeditadditions/utils/nodes.lua index 81ad1a5..f3090f2 100644 --- a/worldeditadditions/utils/nodes.lua +++ b/worldeditadditions/utils/nodes.lua @@ -4,7 +4,7 @@ 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) + -- print("[make_weighted] seen "..node_name.." @ weight "..weight.." → id "..next_id) for i = 1, weight do table.insert(result, next_id) end @@ -37,3 +37,12 @@ function worldeditadditions.registered_nodes_by_group(group) end return result end + +--- Turns a node_name → weight table into a list of { node_name, weight } tables. +function worldeditadditions.weighted_to_list(node_weights) + local result = {} + for node_name, weight in pairs(node_weights) do + table.insert(result, { node_name, weight }) + end + return result +end diff --git a/worldeditadditions/utils/strings.lua b/worldeditadditions/utils/strings.lua index 074e429..bd43f3c 100644 --- a/worldeditadditions/utils/strings.lua +++ b/worldeditadditions/utils/strings.lua @@ -168,9 +168,9 @@ function worldeditadditions.parse_weighted_nodes(parts, as_list, func_normalise) local mode = MODE_NODE local last_node_name = nil for i, part in ipairs(parts) do - print("i: "..i..", part: "..part) + -- print("i: "..i..", part: "..part) if mode == MODE_NODE then - print("mode: node"); + -- print("mode: node"); local next if not func_normalise then next = worldedit.normalize_nodename(part) else next = func_normalise(part) end @@ -180,10 +180,10 @@ function worldeditadditions.parse_weighted_nodes(parts, as_list, func_normalise) last_node_name = next mode = MODE_EITHER elseif mode == MODE_EITHER then - print("mode: either"); + -- print("mode: either"); local chance = tonumber(part) if not chance then - print("not a chance, trying a node name") + -- print("not a chance, trying a node name") local node_name if not func_normalise then node_name = worldedit.normalize_nodename(part) else node_name = func_normalise(part) end @@ -198,7 +198,7 @@ function worldeditadditions.parse_weighted_nodes(parts, as_list, func_normalise) last_node_name = node_name mode = MODE_EITHER else - print("it's a chance: ", chance, "for", last_node_name) + -- 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 @@ -208,7 +208,7 @@ function worldeditadditions.parse_weighted_nodes(parts, as_list, func_normalise) end end if last_node_name then - print("caught trailing node name: ", last_node_name) + -- 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 diff --git a/worldeditadditions_commands/commands/forest.lua b/worldeditadditions_commands/commands/forest.lua index 7d00eb7..d4be63d 100644 --- a/worldeditadditions_commands/commands/forest.lua +++ b/worldeditadditions_commands/commands/forest.lua @@ -4,11 +4,18 @@ -- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██ worldedit.register_command("forest", { - params = " [] [] [ []] ...", - description = "Plants and grows trees in the defined region according to the given list of sapling names and chances. Saplings are planted using //overlay - so the chances a 1-in-N change of actually planting a sapling at each candidate location. Saplings that fail to grow are subsequently removed (this will affect pre-existing saplings too)", + params = "[] [] [] [ []] ...", + description = "Plants and grows trees in the defined region according to the given list of sapling names and chances and density factor. The density controls the relative density of the resulting forest, and defaults to 1 (floating-point numbers allowed). Higher chance numbers result in a lower relative chance with respect to other saplings in the list. Saplings that fail to grow are subsequently removed (this will affect pre-existing saplings too).", privs = { worldedit = true }, require_pos = 2, parse = function(params_text) + local density = 1 + local match_start = params_text:match("^[%d.]+%s+") + if match_start then + density = tonumber(match_start) + params_text = params_text:sub(#match_start + 1) -- everything starts at 1 in Lua :-/ + end + local success, sapling_list = worldeditadditions.parse_weighted_nodes( worldeditadditions.split(params_text, "%s+", false), false, @@ -18,19 +25,28 @@ worldedit.register_command("forest", { ) end ) - return success, sapling_list + return success, density, sapling_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, sapling_list) + func = function(name, density, sapling_list) local start_time = worldeditadditions.get_ms_time() - local changes = worldeditadditions.forest(worldedit.pos1[name], worldedit.pos2[name], sapling_list) - local time_taken = worldeditadditions.get_ms_time() - start_time + local success, stats = worldeditadditions.forest( + worldedit.pos1[name], worldedit.pos2[name], + density, + sapling_list + ) + if not success then return success, stats end + local time_taken = worldeditadditions.human_time(worldeditadditions.get_ms_time() - start_time) - minetest.log("action", name .. " used //forest at " .. worldeditadditions.vector.tostring(worldedit.pos1[name]) .. ", replacing " .. changes.updated .. " nodes and skipping " .. changes.skipped_columns .. " columns in " .. time_taken .. "s") - return true, changes.updated .. " nodes replaced and " .. changes.skipped_columns .. " columns skipped in " .. worldeditadditions.human_time(time_taken) + local distribution_display = worldeditadditions.make_ascii_table( + worldeditadditions.node_distribution_to_list(stats.placed, stats.successes) + ) + + minetest.log("action", name.." used //forest at "..worldeditadditions.vector.tostring(worldedit.pos1[name]).." - "..worldeditadditions.vector.tostring(worldedit.pos2[name])..", "..stats.successes.." trees placed, averaging "..stats.attempts_avg.." growth attempts / tree and "..stats.failures.." failed attempts in "..time_taken) + return true, distribution_display.."\n=========================\n"..stats.successes.." trees placed, averaging "..stats.attempts_avg.." growth attempts / tree and "..stats.failures.." failed attempts in "..time_taken end }) diff --git a/worldeditadditions_commands/commands/saplingaliases.lua b/worldeditadditions_commands/commands/saplingaliases.lua index e443ff1..7fd1b11 100644 --- a/worldeditadditions_commands/commands/saplingaliases.lua +++ b/worldeditadditions_commands/commands/saplingaliases.lua @@ -17,6 +17,7 @@ minetest.register_chatcommand("/saplingaliases", { for node_name, alias in pairs(aliases) do table.insert(display, { node_name, alias }) end + table.sort(display, function(a, b) return a[2] < b[2] end) table.insert(msg, worldeditadditions.make_ascii_table(display)) elseif params_text == "all_saplings" then local results = worldeditadditions.registered_nodes_by_group("sapling")