Npcs/Tasks/FireWeapon.cs
using Sandbox.Npcs.Layers;

namespace Sandbox.Npcs.Tasks;

/// <summary>
/// Shoots a weapon at a target for a specific duration
/// </summary>
public class FireWeapon : TaskBase
{
	/// <summary>The weapon component to fire.</summary>
	public BaseWeapon Weapon { get; }

	/// <summary>The GameObject to aim at.</summary>
	public GameObject Target { get; }

	/// <summary>How long (seconds) to keep firing before the task completes.</summary>
	public float BurstDuration { get; }

	/// <summary>Body rotation speed (degrees/s scale) used while actively aiming. Higher than the default look speed.</summary>
	public float AimTurnSpeed { get; set; } = 8f;

	private TimeUntil _burstEnd;

	public FireWeapon( BaseWeapon weapon, GameObject target, float burstDuration = 1.5f )
	{
		Weapon = weapon;
		Target = target;
		BurstDuration = burstDuration;
	}

	protected override void OnStart()
	{
		_burstEnd = BurstDuration;
	}

	protected override TaskStatus OnUpdate()
	{
		if ( !Weapon.IsValid() )
			return TaskStatus.Failed;

		if ( !Target.IsValid() )
			return TaskStatus.Failed;

		RotateBodyTowardTarget();

		// Only fire once we're actually facing the target
		if ( Npc.Animation.IsFacingTarget() && Weapon.CanPrimaryAttack() )
		{
			Weapon.PrimaryAttack();
			Npc.Animation.TriggerAttack();
		}

		return _burstEnd ? TaskStatus.Success : TaskStatus.Running;
	}

	private void RotateBodyTowardTarget()
	{
		var toTarget = (Target.WorldPosition - Npc.WorldPosition).WithZ( 0 );
		if ( toTarget.LengthSquared < 1f ) return;

		var targetRot = Rotation.LookAt( toTarget.Normal, Vector3.Up );
		Npc.WorldRotation = Rotation.Lerp( Npc.WorldRotation, targetRot, AimTurnSpeed * Time.Delta );
	}
}