using System; using System.Collections.Generic; using System.Linq; using UnityEditor.Animations; using UnityEngine; using VF.Builder.Exceptions; using VRC.SDK3.Avatars.Components; using VRC.SDKBase; namespace VF.Builder { public class VFAController { private readonly AnimatorController ctrl; private readonly VRCAvatarDescriptor.AnimLayerType type; public VFAController(AnimatorController ctrl, VRCAvatarDescriptor.AnimLayerType type) { this.ctrl = ctrl; this.type = type; } public VFALayer NewLayer(string name, int insertAt = -1) { ctrl.AddLayer(name); var layers = ctrl.layers; var layer = layers.Last(); if (insertAt >= 0) { for (var i = layers.Length-1; i > insertAt; i--) { layers[i] = layers[i - 1]; } layers[insertAt] = layer; } layer.defaultWeight = 1; layer.stateMachine.anyStatePosition = VFAState.MovePos(layer.stateMachine.entryPosition, 0, 1); ctrl.layers = layers; return new VFALayer(layer.stateMachine, this); } public void RemoveLayer(int i) { ctrl.RemoveLayer(i); } public VFABool NewTrigger(string name) { return new VFABool(NewParam(name, AnimatorControllerParameterType.Trigger)); } public VFABool NewBool(string name, bool def = false) { return new VFABool(NewParam(name, AnimatorControllerParameterType.Bool, param => param.defaultBool = def)); } public VFANumber NewFloat(string name, float def = 0) { return new VFANumber(NewParam(name, AnimatorControllerParameterType.Float, param => param.defaultFloat = def)); } public VFANumber NewInt(string name, int def = 0) { return new VFANumber(NewParam(name, AnimatorControllerParameterType.Int, param => param.defaultInt = def)); } private AnimatorControllerParameter NewParam(string name, AnimatorControllerParameterType type, Action with = null) { var exists = Array.Find(ctrl.parameters, other => other.name == name); if (exists != null) return exists; ctrl.AddParameter(name, type); var parameters = ctrl.parameters; var param = parameters[parameters.Length-1]; if (with != null) with(param); ctrl.parameters = parameters; return param; } } public class VFALayer { private readonly AnimatorStateMachine stateMachine; private readonly VFAController ctrl; public VFALayer(AnimatorStateMachine stateMachine, VFAController ctrl) { this.stateMachine = stateMachine; this.ctrl = ctrl; } public VFAState NewState(string name) { var lastNode = GetLastNodeForPositioning(); stateMachine.AddState(name); var node = GetLastNode().Value; node.state.writeDefaultValues = true; var state = new VFAState(node, stateMachine); if (lastNode.HasValue) state.Move(lastNode.Value.position, 0, 1); else state.Move(stateMachine.entryPosition, 1, 0); return state; } private ChildAnimatorState? GetLastNodeForPositioning() { var states = stateMachine.states; var index = Array.FindLastIndex(states, state => !state.state.name.StartsWith("_")); if (index < 0) return null; return states[index]; } private ChildAnimatorState? GetLastNode() { var states = stateMachine.states; if (states.Length == 0) return null; return states[states.Length-1]; } public AnimatorStateMachine GetRawStateMachine() { return stateMachine; } } public class VFAState { private ChildAnimatorState node; private readonly AnimatorStateMachine stateMachine; private static readonly float X_OFFSET = 250; private static readonly float Y_OFFSET = 80; public VFAState(ChildAnimatorState node, AnimatorStateMachine stateMachine) { this.node = node; this.stateMachine = stateMachine; } public static Vector3 MovePos(Vector3 orig, float x, float y) { var pos = orig; pos.x += x * X_OFFSET; pos.y += y * Y_OFFSET; return pos; } public VFAState Move(Vector3 orig, float x, float y) { node.position = MovePos(orig, x, y); var states = stateMachine.states; var index = Array.FindIndex(states, n => n.state == node.state); if (index >= 0) { states[index] = node; stateMachine.states = states; } return this; } public VFAState Move(VFAState other, float x, float y) { Move(other.node.position, x, y); return this; } public VFAState Move(float x, float y) { Move(this, x, y); return this; } public VFAState WithAnimation(Motion motion) { node.state.motion = motion; return this; } public VFAState MotionTime(VFANumber param) { node.state.timeParameterActive = true; node.state.timeParameter = param.Name(); return this; } public VFAState Speed(float speed) { node.state.speed = speed; return this; } public VRCAvatarParameterDriver GetDriver(bool local = false) { foreach (var b in node.state.behaviours) { var d = b as VRCAvatarParameterDriver; if (d && d.localOnly == local) return d; } var driver = VRCFAnimatorUtils.AddStateMachineBehaviour(node.state); driver.localOnly = local; return driver; } private VRC_AvatarParameterDriver.Parameter Drives(string param, bool local = false) { var driver = GetDriver(local); var p = new VRC_AvatarParameterDriver.Parameter(); p.name = param; p.type = VRC_AvatarParameterDriver.ChangeType.Set; driver.parameters.Add(p); return p; } public VFAState Drives(VFABool param, bool value, bool local = false) { Drives(param.Name(), local).value = value ? 1 : 0; return this; } public VFAState Drives(VFANumber param, float value, bool local = false) { Drives(param.Name(), local).value = value; return this; } public VFAState DrivesRandom(VFANumber param, float min, float max) { var p = Drives(param.Name(), true); p.type = VRC_AvatarParameterDriver.ChangeType.Random; p.valueMin = min; p.valueMax = max; return this; } public VFAState DrivesDelta(VFANumber param, float delta) { var p = Drives(param.Name(), true); p.type = VRC_AvatarParameterDriver.ChangeType.Add; p.value = delta; return this; } public VFAState DrivesCopy(VFANumber param, VFANumber source) { var driver = GetDriver(true); var p = new VRC_AvatarParameterDriver.Parameter(); p.name = param.Name(); var sourceField = p.GetType().GetField("source"); if (sourceField == null) throw new VRCFBuilderException("VRCFury feature failed to build because VRCSDK is outdated"); sourceField.SetValue(p, source.Name()); // We cast rather than use Copy directly so it doesn't fail to compile on old VRCSDK p.type = (VRC_AvatarParameterDriver.ChangeType)3; //VRC_AvatarParameterDriver.ChangeType.Copy; driver.parameters.Add(p); return this; } public VFAEntryTransition TransitionsFromEntry() { return new VFAEntryTransition(() => stateMachine.AddEntryTransition(node.state)); } public VFATransition TransitionsFromAny() { return new VFATransition(() => stateMachine.AddAnyStateTransition(node.state)); } public VFATransition TransitionsTo(VFAState other) { return new VFATransition(() => node.state.AddTransition(other.node.state)); } public VFATransition TransitionsToExit() { return new VFATransition(() => node.state.AddExitTransition()); } public AnimatorState GetRaw() { return node.state; } public static void FakeAnyState(params (VFAState,VFACondition)[] states) { VFACondition above = null; foreach (var (state, when) in states) { VFACondition myWhen; if (state == states[states.Length - 1].Item1) { if (above == null) throw new VRCFBuilderException("Cannot use FakeAnyState with 1 state."); myWhen = above.Not(); } else if (above == null) { above = myWhen = when; } else { myWhen = when.And(above.Not()); above = above.Or(when); } foreach (var (other,_) in states) { if (other == state) continue; other.TransitionsTo(state).When(myWhen); } } } } public class VFAParam { private readonly AnimatorControllerParameter param; public VFAParam(AnimatorControllerParameter param) { this.param = param; } public string Name() { return param.name; } } public class VFABool : VFAParam { public VFABool(AnimatorControllerParameter param) : base(param) {} public VFACondition IsTrue() { return new VFACondition(new AnimatorCondition { mode = AnimatorConditionMode.If, parameter = Name(), threshold = 0 }); } public VFACondition IsFalse() { return new VFACondition(new AnimatorCondition { mode = AnimatorConditionMode.IfNot, parameter = Name(), threshold = 0 }); } } public class VFANumber : VFAParam { public VFANumber(AnimatorControllerParameter param) : base(param) {} public VFACondition IsGreaterThan(float num) { return new VFACondition(new AnimatorCondition { mode = AnimatorConditionMode.Greater, parameter = Name(), threshold = num }); } public VFACondition IsLessThan(float num) { return new VFACondition(new AnimatorCondition { mode = AnimatorConditionMode.Less, parameter = Name(), threshold = num }); } public VFACondition IsEqualTo(float num) { return new VFACondition(new AnimatorCondition { mode = AnimatorConditionMode.Equals, parameter = Name(), threshold = num }); } public VFACondition IsNotEqualTo(float num) { return new VFACondition(new AnimatorCondition { mode = AnimatorConditionMode.NotEqual, parameter = Name(), threshold = num }); } } public class VFACondition { internal IEnumerable> transitions; public VFACondition(AnimatorCondition cond) { var transition = new List { cond }; transitions = new List> { transition }; } public VFACondition(IEnumerable> transitions) { this.transitions = transitions; } public VFACondition And(VFACondition other) { return new VFACondition(AnimatorConditionLogic.And(transitions, other.transitions)); } public VFACondition Or(VFACondition other) { return new VFACondition(AnimatorConditionLogic.Or(transitions, other.transitions)); } public VFACondition Not() { return new VFACondition(AnimatorConditionLogic.Not(transitions)); } } public class VFAEntryTransition { private readonly Func transitionProvider; public VFAEntryTransition(Func transitionProvider) { this.transitionProvider = transitionProvider; } public VFAEntryTransition When(VFACondition cond) { foreach (var t in cond.transitions) { var transition = transitionProvider(); transition.conditions = t.ToArray(); } return this; } } public class VFATransition { private readonly List createdTransitions = new List(); private Func transitionProvider; public VFATransition(Func transitionProvider) { this.transitionProvider = () => { var trans = transitionProvider(); trans.duration = 0; trans.canTransitionToSelf = false; createdTransitions.Add(trans); return trans; }; } public VFATransition When() { var transition = transitionProvider(); return this; } public VFATransition When(VFACondition cond) { foreach (var t in cond.transitions) { var transition = transitionProvider(); transition.conditions = t.ToArray(); } return this; } public VFATransition WithTransitionToSelf() { foreach (var t in createdTransitions) { t.canTransitionToSelf = true; } var oldProvider = transitionProvider; transitionProvider = () => { var trans = oldProvider(); trans.canTransitionToSelf = true; return trans; }; return this; } public VFATransition WithTransitionDurationSeconds(float time) { foreach (var t in createdTransitions) { t.duration = time; } var oldProvider = transitionProvider; transitionProvider = () => { var trans = oldProvider(); trans.duration = time; return trans; }; return this; } public VFATransition WithTransitionExitTime(float time) { foreach (var t in createdTransitions) { t.hasExitTime = true; t.exitTime = time; } var oldProvider = transitionProvider; transitionProvider = () => { var trans = oldProvider(); trans.hasExitTime = true; trans.exitTime = time; return trans; }; return this; } } }