├── .gitignore ├── .luacheckrc ├── LICENSE.txt ├── README.md ├── api.lua ├── documentation └── doc.md ├── init.lua ├── mod.conf ├── moretrees34.lua ├── settingtypes.txt ├── sounds └── tree_falling.ogg └── trees.lua /.gitignore: -------------------------------------------------------------------------------- 1 | ## Generic ignorable patterns and files 2 | *~ 3 | .*.swp 4 | debug.txt 5 | *.patch 6 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | read_globals = { 2 | "dump", 3 | "vector", 4 | "DIR_DELIM", 5 | minetest = { 6 | fields = { 7 | check_for_falling = { 8 | read_only = false 9 | } 10 | }, 11 | other_fields = true 12 | } 13 | } 14 | globals = {"treecapitator"} 15 | ignore = {"421", "423"} 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The code of this mod is licensed under MIT. 2 | The sound from http://www.freesound.org/people/ecfike/sounds/139952/, 3 | edited with audacity, is licensed under CC0. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | With the treecapitator mod for Luanti, 2 | when a player digs part of a tree's stem, the whole tree is felled at once. 3 | 4 | There are many settings, for example an option to control whether leaves are 5 | dropped, too, or only saplings, resembling leafdecay behaviour. 6 | Support for moretrees is disabled by default and can be enabled with a setting. 7 | 8 | If players want to dig single trunk nodes instead of whole trees, they can hold 9 | the shift key. 10 | When felling a tree, neighbouring trees are detected and left unharmed. 11 | 12 | This mod is based on Jeja's timber mod, however, the code was revised over and 13 | over, so it became a different mod (according to the 14 | [broom theory](https://www.youtube.com/watch?v=51n-EBigXmg) that is).
15 | [There I got the name.](http://www.minecraftforum.net/topic/1009577-147-daftpvfs-mods-treecapitator-ingameinfo-crystalwing-startinginv-floatingruins/) 16 | 17 | **Depends:** see [depends.txt](https://raw.githubusercontent.com/HybridDog/treecapitator/master/depends.txt)
18 | **License:** see [LICENSE.txt](https://raw.githubusercontent.com/HybridDog/treecapitator/master/LICENSE.txt)
19 | **Download:** [zip](https://github.com/HybridDog/treecapitator/archive/master.zip), [tar.gz](https://github.com/HybridDog/treecapitator/archive/master.tar.gz) 20 | 21 | ![I'm a screenshot!](https://forum.minetest.net/download/file.php?id=571)
22 | ↑ you can only see the animation if your browser supports apng 23 | 24 | If you got ideas or found bugs, please tell them to me. 25 | 26 | [How to install a mod?](http://wiki.minetest.net/Installing_Mods) 27 | 28 | 29 | [13.04.2015] Added in trees for moretrees, working out getting proper naming 30 | for capitating to work. 31 | 32 | [14.03.2015] Added sound of a falling tree (taken from there 33 | http://www.freesound.org/people/ecfike/sounds/139952/). 34 | Made "drop_items" and "drop_leaf" both "true" by default. (mintpick) 35 | 36 | TODO: 37 | * fix neighbour tree detection, wrong nodes become dug (test with vertical 38 | trunk fruit), 39 | pertains to non-v6 apple tree and jungle tree 40 | * use range_up and range_down for other trees too for greater precision in 41 | neighbour detection 42 | * improve moretrees support (see issue #2) 43 | * moretrees acacia 44 | * more precise neighbour detection for moretrees (cedar), fix moretree 45 | jungletree ignoring default jungletrees' leaves 46 | * technic chainsaw 47 | * Test if pine trees are different on v6 and non-v6 mapgen 48 | * Add a priority and priority condition to allow handling cone pine trees 49 | differently 50 | * Get rid of the cutting_leaves arguments because the problem is fixed in 51 | minetest 52 | * consider neighbour tree's stem and not only head during neighbour detection 53 | * add dfs head search (and a parameter to enable it) 54 | * continue the documentation 55 | -------------------------------------------------------------------------------- /api.lua: -------------------------------------------------------------------------------- 1 | -- the table containing the tree definitions 2 | treecapitator.trees = {} 3 | 4 | -- Warn if a tree definition passed to treecapitator.register_tree contains 5 | -- invalid options, partly for backwards compatibility. 6 | -- Not all options are checked. 7 | local function check_tree_definition(tr) 8 | if type(tr.trees) ~= "table" then 9 | minetest.log("warning", 10 | "[treecapitator] Invalid tree definition (trees field)") 11 | end 12 | if tr.type ~= "acacia" then 13 | if type(tr.leaves) ~= "table" then 14 | minetest.log("warning", 15 | "[treecapitator] Invalid tree definition (leaves field)") 16 | end 17 | if type(tr.range) ~= "number" then 18 | minetest.log("warning", 19 | "[treecapitator] Invalid tree definition (range field)") 20 | end 21 | end 22 | end 23 | 24 | -- For the usage of this function, see trees.lua. 25 | local after_dig_wrap 26 | local after_dig_nodes = {} 27 | function treecapitator.register_tree(tree_definition) 28 | check_tree_definition(tree_definition) 29 | local tr = {} 30 | for name, value in pairs(tree_definition) do 31 | tr[name] = value 32 | end 33 | tr.fruits = tr.fruits or {} 34 | tr.type = tr.type or "default" 35 | treecapitator.trees[#treecapitator.trees+1] = tr 36 | if treecapitator.after_register[tr.type] then 37 | treecapitator.after_register[tr.type](tr) 38 | end 39 | 40 | for i = 1,#tr.trees do 41 | local nodename = tr.trees[i] 42 | local data = minetest.registered_nodes[nodename] 43 | if not data then 44 | error(nodename .. " has to be registered before calling " .. 45 | "treecapitator.register_tree.") 46 | end 47 | local func = after_dig_wrap 48 | local prev_after_dig = data.after_dig_node 49 | if prev_after_dig 50 | and not after_dig_nodes[nodename] then 51 | func = function(pos, oldnode, oldmetadata, digger) 52 | prev_after_dig(pos, oldnode, oldmetadata, digger) 53 | treecapitator.capitate_tree(pos, digger) 54 | end 55 | end 56 | minetest.override_item(nodename, {after_dig_node = func}) 57 | after_dig_nodes[nodename] = true 58 | end 59 | end 60 | 61 | function treecapitator.capitation_allowed() 62 | return not treecapitator.capitation_usually_disallowed 63 | end 64 | 65 | 66 | -- Example of overriding this function 67 | if treecapitator.no_hand_capitation then 68 | -- disallow capitating trees if no proper tool is used 69 | treecapitator.capitation_usually_disallowed = true 70 | local allowed = treecapitator.capitation_allowed 71 | function treecapitator.capitation_allowed(pos, digger) 72 | local def = minetest.registered_nodes[ 73 | minetest.get_node{x=pos.x, y=pos.y+1, z=pos.z}.name 74 | ] 75 | return def and def.groups and minetest.get_dig_params(def.groups, 76 | digger:get_wielded_item():get_tool_capabilities()).wear > 0 77 | or allowed(pos, digger) 78 | end 79 | end 80 | 81 | -- test if trunk nodes were redefined 82 | minetest.after(2, function() 83 | for nodename in pairs(after_dig_nodes) do 84 | if not minetest.registered_nodes[nodename].after_dig_node then 85 | error(nodename .. " didn't keep after_dig_node.") 86 | end 87 | end 88 | after_dig_nodes = nil 89 | end) 90 | 91 | -- wrapping is necessary, someone may overwrite treecapitator.capitate_tree 92 | function after_dig_wrap(pos, _,_, digger) 93 | treecapitator.capitate_tree(pos, digger) 94 | end 95 | -------------------------------------------------------------------------------- /documentation/doc.md: -------------------------------------------------------------------------------- 1 | # Registering a Tree 2 | 3 | We need to register trees so that they are known to the treecapitator mod. 4 | Since this mod detects neighbouring trees and should ignore user-placed trunks, 5 | the we should define a tree as strictly as possible. 6 | 7 | I have grouped the tree definitions to different types because, for example, 8 | an apple tree requires a different detection algorithm than a palm tree. 9 | 10 | ## Default Tree 11 | 12 | This is probably the most common type of tree in minetest. 13 | 14 | ```lua 15 | treecapitator.register_tree({ 16 | type = "default", 17 | trees = {, , ...}, 18 | leaves = {, , ...}, 19 | range = , 20 | range_up = , 21 | range_down = , 22 | fruits = {, , ...}, 23 | trunk_fruit_vertical = , 24 | cutting_leaves = , 25 | stem_type = , 26 | stem_height_min = , 27 | requisite_leaves = {, , ...}, 28 | }) 29 | ``` 30 | 31 | * Nodes the tree consists of 32 | * `trees` 33 | The trunk nodes of the straight stem. 34 | Rotated trunks (param2 > 0) are usually ignored. 35 | * `leaves` 36 | Nodes of the tree head which represent leaves.
37 | In comparison to `fruits`, these nodes are affected 38 | by the drop_leaf setting. 39 | * `fruits` 40 | Like `leaves` but unaffected by the drop_leaf setting.
41 | This list can also contain trunk nodes which are spread around 42 | in the tree head. 43 | * `trunk_fruit_vertical` 44 | If set to true, a trunk node which is in `trees` and `fruits` is removed 45 | even if it is not rotated (param2 > 0). 46 | * `requisite_leaves` 47 | If defined, abort capitating if one of the nodes in this list were not 48 | found in the tree head.
49 | We can use this if, for example, the tree head always 50 | has green and red leaves and never has only one of them. 51 | * Possible tree sizes 52 | * `range` 53 | The size of the AABB around the biggest possible tree head. 54 | `range_up` and `range_down` can override the size in +Y and -Y 55 | directions.
56 | Note that if the tree has a thick stem, the range needs to be 57 | a bit bigger because the tree head centre can be offset.
58 | Example: `range = 2` represents a 5x5x5 cube. 59 | * `range_up`, defaults to `range` 60 | Head size in +Y direction, ranging from the highest trunk node to 61 | the top of the tree head. 62 | * `range_down`, defaults to `range` 63 | Like `range_up` but in -Y direction. 64 | * `stem_type` 65 | We can ignore this, or set it to `"2x2"` or `"+"`.
66 | `"2x2"` represents a 4 nodes thick stem, `"+"` represents a 5 67 | nodes thick stem which looks like a plus when viewed from above, 68 | and no stem type represents a simple 1 node thick stem. 69 | * `stem_height_min` 70 | This value determines the minimum number of trunk nodes 71 | the stem consists of; it is only used to identify 72 | neighbouring trees, which should be preserved. 73 | * Other parameters 74 | * `cutting_leaves`, deprecated 75 | used as workaround for moretrees#34; 76 | only enable this for buggy trees where leaves can be in the stem 77 | 78 | 79 | ## Acacia Tree 80 | 81 | 82 | ## Palm Tree 83 | 84 | 85 | ## Moretrees Tree 86 | 87 | This type represents a complex tree from moretrees mod. 88 | The tree can have long branches and other things 89 | which the default tree type algorithm cannot easily detect. 90 | The algorithm for this type is relatively simple, so for a good tree detection, 91 | we should use the default type instead if possible. 92 | 93 | 94 | ```lua 95 | treecapitator.register_tree({ 96 | type = "moretrees", 97 | trees = {, , ...}, 98 | leaves = {, , ...}, 99 | range = , 100 | fruits = {, , ...}, 101 | height = , 102 | max_nodes = , 103 | num_trunks_min = , 104 | num_trunks_max = , 105 | num_leaves_min = , 106 | num_leaves_max = , 107 | }) 108 | ``` 109 | 110 | Many parameters mean the same as the default type tree parameters. 111 | * `height` 112 | maximum tree height 113 | * `max_nodes` 114 | maximum number of nodes the tree is allowed to consist of 115 | * num_trunks_min… 116 | 117 | 118 | # Other Functions and Variables 119 | 120 | * `treecapitator.capitation_allowed(pos, player)` 121 | Returns true if the player `player` is allowed to remove the tree 122 | at `pos`.
123 | We can override this function so that capitation transpires only 124 | under certain contitions, e.g. only if the player has a chainsaw. 125 | * `treecapitator.capitation_usually_disallowed` 126 | The value returned by the initial `treecapitator.capitation_allowed` 127 | function.
128 | We can use this if multiple mods add a tool which allows capitating trees, 129 | i.e. if those mods override `treecapitator.capitation_allowed`. 130 | * `treecapitator.no_hand_capitation` 131 | If set to true, players cannot capitate trees with the bare hand. 132 | * `treecapitator.capitate_tree(pos, player)` 133 | This function tests if the player can capitate a tree, 134 | e.g. it calls `treecapitator.capitation_allowed`, 135 | and then capitates the tree if possible.
136 | It is usually invoked in the trees' trunk nodes' `after_dig_node` 137 | node definition field. 138 | The `after_dig_node`s are added/overridden automatically 139 | after registering a tree. 140 | 141 | 142 | # To Do 143 | 144 | The doc is not finished. 145 | I invite the person who reads this to have a look at init.lua and trees.lua, 146 | and help to continue the API documentation. 147 | 148 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | local load_time_start = minetest.get_us_time() 2 | 3 | 4 | ------------------------------------- Settings --------------------------------- 5 | 6 | -- default settings 7 | treecapitator = { 8 | drop_items = false, 9 | drop_leaf = false, 10 | play_sound = true, 11 | moretrees_support = false, 12 | no_hand_capitation = false, 13 | delay = 0, 14 | stem_height_min = 3, 15 | after_register = {}, 16 | } 17 | 18 | -- load custom settings 19 | for name,v in pairs(treecapitator) do 20 | local setting = "treecapitator." .. name 21 | --local typ = type(v) 22 | local neuv 23 | if type(v) == "boolean" then 24 | neuv = minetest.settings:get_bool(setting) 25 | else--if typ == "number" then 26 | neuv = tonumber(minetest.settings:get(setting)) 27 | end 28 | if neuv ~= nil then 29 | treecapitator[name] = neuv 30 | end 31 | end 32 | 33 | 34 | -------------------------- Common functions ------------------------------------ 35 | 36 | local poshash = minetest.hash_node_position 37 | 38 | local function hash2(x, y) 39 | return y * 0x10000 + x 40 | end 41 | 42 | -- don't use minetest.get_node more times for the same position (caching) 43 | local known_nodes 44 | local function clean_cache() 45 | known_nodes = {} 46 | setmetatable(known_nodes, {__mode = "kv"}) 47 | end 48 | clean_cache() 49 | 50 | local function remove_node(pos) 51 | known_nodes[poshash(pos)] = {name="air", param2=0} 52 | minetest.remove_node(pos) 53 | minetest.check_for_falling(pos) 54 | end 55 | 56 | local function get_node(pos) 57 | local vi = poshash(pos) 58 | local node = known_nodes[vi] 59 | if node then 60 | return node 61 | end 62 | node = minetest.get_node(pos) 63 | known_nodes[vi] = node 64 | return node 65 | end 66 | 67 | --definitions of functions for the destruction of nodes 68 | local destroy_node, drop_leaf, remove_leaf 69 | if treecapitator.drop_items then 70 | function drop_leaf(pos, item) 71 | minetest.add_item(pos, item) 72 | end 73 | 74 | function destroy_node(pos, node) 75 | local drops = minetest.get_node_drops(node.name) 76 | for _,item in pairs(drops) do 77 | minetest.add_item(pos, item) 78 | end 79 | remove_node(pos) 80 | end 81 | else 82 | if minetest.settings:get_bool"creative_mode" then 83 | function drop_leaf(_, item, inv) 84 | if inv 85 | and inv:room_for_item("main", item) 86 | and not inv:contains_item("main", item) then 87 | inv:add_item("main", item) 88 | end 89 | end 90 | else 91 | function drop_leaf(pos, item, inv) 92 | if inv 93 | and inv:room_for_item("main", item) then 94 | inv:add_item("main", item) 95 | else 96 | minetest.add_item(pos, item) 97 | end 98 | end 99 | end 100 | 101 | destroy_node = function(pos, node, digger) 102 | known_nodes[poshash(pos)] = {name="air", param2=0} 103 | minetest.node_dig(pos, node, digger) 104 | end 105 | end 106 | 107 | if not treecapitator.drop_leaf then 108 | function remove_leaf(pos, node, inv) 109 | local leaves_drops = minetest.get_node_drops(node.name) 110 | for _, itemname in pairs(leaves_drops) do 111 | if itemname ~= node.name then 112 | drop_leaf(pos, itemname, inv) 113 | end 114 | end 115 | remove_node(pos) --remove the leaves 116 | end 117 | else 118 | function remove_leaf(pos, node, _, digger) 119 | destroy_node(pos, node, digger) 120 | end 121 | end 122 | 123 | local function tree_fall_sound(pos) 124 | if treecapitator.play_sound then 125 | minetest.sound_play("tree_falling", { 126 | gain = 0.2 + 0.2 * math.random() ^ 2, 127 | pitch = 0.7 + 0.5 * math.random(), 128 | pos = pos, 129 | max_hear_distance = 32 130 | }, true) 131 | end 132 | end 133 | 134 | 135 | local function table_contains(t, v) 136 | for i = 1,#t do 137 | if t[i] == v then 138 | return true 139 | end 140 | end 141 | return false 142 | end 143 | 144 | 145 | -- the functions for the available types 146 | local capitate_funcs = {} 147 | 148 | 149 | ------------------------ Function for regular trees ---------------------------- 150 | 151 | -- tests if the node is a trunk which could belong to the same tree sort 152 | local function is_trunk_of_tree(trees, node) 153 | -- param2 is not longer tested to be 0 but smaller than 4 154 | -- because sometimes the trunk is a bit rotated 155 | return node.param2 < 4 156 | and trees ^ node.name 157 | end 158 | 159 | -- test if the trunk node there is the top trunk node of a neighbour tree 160 | -- if so, constrain the possible leaves positions 161 | local function get_a_tree(pos, tab, tr, xo,yo,zo) 162 | local p = {x=pos.x + xo, y=pos.y + yo, z=pos.z + zo} 163 | 164 | -- tests if a trunk is at the current pos 165 | local nd = get_node(p) 166 | if not is_trunk_of_tree(tr.trees, nd) then 167 | return false 168 | end 169 | 170 | -- search for a leaves or fruit node next to the trunk 171 | local leaf = get_node{x=p.x, y=p.y+1, z=p.z}.name 172 | if not tr.leaves ^ leaf 173 | and not tr.fruits ^ leaf then 174 | local leaf = get_node{x=p.x, y=p.y, z=p.z+1}.name 175 | if not tr.leaves ^ leaf 176 | and not tr.fruits ^ leaf then 177 | return false 178 | end 179 | end 180 | 181 | local y_top_trunk = p.y 182 | -- search for the requisite amount of stem trunk nodes 183 | for _ = 1, tr.stem_height_min-1 do 184 | p.y = p.y-1 185 | if not is_trunk_of_tree(tr.trees, get_node(p)) then 186 | return false 187 | end 188 | end 189 | p.y = y_top_trunk 190 | 191 | local r = tr.range 192 | local r_up = tr.range_up or r 193 | local r_down = tr.range_down or r 194 | 195 | if yo > 0 then 196 | -- the neighbour tree is higher than the one which needs to be capitated 197 | -- therefore tag the stem trunks 198 | for _ = 1, tr.stem_height_min do 199 | tab[poshash(p)] = true 200 | p.y = p.y-1 201 | end 202 | 203 | -- tag trunks below the minimum stem height (no 2x2 and + type support) 204 | for _ = tr.stem_height_min+1, r_down do 205 | p.y = p.y-1 206 | if not is_trunk_of_tree(tr.trees, get_node(p)) then 207 | break 208 | end 209 | tab[poshash(p)] = true 210 | end 211 | -- actually only -r_down + yo - 1 to -r_down need to be added 212 | p.y = y_top_trunk 213 | end 214 | 215 | -- reduce x and z avoidance range for thick stem neighbour trees 216 | if tr.stem_type == "2x2" then 217 | r = r - 1 218 | elseif tr.stem_type == "+" then 219 | r = r - 2 220 | end 221 | 222 | -- tag head positions which should not be removed 223 | local z1 = math.max(-r + zo, -r) 224 | local z2 = math.min(r + zo, r) 225 | local y1 = math.max(-r_down + yo, -r_down) 226 | local y2 = math.min(r_up + yo, r_up) 227 | local x1 = math.max(-r + xo, -r) 228 | local x2 = math.min(r + xo, r) 229 | for z = z1,z2 do 230 | for y = y1,y2 do 231 | local i = poshash{x=x1, y=y, z=z} 232 | for _ = x1,x2 do 233 | tab[i] = true 234 | i = i+1 235 | end 236 | end 237 | end 238 | return true 239 | end 240 | 241 | -- returns positions for leaves allowed to be dug 242 | local function find_valid_head_ps(pos, head_ps, trunktop_ps, tr) 243 | -- exclude the stem nodes 244 | local before_stems = {} 245 | for i = 1,#trunktop_ps do 246 | local p = vector.subtract(trunktop_ps[i], pos) 247 | before_stems[hash2(p.x, p.z)] = p.y+1 248 | end 249 | 250 | local r = tr.range 251 | local r_up = tr.range_up or r 252 | local r_down = tr.range_down or r 253 | 254 | -- firstly, detect neighbour trees of the same sort to not hurt them 255 | local tab = {} 256 | local rx2 = 2 * r 257 | local rupdown = r_up + r_down 258 | for z = -rx2, rx2 do 259 | for x = -rx2, rx2 do 260 | local bot = before_stems[hash2(x, z)] or -rupdown 261 | for y = rupdown, bot, -1 do 262 | if get_a_tree(pos, tab, tr, x,y,z) then 263 | break 264 | end 265 | end 266 | end 267 | end 268 | -- now, get the head positions without the neighbouring trees 269 | local n = #head_ps 270 | for z = -r,r do 271 | for x = -r,r do 272 | local bot = before_stems[hash2(x, z)] or -r_down 273 | for y = bot,r_up do 274 | local p = {x=x, y=y, z=z} 275 | if not tab[poshash(p)] then 276 | n = n+1 277 | head_ps[n] = vector.add(pos, p) 278 | end 279 | end 280 | end 281 | end 282 | return n 283 | end 284 | 285 | -- adds the stem to the trunks 286 | local function get_stem(trunktop_ps, trunks, tr, head_ps) 287 | if tr.cutting_leaves then 288 | treecapitator.moretrees34(trunktop_ps, trunks, tr, head_ps, 289 | get_node, is_trunk_of_tree) 290 | return 291 | end 292 | for i = 1,#trunktop_ps do 293 | local pos = trunktop_ps[i] 294 | local node = get_node(pos) 295 | while is_trunk_of_tree(tr.trees, node) do 296 | trunks[#trunks+1] = {pos, node} 297 | pos = {x=pos.x, y=pos.y+1, z=pos.z} 298 | node = get_node(pos) 299 | end 300 | 301 | -- renew trunk top position 302 | pos.y = pos.y-1 303 | trunktop_ps[i] = pos 304 | end 305 | end 306 | 307 | -- part of healthy stem searching 308 | local function here_neat_stemps(pos, tr) 309 | local ps = {} 310 | for i = 1,#tr.stem_offsets do 311 | local o = tr.stem_offsets[i] 312 | local p = {x = pos.x + o[1], y = pos.y, z = pos.z + o[2]} 313 | -- air test is too simple (makeshift solution) 314 | if get_node(p).name ~= "air" then 315 | return 316 | end 317 | p.y = p.y+1 318 | if not is_trunk_of_tree(tr.trees, get_node(p)) then 319 | return 320 | end 321 | ps[#ps+1] = p 322 | end 323 | return ps 324 | end 325 | 326 | -- gives stem positions of a healthy tree 327 | local function find_neat_stemps(pos, tr) 328 | for i = 1,#tr.stem_offsets do 329 | local o = tr.stem_offsets[i] 330 | local p = {x = pos.x - o[1], y = pos.y, z = pos.z - o[2]} 331 | local ps = here_neat_stemps(p, tr) 332 | if ps then 333 | return ps 334 | end 335 | end 336 | -- nothing found 337 | end 338 | 339 | -- part of incomplete stem searching 340 | local function here_incomplete_stemps(pos, tr) 341 | local ps = {} 342 | for i = 1,#tr.stem_offsets do 343 | local o = tr.stem_offsets[i] 344 | local p = {x = pos.x + o[1], y = pos.y+1, z = pos.z + o[2]} 345 | if is_trunk_of_tree(tr.trees, get_node(p)) then 346 | p.y = p.y-1 347 | local node = get_node(p) 348 | if is_trunk_of_tree(tr.trees, node) then 349 | -- stem wasn't chopped enough 350 | return {} 351 | end 352 | -- air test is too simple (makeshift solution) 353 | if node.name == "air" then 354 | p.y = p.y+1 355 | ps[#ps+1] = p 356 | end 357 | end 358 | end 359 | -- #ps ∈ [3] 360 | return ps 361 | end 362 | 363 | -- gives stem positions of an eroded tree 364 | local function find_incomplete_stemps(pos, tr) 365 | local ps 366 | local stemcount = 0 367 | for i = 1,#tr.stem_offsets do 368 | local o = tr.stem_offsets[i] 369 | local p = {x = pos.x - o[1], y = pos.y, z = pos.z - o[2]} 370 | local cps = here_incomplete_stemps(p, tr) 371 | local cnt = #cps 372 | if cnt == 0 then 373 | -- player needs to chop more 374 | return 375 | end 376 | if stemcount < cnt then 377 | stemcount = #cps 378 | ps = cps 379 | end 380 | end 381 | return ps 382 | end 383 | 384 | -- returns the lowest trunk node positions 385 | local function get_stem_ps(pos, tr) 386 | if not tr.stem_type then 387 | -- 1x1 stem 388 | return {{x=pos.x, y=pos.y+1, z=pos.z}} 389 | end 390 | return find_neat_stemps(pos, tr) 391 | or find_incomplete_stemps(pos, tr) 392 | end 393 | 394 | -- gets the middle position of the tree head 395 | local function get_head_center(trunktop_ps, stem_type) 396 | if stem_type == "2x2" then 397 | -- return the highest position 398 | local pos = trunktop_ps[1] 399 | for i = 2,#trunktop_ps do 400 | local p = trunktop_ps[i] 401 | if p.y > pos.y then 402 | pos = p 403 | end 404 | end 405 | return pos 406 | elseif stem_type == "+" then 407 | -- return the middle position 408 | local mid = vector.new() 409 | for i = 1,#trunktop_ps do 410 | mid = vector.add(mid, trunktop_ps[i]) 411 | end 412 | return vector.round(vector.divide(mid, #trunktop_ps)) 413 | else 414 | return trunktop_ps[1] 415 | end 416 | end 417 | 418 | function capitate_funcs.default(pos, tr, _, digger) 419 | local trees = tr.trees 420 | 421 | -- get the stem trunks 422 | local trunks = {} 423 | local trunktop_ps = get_stem_ps(pos, tr) 424 | if not trunktop_ps then 425 | return 426 | end 427 | local head_ps = {} 428 | get_stem(trunktop_ps, trunks, tr, head_ps) 429 | 430 | local leaves = tr.leaves 431 | local fruits = tr.fruits 432 | local hcp = get_head_center(trunktop_ps, tr.stem_type) 433 | 434 | -- abort if the tree lacks leaves/fruits 435 | local ln = get_node{x=hcp.x, y=hcp.y+1, z=hcp.z} 436 | if not leaves ^ ln.name 437 | and not fruits ^ ln.name then 438 | local leaf = get_node{x=hcp.x, y=hcp.y, z=hcp.z+1}.name 439 | if not leaves ^ leaf 440 | and not fruits ^ leaf then 441 | return 442 | end 443 | end 444 | 445 | -- get leaves, fruits and stem fruits 446 | local leaves_found = {} 447 | local n = find_valid_head_ps(hcp, head_ps, trunktop_ps, tr) 448 | local leaves_toremove = {} 449 | local fruits_toremove = {} 450 | for i = 1,n do 451 | local p = head_ps[i] 452 | local node = get_node(p) 453 | local nodename = node.name 454 | if not is_trunk_of_tree(trees, node) then 455 | if leaves ^ nodename then 456 | leaves_found[nodename] = true 457 | leaves_toremove[#leaves_toremove+1] = {p, node} 458 | elseif fruits ^ nodename then 459 | fruits_toremove[#fruits_toremove+1] = {p, node} 460 | end 461 | elseif tr.trunk_fruit_vertical 462 | and fruits ^ nodename then 463 | trunks[#trunks+1] = {p, node} 464 | end 465 | end 466 | 467 | if tr.requisite_leaves then 468 | -- abort if specific leaves weren't found 469 | for i = 1,#tr.requisite_leaves do 470 | if not leaves_found[tr.requisite_leaves[i]] then 471 | return 472 | end 473 | end 474 | end 475 | 476 | -- remove fruits at first due to attachment 477 | -- and disable nodeupdate temporarily 478 | local nodeupdate = minetest.check_for_falling 479 | minetest.check_for_falling = function() end 480 | for i = 1,#fruits_toremove do 481 | destroy_node(fruits_toremove[i][1], fruits_toremove[i][2], digger) 482 | end 483 | minetest.check_for_falling = nodeupdate 484 | local inv = digger:get_inventory() 485 | for i = 1,#leaves_toremove do 486 | remove_leaf(leaves_toremove[i][1], leaves_toremove[i][2], inv, digger) 487 | end 488 | for i = 1,#trunks do 489 | destroy_node(trunks[i][1], trunks[i][2], digger) 490 | end 491 | 492 | -- tree was capitated, play sound 493 | tree_fall_sound(pos) 494 | return true 495 | end 496 | 497 | -- metatable for shorter code: trees ^ name ≙ name ∈ trees 498 | local mt_default = { 499 | __pow = table_contains 500 | } 501 | treecapitator.after_register.default = function(tr) 502 | setmetatable(tr.trees, mt_default) 503 | setmetatable(tr.leaves, mt_default) 504 | setmetatable(tr.fruits, mt_default) 505 | tr.range_up = tr.range_up or tr.range 506 | tr.range_down = tr.range_down or tr.range 507 | tr.stem_height_min = tr.stem_height_min or treecapitator.stem_height_min 508 | 509 | if tr.stem_type == "2x2" then 510 | tr.stem_offsets = { 511 | {0,0}, {1,0}, 512 | {0,1}, {1,1}, 513 | } 514 | elseif tr.stem_type == "+" then 515 | tr.stem_offsets = { 516 | {0,0}, 517 | {0,1}, 518 | {-1,0}, {1,0}, 519 | {0,-1}, 520 | } 521 | end 522 | end 523 | 524 | 525 | --------------------- Acacia tree function ------------------------------------- 526 | 527 | function capitate_funcs.acacia(pos, tr, node_above, digger) 528 | local trunk = tr.trees[1] 529 | 530 | -- fill tab with the stem trunks 531 | local tab, n = {{{x=pos.x, y=pos.y+1, z=pos.z}, node_above}}, 2 532 | local np = {x=pos.x, y=pos.y+2, z=pos.z} 533 | local nd = get_node(np) 534 | while trunk == nd.name 535 | and nd.param2 < 4 do 536 | tab[n] = {vector.new(np), nd} 537 | n = n+1 538 | np.y = np.y+1 539 | nd = get_node(np) 540 | end 541 | np.y = np.y-1 542 | 543 | local inv = digger:get_inventory() 544 | for z = -1,1,2 do 545 | for x = -1,1,2 do 546 | -- add the other trunks to tab 547 | local p = vector.new(np) 548 | p.x = p.x+x 549 | p.z = p.z+z 550 | local nd = get_node(p) 551 | if nd.name ~= trunk then 552 | p.y = p.y+1 553 | nd = get_node(p) 554 | if nd.name ~= trunk then 555 | return 556 | end 557 | end 558 | tab[n] = {vector.new(p), nd} 559 | 560 | p.x = p.x+x 561 | p.z = p.z+z 562 | p.y = p.y+1 563 | 564 | if get_node(p).name ~= trunk then 565 | return 566 | end 567 | tab[n+1] = {vector.new(p), nd} 568 | n = n+2 569 | 570 | -- get neighbouring acacia trunks for delimiting 571 | local no_rms = {} 572 | for z = -4,4 do 573 | for x = -4,4 do 574 | if math.abs(x+z) ~= 8 575 | and (x ~= 0 or z ~= 0) then 576 | if get_node{x=p.x+x, y=p.y, z=p.z+z}.name == trunk 577 | and get_node{x=p.x+x, y=p.y+1, z=p.z+z}.name == tr.leaf then 578 | for z = math.max(-4, z-2), math.min(4, z+2) do 579 | for x = math.max(-4, x-2), math.min(4, x+2) do 580 | no_rms[(z+4)*9 + x+4] = true 581 | end 582 | end 583 | end 584 | end 585 | end 586 | end 587 | 588 | -- remove leaves 589 | p.y = p.y+1 590 | local i = 0 591 | for z = -4,4 do 592 | for x = -4,4 do 593 | if not no_rms[i] then 594 | local p = {x=p.x+x, y=p.y, z=p.z+z} 595 | local node = get_node(p) 596 | if node.name == tr.leaf then 597 | remove_leaf(p, node, inv, digger) 598 | end 599 | end 600 | i = i+1 601 | end 602 | end 603 | end 604 | end 605 | 606 | -- play the sound, then dig the stem 607 | tree_fall_sound(pos) 608 | for i = 1,n-1 do 609 | local pos_stem, node = unpack(tab[i]) 610 | destroy_node(pos_stem, node, digger) 611 | end 612 | return true 613 | end 614 | 615 | 616 | ----------------------- Palm tree function ------------------------------------- 617 | 618 | -- the 17 vectors used for walking the stem 619 | local palm_stem_dirs 620 | do 621 | palm_stem_dirs = { 622 | {0,1,0} 623 | } 624 | local n = 2 625 | for i = -1,1,2 do 626 | palm_stem_dirs[n] = {i,0,0} 627 | palm_stem_dirs[n+1] = {0,0,i} 628 | n = n+2 629 | end 630 | for i = -1,1,2 do 631 | palm_stem_dirs[n] = {i,0,i} 632 | palm_stem_dirs[n+1] = {i,0,-i} 633 | n = n+2 634 | end 635 | for i = -1,1,2 do 636 | palm_stem_dirs[n] = {i,1,0} 637 | palm_stem_dirs[n+1] = {0,1,i} 638 | n = n+2 639 | end 640 | for i = -1,1,2 do 641 | palm_stem_dirs[n] = {i,1,i} 642 | palm_stem_dirs[n+1] = {i,1,-i} 643 | n = n+2 644 | end 645 | for i = 1,17 do 646 | local p = palm_stem_dirs[i] 647 | palm_stem_dirs[i] = vector.new(unpack(p)) 648 | end 649 | end 650 | 651 | local pos_from_hash = minetest.get_position_from_hash 652 | 653 | -- gets a list of leaves positions 654 | local function get_palm_head(hcp, tr, max_forbi) 655 | local pos = {x=hcp.x, y=hcp.y+1, z=hcp.z} 656 | local leaves = {} 657 | if get_node(pos).name ~= tr.leaves then 658 | -- search hub position 659 | for xo = -1,1 do 660 | for zo = -1,1 do 661 | local p = {x=pos.x+xo, y=pos.y, z=pos.z+zo} 662 | if get_node(p).name == tr.leaves then 663 | pos = p 664 | end 665 | end 666 | end 667 | end 668 | -- collect leaves 669 | leaves[poshash(pos)] = true 670 | for i = -1,1 do 671 | for j = -1,1 do 672 | -- don't search around the corner except max_forbi time(s) 673 | local dirs = {{0,0}, {i,0}, {0,j}, {i,j}, {-i,0}, {0,-j}} 674 | local avoids = {} 675 | local todo = {pos} 676 | local sp = 1 677 | while sp > 0 do 678 | local p = todo[sp] 679 | sp = sp-1 680 | -- only walk the "forbidden" dir if still allowed 681 | local forbic = avoids[poshash(p)] or 0 682 | local dirc = 6 683 | if forbic == max_forbi then 684 | dirc = dirc - 2 685 | end 686 | -- walk the directions 687 | for i = 1,dirc do 688 | -- increase forbidden when needed 689 | local forbinc = forbic 690 | if i > 4 then 691 | forbinc = forbinc+1 692 | end 693 | local xz = dirs[i] 694 | for y = -1,2 do 695 | local p = {x=p.x+xz[1], y=p.y+y, z=p.z+xz[2]} 696 | local ph = poshash(p) 697 | local forbi = avoids[ph] 698 | if not forbi 699 | or forbi > forbinc then 700 | avoids[ph] = forbinc 701 | local dif = vector.subtract(p, pos) 702 | if get_node(p).name == tr.leaves 703 | and math.abs(dif.x) <= tr.range 704 | and math.abs(dif.z) <= tr.range 705 | and dif.y <= tr.range_up 706 | and dif.y >= -tr.range_down then 707 | sp = sp+1 708 | todo[sp] = p 709 | leaves[ph] = true 710 | end 711 | end 712 | end 713 | end 714 | end 715 | end 716 | end 717 | local ps = {} 718 | local num_leaves = 0 719 | for ph in pairs(leaves) do 720 | num_leaves = num_leaves+1 721 | ps[num_leaves] = pos_from_hash(ph) 722 | end 723 | return ps, num_leaves 724 | end 725 | 726 | -- returns positions for palm leaves allowed to be dug 727 | local function palm_find_valid_head_ps(pos, head_ps, tr) 728 | local r = tr.range 729 | local r_up = tr.range_up or r 730 | local r_down = tr.range_down or r 731 | 732 | -- firstly, detect neighbour palms' leaves to not hurt them 733 | local tab = {} 734 | local rx2 = 2 * r 735 | local rupdown = r_up + r_down 736 | for z = -rx2, rx2 do 737 | for y = -rupdown, rupdown do 738 | for x = -rx2, rx2 do 739 | local hcp = {x=pos.x+x, y=pos.y+y, z=pos.z+z} 740 | if not vector.equals(hcp, pos) 741 | and get_node(hcp).name == tr.trunk_top then 742 | local leaves, num_leaves = get_palm_head(hcp, tr, 0) 743 | for i = 1, num_leaves do 744 | tab[poshash(leaves[i])] = true 745 | end 746 | end 747 | end 748 | end 749 | end 750 | -- now, get the leaves positions without the neighbouring leaves 751 | local leaves,lc = get_palm_head(pos, tr, tr.max_forbi) 752 | local n = #head_ps 753 | for i = 1,lc do 754 | local p = leaves[i] 755 | if not tab[poshash(p)] then 756 | n = n+1 757 | head_ps[n] = p 758 | end 759 | end 760 | return n 761 | end 762 | 763 | function capitate_funcs.palm(startpos, tr, node_above, digger) 764 | local trunk = tr.trees[1] 765 | 766 | -- walk the stem up to the fruit carrier 767 | local pos_current = {x=startpos.x, y=startpos.y+1, z=startpos.z} 768 | local trunks = {{pos_current, node_above}} 769 | local trunk_found = true 770 | local nohori = false 771 | local hcp 772 | while trunk_found 773 | and not hcp do 774 | trunk_found = false 775 | for i = 1,17 do 776 | local hori = i > 1 and i < 10 777 | if not hori 778 | or not nohori then 779 | local p = vector.add(pos_current, palm_stem_dirs[i]) 780 | local node = get_node(p) 781 | if node.name == trunk then 782 | trunk_found = true 783 | trunks[#trunks+1] = {p, node} 784 | pos_current = p 785 | nohori = hori 786 | break 787 | end 788 | if node.name == tr.trunk_top then 789 | hcp = p 790 | trunks[#trunks+1] = {p, node} 791 | break 792 | end 793 | end 794 | end 795 | end 796 | if not hcp then 797 | return 798 | end 799 | 800 | -- collect coconuts 801 | local fruits = {} 802 | for zo = -1,1 do 803 | for xo = -1,1 do 804 | local p = {x=hcp.x+xo, y=hcp.y, z=hcp.z+zo} 805 | local node = get_node(p) 806 | if node.name:sub(1, #tr.fruit) == tr.fruit then 807 | fruits[#fruits+1] = {p, node} 808 | end 809 | end 810 | end 811 | 812 | -- find the leaves of the palm 813 | local leaves_ps = {} 814 | local lc = palm_find_valid_head_ps(hcp, leaves_ps, tr) 815 | 816 | tree_fall_sound(pos_current) 817 | 818 | local nodeupdate = minetest.check_for_falling 819 | minetest.check_for_falling = function() end 820 | for i = 1,#fruits do 821 | local pos, node = unpack(fruits[i]) 822 | destroy_node(pos, node, digger) 823 | end 824 | minetest.check_for_falling = nodeupdate 825 | 826 | for i = 1,#trunks do 827 | local pos, node = unpack(trunks[i]) 828 | destroy_node(pos, node, digger) 829 | end 830 | 831 | local inv = digger:get_inventory() 832 | for i = 1,lc do 833 | local pos = leaves_ps[i] 834 | remove_leaf(pos, get_node(pos), inv, digger) 835 | end 836 | return true 837 | end 838 | 839 | 840 | ---------------------- A moretrees capitation function ------------------------- 841 | 842 | -- table iteration instead of recursion 843 | local function get_tab(pos, func, max) 844 | local todo = {pos} 845 | local n = 1 846 | local tab_avoid = {[poshash(pos)] = true} 847 | local tab_done,num = {pos},2 848 | while n ~= 0 do 849 | local p = todo[n] 850 | n = n-1 851 | --[[ 852 | for i = -1,1,2 do 853 | for _,p2 in pairs{ 854 | {x=p.x+i, y=p.y, z=p.z}, 855 | {x=p.x, y=p.y+i, z=p.z}, 856 | {x=p.x, y=p.y, z=p.z+i}, 857 | } do]] 858 | for i = -1,1 do 859 | for j = -1,1 do 860 | for k = -1,1 do 861 | local p2 = {x=p.x+i, y=p.y+j, z=p.z+k} 862 | local vi = poshash(p2) 863 | if not tab_avoid[vi] 864 | and func(p2) then 865 | n = n+1 866 | todo[n] = p2 867 | 868 | tab_avoid[vi] = true 869 | 870 | tab_done[num] = p2 871 | num = num+1 872 | 873 | if max 874 | and num > max then 875 | return false 876 | end 877 | end 878 | end 879 | end 880 | end 881 | end 882 | return tab_done 883 | end 884 | 885 | function capitate_funcs.moretrees(pos, tr, _, digger) 886 | local trees = tr.trees 887 | local leaves = tr.leaves 888 | local fruits = tr.fruits 889 | local minx = pos.x-tr.range 890 | local maxx = pos.x+tr.range 891 | local minz = pos.z-tr.range 892 | local maxz = pos.z+tr.range 893 | local maxy = pos.y+tr.height 894 | local num_trunks = 0 895 | local num_leaves = 0 896 | local ps = get_tab({x=pos.x, y=pos.y+1, z=pos.z}, function(p) 897 | if p.x < minx 898 | or p.x > maxx 899 | or p.z < minz 900 | or p.z > maxz 901 | or p.y > maxy then 902 | return false 903 | end 904 | local nam = get_node(p).name 905 | if table_contains(trees, nam) then 906 | num_trunks = num_trunks+1 907 | elseif table_contains(leaves, nam) then 908 | num_leaves = num_leaves+1 909 | elseif not table_contains(fruits, nam) then 910 | return false 911 | end 912 | return true 913 | end, tr.max_nodes) 914 | if not ps then 915 | print"no ps found" 916 | return 917 | end 918 | if num_trunks < tr.num_trunks_min 919 | or num_trunks > tr.num_trunks_max then 920 | print("wrong trunks num: "..num_trunks) 921 | return 922 | end 923 | if num_leaves < tr.num_leaves_min 924 | or num_leaves > tr.num_leaves_max then 925 | print("wrong leaves num: "..num_leaves) 926 | return 927 | end 928 | tree_fall_sound(pos) 929 | local inv = digger:get_inventory() 930 | for _,p in pairs(ps) do 931 | local node = get_node(p) 932 | local nodename = node.name 933 | if table_contains(leaves, nodename) then 934 | remove_leaf(p, node, inv, digger) 935 | else 936 | destroy_node(p, node, digger) 937 | end 938 | end 939 | return true 940 | end 941 | 942 | 943 | --------------------------- api interface -------------------------------------- 944 | 945 | -- the function which is used for capitating the tree 946 | local capitating = false --necessary if minetest.node_dig is used 947 | function treecapitator.capitate_tree(pos, digger) 948 | if capitating 949 | or not digger 950 | or digger:get_player_control().sneak 951 | or not treecapitator.capitation_allowed(pos, digger) then 952 | return 953 | end 954 | local t1 = minetest.get_us_time() 955 | capitating = true 956 | local node_above = get_node{x=pos.x, y=pos.y+1, z=pos.z} 957 | for i = 1,#treecapitator.trees do 958 | local tr = treecapitator.trees[i] 959 | if table_contains(tr.trees, node_above.name) 960 | and node_above.param2 < 4 961 | and capitate_funcs[tr.type](pos, tr, node_above, digger) then 962 | break 963 | end 964 | end 965 | clean_cache() 966 | capitating = false 967 | minetest.log("info", "[treecapitator] tree capitated at (" .. 968 | pos.x .. "|" .. pos.y .. "|" .. pos.z .. ") after ca. " .. 969 | (minetest.get_us_time() - t1) / 1000000 .. " s") 970 | end 971 | 972 | -- delayed capitating 973 | local delay = treecapitator.delay 974 | if delay > 0 then 975 | local oldfunc = treecapitator.capitate_tree 976 | function treecapitator.capitate_tree(...) 977 | minetest.after(delay, function(...) 978 | oldfunc(...) 979 | end, ...) 980 | end 981 | end 982 | 983 | 984 | local path = minetest.get_modpath"treecapitator" .. DIR_DELIM 985 | dofile(path .. "api.lua") 986 | dofile(path .. "trees.lua") 987 | dofile(path .. "moretrees34.lua") 988 | 989 | 990 | local time = (minetest.get_us_time() - load_time_start) / 1000000 991 | local msg = "[treecapitator] loaded after ca. " .. time .. " seconds." 992 | if time > 0.01 then 993 | print(msg) 994 | else 995 | minetest.log("info", msg) 996 | end 997 | -------------------------------------------------------------------------------- /mod.conf: -------------------------------------------------------------------------------- 1 | name = treecapitator 2 | optional_depends = default, farming_plus, moretrees, ethereal 3 | -------------------------------------------------------------------------------- /moretrees34.lua: -------------------------------------------------------------------------------- 1 | -- This provides a workaround for the cutting leaves moretrees problem. 2 | 3 | function treecapitator.moretrees34(trunktop_ps, trunks, tr, head_ps, 4 | get_node, is_trunk_of_tree 5 | ) 6 | local trees = tr.trees 7 | for i = 1,#trunktop_ps do 8 | -- add the usual trunks 9 | local pos = trunktop_ps[i] 10 | local node = get_node(pos) 11 | while is_trunk_of_tree(trees, node) do 12 | trunks[#trunks+1] = {pos, node} 13 | pos = {x=pos.x, y=pos.y+1, z=pos.z} 14 | node = get_node(pos) 15 | end 16 | 17 | -- meddle with the lacunarity 18 | local ys = pos.y 19 | local ye 20 | local detected_trunks = {} 21 | 22 | -- search upwards until the gap is big enough or the tree ended 23 | local foundleaves = 0 24 | while true do 25 | if is_trunk_of_tree(trees, node) then 26 | foundleaves = 0 27 | detected_trunks[pos.y] = node 28 | pos.y = pos.y+1 29 | node = get_node(pos) 30 | elseif tr.leaves ^ node.name 31 | or tr.fruits ^ node.name then 32 | foundleaves = foundleaves+1 33 | if foundleaves > tr.cutting_leaves then 34 | -- cutting leaves count exceeded 35 | ye = pos.y-foundleaves 36 | break 37 | end 38 | pos.y = pos.y+1 39 | node = get_node(pos) 40 | else 41 | -- above the tree 42 | ye = pos.y-1 43 | break 44 | end 45 | end 46 | 47 | -- search downwards until enough trunks are found above each other 48 | -- or no such trunks are found 49 | local ytop = ys-1 50 | local y = ye 51 | local last_test = ys + tr.stem_height_min 52 | while y >= last_test do 53 | if detected_trunks[y] then 54 | local too_short 55 | for ty = y - tr.stem_height_min + 1, y-1 do 56 | if not detected_trunks[y] then 57 | too_short = true 58 | y = ty-1 59 | break 60 | end 61 | end 62 | if not too_short then 63 | -- upper end found 64 | ytop = y 65 | break 66 | end 67 | end 68 | y = y-1 69 | end 70 | 71 | if ytop >= ys then 72 | -- add trunks and leaves/fruits 73 | for y = ys, ytop do 74 | local p = {x=pos.x, y=y, z=pos.z} 75 | if detected_trunks[y] then 76 | trunks[#trunks+1] = {p, detected_trunks[y]} 77 | else 78 | head_ps[#head_ps+1] = p 79 | end 80 | end 81 | end 82 | 83 | -- renew trunk top position 84 | pos.y = ytop 85 | trunktop_ps[i] = pos 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /settingtypes.txt: -------------------------------------------------------------------------------- 1 | # drop items, else get them in the inventory 2 | treecapitator.drop_items (drop items) bool false 3 | 4 | # if set to false, do not drop leaves, just saplings (leafdecay behaviour) 5 | treecapitator.drop_leaf (give leaves) bool false 6 | 7 | # play a sound after digging a tree 8 | treecapitator.play_sound (play sound) bool true 9 | 10 | # capitate moretrees' trees, experimental, use with caution 11 | treecapitator.moretrees_support (support moretrees) bool false 12 | 13 | # lets trees become capitated seconds later, only works if it's > 0 14 | treecapitator.delay (delay) float 0 0 15 | 16 | # If set to true, capitating only works with tools with trunk digging 17 | # capabilities and not with the bare hand, so when a tool is worn. 18 | treecapitator.no_hand_capitation (Disallow bare hand capitation) bool false 19 | 20 | # considers trunks as neighbour trees if there're >= stem_height_min trunks 21 | # if set too low, vertical trunk fruits may be recognized as neighbour tree 22 | # if set too high, short stemmed neighbour trees are not recognited 23 | # stem_height_min can explicitly be overridden in tree definitions 24 | treecapitator.stem_height_min (minimum neighbour stem height) int 3 2 25 | -------------------------------------------------------------------------------- /sounds/tree_falling.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HybridDog/treecapitator/a2c274cf96ed665fc44d21778c0e595334882c6d/sounds/tree_falling.ogg -------------------------------------------------------------------------------- /trees.lua: -------------------------------------------------------------------------------- 1 | if minetest.get_modpath("default") then 2 | if minetest.get_mapgen_setting"mg_name" == "v6" then 3 | treecapitator.register_tree{ 4 | trees = {"default:tree"}, 5 | leaves = {"default:leaves"}, 6 | range = 2, 7 | fruits = {"default:apple"} 8 | } 9 | 10 | treecapitator.register_tree({ 11 | trees = {"default:jungletree"}, 12 | leaves = {"default:jungleleaves"}, 13 | range = 3 14 | }) 15 | else 16 | treecapitator.register_tree{ 17 | trees = {"default:tree"}, 18 | leaves = {"default:leaves"}, 19 | range = 2, 20 | range_up = 4, 21 | range_down = 0, 22 | fruits = {"default:apple", "default:tree"}, 23 | trunk_fruit_vertical = true 24 | } 25 | 26 | treecapitator.register_tree({ 27 | trees = {"default:jungletree"}, 28 | leaves = {"default:jungleleaves"}, 29 | fruits = {"default:jungletree"}, 30 | range = 4, 31 | range_up = 14, 32 | range_down = 5, 33 | trunk_fruit_vertical = true, 34 | stem_height_min = 12, 35 | }) 36 | 37 | treecapitator.register_tree({ 38 | trees = {"default:jungletree"}, 39 | leaves = {"default:jungleleaves"}, 40 | fruits = {"default:jungletree"}, 41 | range = 4, 42 | range_up = 14, 43 | range_down = 3, 44 | trunk_fruit_vertical = true, 45 | stem_type = "2x2", 46 | stem_height_min = 12, 47 | }) 48 | end 49 | 50 | treecapitator.register_tree({ 51 | trees = {"default:pine_tree"}, 52 | leaves = {"default:pine_needles"}, 53 | -- the +2 height is used to also support the coned pine trees 54 | range_up = 2 +2, 55 | range_down = 6, 56 | range = 3, 57 | }) 58 | 59 | treecapitator.register_tree({ 60 | trees = {"default:acacia_tree"}, 61 | leaf = "default:acacia_leaves", 62 | no_param2test = true, 63 | --leavesrange = 4, 64 | type = "acacia" 65 | }) 66 | 67 | treecapitator.register_tree({ 68 | trees = {"default:aspen_tree"}, 69 | leaves = {"default:aspen_leaves"}, 70 | range = 4, 71 | }) 72 | end 73 | 74 | if minetest.get_modpath("farming_plus") then 75 | treecapitator.register_tree({ 76 | trees = {"default:tree"}, 77 | leaves = {"farming_plus:banana_leaves"}, 78 | range = 2, 79 | fruits = {"farming_plus:banana"} 80 | }) 81 | 82 | treecapitator.register_tree({ 83 | trees = {"default:tree"}, 84 | leaves = {"farming_plus:cocoa_leaves"}, 85 | range = 2, 86 | fruits = {"farming_plus:cocoa"} 87 | }) 88 | end 89 | 90 | if treecapitator.moretrees_support 91 | and minetest.get_modpath("moretrees") then 92 | treecapitator.register_tree({ 93 | trees = {"moretrees:acacia_trunk"}, 94 | leaves = {"moretrees:acacia_leaves"}, 95 | range = 10, 96 | }) 97 | 98 | treecapitator.register_tree{ 99 | trees = {"moretrees:poplar_trunk"}, 100 | leaves = {"moretrees:poplar_leaves"}, 101 | range_up = 5, 102 | range_down = 17, 103 | range = 2, 104 | } 105 | 106 | local dates = {"moretrees:dates_fn", "moretrees:dates_m0", 107 | "moretrees:dates_n"} 108 | for i = 0, 4 do 109 | dates[#dates+1] = "moretrees:dates_f" .. i 110 | end 111 | dates[#dates+1] = "moretrees:date_palm_trunk" 112 | treecapitator.register_tree{ 113 | trees = { 114 | "moretrees:date_palm_trunk", 115 | "moretrees:date_palm_mfruit_trunk", 116 | "moretrees:date_palm_ffruit_trunk" 117 | }, 118 | leaves = {"moretrees:date_palm_leaves"}, 119 | fruits = dates, 120 | trunk_fruit_vertical = true, 121 | range = 11, 122 | range_up = 15, 123 | range_down = 0, 124 | } 125 | 126 | treecapitator.register_tree{ 127 | trees = {"moretrees:apple_tree_trunk"}, 128 | leaves = {"moretrees:apple_tree_leaves"}, 129 | fruits = {"default:apple", "moretrees:apple_tree_trunk"}, 130 | trunk_fruit_vertical = true, 131 | range = 9, 132 | range_up = 3, 133 | range_down = 4, 134 | } 135 | 136 | treecapitator.register_tree{ 137 | trees = {"moretrees:beech_trunk"}, 138 | leaves = {"moretrees:beech_leaves"}, 139 | range = 4, 140 | range_down = 2, 141 | range_up = 3, 142 | fruits = {"moretrees:beech_trunk"}, 143 | trunk_fruit_vertical = true 144 | } 145 | 146 | treecapitator.register_tree{ 147 | trees = {"moretrees:birch_trunk"}, 148 | leaves = {"moretrees:birch_leaves"}, 149 | fruits = {"moretrees:birch_trunk"}, 150 | trunk_fruit_vertical = true, 151 | cutting_leaves = 3, 152 | stem_height_min = 4, 153 | range = 8, 154 | range_down = 13, 155 | range_up = 10, 156 | } 157 | 158 | treecapitator.register_tree{ 159 | trees = {"moretrees:fir_trunk"}, 160 | leaves = {"moretrees:fir_leaves", "moretrees:fir_leaves_bright"}, 161 | range_up = 2, 162 | range_down = 21, 163 | range = 7, 164 | fruits = {"moretrees:fir_cone", "moretrees:fir_trunk"}, 165 | trunk_fruit_vertical = true 166 | } 167 | 168 | treecapitator.register_tree({ 169 | trees = {"moretrees:jungletree_trunk"}, 170 | leaves = {"moretrees:jungletree_leaves_green", 171 | "jungletree_leaves_yellow", "jungletree_leaves_red"}, 172 | range = 8, 173 | }) 174 | 175 | treecapitator.register_tree{ 176 | trees = {"moretrees:oak_trunk"}, 177 | leaves = {"moretrees:oak_leaves"}, 178 | fruits = {"moretrees:acorn", "moretrees:oak_trunk"}, 179 | trunk_fruit_vertical = true, 180 | stem_type = "+", 181 | range = 11, 182 | range_up = 11, 183 | range_down = 1, 184 | } 185 | 186 | -- needs special type 187 | treecapitator.register_tree({ 188 | trees = {"moretrees:cedar_trunk"}, 189 | leaves = {"moretrees:cedar_leaves"}, 190 | range = 10, 191 | range_up = 1, 192 | range_down = 19, 193 | trunk_fruit_vertical = true, 194 | fruits = {"moretrees:cedar_cone", "moretrees:cedar_trunk"} 195 | }) 196 | 197 | treecapitator.register_tree{ 198 | trees = {"moretrees:rubber_tree_trunk", 199 | "moretrees:rubber_tree_trunk_empty"}, 200 | leaves = {"moretrees:rubber_tree_leaves"}, 201 | fruits = {"moretrees:rubber_tree_trunk", 202 | "moretrees:rubber_tree_trunk_empty"}, 203 | trunk_fruit_vertical = true, 204 | stem_type = "2x2", 205 | range = 8, 206 | range_down = 1, 207 | range_up = 8, 208 | } 209 | 210 | treecapitator.register_tree{ 211 | trees = {"moretrees:sequoia_trunk"}, 212 | leaves = {"moretrees:sequoia_leaves"}, 213 | fruits = {"moretrees:sequoia_trunk"}, 214 | trunk_fruit_vertical = true, 215 | stem_type = "+", 216 | range = 10, 217 | range_up = 3, 218 | range_down = 33, 219 | cutting_leaves = 6, 220 | stem_height_min = 6, 221 | } 222 | 223 | treecapitator.register_tree{ 224 | trees = {"moretrees:spruce_trunk"}, 225 | leaves = {"moretrees:spruce_leaves"}, 226 | fruits = {"moretrees:spruce_cone", "moretrees:spruce_trunk"}, 227 | trunk_fruit_vertical = true, 228 | cutting_leaves = 1, 229 | stem_type = "+", 230 | range = 10, 231 | range_down = 25, 232 | range_up = 5, 233 | } 234 | 235 | treecapitator.register_tree{ 236 | trees = {"moretrees:willow_trunk"}, 237 | leaves = {"moretrees:willow_leaves"}, 238 | fruits = {"moretrees:willow_trunk"}, 239 | trunk_fruit_vertical = true, 240 | stem_type = "+", 241 | range = 13, 242 | range_up = 6, 243 | range_down = 6, 244 | } 245 | 246 | treecapitator.register_tree{ -- small and 2x2 jungletree at once 247 | trees = {"moretrees:jungletree_trunk"}, 248 | leaves = {"default:jungleleaves", "moretrees:jungletree_leaves_red"}, 249 | fruits = {"moretrees:jungletree_trunk"}, 250 | requisite_leaves = {"moretrees:jungletree_leaves_red"}, 251 | trunk_fruit_vertical = true, 252 | stem_height_min = 4, 253 | cutting_leaves = 5, 254 | stem_type = "2x2", 255 | range = 8, -- 5 small 256 | range_up = 2, -- 1 small 257 | range_down = 17, -- 6 small 258 | } 259 | 260 | treecapitator.register_tree{ 261 | trees = {"moretrees:jungletree_trunk"}, 262 | leaves = {"default:jungleleaves", "moretrees:jungletree_leaves_yellow", 263 | "moretrees:jungletree_leaves_red"}, 264 | fruits = {"moretrees:jungletree_trunk"}, 265 | requisite_leaves = {"moretrees:jungletree_leaves_yellow"}, 266 | trunk_fruit_vertical = true, 267 | cutting_leaves = 5, 268 | stem_type = "+", 269 | range = 8, 270 | range_up = 4, 271 | range_down = 16, 272 | } 273 | 274 | treecapitator.register_tree{ 275 | trees = {"moretrees:palm_trunk"}, 276 | trunk_top = "moretrees:palm_fruit_trunk", 277 | leaves = "moretrees:palm_leaves", 278 | fruit = "moretrees:coconut", 279 | range = 10, 280 | range_up = 7, 281 | range_down = 4, 282 | max_forbi = 2, 283 | type = "palm", 284 | } 285 | 286 | --~ treecapitator.register_tree({ 287 | --~ trees = {"moretrees:sequoia_trunk"}, 288 | --~ leaves = {"moretrees:sequoia_leaves"}, 289 | --~ range = 8, 290 | 291 | 292 | --~ height = 17, 293 | --~ max_nodes = 8000, 294 | --~ num_trunks_min = 5, 295 | --~ num_trunks_max = 400, 296 | --~ num_leaves_min = 10, 297 | --~ num_leaves_max = 4000, 298 | --~ type = "moretrees", 299 | --~ }) 300 | 301 | --~ treecapitator.register_tree({ 302 | --~ trees = {"moretrees:willow_trunk"}, 303 | --~ leaves = {"moretrees:willow_leaves"}, 304 | --~ range = 11, 305 | --~ height = 17, 306 | --~ max_nodes = 8000, 307 | --~ num_trunks_min = 5, 308 | --~ num_trunks_max = 400, 309 | --~ num_leaves_min = 10, 310 | --~ num_leaves_max = 4000, 311 | --~ type = "moretrees", 312 | --~ }) 313 | end 314 | 315 | -- code from amadin and narrnika 316 | if minetest.get_modpath("ethereal") then 317 | treecapitator.register_tree({--jungle [эвкалипт] 318 | trees = {"default:jungletree"}, 319 | leaves = {"default:jungleleaves"}, 320 | range = 3, 321 | height = 20, 322 | max_nodes = 145, 323 | num_trunks_min = 0, 324 | num_trunks_max = 35, 325 | num_leaves_min = 0, 326 | num_leaves_max = 110, 327 | type = "moretrees", 328 | }) 329 | treecapitator.register_tree({--pine [кедр] 330 | trees = {"default:pinetree"}, -- this may need to be changed to pine_tree 331 | leaves = {"ethereal:pineleaves"}, 332 | range = 6, 333 | type = "default", 334 | }) 335 | treecapitator.register_tree({--orange [апельсиновое дерево] 336 | trees = {"default:tree"}, 337 | leaves = {"default:leaves", "ethereal:orange_leaves"}, 338 | fruits = {"default:apple", "ethereal:orange"}, 339 | range = 2, 340 | type = "default", 341 | }) 342 | treecapitator.register_tree({--acacia [акация] 343 | trees = {"ethereal:acacia_trunk"}, 344 | leaves = {"ethereal:acacia_leaves"}, 345 | range = 10, 346 | height = 10, 347 | max_nodes = 122, 348 | num_trunks_min = 0, 349 | num_trunks_max = 22, 350 | num_leaves_min = 0, 351 | num_leaves_max = 100, 352 | type = "moretrees", 353 | }) 354 | treecapitator.register_tree({--banana [банановое дерево] 355 | trees = {"ethereal:banana_trunk"}, 356 | leaves = {"ethereal:bananaleaves"}, 357 | fruits = {"ethereal:banana"}, 358 | range = 3, 359 | height = 7, 360 | max_nodes = 28, 361 | num_trunks_min = 0, 362 | num_trunks_max = 4, 363 | num_leaves_min = 0, 364 | num_leaves_max = 20, 365 | type = "moretrees", 366 | }) 367 | treecapitator.register_tree({--coconut [кокосовое дерево] 368 | trees = {"ethereal:palm_trunk"}, 369 | leaves = {"ethereal:palmleaves"}, 370 | fruits = {"ethereal:coconut"}, 371 | range = 4, 372 | height = 9, 373 | max_nodes = 37, 374 | num_trunks_min = 0, 375 | num_trunks_max = 8, 376 | num_leaves_min = 0, 377 | num_leaves_max = 25, 378 | type = "moretrees", 379 | }) 380 | treecapitator.register_tree({--willow [ива] 381 | trees = {"ethereal:willow_trunk"}, 382 | leaves = {"ethereal:willow_twig"}, 383 | range = 10, 384 | height = 13, 385 | max_nodes = 540, 386 | num_trunks_min = 0, 387 | num_trunks_max = 90, 388 | num_leaves_min = 0, 389 | num_leaves_max = 450, 390 | type = "moretrees", 391 | }) 392 | treecapitator.register_tree({--moshroom [гриб] 393 | trees = {"ethereal:mushroom_trunk"}, 394 | leaves = {"ethereal:mushroom", "ethereal:mushroom_pore"}, 395 | range = 4, 396 | height = 10, 397 | max_nodes = 100, 398 | num_trunks_min = 0, 399 | num_trunks_max = 32, 400 | num_leaves_min = 0, 401 | num_leaves_max = 80, 402 | type = "moretrees", 403 | }) 404 | treecapitator.register_tree({--sakura 405 | trees = {"ethereal:sakura_trunk"}, 406 | leaves = {"ethereal:sakura_leaves", "ethereal:sakura_leaves2"}, 407 | range = 7, 408 | height = 10, 409 | max_nodes = 228, 410 | num_trunks_min = 0, 411 | num_trunks_max = 24, 412 | num_leaves_min = 0, 413 | num_leaves_max = 204, 414 | type = "moretrees", 415 | }) 416 | treecapitator.register_tree({--lemon 417 | trees = {"default:tree"}, 418 | leaves = {"ethereal:lemon_leaves"}, 419 | fruits = {"ethereal:lemon"}, 420 | range = 4, 421 | range_up = 8, 422 | stem_height_min = 3, 423 | }) 424 | treecapitator.register_tree({--frosttree 425 | trees = {"ethereal:frost_tree"}, 426 | leaves = {"ethereal:frost_leaves"}, 427 | stem_type = "2x2", 428 | stem_height_min = 3, 429 | range = 5, 430 | range_up = 19, 431 | }) 432 | treecapitator.register_tree({--birch 433 | trees = {"ethereal:birch_trunk"}, 434 | leaves = {"ethereal:birch_leaves"}, 435 | range = 3, 436 | range_up = 7, 437 | stem_height_min = 3, 438 | }) 439 | treecapitator.register_tree({--yellowtree 440 | trees = {"ethereal:yellow_trunk"}, 441 | leaves = {"ethereal:yellowleaves"}, 442 | fruits = {"ethereal:golden_apple"}, 443 | range = 5, 444 | height = 19, 445 | max_nodes = 149, 446 | num_trunks_min = 0, 447 | num_trunks_max = 54, 448 | num_leaves_min = 0, 449 | num_leaves_max = 85, 450 | stem_height_min = 13, 451 | type = "moretrees", 452 | }) 453 | treecapitator.register_tree({--olive 454 | trees = {"ethereal:olive_trunk"}, 455 | leaves = {"ethereal:olive_leaves"}, 456 | fruits = {"ethereal:olive"}, 457 | range = 4, 458 | height = 9, 459 | max_nodes = 94, 460 | num_trunks_min = 0, 461 | num_trunks_max = 27, 462 | num_leaves_min = 0, 463 | num_leaves_max = 59, 464 | stem_height_min = 8, 465 | type = "moretrees", 466 | }) 467 | 468 | end 469 | --------------------------------------------------------------------------------