Npcs/Diner/DinerInspectFoodSchedule.cs
using Sandbox.Npcs.Tasks;

namespace Sandbox.Npcs.Schedules;

/// <summary>
/// Walk over to a nearby food item (anything tagged with DinerNpc.FoodTag — served by the
/// emitters, or a stray peach), eye it up, react, and if it's small enough pick it up, carry
/// it off a short way, and set it back down.
///
/// Adapted from ScientistInspectPropSchedule, with two deliberate differences:
///   • Targets a specific food GameObject handed in by DinerNpc (tag-filtered), so customers
///     never grab kart wheels or other props — only actual food.
///   • Diner-flavoured reactions, with separate lines for "real" food vs a rogue peach.
///
/// The food target is chosen by DinerNpc.FindNearbyFood; this schedule just acts on it.
/// </summary>
public class DinerInspectFoodSchedule : ScheduleBase
{
	private static readonly string[] InspectLines =
	[
		"Ooh, is this mine?",
		"That smells incredible.",
		"Finally, some service!",
		"Now THAT'S a breakfast.",
		"Don't mind if I do.",
		"Is this the special?",
	];

	private static readonly string[] PickUpLines =
	[
		"I'll have this, thank you.",
		"Mine, all mine.",
		"Come to papa.",
		"Saving this for later.",
	];

	private static readonly string[] PeachLines =
	[
		"...why is there a peach?",
		"Who's throwing fruit?!",
		"A peach? In MY diner?",
		"This isn't on the menu.",
	];

	private static readonly string[] DropLines =
	[
		"Lovely.",
		"There we are.",
		"Best meal all week.",
		"Compliments to the chef.",
	];

	public GameObject FoodTarget { get; set; }

	/// <summary>Set by DinerNpc so we can theme the reaction (a peach gets a different line).</summary>
	public bool IsPeach { get; set; }

	protected override void OnStart()
	{
		if ( !FoodTarget.IsValid() ) return;

		var speech = Npc.Speech;

		// Keep eyes on the food the whole time.
		Npc.Animation.SetLookTarget( FoodTarget );

		// Approach — MoveTo tracks the object if it rolls/gets knocked around.
		AddTask( new MoveTo( FoodTarget, 40f ) );
		AddTask( new Wait( Game.Random.Float( 0.5f, 1f ) ) );

		// React on arrival.
		if ( speech is not null && speech.CanSpeak )
		{
			var lines = IsPeach ? PeachLines : InspectLines;
			AddTask( new Say( lines[Game.Random.Int( 0, lines.Length - 1 )], 2.5f ) );
		}

		// Only pick up genuinely small items so we don't try to hoist a whole turkey.
		var bounds = FoodTarget.GetBounds();
		var isSmall = bounds.Size.Length < 96f;

		if ( isSmall )
		{
			if ( speech is not null && speech.CanSpeak && !IsPeach )
				AddTask( new Say( PickUpLines[Game.Random.Int( 0, PickUpLines.Length - 1 )], 2f ) );

			AddTask( new PickUpProp( FoodTarget ) );

			// Carry it off toward home so food drifts back to the tables, not across the map.
			var diner = Npc as Sandbox.Npcs.Diner.DinerNpc;
			var home  = diner?.HomeArea ?? Npc.WorldPosition;
			var dir   = Vector3.Random.WithZ( 0 ).Normal;
			var carryTarget = home + dir * Game.Random.Float( 60f, 160f );

			if ( Npc.Scene.NavMesh.GetClosestPoint( carryTarget ) is { } navPoint )
				AddTask( new MoveTo( navPoint, 15f ) );

			AddTask( new Wait( Game.Random.Float( 2f, 5f ) ) );
			AddTask( new DropProp( FoodTarget ) );

			if ( speech is not null && speech.CanSpeak )
				AddTask( new Say( DropLines[Game.Random.Int( 0, DropLines.Length - 1 )], 2f ) );
		}
		else
		{
			// Too big to lift — just admire it for a moment.
			AddTask( new Wait( Game.Random.Float( 2f, 4f ) ) );
		}
	}

	protected override void OnEnd()
	{
		Npc.Animation.ClearLookTarget();
		Npc.Animation.ClearHeldProp();
	}
}