PanicSound.cs

Component attached to a Nextbot that plays a looping panic sound and adjusts its volume based on the nearest player distance. It spawns a looping SoundHandle, moves it with the entity, and exposes Intensidad (0..1) for other components.

namespace SBH;

using Sandbox;
using System.Linq;

/// <summary>
/// Reproduce un sonido en loop cuyo volumen crece cuando el jugador
/// está cerca. El clásico "pánico" de nextbot.
/// Va en el mismo GameObject que el NextbotBrain.
/// </summary>
public sealed class PanicSound : Component
{
	[Property, Description( "Sonido en loop (el SoundEvent debe tener Looping activado)" )]
	public SoundEvent Sonido { get; set; }

	[Property, Description( "Distancia a la que el sonido está a volumen máximo" )]
	public float DistanciaMinima { get; set; } = 150f;

	[Property, Description( "Distancia a la que el sonido se deja de oír" )]
	public float DistanciaMaxima { get; set; } = 1500f;

	[Property, Description( "Volumen máximo (0 a 1)" )]
	public float VolumenMaximo { get; set; } = 1f;

	/// <summary>
	/// Qué tan fuerte está sonando el pánico ahora mismo (0 a 1).
	/// Otros componentes lo usan, p.ej. ExpresionesNextbot mueve la boca
	/// cuando el sprunki "canta".
	/// </summary>
	public float Intensidad { get; private set; }

	SoundHandle _handle;

	protected override void OnEnabled()
	{
		if ( Sonido == null ) return;

		_handle = Sound.Play( Sonido, WorldPosition );
		if ( _handle != null ) _handle.Volume = 0f;
	}

	protected override void OnDisabled()
	{
		_handle?.Stop();
		_handle = null;
	}

	protected override void OnUpdate()
	{
		if ( _handle == null || !_handle.IsValid() ) return;

		// El sonido viaja pegado al sprunki
		_handle.Position = WorldPosition;

		// Buscar al jugador más cercano
		var jugador = Scene.GetAllComponents<PlayerController>()
			.OrderBy( p => p.WorldPosition.Distance( WorldPosition ) )
			.FirstOrDefault();

		if ( jugador == null )
		{
			Intensidad = 0f;
			_handle.Volume = 0f;
			return;
		}

		// Volumen: 1 cuando está a DistanciaMinima o menos, 0 a DistanciaMaxima o más
		var distancia = jugador.WorldPosition.Distance( WorldPosition );
		var t = 1f - ( (distancia - DistanciaMinima) / (DistanciaMaxima - DistanciaMinima) );
		Intensidad = t.Clamp( 0f, 1f );
		_handle.Volume = Intensidad * VolumenMaximo;
	}
}