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