07071c74c4a28a56a4d664d5915eccd737f96b00
[oweals/minetest_game.git] / mods / default / trees.lua
1 local random = math.random
2
3 --
4 -- Grow trees from saplings
5 --
6
7 -- 'can grow' function
8
9 function default.can_grow(pos)
10         local node_under = minetest.get_node_or_nil({x = pos.x, y = pos.y - 1, z = pos.z})
11         if not node_under then
12                 return false
13         end
14         local name_under = node_under.name
15         local is_soil = minetest.get_item_group(name_under, "soil")
16         if is_soil == 0 then
17                 return false
18         end
19         local light_level = minetest.get_node_light(pos)
20         if not light_level or light_level < 13 then
21                 return false
22         end
23         return true
24 end
25
26
27 -- 'is snow nearby' function
28
29 local function is_snow_nearby(pos)
30         return #minetest.find_nodes_in_area(
31                 {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
32                 {x = pos.x + 1, y = pos.y + 1, z = pos.z + 1},
33                 {"default:snow", "default:snowblock", "default:dirt_with_snow"}) > 0
34 end
35
36
37 -- Sapling ABM
38
39 function default.grow_sapling(pos)
40         if not default.can_grow(pos) then
41                 -- try a bit later again
42                 minetest.get_node_timer(pos):start(math.random(240, 600))
43                 return
44         end
45
46         local mapgen = minetest.get_mapgen_params().mgname
47         local node = minetest.get_node(pos)
48         if node.name == "default:sapling" then
49                 minetest.log("action", "A sapling grows into a tree at "..
50                         minetest.pos_to_string(pos))
51                 if mapgen == "v6" then
52                         default.grow_tree(pos, random(1, 4) == 1)
53                 else
54                         default.grow_new_apple_tree(pos)
55                 end
56         elseif node.name == "default:junglesapling" then
57                 minetest.log("action", "A jungle sapling grows into a tree at "..
58                         minetest.pos_to_string(pos))
59                 if mapgen == "v6" then
60                         default.grow_jungle_tree(pos)
61                 else
62                         default.grow_new_jungle_tree(pos)
63                 end
64         elseif node.name == "default:pine_sapling" then
65                 minetest.log("action", "A pine sapling grows into a tree at "..
66                         minetest.pos_to_string(pos))
67                 local snow = is_snow_nearby(pos)
68                 if mapgen == "v6" then
69                         default.grow_pine_tree(pos, snow)
70                 elseif snow then
71                         default.grow_new_snowy_pine_tree(pos)
72                 else
73                         default.grow_new_pine_tree(pos)
74                 end
75         elseif node.name == "default:acacia_sapling" then
76                 minetest.log("action", "An acacia sapling grows into a tree at "..
77                         minetest.pos_to_string(pos))
78                 default.grow_new_acacia_tree(pos)
79         elseif node.name == "default:aspen_sapling" then
80                 minetest.log("action", "An aspen sapling grows into a tree at "..
81                         minetest.pos_to_string(pos))
82                 default.grow_new_aspen_tree(pos)
83         end
84 end
85
86 minetest.register_lbm({
87         name = "default:convert_saplings_to_node_timer",
88         nodenames = {"default:sapling", "default:junglesapling",
89                         "default:pine_sapling", "default:acacia_sapling",
90                         "default:aspen_sapling"},
91         action = function(pos)
92                 minetest.get_node_timer(pos):start(math.random(1200, 2400))
93         end
94 })
95
96 --
97 -- Tree generation
98 --
99
100 -- Apple tree and jungle tree trunk and leaves function
101
102 local function add_trunk_and_leaves(data, a, pos, tree_cid, leaves_cid,
103                 height, size, iters, is_apple_tree)
104         local x, y, z = pos.x, pos.y, pos.z
105         local c_air = minetest.get_content_id("air")
106         local c_ignore = minetest.get_content_id("ignore")
107         local c_apple = minetest.get_content_id("default:apple")
108
109         -- Trunk
110         data[a:index(x, y, z)] = tree_cid -- Force-place lowest trunk node to replace sapling
111         for yy = y + 1, y + height - 1 do
112                 local vi = a:index(x, yy, z)
113                 local node_id = data[vi]
114                 if node_id == c_air or node_id == c_ignore or node_id == leaves_cid then
115                         data[vi] = tree_cid
116                 end
117         end
118
119         -- Force leaves near the trunk
120         for z_dist = -1, 1 do
121         for y_dist = -size, 1 do
122                 local vi = a:index(x - 1, y + height + y_dist, z + z_dist)
123                 for x_dist = -1, 1 do
124                         if data[vi] == c_air or data[vi] == c_ignore then
125                                 if is_apple_tree and random(1, 8) == 1 then
126                                         data[vi] = c_apple
127                                 else
128                                         data[vi] = leaves_cid
129                                 end
130                         end
131                         vi = vi + 1
132                 end
133         end
134         end
135
136         -- Randomly add leaves in 2x2x2 clusters.
137         for i = 1, iters do
138                 local clust_x = x + random(-size, size - 1)
139                 local clust_y = y + height + random(-size, 0)
140                 local clust_z = z + random(-size, size - 1)
141
142                 for xi = 0, 1 do
143                 for yi = 0, 1 do
144                 for zi = 0, 1 do
145                         local vi = a:index(clust_x + xi, clust_y + yi, clust_z + zi)
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                 end
154                 end
155                 end
156         end
157 end
158
159
160 -- Apple tree
161
162 function default.grow_tree(pos, is_apple_tree, bad)
163         --[[
164                 NOTE: Tree-placing code is currently duplicated in the engine
165                 and in games that have saplings; both are deprecated but not
166                 replaced yet
167         --]]
168         if bad then
169                 error("Deprecated use of default.grow_tree")
170         end
171
172         local x, y, z = pos.x, pos.y, pos.z
173         local height = random(4, 5)
174         local c_tree = minetest.get_content_id("default:tree")
175         local c_leaves = minetest.get_content_id("default:leaves")
176
177         local vm = minetest.get_voxel_manip()
178         local minp, maxp = vm:read_from_map(
179                 {x = x - 2, y = y, z = z - 2},
180                 {x = x + 2, y = y + height + 1, z = z + 2}
181         )
182         local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
183         local data = vm:get_data()
184
185         add_trunk_and_leaves(data, a, pos, c_tree, c_leaves, height, 2, 8, is_apple_tree)
186
187         vm:set_data(data)
188         vm:write_to_map()
189         vm:update_map()
190 end
191
192
193 -- Jungle tree
194
195 function default.grow_jungle_tree(pos, bad)
196         --[[
197                 NOTE: Jungletree-placing code is currently duplicated in the engine
198                 and in games that have saplings; both are deprecated but not
199                 replaced yet
200         --]]
201         if bad then
202                 error("Deprecated use of default.grow_jungle_tree")
203         end
204
205         local x, y, z = pos.x, pos.y, pos.z
206         local height = random(8, 12)
207         local c_air = minetest.get_content_id("air")
208         local c_ignore = minetest.get_content_id("ignore")
209         local c_jungletree = minetest.get_content_id("default:jungletree")
210         local c_jungleleaves = minetest.get_content_id("default:jungleleaves")
211
212         local vm = minetest.get_voxel_manip()
213         local minp, maxp = vm:read_from_map(
214                 {x = x - 3, y = y - 1, z = z - 3},
215                 {x = x + 3, y = y + height + 1, z = z + 3}
216         )
217         local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
218         local data = vm:get_data()
219
220         add_trunk_and_leaves(data, a, pos, c_jungletree, c_jungleleaves,
221                 height, 3, 30, false)
222
223         -- Roots
224         for z_dist = -1, 1 do
225                 local vi_1 = a:index(x - 1, y - 1, z + z_dist)
226                 local vi_2 = a:index(x - 1, y, z + z_dist)
227                 for x_dist = -1, 1 do
228                         if random(1, 3) >= 2 then
229                                 if data[vi_1] == c_air or data[vi_1] == c_ignore then
230                                         data[vi_1] = c_jungletree
231                                 elseif data[vi_2] == c_air or data[vi_2] == c_ignore then
232                                         data[vi_2] = c_jungletree
233                                 end
234                         end
235                         vi_1 = vi_1 + 1
236                         vi_2 = vi_2 + 1
237                 end
238         end
239
240         vm:set_data(data)
241         vm:write_to_map()
242         vm:update_map()
243 end
244
245
246 -- Pine tree from mg mapgen mod, design by sfan5, pointy top added by paramat
247
248 local function add_pine_needles(data, vi, c_air, c_ignore, c_snow, c_pine_needles)
249         local node_id = data[vi]
250         if node_id == c_air or node_id == c_ignore or node_id == c_snow then
251                 data[vi] = c_pine_needles
252         end
253 end
254
255 local function add_snow(data, vi, c_air, c_ignore, c_snow)
256         local node_id = data[vi]
257         if node_id == c_air or node_id == c_ignore then
258                 data[vi] = c_snow
259         end
260 end
261
262 function default.grow_pine_tree(pos, snow)
263         local x, y, z = pos.x, pos.y, pos.z
264         local maxy = y + random(9, 13) -- Trunk top
265
266         local c_air = minetest.get_content_id("air")
267         local c_ignore = minetest.get_content_id("ignore")
268         local c_pine_tree = minetest.get_content_id("default:pine_tree")
269         local c_pine_needles  = minetest.get_content_id("default:pine_needles")
270         local c_snow = minetest.get_content_id("default:snow")
271
272         local vm = minetest.get_voxel_manip()
273         local minp, maxp = vm:read_from_map(
274                 {x = x - 3, y = y, z = z - 3},
275                 {x = x + 3, y = maxy + 3, z = z + 3}
276         )
277         local a = VoxelArea:new({MinEdge = minp, MaxEdge = maxp})
278         local data = vm:get_data()
279
280         -- Upper branches layer
281         local dev = 3
282         for yy = maxy - 1, maxy + 1 do
283                 for zz = z - dev, z + dev do
284                         local vi = a:index(x - dev, yy, zz)
285                         local via = a:index(x - dev, yy + 1, zz)
286                         for xx = x - dev, x + dev do
287                                 if random() < 0.95 - dev * 0.05 then
288                                         add_pine_needles(data, vi, c_air, c_ignore, c_snow,
289                                                 c_pine_needles)
290                                         if snow then
291                                                 add_snow(data, via, c_air, c_ignore, c_snow)
292                                         end
293                                 end
294                                 vi  = vi + 1
295                                 via = via + 1
296                         end
297                 end
298                 dev = dev - 1
299         end
300
301         -- Centre top nodes
302         add_pine_needles(data, a:index(x, maxy + 1, z), c_air, c_ignore, c_snow,
303                 c_pine_needles)
304         add_pine_needles(data, a:index(x, maxy + 2, z), c_air, c_ignore, c_snow,
305                 c_pine_needles) -- Paramat added a pointy top node
306         if snow then
307                 add_snow(data, a:index(x, maxy + 3, z), c_air, c_ignore, c_snow)
308         end
309
310         -- Lower branches layer
311         local my = 0
312         for i = 1, 20 do -- Random 2x2 squares of needles
313                 local xi = x + random(-3, 2)
314                 local yy = maxy + random(-6, -5)
315                 local zi = z + random(-3, 2)
316                 if yy > my then
317                         my = yy
318                 end
319                 for zz = zi, zi+1 do
320                         local vi = a:index(xi, yy, zz)
321                         local via = a:index(xi, yy + 1, zz)
322                         for xx = xi, xi + 1 do
323                                 add_pine_needles(data, vi, c_air, c_ignore, c_snow,
324                                         c_pine_needles)
325                                 if snow then
326                                         add_snow(data, via, c_air, c_ignore, c_snow)
327                                 end
328                                 vi  = vi + 1
329                                 via = via + 1
330                         end
331                 end
332         end
333
334         dev = 2
335         for yy = my + 1, my + 2 do
336                 for zz = z - dev, z + dev do
337                         local vi = a:index(x - dev, yy, zz)
338                         local via = a:index(x - dev, yy + 1, zz)
339                         for xx = x - dev, x + dev do
340                                 if random() < 0.95 - dev * 0.05 then
341                                         add_pine_needles(data, vi, c_air, c_ignore, c_snow,
342                                                 c_pine_needles)
343                                         if snow then
344                                                 add_snow(data, via, c_air, c_ignore, c_snow)
345                                         end
346                                 end
347                                 vi  = vi + 1
348                                 via = via + 1
349                         end
350                 end
351                 dev = dev - 1
352         end
353
354         -- Trunk
355         -- Force-place lowest trunk node to replace sapling
356         data[a:index(x, y, z)] = c_pine_tree
357         for yy = y + 1, maxy do
358                 local vi = a:index(x, yy, z)
359                 local node_id = data[vi]
360                 if node_id == c_air or node_id == c_ignore or
361                                 node_id == c_pine_needles or node_id == c_snow then
362                         data[vi] = c_pine_tree
363                 end
364         end
365
366         vm:set_data(data)
367         vm:write_to_map()
368         vm:update_map()
369 end
370
371
372 -- New apple tree
373
374 function default.grow_new_apple_tree(pos)
375         local path = minetest.get_modpath("default") ..
376                 "/schematics/apple_tree_from_sapling.mts"
377         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
378                 path, "0", nil, false)
379 end
380
381
382 -- New jungle tree
383
384 function default.grow_new_jungle_tree(pos)
385         local path = minetest.get_modpath("default") ..
386                 "/schematics/jungle_tree_from_sapling.mts"
387         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
388                 path, "random", nil, false)
389 end
390
391
392 -- New pine tree
393
394 function default.grow_new_pine_tree(pos)
395         local path = minetest.get_modpath("default") ..
396                 "/schematics/pine_tree_from_sapling.mts"
397         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
398                 path, "0", nil, false)
399 end
400
401
402 -- New snowy pine tree
403
404 function default.grow_new_snowy_pine_tree(pos)
405         local path = minetest.get_modpath("default") ..
406                 "/schematics/snowy_pine_tree_from_sapling.mts"
407         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
408                 path, "random", nil, false)
409 end
410
411
412 -- New acacia tree
413
414 function default.grow_new_acacia_tree(pos)
415         local path = minetest.get_modpath("default") ..
416                 "/schematics/acacia_tree_from_sapling.mts"
417         minetest.place_schematic({x = pos.x - 4, y = pos.y - 1, z = pos.z - 4},
418                 path, "random", nil, false)
419 end
420
421 -- New aspen tree
422
423 function default.grow_new_aspen_tree(pos)
424         local path = minetest.get_modpath("default") ..
425                 "/schematics/aspen_tree_from_sapling.mts"
426         minetest.place_schematic({x = pos.x - 2, y = pos.y - 1, z = pos.z - 2},
427                 path, "0", nil, false)
428 end