1--
2-- Sounds
3--
4
5function default.node_sound_defaults(table)
6	table = table or {}
7	table.footstep = table.footstep or
8			{name = "", gain = 1.0}
9	table.dug = table.dug or
10			{name = "default_dug_node", gain = 0.25}
11	table.place = table.place or
12			{name = "default_place_node_hard", gain = 1.0}
13	return table
14end
15
16function default.node_sound_stone_defaults(table)
17	table = table or {}
18	table.footstep = table.footstep or
19			{name = "default_hard_footstep", gain = 0.3}
20	table.dug = table.dug or
21			{name = "default_hard_footstep", gain = 1.0}
22	default.node_sound_defaults(table)
23	return table
24end
25
26function default.node_sound_dirt_defaults(table)
27	table = table or {}
28	table.footstep = table.footstep or
29			{name = "default_dirt_footstep", gain = 0.4}
30	table.dug = table.dug or
31			{name = "default_dirt_footstep", gain = 1.0}
32	table.place = table.place or
33			{name = "default_place_node", gain = 1.0}
34	default.node_sound_defaults(table)
35	return table
36end
37
38function default.node_sound_sand_defaults(table)
39	table = table or {}
40	table.footstep = table.footstep or
41			{name = "default_sand_footstep", gain = 0.05}
42	table.dug = table.dug or
43			{name = "default_sand_footstep", gain = 0.15}
44	table.place = table.place or
45			{name = "default_place_node", gain = 1.0}
46	default.node_sound_defaults(table)
47	return table
48end
49
50function default.node_sound_gravel_defaults(table)
51	table = table or {}
52	table.footstep = table.footstep or
53			{name = "default_gravel_footstep", gain = 0.1}
54	table.dig = table.dig or
55			{name = "default_gravel_dig", gain = 0.35}
56	table.dug = table.dug or
57			{name = "default_gravel_dug", gain = 1.0}
58	table.place = table.place or
59			{name = "default_place_node", gain = 1.0}
60	default.node_sound_defaults(table)
61	return table
62end
63
64function default.node_sound_wood_defaults(table)
65	table = table or {}
66	table.footstep = table.footstep or
67			{name = "default_wood_footstep", gain = 0.3}
68	table.dug = table.dug or
69			{name = "default_wood_footstep", gain = 1.0}
70	default.node_sound_defaults(table)
71	return table
72end
73
74function default.node_sound_leaves_defaults(table)
75	table = table or {}
76	table.footstep = table.footstep or
77			{name = "default_grass_footstep", gain = 0.45}
78	table.dug = table.dug or
79			{name = "default_grass_footstep", gain = 0.7}
80	table.place = table.place or
81			{name = "default_place_node", gain = 1.0}
82	default.node_sound_defaults(table)
83	return table
84end
85
86function default.node_sound_glass_defaults(table)
87	table = table or {}
88	table.footstep = table.footstep or
89			{name = "default_glass_footstep", gain = 0.3}
90	table.dig = table.dig or
91			{name = "default_glass_footstep", gain = 0.5}
92	table.dug = table.dug or
93			{name = "default_break_glass", gain = 1.0}
94	default.node_sound_defaults(table)
95	return table
96end
97
98function default.node_sound_ice_defaults(table)
99	table = table or {}
100	table.footstep = table.footstep or
101			{name = "default_ice_footstep", gain = 0.3}
102	table.dig = table.dig or
103			{name = "default_ice_dig", gain = 0.5}
104	table.dug = table.dug or
105			{name = "default_ice_dug", gain = 0.5}
106	default.node_sound_defaults(table)
107	return table
108end
109
110function default.node_sound_metal_defaults(table)
111	table = table or {}
112	table.footstep = table.footstep or
113			{name = "default_metal_footstep", gain = 0.4}
114	table.dig = table.dig or
115			{name = "default_dig_metal", gain = 0.5}
116	table.dug = table.dug or
117			{name = "default_dug_metal", gain = 0.5}
118	table.place = table.place or
119			{name = "default_place_node_metal", gain = 0.5}
120	default.node_sound_defaults(table)
121	return table
122end
123
124function default.node_sound_water_defaults(table)
125	table = table or {}
126	table.footstep = table.footstep or
127			{name = "default_water_footstep", gain = 0.2}
128	default.node_sound_defaults(table)
129	return table
130end
131
132function default.node_sound_snow_defaults(table)
133	table = table or {}
134	table.footstep = table.footstep or
135			{name = "default_snow_footstep", gain = 0.2}
136	table.dig = table.dig or
137			{name = "default_snow_footstep", gain = 0.3}
138	table.dug = table.dug or
139			{name = "default_snow_footstep", gain = 0.3}
140	table.place = table.place or
141			{name = "default_place_node", gain = 1.0}
142	default.node_sound_defaults(table)
143	return table
144end
145
146
147--
148-- Lavacooling
149--
150
151default.cool_lava = function(pos, node)
152	if node.name == "default:lava_source" then
153		minetest.set_node(pos, {name = "default:obsidian"})
154	else -- Lava flowing
155		minetest.set_node(pos, {name = "default:stone"})
156	end
157	minetest.sound_play("default_cool_lava",
158		{pos = pos, max_hear_distance = 16, gain = 0.25}, true)
159end
160
161if minetest.settings:get_bool("enable_lavacooling") ~= false then
162	minetest.register_abm({
163		label = "Lava cooling",
164		nodenames = {"default:lava_source", "default:lava_flowing"},
165		neighbors = {"group:cools_lava", "group:water"},
166		interval = 2,
167		chance = 2,
168		catch_up = false,
169		action = function(...)
170			default.cool_lava(...)
171		end,
172	})
173end
174
175
176--
177-- Optimized helper to put all items in an inventory into a drops list
178--
179
180function default.get_inventory_drops(pos, inventory, drops)
181	local inv = minetest.get_meta(pos):get_inventory()
182	local n = #drops
183	for i = 1, inv:get_size(inventory) do
184		local stack = inv:get_stack(inventory, i)
185		if stack:get_count() > 0 then
186			drops[n+1] = stack:to_table()
187			n = n + 1
188		end
189	end
190end
191
192
193--
194-- Papyrus and cactus growing
195--
196
197-- Wrapping the functions in ABM action is necessary to make overriding them possible
198
199function default.grow_cactus(pos, node)
200	if node.param2 >= 4 then
201		return
202	end
203	pos.y = pos.y - 1
204	if minetest.get_item_group(minetest.get_node(pos).name, "sand") == 0 then
205		return
206	end
207	pos.y = pos.y + 1
208	local height = 0
209	while node.name == "default:cactus" and height < 4 do
210		height = height + 1
211		pos.y = pos.y + 1
212		node = minetest.get_node(pos)
213	end
214	if height == 4 or node.name ~= "air" then
215		return
216	end
217	if minetest.get_node_light(pos) < 13 then
218		return
219	end
220	minetest.set_node(pos, {name = "default:cactus"})
221	return true
222end
223
224function default.grow_papyrus(pos, node)
225	pos.y = pos.y - 1
226	local name = minetest.get_node(pos).name
227	if name ~= "default:dirt" and
228			name ~= "default:dirt_with_grass" and
229			name ~= "default:dirt_with_dry_grass" and
230			name ~= "default:dirt_with_rainforest_litter" and
231			name ~= "default:dry_dirt" and
232			name ~= "default:dry_dirt_with_dry_grass" then
233		return
234	end
235	if not minetest.find_node_near(pos, 3, {"group:water"}) then
236		return
237	end
238	pos.y = pos.y + 1
239	local height = 0
240	while node.name == "default:papyrus" and height < 4 do
241		height = height + 1
242		pos.y = pos.y + 1
243		node = minetest.get_node(pos)
244	end
245	if height == 4 or node.name ~= "air" then
246		return
247	end
248	if minetest.get_node_light(pos) < 13 then
249		return
250	end
251	minetest.set_node(pos, {name = "default:papyrus"})
252	return true
253end
254
255minetest.register_abm({
256	label = "Grow cactus",
257	nodenames = {"default:cactus"},
258	neighbors = {"group:sand"},
259	interval = 12,
260	chance = 83,
261	action = function(...)
262		default.grow_cactus(...)
263	end
264})
265
266minetest.register_abm({
267	label = "Grow papyrus",
268	nodenames = {"default:papyrus"},
269	-- Grows on the dirt and surface dirt nodes of the biomes papyrus appears in,
270	-- including the old savanna nodes.
271	-- 'default:dirt_with_grass' is here only because it was allowed before.
272	neighbors = {
273		"default:dirt",
274		"default:dirt_with_grass",
275		"default:dirt_with_dry_grass",
276		"default:dirt_with_rainforest_litter",
277		"default:dry_dirt",
278		"default:dry_dirt_with_dry_grass",
279	},
280	interval = 14,
281	chance = 71,
282	action = function(...)
283		default.grow_papyrus(...)
284	end
285})
286
287
288--
289-- Dig upwards
290--
291
292function default.dig_up(pos, node, digger)
293	if digger == nil then return end
294	local np = {x = pos.x, y = pos.y + 1, z = pos.z}
295	local nn = minetest.get_node(np)
296	if nn.name == node.name then
297		minetest.node_dig(np, nn, digger)
298	end
299end
300
301
302--
303-- Fence registration helper
304--
305local fence_collision_extra = minetest.settings:get_bool("enable_fence_tall") and 3/8 or 0
306
307function default.register_fence(name, def)
308	minetest.register_craft({
309		output = name .. " 4",
310		recipe = {
311			{ def.material, 'group:stick', def.material },
312			{ def.material, 'group:stick', def.material },
313		}
314	})
315
316	local fence_texture = "default_fence_overlay.png^" .. def.texture ..
317			"^default_fence_overlay.png^[makealpha:255,126,126"
318	-- Allow almost everything to be overridden
319	local default_fields = {
320		paramtype = "light",
321		drawtype = "nodebox",
322		node_box = {
323			type = "connected",
324			fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8},
325			-- connect_top =
326			-- connect_bottom =
327			connect_front = {{-1/16,  3/16, -1/2,   1/16,  5/16, -1/8 },
328				         {-1/16, -5/16, -1/2,   1/16, -3/16, -1/8 }},
329			connect_left =  {{-1/2,   3/16, -1/16, -1/8,   5/16,  1/16},
330				         {-1/2,  -5/16, -1/16, -1/8,  -3/16,  1/16}},
331			connect_back =  {{-1/16,  3/16,  1/8,   1/16,  5/16,  1/2 },
332				         {-1/16, -5/16,  1/8,   1/16, -3/16,  1/2 }},
333			connect_right = {{ 1/8,   3/16, -1/16,  1/2,   5/16,  1/16},
334				         { 1/8,  -5/16, -1/16,  1/2,  -3/16,  1/16}}
335		},
336		collision_box = {
337			type = "connected",
338			fixed = {-1/8, -1/2, -1/8, 1/8, 1/2 + fence_collision_extra, 1/8},
339			-- connect_top =
340			-- connect_bottom =
341			connect_front = {-1/8, -1/2, -1/2,  1/8, 1/2 + fence_collision_extra, -1/8},
342			connect_left =  {-1/2, -1/2, -1/8, -1/8, 1/2 + fence_collision_extra,  1/8},
343			connect_back =  {-1/8, -1/2,  1/8,  1/8, 1/2 + fence_collision_extra,  1/2},
344			connect_right = { 1/8, -1/2, -1/8,  1/2, 1/2 + fence_collision_extra,  1/8}
345		},
346		connects_to = {"group:fence", "group:wood", "group:tree", "group:wall"},
347		inventory_image = fence_texture,
348		wield_image = fence_texture,
349		tiles = {def.texture},
350		sunlight_propagates = true,
351		is_ground_content = false,
352		groups = {},
353	}
354	for k, v in pairs(default_fields) do
355		if def[k] == nil then
356			def[k] = v
357		end
358	end
359
360	-- Always add to the fence group, even if no group provided
361	def.groups.fence = 1
362
363	def.texture = nil
364	def.material = nil
365
366	minetest.register_node(name, def)
367end
368
369
370--
371-- Fence rail registration helper
372--
373
374function default.register_fence_rail(name, def)
375	minetest.register_craft({
376		output = name .. " 16",
377		recipe = {
378			{ def.material, def.material },
379			{ "", ""},
380			{ def.material, def.material },
381		}
382	})
383
384	local fence_rail_texture = "default_fence_rail_overlay.png^" .. def.texture ..
385			"^default_fence_rail_overlay.png^[makealpha:255,126,126"
386	-- Allow almost everything to be overridden
387	local default_fields = {
388		paramtype = "light",
389		drawtype = "nodebox",
390		node_box = {
391			type = "connected",
392			fixed = {{-1/16,  3/16, -1/16, 1/16,  5/16, 1/16},
393				 {-1/16, -3/16, -1/16, 1/16, -5/16, 1/16}},
394			-- connect_top =
395			-- connect_bottom =
396			connect_front = {{-1/16,  3/16, -1/2,   1/16,  5/16, -1/16},
397				         {-1/16, -5/16, -1/2,   1/16, -3/16, -1/16}},
398			connect_left =  {{-1/2,   3/16, -1/16, -1/16,  5/16,  1/16},
399				         {-1/2,  -5/16, -1/16, -1/16, -3/16,  1/16}},
400			connect_back =  {{-1/16,  3/16,  1/16,  1/16,  5/16,  1/2 },
401				         {-1/16, -5/16,  1/16,  1/16, -3/16,  1/2 }},
402			connect_right = {{ 1/16,  3/16, -1/16,  1/2,   5/16,  1/16},
403		                         { 1/16, -5/16, -1/16,  1/2,  -3/16,  1/16}}
404		},
405		collision_box = {
406			type = "connected",
407			fixed = {-1/8, -1/2, -1/8, 1/8, 1/2 + fence_collision_extra, 1/8},
408			-- connect_top =
409			-- connect_bottom =
410			connect_front = {-1/8, -1/2, -1/2,  1/8, 1/2 + fence_collision_extra, -1/8},
411			connect_left =  {-1/2, -1/2, -1/8, -1/8, 1/2 + fence_collision_extra,  1/8},
412			connect_back =  {-1/8, -1/2,  1/8,  1/8, 1/2 + fence_collision_extra,  1/2},
413			connect_right = { 1/8, -1/2, -1/8,  1/2, 1/2 + fence_collision_extra,  1/8}
414		},
415		connects_to = {"group:fence", "group:wall"},
416		inventory_image = fence_rail_texture,
417		wield_image = fence_rail_texture,
418		tiles = {def.texture},
419		sunlight_propagates = true,
420		is_ground_content = false,
421		groups = {},
422	}
423	for k, v in pairs(default_fields) do
424		if def[k] == nil then
425			def[k] = v
426		end
427	end
428
429	-- Always add to the fence group, even if no group provided
430	def.groups.fence = 1
431
432	def.texture = nil
433	def.material = nil
434
435	minetest.register_node(name, def)
436end
437
438--
439-- Mese post registration helper
440--
441
442function default.register_mesepost(name, def)
443	minetest.register_craft({
444		output = name .. " 4",
445		recipe = {
446			{'', 'default:glass', ''},
447			{'default:mese_crystal', 'default:mese_crystal', 'default:mese_crystal'},
448			{'', def.material, ''},
449		}
450	})
451
452	local post_texture = def.texture .. "^default_mese_post_light_side.png^[makealpha:0,0,0"
453	local post_texture_dark = def.texture .. "^default_mese_post_light_side_dark.png^[makealpha:0,0,0"
454	-- Allow almost everything to be overridden
455	local default_fields = {
456		wield_image = post_texture,
457		drawtype = "nodebox",
458		node_box = {
459			type = "fixed",
460			fixed = {
461				{-2 / 16, -8 / 16, -2 / 16, 2 / 16, 8 / 16, 2 / 16},
462			},
463		},
464		paramtype = "light",
465		tiles = {def.texture, def.texture, post_texture_dark, post_texture_dark, post_texture, post_texture},
466		use_texture_alpha = "opaque",
467		light_source = default.LIGHT_MAX,
468		sunlight_propagates = true,
469		is_ground_content = false,
470		groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
471		sounds = default.node_sound_wood_defaults(),
472	}
473	for k, v in pairs(default_fields) do
474		if def[k] == nil then
475			def[k] = v
476		end
477	end
478
479	def.texture = nil
480	def.material = nil
481
482	minetest.register_node(name, def)
483end
484
485--
486-- Leafdecay
487--
488
489-- Prevent decay of placed leaves
490
491default.after_place_leaves = function(pos, placer, itemstack, pointed_thing)
492	if placer and placer:is_player() then
493		local node = minetest.get_node(pos)
494		node.param2 = 1
495		minetest.set_node(pos, node)
496	end
497end
498
499-- Leafdecay
500local function leafdecay_after_destruct(pos, oldnode, def)
501	for _, v in pairs(minetest.find_nodes_in_area(vector.subtract(pos, def.radius),
502			vector.add(pos, def.radius), def.leaves)) do
503		local node = minetest.get_node(v)
504		local timer = minetest.get_node_timer(v)
505		if node.param2 ~= 1 and not timer:is_started() then
506			timer:start(math.random(20, 120) / 10)
507		end
508	end
509end
510
511local movement_gravity = tonumber(
512	minetest.settings:get("movement_gravity")) or 9.81
513
514local function leafdecay_on_timer(pos, def)
515	if minetest.find_node_near(pos, def.radius, def.trunks) then
516		return false
517	end
518
519	local node = minetest.get_node(pos)
520	local drops = minetest.get_node_drops(node.name)
521	for _, item in ipairs(drops) do
522		local is_leaf
523		for _, v in pairs(def.leaves) do
524			if v == item then
525				is_leaf = true
526			end
527		end
528		if minetest.get_item_group(item, "leafdecay_drop") ~= 0 or
529				not is_leaf then
530			minetest.add_item({
531				x = pos.x - 0.5 + math.random(),
532				y = pos.y - 0.5 + math.random(),
533				z = pos.z - 0.5 + math.random(),
534			}, item)
535		end
536	end
537
538	minetest.remove_node(pos)
539	minetest.check_for_falling(pos)
540
541	-- spawn a few particles for the removed node
542	minetest.add_particlespawner({
543		amount = 8,
544		time = 0.001,
545		minpos = vector.subtract(pos, {x=0.5, y=0.5, z=0.5}),
546		maxpos = vector.add(pos, {x=0.5, y=0.5, z=0.5}),
547		minvel = vector.new(-0.5, -1, -0.5),
548		maxvel = vector.new(0.5, 0, 0.5),
549		minacc = vector.new(0, -movement_gravity, 0),
550		maxacc = vector.new(0, -movement_gravity, 0),
551		minsize = 0,
552		maxsize = 0,
553		node = node,
554	})
555end
556
557function default.register_leafdecay(def)
558	assert(def.leaves)
559	assert(def.trunks)
560	assert(def.radius)
561	for _, v in pairs(def.trunks) do
562		minetest.override_item(v, {
563			after_destruct = function(pos, oldnode)
564				leafdecay_after_destruct(pos, oldnode, def)
565			end,
566		})
567	end
568	for _, v in pairs(def.leaves) do
569		minetest.override_item(v, {
570			on_timer = function(pos)
571				leafdecay_on_timer(pos, def)
572			end,
573		})
574	end
575end
576
577
578--
579-- Convert default:dirt to something that fits the environment
580--
581
582minetest.register_abm({
583	label = "Grass spread",
584	nodenames = {"default:dirt"},
585	neighbors = {
586		"air",
587		"group:grass",
588		"group:dry_grass",
589		"default:snow",
590	},
591	interval = 6,
592	chance = 50,
593	catch_up = false,
594	action = function(pos, node)
595		-- Check for darkness: night, shadow or under a light-blocking node
596		-- Returns if ignore above
597		local above = {x = pos.x, y = pos.y + 1, z = pos.z}
598		if (minetest.get_node_light(above) or 0) < 13 then
599			return
600		end
601
602		-- Look for spreading dirt-type neighbours
603		local p2 = minetest.find_node_near(pos, 1, "group:spreading_dirt_type")
604		if p2 then
605			local n3 = minetest.get_node(p2)
606			minetest.set_node(pos, {name = n3.name})
607			return
608		end
609
610		-- Else, any seeding nodes on top?
611		local name = minetest.get_node(above).name
612		-- Snow check is cheapest, so comes first
613		if name == "default:snow" then
614			minetest.set_node(pos, {name = "default:dirt_with_snow"})
615		elseif minetest.get_item_group(name, "grass") ~= 0 then
616			minetest.set_node(pos, {name = "default:dirt_with_grass"})
617		elseif minetest.get_item_group(name, "dry_grass") ~= 0 then
618			minetest.set_node(pos, {name = "default:dirt_with_dry_grass"})
619		end
620	end
621})
622
623
624--
625-- Grass and dry grass removed in darkness
626--
627
628minetest.register_abm({
629	label = "Grass covered",
630	nodenames = {"group:spreading_dirt_type", "default:dry_dirt_with_dry_grass"},
631	interval = 8,
632	chance = 50,
633	catch_up = false,
634	action = function(pos, node)
635		local above = {x = pos.x, y = pos.y + 1, z = pos.z}
636		local name = minetest.get_node(above).name
637		local nodedef = minetest.registered_nodes[name]
638		if name ~= "ignore" and nodedef and not ((nodedef.sunlight_propagates or
639				nodedef.paramtype == "light") and
640				nodedef.liquidtype == "none") then
641			if node.name == "default:dry_dirt_with_dry_grass" then
642				minetest.set_node(pos, {name = "default:dry_dirt"})
643			else
644				minetest.set_node(pos, {name = "default:dirt"})
645			end
646		end
647	end
648})
649
650
651--
652-- Moss growth on cobble near water
653--
654
655local moss_correspondences = {
656	["default:cobble"] = "default:mossycobble",
657	["stairs:slab_cobble"] = "stairs:slab_mossycobble",
658	["stairs:stair_cobble"] = "stairs:stair_mossycobble",
659	["stairs:stair_inner_cobble"] = "stairs:stair_inner_mossycobble",
660	["stairs:stair_outer_cobble"] = "stairs:stair_outer_mossycobble",
661	["walls:cobble"] = "walls:mossycobble",
662}
663minetest.register_abm({
664	label = "Moss growth",
665	nodenames = {"default:cobble", "stairs:slab_cobble", "stairs:stair_cobble",
666		"stairs:stair_inner_cobble", "stairs:stair_outer_cobble",
667		"walls:cobble"},
668	neighbors = {"group:water"},
669	interval = 16,
670	chance = 200,
671	catch_up = false,
672	action = function(pos, node)
673		node.name = moss_correspondences[node.name]
674		if node.name then
675			minetest.set_node(pos, node)
676		end
677	end
678})
679
680--
681-- Register a craft to copy the metadata of items
682--
683
684function default.register_craft_metadata_copy(ingredient, result)
685	minetest.register_craft({
686		type = "shapeless",
687		output = result,
688		recipe = {ingredient, result}
689	})
690
691	minetest.register_on_craft(function(itemstack, player, old_craft_grid, craft_inv)
692		if itemstack:get_name() ~= result then
693			return
694		end
695
696		local original
697		local index
698		for i = 1, #old_craft_grid do
699			if old_craft_grid[i]:get_name() == result then
700				original = old_craft_grid[i]
701				index = i
702			end
703		end
704		if not original then
705			return
706		end
707		local copymeta = original:get_meta():to_table()
708		itemstack:get_meta():from_table(copymeta)
709		-- put the book with metadata back in the craft grid
710		craft_inv:set_stack("craft", index, original)
711	end)
712end
713
714
715--
716-- NOTICE: This method is not an official part of the API yet.
717-- This method may change in future.
718--
719
720function default.can_interact_with_node(player, pos)
721	if player and player:is_player() then
722		if minetest.check_player_privs(player, "protection_bypass") then
723			return true
724		end
725	else
726		return false
727	end
728
729	local meta = minetest.get_meta(pos)
730	local owner = meta:get_string("owner")
731
732	if not owner or owner == "" or owner == player:get_player_name() then
733		return true
734	end
735
736	-- Is player wielding the right key?
737	local item = player:get_wielded_item()
738	if minetest.get_item_group(item:get_name(), "key") == 1 then
739		local key_meta = item:get_meta()
740
741		if key_meta:get_string("secret") == "" then
742			local key_oldmeta = item:get_metadata()
743			if key_oldmeta == "" or not minetest.parse_json(key_oldmeta) then
744				return false
745			end
746
747			key_meta:set_string("secret", minetest.parse_json(key_oldmeta).secret)
748			item:set_metadata("")
749		end
750
751		return meta:get_string("key_lock_secret") == key_meta:get_string("secret")
752	end
753
754	return false
755end
756