From cc0fa41e3be55864914514a658a175d13c7fc588 Mon Sep 17 00:00:00 2001 From: Tangent Date: Sat, 12 Sep 2026 03:29:43 -0600 Subject: [PATCH] started to add treecapitator --- mods/treecapitator/.gitignore | 5 + mods/treecapitator/.luacheckrc | 15 + mods/treecapitator/LICENSE.txt | 3 + mods/treecapitator/README.md | 50 ++ mods/treecapitator/api.lua | 94 ++ mods/treecapitator/documentation/doc.md | 147 +++ mods/treecapitator/init.lua | 996 +++++++++++++++++++++ mods/treecapitator/mod.conf | 2 + mods/treecapitator/moretrees34.lua | 87 ++ mods/treecapitator/settingtypes.txt | 24 + mods/treecapitator/sounds/tree_falling.ogg | Bin 0 -> 32587 bytes mods/treecapitator/trees.lua | 468 ++++++++++ 12 files changed, 1891 insertions(+) create mode 100644 mods/treecapitator/.gitignore create mode 100644 mods/treecapitator/.luacheckrc create mode 100644 mods/treecapitator/LICENSE.txt create mode 100644 mods/treecapitator/README.md create mode 100644 mods/treecapitator/api.lua create mode 100644 mods/treecapitator/documentation/doc.md create mode 100644 mods/treecapitator/init.lua create mode 100644 mods/treecapitator/mod.conf create mode 100644 mods/treecapitator/moretrees34.lua create mode 100644 mods/treecapitator/settingtypes.txt create mode 100644 mods/treecapitator/sounds/tree_falling.ogg create mode 100644 mods/treecapitator/trees.lua diff --git a/mods/treecapitator/.gitignore b/mods/treecapitator/.gitignore new file mode 100644 index 0000000..b5913a4 --- /dev/null +++ b/mods/treecapitator/.gitignore @@ -0,0 +1,5 @@ +## Generic ignorable patterns and files +*~ +.*.swp +debug.txt +*.patch diff --git a/mods/treecapitator/.luacheckrc b/mods/treecapitator/.luacheckrc new file mode 100644 index 0000000..851754b --- /dev/null +++ b/mods/treecapitator/.luacheckrc @@ -0,0 +1,15 @@ +read_globals = { + "dump", + "vector", + "DIR_DELIM", + minetest = { + fields = { + check_for_falling = { + read_only = false + } + }, + other_fields = true + } +} +globals = {"treecapitator"} +ignore = {"421", "423"} diff --git a/mods/treecapitator/LICENSE.txt b/mods/treecapitator/LICENSE.txt new file mode 100644 index 0000000..4a4737b --- /dev/null +++ b/mods/treecapitator/LICENSE.txt @@ -0,0 +1,3 @@ +The code of this mod is licensed under MIT. +The sound from http://www.freesound.org/people/ecfike/sounds/139952/, +edited with audacity, is licensed under CC0. diff --git a/mods/treecapitator/README.md b/mods/treecapitator/README.md new file mode 100644 index 0000000..2726b4a --- /dev/null +++ b/mods/treecapitator/README.md @@ -0,0 +1,50 @@ +[Mod] treecapitator [treecapitator] + +This mod is makes digging a trunk node of a tree destroy the whole tree +including the leaves and the fruits. There are many settings, such as dropping +no leaves but only saplings (as leafdecay does it).
+Holding shift you can avoid its function to dig the single trunk node.
+Neighbour trees are detected and preserved.
+This mod is based on Jeja's timber mod, however, the code was revised over and +over, so it became a different mod (according to the +[broom theory](https://www.youtube.com/watch?v=51n-EBigXmg) that is).
+[There I got the name.](http://www.minecraftforum.net/topic/1009577-147-daftpvfs-mods-treecapitator-ingameinfo-crystalwing-startinginv-floatingruins/) + +**Depends:** see [depends.txt](https://raw.githubusercontent.com/HybridDog/treecapitator/master/depends.txt)
+**License:** see [LICENSE.txt](https://raw.githubusercontent.com/HybridDog/treecapitator/master/LICENSE.txt)
+**Download:** [zip](https://github.com/HybridDog/treecapitator/archive/master.zip), [tar.gz](https://github.com/HybridDog/treecapitator/archive/master.tar.gz) + +![I'm a screenshot!](https://forum.minetest.net/download/file.php?id=571)
+↑ you can only see the animation if your browser supports apng + +If you got ideas or found bugs, please tell them to me. + +[How to install a mod?](http://wiki.minetest.net/Installing_Mods) + + +[13.04.2015] Added in trees for moretrees, working out getting proper naming + for capitating to work. + +[14.03.2015] Added sound of a falling tree (taken from there + http://www.freesound.org/people/ecfike/sounds/139952/). + Made "drop_items" and "drop_leaf" both "true" by default. (mintpick) + +TODO: +* fix neighbour tree detection, wrong nodes become dug (test with vertical + trunk fruit), + pertains to non-v6 apple tree and jungle tree +* use range_up and range_down for other trees too for greater precision in + neighbour detection +* improve moretrees support (see issue #2) +* moretrees acacia +* more precise neighbour detection for moretrees (cedar), fix moretree + jungletree ignoring default jungletrees' leaves +* technic chainsaw +* Test if pine trees are different on v6 and non-v6 mapgen +* Add a priority and priority condition to allow handling cone pine trees + differently +* Get rid of the cutting_leaves arguments because the problem is fixed in + minetest +* consider neighbour tree's stem and not only head during neighbour detection +* add dfs head search (and a parameter to enable it) +* continue the documentation diff --git a/mods/treecapitator/api.lua b/mods/treecapitator/api.lua new file mode 100644 index 0000000..b0bff09 --- /dev/null +++ b/mods/treecapitator/api.lua @@ -0,0 +1,94 @@ +-- the table containing the tree definitions +treecapitator.trees = {} + +-- Warn if a tree definition passed to treecapitator.register_tree contains +-- invalid options, partly for backwards compatibility. +-- Not all options are checked. +local function check_tree_definition(tr) + if type(tr.trees) ~= "table" then + minetest.log("warning", + "[treecapitator] Invalid tree definition (trees field)") + end + if tr.type ~= "acacia" then + if type(tr.leaves) ~= "table" then + minetest.log("warning", + "[treecapitator] Invalid tree definition (leaves field)") + end + if type(tr.range) ~= "number" then + minetest.log("warning", + "[treecapitator] Invalid tree definition (range field)") + end + end +end + +-- For the usage of this function, see trees.lua. +local after_dig_wrap +local after_dig_nodes = {} +function treecapitator.register_tree(tree_definition) + check_tree_definition(tree_definition) + local tr = {} + for name, value in pairs(tree_definition) do + tr[name] = value + end + tr.fruits = tr.fruits or {} + tr.type = tr.type or "default" + treecapitator.trees[#treecapitator.trees+1] = tr + if treecapitator.after_register[tr.type] then + treecapitator.after_register[tr.type](tr) + end + + for i = 1,#tr.trees do + local nodename = tr.trees[i] + local data = minetest.registered_nodes[nodename] + if not data then + error(nodename .. " has to be registered before calling " .. + "treecapitator.register_tree.") + end + local func = after_dig_wrap + local prev_after_dig = data.after_dig_node + if prev_after_dig + and not after_dig_nodes[nodename] then + func = function(pos, oldnode, oldmetadata, digger) + prev_after_dig(pos, oldnode, oldmetadata, digger) + treecapitator.capitate_tree(pos, digger) + end + end + minetest.override_item(nodename, {after_dig_node = func}) + after_dig_nodes[nodename] = true + end +end + +function treecapitator.capitation_allowed() + return not treecapitator.capitation_usually_disallowed +end + + +-- Example of overriding this function +if treecapitator.no_hand_capitation then + -- disallow capitating trees if no proper tool is used + treecapitator.capitation_usually_disallowed = true + local allowed = treecapitator.capitation_allowed + function treecapitator.capitation_allowed(pos, digger) + local def = minetest.registered_nodes[ + minetest.get_node{x=pos.x, y=pos.y+1, z=pos.z}.name + ] + return def and def.groups and minetest.get_dig_params(def.groups, + digger:get_wielded_item():get_tool_capabilities()).wear > 0 + or allowed(pos, digger) + end +end + +-- test if trunk nodes were redefined +minetest.after(2, function() + for nodename in pairs(after_dig_nodes) do + if not minetest.registered_nodes[nodename].after_dig_node then + error(nodename .. " didn't keep after_dig_node.") + end + end + after_dig_nodes = nil +end) + +-- wrapping is necessary, someone may overwrite treecapitator.capitate_tree +function after_dig_wrap(pos, _,_, digger) + treecapitator.capitate_tree(pos, digger) +end diff --git a/mods/treecapitator/documentation/doc.md b/mods/treecapitator/documentation/doc.md new file mode 100644 index 0000000..2780a42 --- /dev/null +++ b/mods/treecapitator/documentation/doc.md @@ -0,0 +1,147 @@ +# Registering a Tree + +We need to register trees so that they are known to the treecapitator mod. +Since this mod detects neighbouring trees and should ignore user-placed trunks, +the we should define a tree as strictly as possible. + +I have grouped the tree definitions to different types because, for example, +an apple tree requires a different detection algorithm than a palm tree. + +## Default Tree + +This is probably the most common type of tree in minetest. + +```lua +treecapitator.register_tree({ + type = "default", + trees = {, , ...}, + leaves = {, , ...}, + range = , + range_up = , + range_down = , + fruits = {, , ...}, + trunk_fruit_vertical = , + cutting_leaves = , + stem_type = , + stem_height_min = , + requisite_leaves = {, , ...}, +}) +``` + +* Nodes the tree consists of + * `trees` + The trunk nodes of the straight stem. + Rotated trunks (param2 > 0) are usually ignored. + * `leaves` + Nodes of the tree head which represent leaves.
+ In comparison to `fruits`, these nodes are affected + by the drop_leaf setting. + * `fruits` + Like `leaves` but unaffected by the drop_leaf setting.
+ This list can also contain trunk nodes which are spread around + in the tree head. + * `trunk_fruit_vertical` + If set to true, a trunk node which is in `trees` and `fruits` is removed + even if it is not rotated (param2 > 0). + * `requisite_leaves` + If defined, abort capitating if one of the nodes in this list were not + found in the tree head.
+ We can use this if, for example, the tree head always + has green and red leaves and never has only one of them. +* Possible tree sizes + * `range` + The size of the AABB around the biggest possible tree head. + `range_up` and `range_down` can override the size in +Y and -Y + directions.
+ Note that if the tree has a thick stem, the range needs to be + a bit bigger because the tree head centre can be offset.
+ Example: `range = 2` represents a 5x5x5 cube. + * `range_up`, defaults to `range` + Head size in +Y direction, ranging from the highest trunk node to + the top of the tree head. + * `range_down`, defaults to `range` + Like `range_up` but in -Y direction. + * `stem_type` + We can ignore this, or set it to `"2x2"` or `"+"`.
+ `"2x2"` represents a 4 nodes thick stem, `"+"` represents a 5 + nodes thick stem which looks like a plus when viewed from above, + and no stem type represents a simple 1 node thick stem. + * `stem_height_min` + This value determines the minimum number of trunk nodes + the stem consists of; it is only used to identify + neighbouring trees, which should be preserved. +* Other parameters + * `cutting_leaves`, deprecated + used as workaround for moretrees#34; + only enable this for buggy trees where leaves can be in the stem + + +## Acacia Tree + + +## Palm Tree + + +## Moretrees Tree + +This type represents a complex tree from moretrees mod. +The tree can have long branches and other things +which the default tree type algorithm cannot easily detect. +The algorithm for this type is relatively simple, so for a good tree detection, +we should use the default type instead if possible. + + +```lua +treecapitator.register_tree({ + type = "moretrees", + trees = {, , ...}, + leaves = {, , ...}, + range = , + fruits = {, , ...}, + height = , + max_nodes = , + num_trunks_min = , + num_trunks_max = , + num_leaves_min = , + num_leaves_max = , +}) +``` + +Many parameters mean the same as the default type tree parameters. +* `height` + maximum tree height +* `max_nodes` + maximum number of nodes the tree is allowed to consist of +* num_trunks_min… + + +# Other Functions and Variables + +* `treecapitator.capitation_allowed(pos, player)` + Returns true if the player `player` is allowed to remove the tree + at `pos`.
+ We can override this function so that capitation transpires only + under certain contitions, e.g. only if the player has a chainsaw. +* `treecapitator.capitation_usually_disallowed` + The value returned by the initial `treecapitator.capitation_allowed` + function.
+ We can use this if multiple mods add a tool which allows capitating trees, + i.e. if those mods override `treecapitator.capitation_allowed`. +* `treecapitator.no_hand_capitation` + If set to true, players cannot capitate trees with the bare hand. +* `treecapitator.capitate_tree(pos, player)` + This function tests if the player can capitate a tree, + e.g. it calls `treecapitator.capitation_allowed`, + and then capitates the tree if possible.
+ It is usually invoked in the trees' trunk nodes' `after_dig_node` + node definition field. + The `after_dig_node`s are added/overridden automatically + after registering a tree. + + +# To Do + +The doc is not finished. +I invite the person who reads this to have a look at init.lua and trees.lua, +and help to continue the API documentation. + diff --git a/mods/treecapitator/init.lua b/mods/treecapitator/init.lua new file mode 100644 index 0000000..a389218 --- /dev/null +++ b/mods/treecapitator/init.lua @@ -0,0 +1,996 @@ +local load_time_start = minetest.get_us_time() + + +------------------------------------- Settings --------------------------------- + +-- default settings +treecapitator = { + drop_items = false, + drop_leaf = false, + play_sound = true, + moretrees_support = false, + no_hand_capitation = false, + delay = 0, + stem_height_min = 3, + after_register = {}, +} + +-- load custom settings +for name,v in pairs(treecapitator) do + local setting = "treecapitator." .. name + --local typ = type(v) + local neuv + if type(v) == "boolean" then + neuv = minetest.settings:get_bool(setting) + else--if typ == "number" then + neuv = tonumber(minetest.settings:get(setting)) + end + if neuv ~= nil then + treecapitator[name] = neuv + end +end + + +-------------------------- Common functions ------------------------------------ + +local poshash = minetest.hash_node_position + +local function hash2(x, y) + return y * 0x10000 + x +end + +-- don't use minetest.get_node more times for the same position (caching) +local known_nodes +local function clean_cache() + known_nodes = {} + setmetatable(known_nodes, {__mode = "kv"}) +end +clean_cache() + +local function remove_node(pos) + known_nodes[poshash(pos)] = {name="air", param2=0} + minetest.remove_node(pos) + minetest.check_for_falling(pos) +end + +local function get_node(pos) + local vi = poshash(pos) + local node = known_nodes[vi] + if node then + return node + end + node = minetest.get_node(pos) + known_nodes[vi] = node + return node +end + +--definitions of functions for the destruction of nodes +local destroy_node, drop_leaf, remove_leaf +if treecapitator.drop_items then + function drop_leaf(pos, item) + minetest.add_item(pos, item) + end + + function destroy_node(pos, node) + local drops = minetest.get_node_drops(node.name) + for _,item in pairs(drops) do + minetest.add_item(pos, item) + end + remove_node(pos) + end +else + if minetest.settings:get_bool"creative_mode" then + function drop_leaf(_, item, inv) + if inv + and inv:room_for_item("main", item) + and not inv:contains_item("main", item) then + inv:add_item("main", item) + end + end + else + function drop_leaf(pos, item, inv) + if inv + and inv:room_for_item("main", item) then + inv:add_item("main", item) + else + minetest.add_item(pos, item) + end + end + end + + destroy_node = function(pos, node, digger) + known_nodes[poshash(pos)] = {name="air", param2=0} + minetest.node_dig(pos, node, digger) + end +end + +if not treecapitator.drop_leaf then + function remove_leaf(pos, node, inv) + local leaves_drops = minetest.get_node_drops(node.name) + for _, itemname in pairs(leaves_drops) do + if itemname ~= node.name then + drop_leaf(pos, itemname, inv) + end + end + remove_node(pos) --remove the leaves + end +else + function remove_leaf(pos, node, _, digger) + destroy_node(pos, node, digger) + end +end + +local function tree_fall_sound(pos) + if treecapitator.play_sound then + minetest.sound_play("tree_falling", { + gain = 0.2 + 0.2 * math.random() ^ 2, + pitch = 0.7 + 0.5 * math.random(), + pos = pos, + max_hear_distance = 32 + }, true) + end +end + + +local function table_contains(t, v) + for i = 1,#t do + if t[i] == v then + return true + end + end + return false +end + + +-- the functions for the available types +local capitate_funcs = {} + + +------------------------ Function for regular trees ---------------------------- + +-- tests if the node is a trunk which could belong to the same tree sort +local function is_trunk_of_tree(trees, node) + -- param2 is not longer tested to be 0 but smaller than 4 + -- because sometimes the trunk is a bit rotated + return node.param2 < 4 + and trees ^ node.name +end + +-- test if the trunk node there is the top trunk node of a neighbour tree +-- if so, constrain the possible leaves positions +local function get_a_tree(pos, tab, tr, xo,yo,zo) + local p = {x=pos.x + xo, y=pos.y + yo, z=pos.z + zo} + + -- tests if a trunk is at the current pos + local nd = get_node(p) + if not is_trunk_of_tree(tr.trees, nd) then + return false + end + + -- search for a leaves or fruit node next to the trunk + local leaf = get_node{x=p.x, y=p.y+1, z=p.z}.name + if not tr.leaves ^ leaf + and not tr.fruits ^ leaf then + local leaf = get_node{x=p.x, y=p.y, z=p.z+1}.name + if not tr.leaves ^ leaf + and not tr.fruits ^ leaf then + return false + end + end + + local y_top_trunk = p.y + -- search for the requisite amount of stem trunk nodes + for _ = 1, tr.stem_height_min-1 do + p.y = p.y-1 + if not is_trunk_of_tree(tr.trees, get_node(p)) then + return false + end + end + p.y = y_top_trunk + + local r = tr.range + local r_up = tr.range_up or r + local r_down = tr.range_down or r + + if yo > 0 then + -- the neighbour tree is higher than the one which needs to be capitated + -- therefore tag the stem trunks + for _ = 1, tr.stem_height_min do + tab[poshash(p)] = true + p.y = p.y-1 + end + + -- tag trunks below the minimum stem height (no 2x2 and + type support) + for _ = tr.stem_height_min+1, r_down do + p.y = p.y-1 + if not is_trunk_of_tree(tr.trees, get_node(p)) then + break + end + tab[poshash(p)] = true + end + -- actually only -r_down + yo - 1 to -r_down need to be added + p.y = y_top_trunk + end + + -- reduce x and z avoidance range for thick stem neighbour trees + if tr.stem_type == "2x2" then + r = r - 1 + elseif tr.stem_type == "+" then + r = r - 2 + end + + -- tag head positions which should not be removed + local z1 = math.max(-r + zo, -r) + local z2 = math.min(r + zo, r) + local y1 = math.max(-r_down + yo, -r_down) + local y2 = math.min(r_up + yo, r_up) + local x1 = math.max(-r + xo, -r) + local x2 = math.min(r + xo, r) + for z = z1,z2 do + for y = y1,y2 do + local i = poshash{x=x1, y=y, z=z} + for _ = x1,x2 do + tab[i] = true + i = i+1 + end + end + end + return true +end + +-- returns positions for leaves allowed to be dug +local function find_valid_head_ps(pos, head_ps, trunktop_ps, tr) + -- exclude the stem nodes + local before_stems = {} + for i = 1,#trunktop_ps do + local p = vector.subtract(trunktop_ps[i], pos) + before_stems[hash2(p.x, p.z)] = p.y+1 + end + + local r = tr.range + local r_up = tr.range_up or r + local r_down = tr.range_down or r + + -- firstly, detect neighbour trees of the same sort to not hurt them + local tab = {} + local rx2 = 2 * r + local rupdown = r_up + r_down + for z = -rx2, rx2 do + for x = -rx2, rx2 do + local bot = before_stems[hash2(x, z)] or -rupdown + for y = rupdown, bot, -1 do + if get_a_tree(pos, tab, tr, x,y,z) then + break + end + end + end + end + -- now, get the head positions without the neighbouring trees + local n = #head_ps + for z = -r,r do + for x = -r,r do + local bot = before_stems[hash2(x, z)] or -r_down + for y = bot,r_up do + local p = {x=x, y=y, z=z} + if not tab[poshash(p)] then + n = n+1 + head_ps[n] = vector.add(pos, p) + end + end + end + end + return n +end + +-- adds the stem to the trunks +local function get_stem(trunktop_ps, trunks, tr, head_ps) + if tr.cutting_leaves then + treecapitator.moretrees34(trunktop_ps, trunks, tr, head_ps, + get_node, is_trunk_of_tree) + return + end + for i = 1,#trunktop_ps do + local pos = trunktop_ps[i] + local node = get_node(pos) + while is_trunk_of_tree(tr.trees, node) do + trunks[#trunks+1] = {pos, node} + pos = {x=pos.x, y=pos.y+1, z=pos.z} + node = get_node(pos) + end + + -- renew trunk top position + pos.y = pos.y-1 + trunktop_ps[i] = pos + end +end + +-- part of healthy stem searching +local function here_neat_stemps(pos, tr) + local ps = {} + for i = 1,#tr.stem_offsets do + local o = tr.stem_offsets[i] + local p = {x = pos.x + o[1], y = pos.y, z = pos.z + o[2]} + -- air test is too simple (makeshift solution) + if get_node(p).name ~= "air" then + return + end + p.y = p.y+1 + if not is_trunk_of_tree(tr.trees, get_node(p)) then + return + end + ps[#ps+1] = p + end + return ps +end + +-- gives stem positions of a healthy tree +local function find_neat_stemps(pos, tr) + for i = 1,#tr.stem_offsets do + local o = tr.stem_offsets[i] + local p = {x = pos.x - o[1], y = pos.y, z = pos.z - o[2]} + local ps = here_neat_stemps(p, tr) + if ps then + return ps + end + end + -- nothing found +end + +-- part of incomplete stem searching +local function here_incomplete_stemps(pos, tr) + local ps = {} + for i = 1,#tr.stem_offsets do + local o = tr.stem_offsets[i] + local p = {x = pos.x + o[1], y = pos.y+1, z = pos.z + o[2]} + if is_trunk_of_tree(tr.trees, get_node(p)) then + p.y = p.y-1 + local node = get_node(p) + if is_trunk_of_tree(tr.trees, node) then + -- stem wasn't chopped enough + return {} + end + -- air test is too simple (makeshift solution) + if node.name == "air" then + p.y = p.y+1 + ps[#ps+1] = p + end + end + end + -- #ps ∈ [3] + return ps +end + +-- gives stem positions of an eroded tree +local function find_incomplete_stemps(pos, tr) + local ps + local stemcount = 0 + for i = 1,#tr.stem_offsets do + local o = tr.stem_offsets[i] + local p = {x = pos.x - o[1], y = pos.y, z = pos.z - o[2]} + local cps = here_incomplete_stemps(p, tr) + local cnt = #cps + if cnt == 0 then + -- player needs to chop more + return + end + if stemcount < cnt then + stemcount = #cps + ps = cps + end + end + return ps +end + +-- returns the lowest trunk node positions +local function get_stem_ps(pos, tr) + if not tr.stem_type then + -- 1x1 stem + return {{x=pos.x, y=pos.y+1, z=pos.z}} + end + return find_neat_stemps(pos, tr) + or find_incomplete_stemps(pos, tr) +end + +-- gets the middle position of the tree head +local function get_head_center(trunktop_ps, stem_type) + if stem_type == "2x2" then + -- return the highest position + local pos = trunktop_ps[1] + for i = 2,#trunktop_ps do + local p = trunktop_ps[i] + if p.y > pos.y then + pos = p + end + end + return pos + elseif stem_type == "+" then + -- return the middle position + local mid = vector.new() + for i = 1,#trunktop_ps do + mid = vector.add(mid, trunktop_ps[i]) + end + return vector.round(vector.divide(mid, #trunktop_ps)) + else + return trunktop_ps[1] + end +end + +function capitate_funcs.default(pos, tr, _, digger) + local trees = tr.trees + + -- get the stem trunks + local trunks = {} + local trunktop_ps = get_stem_ps(pos, tr) + if not trunktop_ps then + return + end + local head_ps = {} + get_stem(trunktop_ps, trunks, tr, head_ps) + + local leaves = tr.leaves + local fruits = tr.fruits + local hcp = get_head_center(trunktop_ps, tr.stem_type) + + -- abort if the tree lacks leaves/fruits + local ln = get_node{x=hcp.x, y=hcp.y+1, z=hcp.z} + if not leaves ^ ln.name + and not fruits ^ ln.name then + local leaf = get_node{x=hcp.x, y=hcp.y, z=hcp.z+1}.name + if not leaves ^ leaf + and not fruits ^ leaf then + return + end + end + + -- get leaves, fruits and stem fruits + local leaves_found = {} + local n = find_valid_head_ps(hcp, head_ps, trunktop_ps, tr) + local leaves_toremove = {} + local fruits_toremove = {} + for i = 1,n do + local p = head_ps[i] + local node = get_node(p) + local nodename = node.name + if not is_trunk_of_tree(trees, node) then + if leaves ^ nodename then + leaves_found[nodename] = true + leaves_toremove[#leaves_toremove+1] = {p, node} + elseif fruits ^ nodename then + fruits_toremove[#fruits_toremove+1] = {p, node} + end + elseif tr.trunk_fruit_vertical + and fruits ^ nodename then + trunks[#trunks+1] = {p, node} + end + end + + if tr.requisite_leaves then + -- abort if specific leaves weren't found + for i = 1,#tr.requisite_leaves do + if not leaves_found[tr.requisite_leaves[i]] then + return + end + end + end + + -- remove fruits at first due to attachment + -- and disable nodeupdate temporarily + local nodeupdate = minetest.check_for_falling + minetest.check_for_falling = function() end + for i = 1,#fruits_toremove do + destroy_node(fruits_toremove[i][1], fruits_toremove[i][2], digger) + end + minetest.check_for_falling = nodeupdate + local inv = digger:get_inventory() + for i = 1,#leaves_toremove do + remove_leaf(leaves_toremove[i][1], leaves_toremove[i][2], inv, digger) + end + for i = 1,#trunks do + destroy_node(trunks[i][1], trunks[i][2], digger) + end + + -- tree was capitated, play sound + tree_fall_sound(pos) + return true +end + +-- metatable for shorter code: trees ^ name ≙ name ∈ trees +local mt_default = { + __pow = table_contains +} +treecapitator.after_register.default = function(tr) + setmetatable(tr.trees, mt_default) + setmetatable(tr.leaves, mt_default) + setmetatable(tr.fruits, mt_default) + tr.range_up = tr.range_up or tr.range + tr.range_down = tr.range_down or tr.range + tr.stem_height_min = tr.stem_height_min or treecapitator.stem_height_min + + if tr.stem_type == "2x2" then + tr.stem_offsets = { + {0,0}, {1,0}, + {0,1}, {1,1}, + } + elseif tr.stem_type == "+" then + tr.stem_offsets = { + {0,0}, + {0,1}, + {-1,0}, {1,0}, + {0,-1}, + } + end +end + + +--------------------- Acacia tree function ------------------------------------- + +function capitate_funcs.acacia(pos, tr, node_above, digger) + local trunk = tr.trees[1] + + -- fill tab with the stem trunks + local tab, n = {{{x=pos.x, y=pos.y+1, z=pos.z}, node_above}}, 2 + local np = {x=pos.x, y=pos.y+2, z=pos.z} + local nd = get_node(np) + while trunk == nd.name + and nd.param2 < 4 do + tab[n] = {vector.new(np), nd} + n = n+1 + np.y = np.y+1 + nd = get_node(np) + end + np.y = np.y-1 + + local inv = digger:get_inventory() + for z = -1,1,2 do + for x = -1,1,2 do + -- add the other trunks to tab + local p = vector.new(np) + p.x = p.x+x + p.z = p.z+z + local nd = get_node(p) + if nd.name ~= trunk then + p.y = p.y+1 + nd = get_node(p) + if nd.name ~= trunk then + return + end + end + tab[n] = {vector.new(p), nd} + + p.x = p.x+x + p.z = p.z+z + p.y = p.y+1 + + if get_node(p).name ~= trunk then + return + end + tab[n+1] = {vector.new(p), nd} + n = n+2 + + -- get neighbouring acacia trunks for delimiting + local no_rms = {} + for z = -4,4 do + for x = -4,4 do + if math.abs(x+z) ~= 8 + and (x ~= 0 or z ~= 0) then + if get_node{x=p.x+x, y=p.y, z=p.z+z}.name == trunk + and get_node{x=p.x+x, y=p.y+1, z=p.z+z}.name == tr.leaf then + for z = math.max(-4, z-2), math.min(4, z+2) do + for x = math.max(-4, x-2), math.min(4, x+2) do + no_rms[(z+4)*9 + x+4] = true + end + end + end + end + end + end + + -- remove leaves + p.y = p.y+1 + local i = 0 + for z = -4,4 do + for x = -4,4 do + if not no_rms[i] then + local p = {x=p.x+x, y=p.y, z=p.z+z} + local node = get_node(p) + if node.name == tr.leaf then + remove_leaf(p, node, inv, digger) + end + end + i = i+1 + end + end + end + end + + -- play the sound, then dig the stem + tree_fall_sound(pos) + for i = 1,n-1 do + local pos_stem, node = unpack(tab[i]) + destroy_node(pos_stem, node, digger) + end + return true +end + + +----------------------- Palm tree function ------------------------------------- + +-- the 17 vectors used for walking the stem +local palm_stem_dirs +do + palm_stem_dirs = { + {0,1,0} + } + local n = 2 + for i = -1,1,2 do + palm_stem_dirs[n] = {i,0,0} + palm_stem_dirs[n+1] = {0,0,i} + n = n+2 + end + for i = -1,1,2 do + palm_stem_dirs[n] = {i,0,i} + palm_stem_dirs[n+1] = {i,0,-i} + n = n+2 + end + for i = -1,1,2 do + palm_stem_dirs[n] = {i,1,0} + palm_stem_dirs[n+1] = {0,1,i} + n = n+2 + end + for i = -1,1,2 do + palm_stem_dirs[n] = {i,1,i} + palm_stem_dirs[n+1] = {i,1,-i} + n = n+2 + end + for i = 1,17 do + local p = palm_stem_dirs[i] + palm_stem_dirs[i] = vector.new(unpack(p)) + end +end + +local pos_from_hash = minetest.get_position_from_hash + +-- gets a list of leaves positions +local function get_palm_head(hcp, tr, max_forbi) + local pos = {x=hcp.x, y=hcp.y+1, z=hcp.z} + local leaves = {} + if get_node(pos).name ~= tr.leaves then + -- search hub position + for xo = -1,1 do + for zo = -1,1 do + local p = {x=pos.x+xo, y=pos.y, z=pos.z+zo} + if get_node(p).name == tr.leaves then + pos = p + end + end + end + end + -- collect leaves + leaves[poshash(pos)] = true + for i = -1,1 do + for j = -1,1 do + -- don't search around the corner except max_forbi time(s) + local dirs = {{0,0}, {i,0}, {0,j}, {i,j}, {-i,0}, {0,-j}} + local avoids = {} + local todo = {pos} + local sp = 1 + while sp > 0 do + local p = todo[sp] + sp = sp-1 + -- only walk the "forbidden" dir if still allowed + local forbic = avoids[poshash(p)] or 0 + local dirc = 6 + if forbic == max_forbi then + dirc = dirc - 2 + end + -- walk the directions + for i = 1,dirc do + -- increase forbidden when needed + local forbinc = forbic + if i > 4 then + forbinc = forbinc+1 + end + local xz = dirs[i] + for y = -1,2 do + local p = {x=p.x+xz[1], y=p.y+y, z=p.z+xz[2]} + local ph = poshash(p) + local forbi = avoids[ph] + if not forbi + or forbi > forbinc then + avoids[ph] = forbinc + local dif = vector.subtract(p, pos) + if get_node(p).name == tr.leaves + and math.abs(dif.x) <= tr.range + and math.abs(dif.z) <= tr.range + and dif.y <= tr.range_up + and dif.y >= -tr.range_down then + sp = sp+1 + todo[sp] = p + leaves[ph] = true + end + end + end + end + end + end + end + local ps = {} + local num_leaves = 0 + for ph in pairs(leaves) do + num_leaves = num_leaves+1 + ps[num_leaves] = pos_from_hash(ph) + end + return ps, num_leaves +end + +-- returns positions for palm leaves allowed to be dug +local function palm_find_valid_head_ps(pos, head_ps, tr) + local r = tr.range + local r_up = tr.range_up or r + local r_down = tr.range_down or r + + -- firstly, detect neighbour palms' leaves to not hurt them + local tab = {} + local rx2 = 2 * r + local rupdown = r_up + r_down + for z = -rx2, rx2 do + for y = -rupdown, rupdown do + for x = -rx2, rx2 do + local hcp = {x=pos.x+x, y=pos.y+y, z=pos.z+z} + if not vector.equals(hcp, pos) + and get_node(hcp).name == tr.trunk_top then + local leaves, num_leaves = get_palm_head(hcp, tr, 0) + for i = 1, num_leaves do + tab[poshash(leaves[i])] = true + end + end + end + end + end + -- now, get the leaves positions without the neighbouring leaves + local leaves,lc = get_palm_head(pos, tr, tr.max_forbi) + local n = #head_ps + for i = 1,lc do + local p = leaves[i] + if not tab[poshash(p)] then + n = n+1 + head_ps[n] = p + end + end + return n +end + +function capitate_funcs.palm(startpos, tr, node_above, digger) + local trunk = tr.trees[1] + + -- walk the stem up to the fruit carrier + local pos_current = {x=startpos.x, y=startpos.y+1, z=startpos.z} + local trunks = {{pos_current, node_above}} + local trunk_found = true + local nohori = false + local hcp + while trunk_found + and not hcp do + trunk_found = false + for i = 1,17 do + local hori = i > 1 and i < 10 + if not hori + or not nohori then + local p = vector.add(pos_current, palm_stem_dirs[i]) + local node = get_node(p) + if node.name == trunk then + trunk_found = true + trunks[#trunks+1] = {p, node} + pos_current = p + nohori = hori + break + end + if node.name == tr.trunk_top then + hcp = p + trunks[#trunks+1] = {p, node} + break + end + end + end + end + if not hcp then + return + end + + -- collect coconuts + local fruits = {} + for zo = -1,1 do + for xo = -1,1 do + local p = {x=hcp.x+xo, y=hcp.y, z=hcp.z+zo} + local node = get_node(p) + if node.name:sub(1, #tr.fruit) == tr.fruit then + fruits[#fruits+1] = {p, node} + end + end + end + + -- find the leaves of the palm + local leaves_ps = {} + local lc = palm_find_valid_head_ps(hcp, leaves_ps, tr) + + tree_fall_sound(pos_current) + + local nodeupdate = minetest.check_for_falling + minetest.check_for_falling = function() end + for i = 1,#fruits do + local pos, node = unpack(fruits[i]) + destroy_node(pos, node, digger) + end + minetest.check_for_falling = nodeupdate + + for i = 1,#trunks do + local pos, node = unpack(trunks[i]) + destroy_node(pos, node, digger) + end + + local inv = digger:get_inventory() + for i = 1,lc do + local pos = leaves_ps[i] + remove_leaf(pos, get_node(pos), inv, digger) + end + return true +end + + +---------------------- A moretrees capitation function ------------------------- + +-- table iteration instead of recursion +local function get_tab(pos, func, max) + local todo = {pos} + local n = 1 + local tab_avoid = {[poshash(pos)] = true} + local tab_done,num = {pos},2 + while n ~= 0 do + local p = todo[n] + n = n-1 + --[[ + for i = -1,1,2 do + for _,p2 in pairs{ + {x=p.x+i, y=p.y, z=p.z}, + {x=p.x, y=p.y+i, z=p.z}, + {x=p.x, y=p.y, z=p.z+i}, + } do]] + for i = -1,1 do + for j = -1,1 do + for k = -1,1 do + local p2 = {x=p.x+i, y=p.y+j, z=p.z+k} + local vi = poshash(p2) + if not tab_avoid[vi] + and func(p2) then + n = n+1 + todo[n] = p2 + + tab_avoid[vi] = true + + tab_done[num] = p2 + num = num+1 + + if max + and num > max then + return false + end + end + end + end + end + end + return tab_done +end + +function capitate_funcs.moretrees(pos, tr, _, digger) + local trees = tr.trees + local leaves = tr.leaves + local fruits = tr.fruits + local minx = pos.x-tr.range + local maxx = pos.x+tr.range + local minz = pos.z-tr.range + local maxz = pos.z+tr.range + local maxy = pos.y+tr.height + local num_trunks = 0 + local num_leaves = 0 + local ps = get_tab({x=pos.x, y=pos.y+1, z=pos.z}, function(p) + if p.x < minx + or p.x > maxx + or p.z < minz + or p.z > maxz + or p.y > maxy then + return false + end + local nam = get_node(p).name + if table_contains(trees, nam) then + num_trunks = num_trunks+1 + elseif table_contains(leaves, nam) then + num_leaves = num_leaves+1 + elseif not table_contains(fruits, nam) then + return false + end + return true + end, tr.max_nodes) + if not ps then + print"no ps found" + return + end + if num_trunks < tr.num_trunks_min + or num_trunks > tr.num_trunks_max then + print("wrong trunks num: "..num_trunks) + return + end + if num_leaves < tr.num_leaves_min + or num_leaves > tr.num_leaves_max then + print("wrong leaves num: "..num_leaves) + return + end + tree_fall_sound(pos) + local inv = digger:get_inventory() + for _,p in pairs(ps) do + local node = get_node(p) + local nodename = node.name + if table_contains(leaves, nodename) then + remove_leaf(p, node, inv, digger) + else + destroy_node(p, node, digger) + end + end + return true +end + + +--------------------------- api interface -------------------------------------- + +-- the function which is used for capitating the tree +local capitating = false --necessary if minetest.node_dig is used +function treecapitator.capitate_tree(pos, digger) + if capitating + or not digger + or digger:get_player_control().sneak + or not treecapitator.capitation_allowed(pos, digger) then + return + end + local t1 = minetest.get_us_time() + capitating = true + local node_above = get_node{x=pos.x, y=pos.y+1, z=pos.z} + for i = 1,#treecapitator.trees do + local tr = treecapitator.trees[i] + if table_contains(tr.trees, node_above.name) + and node_above.param2 < 4 + and capitate_funcs[tr.type](pos, tr, node_above, digger) then + break + end + end + clean_cache() + capitating = false + minetest.log("info", "[treecapitator] tree capitated at (" .. + pos.x .. "|" .. pos.y .. "|" .. pos.z .. ") after ca. " .. + (minetest.get_us_time() - t1) / 1000000 .. " s") +end + +-- delayed capitating +local delay = treecapitator.delay +if delay > 0 then + local oldfunc = treecapitator.capitate_tree + function treecapitator.capitate_tree(...) + minetest.after(delay, function(...) + oldfunc(...) + end, ...) + end +end + + +local path = minetest.get_modpath"treecapitator" .. DIR_DELIM +dofile(path .. "api.lua") +dofile(path .. "trees.lua") +dofile(path .. "moretrees34.lua") + + +local time = (minetest.get_us_time() - load_time_start) / 1000000 +local msg = "[treecapitator] loaded after ca. " .. time .. " seconds." +if time > 0.01 then + print(msg) +else + minetest.log("info", msg) +end diff --git a/mods/treecapitator/mod.conf b/mods/treecapitator/mod.conf new file mode 100644 index 0000000..03269f8 --- /dev/null +++ b/mods/treecapitator/mod.conf @@ -0,0 +1,2 @@ +name = treecapitator +optional_depends = default, farming_plus, moretrees, ethereal diff --git a/mods/treecapitator/moretrees34.lua b/mods/treecapitator/moretrees34.lua new file mode 100644 index 0000000..b7490a6 --- /dev/null +++ b/mods/treecapitator/moretrees34.lua @@ -0,0 +1,87 @@ +-- This provides a workaround for the cutting leaves moretrees problem. + +function treecapitator.moretrees34(trunktop_ps, trunks, tr, head_ps, + get_node, is_trunk_of_tree +) + local trees = tr.trees + for i = 1,#trunktop_ps do + -- add the usual trunks + local pos = trunktop_ps[i] + local node = get_node(pos) + while is_trunk_of_tree(trees, node) do + trunks[#trunks+1] = {pos, node} + pos = {x=pos.x, y=pos.y+1, z=pos.z} + node = get_node(pos) + end + + -- meddle with the lacunarity + local ys = pos.y + local ye + local detected_trunks = {} + + -- search upwards until the gap is big enough or the tree ended + local foundleaves = 0 + while true do + if is_trunk_of_tree(trees, node) then + foundleaves = 0 + detected_trunks[pos.y] = node + pos.y = pos.y+1 + node = get_node(pos) + elseif tr.leaves ^ node.name + or tr.fruits ^ node.name then + foundleaves = foundleaves+1 + if foundleaves > tr.cutting_leaves then + -- cutting leaves count exceeded + ye = pos.y-foundleaves + break + end + pos.y = pos.y+1 + node = get_node(pos) + else + -- above the tree + ye = pos.y-1 + break + end + end + + -- search downwards until enough trunks are found above each other + -- or no such trunks are found + local ytop = ys-1 + local y = ye + local last_test = ys + tr.stem_height_min + while y >= last_test do + if detected_trunks[y] then + local too_short + for ty = y - tr.stem_height_min + 1, y-1 do + if not detected_trunks[y] then + too_short = true + y = ty-1 + break + end + end + if not too_short then + -- upper end found + ytop = y + break + end + end + y = y-1 + end + + if ytop >= ys then + -- add trunks and leaves/fruits + for y = ys, ytop do + local p = {x=pos.x, y=y, z=pos.z} + if detected_trunks[y] then + trunks[#trunks+1] = {p, detected_trunks[y]} + else + head_ps[#head_ps+1] = p + end + end + end + + -- renew trunk top position + pos.y = ytop + trunktop_ps[i] = pos + end +end diff --git a/mods/treecapitator/settingtypes.txt b/mods/treecapitator/settingtypes.txt new file mode 100644 index 0000000..bbe0c3d --- /dev/null +++ b/mods/treecapitator/settingtypes.txt @@ -0,0 +1,24 @@ +# drop items, else get them in the inventory +treecapitator.drop_items (drop items) bool false + +# if set to false, do not drop leaves, just saplings (leafdecay behaviour) +treecapitator.drop_leaf (give leaves) bool false + +# play a sound after digging a tree +treecapitator.play_sound (play sound) bool true + +# capitate moretrees' trees, experimental, use with caution +treecapitator.moretrees_support (support moretrees) bool false + +# lets trees become capitated seconds later, only works if it's > 0 +treecapitator.delay (delay) float 0 0 + +# If set to true, capitating only works with tools with trunk digging +# capabilities and not with the bare hand, so when a tool is worn. +treecapitator.no_hand_capitation (Disallow bare hand capitation) bool false + +# considers trunks as neighbour trees if there're >= stem_height_min trunks +# if set too low, vertical trunk fruits may be recognized as neighbour tree +# if set too high, short stemmed neighbour trees are not recognited +# stem_height_min can explicitly be overridden in tree definitions +treecapitator.stem_height_min (minimum neighbour stem height) int 3 2 diff --git a/mods/treecapitator/sounds/tree_falling.ogg b/mods/treecapitator/sounds/tree_falling.ogg new file mode 100644 index 0000000000000000000000000000000000000000..3e838ac9e75a7003d415c255653e968d3aedbd29 GIT binary patch literal 32587 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 literal 0 HcmV?d00001 diff --git a/mods/treecapitator/trees.lua b/mods/treecapitator/trees.lua new file mode 100644 index 0000000..3b5dc8f --- /dev/null +++ b/mods/treecapitator/trees.lua @@ -0,0 +1,468 @@ +if minetest.get_modpath("default") then + if minetest.get_mapgen_setting"mg_name" == "v6" then + treecapitator.register_tree{ + trees = {"default:tree"}, + leaves = {"default:leaves"}, + range = 2, + fruits = {"default:apple"} + } + + treecapitator.register_tree({ + trees = {"default:jungletree"}, + leaves = {"default:jungleleaves"}, + range = 3 + }) + else + treecapitator.register_tree{ + trees = {"default:tree"}, + leaves = {"default:leaves"}, + range = 2, + range_up = 4, + range_down = 0, + fruits = {"default:apple", "default:tree"}, + trunk_fruit_vertical = true + } + + treecapitator.register_tree({ + trees = {"default:jungletree"}, + leaves = {"default:jungleleaves"}, + fruits = {"default:jungletree"}, + range = 4, + range_up = 14, + range_down = 5, + trunk_fruit_vertical = true, + stem_height_min = 12, + }) + + treecapitator.register_tree({ + trees = {"default:jungletree"}, + leaves = {"default:jungleleaves"}, + fruits = {"default:jungletree"}, + range = 4, + range_up = 14, + range_down = 3, + trunk_fruit_vertical = true, + stem_type = "2x2", + stem_height_min = 12, + }) + end + + treecapitator.register_tree({ + trees = {"default:pine_tree"}, + leaves = {"default:pine_needles"}, + -- the +2 height is used to also support the coned pine trees + range_up = 2 +2, + range_down = 6, + range = 3, + }) + + treecapitator.register_tree({ + trees = {"default:acacia_tree"}, + leaf = "default:acacia_leaves", + no_param2test = true, + --leavesrange = 4, + type = "acacia" + }) + + treecapitator.register_tree({ + trees = {"default:aspen_tree"}, + leaves = {"default:aspen_leaves"}, + range = 4, + }) +end + +if minetest.get_modpath("farming_plus") then + treecapitator.register_tree({ + trees = {"default:tree"}, + leaves = {"farming_plus:banana_leaves"}, + range = 2, + fruits = {"farming_plus:banana"} + }) + + treecapitator.register_tree({ + trees = {"default:tree"}, + leaves = {"farming_plus:cocoa_leaves"}, + range = 2, + fruits = {"farming_plus:cocoa"} + }) +end + +if treecapitator.moretrees_support +and minetest.get_modpath("moretrees") then + treecapitator.register_tree({ + trees = {"moretrees:acacia_trunk"}, + leaves = {"moretrees:acacia_leaves"}, + range = 10, + }) + + treecapitator.register_tree{ + trees = {"moretrees:poplar_trunk"}, + leaves = {"moretrees:poplar_leaves"}, + range_up = 5, + range_down = 17, + range = 2, + } + + local dates = {"moretrees:dates_fn", "moretrees:dates_m0", + "moretrees:dates_n"} + for i = 0, 4 do + dates[#dates+1] = "moretrees:dates_f" .. i + end + dates[#dates+1] = "moretrees:date_palm_trunk" + treecapitator.register_tree{ + trees = { + "moretrees:date_palm_trunk", + "moretrees:date_palm_mfruit_trunk", + "moretrees:date_palm_ffruit_trunk" + }, + leaves = {"moretrees:date_palm_leaves"}, + fruits = dates, + trunk_fruit_vertical = true, + range = 11, + range_up = 15, + range_down = 0, + } + + treecapitator.register_tree{ + trees = {"moretrees:apple_tree_trunk"}, + leaves = {"moretrees:apple_tree_leaves"}, + fruits = {"default:apple", "moretrees:apple_tree_trunk"}, + trunk_fruit_vertical = true, + range = 9, + range_up = 3, + range_down = 4, + } + + treecapitator.register_tree{ + trees = {"moretrees:beech_trunk"}, + leaves = {"moretrees:beech_leaves"}, + range = 4, + range_down = 2, + range_up = 3, + fruits = {"moretrees:beech_trunk"}, + trunk_fruit_vertical = true + } + + treecapitator.register_tree{ + trees = {"moretrees:birch_trunk"}, + leaves = {"moretrees:birch_leaves"}, + fruits = {"moretrees:birch_trunk"}, + trunk_fruit_vertical = true, + cutting_leaves = 3, + stem_height_min = 4, + range = 8, + range_down = 13, + range_up = 10, + } + + treecapitator.register_tree{ + trees = {"moretrees:fir_trunk"}, + leaves = {"moretrees:fir_leaves", "moretrees:fir_leaves_bright"}, + range_up = 2, + range_down = 21, + range = 7, + fruits = {"moretrees:fir_cone", "moretrees:fir_trunk"}, + trunk_fruit_vertical = true + } + + treecapitator.register_tree({ + trees = {"moretrees:jungletree_trunk"}, + leaves = {"moretrees:jungletree_leaves_green", + "jungletree_leaves_yellow", "jungletree_leaves_red"}, + range = 8, + }) + + treecapitator.register_tree{ + trees = {"moretrees:oak_trunk"}, + leaves = {"moretrees:oak_leaves"}, + fruits = {"moretrees:acorn", "moretrees:oak_trunk"}, + trunk_fruit_vertical = true, + stem_type = "+", + range = 11, + range_up = 11, + range_down = 1, + } + + -- needs special type + treecapitator.register_tree({ + trees = {"moretrees:cedar_trunk"}, + leaves = {"moretrees:cedar_leaves"}, + range = 10, + range_up = 1, + range_down = 19, + trunk_fruit_vertical = true, + fruits = {"moretrees:cedar_cone", "moretrees:cedar_trunk"} + }) + + treecapitator.register_tree{ + trees = {"moretrees:rubber_tree_trunk", + "moretrees:rubber_tree_trunk_empty"}, + leaves = {"moretrees:rubber_tree_leaves"}, + fruits = {"moretrees:rubber_tree_trunk", + "moretrees:rubber_tree_trunk_empty"}, + trunk_fruit_vertical = true, + stem_type = "2x2", + range = 8, + range_down = 1, + range_up = 8, + } + + treecapitator.register_tree{ + trees = {"moretrees:sequoia_trunk"}, + leaves = {"moretrees:sequoia_leaves"}, + fruits = {"moretrees:sequoia_trunk"}, + trunk_fruit_vertical = true, + stem_type = "+", + range = 10, + range_up = 3, + range_down = 33, + cutting_leaves = 6, + stem_height_min = 6, + } + + treecapitator.register_tree{ + trees = {"moretrees:spruce_trunk"}, + leaves = {"moretrees:spruce_leaves"}, + fruits = {"moretrees:spruce_cone", "moretrees:spruce_trunk"}, + trunk_fruit_vertical = true, + cutting_leaves = 1, + stem_type = "+", + range = 10, + range_down = 25, + range_up = 5, + } + + treecapitator.register_tree{ + trees = {"moretrees:willow_trunk"}, + leaves = {"moretrees:willow_leaves"}, + fruits = {"moretrees:willow_trunk"}, + trunk_fruit_vertical = true, + stem_type = "+", + range = 13, + range_up = 6, + range_down = 6, + } + + treecapitator.register_tree{ -- small and 2x2 jungletree at once + trees = {"moretrees:jungletree_trunk"}, + leaves = {"default:jungleleaves", "moretrees:jungletree_leaves_red"}, + fruits = {"moretrees:jungletree_trunk"}, + requisite_leaves = {"moretrees:jungletree_leaves_red"}, + trunk_fruit_vertical = true, + stem_height_min = 4, + cutting_leaves = 5, + stem_type = "2x2", + range = 8, -- 5 small + range_up = 2, -- 1 small + range_down = 17, -- 6 small + } + + treecapitator.register_tree{ + trees = {"moretrees:jungletree_trunk"}, + leaves = {"default:jungleleaves", "moretrees:jungletree_leaves_yellow", + "moretrees:jungletree_leaves_red"}, + fruits = {"moretrees:jungletree_trunk"}, + requisite_leaves = {"moretrees:jungletree_leaves_yellow"}, + trunk_fruit_vertical = true, + cutting_leaves = 5, + stem_type = "+", + range = 8, + range_up = 4, + range_down = 16, + } + + treecapitator.register_tree{ + trees = {"moretrees:palm_trunk"}, + trunk_top = "moretrees:palm_fruit_trunk", + leaves = "moretrees:palm_leaves", + fruit = "moretrees:coconut", + range = 10, + range_up = 7, + range_down = 4, + max_forbi = 2, + type = "palm", + } + + --~ treecapitator.register_tree({ + --~ trees = {"moretrees:sequoia_trunk"}, + --~ leaves = {"moretrees:sequoia_leaves"}, + --~ range = 8, + + + --~ height = 17, + --~ max_nodes = 8000, + --~ num_trunks_min = 5, + --~ num_trunks_max = 400, + --~ num_leaves_min = 10, + --~ num_leaves_max = 4000, + --~ type = "moretrees", + --~ }) + + --~ treecapitator.register_tree({ + --~ trees = {"moretrees:willow_trunk"}, + --~ leaves = {"moretrees:willow_leaves"}, + --~ range = 11, + --~ height = 17, + --~ max_nodes = 8000, + --~ num_trunks_min = 5, + --~ num_trunks_max = 400, + --~ num_leaves_min = 10, + --~ num_leaves_max = 4000, + --~ type = "moretrees", + --~ }) +end + +-- code from amadin and narrnika +if minetest.get_modpath("ethereal") then + treecapitator.register_tree({--jungle [эвкалипт] + trees = {"default:jungletree"}, + leaves = {"default:jungleleaves"}, + range = 3, + height = 20, + max_nodes = 145, + num_trunks_min = 0, + num_trunks_max = 35, + num_leaves_min = 0, + num_leaves_max = 110, + type = "moretrees", + }) + treecapitator.register_tree({--pine [кедр] + trees = {"default:pinetree"}, -- this may need to be changed to pine_tree + leaves = {"ethereal:pineleaves"}, + range = 6, + type = "default", + }) + treecapitator.register_tree({--orange [апельсиновое дерево] + trees = {"default:tree"}, + leaves = {"default:leaves", "ethereal:orange_leaves"}, + fruits = {"default:apple", "ethereal:orange"}, + range = 2, + type = "default", + }) + treecapitator.register_tree({--acacia [акация] + trees = {"ethereal:acacia_trunk"}, + leaves = {"ethereal:acacia_leaves"}, + range = 10, + height = 10, + max_nodes = 122, + num_trunks_min = 0, + num_trunks_max = 22, + num_leaves_min = 0, + num_leaves_max = 100, + type = "moretrees", + }) + treecapitator.register_tree({--banana [банановое дерево] + trees = {"ethereal:banana_trunk"}, + leaves = {"ethereal:bananaleaves"}, + fruits = {"ethereal:banana"}, + range = 3, + height = 7, + max_nodes = 28, + num_trunks_min = 0, + num_trunks_max = 4, + num_leaves_min = 0, + num_leaves_max = 20, + type = "moretrees", + }) + treecapitator.register_tree({--coconut [кокосовое дерево] + trees = {"ethereal:palm_trunk"}, + leaves = {"ethereal:palmleaves"}, + fruits = {"ethereal:coconut"}, + range = 4, + height = 9, + max_nodes = 37, + num_trunks_min = 0, + num_trunks_max = 8, + num_leaves_min = 0, + num_leaves_max = 25, + type = "moretrees", + }) + treecapitator.register_tree({--willow [ива] + trees = {"ethereal:willow_trunk"}, + leaves = {"ethereal:willow_twig"}, + range = 10, + height = 13, + max_nodes = 540, + num_trunks_min = 0, + num_trunks_max = 90, + num_leaves_min = 0, + num_leaves_max = 450, + type = "moretrees", + }) + treecapitator.register_tree({--moshroom [гриб] + trees = {"ethereal:mushroom_trunk"}, + leaves = {"ethereal:mushroom", "ethereal:mushroom_pore"}, + range = 4, + height = 10, + max_nodes = 100, + num_trunks_min = 0, + num_trunks_max = 32, + num_leaves_min = 0, + num_leaves_max = 80, + type = "moretrees", + }) + treecapitator.register_tree({--sakura + trees = {"ethereal:sakura_trunk"}, + leaves = {"ethereal:sakura_leaves", "ethereal:sakura_leaves2"}, + range = 7, + height = 10, + max_nodes = 228, + num_trunks_min = 0, + num_trunks_max = 24, + num_leaves_min = 0, + num_leaves_max = 204, + type = "moretrees", + }) + treecapitator.register_tree({--lemon + trees = {"default:tree"}, + leaves = {"ethereal:lemon_leaves"}, + fruits = {"ethereal:lemon"}, + range = 4, + range_up = 8, + stem_height_min = 3, + }) + treecapitator.register_tree({--frosttree + trees = {"ethereal:frost_tree"}, + leaves = {"ethereal:frost_leaves"}, + stem_type = "2x2", + stem_height_min = 3, + range = 5, + range_up = 19, + }) + treecapitator.register_tree({--birch + trees = {"ethereal:birch_trunk"}, + leaves = {"ethereal:birch_leaves"}, + range = 3, + range_up = 7, + stem_height_min = 3, + }) + treecapitator.register_tree({--yellowtree + trees = {"ethereal:yellow_trunk"}, + leaves = {"ethereal:yellowleaves"}, + fruits = {"ethereal:golden_apple"}, + range = 5, + height = 19, + max_nodes = 149, + num_trunks_min = 0, + num_trunks_max = 54, + num_leaves_min = 0, + num_leaves_max = 85, + stem_height_min = 13, + type = "moretrees", + }) + treecapitator.register_tree({--olive + trees = {"ethereal:olive_trunk"}, + leaves = {"ethereal:olive_leaves"}, + fruits = {"ethereal:olive"}, + range = 4, + height = 9, + max_nodes = 94, + num_trunks_min = 0, + num_trunks_max = 27, + num_leaves_min = 0, + num_leaves_max = 59, + stem_height_min = 8, + type = "moretrees", + }) + +end