AI/Guests/Inventory/Components/Consumable.cs
using Sandbox.Diagnostics;

namespace HC3.Inventory;

public partial class Consumable : Component, IItemEvents
{
	/// <summary>
	/// The need we're satisfying
	/// </summary>
	[Property] public NeedDefinition Need { get; set; }

	/// <summary>
	/// Can this consumable create trash?
	/// </summary>
	[Property] public bool CreatesTrash { get; set; }

	/// <summary>
	/// The trash pile (dropped) prefab.
	/// </summary>
	[Property] public GameObject TrashPilePrefab { get; set; }

	/// <summary>
	/// The item prefab for the trash.
	/// </summary>
	[Property] public GameObject TrashItemPrefab { get; set; }

	/// <summary>
	/// A tag to apply to the visitor, for a set amount of time
	/// </summary>
	[Property, Group( "Tags" )] public string Tag { get; set; }

	/// <summary>
	/// How long to apply the tag for?
	/// </summary>
	[Property, Group( "Tags" )] public float Duration { get; set; }

	void IItemEvents.OnUsed( Item item )
	{
		if ( !Networking.IsHost )
			return;

		if ( CreatesTrash )
		{
			TryDropTrash( item.Guest );
		}

		Assert.True( Need.IsValid(), "Need is not valid for Consumable" );
		item.Guest.Needs.GetNeed( Need ).Level = 0;
	}

	void TryDropTrash( Guest guest )
	{
		// We'll always drop trash if the park has no bins for us.
		if ( !Scene.GetAllComponents<Bin>().Any() )
		{
			DropTrashPile( guest );
			return;
		}

		// Only drop trash if we're a delinquent.
		if ( guest.Delinquency < 0.2f )
		{
			GiveTrashItem( guest );
			return;
		}

		// There's a high chance we'll drop trash.
		if ( Game.Random.Float() > 0.8f )
		{
			GiveTrashItem( guest );
			return;
		}

		// We're commiting a crime here, because there is bins and we're not using them.
		guest.CommitCrime( 0.05f );
		DropTrashPile( guest );
	}

	void DropTrashPile( Guest guest )
	{
		var startPosition = guest.WorldPosition + Vector3.Up * 16f;
		startPosition.x += Game.Random.Float( -2f, 2f );
		startPosition.y += Game.Random.Float( -2f, 2f );

		var trace = Scene.Trace
			.WithTag( "path" )
			.Ray( startPosition, startPosition + Vector3.Down * 64f )
			.Run();

		if ( !trace.Hit || TrashPilePrefab is null )
		{
			GiveTrashItem( guest );
			return;
		}

		var go = TrashPilePrefab?.Clone( trace.HitPosition );
		var randomYaw = Rotation.FromAxis( trace.Normal, Game.Random.Float( 0f, 360f ) );
		go.WorldRotation = Rotation.LookAt( randomYaw.Forward, trace.Normal );
		go.NetworkSpawn();
	}

	void GiveTrashItem( Guest guest )
	{
		if ( TrashItemPrefab is null )
			return;

		guest.Inventory.Add( TrashItemPrefab );
	}
}