diff --git a/AvatarScale/AvatarScaleGesture.cs b/AvatarScale/AvatarScaleGesture.cs new file mode 100644 index 0000000..378d3c6 --- /dev/null +++ b/AvatarScale/AvatarScaleGesture.cs @@ -0,0 +1,42 @@ +using UnityEngine; + +namespace NAK.AvatarScaleMod; + +public static class AvatarScaleGesture +{ + public static float InitialModifier = 1f; + public static float InitialTargetHeight = 1.8f; + + public static void OnScaleStart(float modifier, Transform transform1, Transform transform2) + { + // AvatarScaleMod.Logger.Msg("OnScaleStart!"); + + if (AvatarScaleManager.LocalAvatar != null) + { + // store initial modifier + InitialModifier = modifier; + InitialTargetHeight = AvatarScaleManager.LocalAvatar.TargetHeight; + } + } + + public static void OnScaleStay(float modifier, Transform transform1, Transform transform2) + { + // AvatarScaleMod.Logger.Msg("OnScaleStay!"); + + if (AvatarScaleManager.LocalAvatar != null) + { + float modifierRatio = modifier / InitialModifier; + + // Determine the adjustment factor for the height, this will be >1 if scaling up, <1 if scaling down. + float heightAdjustmentFactor = (modifierRatio > 1) ? 1 + (modifierRatio - 1) : 1 - (1 - modifierRatio); + + // Apply the adjustment to the target height + AvatarScaleManager.LocalAvatar.SetTargetHeight(InitialTargetHeight * heightAdjustmentFactor); + } + } + + public static void OnScaleEnd(float modifier, Transform transform1, Transform transform2) + { + // AvatarScaleMod.Logger.Msg("OnScaleEnd!"); + } +} diff --git a/AvatarScale/AvatarScaleManager.cs b/AvatarScale/AvatarScaleManager.cs new file mode 100644 index 0000000..af01c46 --- /dev/null +++ b/AvatarScale/AvatarScaleManager.cs @@ -0,0 +1,209 @@ +using ABI.CCK.Components; +using UnityEngine; +using UnityEngine.Animations; + +namespace NAK.AvatarScaleMod; + +public class AvatarScaleManager : MonoBehaviour +{ + public static AvatarScaleManager LocalAvatar { get; private set; } + + // List of component types to be collected and scaled + private static readonly System.Type[] scaleComponentTypes = new System.Type[] + { + typeof(Light), + typeof(AudioSource), + typeof(ParticleSystem), + typeof(ParentConstraint), + typeof(PositionConstraint), + typeof(ScaleConstraint), + }; + + public const float MinimumHeight = 0.25f; + public const float MaximumHeight = 2.5f; + + // Scalable Components + private List _lights = new List(); + private List _audioSources = new List(); + //private List> _particleSystems = new List>(); + private List _parentConstraints = new List(); + private List _positionConstraints = new List(); + private List _scaleConstraints = new List(); + + public float TargetHeight { get; private set; } + public float InitialHeight { get; private set; } + public Vector3 InitialScale { get; private set; } + public float ScaleFactor { get; private set; } + + public void Initialize(float initialHeight, Vector3 initialScale) + { + // Check for zero height + if (Math.Abs(initialHeight) < 1E-6) + { + AvatarScaleMod.Logger.Warning("Cannot initialize with a height of zero!"); + return; + } + + this.TargetHeight = 1f; + this.InitialHeight = initialHeight; + this.InitialScale = initialScale; + UpdateScaleFactor(); + } + + public void SetTargetHeight(float newHeight) + { + TargetHeight = Mathf.Clamp(newHeight, MinimumHeight, MaximumHeight); + UpdateScaleFactor(); + } + + public void UpdateScaleFactor() + { + // Check for zero + if (Math.Abs(InitialHeight) < 1E-6) + { + AvatarScaleMod.Logger.Warning("InitialHeight is zero, cannot calculate ScaleFactor."); + return; + } + + this.ScaleFactor = TargetHeight / InitialHeight; + } + + private Vector3 CalculateNewScale() + { + return InitialScale * ScaleFactor; + } + + private void Awake() + { + // why am i caching the avatar + CVRAvatar avatar = GetComponent(); + if (avatar == null) + { + AvatarScaleMod.Logger.Error("AvatarScaleManager should be attached to a GameObject with a CVRAvatar component."); + return; + } + + // i cant believe i would stoop this low + if (gameObject.layer == 8 && LocalAvatar == null) + LocalAvatar = this; + + FindComponentsOfType(scaleComponentTypes); + } + + private void OnDestroy() + { + _audioSources.Clear(); + _lights.Clear(); + //_particleSystems.Clear(); // fuck no + _parentConstraints.Clear(); + _positionConstraints.Clear(); + _scaleConstraints.Clear(); + + // local player manager + if (LocalAvatar == this) + LocalAvatar = null; + } + + private void FindComponentsOfType(params System.Type[] types) + { + foreach (var type in types) + { + var components = gameObject.GetComponentsInChildren(type, true); + foreach (var component in components) + { + switch (component) + { + case AudioSource audioSource: + _audioSources.Add(new ScaledAudioSource(audioSource)); + break; + case Light light: + _lights.Add(new ScaledLight(light)); + break; + case ParentConstraint parentConstraint: + _parentConstraints.Add(new ScaledParentConstraint(parentConstraint)); + break; + case PositionConstraint positionConstraint: + _positionConstraints.Add(new ScaledPositionConstraint(positionConstraint)); + break; + case ScaleConstraint scaleConstraint: + _scaleConstraints.Add(new ScaledScaleConstraint(scaleConstraint)); + break; + } + } + } + } + + void Update() + { + ApplyAvatarScaling(); + ApplyComponentScaling(); + } + + void LateUpdate() + { + ApplyAvatarScaling(); + ApplyComponentScaling(); + } + + private void ApplyAvatarScaling() + { + transform.localScale = CalculateNewScale(); + } + + private void ApplyComponentScaling() + { + UpdateLightScales(); + UpdateAudioSourceScales(); + UpdateParentConstraintScales(); + UpdatePositionConstraintScales(); + UpdateScaleConstraintScales(); + } + + private void UpdateLightScales() + { + foreach (var scaledLight in _lights) + { + scaledLight.Component.range = scaledLight.InitialRange * ScaleFactor; + } + } + + private void UpdateAudioSourceScales() + { + foreach (var scaledAudioSource in _audioSources) + { + scaledAudioSource.Component.minDistance = scaledAudioSource.InitialMinDistance * ScaleFactor; + scaledAudioSource.Component.maxDistance = scaledAudioSource.InitialMaxDistance * ScaleFactor; + } + } + + private void UpdateParentConstraintScales() + { + foreach (var scaledParentConstraint in _parentConstraints) + { + scaledParentConstraint.Component.translationAtRest = scaledParentConstraint.InitialTranslationAtRest * ScaleFactor; + + for (int i = 0; i < scaledParentConstraint.InitialTranslationOffsets.Count; i++) + { + scaledParentConstraint.Component.translationOffsets[i] = scaledParentConstraint.InitialTranslationOffsets[i] * ScaleFactor; + } + } + } + + private void UpdatePositionConstraintScales() + { + foreach (var scaledPositionConstraint in _positionConstraints) + { + scaledPositionConstraint.Component.translationAtRest = scaledPositionConstraint.InitialTranslationAtRest * ScaleFactor; + scaledPositionConstraint.Component.translationOffset = scaledPositionConstraint.InitialTranslationOffset * ScaleFactor; + } + } + + private void UpdateScaleConstraintScales() + { + foreach (var scaledScaleConstraint in _scaleConstraints) + { + scaledScaleConstraint.Component.scaleAtRest = scaledScaleConstraint.InitialScaleAtRest * ScaleFactor; + scaledScaleConstraint.Component.scaleOffset = scaledScaleConstraint.InitialScaleOffset * ScaleFactor; + } + } +} \ No newline at end of file diff --git a/AvatarScale/HarmonyPatches.cs b/AvatarScale/HarmonyPatches.cs index a8b7e20..0cac1c3 100644 --- a/AvatarScale/HarmonyPatches.cs +++ b/AvatarScale/HarmonyPatches.cs @@ -1,42 +1,13 @@ using ABI_RC.Core.Player; +using ABI_RC.Core.Savior; using HarmonyLib; +using UnityEngine; +using UnityEngine.Events; namespace NAK.AvatarScaleMod.HarmonyPatches; class PlayerSetupPatches { - /** - [HarmonyPostfix] - [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupAvatar))] - static void Postfix_PlayerSetup_SetupAvatar(ref PlayerSetup __instance, ref float ____initialAvatarHeight) - { - if (!AvatarScaleMod.EntryEnabled.Value) return; - - if (AvatarScaleMod.HiddenAvatarScale.Value > 0f) - { - __instance.changeAnimatorParam(AvatarScaleMod.ParameterName, AvatarScaleMod.HiddenAvatarScale.Value); - return; - } - - // User has cleared MelonPrefs, store a default value. - AvatarScaleMod.HiddenAvatarScale.Value = Utils.CalculateParameterValue(____initialAvatarHeight); - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.ClearAvatar))] - static void Prefix_PlayerSetup_ClearAvatar(ref PlayerSetup __instance, ref float ____avatarHeight) - { - if (!AvatarScaleMod.EntryEnabled.Value) return; - - if (!Utils.IsSupportedAvatar(__instance.animatorManager) && !AvatarScaleMod.EntryPersistAnyways.Value) - { - return; - } - - AvatarScaleMod.HiddenAvatarScale.Value = Utils.CalculateParameterValue(____avatarHeight); - } - **/ - [HarmonyPostfix] [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupAvatar))] static void Postfix_PlayerSetup_SetupAvatar(ref PlayerSetup __instance) @@ -51,4 +22,39 @@ class PlayerSetupPatches AvatarScaleMod.Logger.Error(e); } } +} + +class GesturePlaneTestPatches +{ + [HarmonyPostfix] + [HarmonyPatch(typeof(GesturePlaneTest), nameof(GesturePlaneTest.Start))] + static void Postfix_GesturePlaneTest_Start() + { + try + { + // nicked from Kafe >:)))) + var gesture = new CVRGesture + { + name = "avatarScale", + type = CVRGesture.GestureType.Hold, + }; + gesture.steps.Add(new CVRGestureStep + { + firstGesture = CVRGestureStep.Gesture.Fist, + secondGesture = CVRGestureStep.Gesture.Fist, + startDistance = 0.5f, + endDistance = 0.4f, + direction = CVRGestureStep.GestureDirection.MovingIn, + }); + gesture.onStart.AddListener(new UnityAction(AvatarScaleGesture.OnScaleStart)); + gesture.onStay.AddListener(new UnityAction(AvatarScaleGesture.OnScaleStay)); + gesture.onEnd.AddListener(new UnityAction(AvatarScaleGesture.OnScaleEnd)); + CVRGestureRecognizer.Instance.gestures.Add(gesture); + } + catch (Exception e) + { + AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_GesturePlaneTest_Start)}"); + AvatarScaleMod.Logger.Error(e); + } + } } \ No newline at end of file diff --git a/AvatarScale/Main.cs b/AvatarScale/Main.cs index df26ce9..2a6794b 100644 --- a/AvatarScale/Main.cs +++ b/AvatarScale/Main.cs @@ -4,28 +4,22 @@ namespace NAK.AvatarScaleMod; public class AvatarScaleMod : MelonMod { - internal const string ParameterName = "AvatarScale"; - internal const float MinimumHeight = 0.25f; - internal const float MaximumHeight = 2.5f; - internal static MelonLogger.Instance Logger; public static readonly MelonPreferences_Category Category = MelonPreferences.CreateCategory(nameof(AvatarScaleMod)); public static readonly MelonPreferences_Entry EntryEnabled = - Category.CreateEntry("Enabled", true, description: "Should there be persistant avatar scaling? This only works properly across supported avatars."); + Category.CreateEntry("Enabled", true, description: "Toggle AvatarScaleMod entirely."); - public static readonly MelonPreferences_Entry EntryPersistAnyways = - Category.CreateEntry("Persist From Unsupported", true, description: "Should avatar scale persist even from unsupported avatars?"); - - public static readonly MelonPreferences_Entry HiddenAvatarScale = - Category.CreateEntry("Last Avatar Scale", -1f, is_hidden: true); + public static readonly MelonPreferences_Entry EntryUseScaleGesture = + Category.CreateEntry("Scale Gesture", true, description: "Use two fists to scale yourself easily."); public override void OnInitializeMelon() { Logger = LoggerInstance; ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); + ApplyPatches(typeof(HarmonyPatches.GesturePlaneTestPatches)); } void ApplyPatches(Type type) diff --git a/AvatarScale/README.md b/AvatarScale/README.md index 0fa945f..d2c9399 100644 --- a/AvatarScale/README.md +++ b/AvatarScale/README.md @@ -1,11 +1,9 @@ # AvatarScaleMod -Makes "AvatarScale" parameter persistant across avatars. +Proof of concept mod to add Avatar Scaling to any avatar. This is local-only. -Combined with [AvatarScaleTool](https://github.com/NotAKidOnSteam/AvatarScaleTool), this allows for consistant scale when switching between avatars. +Legit threw this together in three hours. ChilloutVR handles all the hard stuff already lmao. - - --- Here is the block of text where I tell you this mod is not affiliated or endorsed by ABI. diff --git a/AvatarScale/ScaledComponents.cs b/AvatarScale/ScaledComponents.cs new file mode 100644 index 0000000..c490550 --- /dev/null +++ b/AvatarScale/ScaledComponents.cs @@ -0,0 +1,72 @@ +using UnityEngine; +using UnityEngine.Animations; + +namespace NAK.AvatarScaleMod; + +public class ScaledAudioSource +{ + public AudioSource Component { get; } + public float InitialMinDistance { get; } + public float InitialMaxDistance { get; } + + public ScaledAudioSource(AudioSource component) + { + Component = component; + InitialMinDistance = component.minDistance; + InitialMaxDistance = component.maxDistance; + } +} + +public class ScaledLight +{ + public Light Component { get; } + public float InitialRange { get; } + + public ScaledLight(Light component) + { + Component = component; + InitialRange = component.range; + } +} + +public class ScaledPositionConstraint +{ + public PositionConstraint Component { get; } + public Vector3 InitialTranslationAtRest { get; } + public Vector3 InitialTranslationOffset { get; } + + public ScaledPositionConstraint(PositionConstraint component) + { + Component = component; + InitialTranslationAtRest = component.translationAtRest; + InitialTranslationOffset = component.translationOffset; + } +} + +public class ScaledParentConstraint +{ + public ParentConstraint Component { get; } + public Vector3 InitialTranslationAtRest { get; } + public List InitialTranslationOffsets { get; } + + public ScaledParentConstraint(ParentConstraint component) + { + Component = component; + InitialTranslationAtRest = component.translationAtRest; + InitialTranslationOffsets = component.translationOffsets.ToList(); + } +} + +public class ScaledScaleConstraint +{ + public ScaleConstraint Component { get; } + public Vector3 InitialScaleAtRest { get; } + public Vector3 InitialScaleOffset { get; } + + public ScaledScaleConstraint(ScaleConstraint component) + { + Component = component; + InitialScaleAtRest = component.scaleAtRest; + InitialScaleOffset = component.scaleOffset; + } +} \ No newline at end of file diff --git a/AvatarScale/Utils.cs b/AvatarScale/Utils.cs deleted file mode 100644 index 5388f01..0000000 --- a/AvatarScale/Utils.cs +++ /dev/null @@ -1,24 +0,0 @@ -using ABI_RC.Core; - -namespace NAK.AvatarScaleMod; - -class Utils -{ - public static bool IsSupportedAvatar(CVRAnimatorManager manager) - { - if (manager.animatorParameterFloatList.Contains(AvatarScaleMod.ParameterName) && manager._animator != null) - { - if (manager._advancedAvatarIndicesFloat.TryGetValue(AvatarScaleMod.ParameterName, out int index)) - { - return index < manager._advancedAvatarCacheFloat.Count; - } - } - return false; - } - - public static float CalculateParameterValue(float lastAvatarHeight) - { - float t = (lastAvatarHeight - AvatarScaleMod.MinimumHeight) / (AvatarScaleMod.MaximumHeight - AvatarScaleMod.MinimumHeight); - return t; - } -}