diff --git a/mods/ITEMS/mcl_fire/README.txt b/mods/ITEMS/mcl_fire/README.txt index 3ff9f237df..05ec1410c2 100644 --- a/mods/ITEMS/mcl_fire/README.txt +++ b/mods/ITEMS/mcl_fire/README.txt @@ -19,8 +19,13 @@ http://creativecommons.org/licenses/by-sa/3.0/ Authors of media files ----------------------- -fire_small.ogg sampled from: - http://www.freesound.org/people/dobroide/sounds/4211/ +Muadtralk (CC BY-SA 3.0) + fire_basic_flame_animated.png -fire_large.ogg sampled from: - http://www.freesound.org/people/Dynamicell/sounds/17548/ +Dynamicell (CC BY 3.0) +http://www.freesound.org/people/Dynamicell/sounds/17548/ + fire_fire.*.ogg + +Benboncan (CC BY 3.0) +https://www.freesound.org/people/Benboncan/sounds/66457/ + fire_flint_and_steel.ogg diff --git a/mods/ITEMS/mcl_fire/init.lua b/mods/ITEMS/mcl_fire/init.lua index 5f5b5e38ee..136e6139ad 100644 --- a/mods/ITEMS/mcl_fire/init.lua +++ b/mods/ITEMS/mcl_fire/init.lua @@ -1,237 +1,305 @@ -local init = os.clock() -minetest.register_node("mcl_fire:basic_flame", { - description = "Fire", - drawtype = "firelike", - tiles = {{ - name="fire_basic_flame_animated.png", - animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=1}, - }}, - inventory_image = "fire_basic_flame.png", - -- Real light level: 15 (but Minetest caps at 14) - light_source = 14, - groups = {igniter=2,dig_immediate=3,dig_by_water=1,not_in_creative_inventory=1}, - drop = '', - walkable = false, - buildable_to = true, - damage_per_second = 4, - - after_place_node = function(pos, placer) - mcl_fire.on_flame_add_at(pos) - end, - - after_dig_node = function(pos, oldnode, oldmetadata, digger) - mcl_fire.on_flame_remove_at(pos) - end, -}) +-- Global namespace for functions mcl_fire = {} -mcl_fire.D = 6 --- key: position hash of low corner of area --- value: {handle=sound handle, name=sound name} -mcl_fire.sounds = {} -function mcl_fire.get_area_p0p1(pos) - local p0 = { - x=math.floor(pos.x/mcl_fire.D)*mcl_fire.D, - y=math.floor(pos.y/mcl_fire.D)*mcl_fire.D, - z=math.floor(pos.z/mcl_fire.D)*mcl_fire.D, - } - local p1 = { - x=p0.x+mcl_fire.D-1, - y=p0.y+mcl_fire.D-1, - z=p0.z+mcl_fire.D-1 - } - return p0, p1 -end -function mcl_fire.update_sounds_around(pos) - local p0, p1 = mcl_fire.get_area_p0p1(pos) - local cp = {x=(p0.x+p1.x)/2, y=(p0.y+p1.y)/2, z=(p0.z+p1.z)/2} - local flames_p = minetest.find_nodes_in_area(p0, p1, {"mcl_fire:basic_flame"}) - --print("number of flames at "..minetest.pos_to_string(p0).."/" - -- ..minetest.pos_to_string(p1)..": "..#flames_p) - local should_have_sound = (#flames_p > 0) - local wanted_sound = nil - if #flames_p >= 9 then - wanted_sound = {name="fire_large", gain=1.5} - elseif #flames_p > 0 then - wanted_sound = {name="fire_small", gain=1.5} - end - local p0_hash = minetest.hash_node_position(p0) - local sound = mcl_fire.sounds[p0_hash] - if not sound then - if should_have_sound then - mcl_fire.sounds[p0_hash] = { - handle = minetest.sound_play(wanted_sound, {pos=cp, loop=true}), - name = wanted_sound.name, - } - end - else - if not wanted_sound then - minetest.sound_stop(sound.handle) - mcl_fire.sounds[p0_hash] = nil - elseif sound.name ~= wanted_sound.name then - minetest.sound_stop(sound.handle) - mcl_fire.sounds[p0_hash] = { - handle = minetest.sound_play(wanted_sound, {pos=cp, loop=true}), - name = wanted_sound.name, - } - end - end -end +-- +-- Items +-- -function mcl_fire.on_flame_add_at(pos) - --print("flame added at "..minetest.pos_to_string(pos)) - mcl_fire.update_sounds_around(pos) -end +-- Flame nodes -function mcl_fire.on_flame_remove_at(pos) - --print("flame removed at "..minetest.pos_to_string(pos)) - mcl_fire.update_sounds_around(pos) -end - -function mcl_fire.find_pos_for_flame_around(pos) - return minetest.find_node_near(pos, 1, {"air"}) -end - -function mcl_fire.flame_should_extinguish(pos) - if minetest.setting_getbool("disable_fire") then return true end - --return minetest.find_node_near(pos, 1, {"group:puts_out_fire"}) - local p0 = {x=pos.x-2, y=pos.y, z=pos.z-2} - local p1 = {x=pos.x+2, y=pos.y, z=pos.z+2} - local ps = minetest.find_nodes_in_area(p0, p1, {"group:puts_out_fire"}) - return (#ps ~= 0) -end - --- Ignite neighboring nodes -minetest.register_abm({ - nodenames = {"group:flammable"}, - neighbors = {"group:igniter"}, - interval = 1, - chance = 2, - action = function(p0, node, _, _) - -- If there is water or stuff like that around flame, don't ignite - if mcl_fire.flame_should_extinguish(p0) then +minetest.register_node("mcl_fire:fire", { + description = "Fire", + drawtype = "firelike", + tiles = { + { + name = "fire_basic_flame_animated.png", + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 1 + }, + }, + }, + inventory_image = "fire_basic_flame.png", + paramtype = "light", + -- Real light level: 15 (but Minetest caps at 14) + light_source = 14, + walkable = false, + buildable_to = true, + sunlight_propagates = true, + damage_per_second = 1, + groups = {igniter = 1, dig_immediate = 3, not_in_creative_inventory = 1}, + on_timer = function(pos) + local f = minetest.find_node_near(pos, 1, {"group:flammable"}) + if not f then + minetest.remove_node(pos) return end - local p = mcl_fire.find_pos_for_flame_around(p0) - if p then - minetest.set_node(p, {name="mcl_fire:basic_flame"}) - mcl_fire.on_flame_add_at(p) - end + -- Restart timer + return true + end, + drop = "", + + on_construct = function(pos) + minetest.get_node_timer(pos):start(math.random(30, 60)) + end, + + on_blast = function() -- Unaffected by explosions end, }) --- Rarely ignite things from far -minetest.register_abm({ - nodenames = {"group:igniter"}, - neighbors = {"air"}, - interval = 2, - chance = 10, - action = function(p0, node, _, _) - local reg = minetest.registered_nodes[node.name] - if not reg or not reg.groups.igniter or reg.groups.igniter < 2 then +minetest.register_node("mcl_fire:eternal_fire", { + description = "Eternal Fire", + drawtype = "firelike", + tiles = { + { + name = "fire_basic_flame_animated.png", + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 1 + }, + }, + }, + inventory_image = "fire_basic_flame.png", + paramtype = "light", + -- Real light level: 15 (but Minetest caps at 14) + light_source = 14, + walkable = false, + buildable_to = true, + sunlight_propagates = true, + damage_per_second = 1, + groups = {igniter = 1, dig_immediate = 3, not_in_creative_inventory = 1}, + drop = "", + + on_blast = function() -- Unaffected by explosions + end, +}) + +-- +-- Sound +-- + +local flame_sound = minetest.setting_getbool("flame_sound") +if flame_sound == nil then + -- Enable if no setting present + flame_sound = true +end + +if flame_sound then + + local handles = {} + local timer = 0 + + -- Parameters + + local radius = 8 -- Flame node search radius around player + local cycle = 3 -- Cycle time for sound updates + + -- Update sound for player + + function mcl_fire.update_player_sound(player) + local player_name = player:get_player_name() + -- Search for flame nodes in radius around player + local ppos = player:getpos() + local areamin = vector.subtract(ppos, radius) + local areamax = vector.add(ppos, radius) + local fpos, num = minetest.find_nodes_in_area( + areamin, + areamax, + {"mcl_fire:fire", "mcl_fire:eternal_fire"} + ) + -- Total number of flames in radius + local flames = (num["mcl_fire:fire"] or 0) + + (num["mcl_fire:eternal_fire"] or 0) + -- Stop previous sound + if handles[player_name] then + minetest.sound_stop(handles[player_name]) + handles[player_name] = nil + end + -- If flames + if flames > 0 then + -- Find centre of flame positions + local fposmid = fpos[1] + -- If more than 1 flame + if #fpos > 1 then + local fposmin = areamax + local fposmax = areamin + for i = 1, #fpos do + local fposi = fpos[i] + if fposi.x > fposmax.x then + fposmax.x = fposi.x + end + if fposi.y > fposmax.y then + fposmax.y = fposi.y + end + if fposi.z > fposmax.z then + fposmax.z = fposi.z + end + if fposi.x < fposmin.x then + fposmin.x = fposi.x + end + if fposi.y < fposmin.y then + fposmin.y = fposi.y + end + if fposi.z < fposmin.z then + fposmin.z = fposi.z + end + end + fposmid = vector.divide(vector.add(fposmin, fposmax), 2) + end + -- Play sound + local handle = minetest.sound_play( + "fire_fire", + { + pos = fposmid, + to_player = player_name, + gain = math.min(0.06 * (1 + flames * 0.125), 0.18), + max_hear_distance = 32, + loop = true, -- In case of lag + } + ) + -- Store sound handle for this player + if handle then + handles[player_name] = handle + end + end + end + + -- Cycle for updating players sounds + + minetest.register_globalstep(function(dtime) + timer = timer + dtime + if timer < cycle then return end - local d = reg.groups.igniter - local p = minetest.find_node_near(p0, d, {"group:flammable"}) - if p then - -- If there is water or stuff like that around flame, don't ignite - if mcl_fire.flame_should_extinguish(p) then + + timer = 0 + local players = minetest.get_connected_players() + for n = 1, #players do + mcl_fire.update_player_sound(players[n]) + end + end) + + -- Stop sound and clear handle on player leave + + minetest.register_on_leaveplayer(function(player) + local player_name = player:get_player_name() + if handles[player_name] then + minetest.sound_stop(handles[player_name]) + handles[player_name] = nil + end + end) +end + + +-- +-- ABMs +-- + +-- Extinguish all flames quickly with water, snow, ice + +minetest.register_abm({ + label = "Extinguish flame", + nodenames = {"mcl_fire:fire", "mcl_fire:eternal_fire"}, + neighbors = {"group:puts_out_fire"}, + interval = 3, + chance = 1, + catch_up = false, + action = function(pos, node, active_object_count, active_object_count_wider) + minetest.remove_node(pos) + minetest.sound_play("fire_extinguish_flame", + {pos = pos, max_hear_distance = 16, gain = 0.15}) + end, +}) + + +-- Enable the following ABMs according to 'enable fire' setting + +local fire_enabled = minetest.setting_getbool("enable_fire") +if fire_enabled == nil then + -- New setting not specified, check for old setting. + -- If old setting is also not specified, 'not nil' is true. + fire_enabled = not minetest.setting_getbool("disable_fire") +end + +if not fire_enabled then + + -- Remove basic flames only if fire disabled + + minetest.register_abm({ + label = "Remove disabled fire", + nodenames = {"fire:basic_flame"}, + interval = 7, + chance = 1, + catch_up = false, + action = minetest.remove_node, + }) + +else -- Fire enabled + + -- Ignite neighboring nodes, add basic flames + + minetest.register_abm({ + label = "Ignite flame", + nodenames = {"group:flammable"}, + neighbors = {"group:igniter"}, + interval = 7, + chance = 12, + catch_up = false, + action = function(pos, node, active_object_count, active_object_count_wider) + -- If there is water or stuff like that around node, don't ignite + if minetest.find_node_near(pos, 1, {"group:puts_out_fire"}) then return end - local p2 = mcl_fire.find_pos_for_flame_around(p) - if p2 then - minetest.set_node(p2, {name="mcl_fire:basic_flame"}) - mcl_fire.on_flame_add_at(p2) - end - end - end, -}) - --- Remove flammable nodes and flame -minetest.register_abm({ - nodenames = {"mcl_fire:basic_flame"}, - interval = 1, - chance = 2, - action = function(p0, node, _, _) - -- If there is water or stuff like that around flame, remove flame - - if mcl_fire.flame_should_extinguish(p0) then - minetest.remove_node(p0) - mcl_fire.on_flame_remove_at(p0) - return - end - -- Make the following things rarer - if math.random(1,3) == 1 then - return - end - -- If there are no flammable nodes around flame, remove flame - if not minetest.find_node_near(p0, 1, {"group:flammable"}) then - minetest.remove_node(p0) - mcl_fire.on_flame_remove_at(p0) - return - end - if math.random(1,3) == 1 then - -- remove a flammable node around flame - local p = minetest.find_node_near(p0, 1, {"group:flammable"}) + local p = minetest.find_node_near(pos, 1, {"air"}) if p then - -- If there is water or stuff like that around flame, don't remove - if mcl_fire.flame_should_extinguish(p0) then - return - end - minetest.remove_node(p) - core.check_for_falling(p) + minetest.set_node(p, {name = "mcl_fire:fire"}) end - else - -- remove flame - minetest.remove_node(p0) - mcl_fire.on_flame_remove_at(p0) - end - end, -}) + end, + }) --- --- Flint and Steel --- + -- Remove flammable nodes around fire -function mcl_fire.set_fire(pointed_thing) + minetest.register_abm({ + label = "Remove flammable nodes", + nodenames = {"mcl_fire:fire"}, + neighbors = "group:flammable", + interval = 5, + chance = 18, + catch_up = false, + action = function(pos, node, active_object_count, active_object_count_wider) + local p = minetest.find_node_near(pos, 1, {"group:flammable"}) + if p then + local flammable_node = minetest.get_node(p) + local def = minetest.registered_nodes[flammable_node.name] + if def.on_burn then + def.on_burn(p) + else + minetest.remove_node(p) + minetest.check_for_falling(p) + end + end + end, + }) + +end + +-- Set pointed_thing on fire +mcl_fire.set_fire = function(pointed_thing) local n = minetest.get_node(pointed_thing.above) if n.name ~= "" and n.name == "air" and not minetest.is_protected(pointed_thing.above, "fire") then - minetest.add_node(pointed_thing.above, {name="mcl_fire:basic_flame"}) + minetest.add_node(pointed_thing.above, {name="mcl_fire:fire"}) end end --- --- Fire Particles --- - -function mcl_fire.add_fire(pos) - local null = {x=0, y=0, z=0} - pos.y = pos.y+0.19 - minetest.add_particle({ - pos = pos, - velocity = null, - acceleration = null, - expirationtime = 1.1, - size = 1.5, - collisiondetection = true, - texture = "default_fire_particle"..tostring(math.random(1,2)) ..".png" - }) - pos.y = pos.y +0.01 - minetest.add_particle({ - pos = pos, - velocity = null, - acceleration = null, - expirationtime = 0.8, - size = 1.5, - collisiondetection = true, - texture = "default_fire_particle"..tostring(math.random(1,2)) ..".png" - }) -end +minetest.register_alias("mcl_fire:basic_flame", "mcl_fire:fire") +minetest.register_alias("fire:basic_flame", "mcl_fire:fire") +minetest.register_alias("fire:permanent_flame", "mcl_fire:eternal_flame") dofile(minetest.get_modpath(minetest.get_current_modname()).."/flint_and_steel.lua") dofile(minetest.get_modpath(minetest.get_current_modname()).."/fire_charge.lua") - -local time_to_load= os.clock() - init -print(string.format("[MOD] "..minetest.get_current_modname().." loaded in %.4f s", time_to_load)) diff --git a/mods/ITEMS/mcl_fire/sounds/fire_extinguish_flame.1.ogg b/mods/ITEMS/mcl_fire/sounds/fire_extinguish_flame.1.ogg new file mode 100644 index 0000000000..42506ddffa Binary files /dev/null and b/mods/ITEMS/mcl_fire/sounds/fire_extinguish_flame.1.ogg differ diff --git a/mods/ITEMS/mcl_fire/sounds/fire_extinguish_flame.2.ogg b/mods/ITEMS/mcl_fire/sounds/fire_extinguish_flame.2.ogg new file mode 100644 index 0000000000..2747ab81cb Binary files /dev/null and b/mods/ITEMS/mcl_fire/sounds/fire_extinguish_flame.2.ogg differ diff --git a/mods/ITEMS/mcl_fire/sounds/fire_extinguish_flame.3.ogg b/mods/ITEMS/mcl_fire/sounds/fire_extinguish_flame.3.ogg new file mode 100644 index 0000000000..8baeac32ed Binary files /dev/null and b/mods/ITEMS/mcl_fire/sounds/fire_extinguish_flame.3.ogg differ diff --git a/mods/ITEMS/mcl_fire/sounds/fire_fire.1.ogg b/mods/ITEMS/mcl_fire/sounds/fire_fire.1.ogg new file mode 100644 index 0000000000..cbfee4c65f Binary files /dev/null and b/mods/ITEMS/mcl_fire/sounds/fire_fire.1.ogg differ diff --git a/mods/ITEMS/mcl_fire/sounds/fire_fire.2.ogg b/mods/ITEMS/mcl_fire/sounds/fire_fire.2.ogg new file mode 100644 index 0000000000..e8d0eb1356 Binary files /dev/null and b/mods/ITEMS/mcl_fire/sounds/fire_fire.2.ogg differ diff --git a/mods/ITEMS/mcl_fire/sounds/fire_fire.3.ogg b/mods/ITEMS/mcl_fire/sounds/fire_fire.3.ogg new file mode 100644 index 0000000000..5cad3d9b8a Binary files /dev/null and b/mods/ITEMS/mcl_fire/sounds/fire_fire.3.ogg differ diff --git a/mods/ITEMS/mcl_fire/sounds/fire_large.ogg b/mods/ITEMS/mcl_fire/sounds/fire_large.ogg deleted file mode 100644 index fe78e62531..0000000000 Binary files a/mods/ITEMS/mcl_fire/sounds/fire_large.ogg and /dev/null differ diff --git a/mods/ITEMS/mcl_fire/sounds/fire_small.ogg b/mods/ITEMS/mcl_fire/sounds/fire_small.ogg deleted file mode 100644 index 5aac595b94..0000000000 Binary files a/mods/ITEMS/mcl_fire/sounds/fire_small.ogg and /dev/null differ