[AvatarScaleMod] Implement scaled components.

implemented scaling for specific components that Avatar Scale Tool would

they conflict, if you ran your avatar through Avatar Scale Tool then the animations will add on top
This commit is contained in:
NotAKidoS 2023-06-22 23:56:01 -05:00
parent 44d5c7762b
commit e8d3183bc3
7 changed files with 367 additions and 70 deletions

View file

@ -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!");
}
}

View file

@ -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<ScaledLight> _lights = new List<ScaledLight>();
private List<ScaledAudioSource> _audioSources = new List<ScaledAudioSource>();
//private List<ScaledComponent<ParticleSystem>> _particleSystems = new List<ScaledComponent<ParticleSystem>>();
private List<ScaledParentConstraint> _parentConstraints = new List<ScaledParentConstraint>();
private List<ScaledPositionConstraint> _positionConstraints = new List<ScaledPositionConstraint>();
private List<ScaledScaleConstraint> _scaleConstraints = new List<ScaledScaleConstraint>();
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<CVRAvatar>();
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;
}
}
}

View file

@ -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<float, Transform, Transform>(AvatarScaleGesture.OnScaleStart));
gesture.onStay.AddListener(new UnityAction<float, Transform, Transform>(AvatarScaleGesture.OnScaleStay));
gesture.onEnd.AddListener(new UnityAction<float, Transform, Transform>(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);
}
}
}

View file

@ -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<bool> 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<bool> EntryPersistAnyways =
Category.CreateEntry("Persist From Unsupported", true, description: "Should avatar scale persist even from unsupported avatars?");
public static readonly MelonPreferences_Entry<float> HiddenAvatarScale =
Category.CreateEntry("Last Avatar Scale", -1f, is_hidden: true);
public static readonly MelonPreferences_Entry<bool> 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)

View file

@ -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.

View file

@ -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<Vector3> 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;
}
}

View file

@ -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;
}
}