From 2e79f1f0ed408557d3f8c1acbe775c3360ae818d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20=C3=85str=C3=B6m?= Date: Sat, 23 May 2020 19:37:02 +0200 Subject: [PATCH] Check if there is room for tree to grow Previously tree saplings would grow regardless of the blocks above it. This could be abused to destroy bedrock and other nodes. To solve this, checks are added for nodes around the trees. The volume checked depends on the tree type. The volume for each tree has been set so reject trees where branches would grow into other nodes. Some nodes types like torches, plants and wood are ignored in the checks. Saplings in a 2x2 formation will grow into normal trees if there is no room for a huge tree. Oak trees has separate checks for balloon oak trees and normal oak trees, if there is no room for a balloon oak, it becomes a normal oak tree. --- mods/ITEMS/mcl_core/functions.lua | 213 +++++++++++++++++++++++------- 1 file changed, 165 insertions(+), 48 deletions(-) diff --git a/mods/ITEMS/mcl_core/functions.lua b/mods/ITEMS/mcl_core/functions.lua index e8d7c2c2d3..8d73391d84 100644 --- a/mods/ITEMS/mcl_core/functions.lua +++ b/mods/ITEMS/mcl_core/functions.lua @@ -4,6 +4,13 @@ local mg_name = minetest.get_mapgen_setting("mg_name") +local OAK_TREE_ID = 1 +local DARK_OAK_TREE_ID = 2 +local SPRUCE_TREE_ID = 3 +local ACACIA_TREE_ID = 4 +local JUNGLE_TREE_ID = 5 +local BIRCH_TREE_ID = 6 + minetest.register_abm({ label = "Lava cooling", nodenames = {"group:lava"}, @@ -221,25 +228,122 @@ local function air_leaf(leaftype) end end -function mcl_core.generate_tree(pos, tree_type, two_by_two) +-- Check if a node stops a tree from growing. Torches, plants, wood, tree, +-- leaves and dirt does not affect tree growth. +function node_stops_growth(node) + if node.name == 'air' then + return false + end + + def = minetest.registered_nodes[node.name] + if not def then + return true + end + + groups = def.groups + if not groups then + return true + end + if groups.plant or groups.torch or groups.dirt or groups.tree + or groups.bark or groups.leaves or groups.wood then + return false + end + + return true +end + +-- Check if a tree can grow at position. The width is the width to check +-- around the tree. A width of 3 and height of 5 will check a 3x3 area, 5 +-- nodes above the sapling. If any walkable node other than dirt, wood or +-- leaves occurs in those blocks the tree cannot grow. +function check_growth_width(pos, width, height) + -- Huge tree (with even width to check) will check one more node in + -- positive x and y directions. + neg_space = math.min((width - 1) / 2) + pos_space = math.max((width - 1) / 2) + for x = -neg_space, pos_space do + for z = -neg_space, pos_space do + for y = 1, height do + local np = vector.new( + pos.x + x, + pos.y + y, + pos.z + z) + if node_stops_growth(minetest.get_node(np)) then + return false + end + end + end + end + return true +end + +-- Check if a tree with id can grow at a position. Options is a table of flags +-- for varieties of trees. The 'two_by_two' option is used to check if there is +-- room to generate huge trees for spruce and jungle. The 'balloon' option is +-- used to check if there is room to generate a balloon tree for oak. +function check_tree_growth(pos, tree_id, options) + local two_by_two = options and options.two_by_two + local balloon = options and options.balloon + + if tree_id == OAK_TREE_ID then + if balloon then + return check_growth_width(pos, 7, 11) + else + return check_growth_width(pos, 3, 5) + end + elseif tree_id == BIRCH_TREE_ID then + return check_growth_width(pos, 3, 6) + elseif tree_id == SPRUCE_TREE_ID then + if two_by_two then + return check_growth_width(pos, 6, 20) + else + return check_growth_width(pos, 5, 11) + end + elseif tree_id == JUNGLE_TREE_ID then + if two_by_two then + return check_growth_width(pos, 8, 23) + else + return check_growth_width(pos, 3, 8) + end + elseif tree_id == ACACIA_TREE_ID then + return check_growth_width(pos, 7, 8) + elseif tree_id == DARK_OAK_TREE_ID and two_by_two then + return check_growth_width(pos, 4, 7) + end + + return false +end + +-- Generates a tree with a type. Options is a table of flags for varieties of +-- trees. The 'two_by_two' option is used by jungle and spruce trees to +-- generate huge trees. The 'balloon' option is used by oak to generate a balloon +-- oak tree. +function mcl_core.generate_tree(pos, tree_type, options) pos.y = pos.y-1 local nodename = minetest.get_node(pos).name - + pos.y = pos.y+1 if not minetest.get_node_light(pos) then return end local node - if tree_type == nil or tree_type == 1 then + local two_by_two = options and options.two_by_two + local balloon = options and options.balloon + + if tree_type == nil or tree_type == OAK_TREE_ID then if mg_name == "v6" then mcl_core.generate_v6_oak_tree(pos) else - mcl_core.generate_oak_tree(pos) + if balloon then + mcl_core.generate_balloon_oak_tree(pos) + else + mcl_core.generate_oak_tree(pos) + end end - elseif tree_type == 2 and two_by_two then + elseif tree_type == DARK_OAK_TREE_ID then mcl_core.generate_dark_oak_tree(pos) - elseif tree_type == 3 then + elseif tree_type == SPRUCE_TREE_ID then if two_by_two then mcl_core.generate_huge_spruce_tree(pos) else @@ -249,9 +353,9 @@ function mcl_core.generate_tree(pos, tree_type, two_by_two) mcl_core.generate_spruce_tree(pos) end end - elseif tree_type == 4 then + elseif tree_type == ACACIA_TREE_ID then mcl_core.generate_acacia_tree(pos) - elseif tree_type == 5 then + elseif tree_type == JUNGLE_TREE_ID then if two_by_two then mcl_core.generate_huge_jungle_tree(pos) else @@ -261,7 +365,7 @@ function mcl_core.generate_tree(pos, tree_type, two_by_two) mcl_core.generate_jungle_tree(pos) end end - elseif tree_type == 6 then + elseif tree_type == BIRCH_TREE_ID then mcl_core.generate_birch_tree(pos) end end @@ -331,37 +435,36 @@ function mcl_core.generate_v6_oak_tree(pos) end end --- Oak -function mcl_core.generate_oak_tree(pos) - local r = math.random(1, 12) +-- Ballon Oak +function mcl_core.generate_balloon_oak_tree(pos) local path local offset - -- Balloon oak - if r == 1 then - local s = math.random(1, 12) - if s == 1 then - -- Small balloon oak - path = minetest.get_modpath("mcl_core") .. "/schematics/mcl_core_oak_balloon.mts" - offset = { x = -2, y = -1, z = -2 } - else - -- Large balloon oak - local t = math.random(1, 4) - path = minetest.get_modpath("mcl_core") .. "/schematics/mcl_core_oak_large_"..t..".mts" - if t == 1 or t == 3 then - offset = { x = -3, y = -1, z = -3 } - elseif t == 2 or t == 4 then - offset = { x = -4, y = -1, z = -4 } - end - end - -- Classic oak - else - path = minetest.get_modpath("mcl_core") .. "/schematics/mcl_core_oak_classic.mts" + local s = math.random(1, 12) + if s == 1 then + -- Small balloon oak + path = minetest.get_modpath("mcl_core") .. "/schematics/mcl_core_oak_balloon.mts" offset = { x = -2, y = -1, z = -2 } - + else + -- Large balloon oak + local t = math.random(1, 4) + path = minetest.get_modpath("mcl_core") .. "/schematics/mcl_core_oak_large_"..t..".mts" + if t == 1 or t == 3 then + offset = { x = -3, y = -1, z = -3 } + elseif t == 2 or t == 4 then + offset = { x = -4, y = -1, z = -4 } + end end minetest.place_schematic(vector.add(pos, offset), path, "random", nil, false) end +-- Oak +function mcl_core.generate_oak_tree(pos) + local path = minetest.get_modpath("mcl_core") .. "/schematics/mcl_core_oak_classic.mts" + local offset = { x = -2, y = -1, z = -2 } + + minetest.place_schematic(vector.add(pos, offset), path, "random", nil, false) +end + -- Birch function mcl_core.generate_birch_tree(pos) local path = minetest.get_modpath("mcl_core") .. @@ -833,41 +936,55 @@ local sapling_grow_action = function(tree_id, soil_needed, one_by_one, two_by_tw local s8 = is_sapling(p8, sapling) local s9 = is_sapling(p9, sapling) -- In a 9×9 field there are 4 possible 2×2 squares. We check them all. - if s2 and s3 and s4 then + if s2 and s3 and s4 and check_tree_growth(pos, tree_id, { two_by_two = true }) then -- Success: Remove saplings and place tree minetest.remove_node(pos) minetest.remove_node(p2) minetest.remove_node(p3) minetest.remove_node(p4) - mcl_core.generate_tree(pos, tree_id, true) + mcl_core.generate_tree(pos, tree_id, { two_by_two = true }) return - elseif s3 and s5 and s6 then + elseif s3 and s5 and s6 and check_tree_growth(p6, tree_id, { two_by_two = true }) then minetest.remove_node(pos) minetest.remove_node(p3) minetest.remove_node(p5) minetest.remove_node(p6) - mcl_core.generate_tree(p6, tree_id, true) + mcl_core.generate_tree(p6, tree_id, { two_by_two = true }) return - elseif s6 and s7 and s8 then + elseif s6 and s7 and s8 and check_tree_growth(p7, tree_id, { two_by_two = true }) then minetest.remove_node(pos) minetest.remove_node(p6) minetest.remove_node(p7) minetest.remove_node(p8) - mcl_core.generate_tree(p7, tree_id, true) + mcl_core.generate_tree(p7, tree_id, { two_by_two = true }) return - elseif s2 and s8 and s9 then + elseif s2 and s8 and s9 and check_tree_growth(p8, tree_id, { two_by_two = true }) then minetest.remove_node(pos) minetest.remove_node(p2) minetest.remove_node(p8) minetest.remove_node(p9) - mcl_core.generate_tree(p8, tree_id, true) + mcl_core.generate_tree(p8, tree_id, { two_by_two = true }) return end end + + if one_by_one and tree_id == OAK_TREE_ID then + -- There is a chance that this tree wants to grow as a balloon oak + if math.random(1, 12) == 1 then + -- Check if there is room for that + if check_tree_growth(pos, tree_id, { balloon = true }) then + minetest.set_node(pos, {name="air"}) + mcl_core.generate_tree(pos, tree_id, { balloon = true }) + return + end + end + end + -- If this sapling can grow alone - if one_by_one then + if one_by_one and check_tree_growth(pos, tree_id) then -- Single sapling minetest.set_node(pos, {name="air"}) + local r = math.random(1, 12) mcl_core.generate_tree(pos, tree_id) return end @@ -878,12 +995,12 @@ local sapling_grow_action = function(tree_id, soil_needed, one_by_one, two_by_tw end end -local grow_oak = sapling_grow_action(1, 1, true, false) -local grow_dark_oak = sapling_grow_action(2, 2, false, true, "mcl_core:darksapling") -local grow_jungle_tree = sapling_grow_action(5, 1, true, true, "mcl_core:junglesapling") -local grow_acacia = sapling_grow_action(4, 2, true, false) -local grow_spruce = sapling_grow_action(3, 1, true, true, "mcl_core:sprucesapling") -local grow_birch = sapling_grow_action(6, 1, true, false) +local grow_oak = sapling_grow_action(OAK_TREE_ID, 1, true, false) +local grow_dark_oak = sapling_grow_action(DARK_OAK_TREE_ID, 2, false, true, "mcl_core:darksapling") +local grow_jungle_tree = sapling_grow_action(JUNGLE_TREE_ID, 1, true, true, "mcl_core:junglesapling") +local grow_acacia = sapling_grow_action(ACACIA_TREE_ID, 2, true, false) +local grow_spruce = sapling_grow_action(SPRUCE_TREE_ID, 1, true, true, "mcl_core:sprucesapling") +local grow_birch = sapling_grow_action(BIRCH_TREE_ID, 1, true, false) -- Attempts to grow the sapling at the specified position -- pos: Position