combat/CombatVolume.cs
// CombatVolume.cs
using System;
using System.Collections.Generic;
using Sandbox;

public static class CombatVolume
{
    public static T FindBestInCone<T>( Vector3 origin, Vector3 direction, float maxDistance, float coneAngleDegrees, Func<T, bool> filter = null ) where T : Component
    {
        T bestTarget = null;
        var bestScore = -1f;
        var halfAngleRad = MathX.DegreeToRadian( coneAngleDegrees * 0.5f );
        var cosHalfAngle = MathF.Cos( halfAngleRad );
        var dirNorm = direction.Normal;
        var maxDistSq = maxDistance * maxDistance;

        foreach ( var comp in Game.ActiveScene.GetAllComponents<T>() )
        {
            if ( !comp.IsValid ) continue;
            if ( filter != null && !filter( comp ) ) continue;

            var toTarget = comp.WorldPosition - origin;
            var distSq = toTarget.LengthSquared;
            if ( distSq > maxDistSq || distSq < 0.001f ) continue;

            var dist = MathF.Sqrt( distSq );
            var dirToTarget = toTarget / dist;
            var dot = Vector3.Dot( dirNorm, dirToTarget );

            if ( dot < cosHalfAngle ) continue;

            var score = dot * (1.0f - (dist / maxDistance) * 0.4f);
            if ( score > bestScore )
            {
                bestScore = score;
                bestTarget = comp;
            }
        }

        return bestTarget;
    }
}