IncomingPaintAwareness.cs
using Sandbox;
namespace PaintballBaddies;

/// <summary>Shared physical paintball awareness for player and AI projectiles.</summary>
public static class IncomingPaintAwareness
{
    public static void Report(Scene scene,GameObject shooter,Vector3 origin,Vector3 from,Vector3 to,
        GameObject impact,HashSet<ArenaOpponent> alerted)
    {
        var segment=to-from;
        var lengthSquared=segment.LengthSquared;
        foreach(var bot in scene.GetAllComponents<ArenaOpponent>())
        {
            if(!bot.CombatEnabled || !bot.Enabled || bot.GameObject==shooter || alerted.Contains(bot))continue;
            var centre=bot.WorldPosition+Vector3.Up*38;
            float t=lengthSquared>.001f ? (Vector3.Dot(centre-from,segment)/lengthSquared).Clamp(0,1) : 0;
            var closest=from+segment*t;
            bool ownCover=impact.IsValid() && bot.ReservedCover.IsValid() && impact==bot.ReservedCover.GameObject
                && (to-centre).Length<150;
            if(!ownCover)
            {
                if((closest-centre).Length>85)continue;
                var nearby=scene.Trace.Ray(closest,centre).IgnoreGameObjectHierarchy(shooter)
                    .IgnoreGameObjectHierarchy(bot.GameObject).WithoutTags("paintball_debris").Run();
                if(nearby.Hit && nearby.GameObject!=bot.ReservedCover?.GameObject)continue;
            }
            alerted.Add(bot);
            bot.ObserveIncomingPaint(shooter,origin);
        }
    }
}