Conveyance/PatrolMover.cs
using Sandbox;
using System.Collections.Generic;

/// <summary>
/// Moves the GameObject between a sequence of waypoint GameObjects.
/// Designed to sit alongside (not inside) DragonTurret — the mover handles where it is,
/// the turret handles where it's looking and what it's shooting.
/// </summary>
public sealed class PatrolMover : Component
{
	/// <summary>
	/// Ordered list of waypoint GameObjects to visit. Only the WorldPosition is used —
	/// the waypoints can be empty GameObjects placed in the scene for visual reference.
	/// Need at least 2 waypoints for anything to happen.
	/// </summary>
	[Property] public List<GameObject> Waypoints { get; set; } = new();

	/// <summary>
	/// How fast the mover travels between waypoints, in units/second.
	/// </summary>
	[Property, Range( 50, 1000 )] public float Speed { get; set; } = 200f;

	/// <summary>
	/// How close the mover needs to get to a waypoint before switching to the next.
	/// Too small and floating-point error means it might never "arrive"; too large and
	/// it cuts corners.
	/// </summary>
	[Property, Range( 1, 100 )] public float ArrivalRadius { get; set; } = 20f;

	/// <summary>
	/// Patrol behaviour at the end of the waypoint list.
	/// PingPong: A→B→A→B (reverses direction at each end).
	/// Loop: A→B→C→A→B→C (jumps back to start after last).
	/// </summary>
	[Property] public PatrolMode Mode { get; set; } = PatrolMode.PingPong;

	/// <summary>
	/// If true, the GameObject smoothly rotates to face its direction of travel.
	/// Disable this if something else is controlling rotation (e.g. a turret aiming at the player —
	/// but in our case the turret only rotates the Gun child, not the root, so leaving this on is fine).
	/// </summary>
	[Property] public bool FaceDirectionOfTravel { get; set; } = true;

	/// <summary>
	/// Degrees per second the body rotates to face travel direction.
	/// </summary>
	[Property, Range( 30, 720 )] public float TurnRateDegPerSec { get; set; } = 90f;

	public enum PatrolMode
	{
		PingPong,
		Loop,
	}

	// === Runtime ===

	private int currentWaypointIndex = 0;
	private int direction = 1; // +1 forward through list, -1 backwards (only used in PingPong)

	protected override void OnUpdate()
	{
		if ( Waypoints == null || Waypoints.Count < 2 )
			return;

		var target = Waypoints[currentWaypointIndex];
		if ( !target.IsValid() )
			return;

		var toTarget = target.WorldPosition - WorldPosition;
		var distance = toTarget.Length;

		// Arrived at this waypoint — pick the next one.
		if ( distance < ArrivalRadius )
		{
			AdvanceWaypoint();
			return;
		}

		// Move toward the waypoint at the configured speed, capped at the remaining distance
		// so we don't overshoot in a single frame on a slow machine.
		var moveDir = toTarget.Normal;
		var moveStep = Speed * Time.Delta;
		if ( moveStep > distance ) moveStep = distance;

		WorldPosition += moveDir * moveStep;

		// Optionally rotate the body to face direction of travel.
		if ( FaceDirectionOfTravel )
		{
			var desiredRotation = Rotation.LookAt( moveDir );
			var maxStepDeg = TurnRateDegPerSec * Time.Delta;
			WorldRotation = WorldRotation.Clamp( desiredRotation, maxStepDeg );
		}
	}

	/// <summary>
	/// Choose the next waypoint index according to the patrol mode.
	/// </summary>
	private void AdvanceWaypoint()
	{
		switch ( Mode )
		{
			case PatrolMode.Loop:
				currentWaypointIndex = (currentWaypointIndex + 1) % Waypoints.Count;
				break;

			case PatrolMode.PingPong:
				currentWaypointIndex += direction;
				// Bounce off either end of the list.
				if ( currentWaypointIndex >= Waypoints.Count )
				{
					currentWaypointIndex = Waypoints.Count - 2;
					direction = -1;
				}
				else if ( currentWaypointIndex < 0 )
				{
					currentWaypointIndex = 1;
					direction = 1;
				}
				break;
		}
	}
}