1 -- Global namespace for functions
5 local S
= minetest
.get_translator("mcl_fire")
6 local N
= function(s
) return s
end
16 -- When enabled, fire destroys other blocks.
17 local fire_enabled
= minetest
.settings
:get_bool("enable_fire", true)
20 local flame_sound
= minetest
.settings
:get_bool("flame_sound", true)
23 local fire_help
, eternal_fire_help
25 fire_help
= S("Fire is a damaging and destructive but short-lived kind of block. It will destroy and spread towards near flammable blocks, but fire will disappear when there is nothing to burn left. It will be extinguished by nearby water and rain. Fire can be destroyed safely by punching it, but it is hurtful if you stand directly in it. If a fire is started above netherrack or a magma block, it will immediately turn into an eternal fire.")
27 fire_help
= S("Fire is a damaging but non-destructive short-lived kind of block. It will disappear when there is no flammable block around. Fire does not destroy blocks, at least not in this world. It will be extinguished by nearby water and rain. Fire can be destroyed safely by punching it, but it is hurtful if you stand directly in it. If a fire is started above netherrack or a magma block, it will immediately turn into an eternal fire.")
31 eternal_fire_help
= S("Eternal fire is a damaging block that might create more fire. It will create fire around it when flammable blocks are nearby. Eternal fire can be extinguished by punches and nearby water blocks. Other than (normal) fire, eternal fire does not get extinguished on its own and also continues to burn under rain. Punching eternal fire is safe, but it hurts if you stand inside.")
33 eternal_fire_help
= S("Eternal fire is a damaging block. Eternal fire can be extinguished by punches and nearby water blocks. Other than (normal) fire, eternal fire does not get extinguished on its own and also continues to burn under rain. Punching eternal fire is safe, but it hurts if you stand inside.")
36 local fire_death_messages
= {
37 N("@1 has been cooked crisp."),
38 N("@1 felt the burn."),
39 N("@1 died in the flames."),
40 N("@1 died in a fire."),
43 local fire_timer
= function(pos
)
44 minetest
.get_node_timer(pos
):start(math
.random(3, 7))
47 local spawn_fire
= function(pos
, age
)
48 minetest
.set_node(pos
, {name
="mcl_fire:fire", param2
= age
})
49 minetest
.check_single_for_falling({x
=pos
.x
, y
=pos
.y
+1, z
=pos
.z
})
52 minetest
.register_node("mcl_fire:fire", {
53 description
= S("Fire"),
54 _doc_items_longdesc
= fire_help
,
55 drawtype
= "firelike",
58 name
= "fire_basic_flame_animated.png",
60 type = "vertical_frames",
67 inventory_image
= "fire_basic_flame.png",
69 light_source
= minetest
.LIGHT_MAX
,
72 sunlight_propagates
= true,
73 damage_per_second
= 1,
74 _mcl_node_death_message
= fire_death_messages
,
75 groups
= {fire
= 1, dig_immediate
= 3, not_in_creative_inventory
= 1, dig_by_piston
=1, destroys_items
=1 },
77 on_flood
= function(pos
, oldnode
, newnode
)
78 if minetest
.get_item_group(newnode
.name
, "water") ~= 0 then
79 minetest
.sound_play("fire_extinguish_flame", {pos
= pos
, gain
= 0.25, max_hear_distance
= 16}, true)
82 on_timer
= function(pos
)
83 local node
= minetest
.get_node(pos
)
84 -- Age is a number from 0 to 15 and is increased every timer step.
85 -- "old" fire is more likely to be extinguished
86 local age
= node
.param2
87 local flammables
= minetest
.find_nodes_in_area({x
=pos
.x
-1, y
=pos
.y
-1, z
=pos
.z
-1}, {x
=pos
.x
+1, y
=pos
.y
+4, z
=pos
.z
+1}, {"group:flammable"})
88 local below
= minetest
.get_node({x
=pos
.x
, y
=pos
.z
-1, z
=pos
.z
})
89 local below_is_flammable
= minetest
.get_item_group(below
.name
, "flammable") > 0
91 if (not fire_enabled
) and (math
.random(1,3) == 1) then
92 minetest
.remove_node(pos
)
95 if age
== 15 and not below_is_flammable
then
96 minetest
.remove_node(pos
)
98 elseif age
> 3 and #flammables
== 0 and not below_is_flammable
and math
.random(1,4) == 1 then
99 minetest
.remove_node(pos
)
103 -- If fire spread is disabled, we have to skip the "destructive" code
104 if (not fire_enabled
) then
105 if age
+ age_add
<= 15 then
106 node
.param2
= age
+ age_add
107 minetest
.set_node(pos
, node
)
113 -- Spawn fire to nearby flammable nodes
114 local is_next_to_flammable
= minetest
.find_node_near(pos
, 2, {"group:flammable"}) ~= nil
115 if is_next_to_flammable
and math
.random(1,2) == 1 then
116 -- The fire we spawn copies the age of this fire.
117 -- This prevents fire from spreading infinitely far as the fire fire dies off
118 -- quicker the further it has spreaded.
119 local age_next
= math
.min(15, age
+ math
.random(0, 1))
120 -- Select random type of fire spread
121 local burntype
= math
.random(1,2)
122 if burntype
== 1 then
124 local nodes
= minetest
.find_nodes_in_area({x
=pos
.x
-1, y
=pos
.y
-1, z
=pos
.z
-1}, {x
=pos
.x
+1, y
=pos
.y
+4, z
=pos
.z
+1}, {"air"})
126 local r
= math
.random(1, #nodes
)
127 if minetest
.find_node_near(nodes
[r
], 1, {"group:flammable"}) then
128 spawn_fire(nodes
[r
], age_next
)
131 table.remove(nodes
, r
)
135 -- Replace flammable node with fire
136 local nodes
= minetest
.find_nodes_in_area({x
=pos
.x
-1, y
=pos
.y
-1, z
=pos
.z
-1}, {x
=pos
.x
+1, y
=pos
.y
+4, z
=pos
.z
+1}, {"group:flammable"})
138 local r
= math
.random(1, #nodes
)
139 spawn_fire(nodes
[r
], age_next
)
143 -- Regular age increase
144 if age
+ age_add
<= 15 then
145 node
.param2
= age
+ age_add
146 minetest
.set_node(pos
, node
)
153 -- Turn into eternal fire on special blocks, light Nether portal (if possible), start burning timer
154 on_construct
= function(pos
)
155 local bpos
= {x
=pos
.x
, y
=pos
.y
-1, z
=pos
.z
}
156 local under
= minetest
.get_node(bpos
).name
158 local dim
= mcl_worlds
.pos_to_dimension(bpos
)
159 if under
== "mcl_nether:magma" or under
== "mcl_nether:netherrack" or (under
== "mcl_core:bedrock" and dim
== "end") then
160 minetest
.swap_node(pos
, {name
= "mcl_fire:eternal_fire"})
163 if minetest
.get_modpath("mcl_portals") then
164 mcl_portals
.light_nether_portal(pos
)
169 _mcl_blast_resistance
= 0,
172 minetest
.register_node("mcl_fire:eternal_fire", {
173 description
= S("Eternal Fire"),
174 _doc_items_longdesc
= eternal_fire_help
,
175 drawtype
= "firelike",
178 name
= "fire_basic_flame_animated.png",
180 type = "vertical_frames",
187 inventory_image
= "fire_basic_flame.png",
189 light_source
= minetest
.LIGHT_MAX
,
192 sunlight_propagates
= true,
193 damage_per_second
= 1,
194 _mcl_node_death_message
= fire_death_messages
,
195 groups
= {fire
= 1, dig_immediate
= 3, not_in_creative_inventory
= 1, dig_by_piston
= 1, destroys_items
= 1},
197 on_flood
= function(pos
, oldnode
, newnode
)
198 if minetest
.get_item_group(newnode
.name
, "water") ~= 0 then
199 minetest
.sound_play("fire_extinguish_flame", {pos
= pos
, gain
= 0.25, max_hear_distance
= 16}, true)
202 on_timer
= function(pos
)
204 local airs
= minetest
.find_nodes_in_area({x
=pos
.x
-1, y
=pos
.y
-1, z
=pos
.z
-1}, {x
=pos
.x
+1, y
=pos
.y
+4, z
=pos
.z
+1}, {"air"})
206 local r
= math
.random(1, #airs
)
207 if minetest
.find_node_near(airs
[r
], 1, {"group:flammable"}) then
208 spawn_fire(airs
[r
], age_next
)
211 table.remove(airs
, r
)
218 -- Start burning timer and light Nether portal (if possible)
219 on_construct
= function(pos
)
222 if minetest
.get_modpath("mcl_portals") then
223 mcl_portals
.light_nether_portal(pos
)
228 _mcl_blast_resistance
= 0,
242 local radius
= 8 -- Flame node search radius around player
243 local cycle
= 3 -- Cycle time for sound updates
245 -- Update sound for player
247 function mcl_fire
.update_player_sound(player
)
248 local player_name
= player
:get_player_name()
249 -- Search for flame nodes in radius around player
250 local ppos
= player
:get_pos()
251 local areamin
= vector
.subtract(ppos
, radius
)
252 local areamax
= vector
.add(ppos
, radius
)
253 local fpos
, num
= minetest
.find_nodes_in_area(
256 {"mcl_fire:fire", "mcl_fire:eternal_fire"}
258 -- Total number of flames in radius
259 local flames
= (num
["mcl_fire:fire"] or 0) +
260 (num
["mcl_fire:eternal_fire"] or 0)
261 -- Stop previous sound
262 if handles
[player_name
] then
263 minetest
.sound_fade(handles
[player_name
], -0.4, 0.0)
264 handles
[player_name
] = nil
268 -- Find centre of flame positions
269 local fposmid
= fpos
[1]
270 -- If more than 1 flame
272 local fposmin
= areamax
273 local fposmax
= areamin
275 local fposi
= fpos
[i
]
276 if fposi
.x
> fposmax
.x
then
279 if fposi
.y
> fposmax
.y
then
282 if fposi
.z
> fposmax
.z
then
285 if fposi
.x
< fposmin
.x
then
288 if fposi
.y
< fposmin
.y
then
291 if fposi
.z
< fposmin
.z
then
295 fposmid
= vector
.divide(vector
.add(fposmin
, fposmax
), 2)
298 local handle
= minetest
.sound_play(
302 to_player
= player_name
,
303 gain
= math
.min(0.06 * (1 + flames
* 0.125), 0.18),
304 max_hear_distance
= 32,
305 loop
= true, -- In case of lag
308 -- Store sound handle for this player
310 handles
[player_name
] = handle
315 -- Cycle for updating players sounds
317 minetest
.register_globalstep(function(dtime
)
318 timer
= timer
+ dtime
319 if timer
< cycle
then
324 local players
= minetest
.get_connected_players()
325 for n
= 1, #players
do
326 mcl_fire
.update_player_sound(players
[n
])
330 -- Stop sound and clear handle on player leave
332 minetest
.register_on_leaveplayer(function(player
)
333 local player_name
= player
:get_player_name()
334 if handles
[player_name
] then
335 minetest
.sound_stop(handles
[player_name
])
336 handles
[player_name
] = nil
346 -- Extinguish all flames quickly with water and such
348 minetest
.register_abm({
349 label
= "Extinguish fire",
350 nodenames
= {"mcl_fire:fire", "mcl_fire:eternal_fire"},
351 neighbors
= {"group:puts_out_fire"},
355 action
= function(pos
, node
, active_object_count
, active_object_count_wider
)
356 minetest
.remove_node(pos
)
357 minetest
.sound_play("fire_extinguish_flame",
358 {pos
= pos
, max_hear_distance
= 16, gain
= 0.15}, true)
363 -- Enable the following ABMs according to 'enable fire' setting
365 if not fire_enabled
then
367 -- Occasionally remove fire if fire disabled
368 -- NOTE: Fire is normally extinguished in timer function
369 minetest
.register_abm({
370 label
= "Remove disabled fire",
371 nodenames
= {"mcl_fire:fire"},
375 action
= minetest
.remove_node
,
380 -- Set fire to air nodes (inverse pyramid pattern) above lava source
381 minetest
.register_abm({
382 label
= "Ignite fire by lava",
383 nodenames
= {"group:lava"},
387 action
= function(pos
)
388 local node
= minetest
.get_node(pos
)
389 local def
= minetest
.registered_nodes
[node
.name
]
390 -- Check if liquid source node
391 if def
and def
.liquidtype
~= "source" then
394 local function try_ignite(airs
)
396 local r
= math
.random(1, #airs
)
397 if minetest
.find_node_near(airs
[r
], 1, {"group:flammable"}) then
401 table.remove(airs
, r
)
406 local airs1
= minetest
.find_nodes_in_area({x
=pos
.x
-1, y
=pos
.y
+1, z
=pos
.z
-1}, {x
=pos
.x
+1, y
=pos
.y
+1, z
=pos
.z
+1}, {"air"})
407 local h
= math
.random(1, 2)
408 if h
== 2 and #airs1
> 0 then
409 local airs2
= minetest
.find_nodes_in_area({x
=pos
.x
-2, y
=pos
.y
+2, z
=pos
.z
-2}, {x
=pos
.x
+2, y
=pos
.y
+2, z
=pos
.z
+2}, {"air"})
419 -- Set pointed_thing on (normal) fire.
420 -- * pointed_thing: Pointed thing to ignite
421 -- * player: Player who sets fire or nil if nobody
422 -- * allow_on_fire: If false, can't ignite fire on fire (default: true)
423 mcl_fire
.set_fire
= function(pointed_thing
, player
, allow_on_fire
)
425 if player
== nil then
428 pname
= player
:get_player_name()
430 local n
= minetest
.get_node(pointed_thing
.above
)
431 local nu
= minetest
.get_node(pointed_thing
.under
)
432 if allow_on_fire
== false and minetest
.get_item_group(nu
.name
, "fire") ~= 0 then
435 if minetest
.is_protected(pointed_thing
.above
, pname
) then
436 minetest
.record_protection_violation(pointed_thing
.above
, pname
)
439 if n
.name
== "air" then
440 minetest
.add_node(pointed_thing
.above
, {name
="mcl_fire:fire"})
444 minetest
.register_alias("mcl_fire:basic_flame", "mcl_fire:fire")
445 minetest
.register_alias("fire:basic_flame", "mcl_fire:fire")
446 minetest
.register_alias("fire:permanent_flame", "mcl_fire:eternal_flame")
448 dofile(minetest
.get_modpath(minetest
.get_current_modname()).."/flint_and_steel.lua")
449 dofile(minetest
.get_modpath(minetest
.get_current_modname()).."/fire_charge.lua")