AI/ActionSystem/Actions/Staff/CollectTrashAction.cs
using HC3.Inventory;

namespace HC3;

public sealed class CollectTrashAction : BehaviorTreeAction
{
	private TrashPile Trash { get; set; }

	public override float Score()
	{
		if ( !Agent.IsValid() ) return 0f;
		if ( Trash.IsValid() ) return 300f;
		if ( TrashPile.All.Count == 0 ) return 0f;

		FindClosestTrash();
		return Trash.IsValid() ? 300f : 0f;
	}

	protected override void OnTreeStart()
	{
		if ( !Trash.IsValid() )
			FindClosestTrash();

		Controller.ClearNavigation();
		Agent.Body.SetBodyGroup( 0, 1 );
	}

	protected override void OnTreeStop()
	{
		Trash = null;
		Agent.Body.SetBodyGroup( 0, 0 );
	}

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

		return new SequenceNode( new()
		{
			new MoveToNode( Agent, Trash.WorldPosition, reason: "Heading to trash pile" ),
			new AgentSequenceNode( Agent, "Action_Sweep", 5f, reason: "Cleaning trash" ),
			new RemoveTrashNode( Agent, Trash )
		} );
	}

	private void FindClosestTrash()
	{
		TrashPile closest = null;
		float closestDist = float.MaxValue;
		foreach ( var t in TrashPile.All )
		{
			float dist = t.WorldPosition.DistanceSquared( Agent.WorldPosition );
			if ( dist < closestDist )
			{
				closestDist = dist;
				closest = t;
			}
		}
		Trash = closest;
	}
}

file sealed class RemoveTrashNode : Node
{
	private readonly TrashPile Trash;
	private readonly Agent Agent;
	private bool _done;

	public RemoveTrashNode( Agent agent, TrashPile trash )
	{
		Agent = agent;
		Trash = trash;
	}

	public override Status Tick()
	{
		if ( _done )
			return Status.Success;

		if ( Trash?.GameObject?.Root.IsValid() != true )
			return Status.Failure;

		if ( Agent is Staff staff )
			staff.IncreaseStat( StaffStats.TrashCleanedUp );

		Trash.GameObject.Root.Destroy();
		Stats.Increment( "staff.trash_cleaned" );
		_done = true;

		return Status.Success;
	}

	public override ActionDisplayInfo? GetDisplay()
	{
		return new ActionDisplayInfo( "recycling", "Cleaning trash...", 0 );
	}
}