Merge pull request #2322 from Calinou/travis-luacheck-color
[oweals/minetest_game.git] / mods / fire / init.lua
1 -- Global namespace for functions
2
3 fire = {}
4
5 -- 'Enable fire' setting
6
7 local fire_enabled = minetest.settings:get_bool("enable_fire")
8 if fire_enabled == nil then
9         -- enable_fire setting not specified, check for disable_fire
10         local fire_disabled = minetest.settings:get_bool("disable_fire")
11         if fire_disabled == nil then
12                 -- Neither setting specified, check whether singleplayer
13                 fire_enabled = minetest.is_singleplayer()
14         else
15                 fire_enabled = not fire_disabled
16         end
17 end
18
19 --
20 -- Items
21 --
22
23 -- Flood flame function
24
25 local function flood_flame(pos, oldnode, newnode)
26         -- Play flame extinguish sound if liquid is not an 'igniter'
27         local nodedef = minetest.registered_items[newnode.name]
28         if not (nodedef and nodedef.groups and
29                         nodedef.groups.igniter and nodedef.groups.igniter > 0) then
30                 minetest.sound_play("fire_extinguish_flame",
31                         {pos = pos, max_hear_distance = 16, gain = 0.15})
32         end
33         -- Remove the flame
34         return false
35 end
36
37 -- Flame nodes
38
39 minetest.register_node("fire:basic_flame", {
40         drawtype = "firelike",
41         tiles = {
42                 {
43                         name = "fire_basic_flame_animated.png",
44                         animation = {
45                                 type = "vertical_frames",
46                                 aspect_w = 16,
47                                 aspect_h = 16,
48                                 length = 1
49                         },
50                 },
51         },
52         inventory_image = "fire_basic_flame.png",
53         paramtype = "light",
54         light_source = 13,
55         walkable = false,
56         buildable_to = true,
57         sunlight_propagates = true,
58         floodable = true,
59         damage_per_second = 4,
60         groups = {igniter = 2, dig_immediate = 3, not_in_creative_inventory = 1},
61         drop = "",
62
63         on_timer = function(pos)
64                 local f = minetest.find_node_near(pos, 1, {"group:flammable"})
65                 if not fire_enabled or not f then
66                         minetest.remove_node(pos)
67                         return
68                 end
69                 -- Restart timer
70                 return true
71         end,
72
73         on_construct = function(pos)
74                 if not fire_enabled then
75                         minetest.remove_node(pos)
76                 else
77                         minetest.get_node_timer(pos):start(math.random(30, 60))
78                 end
79         end,
80
81         on_flood = flood_flame,
82 })
83
84 minetest.register_node("fire:permanent_flame", {
85         description = "Permanent Flame",
86         drawtype = "firelike",
87         tiles = {
88                 {
89                         name = "fire_basic_flame_animated.png",
90                         animation = {
91                                 type = "vertical_frames",
92                                 aspect_w = 16,
93                                 aspect_h = 16,
94                                 length = 1
95                         },
96                 },
97         },
98         inventory_image = "fire_basic_flame.png",
99         paramtype = "light",
100         light_source = 13,
101         walkable = false,
102         buildable_to = true,
103         sunlight_propagates = true,
104         floodable = true,
105         damage_per_second = 4,
106         groups = {igniter = 2, dig_immediate = 3},
107         drop = "",
108
109         on_flood = flood_flame,
110 })
111
112
113 -- Flint and steel
114
115 minetest.register_tool("fire:flint_and_steel", {
116         description = "Flint and Steel",
117         inventory_image = "fire_flint_steel.png",
118         sound = {breaks = "default_tool_breaks"},
119
120         on_use = function(itemstack, user, pointed_thing)
121                 local sound_pos = pointed_thing.above or user:get_pos()
122                 minetest.sound_play(
123                         "fire_flint_and_steel",
124                         {pos = sound_pos, gain = 0.5, max_hear_distance = 8}
125                 )
126                 local player_name = user:get_player_name()
127                 if pointed_thing.type == "node" then
128                         local node_under = minetest.get_node(pointed_thing.under).name
129                         local nodedef = minetest.registered_nodes[node_under]
130                         if not nodedef then
131                                 return
132                         end
133                         if minetest.is_protected(pointed_thing.under, player_name) then
134                                 minetest.chat_send_player(player_name, "This area is protected")
135                                 return
136                         end
137                         if nodedef.on_ignite then
138                                 nodedef.on_ignite(pointed_thing.under, user)
139                         elseif minetest.get_item_group(node_under, "flammable") >= 1
140                                         and minetest.get_node(pointed_thing.above).name == "air" then
141                                 minetest.set_node(pointed_thing.above, {name = "fire:basic_flame"})
142                         end
143                 end
144                 if not (creative and creative.is_enabled_for
145                                 and creative.is_enabled_for(player_name)) then
146                         -- Wear tool
147                         local wdef = itemstack:get_definition()
148                         itemstack:add_wear(1000)
149                         -- Tool break sound
150                         if itemstack:get_count() == 0 and wdef.sound and wdef.sound.breaks then
151                                 minetest.sound_play(wdef.sound.breaks, {pos = sound_pos, gain = 0.5})
152                         end
153                         return itemstack
154                 end
155         end
156 })
157
158 minetest.register_craft({
159         output = "fire:flint_and_steel",
160         recipe = {
161                 {"default:flint", "default:steel_ingot"}
162         }
163 })
164
165
166 -- Override coalblock to enable permanent flame above
167 -- Coalblock is non-flammable to avoid unwanted basic_flame nodes
168
169 minetest.override_item("default:coalblock", {
170         after_destruct = function(pos, oldnode)
171                 pos.y = pos.y + 1
172                 if minetest.get_node(pos).name == "fire:permanent_flame" then
173                         minetest.remove_node(pos)
174                 end
175         end,
176         on_ignite = function(pos, igniter)
177                 local flame_pos = {x = pos.x, y = pos.y + 1, z = pos.z}
178                 if minetest.get_node(flame_pos).name == "air" then
179                         minetest.set_node(flame_pos, {name = "fire:permanent_flame"})
180                 end
181         end,
182 })
183
184
185 --
186 -- Sound
187 --
188
189 local flame_sound = minetest.settings:get_bool("flame_sound")
190 if flame_sound == nil then
191         -- Enable if no setting present
192         flame_sound = true
193 end
194
195 if flame_sound then
196
197         local handles = {}
198         local timer = 0
199
200         -- Parameters
201
202         local radius = 8 -- Flame node search radius around player
203         local cycle = 3 -- Cycle time for sound updates
204
205         -- Update sound for player
206
207         function fire.update_player_sound(player)
208                 local player_name = player:get_player_name()
209                 -- Search for flame nodes in radius around player
210                 local ppos = player:get_pos()
211                 local areamin = vector.subtract(ppos, radius)
212                 local areamax = vector.add(ppos, radius)
213                 local fpos, num = minetest.find_nodes_in_area(
214                         areamin,
215                         areamax,
216                         {"fire:basic_flame", "fire:permanent_flame"}
217                 )
218                 -- Total number of flames in radius
219                 local flames = (num["fire:basic_flame"] or 0) +
220                         (num["fire:permanent_flame"] or 0)
221                 -- Stop previous sound
222                 if handles[player_name] then
223                         minetest.sound_stop(handles[player_name])
224                         handles[player_name] = nil
225                 end
226                 -- If flames
227                 if flames > 0 then
228                         -- Find centre of flame positions
229                         local fposmid = fpos[1]
230                         -- If more than 1 flame
231                         if #fpos > 1 then
232                                 local fposmin = areamax
233                                 local fposmax = areamin
234                                 for i = 1, #fpos do
235                                         local fposi = fpos[i]
236                                         if fposi.x > fposmax.x then
237                                                 fposmax.x = fposi.x
238                                         end
239                                         if fposi.y > fposmax.y then
240                                                 fposmax.y = fposi.y
241                                         end
242                                         if fposi.z > fposmax.z then
243                                                 fposmax.z = fposi.z
244                                         end
245                                         if fposi.x < fposmin.x then
246                                                 fposmin.x = fposi.x
247                                         end
248                                         if fposi.y < fposmin.y then
249                                                 fposmin.y = fposi.y
250                                         end
251                                         if fposi.z < fposmin.z then
252                                                 fposmin.z = fposi.z
253                                         end
254                                 end
255                                 fposmid = vector.divide(vector.add(fposmin, fposmax), 2)
256                         end
257                         -- Play sound
258                         local handle = minetest.sound_play(
259                                 "fire_fire",
260                                 {
261                                         pos = fposmid,
262                                         to_player = player_name,
263                                         gain = math.min(0.06 * (1 + flames * 0.125), 0.18),
264                                         max_hear_distance = 32,
265                                         loop = true, -- In case of lag
266                                 }
267                         )
268                         -- Store sound handle for this player
269                         if handle then
270                                 handles[player_name] = handle
271                         end
272                 end
273         end
274
275         -- Cycle for updating players sounds
276
277         minetest.register_globalstep(function(dtime)
278                 timer = timer + dtime
279                 if timer < cycle then
280                         return
281                 end
282
283                 timer = 0
284                 local players = minetest.get_connected_players()
285                 for n = 1, #players do
286                         fire.update_player_sound(players[n])
287                 end
288         end)
289
290         -- Stop sound and clear handle on player leave
291
292         minetest.register_on_leaveplayer(function(player)
293                 local player_name = player:get_player_name()
294                 if handles[player_name] then
295                         minetest.sound_stop(handles[player_name])
296                         handles[player_name] = nil
297                 end
298         end)
299 end
300
301
302 -- Deprecated function kept temporarily to avoid crashes if mod fire nodes call it
303
304 function fire.update_sounds_around(pos)
305 end
306
307
308 --
309 -- ABMs
310 --
311
312 if fire_enabled then
313
314         -- Ignite neighboring nodes, add basic flames
315
316         minetest.register_abm({
317                 label = "Ignite flame",
318                 nodenames = {"group:flammable"},
319                 neighbors = {"group:igniter"},
320                 interval = 7,
321                 chance = 12,
322                 catch_up = false,
323                 action = function(pos)
324                         local p = minetest.find_node_near(pos, 1, {"air"})
325                         if p then
326                                 minetest.set_node(p, {name = "fire:basic_flame"})
327                         end
328                 end,
329         })
330
331         -- Remove flammable nodes around basic flame
332
333         minetest.register_abm({
334                 label = "Remove flammable nodes",
335                 nodenames = {"fire:basic_flame"},
336                 neighbors = "group:flammable",
337                 interval = 5,
338                 chance = 18,
339                 catch_up = false,
340                 action = function(pos)
341                         local p = minetest.find_node_near(pos, 1, {"group:flammable"})
342                         if not p then
343                                 return
344                         end
345                         local flammable_node = minetest.get_node(p)
346                         local def = minetest.registered_nodes[flammable_node.name]
347                         if def.on_burn then
348                                 def.on_burn(p)
349                         else
350                                 minetest.remove_node(p)
351                                 minetest.check_for_falling(p)
352                         end
353                 end,
354         })
355
356 end