NPCs/AgentAnimator.cs
using Sandbox;
using System.Text.Json.Serialization;

namespace Opium.AI;

public abstract class AgentAnimator : Component
{
	/// <summary>
	/// The agent in question
	/// </summary>
	[Property] public Agent Agent { get; set; }

	/// <summary>
	/// Are we in a full body sequence? (Turn off upper body / lower body mask )
	/// </summary>
	[Property, ReadOnly, JsonIgnore] public bool IsFullBody { get; set; }
	[Property, ReadOnly, JsonIgnore] public UpperBodyState UpperBodyState { get; set; }
	[Property, ReadOnly, JsonIgnore] public LowerBodyState LowerBodyState { get; set; }
	[Property, ReadOnly, JsonIgnore] public FullBodyState FullBodyState { get; set; }

	Vector2 lerpedMove = 0;

	protected override void OnUpdate()
	{
		var stateMachine = Agent?.StateMachine;

		Agent?.Model?.Set( "upper_state", (int)UpperBodyState );
		Agent?.Model?.Set( "lower_state", (int)LowerBodyState );

		Agent?.Model?.Set( "mask_fullbody", IsFullBody );
		// Only active when mask_fullbody = 1
		Agent?.Model?.Set( "full_body_state", (int)FullBodyState );

		lerpedMove = lerpedMove.LerpTo( Agent.WishMove, Time.Delta * 4f );
		Agent?.Model?.Set( "move_x", lerpedMove.y );
		Agent?.Model?.Set( "move_y", lerpedMove.x );
	}
}

public enum UpperBodyState : int
{
	Default = 0, // Idle
	Charge = 1, // Holding a weapon at an enemy
	Attack = 2, // Attacking right now,
	Block = 3, // Blocking an attack
}

public enum LowerBodyState : int
{
	Default = 0, // Traversal
	KickingDoor = 1,
}

public enum FullBodyState : int
{
	Idle = 0,
	AttackStun = 1,
	BlockStun = 2
}