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