--[[ | 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)