Browse Source

Fire: Rewrite fire sound code

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.
paramat 7 years ago
parent
commit
fed2151d70

+ 3 - 0
minetest.conf.example

@@ -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

+ 17 - 10
mods/fire/README.txt

@@ -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

+ 127 - 101
mods/fire/init.lua

@@ -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,
-})
---]]

BIN
mods/fire/sounds/fire_fire.1.ogg


BIN
mods/fire/sounds/fire_fire.2.ogg


BIN
mods/fire/sounds/fire_fire.3.ogg


+ 3 - 0
settingtypes.txt

@@ -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