FeelAudio.cs
using Sandbox;
using System;
namespace PaintballBaddies;
// Extra audio layers: a shooter-only low-end thump under the shotgun, a
// spatial pump rack after each blast, and a low-health heartbeat.
// Sources are synthesized project WAVs under sounds/feel/.
public static class FeelAudio
{
private static readonly Dictionary<string,SoundEvent[]> banks=new();
private static readonly Dictionary<string,int> previous=new();
public static int Plays {get;private set;}
private static SoundEvent[] Bank(string name,int count,float distance)
{
if(banks.TryGetValue(name,out var bank))return bank;
return banks[name]=Enumerable.Range(0,count).Select(i=>new SoundEvent
{Sounds=[SoundFile.Load(count==1 ? $"sounds/feel/{name}.vsnd" : $"sounds/feel/{name}_{i}.vsnd")],Volume=1,Pitch=1,
Distance=distance,DistanceAttenuation=distance>0,OcclusionEnabled=false,AirAbsorption=false,ReverbEnabled=false}).ToArray();
}
public static SoundHandle Play(string name,int count,Vector3 point,float volume,float distance=900,float pitchSpread=.03f)
{
var bank=Bank(name,count,distance);
int take=count==1 ? 0 : previous.TryGetValue(name,out var old) ? (old+Game.Random.Int(1,count-1))%count : Game.Random.Int(0,count-1);
previous[name]=take;
var handle=Sound.Play(bank[take],point);
if(handle is not null){handle.Volume=volume;handle.Pitch=1+Game.Random.Float(-pitchSpread,pitchSpread);Plays++;}
return handle;
}
public static void ShotgunLayers(Scene scene,GameObject shooter,Vector3 origin)
{
var local=MultiplayerSession.LocalPlayer(scene);
if(local.IsValid() && local.GameObject==shooter && scene.Camera.IsValid())
Play("shotgun_thump",3,scene.Camera.WorldPosition,.6f,0);
DelayedSound.Schedule(scene,"shotgun_pump",2,origin,.34f,.5f);
}
}
/// <summary>Plays one cue after a short delay, e.g. the pump rack after a blast.</summary>
public sealed class DelayedSound : Component
{
private string cue;private int count;private float delay,volume;
public static void Schedule(Scene scene,string cue,int count,Vector3 at,float delay,float volume)
{
var go=new GameObject(true,"Delayed sound"){NetworkMode=NetworkMode.Never,WorldPosition=at};go.Tags.Add("paintball_debris");
var d=go.Components.Create<DelayedSound>();d.cue=cue;d.count=count;d.delay=delay;d.volume=volume;
}
protected override void OnUpdate()
{
delay-=Time.Delta;if(delay>0)return;
FeelAudio.Play(cue,count,WorldPosition,volume);GameObject.Destroy();
}
}
/// <summary>Diner room tone was removed: its mains hum read as an unwanted buzz.
/// This stub only stops any tone an already-running scene is still playing.</summary>
public sealed class ArenaAmbience : Component
{
protected override void OnStart(){Destroy();}
}