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.
This commit is contained in:
paramat 2016-12-03 04:54:44 +00:00
parent 58038a7941
commit fed2151d70
7 changed files with 154 additions and 115 deletions

@ -24,6 +24,9 @@
# 'permanent flame' nodes will remain with either setting. # 'permanent flame' nodes will remain with either setting.
#enable_fire = true #enable_fire = true
# Enable flame sound.
#flame_sound = true
# Whether the stuff in initial_stuff should be given to new players # Whether the stuff in initial_stuff should be given to new players
#give_initial_stuff = false #give_initial_stuff = false
#initial_stuff = default:pick_steel,default:axe_steel,default:shovel_steel,default:torch 99,default:cobble 99 #initial_stuff = default:pick_steel,default:axe_steel,default:shovel_steel,default:torch 99,default:cobble 99

@ -12,17 +12,24 @@ Authors of media (textures and sounds)
Everything not listed in here: Everything not listed in here:
Copyright (C) 2012 Perttu Ahola (celeron55) <celeron55@gmail.com> (CC BY-SA 3.0) 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: dobroide (CC BY 3.0)
http://www.freesound.org/people/dobroide/sounds/4211/ (CC BY 3.0) http://www.freesound.org/people/dobroide/sounds/4211/
fire_small.ogg
fire_large.ogg sampled from: Dynamicell (CC BY 3.0)
http://www.freesound.org/people/Dynamicell/sounds/17548/ (CC BY 3.0) http://www.freesound.org/people/Dynamicell/sounds/17548/
fire_large.ogg
fire_fire.*.ogg
fire_flint_and_steel.ogg fire_small.ogg and fire_large.ogg are unused but kept temporarily to not break
https://www.freesound.org/people/Benboncan/sounds/66457/ (CC BY 3.0) other mods that may use them.
Benboncan (CC BY 3.0)
https://www.freesound.org/people/Benboncan/sounds/66457/
fire_flint_and_steel.ogg

@ -1,11 +1,13 @@
-- minetest/fire/init.lua
-- Global namespace for functions -- Global namespace for functions
fire = {} fire = {}
-- Register flame nodes --
-- Items
--
-- Flame nodes
minetest.register_node("fire:basic_flame", { minetest.register_node("fire:basic_flame", {
drawtype = "firelike", drawtype = "firelike",
@ -34,22 +36,17 @@ minetest.register_node("fire:basic_flame", {
minetest.remove_node(pos) minetest.remove_node(pos)
return return
end end
-- restart timer -- Restart timer
return true return true
end, end,
drop = "", drop = "",
on_construct = function(pos) on_construct = function(pos)
minetest.get_node_timer(pos):start(math.random(30, 60)) minetest.get_node_timer(pos):start(math.random(30, 60))
minetest.after(0, fire.update_sounds_around, pos)
end, end,
on_destruct = function(pos) on_blast = function() -- Unaffected by explosions
minetest.after(0, fire.update_sounds_around, pos)
end, end,
on_blast = function()
end, -- unaffected by explosions
}) })
minetest.register_node("fire:permanent_flame", { minetest.register_node("fire:permanent_flame", {
@ -76,7 +73,7 @@ minetest.register_node("fire:permanent_flame", {
groups = {igniter = 2, dig_immediate = 3}, groups = {igniter = 2, dig_immediate = 3},
drop = "", drop = "",
on_blast = function() on_blast = function() -- Unaffected by explosions
end, end,
}) })
@ -113,10 +110,10 @@ minetest.register_tool("fire:flint_and_steel", {
end end
end end
if not minetest.setting_getbool("creative_mode") then if not minetest.setting_getbool("creative_mode") then
-- wear tool -- Wear tool
local wdef = itemstack:get_definition() local wdef = itemstack:get_definition()
itemstack:add_wear(1000) itemstack:add_wear(1000)
-- tool break sound -- Tool break sound
if itemstack:get_count() == 0 and wdef.sound and wdef.sound.breaks then 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}) minetest.sound_play(wdef.sound.breaks, {pos = pt.above, gain = 0.5})
end end
@ -151,72 +148,134 @@ minetest.override_item("default:coalblock", {
end, end,
}) })
-- Get sound area of position
fire.D = 6 -- size of sound areas --
-- Sound
--
function fire.get_area_p0p1(pos) local flame_sound = minetest.setting_getbool("flame_sound")
local p0 = { if flame_sound == nil then
x = math.floor(pos.x / fire.D) * fire.D, -- Enable if no setting present
y = math.floor(pos.y / fire.D) * fire.D, flame_sound = true
z = math.floor(pos.z / fire.D) * fire.D, end
}
local p1 = { if flame_sound then
x = p0.x + fire.D - 1,
y = p0.y + fire.D - 1, local handles = {}
z = p0.z + fire.D - 1 local timer = 0
}
return p0, p1 -- Parameters
local radius = 8 -- Flame node search radius around player
local cycle = 3 -- Cycle time for sound updates
-- 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
-- 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 end
-- Fire sounds table -- Deprecated function kept temporarily to avoid crashes if mod fire nodes call it
-- key: position hash of low corner of area
-- value: {handle=sound handle, name=sound name}
fire.sounds = {}
-- Update fire sounds in sound area of position
function fire.update_sounds_around(pos) 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,
}
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,
}
end
end
end end
--
-- ABMs
--
-- Extinguish all flames quickly with water, snow, ice -- Extinguish all flames quickly with water, snow, ice
minetest.register_abm({ minetest.register_abm({
@ -229,7 +288,7 @@ minetest.register_abm({
action = function(pos, node, active_object_count, active_object_count_wider) action = function(pos, node, active_object_count, active_object_count_wider)
minetest.remove_node(pos) minetest.remove_node(pos)
minetest.sound_play("fire_extinguish_flame", 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, end,
}) })
@ -245,7 +304,7 @@ end
if not fire_enabled then if not fire_enabled then
-- Remove basic flames only -- Remove basic flames only if fire disabled
minetest.register_abm({ minetest.register_abm({
label = "Remove disabled fire", label = "Remove disabled fire",
@ -279,7 +338,7 @@ else -- Fire enabled
end, end,
}) })
-- Remove flammable nodes -- Remove flammable nodes around basic flame
minetest.register_abm({ minetest.register_abm({
label = "Remove flammable nodes", label = "Remove flammable nodes",
@ -291,7 +350,6 @@ else -- Fire enabled
action = function(pos, node, active_object_count, active_object_count_wider) action = function(pos, node, active_object_count, active_object_count_wider)
local p = minetest.find_node_near(pos, 1, {"group:flammable"}) local p = minetest.find_node_near(pos, 1, {"group:flammable"})
if p then if p then
-- remove flammable nodes around flame
local flammable_node = minetest.get_node(p) local flammable_node = minetest.get_node(p)
local def = minetest.registered_nodes[flammable_node.name] local def = minetest.registered_nodes[flammable_node.name]
if def.on_burn then if def.on_burn then
@ -305,35 +363,3 @@ else -- Fire enabled
}) })
end 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,
})
--]]

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -13,6 +13,9 @@ creative_mode (Creative mode) bool false
# 'permanent_flame' nodes are unaffected. # 'permanent_flame' nodes are unaffected.
enable_fire (Fire) bool true 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 # If enabled, steel tools, torches and cobblestone will be given to new
# players. # players.
give_initial_stuff (Give initial items) bool false give_initial_stuff (Give initial items) bool false