AI/Animals/Animal.cs
using HC3.UI;

namespace HC3;

public partial class Animal : Agent, IInspectable, IPlacementObject
{
	[Property]
	public string Name { get; set; } = "Animal";

	[Property]
	public int Cost { get; set; } = 0;

	[Property, ImageAssetPath]
	public string Thumbnail { get; set; }

	[Property]
	public LineRenderer Line { get; set; }

	public string Title => Name;
	[Property] public bool IsPlaced { get; set; }
	public Group Group => new( "Animal", "Mammals" );

	private RealTimeSince _lastEnclosureCheck;
	private const float EnclosureCheckInterval = 2.0f;

	private Color green = "#2FFF3D";
	private Color red => green.WithAlpha( 0f );

	Window IInspectable.Select()
	{
		var inspector = new AnimalInspector();
		inspector.Animal = this;

		return inspector;
	}

	public override void Tick()
	{
		if ( !IsPlaced )
			return;

		Animate();

		// Only check periodically
		if ( _lastEnclosureCheck > EnclosureCheckInterval )
		{
			_lastEnclosureCheck = 0;
			UpdateEscapedStatus();
		}

		if ( IInspectable.Current == this )
		{
			UpdateSelected();
		}
		else if ( Line.Enabled )
		{
			Line.Enabled = false;
		}
	}

	void UpdateSelected()
	{
		if ( Controller.IsNavigating )
		{
			Line.Enabled = true;
			Line.VectorPoints = Controller.GetNetworkedPath().Select( x => x.WithZ( x.z + 8f ) ).ToList();

			var progress = Controller.GetPathProgress();

			Line.Color = new Gradient(
			[
				new Gradient.ColorFrame( 0f, red ),
				new Gradient.ColorFrame( progress + 0.0025f, red ),
				new Gradient.ColorFrame( progress + 0.02f, green ),
				new Gradient.ColorFrame( 1f, green )
			] );
		}
	}

	void Animate()
	{
		if ( Tags.Has( "sleep" ) )
		{
			SetSequence( "Sleep" );
			return;
		}

		if ( !Tags.Has( "hunt" ) )
		{
			// Use default animations
			UpdateAnimation();
			return;
		}

		if ( !Game.IsPlaying )
			return;

		if ( Networking.IsHost )
			IsMoving = Controller.IsNavigating;

		if ( !string.IsNullOrEmpty( _sequenceOverride ) )
		{
			SetSequence( _sequenceOverride, _sequenceOverrideSpeed );
			return;
		}

		if ( Tags.Has( "eating" ) )
		{
			SetSequence( "Eating" );
		}
		else if ( Tags.Has( "attack" ) )
		{
			SetSequence( "Run_Attack" );
		}
		if ( IsMoving )
		{
			SetSequence( Controller.IsRunning ? "Run" : "Walk", WalkSpeed );
		}
	}

	/// <summary>
	/// Check if this animal is within a region that has an enclosure AreaTile.
	/// Returns true if the animal is in an enclosure, false if it has escaped.
	/// </summary>
	public bool IsInEnclosure()
	{
		if ( Controller == null )
			return true; // Default to true if we can't check

		// Get the cell at the animal's current position
		var gridPos = GridManager.WorldToGridPosition( WorldPosition );
		var cell = GridManager.Instance?.GetCell( gridPos );

		if ( cell == null )
			return false;

		// Check if this cell contains an enclosure area tile
		return cell.GetComponents<AreaTile>()
			.Any( area => area.AreaType == AreaType.Enclosure && !area.GhostPreview );
	}

	/// <summary>
	/// Update the "escaped" tag based on whether the animal is in an enclosure
	/// </summary>
	private void UpdateEscapedStatus()
	{
		bool inEnclosure = IsInEnclosure();
		bool hasEscapedTag = Tags.Has( "escaped" );

		if ( !inEnclosure && !hasEscapedTag )
		{
			// Animal has escaped!
			Tags.Set( "escaped", true );
		}
		else if ( inEnclosure && hasEscapedTag )
		{
			// Animal is back in an enclosure
			Tags.Set( "escaped", false );
		}
	}
}