1 -- Global namespace for functions
5 local S
= minetest
.get_translator("mcl_fire")
6 local N
= function(s
) return s
end
8 local spawn_smoke
= function(pos
)
9 mcl_particles
.add_node_particlespawner(pos
, {
12 minpos
= vector
.add(pos
, { x
= -0.45, y
= -0.45, z
= -0.45 }),
13 maxpos
= vector
.add(pos
, { x
= 0.45, y
= 0.45, z
= 0.45 }),
14 minvel
= { x
= 0, y
= 0.5, z
= 0 },
15 maxvel
= { x
= 0, y
= 0.6, z
= 0 },
20 texture
= "mcl_particles_smoke_anim.png^[colorize:#000000:127",
22 type = "vertical_frames",
38 -- When enabled, fire destroys other blocks.
39 local fire_enabled
= minetest
.settings
:get_bool("enable_fire", true)
42 local flame_sound
= minetest
.settings
:get_bool("flame_sound", true)
45 local fire_help
, eternal_fire_help
47 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.")
49 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.")
53 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.")
55 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.")
58 local fire_death_messages
= {
59 N("@1 has been cooked crisp."),
60 N("@1 felt the burn."),
61 N("@1 died in the flames."),
62 N("@1 died in a fire."),
65 local fire_timer
= function(pos
)
66 minetest
.get_node_timer(pos
):start(math
.random(3, 7))
69 local spawn_fire
= function(pos
, age
)
70 minetest
.set_node(pos
, {name
="mcl_fire:fire", param2
= age
})
71 minetest
.check_single_for_falling({x
=pos
.x
, y
=pos
.y
+1, z
=pos
.z
})
74 minetest
.register_node("mcl_fire:fire", {
75 description
= S("Fire"),
76 _doc_items_longdesc
= fire_help
,
77 drawtype
= "firelike",
80 name
= "fire_basic_flame_animated.png",
82 type = "vertical_frames",
89 inventory_image
= "fire_basic_flame.png",
91 light_source
= minetest
.LIGHT_MAX
,
94 sunlight_propagates
= true,
95 damage_per_second
= 1,
96 _mcl_node_death_message
= fire_death_messages
,
97 groups
= {fire
= 1, dig_immediate
= 3, not_in_creative_inventory
= 1, dig_by_piston
=1, destroys_items
=1 },
99 on_flood
= function(pos
, oldnode
, newnode
)
100 if minetest
.get_item_group(newnode
.name
, "water") ~= 0 then
101 minetest
.sound_play("fire_extinguish_flame", {pos
= pos
, gain
= 0.25, max_hear_distance
= 16}, true)
104 on_timer
= function(pos
)
105 local node
= minetest
.get_node(pos
)
106 -- Age is a number from 0 to 15 and is increased every timer step.
107 -- "old" fire is more likely to be extinguished
108 local age
= node
.param2
109 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"})
110 local below
= minetest
.get_node({x
=pos
.x
, y
=pos
.z
-1, z
=pos
.z
})
111 local below_is_flammable
= minetest
.get_item_group(below
.name
, "flammable") > 0
113 if (not fire_enabled
) and (math
.random(1,3) == 1) then
114 minetest
.remove_node(pos
)
117 if age
== 15 and not below_is_flammable
then
118 minetest
.remove_node(pos
)
120 elseif age
> 3 and #flammables
== 0 and not below_is_flammable
and math
.random(1,4) == 1 then
121 minetest
.remove_node(pos
)
125 -- If fire spread is disabled, we have to skip the "destructive" code
126 if (not fire_enabled
) then
127 if age
+ age_add
<= 15 then
128 node
.param2
= age
+ age_add
129 minetest
.set_node(pos
, node
)
135 -- Spawn fire to nearby flammable nodes
136 local is_next_to_flammable
= minetest
.find_node_near(pos
, 2, {"group:flammable"}) ~= nil
137 if is_next_to_flammable
and math
.random(1,2) == 1 then
138 -- The fire we spawn copies the age of this fire.
139 -- This prevents fire from spreading infinitely far as the fire fire dies off
140 -- quicker the further it has spreaded.
141 local age_next
= math
.min(15, age
+ math
.random(0, 1))
142 -- Select random type of fire spread
143 local burntype
= math
.random(1,2)
144 if burntype
== 1 then
146 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"})
148 local r
= math
.random(1, #nodes
)
149 if minetest
.find_node_near(nodes
[r
], 1, {"group:flammable"}) then
150 spawn_fire(nodes
[r
], age_next
)
153 table.remove(nodes
, r
)
157 -- Burn flammable block
158 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"})
160 local r
= math
.random(1, #nodes
)
161 local nn
= minetest
.get_node(nodes
[r
]).name
162 local ndef
= minetest
.registered_nodes
[nn
]
163 local fgroup
= minetest
.get_item_group(nn
, "flammable")
164 if ndef
and ndef
._on_burn
then
165 ndef
._on_burn(nodes
[r
])
166 elseif fgroup
~= -1 then
167 spawn_fire(nodes
[r
], age_next
)
172 -- Regular age increase
173 if age
+ age_add
<= 15 then
174 node
.param2
= age
+ age_add
175 minetest
.set_node(pos
, node
)
182 -- Turn into eternal fire on special blocks, light Nether portal (if possible), start burning timer
183 on_construct
= function(pos
)
184 local bpos
= {x
=pos
.x
, y
=pos
.y
-1, z
=pos
.z
}
185 local under
= minetest
.get_node(bpos
).name
187 local dim
= mcl_worlds
.pos_to_dimension(bpos
)
188 if under
== "mcl_nether:magma" or under
== "mcl_nether:netherrack" or (under
== "mcl_core:bedrock" and dim
== "end") then
189 minetest
.swap_node(pos
, {name
= "mcl_fire:eternal_fire"})
192 if minetest
.get_modpath("mcl_portals") then
193 mcl_portals
.light_nether_portal(pos
)
199 on_destruct
= function(pos
)
200 mcl_particles
.delete_node_particlespawners(pos
)
202 _mcl_blast_resistance
= 0,
205 minetest
.register_node("mcl_fire:eternal_fire", {
206 description
= S("Eternal Fire"),
207 _doc_items_longdesc
= eternal_fire_help
,
208 drawtype
= "firelike",
211 name
= "fire_basic_flame_animated.png",
213 type = "vertical_frames",
220 inventory_image
= "fire_basic_flame.png",
222 light_source
= minetest
.LIGHT_MAX
,
225 sunlight_propagates
= true,
226 damage_per_second
= 1,
227 _mcl_node_death_message
= fire_death_messages
,
228 groups
= {fire
= 1, dig_immediate
= 3, not_in_creative_inventory
= 1, dig_by_piston
= 1, destroys_items
= 1},
230 on_flood
= function(pos
, oldnode
, newnode
)
231 if minetest
.get_item_group(newnode
.name
, "water") ~= 0 then
232 minetest
.sound_play("fire_extinguish_flame", {pos
= pos
, gain
= 0.25, max_hear_distance
= 16}, true)
235 on_timer
= function(pos
)
237 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"})
239 local r
= math
.random(1, #airs
)
240 if minetest
.find_node_near(airs
[r
], 1, {"group:flammable"}) then
241 local node
= minetest
.get_node(airs
[r
])
242 local age
= node
.param2
243 local age_next
= math
.min(15, age
+ math
.random(0, 1))
244 spawn_fire(airs
[r
], age_next
)
247 table.remove(airs
, r
)
254 -- Start burning timer and light Nether portal (if possible)
255 on_construct
= function(pos
)
258 if minetest
.get_modpath("mcl_portals") then
259 mcl_portals
.light_nether_portal(pos
)
263 on_destruct
= function(pos
)
264 mcl_particles
.delete_node_particlespawners(pos
)
268 _mcl_blast_resistance
= 0,
282 local radius
= 8 -- Flame node search radius around player
283 local cycle
= 3 -- Cycle time for sound updates
285 -- Update sound for player
287 function mcl_fire
.update_player_sound(player
)
288 local player_name
= player
:get_player_name()
289 -- Search for flame nodes in radius around player
290 local ppos
= player
:get_pos()
291 local areamin
= vector
.subtract(ppos
, radius
)
292 local areamax
= vector
.add(ppos
, radius
)
293 local fpos
, num
= minetest
.find_nodes_in_area(
296 {"mcl_fire:fire", "mcl_fire:eternal_fire"}
298 -- Total number of flames in radius
299 local flames
= (num
["mcl_fire:fire"] or 0) +
300 (num
["mcl_fire:eternal_fire"] or 0)
301 -- Stop previous sound
302 if handles
[player_name
] then
303 minetest
.sound_fade(handles
[player_name
], -0.4, 0.0)
304 handles
[player_name
] = nil
308 -- Find centre of flame positions
309 local fposmid
= fpos
[1]
310 -- If more than 1 flame
312 local fposmin
= areamax
313 local fposmax
= areamin
315 local fposi
= fpos
[i
]
316 if fposi
.x
> fposmax
.x
then
319 if fposi
.y
> fposmax
.y
then
322 if fposi
.z
> fposmax
.z
then
325 if fposi
.x
< fposmin
.x
then
328 if fposi
.y
< fposmin
.y
then
331 if fposi
.z
< fposmin
.z
then
335 fposmid
= vector
.divide(vector
.add(fposmin
, fposmax
), 2)
338 local handle
= minetest
.sound_play(
342 to_player
= player_name
,
343 gain
= math
.min(0.06 * (1 + flames
* 0.125), 0.18),
344 max_hear_distance
= 32,
345 loop
= true, -- In case of lag
348 -- Store sound handle for this player
350 handles
[player_name
] = handle
355 -- Cycle for updating players sounds
357 minetest
.register_globalstep(function(dtime
)
358 timer
= timer
+ dtime
359 if timer
< cycle
then
364 local players
= minetest
.get_connected_players()
365 for n
= 1, #players
do
366 mcl_fire
.update_player_sound(players
[n
])
370 -- Stop sound and clear handle on player leave
372 minetest
.register_on_leaveplayer(function(player
)
373 local player_name
= player
:get_player_name()
374 if handles
[player_name
] then
375 minetest
.sound_stop(handles
[player_name
])
376 handles
[player_name
] = nil
386 -- Extinguish all flames quickly with water and such
388 minetest
.register_abm({
389 label
= "Extinguish fire",
390 nodenames
= {"mcl_fire:fire", "mcl_fire:eternal_fire"},
391 neighbors
= {"group:puts_out_fire"},
395 action
= function(pos
, node
, active_object_count
, active_object_count_wider
)
396 minetest
.remove_node(pos
)
397 minetest
.sound_play("fire_extinguish_flame",
398 {pos
= pos
, max_hear_distance
= 16, gain
= 0.15}, true)
403 -- Enable the following ABMs according to 'enable fire' setting
405 if not fire_enabled
then
407 -- Occasionally remove fire if fire disabled
408 -- NOTE: Fire is normally extinguished in timer function
409 minetest
.register_abm({
410 label
= "Remove disabled fire",
411 nodenames
= {"mcl_fire:fire"},
415 action
= minetest
.remove_node
,
420 -- Set fire to air nodes (inverse pyramid pattern) above lava source
421 minetest
.register_abm({
422 label
= "Ignite fire by lava",
423 nodenames
= {"group:lava"},
427 action
= function(pos
)
428 local node
= minetest
.get_node(pos
)
429 local def
= minetest
.registered_nodes
[node
.name
]
430 -- Check if liquid source node
431 if def
and def
.liquidtype
~= "source" then
434 local function try_ignite(airs
)
436 local r
= math
.random(1, #airs
)
437 if minetest
.find_node_near(airs
[r
], 1, {"group:flammable"}) then
441 table.remove(airs
, r
)
446 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"})
447 local h
= math
.random(1, 2)
448 if h
== 2 and #airs1
> 0 then
449 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"})
459 -- Set pointed_thing on (normal) fire.
460 -- * pointed_thing: Pointed thing to ignite
461 -- * player: Player who sets fire or nil if nobody
462 -- * allow_on_fire: If false, can't ignite fire on fire (default: true)
463 mcl_fire
.set_fire
= function(pointed_thing
, player
, allow_on_fire
)
465 if player
== nil then
468 pname
= player
:get_player_name()
470 local n
= minetest
.get_node(pointed_thing
.above
)
471 local nu
= minetest
.get_node(pointed_thing
.under
)
472 if allow_on_fire
== false and minetest
.get_item_group(nu
.name
, "fire") ~= 0 then
475 if minetest
.is_protected(pointed_thing
.above
, pname
) then
476 minetest
.record_protection_violation(pointed_thing
.above
, pname
)
479 if n
.name
== "air" then
480 minetest
.add_node(pointed_thing
.above
, {name
="mcl_fire:fire"})
484 minetest
.register_lbm({
485 label
= "Smoke particles from fire",
486 name
= "mcl_fire:smoke",
487 nodenames
= {"group:fire"},
488 run_at_every_load
= true,
489 action
= function(pos
, node
)
494 minetest
.register_alias("mcl_fire:basic_flame", "mcl_fire:fire")
495 minetest
.register_alias("fire:basic_flame", "mcl_fire:fire")
496 minetest
.register_alias("fire:permanent_flame", "mcl_fire:eternal_flame")
498 dofile(minetest
.get_modpath(minetest
.get_current_modname()).."/flint_and_steel.lua")
499 dofile(minetest
.get_modpath(minetest
.get_current_modname()).."/fire_charge.lua")