Utility/Pushables.cs

Utility static class that computes a soft-body separation vector for nearby pushable actors (players and NPCs). It samples Player and NPC components in the same Scene and accumulates a falloff-weighted push away from each within radius*2 using MathE.SmoothKernel.

Reflection
using Sandbox;

namespace BrickJam;

/// <summary>
/// Soft-body separation for <see cref="IPushable"/> actors (players + NPCs). Scene-System replacement for
/// the legacy <c>StartTouch</c>/<c>EndTouch</c> "toPush" list: rather than tracking physics touches, each
/// fixed update we sample nearby pushables and accumulate a falloff-weighted push away from them. Keeps the
/// legacy formula - <c>MathE.SmoothKernel( radius*2, distance ) * PushForce</c> - so two actors gently shove
/// apart instead of stacking (player movement ignores the "player"/"npc" collision tags).
/// </summary>
public static class Pushables
{
	/// <summary>
	/// Accumulated push velocity (pre-<c>Time.Delta</c>) separating <paramref name="self"/> from every other
	/// pushable within <c>radius*2</c>. Caller scales by <c>Time.Delta</c> and adds it to its own velocity.
	/// </summary>
	public static Vector3 ComputeSeparation( Component self, Vector3 position, float radius, float pushForce )
	{
		var scene = self.Scene;
		if ( !scene.IsValid() )
			return Vector3.Zero;

		var reach = radius * 2f;
		var push = Vector3.Zero;

		void Consider( Component other, Vector3 otherPos )
		{
			if ( other == self || !other.IsValid() )
				return;

			var delta = (position - otherPos).WithZ( 0 );
			var distance = delta.Length;
			if ( distance >= reach || distance <= 0.001f )
				return;

			push += delta.Normal * MathE.SmoothKernel( reach, distance ) * pushForce;
		}

		foreach ( var p in scene.GetAllComponents<Player>() )
			Consider( p, p.WorldPosition );

		foreach ( var n in scene.GetAllComponents<NPC>() )
			Consider( n, n.WorldPosition );

		return push;
	}
}