1-- minetest/fire/init.lua
2local weather = minetest.setting_getbool("weather")
3
4minetest.register_node("fire:basic_flame", {
5	description = "Fire",
6	drawtype = "firelike",
7	tiles = {{
8		name="fire_basic_flame_animated.png",
9		animation={type="vertical_frames", aspect_w=16, aspect_h=16, length=1},
10	}},
11	inventory_image = "fire_basic_flame.png",
12	light_source = 14,
13	waving = 1,
14	groups = {igniter=2,dig_immediate=3,hot=200,wield_light=13, drop_by_liquid=1},
15	drop = '',
16	walkable = false,
17	buildable_to = true,
18	damage_per_second = 4,
19
20	after_place_node = function(pos, placer)
21		fire.on_flame_add_at(pos)
22	end,
23
24	after_dig_node = function(pos, oldnode, oldmetadata, digger)
25		fire.on_flame_remove_at(pos)
26	end,
27})
28
29fire = {}
30fire.D = 6
31-- key: position hash of low corner of area
32-- value: {handle=sound handle, name=sound name}
33fire.sounds = {}
34
35function fire.get_area_p0p1(pos)
36	local p0 = {
37		x=math.floor(pos.x/fire.D)*fire.D,
38		y=math.floor(pos.y/fire.D)*fire.D,
39		z=math.floor(pos.z/fire.D)*fire.D,
40	}
41	local p1 = {
42		x=p0.x+fire.D-1,
43		y=p0.y+fire.D-1,
44		z=p0.z+fire.D-1
45	}
46	return p0, p1
47end
48
49function fire.update_sounds_around(pos)
50	local p0, p1 = fire.get_area_p0p1(pos)
51	local cp = {x=(p0.x+p1.x)/2, y=(p0.y+p1.y)/2, z=(p0.z+p1.z)/2}
52	local flames_p = minetest.find_nodes_in_area(p0, p1, {"fire:basic_flame"})
53	--print("number of flames at "..minetest.pos_to_string(p0).."/"
54	--		..minetest.pos_to_string(p1)..": "..#flames_p)
55	local should_have_sound = (#flames_p > 0)
56	local wanted_sound = nil
57	if #flames_p >= 9 then
58		wanted_sound = {name="fire_large", gain=1.5}
59	elseif #flames_p > 0 then
60		wanted_sound = {name="fire_small", gain=1.5}
61	end
62	local p0_hash = minetest.hash_node_position(p0)
63	local sound = fire.sounds[p0_hash]
64	if not sound then
65		if should_have_sound then
66			fire.sounds[p0_hash] = {
67				handle = minetest.sound_play(wanted_sound, {pos=cp, loop=true}),
68				name = wanted_sound.name,
69			}
70		end
71	else
72		if not wanted_sound then
73			minetest.sound_stop(sound.handle)
74			fire.sounds[p0_hash] = nil
75		elseif sound.name ~= wanted_sound.name then
76			minetest.sound_stop(sound.handle)
77			fire.sounds[p0_hash] = {
78				handle = minetest.sound_play(wanted_sound, {pos=cp, loop=true}),
79				name = wanted_sound.name,
80			}
81		end
82	end
83end
84
85function fire.on_flame_add_at(pos)
86	--print("flame added at "..minetest.pos_to_string(pos))
87	fire.update_sounds_around(pos)
88end
89
90function fire.on_flame_remove_at(pos)
91	--print("flame removed at "..minetest.pos_to_string(pos))
92	fire.update_sounds_around(pos)
93end
94
95function fire.find_pos_for_flame_around(pos)
96	return minetest.find_node_near(pos, 1, {"air"})
97end
98
99function fire.flame_should_extinguish(pos)
100	if minetest.setting_getbool("disable_fire") then return true end
101	if weather then
102		local humidity = core.get_humidity(pos)
103		if humidity > 55 and math.random(55, humidity) >= 60 then
104			return 1
105		end
106	end
107	--return minetest.find_node_near(pos, 1, {"group:puts_out_fire"})
108	local p0 = {x=pos.x-2, y=pos.y, z=pos.z-2}
109	local p1 = {x=pos.x+2, y=pos.y, z=pos.z+2}
110	local ps = minetest.find_nodes_in_area(p0, p1, {"group:puts_out_fire"})
111	return (#ps ~= 0)
112end
113
114-- Ignite neighboring nodes
115minetest.register_abm({
116	nodenames = {"group:flammable"},
117	neighbors = {"group:igniter"},
118	interval = 1,
119	chance = 2,
120	action = function(p0, node, _, _)
121		-- If there is water or stuff like that around flame, don't ignite
122		if fire.flame_should_extinguish(p0) then
123			return
124		end
125		local p = fire.find_pos_for_flame_around(p0)
126		if p then
127			minetest.set_node(p, {name="fire:basic_flame"})
128			fire.on_flame_add_at(p)
129		end
130	end,
131})
132
133-- Rarely ignite things from far
134minetest.register_abm({
135	nodenames = {"group:igniter"},
136	neighbors = {"air"},
137	interval = 2,
138	chance = 10,
139	action = function(p0, node, _, _)
140		local reg = minetest.registered_nodes[node.name]
141		if not reg or not reg.groups.igniter or reg.groups.igniter < 2 then
142			return
143		end
144		local d = reg.groups.igniter
145		local p = minetest.find_node_near(p0, d, {"group:flammable"})
146		if p then
147			-- If there is water or stuff like that around flame, don't ignite
148			if fire.flame_should_extinguish(p) then
149				return
150			end
151			local p2 = fire.find_pos_for_flame_around(p)
152			if p2 then
153				minetest.set_node(p2, {name="fire:basic_flame"})
154				fire.on_flame_add_at(p2)
155			end
156		end
157	end,
158})
159
160-- Remove flammable nodes and flame
161minetest.register_abm({
162	nodenames = {"fire:basic_flame"},
163	interval = 1,
164	chance = 2,
165	action = function(p0, node, _, _)
166		-- If there is water or stuff like that around flame, remove flame
167		if fire.flame_should_extinguish(p0) then
168			minetest.remove_node(p0)
169			fire.on_flame_remove_at(p0)
170			return
171		end
172		--check if fire is permenant or not
173		if core.get_node(p0).param2 == 128 then return end
174		-- Make the following things rarer
175		if math.random(1,3) == 1 then
176			return
177		end
178		-- If there are no flammable nodes around flame, remove flame
179		if not minetest.find_node_near(p0, 1, {"group:flammable"}) then
180			minetest.remove_node(p0)
181			fire.on_flame_remove_at(p0)
182			return
183		end
184		if math.random(1,4) == 1 then
185			-- remove a flammable node around flame
186			local p = minetest.find_node_near(p0, 1, {"group:flammable"})
187			if p then
188				-- If there is water or stuff like that around flame, don't remove
189				if fire.flame_should_extinguish(p0) then
190					return
191				end
192				minetest.remove_node(p)
193				nodeupdate(p)
194			end
195		else
196			-- remove flame
197			minetest.remove_node(p0)
198			fire.on_flame_remove_at(p0)
199		end
200	end,
201})
202
203-- too hot
204if weather then
205minetest.register_abm({
206	nodenames = {"group:flammable"},
207	interval = 5,
208	chance = 5,
209	action = function(p0, node, _, _)
210		-- If there is water or stuff like that around flame, don't ignite
211		if core.get_heat(p0) < 500 then return end
212		if fire.flame_should_extinguish(p0) then
213			return
214		end
215		local p = fire.find_pos_for_flame_around(p0)
216		if p then
217			minetest.set_node(p, {name="fire:basic_flame"})
218			fire.on_flame_add_at(p)
219		end
220	end,
221})
222end
223