diff --git a/worldeditadditions/init.lua b/worldeditadditions/init.lua index e96fa9f..0c27906 100644 --- a/worldeditadditions/init.lua +++ b/worldeditadditions/init.lua @@ -11,10 +11,13 @@ dofile(worldeditadditions.modpath.."/utils/vector.lua") dofile(worldeditadditions.modpath.."/utils/strings.lua") dofile(worldeditadditions.modpath.."/utils/numbers.lua") dofile(worldeditadditions.modpath.."/utils/nodes.lua") +dofile(worldeditadditions.modpath.."/utils/node_identification.lua") dofile(worldeditadditions.modpath.."/utils/tables.lua") dofile(worldeditadditions.modpath.."/utils/terrain.lua") dofile(worldeditadditions.modpath.."/utils/raycast_adv.lua") -- For the farwand +dofile(worldeditadditions.modpath.."/lib/compat/saplingnames.lua") + dofile(worldeditadditions.modpath.."/lib/floodfill.lua") dofile(worldeditadditions.modpath.."/lib/overlay.lua") dofile(worldeditadditions.modpath.."/lib/layers.lua") @@ -31,3 +34,4 @@ dofile(worldeditadditions.modpath.."/lib/erode/erode.lua") dofile(worldeditadditions.modpath.."/lib/count.lua") dofile(worldeditadditions.modpath.."/lib/bonemeal.lua") +dofile(worldeditadditions.modpath.."/lib/forest.lua") diff --git a/worldeditadditions/lib/compat/saplingnames.lua b/worldeditadditions/lib/compat/saplingnames.lua new file mode 100644 index 0000000..7251da1 --- /dev/null +++ b/worldeditadditions/lib/compat/saplingnames.lua @@ -0,0 +1,106 @@ +--[[ +This file contains sapling alias definitions for a number of different mods. +If your mod is not listed here, please open a pull request to add it :-) + +This mod's repository can be found here: https://github.com/sbrl/Minetest-WorldEditAdditions + +Adding support for your mod is a 2 step process: + +1. Update this file with your definitions +2. Update depends.txt to add a soft dependency on your mod. Find that file here: https://github.com/sbrl/Minetest-WorldEditAdditions/blob/master/worldeditadditions/depends.txt - for example, add something line "my_awesome_mod?" (note the question mark at the end) on a new line(without quotes). + +Alternatively, you can register support in your mod directly. Do that by adding a soft dependency on worldeditadditions, and then calling worldeditadditions.normalise_saplingname if worldeditadditions is loaded. +]]-- + +-- worldeditadditions.register_sapling_alias("") + +if minetest.get_modpath("default") then + worldeditadditions.register_sapling_alias_many({ + { "default:sapling", "oak" }, + { "default:bush_sapling", "bush" }, + { "default:pine_sapling", "pine" }, + { "default:pine_bush_sapling", "pine_bush" }, + { "default:aspen_sapling", "aspen" }, + { "default:junglesapling", "jungle" }, + { "default:emergent_jungle_sapling", "jungle_emergent" }, + { "default:acacia_sapling", "acacia" }, + { "default:acacia_bush_sapling", "acacia_bush" }, + { "default:blueberry_bush_sapling", "blueberry_bush" } + }) +end + +if minetest.get_modpath("moretrees") then + worldeditadditions.register_sapling_alias_many({ + { "moretrees:spruce_sapling_ongen", "spruce" }, + { "moretrees:rubber_tree_sapling_ongen", "rubbe" }, + { "moretrees:beech_sapling_ongen", "beech" }, + { "moretrees:jungletree_sapling_ongen", "jungle_moretrees" }, + { "moretrees:fir_sapling_ongen", "fir" }, + { "moretrees:willow_sapling_ongen", "willow" }, + { "moretrees:poplar_sapling_ongen", "poplar" }, + { "moretrees:poplar_small_sapling_ongen", "poplar_small" }, + { "moretrees:apple_tree_sapling_ongen", "apple" }, + { "moretrees:birch_sapling_ongen", "birch" }, + { "moretrees:palm_sapling_ongen", "palm" }, + { "moretrees:date_palm_sapling_ongen", "palm_date" }, + { "moretrees:sequoia_sapling_ongen", "sequoia" }, + { "moretrees:oak_sapling_ongen", "oak_moretrees" }, + { "moretrees:cedar_sapling_ongen", "cedar" } + }) +end + + +-- ██████ ██████ ██████ ██ ████████ ██████ ███████ ███████ ███████ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ██ ██ ██ ██ ██ ██ ██ ██████ █████ █████ ███████ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ██████ ██████ ██████ ███████ ███████ ██ ██ ██ ███████ ███████ ███████ + +if minetest.get_modpath("lemontree") then + worldeditadditions.register_sapling_alias("lemontree:sapling", "lemontree") +end +if minetest.get_modpath("pineapple") then + worldeditadditions.register_sapling_alias("pineapple:sapling", "pineapple") +end +if minetest.get_modpath("baldcypress") then + worldeditadditions.register_sapling_alias("baldcypress:sapling", "baldcypress") +end +if minetest.get_modpath("bamboo") then + worldeditadditions.register_sapling_alias("bamboo:sapling", "bamboo") +end +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") +end +if minetest.get_modpath("clementinetree") then + worldeditadditions.register_sapling_alias("clementinetree:sapling", "clementinetree") +end +if minetest.get_modpath("ebony") then + worldeditadditions.register_sapling_alias("ebony:sapling", "ebony") +end +if minetest.get_modpath("jacaranda") then + worldeditadditions.register_sapling_alias("jacaranda:sapling", "jacaranda") +end +if minetest.get_modpath("larch") then + worldeditadditions.register_sapling_alias("larch:sapling", "larch") +end +if minetest.get_modpath("maple") then + worldeditadditions.register_sapling_alias("maple:sapling", "maple") +end +if minetest.get_modpath("palm") then + worldeditadditions.register_sapling_alias("palm:sapling", "palm") +end +if minetest.get_modpath("pomegranate") then + worldeditadditions.register_sapling_alias("pomegranate:sapling", "pomegranate") +end +if minetest.get_modpath("willow") then + worldeditadditions.register_sapling_alias("willow:sapling", "willow") +end +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") +end diff --git a/worldeditadditions/utils/node_identification.lua b/worldeditadditions/utils/node_identification.lua new file mode 100644 index 0000000..992c200 --- /dev/null +++ b/worldeditadditions/utils/node_identification.lua @@ -0,0 +1,94 @@ +local node_id_air = minetest.get_content_id("air") +local node_id_ignore = minetest.get_content_id("ignore") + +--- Determines whether the given node/content id is an airlike node or not. +-- It is recommended that the result of this function be cached. +-- @param id number The content/node id to check. +-- @return bool Whether the given node/content id is an airlike node or not. +function worldeditadditions.is_airlike(id) + -- Do a fast check against air and ignore + if id == node_id_air then + return true + elseif id == node_id_ignore then -- ignore = not loaded yet IIRC (so it could be anything) + return false + end + + -- If the node isn't registered, then it might not be an air node + if not minetest.registered_nodes[id] then return false end + if minetest.registered_nodes[id].sunlight_propagates == true then + return true + end + -- Check for membership of the airlike group + local name = minetest.get_name_from_content_id(id) + local airlike_value = minetest.get_item_group(name, "airlike") + if airlike_value ~= nil and airlike_value > 0 then + 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 + +--- Determines whether the given node/content id is a liquid-ish node or not. +-- It is recommended that the result of this function be cached. +-- @param id number The content/node id to check. +-- @return bool Whether the given node/content id is a liquid-ish node or not. +function worldeditadditions.is_liquidlike(id) + -- print("[is_liquidlike]") + if id == node_id_ignore then return false end + + local node_name = minetest.get_name_from_content_id(id) + if node_name == nil or not minetest.registered_nodes[node_name] then return false end + + local liquidtype = minetest.registered_nodes[node_name].liquidtype + -- print("[is_liquidlike]", "id", id, "liquidtype", liquidtype) + + if liquidtype == nil or liquidtype == "none" then return false end + -- If it's not none, then it has to be a liquid as the only other values are source and flowing + return true +end + +--- Determines whether the given node/content id is a sapling or not. +-- Nodes with the "sapling" group are considered saplings. +-- It is recommended that the result of this function be cached. +-- @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]) + 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 + return false, "Error: An alias against the node name '"..sapling_node_name.."' already exists." + end + sapling_aliases[alias] = sapling_node_name + return true +end +function worldeditadditions.register_sapling_alias_many(tbl) + for i, next in ipairs(tbl) do + local success, msg = worldeditadditions.register_sapling_alias( + next[1], + next[2] + ) + if not success then return success, msg end + end +end +--- Returns the current key ⇒ value table of sapling names and aliases. +-- @return table +function worldeditadditions.get_all_sapling_aliases() + return sapling_aliases +end + +--- Attempts to normalise a sapling name using the currently registered aliases. +-- @param in_name string The sapling name to normalise +-- @param return_nil_on_failure bool Whether to return nil if we fail to resolve the sapling name with an alias, or return the original node name instead (default: false). +function worldeditadditions.normalise_saplingname(in_name, return_nil_on_failure) + if sapling_aliases[in_name] then return sapling_aliases[in_name] + elseif return_nil_on_failure then return nil + else return in_name end +end diff --git a/worldeditadditions/utils/nodes.lua b/worldeditadditions/utils/nodes.lua index 33c8b29..81ad1a5 100644 --- a/worldeditadditions/utils/nodes.lua +++ b/worldeditadditions/utils/nodes.lua @@ -28,65 +28,12 @@ function worldeditadditions.unwind_node_list(list) return result, #result end -local node_id_air = minetest.get_content_id("air") -local node_id_ignore = minetest.get_content_id("ignore") - ---- Determines whether the given node/content id is an airlike node or not. --- It is recommended that the result of this function be cached. --- @param id number The content/node id to check. --- @return bool Whether the given node/content id is an airlike node or not. -function worldeditadditions.is_airlike(id) - -- Do a fast check against air and ignore - if id == node_id_air then - return true - elseif id == node_id_ignore then -- ignore = not loaded yet IIRC (so it could be anything) - return false +function worldeditadditions.registered_nodes_by_group(group) + local result = {} + for name, def in pairs(minetest.registered_nodes) do + if def.groups[group] then + result[#result+1] = name + end end - - -- If the node isn't registered, then it might not be an air node - if not minetest.registered_nodes[id] then return false end - if minetest.registered_nodes[id].sunlight_propagates == true then - return true - end - -- Check for membership of the airlike group - local name = minetest.get_name_from_content_id(id) - local airlike_value = minetest.get_item_group(name, "airlike") - if airlike_value ~= nil and airlike_value > 0 then - 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 - ---- Determines whether the given node/content id is a liquid-ish node or not. --- It is recommended that the result of this function be cached. --- @param id number The content/node id to check. --- @return bool Whether the given node/content id is a liquid-ish node or not. -function worldeditadditions.is_liquidlike(id) - -- print("[is_liquidlike]") - if id == node_id_ignore then return false end - - local node_name = minetest.get_name_from_content_id(id) - if node_name == nil or not minetest.registered_nodes[node_name] then return false end - - local liquidtype = minetest.registered_nodes[node_name].liquidtype - -- print("[is_liquidlike]", "id", id, "liquidtype", liquidtype) - - if liquidtype == nil or liquidtype == "none" then return false end - -- If it's not none, then it has to be a liquid as the only other values are source and flowing - return true -end - ---- Determines whether the given node/content id is a sapling or not. --- Nodes with the "sapling" group are considered saplings. --- It is recommended that the result of this function be cached. --- @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]) - return minetest.get_item_group(node_name, "sapling") ~= 0 + return result end diff --git a/worldeditadditions/utils/strings.lua b/worldeditadditions/utils/strings.lua index 1b2f805..074e429 100644 --- a/worldeditadditions/utils/strings.lua +++ b/worldeditadditions/utils/strings.lua @@ -157,8 +157,9 @@ 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. +-- @param func_normalise callable If specified, the given function will be used to normalise node names instead of worldedit.normalize_nodename. A single argument is passed containing the un-normalised node name, and the return value is assumed to be the normalised node name. -- @returns table A table in the form node_name => weight. -function worldeditadditions.parse_weighted_nodes(parts, as_list) +function worldeditadditions.parse_weighted_nodes(parts, as_list, func_normalise) if as_list == nil then as_list = false end local MODE_EITHER = 1 local MODE_NODE = 2 @@ -170,7 +171,9 @@ function worldeditadditions.parse_weighted_nodes(parts, as_list) print("i: "..i..", part: "..part) if mode == MODE_NODE then print("mode: node"); - local next = worldedit.normalize_nodename(part) + local next + if not func_normalise then next = worldedit.normalize_nodename(part) + else next = func_normalise(part) end if not next then return false, "Error: Invalid node name '"..part.."'" end @@ -181,7 +184,10 @@ function worldeditadditions.parse_weighted_nodes(parts, as_list) local chance = tonumber(part) if not chance then print("not a chance, trying a node name") - local node_name = worldedit.normalize_nodename(part) + local node_name + if not func_normalise then node_name = worldedit.normalize_nodename(part) + else node_name = func_normalise(part) end + if not node_name then return false, "Error: Invalid number '"..chance.."'" end diff --git a/worldeditadditions_commands/commands/forest.lua b/worldeditadditions_commands/commands/forest.lua new file mode 100644 index 0000000..7d00eb7 --- /dev/null +++ b/worldeditadditions_commands/commands/forest.lua @@ -0,0 +1,36 @@ +-- ██████ ██ ██ ███████ ██████ ██ █████ ██ ██ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ██ ██ ██ ██ █████ ██████ ██ ███████ ████ +-- ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +-- ██████ ████ ███████ ██ ██ ███████ ██ ██ ██ +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)", + privs = { worldedit = true }, + require_pos = 2, + parse = function(params_text) + local success, sapling_list = worldeditadditions.parse_weighted_nodes( + worldeditadditions.split(params_text, "%s+", false), + false, + function(name) + return worldedit.normalize_nodename( + worldeditadditions.normalise_saplingname(name) + ) + end + ) + return success, 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) + 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 + + 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) + end +}) diff --git a/worldeditadditions_commands/commands/saplingaliases.lua b/worldeditadditions_commands/commands/saplingaliases.lua new file mode 100644 index 0000000..e443ff1 --- /dev/null +++ b/worldeditadditions_commands/commands/saplingaliases.lua @@ -0,0 +1,34 @@ +minetest.register_chatcommand("/saplingaliases", { + params = "[aliases|all_saplings]", + 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\")", + privs = { worldedit = true }, + func = function(name, params_text) + if name == nil then return end + if params_text == "" or not params_text then + params_text = "aliases" + end + + local msg = {} + + if params_text == "aliases" then + table.insert(msg, "Currently registered aliases:\n") + local aliases = worldeditadditions.get_all_sapling_aliases() + local display = {} + for node_name, alias in pairs(aliases) do + table.insert(display, { node_name, alias }) + end + table.insert(msg, worldeditadditions.make_ascii_table(display)) + elseif params_text == "all_saplings" then + local results = worldeditadditions.registered_nodes_by_group("sapling") + table.insert(msg, "Sapling-like nodes:\n") + local str = table.concat(results, "\n") + print(str) + table.insert(msg, str) + else + table.insert(msg, "Unknown mode '") + table.insert(msg, params_text) + table.insert(msg, "' (valid modes: aliases, all_saplings).") + end + worldedit.player_notify(name, table.concat(msg)) + end +}) diff --git a/worldeditadditions_commands/init.lua b/worldeditadditions_commands/init.lua index 0c7d879..1c08469 100644 --- a/worldeditadditions_commands/init.lua +++ b/worldeditadditions_commands/init.lua @@ -34,6 +34,7 @@ dofile(we_c.modpath.."/commands/convolve.lua") dofile(we_c.modpath.."/commands/erode.lua") dofile(we_c.modpath.."/commands/count.lua") +dofile(we_c.modpath.."/commands/forestaliases.lua") dofile(we_c.modpath.."/commands/subdivide.lua")