AI/Animals/Actions/HuntAction.cs
namespace HC3;

public partial class HuntAction : BehaviorTreeAction
{
	[Property] public float HungerStopThreshold { get; set; } = 0.15f;

	private Agent _target;

	public override float Score()
	{
		if ( !Agent.IsValid() )
			return 0f;

		if ( !Agent.Tags.Has( "escaped" ) )
			return 0f;

		// Stop hunting if nearly full
		var needSystem = Agent.GetComponent<NeedSystem>();
		if ( needSystem != null )
		{
			var hunger = needSystem.GetNeed( "Hunger" );
			if ( hunger.IsValid() && hunger.Level < HungerStopThreshold )
				return 0f;
		}

		_target = FindClosestTarget();

		return _target.IsValid() ? 999f : 0f;
	}

	protected override void OnTreeStart()
	{
		Agent.Controller.IsRunning = true;
		Agent.Tags.Set( "hunt", true );
		Agent.Tags.Set( "eating", false );
	}

	protected override void OnTreeStop()
	{
		Agent.Controller.IsRunning = false;
		Agent.Tags.Set( "hunt", false );
		Agent.Tags.Set( "eating", false );
	}

	protected override Node BuildTree()
	{
		if ( !_target.IsValid() )
			return null;

		return new SequenceNode(
		[
			// Follow the target
			new FollowTargetNode( Agent, _target.GameObject, 24f, 32f, "Hunting down target" ),
        
			// Attack the target with animation
			new TimedSequenceNode(
				Agent,
				"Run_Attack",
				1f,
				onStart: () => {
					SetTagFor( "attack", 1f );
				},
				conditionCheck: () => _target.IsValid() &&
					_target.WorldPosition.DistanceSquared( Agent.WorldPosition ) < 64f * 64f,
				reason: "Attacking prey",
				icon: "gavel"
			),

			// Eat the target and restore hunger
			new TimedSequenceNode(
				Agent,
				"Eating",
				3.0f,
				onStart: () => {
					_target.Damage( 999 );
					SetTagFor( "eating", 2f );
				},
				reason: "Consuming prey",
				icon: "restaurant"
			),

			// Reduce hunger after eating
			new RestoreNeedNode( Agent, "Hunger", -0.5f, isRelative: true )
		] );
	}

	async void SetTagFor( string tag, float time )
	{
		Agent.Tags.Set( tag, true );
		await GameTask.DelaySeconds( time );
		Agent.Tags.Set( "attack", false );
	}

	private Agent FindClosestTarget()
	{
		Agent closest = null;
		float closestDist = float.MaxValue;
		foreach ( var x in Scene.GetAll<Agent>() )
		{
			if ( x is Animal ) continue;
			if ( x == Agent || !x.IsValid() ) continue;
			if ( x.Building.IsValid() ) continue;
			float dist = x.WorldPosition.DistanceSquared( Agent.WorldPosition );
			if ( dist < closestDist )
			{
				closestDist = dist;
				closest = x;
			}
		}
		return closest;
	}
}