Projectile component for a hook gun. It moves from a spawn origin along Velocity, traces for impacts up to MaxRange, latches to world surfaces (ignoring the firing player, weapon, and dead-tagged objects), notifies the OwnerGun on attach or miss, and destroys itself on miss.
using Sandbox;
using System;
/// <summary>Travels from the hook gun, latches on world surfaces, then reports attach/miss to the owner.</summary>
public sealed class HookProjectile : Component
{
public Vector3 Velocity { get; set; }
public GameObject WeaponObject { get; set; }
public HookGun OwnerGun { get; set; }
public Vector3 SpawnOrigin { get; set; }
public float MaxRange { get; set; } = 2500f;
private bool _latched;
private bool _notified;
protected override void OnUpdate()
{
if ( _latched )
return;
if ( (WorldPosition - SpawnOrigin).Length >= MaxRange )
{
NotifyMissed();
GameObject.Destroy();
return;
}
Vector3 frameMovement = Velocity * Time.Delta;
Vector3 nextPosition = WorldPosition + frameMovement;
var trace = Scene.Trace.Ray( WorldPosition, nextPosition )
.Size( 1f )
.UseHitboxes()
.WithoutTags( "dead" )
.IgnoreGameObject( GameObject )
.IgnoreGameObject( WeaponObject );
if ( Player.CurrentPlayer.IsValid() )
trace = trace.IgnoreGameObjectHierarchy( Player.CurrentPlayer.GameObject );
var result = trace.Run();
if ( result.Hit )
{
HandleImpact( result );
return;
}
WorldPosition = nextPosition;
}
private void HandleImpact( SceneTraceResult trace )
{
var health = trace.GameObject?.Components.Get<HealthComponent>( FindMode.InSelf );
if ( health.IsValid() )
{
NotifyMissed();
GameObject.Destroy();
return;
}
_latched = true;
WorldPosition = trace.HitPosition;
Velocity = Vector3.Zero;
OwnerGun?.OnAttached( trace.HitPosition );
}
protected override void OnDestroy()
{
if ( !_latched )
NotifyMissed();
}
private void NotifyMissed()
{
if ( _notified )
return;
_notified = true;
OwnerGun?.OnHookMissed();
}
}