Files
wnsrc/lua/stormfox2/lib/cl_ambiencesnd.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

275 lines
8.9 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
SF_AMB_SND = SF_AMB_SND or {}
SF_AMB_CHANNEL = SF_AMB_CHANNEL or {} -- [snd]{station, target_vol, current_vol}
StormFox2.Ambience = {}
--[[
- Outside Constant
- Near Outside 3D
- Near Window By Distance to nearest
- Roof By Distance to nearest
- Glass Roof (Like window) By Distance to nearest
- Metal Roof By Distance to nearest
]]
--[[ Enums
SF_AMB_CONSTANT = 0
SF_AMB_DISTANCE = 1
SF_AMB_FAKE3D = 2 -- Pans the sound towards the point
SF_AMB_USE3D = 3
]]
SF_AMB_OUTSIDE = 0 -- CONSTANT VOLUME
SF_AMB_NEAR_OUTSIDE = 1 -- DISTANCE VOLUME
SF_AMB_WINDOW = 2 -- DISTANCE VOLUME
SF_AMB_UNDER_WATER = 3 -- CONSTANT VOLUME
SF_AMB_UNDER_WATER_Z = 4 -- Z-DISTANCE VOLUME (The distance to surface)
SF_AMB_ROOF_ANY = 5 -- Z-DISTANCE (SF_AMB_ROOF_CONCRETE and SF_AMB_ROOF_GROUND will be ignored)
SF_AMB_ROOF_GLASS = 6 -- Z-DISTANCE
SF_AMB_ROOF_METAL = 7 -- Z-DISTANCE
SF_AMB_ROOF_WOOD = 8 -- Z-DISTANCE
SF_AMB_ROOF_CONCRETE = 9 -- Z-DISTANCE
SF_AMB_ROOF_GROUND = 10-- Z-DISTANCE (Default roof)
SF_AMB_ROOF_WATER = 11-- Z-DISTANCE
-- Smooth the volume of SF_AMB_CHANNEL
hook.Add("Think", "StormFox2.Ambiences.Smooth", function()
for snd,t in pairs( SF_AMB_CHANNEL ) do
if not IsValid( t[1] ) then -- In case something goes wrong. Delete the channel
SF_AMB_CHANNEL[snd] = nil
continue
end
-- Calc the new volume
local c_vol = t[3]
local newvol = math.Approach( c_vol, t[2], FrameTime() )
if c_vol == newvol then continue end
if newvol <= 0 then
-- Stop the sound and remove channel
t[1]:Stop()
SF_AMB_CHANNEL[snd] = nil
else
if system.HasFocus() then -- We don't want sound playing when gmod is unfocused.
t[1]:SetVolume( newvol )
else
t[1]:SetVolume( 0 )
end
SF_AMB_CHANNEL[snd][3] = newvol
end
end
end)
local AMB_LOAD = {}
-- Handles the sound-channel.
local function RequestChannel( snd )
if AMB_LOAD[snd] then return end -- Already loading, or error
if SF_AMB_CHANNEL[snd] then return end -- Already loaded
AMB_LOAD[snd] = true
sound.PlayFile( snd, "noblock noplay", function( station, errCode, errStr )
if ( IsValid( station ) ) then
SF_AMB_CHANNEL[snd] = {station, 0.1, 0}
station:SetVolume( 0 )
station:EnableLooping( true )
station:Play()
AMB_LOAD[snd] = nil -- Allow it to be loaded again
else
if errCode == 1 then
StormFox2.Warning("Sound Error! [1] Memory error.")
elseif errCode == 2 then
StormFox2.Warning("Sound Error! [2] Unable to locate or open: " .. snd .. ".")
else
StormFox2.Warning("Sound Error! [" .. errCode .. "] " .. errStr .. ".")
end
end
end)
end
local snd_meta = {}
snd_meta.__index = snd_meta
---Creates an ambience sound and returns a sound-object.
---@param snd string
---@param SF_AMB_TYPE number
---@param vol_scale? number
---@param min? number
---@param max? number
---@param playrate? number
---@return table
---@client
function StormFox2.Ambience.CreateAmbienceSnd( snd, SF_AMB_TYPE, vol_scale, min, max, playrate )
local t = {}
t.snd = "sound/" .. snd
t.m_vol = vol_scale or 1
t.min = min or 60
t.max = max or 300
t.SF_AMB_TYPE = SF_AMB_TYPE or SF_AMB_OUTSIDE
t.playbackrate = playrate or 1
setmetatable( t , snd_meta )
return t
end
---Returns the current sound channels / data.
---@return table
---@client
function StormFox2.Ambience.DebugList()
return SF_AMB_CHANNEL
end
-- Sets the scale of the sound
function snd_meta:SetVolume( num )
self.m_vol = math.Clamp(num, 0, 2) -- Just in case
end
-- Doesn't work on sounds with SF_AMB_OUTSIDE or SF_AMB_UNDER_WATER
function snd_meta:SetFadeDistance( min, max )
self.min = min
self.max = max
end
-- Set playback rate.
function snd_meta:SetPlaybackRate( n )
self.playbackrate = n or 1
end
-- Adds ambience for weather
hook.Add("stormfox2.preloadweather", "StormFox2.Amb.Create", function( w_meta )
function w_meta:AddAmbience( amb_object )
if not self.ambience_tab then self.ambience_tab = {} end
table.insert(self.ambience_tab, amb_object)
end
function w_meta:ClearAmbience()
self.ambience_tab = {}
end
hook.Remove("stormfox2.preloadweather", "StormFox2.Amb.Create")
end)
-- Applies the ambience sound
local function check(SF_AMB_TYPE, env)
if SF_AMB_TYPE == SF_AMB_NEAR_OUTSIDE and env.nearest_outside then return env.nearest_outside end
if SF_AMB_TYPE == SF_AMB_WINDOW and env.nearest_window then return env.nearest_window end
end
local p_br = {}
-- Forces a sound to play
local fP
---Insers ambience sound and forces it to play.
---@param snd string
---@param nVolume number
---@param playbackSpeed number
---@client
function StormFox2.Ambience.ForcePlay( snd, nVolume, playbackSpeed )
if string.sub(snd, 0, 6) ~= "sound/" then
snd = "sound/" .. snd
end
fP[snd] = nVolume
p_br[snd] = playbackSpeed or 1
end
hook.Add("Think", "StormFox2.Ambiences.Logic", function()
if not StormFox2 or not StormFox2.Weather or not StormFox2.Weather.GetCurrent then return end
local c = StormFox2.Weather.GetCurrent()
local v_pos = StormFox2.util.GetCalcView().pos
local env = StormFox2.Environment.Get()
-- Set all target volume to 0
for _,t2 in pairs( SF_AMB_CHANNEL ) do
t2[2] = 0
end
-- Generate a list of all sounds the client should hear. And set the the volume
local t = {}
if c.ambience_tab and StormFox2.Setting.SFEnabled() then
for _,amb_object in ipairs( c.ambience_tab ) do
local c_vol = t[amb_object.snd] or 0
-- WATER
if env.in_water then -- All sounds gets ignored in water. Exp SF_AMB_INWATER
if amb_object.SF_AMB_TYPE == SF_AMB_INWATER then
if c_vol > amb_object.m_vol then
continue
end
c_vol = amb_object.m_vol
elseif env.outside and amb_object.SF_AMB_TYPE == SF_AMB_UNDER_WATER_Z then
local dis = env.in_water - v_pos.z
local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol
if c_vol > vol then
continue
end
c_vol = vol
end
-- OUTSIDE
elseif amb_object.SF_AMB_TYPE == SF_AMB_OUTSIDE and env.outside then
if c_vol > amb_object.m_vol then
continue
end
c_vol = amb_object.m_vol -- Outside is a constant volume
-- ROOFS
elseif amb_object.SF_AMB_TYPE >= SF_AMB_ROOF_ANY and amb_object.SF_AMB_TYPE <= SF_AMB_ROOF_WATER then
if amb_object.SF_AMB_TYPE == SF_AMB_ROOF_ANY and env.roof_z then
if env.roof_type ~= SF_AMB_ROOF_CONCRETE and env.roof_type ~= SF_AMB_ROOF_GROUND then
local dis = env.roof_z - v_pos.z
local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol
if c_vol > vol then
continue
end
c_vol = vol
end
elseif env.roof_z and env.roof_type then
if amb_object.SF_AMB_TYPE == SF_AMB_ROOF_GROUND and env.roof_type == SF_DOWNFALL_HIT_GROUND then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_GLASS and env.roof_type == SF_DOWNFALL_HIT_GLASS then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_METAL and env.roof_type == SF_DOWNFALL_HIT_METAL then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_WOOD and env.roof_type == SF_DOWNFALL_HIT_WOOD then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_CONCRETE and env.roof_type == SF_DOWNFALL_HIT_CONCRETE then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_WATER and env.roof_type == SF_DOWNFALL_HIT_WATER then
else
continue
end
local dis = env.roof_z - v_pos.z
local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol
if c_vol > vol then
continue
end
c_vol = vol
end
else
local pos = check( amb_object.SF_AMB_TYPE, env )
if not pos then continue end
local dis = pos:Distance( v_pos )
--if amb_object.SF_AMB_TYPE == SF_AMB_WINDOW and env.nearest_outside then
-- dis = math.max(dis, 250 - env.nearest_outside:Distance(v_pos))
--end
if dis > amb_object.max then continue end -- Too far away
local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol
if vol <= 0 then continue end -- Vol too low
if c_vol > vol then
continue
end
c_vol = vol
end
if c_vol > 0 then
t[amb_object.snd] = c_vol
p_br[amb_object.snd] = amb_object.playbackrate
end
end
end
fP = t
hook.Run("StormFox2.Ambiences.OnSound")
-- Set the target volume
for snd, vol in pairs( t ) do
if not SF_AMB_CHANNEL[snd] then -- Request to create the sound channel
RequestChannel( snd )
else
SF_AMB_CHANNEL[snd][2] = vol -- Set the target volume
if IsValid( SF_AMB_CHANNEL[snd][1] ) then
if SF_AMB_CHANNEL[snd][1]:GetState() == 0 then -- Somehow stopped
SF_AMB_CHANNEL[snd][1]:Play()
end
if SF_AMB_CHANNEL[snd][1]:GetPlaybackRate() ~= p_br[snd] then
SF_AMB_CHANNEL[snd][1]:SetPlaybackRate(p_br[snd])
end
end
end
end
end)