using System; using VRC.SDK3.Avatars.Components; using VRC.SDKBase; namespace AnimatorAsCode.V0.Extensions.VRChat { public static class VRChatExtensions { /// /// Set parameter to a given value. For unsynced parameters, also see DrivingLocally. /// public static TNode Drives(this TNode node, AacFlParameter parameter, TParam value) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.parameters.Add(new VRC_AvatarParameterDriver.Parameter { type = VRC_AvatarParameterDriver.ChangeType.Set, name = parameter.Name, value = parameter.ValueToFloat(value) }); return node; } /// /// Set parameter by increasing its current value by additiveValue. /// public static TNode DrivingIncreases(this TNode node, AacFlNumericParameter parameter, TParam additiveValue) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.parameters.Add(new VRC_AvatarParameterDriver.Parameter { type = VRC_AvatarParameterDriver.ChangeType.Add, name = parameter.Name, value = parameter.ValueToFloat(additiveValue) }); return node; } /// /// Set parameter by decreasing its current value by positiveValueToDecreaseBy. /// public static TNode DrivingDecreases(this TNode node, AacFlNumericParameter parameter, TParam positiveValueToDecreaseBy) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.parameters.Add(new VRC_AvatarParameterDriver.Parameter { type = VRC_AvatarParameterDriver.ChangeType.Add, name = parameter.Name, value = parameter.ValueToFloat(positiveValueToDecreaseBy) * -1 }); return node; } /// /// Copies sourceParameter to destParameter with the given custom ranges. /// https://docs.vrchat.com/docs/state-behaviors#copy /// public static TNode DrivingRemaps(this TNode node, AacFlParameter sourceParameter, TSource sourceMin, TSource sourceMax, AacFlParameter destParameter, TDest destMin, TDest destMax) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.EnsureParameter(new VRC_AvatarParameterDriver.Parameter { name = destParameter.Name, type = VRC_AvatarParameterDriver.ChangeType.Copy, source = sourceParameter.Name, convertRange = true, sourceMin = sourceParameter.ValueToFloat(sourceMin), sourceMax = sourceParameter.ValueToFloat(sourceMax), destMin = destParameter.ValueToFloat(destMin), destMax = destParameter.ValueToFloat(destMax) }); return node; } /// /// Copies sourceParameter to destParameter with no custom ranges. /// https://docs.vrchat.com/docs/state-behaviors#copy /// public static TNode DrivingCopies(this TNode node, AacFlParameter sourceParameter, AacFlParameter destParameter) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.EnsureParameter(new VRC_AvatarParameterDriver.Parameter { name = destParameter.Name, type = VRC_AvatarParameterDriver.ChangeType.Copy, source = sourceParameter.Name, }); return node; } /// /// Sets parameter to a random value between min and max. /// This is only ran for the person wearing the avatar, and usually needs to be backed by a synced variable. /// https://docs.vrchat.com/docs/state-behaviors#random /// public static TNode DrivingRandomizesLocally(this TNode node, AacFlNumericParameter parameter, TParam min, TParam max) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.parameters.Add(new VRC_AvatarParameterDriver.Parameter { type = VRC_AvatarParameterDriver.ChangeType.Random, name = parameter.Name, valueMin = parameter.ValueToFloat(min), valueMax = parameter.ValueToFloat(max) }); driver.localOnly = true; return node; } /// /// Sets parameter to either true or false, with the given chance. /// This is only ran for the person wearing the avatar, and usually needs to be backed by a synced variable. /// https://docs.vrchat.com/docs/state-behaviors#random /// public static TNode DrivingRandomizesLocally(this TNode node, AacFlBoolParameter parameter, float chance) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.parameters.Add(new VRC_AvatarParameterDriver.Parameter { type = VRC_AvatarParameterDriver.ChangeType.Random, name = parameter.Name, chance = chance }); driver.localOnly = true; return node; } /// /// Sets parameter to a random value between min and max. /// For use with unsynced parameters. Warning: The resulting value will be different for everyone seeing your avatar. /// https://docs.vrchat.com/docs/state-behaviors#random /// public static TNode DrivingRandomizesUnsynced(this TNode node, AacFlNumericParameter parameter, TParam min, TParam max) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.parameters.Add(new VRC_AvatarParameterDriver.Parameter { type = VRC_AvatarParameterDriver.ChangeType.Random, name = parameter.Name, valueMin = parameter.ValueToFloat(min), valueMax = parameter.ValueToFloat(max) }); return node; } /// /// Sets parameter to either true or false, with the given chance. /// For use with unsynced parameters. Warning: The resulting value will be different for everyone seeing your avatar. /// https://docs.vrchat.com/docs/state-behaviors#random /// public static TNode DrivingRandomizesUnsynced(this TNode node, AacFlBoolParameter parameter, float chance) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.parameters.Add(new VRC_AvatarParameterDriver.Parameter { type = VRC_AvatarParameterDriver.ChangeType.Random, name = parameter.Name, chance = chance }); return node; } public static TNode Drives(this TNode node,AacFlBoolParameterGroup parameters, bool value) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); foreach (var parameter in parameters.ToList()) { driver.parameters.Add(new VRC_AvatarParameterDriver.Parameter { name = parameter.Name, value = value ? 1 : 0 }); } return node; } /// /// Only set this parameter for the person wearing the avatar (recommended for synced parameters). /// public static TNode DrivingLocally(this TNode node) where TNode : AacAnimatorNode { var driver = node.EnsureBehaviour(); driver.localOnly = true; return node; } private static void EnsureParameter(this VRC_AvatarParameterDriver driver, VRC_AvatarParameterDriver.Parameter p) { for (var i = 0; i < driver.parameters.Count; i++) { if (driver.parameters[i].name != p.name) continue; driver.parameters[i] = p; return; } driver.parameters.Add(p); } public static TNode PrintsToLogUsingTrackingBehaviour(this TNode node, string value) where TNode : AacAnimatorNode { var tracking = node.EnsureBehaviour(); tracking.debugString = value; return node; } public static TNode TrackingTracks(this TNode node, TrackingElement element) where TNode : AacAnimatorNode { var tracking = node.EnsureBehaviour(); SettingElementTo(tracking, element, VRC_AnimatorTrackingControl.TrackingType.Tracking); return node; } public static TNode TrackingAnimates(this TNode node, TrackingElement element) where TNode : AacAnimatorNode { var tracking = node.EnsureBehaviour(); SettingElementTo(tracking, element, VRC_AnimatorTrackingControl.TrackingType.Animation); return node; } public static TNode TrackingSets(this TNode node, TrackingElement element, VRC_AnimatorTrackingControl.TrackingType trackingType) where TNode : AacAnimatorNode { var tracking = node.EnsureBehaviour(); SettingElementTo(tracking, element, trackingType); return node; } private static void SettingElementTo(VRCAnimatorTrackingControl tracking, TrackingElement element, VRC_AnimatorTrackingControl.TrackingType target) { switch (element) { case TrackingElement.Head: tracking.trackingHead = target; break; case TrackingElement.LeftHand: tracking.trackingLeftHand = target; break; case TrackingElement.RightHand: tracking.trackingRightHand = target; break; case TrackingElement.Hip: tracking.trackingHip = target; break; case TrackingElement.LeftFoot: tracking.trackingLeftFoot = target; break; case TrackingElement.RightFoot: tracking.trackingRightFoot = target; break; case TrackingElement.LeftFingers: tracking.trackingLeftFingers = target; break; case TrackingElement.RightFingers: tracking.trackingRightFingers = target; break; case TrackingElement.Eyes: tracking.trackingEyes = target; break; case TrackingElement.Mouth: tracking.trackingMouth = target; break; default: throw new ArgumentOutOfRangeException(nameof(element), element, null); } } public static TNode LocomotionEnabled(this TNode node) where TNode : AacAnimatorNode { var locomotionControl = node.EnsureBehaviour(); locomotionControl.disableLocomotion = false; return node; } public static TNode LocomotionDisabled(this TNode node) where TNode : AacAnimatorNode { var locomotionControl = node.EnsureBehaviour(); locomotionControl.disableLocomotion = true; return node; } public static TNode PlayableEnables(this TNode node, VRC_PlayableLayerControl.BlendableLayer blendable, float blendDurationSeconds = 0f) where TNode : AacAnimatorNode { var playable = node.EnsureBehaviour(); PlayableSets(playable, blendable, blendDurationSeconds, 1.0f); return node; } public static TNode PlayableDisables(this TNode node, VRC_PlayableLayerControl.BlendableLayer blendable, float blendDurationSeconds = 0f) where TNode : AacAnimatorNode { var playable = node.EnsureBehaviour(); PlayableSets(playable, blendable, blendDurationSeconds, 0.0f); return node; } private static void PlayableSets(VRCPlayableLayerControl playable, VRC_PlayableLayerControl.BlendableLayer blendable, float blendDurationSeconds, float weight) { playable.layer = blendable; playable.goalWeight = weight; playable.blendDuration = blendDurationSeconds; } public static TNode PoseSpaceEntered(this TNode node, float delaySeconds = 0f) where TNode : AacAnimatorNode { var temporaryPoseSpace = node.EnsureBehaviour(); temporaryPoseSpace.enterPoseSpace = true; temporaryPoseSpace.fixedDelay = true; temporaryPoseSpace.delayTime = delaySeconds; return node; } public static TNode PoseSpaceExited(this TNode node, float delaySeconds = 0f) where TNode : AacAnimatorNode { var temporaryPoseSpace = node.EnsureBehaviour(); temporaryPoseSpace.enterPoseSpace = false; temporaryPoseSpace.fixedDelay = true; temporaryPoseSpace.delayTime = delaySeconds; return node; } public static TNode PoseSpaceEnteredPercent(this TNode node, float delayNormalized) where TNode : AacAnimatorNode { var temporaryPoseSpace = node.EnsureBehaviour(); temporaryPoseSpace.enterPoseSpace = true; temporaryPoseSpace.fixedDelay = false; temporaryPoseSpace.delayTime = delayNormalized; return node; } public static TNode PoseSpaceExitedPercent(this TNode node, float delayNormalized) where TNode : AacAnimatorNode { var temporaryPoseSpace = node.EnsureBehaviour(); temporaryPoseSpace.enterPoseSpace = false; temporaryPoseSpace.fixedDelay = false; temporaryPoseSpace.delayTime = delayNormalized; return node; } } }