using System;
using UnityEditor;
using UnityEngine;
using VRC.SDK3.Avatars.Components;
using VRC.SDKBase;
// ReSharper disable once CheckNamespace
namespace AnimatorAsCode.V1.VRC
{
public static class AacVRCExtensions
{
/// Return an AacVrcAssetLibrary, which lets you select various assets from VRChat.
public static AacVrcAssetLibrary VrcAssets(this AacFlBase that)
{
return new AacVrcAssetLibrary();
}
public static AacAv3 Av3(this AacFlLayer that)
{
return new AacAv3(that.InternalStateMachine().InternalBackingAnimator());
}
///
/// 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, AacAv3.Av3TrackingElement element) where TNode : AacAnimatorNode
{
var tracking = node.EnsureBehaviour();
SettingElementTo(tracking, element, VRC_AnimatorTrackingControl.TrackingType.Tracking);
return node;
}
public static TNode TrackingAnimates(this TNode node, AacAv3.Av3TrackingElement element) where TNode : AacAnimatorNode
{
var tracking = node.EnsureBehaviour();
SettingElementTo(tracking, element, VRC_AnimatorTrackingControl.TrackingType.Animation);
return node;
}
public static TNode TrackingSets(this TNode node, AacAv3.Av3TrackingElement element, VRC_AnimatorTrackingControl.TrackingType trackingType) where TNode : AacAnimatorNode
{
var tracking = node.EnsureBehaviour();
SettingElementTo(tracking, element, trackingType);
return node;
}
private static void SettingElementTo(VRCAnimatorTrackingControl tracking, AacAv3.Av3TrackingElement element, VRC_AnimatorTrackingControl.TrackingType target)
{
switch (element)
{
case AacAv3.Av3TrackingElement.Head:
tracking.trackingHead = target;
break;
case AacAv3.Av3TrackingElement.LeftHand:
tracking.trackingLeftHand = target;
break;
case AacAv3.Av3TrackingElement.RightHand:
tracking.trackingRightHand = target;
break;
case AacAv3.Av3TrackingElement.Hip:
tracking.trackingHip = target;
break;
case AacAv3.Av3TrackingElement.LeftFoot:
tracking.trackingLeftFoot = target;
break;
case AacAv3.Av3TrackingElement.RightFoot:
tracking.trackingRightFoot = target;
break;
case AacAv3.Av3TrackingElement.LeftFingers:
tracking.trackingLeftFingers = target;
break;
case AacAv3.Av3TrackingElement.RightFingers:
tracking.trackingRightFingers = target;
break;
case AacAv3.Av3TrackingElement.Eyes:
tracking.trackingEyes = target;
break;
case AacAv3.Av3TrackingElement.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;
}
}
public class AacAv3
{
private readonly AacBackingAnimator _backingAnimator;
internal AacAv3(AacBackingAnimator backingAnimator)
{
_backingAnimator = backingAnimator;
}
// ReSharper disable InconsistentNaming
public AacFlBoolParameter IsLocal => _backingAnimator.BoolParameter("IsLocal");
public AacFlEnumIntParameter Viseme => _backingAnimator.EnumParameter("Viseme");
public AacFlEnumIntParameter GestureLeft => _backingAnimator.EnumParameter("GestureLeft");
public AacFlEnumIntParameter GestureRight => _backingAnimator.EnumParameter("GestureRight");
public AacFlFloatParameter GestureLeftWeight => _backingAnimator.FloatParameter("GestureLeftWeight");
public AacFlFloatParameter GestureRightWeight => _backingAnimator.FloatParameter("GestureRightWeight");
public AacFlFloatParameter AngularY => _backingAnimator.FloatParameter("AngularY");
public AacFlFloatParameter VelocityX => _backingAnimator.FloatParameter("VelocityX");
public AacFlFloatParameter VelocityY => _backingAnimator.FloatParameter("VelocityY");
public AacFlFloatParameter VelocityZ => _backingAnimator.FloatParameter("VelocityZ");
public AacFlFloatParameter Upright => _backingAnimator.FloatParameter("Upright");
public AacFlBoolParameter Grounded => _backingAnimator.BoolParameter("Grounded");
public AacFlBoolParameter Seated => _backingAnimator.BoolParameter("Seated");
public AacFlBoolParameter AFK => _backingAnimator.BoolParameter("AFK");
public AacFlIntParameter TrackingType => _backingAnimator.IntParameter("TrackingType");
public AacFlIntParameter VRMode => _backingAnimator.IntParameter("VRMode");
public AacFlBoolParameter MuteSelf => _backingAnimator.BoolParameter("MuteSelf");
public AacFlBoolParameter InStation => _backingAnimator.BoolParameter("InStation");
public AacFlFloatParameter Voice => _backingAnimator.FloatParameter("Voice");
// ReSharper restore InconsistentNaming
public IAacFlCondition ItIsRemote() => IsLocal.IsFalse();
public IAacFlCondition ItIsLocal() => IsLocal.IsTrue();
public enum Av3TrackingElement
{
Head,
LeftHand,
RightHand,
Hip,
LeftFoot,
RightFoot,
LeftFingers,
RightFingers,
Eyes,
Mouth
}
public enum Av3Gesture
{
// Specify all the values explicitly because they should be dictated by VRChat, not enumeration order.
Neutral = 0,
Fist = 1,
HandOpen = 2,
Fingerpoint = 3,
Victory = 4,
RockNRoll = 5,
HandGun = 6,
ThumbsUp = 7
}
public enum Av3Viseme
{
// Specify all the values explicitly because they should be dictated by VRChat, not enumeration order.
// ReSharper disable InconsistentNaming
sil = 0,
pp = 1,
ff = 2,
th = 3,
dd = 4,
kk = 5,
ch = 6,
ss = 7,
nn = 8,
rr = 9,
aa = 10,
e = 11,
ih = 12,
oh = 13,
ou = 14
// ReSharper restore InconsistentNaming
}
}
public class AacVrcAssetLibrary
{
public AvatarMask LeftHandAvatarMask()
{
return AssetDatabase.LoadAssetAtPath("Packages/com.vrchat.avatars/Samples/AV3 Demo Assets/Animation/Masks/vrc_Hand Left.mask");
}
public AvatarMask RightHandAvatarMask()
{
return AssetDatabase.LoadAssetAtPath("Packages/com.vrchat.avatars/Samples/AV3 Demo Assets/Animation/Masks/vrc_Hand Right.mask");
}
public AnimationClip ProxyForGesture(AacAv3.Av3Gesture gesture, bool masculine)
{
return AssetDatabase.LoadAssetAtPath("Packages/com.vrchat.avatars/Samples/AV3 Demo Assets/Animation/ProxyAnim/" + ResolveProxyFilename(gesture, masculine));
}
private static string ResolveProxyFilename(AacAv3.Av3Gesture gesture, bool masculine)
{
switch (gesture)
{
case AacAv3.Av3Gesture.Neutral: return masculine ? "proxy_hands_idle.anim" : "proxy_hands_idle2.anim";
case AacAv3.Av3Gesture.Fist: return "proxy_hands_fist.anim";
case AacAv3.Av3Gesture.HandOpen: return "proxy_hands_open.anim";
case AacAv3.Av3Gesture.Fingerpoint: return "proxy_hands_point.anim";
case AacAv3.Av3Gesture.Victory: return "proxy_hands_peace.anim";
case AacAv3.Av3Gesture.RockNRoll: return "proxy_hands_rock.anim";
case AacAv3.Av3Gesture.HandGun: return "proxy_hands_gun.anim";
case AacAv3.Av3Gesture.ThumbsUp: return "proxy_hands_thumbs_up.anim";
default:
throw new ArgumentOutOfRangeException(nameof(gesture), gesture, null);
}
}
}
}