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