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