Npcs/NpcStimulusSystem.cs

A game system that tracks temporary world stimuli for NPCs. It stores active Stimulus instances, allows broadcasting new stimuli with position, radius and lifetime, enumerates stimuli near a position, and prunes expired stimuli each update.

NetworkingFile Access
namespace Sandbox.Npcs;

/// <summary>
/// The world stimulus bus. Anything can broadcast a <see cref="Stimulus"/> (a gunshot, a
/// death, a crime) and nearby NPCs sense it through their <see cref="Layers.SensesLayer"/>.
/// This is how the world pushes events at NPCs, rather than NPCs only polling for entities.
/// </summary>
public sealed class NpcStimulusSystem : GameObjectSystem<NpcStimulusSystem>
{
	readonly List<Stimulus> _active = new();

	public NpcStimulusSystem( Scene scene ) : base( scene )
	{
		Listen( Stage.StartUpdate, 0, Prune, "PruneStimuli" );
	}

	/// <summary>
	/// Broadcast a stimulus into the world. <paramref name="radius"/> is how far it can be
	/// sensed; <paramref name="lifetime"/> is how long it lingers (seconds).
	/// </summary>
	public void Broadcast( string kind, Vector3 position, GameObject source = null, float radius = 1024f, float lifetime = 1f )
	{
		_active.Add( new Stimulus( kind, position, source, radius, Time.Now + lifetime ) );
	}

	/// <summary>
	/// Active stimuli whose range reaches a position.
	/// </summary>
	public IEnumerable<Stimulus> Near( Vector3 position )
	{
		var now = Time.Now;

		foreach ( var stimulus in _active )
		{
			if ( now >= stimulus.Expiry )
				continue;

			if ( (position - stimulus.Position).LengthSquared <= stimulus.Radius * stimulus.Radius )
				yield return stimulus;
		}
	}

	void Prune()
	{
		var now = Time.Now;
		_active.RemoveAll( s => now >= s.Expiry );
	}
}