Fire: Rewrite fire sound code
authorparamat <mat.gregory@virginmedia.com>
Sat, 3 Dec 2016 04:54:44 +0000 (04:54 +0000)
committerparamat <mat.gregory@virginmedia.com>
Wed, 7 Dec 2016 08:28:08 +0000 (08:28 +0000)
Previous code:
Used looped sounds without attaching them to objects or playing direct
to players.
The looped sounds are not 'stopped' when players leave the area.
These may be causing the bug where sounds are heard at extreme
distances.
Entering a world with already present flames results in silent flames.
Sounds are often played at a large number of points in a 6 node lattice.
A large fire is reported to cause a high load, disabling the sound code
is reported to help this.

New code:
Optional flame sound to not interfere with ambience mods.
Permanent flame now has sound.
For multiple flames, sound is positioned at the centre of all flames, and
has volume determined by flame number.
The original freesound 'large fire' recording was used to create 3 sounds
that play at random for a non-repetitive effect. At low volume it is
suitable for small fires.

Original sound files and sound function (as an empty function) kept
temporarily to reduce disruption.

Reduce gain of flame extinguish sound.

minetest.conf.example
mods/fire/README.txt
mods/fire/init.lua
mods/fire/sounds/fire_fire.1.ogg [new file with mode: 0644]
mods/fire/sounds/fire_fire.2.ogg [new file with mode: 0644]
mods/fire/sounds/fire_fire.3.ogg [new file with mode: 0644]
settingtypes.txt

index f5e4e85ddc747e83c94724d9d98234e24bc874ae..3f20eb7708f0441ae05352b9083fe024ae4d1ff7 100644 (file)
@@ -24,6 +24,9 @@
 # 'permanent flame' nodes will remain with either setting.
 #enable_fire = true
 
+# Enable flame sound.
+#flame_sound = true
+
 # Whether the stuff in initial_stuff should be given to new players
 #give_initial_stuff = false
 #initial_stuff = default:pick_steel,default:axe_steel,default:shovel_steel,default:torch 99,default:cobble 99
index 35affc264032080dfda818a8e83b00d4561a08b8..099da1c245dd84f298d08fe056b98b7abb03f690 100644 (file)
@@ -12,17 +12,24 @@ Authors of media (textures and sounds)
 Everything not listed in here:
 Copyright (C) 2012 Perttu Ahola (celeron55) <celeron55@gmail.com> (CC BY-SA 3.0)
 
-fire_basic_flame_animated.png:
-  Muadtralk (CC BY-SA 3.0)
+Muadtralk (CC BY-SA 3.0)
+  fire_basic_flame_animated.png
 
-fire_flint_steel.png
-  Gambit (CC BY-SA 3.0)
+Gambit (CC BY-SA 3.0)
+  fire_flint_steel.png
 
-fire_small.ogg sampled from:
-  http://www.freesound.org/people/dobroide/sounds/4211/ (CC BY 3.0)
+dobroide (CC BY 3.0)
+http://www.freesound.org/people/dobroide/sounds/4211/
+  fire_small.ogg
 
-fire_large.ogg sampled from:
-  http://www.freesound.org/people/Dynamicell/sounds/17548/ (CC BY 3.0)
+Dynamicell (CC BY 3.0)
+http://www.freesound.org/people/Dynamicell/sounds/17548/
+  fire_large.ogg
+  fire_fire.*.ogg
 
-fire_flint_and_steel.ogg
-  https://www.freesound.org/people/Benboncan/sounds/66457/ (CC BY 3.0)
+fire_small.ogg and fire_large.ogg are unused but kept temporarily to not break
+other mods that may use them.
+
+Benboncan (CC BY 3.0)
+https://www.freesound.org/people/Benboncan/sounds/66457/
+  fire_flint_and_steel.ogg
index 0dbd7c3e5e9654a3669b58b2e593c379b7107bcd..bee487a57e2d42c0bc264ebcd53cd44a50707963 100644 (file)
@@ -1,11 +1,13 @@
--- minetest/fire/init.lua
-
 -- Global namespace for functions
 
 fire = {}
 
 
--- Register flame nodes
+--
+-- Items
+--
+
+-- Flame nodes
 
 minetest.register_node("fire:basic_flame", {
        drawtype = "firelike",
@@ -34,22 +36,17 @@ minetest.register_node("fire:basic_flame", {
                        minetest.remove_node(pos)
                        return
                end
-               -- restart timer
+               -- Restart timer
                return true
        end,
        drop = "",
 
        on_construct = function(pos)
                minetest.get_node_timer(pos):start(math.random(30, 60))
-               minetest.after(0, fire.update_sounds_around, pos)
        end,
 
-       on_destruct = function(pos)
-               minetest.after(0, fire.update_sounds_around, pos)
+       on_blast = function() -- Unaffected by explosions
        end,
-
-       on_blast = function()
-       end, -- unaffected by explosions
 })
 
 minetest.register_node("fire:permanent_flame", {
@@ -76,7 +73,7 @@ minetest.register_node("fire:permanent_flame", {
        groups = {igniter = 2, dig_immediate = 3},
        drop = "",
 
-       on_blast = function()
+       on_blast = function() -- Unaffected by explosions
        end,
 })
 
@@ -113,10 +110,10 @@ minetest.register_tool("fire:flint_and_steel", {
                        end
                end
                if not minetest.setting_getbool("creative_mode") then
-                       -- wear tool
+                       -- Wear tool
                        local wdef = itemstack:get_definition()
                        itemstack:add_wear(1000)
-                       -- tool break sound
+                       -- Tool break sound
                        if itemstack:get_count() == 0 and wdef.sound and wdef.sound.breaks then
                                minetest.sound_play(wdef.sound.breaks, {pos = pt.above, gain = 0.5})
                        end
@@ -151,72 +148,134 @@ minetest.override_item("default:coalblock", {
        end,
 })
 
--- Get sound area of position
 
-fire.D = 6 -- size of sound areas
+--
+-- Sound
+--
 
-function fire.get_area_p0p1(pos)
-       local p0 = {
-               x = math.floor(pos.x / fire.D) * fire.D,
-               y = math.floor(pos.y / fire.D) * fire.D,
-               z = math.floor(pos.z / fire.D) * fire.D,
-       }
-       local p1 = {
-               x = p0.x + fire.D - 1,
-               y = p0.y + fire.D - 1,
-               z = p0.z + fire.D - 1
-       }
-       return p0, p1
+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
 
--- Fire sounds table
--- key: position hash of low corner of area
--- value: {handle=sound handle, name=sound name}
-fire.sounds = {}
+       local handles = {}
+       local timer = 0
 
+       -- Parameters
 
--- Update fire sounds in sound area of position
+       local radius = 8 -- Flame node search radius around player
+       local cycle = 3 -- Cycle time for sound updates
 
-function fire.update_sounds_around(pos)
-       local p0, p1 = 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, {"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 = 0.7}
-       elseif #flames_p > 0 then
-               wanted_sound = {name = "fire_small", gain = 0.9}
-       end
-       local p0_hash = minetest.hash_node_position(p0)
-       local sound = fire.sounds[p0_hash]
-       if not sound then
-               if should_have_sound then
-                       fire.sounds[p0_hash] = {
-                               handle = minetest.sound_play(wanted_sound,
-                                       {pos = cp, max_hear_distance = 16, loop = true}),
-                               name = wanted_sound.name,
-                       }
+       -- Update sound for player
+
+       function 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,
+                       {"fire:basic_flame", "fire:permanent_flame"}
+               )
+               -- Total number of flames in radius
+               local flames = (num["fire:basic_flame"] or 0) +
+                       (num["fire:permanent_flame"] or 0)
+               -- Stop previous sound
+               if handles[player_name] then
+                       minetest.sound_stop(handles[player_name])
+                       handles[player_name] = nil
                end
-       else
-               if not wanted_sound then
-                       minetest.sound_stop(sound.handle)
-                       fire.sounds[p0_hash] = nil
-               elseif sound.name ~= wanted_sound.name then
-                       minetest.sound_stop(sound.handle)
-                       fire.sounds[p0_hash] = {
-                               handle = minetest.sound_play(wanted_sound,
-                                       {pos = cp, max_hear_distance = 16, loop = true}),
-                               name = wanted_sound.name,
-                       }
+               -- 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
+
+               timer = 0
+               local players = minetest.get_connected_players()
+               for n = 1, #players do
+                       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
 
 
+-- Deprecated function kept temporarily to avoid crashes if mod fire nodes call it
+
+function fire.update_sounds_around(pos)
+end
+
+
+--
+-- ABMs
+--
+
 -- Extinguish all flames quickly with water, snow, ice
 
 minetest.register_abm({
@@ -229,7 +288,7 @@ minetest.register_abm({
        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.25})
+                       {pos = pos, max_hear_distance = 16, gain = 0.15})
        end,
 })
 
@@ -245,7 +304,7 @@ end
 
 if not fire_enabled then
 
-       -- Remove basic flames only
+       -- Remove basic flames only if fire disabled
 
        minetest.register_abm({
                label = "Remove disabled fire",
@@ -279,7 +338,7 @@ else -- Fire enabled
                end,
        })
 
-       -- Remove flammable nodes
+       -- Remove flammable nodes around basic flame
 
        minetest.register_abm({
                label = "Remove flammable nodes",
@@ -291,7 +350,6 @@ else -- Fire enabled
                action = function(pos, node, active_object_count, active_object_count_wider)
                        local p = minetest.find_node_near(pos, 1, {"group:flammable"})
                        if p then
-                               -- remove flammable nodes around flame
                                local flammable_node = minetest.get_node(p)
                                local def = minetest.registered_nodes[flammable_node.name]
                                if def.on_burn then
@@ -305,35 +363,3 @@ else -- Fire enabled
        })
 
 end
-
-
--- Rarely ignite things from far
-
---[[ Currently disabled to reduce the chance of uncontrollable spreading
-       fires that disrupt servers. Also for less lua processing load.
-
-minetest.register_abm({
-       nodenames = {"group:igniter"},
-       neighbors = {"air"},
-       interval = 5,
-       chance = 10,
-       action = function(pos, node, active_object_count, active_object_count_wider)
-               local reg = minetest.registered_nodes[node.name]
-               if not reg or not reg.groups.igniter or reg.groups.igniter < 2 then
-                       return
-               end
-               local d = reg.groups.igniter
-               local p = minetest.find_node_near(pos, d, {"group:flammable"})
-               if p then
-                       -- If there is water or stuff like that around flame, don't ignite
-                       if fire.flame_should_extinguish(p) then
-                               return
-                       end
-                       local p2 = fire.find_pos_for_flame_around(p)
-                       if p2 then
-                               minetest.set_node(p2, {name = "fire:basic_flame"})
-                       end
-               end
-       end,
-})
---]]
diff --git a/mods/fire/sounds/fire_fire.1.ogg b/mods/fire/sounds/fire_fire.1.ogg
new file mode 100644 (file)
index 0000000..cbfee4c
Binary files /dev/null and b/mods/fire/sounds/fire_fire.1.ogg differ
diff --git a/mods/fire/sounds/fire_fire.2.ogg b/mods/fire/sounds/fire_fire.2.ogg
new file mode 100644 (file)
index 0000000..e8d0eb1
Binary files /dev/null and b/mods/fire/sounds/fire_fire.2.ogg differ
diff --git a/mods/fire/sounds/fire_fire.3.ogg b/mods/fire/sounds/fire_fire.3.ogg
new file mode 100644 (file)
index 0000000..5cad3d9
Binary files /dev/null and b/mods/fire/sounds/fire_fire.3.ogg differ
index 2f91a7b6bdbf5ba0558eea4b22fd53f5643b98c1..eeea0bfcdb8a3ab7799498ce735141e355755adb 100644 (file)
@@ -13,6 +13,9 @@ creative_mode (Creative mode) bool false
 #    'permanent_flame' nodes are unaffected.
 enable_fire (Fire) bool true
 
+#    Enable flame sound.
+flame_sound (Flame sound) bool true
+
 #    If enabled, steel tools, torches and cobblestone will be given to new
 #    players.
 give_initial_stuff (Give initial items) bool false