core/npc/NpcAnimator.cs
public class NpcAnimator : Component {
	[Property, ReadOnly] public IsoEntity Owner {get; set;}

	public IsoActivitySet ActivitySet => Owner.ActivitySet;
	private SkinnedModelRenderer Renderer => Owner.Renderer;
	[Property] public string Weapon {get; set;} = "unarmed";
	[Property] public string Activity {get; set;}
	public Action AttackAction {get; set;}

	protected override void OnAwake() {
		Tags.Add("dynamic");
		Tags.Add("entity");
		if (ActivitySet.IsValid() && string.IsNullOrEmpty(Activity))
			Activity = ActivitySet.Default;
		base.OnAwake();
	}

	protected override void OnFixedUpdate() {
		UpdateAnimation();
		base.OnFixedUpdate();
	}

	[Button] public void ResetToIdle() {
		AnimationState = AnimatorState.Idle;
		Renderer.Sequence.TimeNormalized = 1f;
		UpdateAnimation();
	}

	private enum AnimatorState {
		Enter,
		WaitForEnter,
		Idle,
		Exit
	}
	[Property, Category("anm"), ReadOnly] private AnimatorState AnimationState {get; set;}
	[Property, Category("anm"), ReadOnly] private string CurrentActivity;
	[Property, ReadOnly] public List<Component> Attached {get; set;} = new();
	[Property, ReadOnly] public bool PlayedAttackEvent {get; set;}
	public void UpdateAnimation() {
		void InternalUpdate() {
			if (!ActivitySet.IsValid())
				return;
			Renderer.UseAnimGraph = false;
			Renderer.Sequence.Looping = false;
			var lastactivity = CurrentActivity;
			if (!string.IsNullOrEmpty(Activity) && ActivitySet.WeaponActivityMap.TryGetValue(Weapon, out var map)) {
				if (map.Map.TryGetValue(Activity, out var newactivity))
					Activity = newactivity;
			}
			if (ActivitySet.Activities.ContainsKey(Activity))
				CurrentActivity = Activity;
			if (string.IsNullOrEmpty(CurrentActivity))
				CurrentActivity = ActivitySet.Default;
			if (string.IsNullOrEmpty(CurrentActivity))
				return;
			if (lastactivity != CurrentActivity) {
				if (lastactivity == "die")
					return;
				if (AnimationState == AnimatorState.WaitForEnter || AnimationState == AnimatorState.Idle)
					AnimationState = AnimatorState.Exit;
				else if(!string.IsNullOrEmpty(lastactivity))
					CurrentActivity = lastactivity;
			}

			if (AnimationState == AnimatorState.Enter) { //entry
				if (Renderer.Sequence.Duration > 0 && !Renderer.Sequence.IsFinished)
					return;
				if (!string.IsNullOrEmpty(ActivitySet.Activities[CurrentActivity].Entry))
					Renderer.Sequence.Name = ActivitySet.Activities[CurrentActivity].Entry;
				AnimationState = AnimatorState.WaitForEnter;
			}
			if (AnimationState == AnimatorState.WaitForEnter || AnimationState == AnimatorState.Idle) { //idle
				if (Renderer.Sequence.Duration > 0 && !Renderer.Sequence.IsFinished)
					return;
				AnimationState = AnimatorState.Idle;
				var act = ActivitySet.Activities[CurrentActivity].Idle;
				if ((string.IsNullOrEmpty(act) || Random.Shared.Int(3) == 1) && ActivitySet.Activities[CurrentActivity].Fidget.Count > 0)
					act = Random.Shared.FromList(ActivitySet.Activities[CurrentActivity].Fidget);
				if (!string.IsNullOrEmpty(act))
					Renderer.Sequence.Name = act;
				else
					AnimationState = AnimatorState.Exit;
			}
			if (AnimationState == AnimatorState.Exit) { //exit
				if (lastactivity == CurrentActivity)
					AnimationState = AnimatorState.Enter;
				if (string.IsNullOrEmpty(lastactivity))
					AnimationState = AnimatorState.Enter;
				else if (!string.IsNullOrEmpty(ActivitySet.Activities[lastactivity].Exit))
					Renderer.Sequence.Name = ActivitySet.Activities[lastactivity].Exit;
				else
					Renderer.Sequence.TimeNormalized = 1;
				if (!string.IsNullOrEmpty(lastactivity) && !string.IsNullOrEmpty(ActivitySet.Activities[lastactivity].ReturnToActivity))
					Activity = ActivitySet.Activities[lastactivity].ReturnToActivity;
				AnimationState = AnimatorState.Enter;
			}
		}
		var startcycle = Renderer.Sequence.TimeNormalized;
		var startsequence = Renderer.Sequence.Name;
		var startact = CurrentActivity;
		InternalUpdate();
		if (startcycle <= Renderer.Sequence.TimeNormalized) {
			if (string.IsNullOrEmpty(startact))
				return;
			if (startact != CurrentActivity)
				return;
			if (ActivitySet.DeathActivies.Contains(CurrentActivity)) {
				Components.Get<NpcMovement>()?.Destroy();
				Components.Get<NpcTalker>()?.Destroy();
				Components.Get<BoxCollider>()?.Destroy();
				var blood = Owner.Renderer.GetAttachment("blood");
				if (blood.HasValue) {
					var pool = Scene.CreateObject().Components.Create<BloodPool>();
					pool.GameObject.WorldTransform = blood.Value;
					pool.GameObject.Name = "BLOOD_POOL";
				}
				Owner.Destroy();
				Renderer.Sequence.Name = ActivitySet.Activities[CurrentActivity].Entry;
				Destroy();
				return;
			}
			if (!PlayedAttackEvent && ActivitySet.AttackEvents.TryGetValue(startact, out var time) && time <= Renderer.Sequence.Time) {
				AttackAction?.Invoke();
				PlayedAttackEvent = true;
			}
			return;
		}
		PlayedAttackEvent = false;
		foreach (var attached in Attached)
			attached.Destroy();
		Attached.Clear();
		if (ActivitySet.ActivityAttachments.TryGetValue(CurrentActivity, out var attachments)) {
			Renderer.CreateAttachments = true;
			foreach (var model in attachments.Models) {
				var smr = Renderer.GetAttachmentObject(model.Key).Components.Create<SkinnedModelRenderer>();
				smr.Model = model.Value;
				Attached.Add(smr);
			}
		}
		if (ActivitySet.SequenceRootMotion.TryGetValue(startsequence, out var motion)) {
			if (startcycle > 0.9f)
				WorldPosition += motion.RotateAround(Vector3.Zero, WorldRotation);
			else
				WorldPosition += motion.RotateAround(Vector3.Zero, WorldRotation) * startcycle;
			var m = Components.Get<NpcMovement>();
			if (m.IsValid())
				m.EntityRoot = WorldPosition;
			GameObject.Transform.ClearInterpolation();
		}
	}
}

[GameResource("Activity Set", "anm", "", Icon = "sports_martial_arts")]
public class IsoActivitySet : GameResource {
	public struct Activity {
		public string Entry {get; set;}
		public string Idle {get; set;}
		public List<string> Fidget {get; set;}
		public string Exit {get; set;}
		public string ReturnToActivity {get; set;}
	}
	[WideMode, InlineEditor] public Dictionary<string, Activity> Activities {get; set;} = new();
	public string Default {get; set;}
	public struct WeaponActivities {
		public Dictionary<string, string> Map {get; set;}
	}
	[WideMode, InlineEditor] public Dictionary<string, WeaponActivities> WeaponActivityMap {get; set;} = new();
	public struct Attachments {
		[WideMode] public Dictionary<string, Model> Models {get; set;}
	}
	[WideMode, InlineEditor] public Dictionary<string, Attachments> ActivityAttachments {get; set;} = new();
	[WideMode] public Dictionary<string, Vector3> SequenceRootMotion {get; set;} = new();
	public List<string> NonInteruptActivities {get; set;} = new();
	public List<string> DeathActivies {get; set;} = new();
	[WideMode] public Dictionary<string, float> AttackEvents {get; set;} = new();
}