├── .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 | 
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 |
--------------------------------------------------------------------------------