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

namespace Sandbox.Npcs.Schedules;

/// <summary>
/// Relaxed ambient stroll around the café. This is the calm cousin of DinerMillSchedule:
/// where milling is the agitated "there's no free seat!" loiter, wandering is a content
/// customer stretching their legs between sittings, so the crowd feels alive.
///
/// Ported from ScientistWanderSchedule, with two diner-appropriate changes:
///   • Anchored to HomeArea (not current position) so customers stay around the diner
///     instead of drifting across the map.
///   • Diner-flavoured lines.
///
/// Completes quickly (move → maybe say → short wait) so GetSchedule re-evaluates often,
/// exactly like the scientist idle loop. Relies on DinerNpc.Scare() → EndCurrentSchedule()
/// to interrupt mid-stroll if a threat appears, matching DinerMillSchedule.
/// </summary>
public class DinerWanderSchedule : ScheduleBase
{
	private static readonly string[] WanderLines =
	[
		"Lovely spot for a stroll.",
		"Think I'll stretch my legs.",
		"I'll just take the air.",
		"*hums contentedly*",
		"Wonder if the special's still on.",
		"Back in a moment.",
		"Beautiful morning for it.",
		"Might pop to the counter.",
	];

	protected override void OnStart()
	{
		var diner        = Npc as Sandbox.Npcs.Diner.DinerNpc;
		var home         = diner?.HomeArea ?? Npc.WorldPosition;
		var wanderRadius = diner?.MillRadius ?? 300f;

		var randomDir = Vector3.Random.WithZ( 0 ).Normal;

		// Don't stroll straight at the nearest player — sidestep away with some spread.
		var nearest = Npc.Senses.Nearest;
		if ( nearest.IsValid() )
		{
			var toPlayer = (nearest.WorldPosition - Npc.WorldPosition).WithZ( 0 ).Normal;
			if ( randomDir.Dot( toPlayer ) > 0.3f )
				randomDir = (-toPlayer + Vector3.Random.WithZ( 0 ).Normal * 0.5f).WithZ( 0 ).Normal;
		}

		// Wider spread than milling (0.3–1.0 × radius) so wandering reads as real movement.
		var target = home + randomDir * Game.Random.Float( wanderRadius * 0.3f, wanderRadius );

		if ( Npc.Scene.NavMesh.GetClosestPoint( target ) is { } navPoint )
			AddTask( new MoveTo( navPoint, 20f ) );
		else
			AddTask( new MoveTo( target, 20f ) );

		if ( Npc.Speech.CanSpeak && Game.Random.Float() < 0.25f )
		{
			var line = WanderLines[Game.Random.Int( 0, WanderLines.Length - 1 )];
			AddTask( new Say( line, 2.5f ) );
		}

		AddTask( new Wait( Game.Random.Float( 1f, 2.5f ) ) );
	}
}