GameAudio.cs
namespace NiuLai;

/// <summary>
/// Movie-sampled audio. Paths are Assets/sounds/… compiled by sbox-dev.
/// </summary>
public static class GameAudio
{
	public const string Song = "sounds/music/yu_hou_qing_feng";
	public const string OpenSting = "sounds/sfx/open_sting";
	public const string LarkNg = "sounds/sfx/lark_ng";
	public const string LarkRide = "sounds/sfx/lark_ride";
	public const string LikeHere = "sounds/sfx/like_here";
	public const string MamaWake = "sounds/sfx/mama_wake";
	public const string MamaOk = "sounds/sfx/mama_ok";
	public const string SlowDown = "sounds/sfx/slow_down";
	public const string Wolves = "sounds/sfx/wolves";
	public const string WolfStare = "sounds/sfx/wolf_stare";
	public const string Leopard = "sounds/sfx/leopard";
	public const string CanRest = "sounds/sfx/can_rest";
	public const string Moo = "sounds/sfx/moo";
	public const string Cart = "sounds/sfx/cart";
	public const string CartGrow = "sounds/sfx/cart_grow";
	public const string Rain = "sounds/sfx/rain";
	public const string Snake = "sounds/sfx/snake";
	public const string Stand = "sounds/sfx/stand";
	public const string Milk = "sounds/sfx/milk";
	public const string NameNiu = "sounds/sfx/name_niu";
	public const string CrowdWake = "sounds/sfx/crowd_wake";
	public const string CrowdRide = "sounds/sfx/crowd_ride";
	public const string CrowdMilk = "sounds/sfx/crowd_milk";
	public const string CrowdBig = "sounds/sfx/crowd_big";
	public const string CrowdCart = "sounds/sfx/crowd_cart";
	public const string CrowdNg = "sounds/sfx/crowd_ng";

	static SoundHandle _music;
	static SoundHandle _oneshot;
	static string _musicPath;

	public static bool SongPlaying => _music.IsValid() && _musicPath == Song;

	public static void PlaySong()
	{
		StartMusic( Song, 0.72f );
	}

	public static void PlayTitleSting()
	{
		PlayOne( OpenSting, 0.9f );
	}

	public static void StopMusic()
	{
		if ( _music.IsValid() )
			_music.Stop();
		_music = default;
		_musicPath = null;
	}

	public static void PlayClip( string path, float volume = 0.95f )
	{
		PlayOne( path, volume );
	}

	public static void PlayCrowd( string path )
	{
		// Second layer: the room laughing, still muddy on purpose.
		PlayLocal( path, 0.7f );
	}

	public static void PlayForHotspot( string id )
	{
		switch ( id )
		{
			case "lark":
				PlayOne( GameState.HeardLikeHere ? LarkRide : LikeHere, 0.95f );
				break;
			case "mom":
				if ( GameState.MomGone )
					PlayOne( CanRest, 0.95f );
				else if ( GameState.Beat == Beat.Night && !GameState.HasMilk )
					PlayOne( MamaOk, 0.95f );
				else
					PlayOne( MamaWake, 0.95f );
				break;
			case "leopard":
				PlayOne( GameState.GaveMilk ? Leopard : Milk, 0.95f );
				break;
			case "grass":
				PlayOne( SlowDown, 0.9f );
				break;
			case "cart":
				PlayOne( CartGrow, 0.95f );
				break;
			case "snake":
				PlayOne( Snake, 0.95f );
				break;
			case "rock":
				PlayOne( WolfStare, 0.95f );
				break;
			case "dad":
				PlaySong();
				break;
		}
	}

	public static void PlayWolvesAt( Vector3 pos )
	{
		try
		{
			var h = Sound.Play( Wolves, pos );
			if ( h.IsValid() )
				h.Volume = 0.9f;
		}
		catch
		{
			PlayOne( Wolves, 0.85f );
		}
	}

	static void StartMusic( string path, float volume )
	{
		if ( _music.IsValid() && _musicPath == path )
			return;

		StopMusic();
		_music = PlayLocal( path, volume );
		_musicPath = _music.IsValid() ? path : null;
		if ( !_music.IsValid() )
			Log.Warning( $"[Niu Lai] music missing: {path} — play once in sbox-dev to compile .vsnd" );
	}

	static void PlayOne( string path, float volume )
	{
		if ( _oneshot.IsValid() )
			_oneshot.Stop();
		_oneshot = PlayLocal( path, volume );
	}

	static SoundHandle PlayLocal( string path, float volume )
	{
		if ( string.IsNullOrWhiteSpace( path ) )
			return default;

		var clean = path.Trim().Replace( '\\', '/' );
		if ( clean.EndsWith( ".sound", StringComparison.OrdinalIgnoreCase ) )
			clean = clean[..^6];

		var candidates = new[]
		{
			clean + ".sound",
			clean,
			"sounds/" + clean.TrimStart( '/' ).Replace( "sounds/", "" ) + ".sound",
		};

		foreach ( var candidate in candidates )
		{
			try
			{
				if ( ResourceLibrary.Get<SoundEvent>( candidate ) is SoundEvent evt )
				{
					var fromEvt = Sound.Play( evt, Vector3.Zero );
					if ( fromEvt.IsValid() )
					{
						fromEvt.ListenLocal = true;
						fromEvt.SpacialBlend = 0f;
						fromEvt.Volume = volume;
						fromEvt.Pitch = 0.90f + Random.Shared.Float() * 0.16f;
						return fromEvt;
					}
				}
			}
			catch { }

			try
			{
				var h = Sound.Play( candidate, Vector3.Zero );
				if ( !h.IsValid() )
					continue;
				h.ListenLocal = true;
				h.SpacialBlend = 0f;
				h.Volume = volume;
				h.Pitch = 0.90f + Random.Shared.Float() * 0.16f;
				return h;
			}
			catch { }
		}

		return default;
	}
}