Make can_interact_with_node() check for key group instead of default:key
[oweals/minetest_game.git] / mods / default / trees.lua
1 -- default/trees.lua
2
3 -- support for MT game translation.
4 local S = default.get_translator
5
6 local random = math.random
7
8 --
9 -- Grow trees from saplings
10 --
11
12 -- 'can grow' function
13
14 function default.can_grow(pos)
15         local node_under = minetest.get_node_or_nil({x = pos.x, y = pos.y - 1, z = pos.z})
16         if not node_under then
17                 return false
18         end
19         local name_under = node_under.name
20         local is_soil = minetest.get_item_group(name_under, "soil")
21         if is_soil == 0 then
22                 return false
23         end
24         local light_level = minetest.get_node_light(pos)
25         if not light_level or light_level < 13 then
26                 return false
27         end
28         return true
29 end
30
31
32 -- 'is snow nearby' function
33
34 local function is_snow_nearby(pos)
35         return minetest.find_node_near(pos, 1, {"group:snowy"})
36 end
37
38
39 -- Grow sapling
40
41 function default.grow_sapling(pos)
42         if not default.can_grow(pos) then
43                 -- try again 5 min later
44                 minetest.get_node_timer(pos):start(300)
45                 return
46         end
47
48         local mg_name = minetest.get_mapgen_setting("mg_name")
49         local node = minetest.get_node(pos)
50         if node.name == "default:sapling" then
51                 minetest.log("action", "A sapling grows into a tree at "..
52                         minetest.pos_to_string(pos))
53                 if mg_name == "v6" then
54                         default.grow_tree(pos, random(1, 4) == 1)
55                 else
56                         default.grow_new_apple_tree(pos)
57                 end
58         elseif node.name == "default:junglesapling" then
59                 minetest.log("action", "A jungle sapling grows into a tree at "..
60                         minetest.pos_to_string(pos))
61                 if mg_name == "v6" then
62                         default.grow_jungle_tree(pos)
63                 else
64                         default.grow_new_jungle_tree(pos)
65                 end
66         elseif node.name == "default:pine_sapling" then
67                 minetest.log("action", "A pine sapling grows into a tree at "..
68                         minetest.pos_to_string(pos))
69                 local snow = is_snow_nearby(pos)
70                 if mg_name == "v6" then
71                         default.grow_pine_tree(pos, snow)
72                 elseif snow then
73                         default.grow_new_snowy_pine_tree(pos)
74                 else
75                         default.grow_new_pine_tree(pos)
76                 end
77         elseif node.name == "default:acacia_sapling" then
78                 minetest.log("action", "An acacia sapling grows into a tree at "..
79                         minetest.pos_to_string(pos))
80                 default.grow_new_acacia_tree(pos)
81         elseif node.name == "default:aspen_sapling" then
82                 minetest.log("action", "An aspen sapling grows into a tree at "..
83                         minetest.pos_to_string(pos))
84                 default.grow_new_aspen_tree(pos)
85         elseif node.name == "default:bush_sapling" then
86                 minetest.log("action", "A bush sapling grows into a bush at "..
87                         minetest.pos_to_string(pos))
88                 default.grow_bush(pos)
89         elseif node.name == "default:blueberry_bush_sapling" then
90                 minetest.log("action", "A blueberry bush sapling grows into a bush at "..
91                         minetest.pos_to_string(pos))
92                 default.grow_blueberry_bush(pos)
93         elseif node.name == "default:acacia_bush_sapling" then
94                 minetest.log("action", "An acacia bush sapling grows into a bush at "..
95                         minetest.pos_to_string(pos))
96                 default.grow_acacia_bush(pos)
97         elseif node.name == "default:pine_bush_sapling" then
98                 minetest.log("action", "A pine bush sapling grows into a bush at "..
99                         minetest.pos_to_string(pos))
100                 default.grow_pine_bush(pos)
101         elseif node.name == "default:emergent_jungle_sapling" then
102                 minetest.log("action", "An emergent jungle sapling grows into a tree at "..
103                         minetest.pos_to_string(pos))
104                 default.grow_new_emergent_jungle_tree(pos)
105         end
106 end
107
108 minetest.register_lbm({
109         name = "default:convert_saplings_to_node_timer",
110         nodenames = {"default:sapling", "default:junglesapling",
111                         "default:pine_sapling", "default:acacia_sapling",
112                         "default:aspen_sapling"},
113         action = function(pos)
114                 minetest.get_node_timer(pos):start(math.random(300, 1500))
115         end
116 })
117
118 --
119 -- Tree generation
120 --
121
122 -- Apple tree and jungle tree trunk and leaves function
123
124 local function add_trunk_and_leaves(data, a, pos, tree_cid, leaves_cid,
125                 height, size, iters, is_apple_tree)
126         local x, y, z = pos.x, pos.y, pos.z
127         local c_air = minetest.get_content_id("air")
128         local c_ignore = minetest.get_content_id("ignore")
129         local c_apple = minetest.get_content_id("default:apple")
130
131         -- Trunk
132         data[a:index(x, y, z)] = tree_cid -- Force-place lowest trunk node to replace sapling
133         for yy = y + 1, y + height - 1 do
134                 local vi = a:index(x, yy, z)
135                 local node_id = data[vi]
136                 if node_id == c_air or node_id == c_ignore or node_id == leaves_cid then
137                         data[vi] = tree_cid
138                 end
139         end
140
141         -- Force leaves near the trunk
142         for z_dist = -1, 1 do
143         for y_dist = -size, 1 do
144                 local vi = a:index(x - 1, y + height + y_dist, z + z_dist)
145                 for x_dist = -1, 1 do
146                         if data[vi] == c_air or data[vi] == c_ignore then
147                                 if is_apple_tree and random(1, 8) == 1 then
148                                         data[vi] = c_apple
149                                 else
150                                         data[vi] = leaves_cid
151                                 end
152                         end
153                         vi = vi + 1
154                 end
155         end
156         end
157
158         -- Randomly add leaves in 2x2x2 clusters.
159         for i = 1, iters do
160                 local clust_x = x + random(-size, size - 1)
161                 local clust_y = y + height + random(-size, 0)
162                 local clust_z = z + random(-size, size - 1)
163
164                 for xi = 0, 1 do
165                 for yi = 0, 1 do
166                 for zi = 0, 1 do
167                         local vi = a:index(clust_x + xi, clust_y + yi, clust_z + zi)
168                         if data[vi] == c_air or data[vi] == c_ignore then
169                                 if is_apple_tree and random(1, 8) == 1 then
170                                         data[vi] = c_apple
171                                 else
172                                         data[vi] = leaves_cid
173                                 end
174                         end
175                 end
176                 end
177                 end
178         end
179 end
180
181
182 -- Apple tree
183
184 function default.grow_tree(pos, is_apple_tree, bad)
185         --[[
186                 NOTE: Tree-placing code is currently duplicated in the engine
187                 and in games that have saplings; both are deprecated but not
188                 replaced yet
189         --]]
190         if bad then
191                 error("Deprecated use of default.grow_tree")
192         end
193
194         local x, y, z = pos.x, pos.y, pos.z
195         local height = random(4, 5)
196         local c_tree = minetest.get_content_id("default:tree")
197         local c_leaves = minetest.get_content_id("default:leaves")
198
199         local vm = minetest.get_voxel_manip()
200         local minp, maxp = vm:read_from_map(
201                 {x = x - 2, y = y, z = z - 2},
202                 {x = x + 2, y = y + height + 1, z = z + 2}
203         )
204         local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
205         local data = vm:get_data()
206
207         add_trunk_and_leaves(data, a, pos, c_tree, c_leaves, height, 2, 8, is_apple_tree)
208
209         vm:set_data(data)
210         vm:write_to_map()
211         vm:update_map()
212 end
213
214
215 -- Jungle tree
216
217 function default.grow_jungle_tree(pos, bad)
218         --[[
219                 NOTE: Jungletree-placing code is currently duplicated in the engine
220                 and in games that have saplings; both are deprecated but not
221                 replaced yet
222         --]]
223         if bad then
224                 error("Deprecated use of default.grow_jungle_tree")
225         end
226
227         local x, y, z = pos.x, pos.y, pos.z
228         local height = random(8, 12)
229         local c_air = minetest.get_content_id("air")
230         local c_ignore = minetest.get_content_id("ignore")
231         local c_jungletree = minetest.get_content_id("default:jungletree")
232         local c_jungleleaves = minetest.get_content_id("default:jungleleaves")
233
234         local vm = minetest.get_voxel_manip()
235         local minp, maxp = vm:read_from_map(
236                 {x = x - 3, y = y - 1, z = z - 3},
237                 {x = x + 3, y = y + height + 1, z = z + 3}
238         )
239         local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
240         local data = vm:get_data()
241
242         add_trunk_and_leaves(data, a, pos, c_jungletree, c_jungleleaves,
243                 height, 3, 30, false)
244
245         -- Roots
246         for z_dist = -1, 1 do
247                 local vi_1 = a:index(x - 1, y - 1, z + z_dist)
248                 local vi_2 = a:index(x - 1, y, z + z_dist)
249                 for x_dist = -1, 1 do
250                         if random(1, 3) >= 2 then
251                                 if data[vi_1] == c_air or data[vi_1] == c_ignore then
252                                         data[vi_1] = c_jungletree
253                                 elseif data[vi_2] == c_air or data[vi_2] == c_ignore then
254                                         data[vi_2] = c_jungletree
255                                 end
256                         end
257                         vi_1 = vi_1 + 1
258                         vi_2 = vi_2 + 1
259                 end
260         end
261
262         vm:set_data(data)
263         vm:write_to_map()
264         vm:update_map()
265 end
266
267
268 -- Pine tree from mg mapgen mod, design by sfan5, pointy top added by paramat
269
270 local function add_pine_needles(data, vi, c_air, c_ignore, c_snow, c_pine_needles)
271         local node_id = data[vi]
272         if node_id == c_air or node_id == c_ignore or node_id == c_snow then
273                 data[vi] = c_pine_needles
274         end
275 end
276
277 local function add_snow(data, vi, c_air, c_ignore, c_snow)
278         local node_id = data[vi]
279         if node_id == c_air or node_id == c_ignore then
280                 data[vi] = c_snow
281         end
282 end
283
284 function default.grow_pine_tree(pos, snow)
285         local x, y, z = pos.x, pos.y, pos.z
286         local maxy = y + random(9, 13) -- Trunk top
287
288         local c_air = minetest.get_content_id("air")
289         local c_ignore = minetest.get_content_id("ignore")
290         local c_pine_tree = minetest.get_content_id("default:pine_tree")
291         local c_pine_needles  = minetest.get_content_id("default:pine_needles")
292         local c_snow = minetest.get_content_id("default:snow")
293
294         local vm = minetest.get_voxel_manip()
295         local minp, maxp = vm:read_from_map(
296                 {x = x - 3, y = y, z = z - 3},
297                 {x = x + 3, y = maxy + 3, z = z + 3}
298         )
299         local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
300         local data = vm:get_data()
301
302         -- Upper branches layer
303         local dev = 3
304         for yy = maxy - 1, maxy + 1 do
305                 for zz = z - dev, z + dev do
306                         local vi = a:index(x - dev, yy, zz)
307                         local via = a:index(x - dev, yy + 1, zz)
308                         for xx = x - dev, x + dev do
309                                 if random() < 0.95 - dev * 0.05 then
310                                         add_pine_needles(data, vi, c_air, c_ignore, c_snow,
311                                                 c_pine_needles)
312                                         if snow then
313                                                 add_snow(data, via, c_air, c_ignore, c_snow)
314                                         end
315                                 end
316                                 vi  = vi + 1
317                                 via = via + 1
318                         end
319                 end
320                 dev = dev - 1
321         end
322
323         -- Centre top nodes
324         add_pine_needles(data, a:index(x, maxy + 1, z), c_air, c_ignore, c_snow,
325                 c_pine_needles)
326         add_pine_needles(data, a:index(x, maxy + 2, z), c_air, c_ignore, c_snow,
327                 c_pine_needles) -- Paramat added a pointy top node
328         if snow then
329                 add_snow(data, a:index(x, maxy + 3, z), c_air, c_ignore, c_snow)
330         end
331
332         -- Lower branches layer
333         local my = 0
334         for i = 1, 20 do -- Random 2x2 squares of needles
335                 local xi = x + random(-3, 2)
336                 local yy = maxy + random(-6, -5)
337                 local zi = z + random(-3, 2)
338                 if yy > my then
339                         my = yy
340                 end
341                 for zz = zi, zi+1 do
342                         local vi = a:index(xi, yy, zz)
343                         local via = a:index(xi, yy + 1, zz)
344                         for xx = xi, xi + 1 do
345                                 add_pine_needles(data, vi, c_air, c_ignore, c_snow,
346                                         c_pine_needles)
347                                 if snow then
348                                         add_snow(data, via, c_air, c_ignore, c_snow)
349                                 end
350                                 vi  = vi + 1
351                                 via = via + 1
352                         end
353                 end
354         end
355
356         dev = 2
357         for yy = my + 1, my + 2 do
358                 for zz = z - dev, z + dev do
359                         local vi = a:index(x - dev, yy, zz)
360                         local via = a:index(x - dev, yy + 1, zz)
361                         for xx = x - dev, x + dev do
362                                 if random() < 0.95 - dev * 0.05 then
363                                         add_pine_needles(data, vi, c_air, c_ignore, c_snow,
364                                                 c_pine_needles)
365                                         if snow then
366                                                 add_snow(data, via, c_air, c_ignore, c_snow)
367                                         end
368                                 end
369                                 vi  = vi + 1
370                                 via = via + 1
371                         end
372                 end
373                 dev = dev - 1
374         end
375
376         -- Trunk
377         -- Force-place lowest trunk node to replace sapling
378         data[a:index(x, y, z)] = c_pine_tree
379         for yy = y + 1, maxy do
380                 local vi = a:index(x, yy, z)
381                 local node_id = data[vi]
382                 if node_id == c_air or node_id == c_ignore or
383                                 node_id == c_pine_needles or node_id == c_snow then
384                         data[vi] = c_pine_tree
385                 end
386         end
387
388         vm:set_data(data)
389         vm:write_to_map()
390         vm:update_map()
391 end
392
393
394 -- New apple tree
395
396 function default.grow_new_apple_tree(pos)
397         local path = minetest.get_modpath("default") ..
398                 "/schematics/apple_tree_from_sapling.mts"
399         minetest.place_schematic({x = pos.x - 3, y = pos.y - 1, z = pos.z - 3},
400                 path, "random", nil, false)
401 end
402
403
404 -- New jungle tree
405
406 function default.grow_new_jungle_tree(pos)
407         local path = minetest.get_modpath("default") ..
408                 "/schematics/jungle_tree_from_sapling.mts"
409         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
410                 path, "random", nil, false)
411 end
412
413
414 -- New emergent jungle tree
415
416 function default.grow_new_emergent_jungle_tree(pos)
417         local path = minetest.get_modpath("default") ..
418                 "/schematics/emergent_jungle_tree_from_sapling.mts"
419         minetest.place_schematic({x = pos.x - 3, y = pos.y - 5, z = pos.z - 3},
420                 path, "random", nil, false)
421 end
422
423
424 -- New pine tree
425
426 function default.grow_new_pine_tree(pos)
427         local path
428         if math.random() > 0.5 then
429                 path = minetest.get_modpath("default") ..
430                         "/schematics/pine_tree_from_sapling.mts"
431         else
432                 path = minetest.get_modpath("default") ..
433                         "/schematics/small_pine_tree_from_sapling.mts"
434         end
435         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
436                 path, "0", nil, false)
437 end
438
439
440 -- New snowy pine tree
441
442 function default.grow_new_snowy_pine_tree(pos)
443         local path
444         if math.random() > 0.5 then
445                 path = minetest.get_modpath("default") ..
446                         "/schematics/snowy_pine_tree_from_sapling.mts"
447         else
448                 path = minetest.get_modpath("default") ..
449                         "/schematics/snowy_small_pine_tree_from_sapling.mts"
450         end
451         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
452                 path, "random", nil, false)
453 end
454
455
456 -- New acacia tree
457
458 function default.grow_new_acacia_tree(pos)
459         local path = minetest.get_modpath("default") ..
460                 "/schematics/acacia_tree_from_sapling.mts"
461         minetest.place_schematic({x = pos.x - 4, y = pos.y - 1, z = pos.z - 4},
462                 path, "random", nil, false)
463 end
464
465
466 -- New aspen tree
467
468 function default.grow_new_aspen_tree(pos)
469         local path = minetest.get_modpath("default") ..
470                 "/schematics/aspen_tree_from_sapling.mts"
471         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
472                 path, "0", nil, false)
473 end
474
475
476 -- Bushes do not need 'from sapling' schematic variants because
477 -- only the stem node is force-placed in the schematic.
478
479 -- Bush
480
481 function default.grow_bush(pos)
482         local path = minetest.get_modpath("default") ..
483                 "/schematics/bush.mts"
484         minetest.place_schematic({x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
485                 path, "0", nil, false)
486 end
487
488 -- Blueberry bush
489
490 function default.grow_blueberry_bush(pos)
491         local path = minetest.get_modpath("default") ..
492                 "/schematics/blueberry_bush.mts"
493         minetest.place_schematic({x = pos.x - 1, y = pos.y, z = pos.z - 1},
494                 path, "0", nil, false)
495 end
496
497
498 -- Acacia bush
499
500 function default.grow_acacia_bush(pos)
501         local path = minetest.get_modpath("default") ..
502                 "/schematics/acacia_bush.mts"
503         minetest.place_schematic({x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
504                 path, "0", nil, false)
505 end
506
507
508 -- Pine bush
509
510 function default.grow_pine_bush(pos)
511         local path = minetest.get_modpath("default") ..
512                 "/schematics/pine_bush.mts"
513         minetest.place_schematic({x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
514                 path, "0", nil, false)
515 end
516
517
518 -- Large cactus
519
520 function default.grow_large_cactus(pos)
521         local path = minetest.get_modpath("default") ..
522                 "/schematics/large_cactus.mts"
523         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
524                 path, "random", nil, false)
525 end
526
527
528 --
529 -- Sapling 'on place' function to check protection of node and resulting tree volume
530 --
531
532 function default.sapling_on_place(itemstack, placer, pointed_thing,
533                 sapling_name, minp_relative, maxp_relative, interval)
534         -- Position of sapling
535         local pos = pointed_thing.under
536         local node = minetest.get_node_or_nil(pos)
537         local pdef = node and minetest.registered_nodes[node.name]
538
539         if pdef and pdef.on_rightclick and
540                         not (placer and placer:is_player() and
541                         placer:get_player_control().sneak) then
542                 return pdef.on_rightclick(pos, node, placer, itemstack, pointed_thing)
543         end
544
545         if not pdef or not pdef.buildable_to then
546                 pos = pointed_thing.above
547                 node = minetest.get_node_or_nil(pos)
548                 pdef = node and minetest.registered_nodes[node.name]
549                 if not pdef or not pdef.buildable_to then
550                         return itemstack
551                 end
552         end
553
554         local player_name = placer and placer:get_player_name() or ""
555         -- Check sapling position for protection
556         if minetest.is_protected(pos, player_name) then
557                 minetest.record_protection_violation(pos, player_name)
558                 return itemstack
559         end
560         -- Check tree volume for protection
561         if minetest.is_area_protected(
562                         vector.add(pos, minp_relative),
563                         vector.add(pos, maxp_relative),
564                         player_name,
565                         interval) then
566                 minetest.record_protection_violation(pos, player_name)
567                 -- Print extra information to explain
568 --              minetest.chat_send_player(player_name,
569 --                      itemstack:get_definition().description .. " will intersect protection " ..
570 --                      "on growth")
571                 minetest.chat_send_player(player_name,
572                     S("@1 will intersect protection on growth.",
573                         itemstack:get_definition().description))
574                 return itemstack
575         end
576
577         minetest.log("action", player_name .. " places node "
578                         .. sapling_name .. " at " .. minetest.pos_to_string(pos))
579
580         local take_item = not (creative and creative.is_enabled_for
581                 and creative.is_enabled_for(player_name))
582         local newnode = {name = sapling_name}
583         local ndef = minetest.registered_nodes[sapling_name]
584         minetest.set_node(pos, newnode)
585
586         -- Run callback
587         if ndef and ndef.after_place_node then
588                 -- Deepcopy place_to and pointed_thing because callback can modify it
589                 if ndef.after_place_node(table.copy(pos), placer,
590                                 itemstack, table.copy(pointed_thing)) then
591                         take_item = false
592                 end
593         end
594
595         -- Run script hook
596         for _, callback in ipairs(minetest.registered_on_placenodes) do
597                 -- Deepcopy pos, node and pointed_thing because callback can modify them
598                 if callback(table.copy(pos), table.copy(newnode),
599                                 placer, table.copy(node or {}),
600                                 itemstack, table.copy(pointed_thing)) then
601                         take_item = false
602                 end
603         end
604
605         if take_item then
606                 itemstack:take_item()
607         end
608
609         return itemstack
610 end