AI/Animals/Actions/EatFoodAction.cs
using System;

namespace HC3;

public sealed class EatFoodAction : BehaviorTreeAction
{
	private GameObject _foodSource;
	private Vector3 _targetPosition;
	private bool _isEating;

	[Property] public float SearchRadius { get; set; } = 500f;
	[Property] public string FoodTag { get; set; } = "food";
	[Property] public float HungerThreshold { get; set; } = 0.4f;
	[Property] public RangedFloat EatingDuration { get; set; } = new RangedFloat( 5f, 10f );
	[Property] public string EatAnimation { get; set; } = "eat";

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

		var needSystem = Agent.GetComponent<NeedSystem>();
		if ( needSystem == null ) return 0f;

		var hungerNeed = needSystem.GetNeed( "Hunger" );
		if ( hungerNeed == null ) return 0f;

		// If already eating, keep eating
		if ( _isEating ) return 300f;

		// If hunger is high enough, find food
		if ( hungerNeed.GetPriority() > HungerThreshold )
		{
			_foodSource = FindFood();

			if ( _foodSource.IsValid() )
			{
				_targetPosition = _foodSource.WorldPosition;
				return 150f;
			}
		}

		return 0f;
	}

	protected override void OnTreeStart()
	{
		_isEating = true;
		Agent.Tags.Set( "eating", true );
	}

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

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

		return new SequenceNode(
		[
			new MoveToNode( Agent, _targetPosition, "Going to food"),
			new TimedTagNode( Agent, "eating", Random.Float( EatingDuration.Min, EatingDuration.Max ), "Eating food" ),
			new RestoreNeedNode(Agent, "Hunger", 0.0f)
		] );
	}

	private GameObject FindFood()
	{
		// Use Physics.Sphere to find all colliders within search radius
		var position = Agent.WorldPosition;
		var results = Scene.FindInPhysics( new Sphere( position, SearchRadius ) );

		foreach ( var gameObject in results )
		{
			// Check if this is a valid food source with the right tag
			if ( gameObject.IsValid() && gameObject.Tags.Has( FoodTag ) )
			{
				// Check if we can walk to it
				if ( GridManager.IsWalkable( Agent.WorldPosition, gameObject.WorldPosition ) )
				{
					return gameObject;
				}
			}
		}

		return null;
	}
}