AI/Agents/Agent.Animation.cs
namespace HC3;

public partial class Agent
{
	/// <summary>
	/// The skinned model renderer for this agent.
	/// </summary>
	[Property, Feature( "Animation" )] public SkinnedModelRenderer Body { get; set; }

	/// <summary>
	/// Synced property to determine if the agent is moving.
	/// </summary>
	[Sync( SyncFlags.FromHost )] public bool IsMoving { get; set; }

	/// <summary>
	/// The run sequence to use
	/// </summary>
	[Property, Feature( "Animation" )] public string RunSequence { get; set; } = "Run_01";

	/// <summary>
	/// The walk sequence to use
	/// </summary>
	[Property, Feature( "Animation" )] public string WalkSequence { get; set; } = "Walk_01";

	/// <summary>
	/// Scale move speed by this factor to avoid skating
	/// </summary>
	[Property, Feature( "Animation" )] public float SkatingMultiplier { get; set; } = 1.5f;

	/// <summary>
	/// A list of idle sequences to use
	/// </summary>
	[Property, Feature( "Animation" )]
	public List<string> IdleSequences { get; set; } = new()
	{
		"IdlePose_01",
		"IdlePose_02",
		"IdlePose_03",
		"IdlePose_04",
		"IdlePose_05"
	};

	protected TimeSince _timeSinceIdle = 999;
	protected string _sequenceOverride;
	protected float _sequenceOverrideSpeed = 1f;

	/// <summary>
	///	Sets the sequence for the agent.
	/// </summary>
	/// <param name="sequence"></param>
	/// <param name="speed"></param>
	protected void SetSequence( string sequence, float speed = 1f )
	{
		Body.Sequence.PlaybackRate = speed;

		// Don't reset if we're already running this
		if ( Body.Sequence?.Name?.Equals( sequence ) ?? false )
			return;

		Body.Sequence.Name = sequence;
	}

	/// <summary>
	/// Sets the sequence override for the agent. This means the agent will always play this sequence, on a loop. Remember to unset.
	/// </summary>
	/// <param name="sequence"></param>
	/// <param name="speed"></param>
	public void SetSequenceOverride( string sequence, float speed = 1f )
	{
		_sequenceOverride = sequence;
		_sequenceOverrideSpeed = speed;
	}

	protected void UpdateIdle()
	{
		if ( _timeSinceIdle > 5 )
		{
			SetSequence( Game.Random.FromList( IdleSequences ) );
			_timeSinceIdle = 0;
		}
	}

	public virtual float WalkSpeed
	{
		get
		{
			// 0.15f because the model is scaled incorrectly
			var scale = Body.WorldScale.z / 0.15f;

			if ( scale >= 0.8f )
				return 1f;

			var t = scale.LerpInverse( 0.8f, 0.5f, true );
			var val = 1f.LerpTo( 2f, t );

			return val;
		}
	}

	protected void UpdateAnimation()
	{
		if ( !Game.IsPlaying )
			return;

		if ( Networking.IsHost )
			IsMoving = Controller.IsNavigating;

		if ( !string.IsNullOrEmpty( _sequenceOverride ) )
		{
			SetSequence( _sequenceOverride, _sequenceOverrideSpeed );
			return;
		}

		if ( !Controller.IsGrounded )
		{
			SetSequence( "Flail" );
		}
		else if ( IsMoving )
		{
			SetSequence( Controller.IsRunning ? RunSequence : WalkSequence, WalkSpeed * SkatingMultiplier );
		}
		else
		{
			UpdateIdle();
		}
	}
}