Units/Targeter.cs
namespace PlanetMeat;
[Group( "Planet Meat - Units" ), Title( "Targeter" ), Icon( "visibility" )]
public sealed class Targeter : Component
{
public enum TargetingStrategies { Nearest, Healthiest }
[Property]
public TargetingStrategies TargetingStrategy
{
get;
set
{
field = value;
TargetingComparer = null;
}
} = TargetingStrategies.Nearest;
[Property] public bool CanTargetEnemies { get; set; } = true;
[Property] public bool CanTargetFriends { get; set; } = false;
[Property] public bool CanTargetLiving { get; set; } = true;
[Property] public bool CanTargetDead { get; set; } = false;
public int NumTargets
{
get;
set
{
if ( _nextTargets is not null && _nextTargets.Capacity < value + 1 )
_nextTargets.Capacity = value + 1;
field = value;
}
} = 1;
public Targetable SoleFocus { get; set; }
private List<Targetable> _nextTargets;
public List<Targetable> NextTargets
{
get
{
if ( _nextTargets is null )
_nextTargets = new List<Targetable>( NumTargets + 1 );
else
_nextTargets.Clear();
if ( SoleFocus is null )
{
if ( TargetingComparer is IComparer<Targetable> comparer )
{
foreach ( var t in TargetsInRange )
{
var index = _nextTargets.BinarySearch( t, comparer );
if ( index < 0 ) index = ~index;
if ( index < NumTargets )
{
_nextTargets.Insert( index, t );
if ( _nextTargets.Count > NumTargets )
_nextTargets.RemoveAt( NumTargets );
}
}
}
}
else if ( IsValidTarget( SoleFocus ) )
{
_nextTargets.Add( SoleFocus );
}
return _nextTargets;
}
}
public IEnumerable<Targetable> TargetsInRange => Scene
.FindInPhysics( new Sphere( WorldPosition, Range ) )
.Select( g => g.Components.Get<Targetable>() )
.Where( t => t is not null && CanTarget( t ) );
public ITargeterStatus Status => field ??= this.FindNearestUpwards<ITargeterStatus>();
public float Range => AggregateRange.Total;
private float RangeSq
{
get
{
if ( float.IsNaN( field ) )
{
var r = Range;
field = r > 0 ? r * r : -1;
}
return field;
}
set;
} = float.NaN;
[Property] public int BaseRange { get; set; } = 2048;
[Property] public string RangeImprovement { get; set; }
private ValueAggregator<int> AggregateRange
{
get
{
if ( field is null )
{
field = ValueAggregator<int>.FromImprovement( RangeImprovement, BaseRange );
DirtyRange();
}
return field;
}
}
[Property] public float Arc { get; set; } = -1.0f;
private float DistSqTo( Targetable t ) => t.WorldPosition.DistanceSquared( WorldPosition );
private bool InRange( Targetable t ) => RangeSq < 0 || DistSqTo( t ) <= RangeSq;
private bool InArc( Targetable t ) => Arc < -0.99f || (t.WorldPosition - WorldPosition).Normal.Dot( WorldRotation.Forward ) > 0.5f;
private bool CanSee( Targetable t ) => InRange( t ) && InArc( t );
public bool IsValidTarget( Targetable t ) => t is not null && CanTarget( t ) && CanSee( t );
private delegate I TargetCompare<in T, out I>( T leader, T challenger );
private IComparer<Targetable> TargetingComparer
{
get
{
return field ??= Comparer<Targetable>.Create( TargetingStrategy switch
{
TargetingStrategies.Healthiest => CompareHealthiest,
_ => CompareNearest
} );
}
set;
} = null;
private int CompareNearest( Targetable a, Targetable b )
{
if ( a is null )
return 1;
if ( b is null )
return -1;
return (int)(DistSqTo( a ) - DistSqTo( b ));
}
private int CompareHealthiest( Targetable a, Targetable b )
{
if ( a is null )
return 1;
if ( b is null )
return -1;
var compareCurrent = b.CurrentHp.CompareTo( a.CurrentHp );
if ( compareCurrent != 0 )
return compareCurrent;
var compareMax = b.MaxHp.CompareTo( a.MaxHp );
if ( compareMax != 0 )
return compareMax;
return (int)(DistSqTo( a ) - DistSqTo( b ));
}
public void DirtyRange()
{
RangeSq = float.NaN;
}
public void ContributeRange( string source, int amount )
{
AggregateRange.SetContribution( source, amount );
DirtyRange();
}
public void ContributeRange( Component comp, int amount )
{
AggregateRange.SetContribution( comp, amount );
DirtyRange();
}
public float GetRangeWithout( Component contributor )
{
return AggregateRange.Without( contributor );
}
public bool CanTarget( Targetable t )
{
return t.IsValid
&& (t.Killed ? CanTargetDead : CanTargetLiving)
&& (Status.IsFriendlyTo( t ) ? CanTargetFriends : CanTargetEnemies);
}
protected override void DrawGizmos()
{
base.DrawGizmos();
Gizmo.Draw.Color = Color.Orange;
Gizmo.Draw.LineSphere( Vector3.Zero, BaseRange );
}
}