Component attached to a bullet that detects nearby valid targets and tells the Bullet to home in. It listens for trigger enter/exit to track potential targets, checks distance and timing conditions each update, then calls Bullet.ApplyHoming when a closest target is chosen.
using System;
using Sandbox;
public sealed class BulletHomingDetector : Component, Component.ITriggerListener
{
[Property] public SphereCollider SphereCollider { get; set; }
public Bullet Bullet { get; set; }
public TimeSince TimeSinceHome { get; set; }
public float HomingDelay { get; set; }
private List<Thing> _targets = new();
public float Radius { get; set; }
public bool CanTargetPlayers { get; set; } // todo:
protected override void OnStart()
{
base.OnStart();
TimeSinceHome = 0f;
HomingDelay = 0.17f;
}
public void Refresh()
{
Bullet.HasHomed = false;
TimeSinceHome = 0f;
}
protected override void OnUpdate()
{
//Gizmo.Draw.Color = _targets.Count == 0 ? Color.White.WithAlpha( 0.5f ) : (HasHomed || TimeSinceHome < HomingDelay ? Color.Red.WithAlpha( 0.5f ) : new Color( 0f, 0f, 1f, 0.9f ));
//Gizmo.Draw.LineSphere( WorldPosition, Radius );
if ( Bullet.HasHomed || TimeSinceHome < HomingDelay || _targets.Count == 0 )
return;
if ( Bullet.Stats[BulletStat.ArcHeight] > 0f )
{
var lifetimeProgress = Bullet.TimeSinceSpawn / Bullet.Stats[BulletStat.Lifetime];
if ( lifetimeProgress < 0.5f || MathF.Abs( Bullet.WorldPosition.z - Bullet.BaseZPos ) > 15f )
return;
}
float closestDistSqr = float.MaxValue;
Thing closestTarget = null;
foreach ( var target in _targets )
{
if ( !target.IsValid() )
continue;
var distSqr = (target.Position2D - Bullet.Position2D).LengthSquared;
if ( distSqr > MathF.Pow( Radius, 2f ) )
continue;
if ( distSqr < closestDistSqr )
{
closestDistSqr = distSqr;
closestTarget = target;
}
}
if ( closestTarget.IsValid() )
Home( (closestTarget.Position2D - Bullet.Position2D).Normal );
}
void ITriggerListener.OnTriggerEnter( Collider collider )
{
if ( !collider.IsTrigger )
return;
if ( collider.Tags.Has( "enemy" ) )
{
var enemy = collider.GetComponent<Enemy>();
if( enemy.IsValid() )
{
_targets.Add( collider.GetComponent<Thing>() );
}
}
}
void ITriggerListener.OnTriggerExit( Collider collider )
{
if ( !collider.IsTrigger )
return;
if ( collider.Tags.Has( "enemy" ) )
{
_targets.Remove( collider.GetComponent<Thing>() );
}
}
public void Home(Vector2 dir)
{
Bullet.ApplyHoming( dir );
Bullet.HasHomed = true;
TimeSinceHome = 0f;
}
}