Disable some demanding particles by default
[MineClone/MineClone2.git] / mods / ITEMS / mcl_fire / init.lua
blob81b963eabe73b2bd0f4e1a4856ce348e8c7ff380
1 -- Global namespace for functions
3 mcl_fire = {}
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, {
10 amount = 0.1,
11 time = 0,
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 },
16 minexptime = 2.0,
17 maxexptime = 2.0,
18 minsize = 3.0,
19 maxsize = 4.0,
20 texture = "mcl_particles_smoke_anim.png^[colorize:#000000:127",
21 animation = {
22 type = "vertical_frames",
23 aspect_w = 8,
24 aspect_h = 8,
25 length = 2.05,
27 }, "high")
28 end
31 -- Items
34 -- Flame nodes
36 -- Fire settings
38 -- When enabled, fire destroys other blocks.
39 local fire_enabled = minetest.settings:get_bool("enable_fire", true)
41 -- Enable sound
42 local flame_sound = minetest.settings:get_bool("flame_sound", true)
44 -- Help texts
45 local fire_help, eternal_fire_help
46 if fire_enabled then
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.")
48 else
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.")
50 end
52 if fire_enabled then
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.")
54 else
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.")
56 end
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))
67 end
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})
72 end
74 minetest.register_node("mcl_fire:fire", {
75 description = S("Fire"),
76 _doc_items_longdesc = fire_help,
77 drawtype = "firelike",
78 tiles = {
80 name = "fire_basic_flame_animated.png",
81 animation = {
82 type = "vertical_frames",
83 aspect_w = 16,
84 aspect_h = 16,
85 length = 1
89 inventory_image = "fire_basic_flame.png",
90 paramtype = "light",
91 light_source = minetest.LIGHT_MAX,
92 walkable = false,
93 buildable_to = true,
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 },
98 floodable = true,
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)
103 end,
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
112 -- Extinguish fire
113 if (not fire_enabled) and (math.random(1,3) == 1) then
114 minetest.remove_node(pos)
115 return
117 if age == 15 and not below_is_flammable then
118 minetest.remove_node(pos)
119 return
120 elseif age > 3 and #flammables == 0 and not below_is_flammable and math.random(1,4) == 1 then
121 minetest.remove_node(pos)
122 return
124 local age_add = 1
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)
131 -- Restart timer
132 fire_timer(pos)
133 return
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
145 -- Spawn fire in air
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"})
147 while #nodes > 0 do
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)
151 break
152 else
153 table.remove(nodes, r)
156 else
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"})
159 if #nodes > 0 then
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)
177 -- Restart timer
178 fire_timer(pos)
179 end,
180 drop = "",
181 sounds = {},
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)
196 fire_timer(pos)
197 spawn_smoke(pos)
198 end,
199 on_destruct = function(pos)
200 mcl_particles.delete_node_particlespawners(pos)
201 end,
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",
209 tiles = {
211 name = "fire_basic_flame_animated.png",
212 animation = {
213 type = "vertical_frames",
214 aspect_w = 16,
215 aspect_h = 16,
216 length = 1
220 inventory_image = "fire_basic_flame.png",
221 paramtype = "light",
222 light_source = minetest.LIGHT_MAX,
223 walkable = false,
224 buildable_to = true,
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},
229 floodable = true,
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)
234 end,
235 on_timer = function(pos)
236 if fire_enabled then
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"})
238 while #airs > 0 do
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)
245 break
246 else
247 table.remove(airs, r)
251 -- Restart timer
252 fire_timer(pos)
253 end,
254 -- Start burning timer and light Nether portal (if possible)
255 on_construct = function(pos)
256 fire_timer(pos)
258 if minetest.get_modpath("mcl_portals") then
259 mcl_portals.light_nether_portal(pos)
261 spawn_smoke(pos)
262 end,
263 on_destruct = function(pos)
264 mcl_particles.delete_node_particlespawners(pos)
265 end,
266 sounds = {},
267 drop = "",
268 _mcl_blast_resistance = 0,
272 -- Sound
275 if flame_sound then
277 local handles = {}
278 local timer = 0
280 -- Parameters
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(
294 areamin,
295 areamax,
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
306 -- If flames
307 if flames > 0 then
308 -- Find centre of flame positions
309 local fposmid = fpos[1]
310 -- If more than 1 flame
311 if #fpos > 1 then
312 local fposmin = areamax
313 local fposmax = areamin
314 for i = 1, #fpos do
315 local fposi = fpos[i]
316 if fposi.x > fposmax.x then
317 fposmax.x = fposi.x
319 if fposi.y > fposmax.y then
320 fposmax.y = fposi.y
322 if fposi.z > fposmax.z then
323 fposmax.z = fposi.z
325 if fposi.x < fposmin.x then
326 fposmin.x = fposi.x
328 if fposi.y < fposmin.y then
329 fposmin.y = fposi.y
331 if fposi.z < fposmin.z then
332 fposmin.z = fposi.z
335 fposmid = vector.divide(vector.add(fposmin, fposmax), 2)
337 -- Play sound
338 local handle = minetest.sound_play(
339 "fire_fire",
341 pos = fposmid,
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
349 if handle then
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
360 return
363 timer = 0
364 local players = minetest.get_connected_players()
365 for n = 1, #players do
366 mcl_fire.update_player_sound(players[n])
368 end)
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
378 end)
383 -- ABMs
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"},
392 interval = 3,
393 chance = 1,
394 catch_up = false,
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)
399 end,
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"},
412 interval = 10,
413 chance = 10,
414 catch_up = false,
415 action = minetest.remove_node,
418 else -- Fire enabled
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"},
424 interval = 7,
425 chance = 2,
426 catch_up = false,
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
432 return
434 local function try_ignite(airs)
435 while #airs > 0 do
436 local r = math.random(1, #airs)
437 if minetest.find_node_near(airs[r], 1, {"group:flammable"}) then
438 spawn_fire(airs[r])
439 return true
440 else
441 table.remove(airs, r)
444 return false
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"})
450 try_ignite(airs2)
451 else
452 try_ignite(airs1)
454 end,
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)
464 local pname
465 if player == nil then
466 pname = ""
467 else
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
473 return
475 if minetest.is_protected(pointed_thing.above, pname) then
476 minetest.record_protection_violation(pointed_thing.above, pname)
477 return
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)
490 spawn_smoke(pos)
491 end,
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")