mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 06:19:22 +00:00
Move many mods to Deprecated folder, fix spelling
This commit is contained in:
parent
5e822cec8d
commit
0042590aa6
539 changed files with 7475 additions and 3120 deletions
36
.Deprecated/AvatarScaleMod/AvatarScaleMod.csproj
Normal file
36
.Deprecated/AvatarScaleMod/AvatarScaleMod.csproj
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<NoWarn />
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<NoWarn />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="resources\menu.js" />
|
||||
<None Remove="resources\nak_menu.css" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="resources\menu.js" />
|
||||
<None Remove="resources\Icon_AvatarHeightConfig.png" />
|
||||
<EmbeddedResource Include="resources\ASM_Icon_AvatarHeightConfig.png" />
|
||||
<None Remove="resources\ASM_Icon_AvatarHeightCopy.png" />
|
||||
<EmbeddedResource Include="resources\ASM_Icon_AvatarHeightCopy.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="BTKUILib">
|
||||
<HintPath>$(MsBuildThisFileDirectory)\..\.ManagedLibs\BTKUILib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
397
.Deprecated/AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs
Normal file
397
.Deprecated/AvatarScaleMod/AvatarScaling/AvatarScaleManager.cs
Normal file
|
@ -0,0 +1,397 @@
|
|||
using System.Collections;
|
||||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Player.AvatarTracking;
|
||||
using ABI_RC.Core.UI;
|
||||
using ABI_RC.Systems.GameEventSystem;
|
||||
using NAK.AvatarScaleMod.Components;
|
||||
using NAK.AvatarScaleMod.Networking;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.AvatarScaling;
|
||||
|
||||
public class AvatarScaleManager : MonoBehaviour
|
||||
{
|
||||
public static AvatarScaleManager Instance { get; private set; }
|
||||
|
||||
private LocalScaler _localAvatarScaler;
|
||||
private Dictionary<string, NetworkScaler> _networkedScalers;
|
||||
|
||||
private Coroutine _heightUpdateCoroutine;
|
||||
private readonly YieldInstruction _heightUpdateYield = new WaitForEndOfFrame();
|
||||
|
||||
#region Universal Scaling Limits
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// To match AvatarScaleTool: https://github.com/NotAKidoS/AvatarScaleTool/tree/main
|
||||
public const float DefaultMinHeight = 0.25f;
|
||||
public const float DefaultMaxHeight = 2.50f;
|
||||
// ReSharper restore MemberCanBePrivate.Global
|
||||
|
||||
// Universal Scaling Limits
|
||||
public static float MinHeight { get; private set; } = DefaultMinHeight;
|
||||
public static float MaxHeight { get; private set; } = DefaultMaxHeight;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Settings
|
||||
|
||||
private bool _settingUniversalScaling;
|
||||
public bool Setting_UniversalScaling
|
||||
{
|
||||
get => _settingUniversalScaling;
|
||||
set
|
||||
{
|
||||
if (_settingUniversalScaling == value)
|
||||
return;
|
||||
|
||||
_settingUniversalScaling = value;
|
||||
|
||||
if (_localAvatarScaler != null)
|
||||
_localAvatarScaler.UseTargetHeight = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _settingAnimationClipScalingOverride;
|
||||
public bool Setting_AnimationClipScalingOverride
|
||||
{
|
||||
get => _settingAnimationClipScalingOverride;
|
||||
set
|
||||
{
|
||||
if (_settingAnimationClipScalingOverride == value)
|
||||
return;
|
||||
|
||||
_settingAnimationClipScalingOverride = value;
|
||||
|
||||
if (_localAvatarScaler != null)
|
||||
_localAvatarScaler.overrideAnimationHeight = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _settingPersistentHeight;
|
||||
public bool Setting_PersistentHeight
|
||||
{
|
||||
get => _settingPersistentHeight;
|
||||
set
|
||||
{
|
||||
if (_settingPersistentHeight == value)
|
||||
return;
|
||||
|
||||
_settingPersistentHeight = value;
|
||||
|
||||
// if (_localAvatarScaler != null)
|
||||
// _localAvatarScaler.persistHeight = value;
|
||||
}
|
||||
}
|
||||
|
||||
private float _lastTargetHeight = -1f;
|
||||
public float LastTargetHeight
|
||||
{
|
||||
get => _lastTargetHeight;
|
||||
set
|
||||
{
|
||||
_lastTargetHeight = value;
|
||||
ModSettings.EntryHiddenAvatarHeight.Value = _lastTargetHeight;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null
|
||||
&& Instance != this)
|
||||
{
|
||||
DestroyImmediate(this);
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
_networkedScalers = new Dictionary<string, NetworkScaler>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_localAvatarScaler = PlayerSetup.Instance.gameObject.AddComponent<LocalScaler>();
|
||||
_localAvatarScaler.Initialize();
|
||||
|
||||
_settingUniversalScaling = ModSettings.EntryUseUniversalScaling.Value;
|
||||
Setting_AnimationClipScalingOverride = ModSettings.EntryAnimationScalingOverride.Value;
|
||||
Setting_PersistentHeight = ModSettings.EntryPersistentHeight.Value;
|
||||
_lastTargetHeight = ModSettings.EntryPersistThroughRestart.Value
|
||||
? ModSettings.EntryHiddenAvatarHeight.Value : -1f; // -1f is default
|
||||
|
||||
// listen for events
|
||||
_localAvatarScaler.OnAnimatedHeightOverride += OnAnimationHeightOverride;
|
||||
|
||||
CVRGameEventSystem.Instance.OnConnected.AddListener(OnInstanceConnected);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_heightUpdateCoroutine != null) StopCoroutine(_heightUpdateCoroutine);
|
||||
_heightUpdateCoroutine = StartCoroutine(HeightUpdateCoroutine());
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_heightUpdateCoroutine != null) StopCoroutine(_heightUpdateCoroutine);
|
||||
_heightUpdateCoroutine = null;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_heightUpdateCoroutine = null;
|
||||
CVRGameEventSystem.Instance.OnConnected.RemoveListener(OnInstanceConnected);
|
||||
|
||||
if (_localAvatarScaler != null) Destroy(_localAvatarScaler);
|
||||
_localAvatarScaler = null;
|
||||
|
||||
foreach (NetworkScaler scaler in _networkedScalers.Values) Destroy(scaler);
|
||||
_networkedScalers.Clear();
|
||||
|
||||
if (Instance == this)
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
// only update the height per scaler once per frame, to prevent spam & jitter
|
||||
// this is to ensure that the height is also set at correct time during frame, no matter when it is called
|
||||
private IEnumerator HeightUpdateCoroutine()
|
||||
{
|
||||
while (enabled) yield return _heightUpdateYield;
|
||||
// ReSharper disable once IteratorNeverReturns
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Game Events
|
||||
|
||||
public void OnInstanceConnected(string instanceId)
|
||||
{
|
||||
// TODO: need to know if this causes issues when in a reconnection loop
|
||||
SchedulerSystem.AddJob(ModNetwork.RequestHeightSync, 2f, 1f, 1);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Local Methods
|
||||
|
||||
public void OnAvatarInstantiated(PlayerSetup playerSetup)
|
||||
{
|
||||
if (playerSetup._avatar == null)
|
||||
return;
|
||||
|
||||
_localAvatarScaler.OnAvatarInstantiated(playerSetup._avatar, playerSetup._initialAvatarHeight,
|
||||
playerSetup.initialScale);
|
||||
|
||||
if (!_settingUniversalScaling)
|
||||
return;
|
||||
|
||||
SetTargetHeight(_lastTargetHeight);
|
||||
}
|
||||
|
||||
public void OnAvatarDestroyed(PlayerSetup playerSetup)
|
||||
{
|
||||
if (_localAvatarScaler != null)
|
||||
_localAvatarScaler.OnAvatarDestroyed();
|
||||
}
|
||||
|
||||
public void SetTargetHeight(float targetHeight)
|
||||
{
|
||||
_lastTargetHeight = targetHeight; // save for persistent height
|
||||
|
||||
if (_localAvatarScaler == null)
|
||||
return;
|
||||
|
||||
_localAvatarScaler.TargetHeight = _lastTargetHeight;
|
||||
}
|
||||
|
||||
public float GetHeight()
|
||||
{
|
||||
if (_localAvatarScaler == null)
|
||||
return PlayerAvatarPoint.DefaultAvatarHeight;
|
||||
|
||||
if (!_localAvatarScaler.IsForcingHeight())
|
||||
return PlayerSetup.Instance.GetAvatarHeight();
|
||||
|
||||
return _localAvatarScaler.GetTargetHeight();
|
||||
}
|
||||
|
||||
public float GetAnimationClipHeight()
|
||||
{
|
||||
if (_localAvatarScaler == null)
|
||||
return PlayerAvatarPoint.DefaultAvatarHeight;
|
||||
|
||||
if (!_localAvatarScaler.IsForcingHeight())
|
||||
return PlayerSetup.Instance.GetAvatarHeight();
|
||||
|
||||
return _localAvatarScaler.GetAnimatedHeight();
|
||||
}
|
||||
|
||||
public float GetHeightForNetwork()
|
||||
{
|
||||
if (!_settingUniversalScaling)
|
||||
return -1f;
|
||||
|
||||
if (_localAvatarScaler == null)
|
||||
return -1f;
|
||||
|
||||
if (!_localAvatarScaler.IsForcingHeight())
|
||||
return -1f;
|
||||
|
||||
return _localAvatarScaler.GetTargetHeight();
|
||||
}
|
||||
|
||||
public float GetInitialHeight()
|
||||
{
|
||||
if (_localAvatarScaler == null)
|
||||
return -1f;
|
||||
|
||||
return _localAvatarScaler.GetInitialHeight();
|
||||
}
|
||||
|
||||
public bool IsHeightAdjustedFromInitial()
|
||||
{
|
||||
return _localAvatarScaler != null && _localAvatarScaler.IsForcingHeight();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Network Methods
|
||||
|
||||
public bool DoesNetworkHeightScalerExist(string playerId)
|
||||
=> _networkedScalers.ContainsKey(playerId);
|
||||
|
||||
public int GetNetworkHeightScalerCount()
|
||||
=> _networkedScalers.Count;
|
||||
|
||||
public float GetNetworkHeight(string playerId)
|
||||
{
|
||||
if (_networkedScalers.TryGetValue(playerId, out NetworkScaler scaler))
|
||||
if (scaler.IsForcingHeight()) return scaler.GetTargetHeight();
|
||||
|
||||
//doesn't have mod or has no custom height, get from player avatar directly
|
||||
CVRPlayerEntity playerEntity = CVRPlayerManager.Instance.NetworkPlayers.Find((players) => players.Uuid == playerId);
|
||||
if (playerEntity != null && playerEntity.PuppetMaster != null)
|
||||
return playerEntity.PuppetMaster.netIkController.GetRemoteHeight();
|
||||
|
||||
// player is invalid???
|
||||
return -1f;
|
||||
}
|
||||
|
||||
// we will create a Universal Scaler for only users that send a height update
|
||||
// this is sent at a rate of 10s locally!
|
||||
|
||||
internal void OnNetworkHeightUpdateReceived(string playerId, float targetHeight)
|
||||
{
|
||||
if (_networkedScalers.TryGetValue(playerId, out NetworkScaler scaler))
|
||||
scaler.TargetHeight = targetHeight;
|
||||
else
|
||||
SetupHeightScalerForNetwork(playerId, targetHeight);
|
||||
}
|
||||
|
||||
internal void OnNetworkAvatarInstantiated(PuppetMaster puppetMaster)
|
||||
{
|
||||
var playerId = puppetMaster._playerDescriptor.ownerId;
|
||||
if (_networkedScalers.TryGetValue(playerId, out NetworkScaler scaler))
|
||||
scaler.OnAvatarInstantiated(puppetMaster.avatarObject, puppetMaster.netIkController._initialHeight,
|
||||
puppetMaster.netIkController._initialScale);
|
||||
}
|
||||
|
||||
internal void OnNetworkAvatarDestroyed(PuppetMaster puppetMaster)
|
||||
{
|
||||
// on disconnect
|
||||
if (puppetMaster == null || puppetMaster._playerDescriptor == null)
|
||||
return;
|
||||
|
||||
var playerId = puppetMaster._playerDescriptor.ownerId;
|
||||
if (_networkedScalers.TryGetValue(playerId, out NetworkScaler scaler))
|
||||
scaler.OnAvatarDestroyed();
|
||||
}
|
||||
|
||||
internal void RemoveNetworkHeightScaler(string playerId)
|
||||
{
|
||||
if (_networkedScalers.ContainsKey(playerId))
|
||||
{
|
||||
AvatarScaleMod.Logger.Msg(
|
||||
$"Removed user height scaler! This is hopefully due to a disconnect or block. : {playerId}");
|
||||
|
||||
_networkedScalers.Remove(playerId);
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarScaleMod.Logger.Msg(
|
||||
$"Failed to remove a user height scaler! This shouldn't happen. : {playerId}");
|
||||
}
|
||||
|
||||
private void SetupHeightScalerForNetwork(string playerId, float targetHeight)
|
||||
{
|
||||
CVRPlayerEntity playerEntity =
|
||||
CVRPlayerManager.Instance.NetworkPlayers.Find(players => players.Uuid == playerId);
|
||||
|
||||
PuppetMaster puppetMaster = playerEntity?.PuppetMaster;
|
||||
|
||||
if (playerEntity == null || puppetMaster == null)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error(
|
||||
$"Attempted to set up height scaler for user which does not exist! : {playerId}");
|
||||
return;
|
||||
}
|
||||
|
||||
AvatarScaleMod.Logger.Msg(
|
||||
$"Setting up new height scaler for user which has sent a height update! : {playerId}");
|
||||
|
||||
if (_networkedScalers.ContainsKey(playerId))
|
||||
_networkedScalers.Remove(playerId); // ??
|
||||
|
||||
NetworkScaler scaler = puppetMaster.gameObject.AddComponent<NetworkScaler>();
|
||||
scaler.Initialize(playerId);
|
||||
|
||||
scaler.OnAvatarInstantiated(puppetMaster.avatarObject, puppetMaster.netIkController._initialHeight,
|
||||
puppetMaster.netIkController._initialScale);
|
||||
|
||||
_networkedScalers[playerId] = scaler;
|
||||
|
||||
scaler.TargetHeight = targetHeight;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Manager Methods
|
||||
|
||||
// sometimes fun to play with via UE
|
||||
public void SetUniversalScalingLimit(float min, float max)
|
||||
{
|
||||
const float HardCodedMinLimit = 0.01f;
|
||||
const float HardCodedMaxLimit = 100f;
|
||||
|
||||
MinHeight = Mathf.Clamp(min, HardCodedMinLimit, HardCodedMaxLimit);
|
||||
MaxHeight = Mathf.Clamp(max, HardCodedMinLimit, HardCodedMaxLimit);
|
||||
|
||||
AvatarScaleMod.Logger.Msg($"Universal Scaling Limits changed: {min} - {max}");
|
||||
AvatarScaleMod.Logger.Warning("This will not network to other users unless they also have the same limits set!");
|
||||
}
|
||||
|
||||
public void ResetUniversalScalingLimit()
|
||||
{
|
||||
MinHeight = DefaultMinHeight;
|
||||
MaxHeight = DefaultMaxHeight;
|
||||
|
||||
AvatarScaleMod.Logger.Msg("Universal Scaling Limits reset to default!");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Listeners
|
||||
|
||||
private static void OnAnimationHeightOverride(BaseScaler scaler)
|
||||
{
|
||||
AvatarScaleMod.Logger.Msg("AnimationClip-based avatar scaling detected. Disabling Universal Scaling.");
|
||||
CohtmlHud.Instance.ViewDropTextImmediate("(Local) AvatarScaleMod", "Avatar Scale Changed!",
|
||||
"Universal Scaling is now disabled in favor of built-in avatar scaling.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,367 @@
|
|||
using System.Diagnostics;
|
||||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Util.AnimatorManager;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using NAK.AvatarScaleMod.ScaledComponents;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Components;
|
||||
|
||||
[DefaultExecutionOrder(-99999)] // before playersetup/puppetmaster but after animator
|
||||
public class BaseScaler : MonoBehaviour
|
||||
{
|
||||
#region Constants
|
||||
|
||||
protected const string ScaleFactorParameterName = "ScaleFactor";
|
||||
protected const string ScaleFactorParameterNameLocal = "#" + ScaleFactorParameterName;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
// OnAnimatedHeightChanged
|
||||
public delegate void AnimatedHeightChangedDelegate(BaseScaler scaler);
|
||||
public event AnimatedHeightChangedDelegate OnAnimatedHeightChanged;
|
||||
|
||||
// OnAnimatedHeightOverride
|
||||
public delegate void AnimatedHeightOverrideDelegate(BaseScaler scaler);
|
||||
public event AnimatedHeightOverrideDelegate OnAnimatedHeightOverride;
|
||||
|
||||
// OnTargetHeightChanged
|
||||
public delegate void TargetHeightChangedDelegate(BaseScaler scaler);
|
||||
public event TargetHeightChangedDelegate OnTargetHeightChanged;
|
||||
|
||||
// OnHeightReset
|
||||
public delegate void HeightResetDelegate(BaseScaler scaler);
|
||||
public event HeightResetDelegate OnTargetHeightReset;
|
||||
|
||||
// ------------------------------------------------
|
||||
|
||||
protected void InvokeAnimatedHeightChanged()
|
||||
=> OnAnimatedHeightChanged?.Invoke(this);
|
||||
|
||||
protected void InvokeAnimatedHeightOverride()
|
||||
=> OnAnimatedHeightOverride?.Invoke(this);
|
||||
|
||||
protected void InvokeTargetHeightChanged()
|
||||
=> OnTargetHeightChanged?.Invoke(this);
|
||||
|
||||
protected void InvokeTargetHeightReset()
|
||||
=> OnTargetHeightReset?.Invoke(this);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Variables
|
||||
|
||||
private float _targetHeight;
|
||||
public float TargetHeight
|
||||
{
|
||||
get => _targetHeight;
|
||||
set
|
||||
{
|
||||
if (value < float.Epsilon)
|
||||
{
|
||||
// reset to animated height
|
||||
_targetHeight = _animatedHeight;
|
||||
_targetScale = _animatedScale;
|
||||
_scaleFactor = _animatedScaleFactor;
|
||||
_targetHeightChanged = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_targetHeight = Mathf.Clamp(value, AvatarScaleManager.MinHeight, AvatarScaleManager.MaxHeight);
|
||||
_scaleFactor = Mathf.Max(_targetHeight / _initialHeight, 0.01f); //safety
|
||||
_targetScale = _initialScale * _scaleFactor;
|
||||
_targetHeightChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool _useTargetHeight;
|
||||
public bool UseTargetHeight
|
||||
{
|
||||
get => _useTargetHeight;
|
||||
set
|
||||
{
|
||||
if (_useTargetHeight == value)
|
||||
return;
|
||||
|
||||
_useTargetHeight = value;
|
||||
_targetHeightChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Config variables
|
||||
public bool avatarIsHidden { get; set; }
|
||||
public bool overrideAnimationHeight { get; set; }
|
||||
|
||||
// State variables
|
||||
internal bool _isAvatarInstantiated;
|
||||
private bool _shouldForceHeight => _useTargetHeight || avatarIsHidden;
|
||||
private bool _targetHeightChanged;
|
||||
|
||||
// Avatar info
|
||||
internal Transform _avatarTransform;
|
||||
internal CVRAnimatorManager _animatorManager;
|
||||
|
||||
// Initial scaling
|
||||
internal float _initialHeight;
|
||||
internal Vector3 _initialScale;
|
||||
|
||||
// Forced scaling (Universal & Hidden Avatar)
|
||||
internal Vector3 _targetScale = Vector3.one;
|
||||
internal float _scaleFactor = 1f;
|
||||
|
||||
// AnimationClip-based scaling (Local Avatar)
|
||||
internal float _animatedHeight;
|
||||
internal Vector3 _animatedScale;
|
||||
internal float _animatedScaleFactor = 1f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Avatar Events
|
||||
|
||||
public virtual void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale)
|
||||
{
|
||||
if (_isAvatarInstantiated) return;
|
||||
_isAvatarInstantiated = true;
|
||||
|
||||
_initialHeight = _animatedHeight = Mathf.Clamp(initialHeight, 0.01f, 100f);
|
||||
_initialScale = _animatedScale = initialScale;
|
||||
_animatedScaleFactor = 1f;
|
||||
|
||||
if (!_shouldForceHeight) // not universal or hidden avatar
|
||||
{
|
||||
_targetHeight = _initialHeight;
|
||||
_targetScale = _initialScale;
|
||||
_scaleFactor = 1f;
|
||||
}
|
||||
|
||||
_avatarTransform = avatarObject.transform;
|
||||
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
|
||||
FindComponentsOfType(scalableComponentTypes);
|
||||
|
||||
stopwatch.Stop();
|
||||
if (ModSettings.Debug_ComponentSearchTime.Value)
|
||||
AvatarScaleMod.Logger.Msg($"({typeof(LocalScaler)}) Component search time for {avatarObject}: {stopwatch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
public void OnAvatarDestroyed()
|
||||
{
|
||||
if (!_isAvatarInstantiated) return;
|
||||
_isAvatarInstantiated = false;
|
||||
|
||||
_avatarTransform = null;
|
||||
ClearComponentLists();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public float GetInitialHeight() => _initialHeight;
|
||||
public float GetTargetHeight() => _targetHeight;
|
||||
public float GetAnimatedHeight() => _animatedHeight;
|
||||
public bool IsForcingHeight() => _shouldForceHeight;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
public bool ApplyTargetHeight()
|
||||
{
|
||||
if (!_isAvatarInstantiated || _initialHeight == 0)
|
||||
return false;
|
||||
|
||||
if (_avatarTransform == null)
|
||||
return false;
|
||||
|
||||
_targetHeightChanged = false;
|
||||
|
||||
ScaleAvatarRoot();
|
||||
UpdateAnimatorParameter();
|
||||
ApplyComponentScaling();
|
||||
|
||||
InvokeTargetHeightChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void ResetTargetHeight()
|
||||
{
|
||||
if (!_isAvatarInstantiated)
|
||||
return;
|
||||
|
||||
if (_avatarTransform == null)
|
||||
return;
|
||||
|
||||
_targetHeight = _initialHeight;
|
||||
_targetScale = _initialScale;
|
||||
_scaleFactor = 1f;
|
||||
_targetHeightChanged = false;
|
||||
|
||||
ScaleAvatarRoot();
|
||||
UpdateAnimatorParameter();
|
||||
ApplyComponentScaling();
|
||||
|
||||
InvokeTargetHeightReset();
|
||||
}
|
||||
|
||||
private void ScaleAvatarRoot()
|
||||
{
|
||||
if (_avatarTransform == null) return;
|
||||
_avatarTransform.localScale = _targetScale;
|
||||
}
|
||||
|
||||
protected virtual void UpdateAnimatorParameter()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Events
|
||||
|
||||
public virtual void LateUpdate()
|
||||
{
|
||||
if (!_isAvatarInstantiated)
|
||||
return; // no avatar
|
||||
|
||||
if (!_shouldForceHeight)
|
||||
return; // using built-in scaling
|
||||
|
||||
// called on state change
|
||||
if (_targetHeightChanged)
|
||||
{
|
||||
if (_useTargetHeight)
|
||||
ApplyTargetHeight();
|
||||
else
|
||||
ResetTargetHeight();
|
||||
_targetHeightChanged = false;
|
||||
InvokeTargetHeightChanged();
|
||||
}
|
||||
|
||||
// called constantly when forcing change
|
||||
if (_shouldForceHeight)
|
||||
ScaleAvatarRoot();
|
||||
}
|
||||
|
||||
internal virtual void OnDestroy()
|
||||
{
|
||||
ClearComponentLists();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Component Scaling
|
||||
|
||||
internal static readonly Type[] scalableComponentTypes =
|
||||
{
|
||||
typeof(Light),
|
||||
typeof(AudioSource),
|
||||
typeof(ParticleSystem),
|
||||
typeof(ParentConstraint),
|
||||
typeof(PositionConstraint),
|
||||
typeof(ScaleConstraint)
|
||||
};
|
||||
|
||||
private readonly List<ScaledLight> _scaledLights = new();
|
||||
private readonly List<ScaledAudioSource> _scaledAudioSources = new();
|
||||
private readonly List<ScaledParentConstraint> _scaledParentConstraints = new();
|
||||
private readonly List<ScaledPositionConstraint> _scaledPositionConstraints = new();
|
||||
private readonly List<ScaledScaleConstraint> _scaledScaleConstraints = new();
|
||||
|
||||
private void ClearComponentLists()
|
||||
{
|
||||
_scaledLights.Clear();
|
||||
_scaledAudioSources.Clear();
|
||||
_scaledParentConstraints.Clear();
|
||||
_scaledPositionConstraints.Clear();
|
||||
_scaledScaleConstraints.Clear();
|
||||
}
|
||||
|
||||
internal void FindComponentsOfType(Type[] types)
|
||||
{
|
||||
var components = _avatarTransform.gameObject.GetComponentsInChildren<Component>(true);
|
||||
|
||||
foreach (Component component in components)
|
||||
{
|
||||
if (this == null) break;
|
||||
if (component == null) continue;
|
||||
|
||||
Type componentType = component.GetType();
|
||||
if (types.Contains(componentType))
|
||||
AddScaledComponent(componentType, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddScaledComponent(Type type, Component component)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case not null when type == typeof(AudioSource):
|
||||
_scaledAudioSources.Add(new ScaledAudioSource((AudioSource)component));
|
||||
break;
|
||||
case not null when type == typeof(Light):
|
||||
_scaledLights.Add(new ScaledLight((Light)component));
|
||||
break;
|
||||
case not null when type == typeof(ParentConstraint):
|
||||
_scaledParentConstraints.Add(new ScaledParentConstraint((ParentConstraint)component));
|
||||
break;
|
||||
case not null when type == typeof(PositionConstraint):
|
||||
_scaledPositionConstraints.Add(new ScaledPositionConstraint((PositionConstraint)component));
|
||||
break;
|
||||
case not null when type == typeof(ScaleConstraint):
|
||||
_scaledScaleConstraints.Add(new ScaledScaleConstraint((ScaleConstraint)component));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyComponentScaling()
|
||||
{
|
||||
// UpdateLightScales(); // might break dps
|
||||
UpdateAudioSourceScales();
|
||||
UpdateParentConstraintScales();
|
||||
UpdatePositionConstraintScales();
|
||||
UpdateScaleConstraintScales();
|
||||
}
|
||||
|
||||
private void UpdateLightScales()
|
||||
{
|
||||
// Update range of each light component
|
||||
foreach (ScaledLight light in _scaledLights)
|
||||
light.Scale(_scaleFactor);
|
||||
}
|
||||
|
||||
private void UpdateAudioSourceScales()
|
||||
{
|
||||
// Update min and max distance of each audio source component
|
||||
foreach (ScaledAudioSource audioSource in _scaledAudioSources)
|
||||
audioSource.Scale(_scaleFactor);
|
||||
}
|
||||
|
||||
private void UpdateParentConstraintScales()
|
||||
{
|
||||
// Update translationAtRest and translationOffsets of each parent constraint component
|
||||
foreach (ScaledParentConstraint parentConstraint in _scaledParentConstraints)
|
||||
parentConstraint.Scale(_scaleFactor);
|
||||
}
|
||||
|
||||
private void UpdatePositionConstraintScales()
|
||||
{
|
||||
// Update translationAtRest and translationOffset of each position constraint component
|
||||
foreach (ScaledPositionConstraint positionConstraint in _scaledPositionConstraints)
|
||||
positionConstraint.Scale(_scaleFactor);
|
||||
}
|
||||
|
||||
private void UpdateScaleConstraintScales()
|
||||
{
|
||||
// Update scaleAtRest and scaleOffset of each scale constraint component
|
||||
foreach (ScaledScaleConstraint scaleConstraint in _scaledScaleConstraints)
|
||||
scaleConstraint.Scale(_scaleFactor);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
using ABI_RC.Core;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.UI;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Components;
|
||||
|
||||
public class LocalScaler : BaseScaler
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_animatorManager = GetComponentInParent<PlayerSetup>().animatorManager;
|
||||
_isAvatarInstantiated = false;
|
||||
|
||||
// listen for events
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
public override void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale)
|
||||
{
|
||||
if (avatarObject == null)
|
||||
return;
|
||||
|
||||
base.OnAvatarInstantiated(avatarObject, initialHeight, initialScale);
|
||||
}
|
||||
|
||||
protected override void UpdateAnimatorParameter()
|
||||
{
|
||||
if (_animatorManager == null)
|
||||
return;
|
||||
|
||||
_animatorManager.SetParameter(ScaleFactorParameterName, _scaleFactor);
|
||||
_animatorManager.SetParameter(ScaleFactorParameterNameLocal, _scaleFactor);
|
||||
}
|
||||
|
||||
public override void LateUpdate()
|
||||
{
|
||||
if (!CheckForAnimationScaleChange())
|
||||
base.LateUpdate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private bool CheckForAnimationScaleChange()
|
||||
{
|
||||
if (_avatarTransform == null)
|
||||
return false;
|
||||
|
||||
Vector3 localScale = _avatarTransform.localScale;
|
||||
|
||||
// scale matches last recorded animation scale
|
||||
if (localScale == _animatedScale)
|
||||
return false;
|
||||
|
||||
// avatar may not have scale animation, check if it isn't equal to targetScale
|
||||
if (localScale == _targetScale)
|
||||
return false;
|
||||
|
||||
// this is the first time we've seen the avatar animated scale, record it!
|
||||
if (_animatedScale == Vector3.zero)
|
||||
{
|
||||
_animatedScale = localScale;
|
||||
return false;
|
||||
}
|
||||
|
||||
// animation scale changed, record it!
|
||||
Vector3 scaleDifference = CVRTools.DivideVectors(localScale - _initialScale, _initialScale);
|
||||
_animatedScaleFactor = scaleDifference.y;
|
||||
_animatedHeight = (_initialHeight * _animatedScaleFactor) + _initialHeight;
|
||||
_animatedScale = localScale;
|
||||
|
||||
if (overrideAnimationHeight
|
||||
|| !_useTargetHeight)
|
||||
return false; // user has disabled animation height override or is not using universal scaling
|
||||
|
||||
// animation scale changed and now will override universal scaling
|
||||
ResetTargetHeight();
|
||||
InvokeAnimatedHeightOverride();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using System.Diagnostics;
|
||||
using ABI_RC.Core.Player;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Components;
|
||||
|
||||
public class NetworkScaler : BaseScaler
|
||||
{
|
||||
private string playerGuid;
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void Initialize(string playerId)
|
||||
{
|
||||
playerGuid = playerId;
|
||||
|
||||
_animatorManager = GetComponentInParent<PuppetMaster>().animatorManager;
|
||||
|
||||
_isAvatarInstantiated = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides
|
||||
|
||||
public override void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale)
|
||||
{
|
||||
if (avatarObject == null)
|
||||
return;
|
||||
|
||||
base.OnAvatarInstantiated(avatarObject, initialHeight, initialScale);
|
||||
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
FindComponentsOfType(scalableComponentTypes);
|
||||
stopwatch.Stop();
|
||||
if (ModSettings.Debug_ComponentSearchTime.Value)
|
||||
AvatarScaleMod.Logger.Msg($"({typeof(NetworkScaler)}) Component search time for {avatarObject}: {stopwatch.ElapsedMilliseconds}ms");
|
||||
|
||||
// TODO: why did i do this? height is never set prior to this method being called
|
||||
// if (_isHeightAdjustedFromInitial && heightNeedsUpdate)
|
||||
// UpdateScaleIfInstantiated();
|
||||
}
|
||||
|
||||
protected override void UpdateAnimatorParameter()
|
||||
{
|
||||
_animatorManager?.SetParameter(ScaleFactorParameterNameLocal, _scaleFactor);
|
||||
}
|
||||
|
||||
internal override void OnDestroy()
|
||||
{
|
||||
AvatarScaleManager.Instance.RemoveNetworkHeightScaler(playerGuid);
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
using NAK.AvatarScaleMod.Components;
|
||||
|
||||
namespace NAK.AvatarScaleMod.AvatarScaling;
|
||||
|
||||
public static class AvatarScaleEvents
|
||||
{
|
||||
#region Local Avatar Scaling Events
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the local avatar's height changes for any reason.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<LocalScaler> OnLocalAvatarHeightChanged = new();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the local avatar's animated height changes.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<LocalScaler> OnLocalAvatarAnimatedHeightChanged = new();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the local avatar's target height changes.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<LocalScaler> OnLocalAvatarTargetHeightChanged = new();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the local avatar's height is reset.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<LocalScaler> OnLocalAvatarHeightReset = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Avatar Scaling Events
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a remote avatar's height changes.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<string, NetworkScaler> OnRemoteAvatarHeightChanged = new();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a remote avatar's height is reset.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<string, NetworkScaler> OnRemoteAvatarHeightReset = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Classes
|
||||
|
||||
public class AvatarScaleEvent<T>
|
||||
{
|
||||
private Action<T> _listener = arg => { };
|
||||
|
||||
public void AddListener(Action<T> listener) => _listener += listener;
|
||||
public void RemoveListener(Action<T> listener) => _listener -= listener;
|
||||
|
||||
public void Invoke(T arg)
|
||||
{
|
||||
var invokeList = _listener.GetInvocationList();
|
||||
foreach (Delegate method in invokeList)
|
||||
{
|
||||
if (method is not Action<T> action)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
action(arg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error($"Unable to invoke listener, an exception was thrown and not handled: {e}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AvatarScaleEvent<T1, T2>
|
||||
{
|
||||
private Action<T1, T2> _listener = (arg1, arg2) => { };
|
||||
|
||||
public void AddListener(Action<T1, T2> listener) => _listener += listener;
|
||||
public void RemoveListener(Action<T1, T2> listener) => _listener -= listener;
|
||||
|
||||
public void Invoke(T1 arg1, T2 arg2)
|
||||
{
|
||||
var invokeList = _listener.GetInvocationList();
|
||||
foreach (Delegate method in invokeList)
|
||||
{
|
||||
if (method is not Action<T1, T2> action)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
action(arg1, arg2);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error($"Unable to invoke listener, an exception was thrown and not handled: {e}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
132
.Deprecated/AvatarScaleMod/AvatarScaling/ScaledComponents.cs
Normal file
132
.Deprecated/AvatarScaleMod/AvatarScaling/ScaledComponents.cs
Normal file
|
@ -0,0 +1,132 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
|
||||
namespace NAK.AvatarScaleMod.ScaledComponents;
|
||||
|
||||
public class ScaledAudioSource
|
||||
{
|
||||
private readonly AudioSource Component;
|
||||
private readonly float InitialMinDistance;
|
||||
private readonly float InitialMaxDistance;
|
||||
|
||||
public ScaledAudioSource(AudioSource component)
|
||||
{
|
||||
Component = component;
|
||||
InitialMinDistance = component.minDistance;
|
||||
InitialMaxDistance = component.maxDistance;
|
||||
}
|
||||
|
||||
public void Scale(float scaleFactor)
|
||||
{
|
||||
Component.minDistance = InitialMinDistance * scaleFactor;
|
||||
Component.maxDistance = InitialMaxDistance * scaleFactor;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Component.minDistance = InitialMinDistance;
|
||||
Component.maxDistance = InitialMaxDistance;
|
||||
}
|
||||
}
|
||||
|
||||
public class ScaledLight
|
||||
{
|
||||
private readonly Light Component;
|
||||
private readonly float InitialRange;
|
||||
|
||||
public ScaledLight(Light component)
|
||||
{
|
||||
Component = component;
|
||||
InitialRange = component.range;
|
||||
}
|
||||
|
||||
public void Scale(float scaleFactor)
|
||||
{
|
||||
Component.range = InitialRange * scaleFactor;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Component.range = InitialRange;
|
||||
}
|
||||
}
|
||||
|
||||
public class ScaledPositionConstraint
|
||||
{
|
||||
private readonly PositionConstraint Component;
|
||||
private readonly Vector3 InitialTranslationAtRest;
|
||||
private readonly Vector3 InitialTranslationOffset;
|
||||
|
||||
public ScaledPositionConstraint(PositionConstraint component)
|
||||
{
|
||||
Component = component;
|
||||
InitialTranslationAtRest = component.translationAtRest;
|
||||
InitialTranslationOffset = component.translationOffset;
|
||||
}
|
||||
|
||||
public void Scale(float scaleFactor)
|
||||
{
|
||||
Component.translationAtRest = InitialTranslationAtRest * scaleFactor;
|
||||
Component.translationOffset = InitialTranslationOffset * scaleFactor;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Component.translationAtRest = InitialTranslationAtRest;
|
||||
Component.translationOffset = InitialTranslationOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public class ScaledParentConstraint
|
||||
{
|
||||
private readonly ParentConstraint Component;
|
||||
private readonly Vector3 InitialTranslationAtRest;
|
||||
private readonly List<Vector3> InitialTranslationOffsets;
|
||||
|
||||
public ScaledParentConstraint(ParentConstraint component)
|
||||
{
|
||||
Component = component;
|
||||
InitialTranslationAtRest = component.translationAtRest;
|
||||
InitialTranslationOffsets = component.translationOffsets.ToList();
|
||||
}
|
||||
|
||||
public void Scale(float scaleFactor)
|
||||
{
|
||||
Component.translationAtRest = InitialTranslationAtRest * scaleFactor;
|
||||
for (int i = 0; i < InitialTranslationOffsets.Count; i++)
|
||||
Component.translationOffsets[i] = InitialTranslationOffsets[i] * scaleFactor;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Component.translationAtRest = InitialTranslationAtRest;
|
||||
for (int i = 0; i < InitialTranslationOffsets.Count; i++)
|
||||
Component.translationOffsets[i] = InitialTranslationOffsets[i];
|
||||
}
|
||||
}
|
||||
|
||||
public class ScaledScaleConstraint
|
||||
{
|
||||
private readonly ScaleConstraint Component;
|
||||
private readonly Vector3 InitialScaleAtRest;
|
||||
private readonly Vector3 InitialScaleOffset;
|
||||
|
||||
public ScaledScaleConstraint(ScaleConstraint component)
|
||||
{
|
||||
Component = component;
|
||||
InitialScaleAtRest = component.scaleAtRest;
|
||||
InitialScaleOffset = component.scaleOffset;
|
||||
}
|
||||
|
||||
public void Scale(float scaleFactor)
|
||||
{
|
||||
Component.scaleAtRest = InitialScaleAtRest * scaleFactor;
|
||||
// Component.scaleOffset = InitialScaleOffset * scaleFactor;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Component.scaleAtRest = InitialScaleAtRest;
|
||||
Component.scaleOffset = InitialScaleOffset;
|
||||
}
|
||||
}
|
113
.Deprecated/AvatarScaleMod/HarmonyPatches.cs
Normal file
113
.Deprecated/AvatarScaleMod/HarmonyPatches.cs
Normal file
|
@ -0,0 +1,113 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using HarmonyLib;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using NAK.AvatarScaleMod.GestureReconizer;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace NAK.AvatarScaleMod.HarmonyPatches;
|
||||
|
||||
internal class PlayerSetupPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
|
||||
private static void Postfix_PlayerSetup_Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
GameObject scaleManager = new (nameof(AvatarScaleManager), typeof(AvatarScaleManager));
|
||||
Object.DontDestroyOnLoad(scaleManager);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_Start)}");
|
||||
AvatarScaleMod.Logger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupAvatar))]
|
||||
private static void Postfix_PlayerSetup_SetupAvatar(ref PlayerSetup __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
AvatarScaleManager.Instance.OnAvatarInstantiated(__instance);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_SetupAvatar)}");
|
||||
AvatarScaleMod.Logger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.ClearAvatar))]
|
||||
private static void Postfix_PlayerSetup_ClearAvatar(ref PlayerSetup __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (__instance == null) return; // this is called when the game is closed
|
||||
AvatarScaleManager.Instance.OnAvatarDestroyed(__instance);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_ClearAvatar)}");
|
||||
AvatarScaleMod.Logger.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class PuppetMasterPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.AvatarInstantiated))]
|
||||
private static void Postfix_PuppetMaster_AvatarInstantiated(ref PuppetMaster __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
AvatarScaleManager.Instance.OnNetworkAvatarInstantiated(__instance);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error(
|
||||
$"Error during the patched method {nameof(Postfix_PuppetMaster_AvatarInstantiated)}");
|
||||
AvatarScaleMod.Logger.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.AvatarDestroyed))]
|
||||
private static void Postfix_PuppetMaster_AvatarDestroyed(ref PuppetMaster __instance)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (__instance == null) return; // this is called when the game is closed
|
||||
AvatarScaleManager.Instance.OnNetworkAvatarDestroyed(__instance);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error(
|
||||
$"Error during the patched method {nameof(Postfix_PuppetMaster_AvatarDestroyed)}");
|
||||
AvatarScaleMod.Logger.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class GesturePlaneTestPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(GesturePlaneTest), nameof(GesturePlaneTest.Start))]
|
||||
private static void Postfix_GesturePlaneTest_Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
// nicked from Kafe >:))))
|
||||
ScaleReconizer.Initialize();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_GesturePlaneTest_Start)}");
|
||||
AvatarScaleMod.Logger.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
43
.Deprecated/AvatarScaleMod/Input/DebugKeybinds.cs
Normal file
43
.Deprecated/AvatarScaleMod/Input/DebugKeybinds.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.InputHandling;
|
||||
|
||||
internal static class DebugKeybinds
|
||||
{
|
||||
private const float Step = 0.1f;
|
||||
|
||||
internal static void DoDebugInput()
|
||||
{
|
||||
if (AvatarScaleManager.Instance == null)
|
||||
return;
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.Equals) || Input.GetKeyDown(KeyCode.KeypadPlus))
|
||||
{
|
||||
AdjustHeight(Step);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Minus) || Input.GetKeyDown(KeyCode.KeypadMinus))
|
||||
{
|
||||
AdjustHeight(-Step);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Backspace))
|
||||
{
|
||||
ResetHeight();
|
||||
}
|
||||
}
|
||||
|
||||
private static void AdjustHeight(float adjustment)
|
||||
{
|
||||
float currentHeight = AvatarScaleManager.Instance.GetHeight() + adjustment;
|
||||
currentHeight = Mathf.Max(0f, currentHeight);
|
||||
AvatarScaleManager.Instance.SetTargetHeight(currentHeight);
|
||||
|
||||
AvatarScaleMod.Logger.Msg($"[Debug] Setting height: {currentHeight}");
|
||||
}
|
||||
|
||||
private static void ResetHeight()
|
||||
{
|
||||
AvatarScaleManager.Instance.Setting_UniversalScaling = false;
|
||||
AvatarScaleMod.Logger.Msg("[Debug] Resetting height.");
|
||||
}
|
||||
}
|
108
.Deprecated/AvatarScaleMod/Input/ScaleReconizer.cs
Normal file
108
.Deprecated/AvatarScaleMod/Input/ScaleReconizer.cs
Normal file
|
@ -0,0 +1,108 @@
|
|||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.GestureReconizer;
|
||||
|
||||
public static class ScaleReconizer
|
||||
{
|
||||
public static bool Enabled = true;
|
||||
|
||||
// Require triggers to be down while doing fist - Exteratta
|
||||
public static bool RequireTriggers = true;
|
||||
|
||||
// Initial values when scale gesture is started
|
||||
private static float _initialModifier;
|
||||
private static float _initialTargetHeight;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
// This requires arms far outward- pull inward with fist and triggers.
|
||||
// Release triggers while still holding fist to readjust.
|
||||
|
||||
CVRGesture gesture = new CVRGesture
|
||||
{
|
||||
name = "avatarScaleIn",
|
||||
type = CVRGesture.GestureType.Hold
|
||||
};
|
||||
gesture.steps.Add(new CVRGestureStep
|
||||
{
|
||||
firstGesture = CVRGestureStep.Gesture.Fist,
|
||||
secondGesture = CVRGestureStep.Gesture.Fist,
|
||||
startDistance = 1f,
|
||||
endDistance = 0.25f,
|
||||
direction = CVRGestureStep.GestureDirection.MovingIn,
|
||||
needsToBeInView = true,
|
||||
});
|
||||
gesture.onStart.AddListener(OnScaleStart);
|
||||
gesture.onStay.AddListener(OnScaleStay);
|
||||
gesture.onEnd.AddListener(OnScaleEnd);
|
||||
CVRGestureRecognizer.Instance.gestures.Add(gesture);
|
||||
|
||||
gesture = new CVRGesture
|
||||
{
|
||||
name = "avatarScaleOut",
|
||||
type = CVRGesture.GestureType.Hold
|
||||
};
|
||||
gesture.steps.Add(new CVRGestureStep
|
||||
{
|
||||
firstGesture = CVRGestureStep.Gesture.Fist,
|
||||
secondGesture = CVRGestureStep.Gesture.Fist,
|
||||
startDistance = 0.25f,
|
||||
endDistance = 1f,
|
||||
direction = CVRGestureStep.GestureDirection.MovingOut,
|
||||
needsToBeInView = true,
|
||||
});
|
||||
gesture.onStart.AddListener(OnScaleStart);
|
||||
gesture.onStay.AddListener(OnScaleStay);
|
||||
gesture.onEnd.AddListener(OnScaleEnd);
|
||||
CVRGestureRecognizer.Instance.gestures.Add(gesture);
|
||||
}
|
||||
|
||||
private static void OnScaleStart(float modifier, Transform transform1, Transform transform2)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
// Store initial modifier so we can get difference later
|
||||
_initialModifier = Mathf.Max(modifier, 0.01f); // no zero
|
||||
_initialTargetHeight = AvatarScaleManager.Instance.GetHeight();
|
||||
}
|
||||
|
||||
private static void OnScaleStay(float modifier, Transform transform1, Transform transform2)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
modifier = Mathf.Max(modifier, 0.01f); // no zero
|
||||
|
||||
// Allow user to release triggers to reset "world grip"
|
||||
if (RequireTriggers && !AreBothTriggersDown())
|
||||
{
|
||||
_initialModifier = modifier;
|
||||
_initialTargetHeight = AvatarScaleManager.Instance.GetHeight();
|
||||
return;
|
||||
}
|
||||
|
||||
// Invert so the gesture is more of a world squish instead of happy hug
|
||||
float modifierRatio = 1f / (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.Instance.SetTargetHeight(_initialTargetHeight * heightAdjustmentFactor);
|
||||
}
|
||||
|
||||
private static void OnScaleEnd(float modifier, Transform transform1, Transform transform2)
|
||||
{
|
||||
// Unused, needed for mod network?
|
||||
}
|
||||
|
||||
private static bool AreBothTriggersDown()
|
||||
{
|
||||
// Maybe it should be one trigger? Imagine XSOverlay scaling but for player.
|
||||
return CVRInputManager.Instance.interactLeftValue > 0.75f && CVRInputManager.Instance.interactRightValue > 0.75f;
|
||||
}
|
||||
}
|
99
.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs
Normal file
99
.Deprecated/AvatarScaleMod/Integrations/BTKUI/BtkUiAddon.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations;
|
||||
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static Page _asmRootPage;
|
||||
private static string _rootPageElementID;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Prepare_Icons();
|
||||
Setup_AvatarScaleModTab();
|
||||
Setup_PlayerSelectPage();
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
private static void Prepare_Icons()
|
||||
{
|
||||
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightConfig",
|
||||
GetIconStream("ASM_Icon_AvatarHeightConfig.png"));
|
||||
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightCopy",
|
||||
GetIconStream("ASM_Icon_AvatarHeightCopy.png"));
|
||||
}
|
||||
|
||||
private static void Setup_AvatarScaleModTab()
|
||||
{
|
||||
_asmRootPage = new Page(ModSettings.ModName, ModSettings.ASM_SettingsCategory, true, "ASM_Icon_AvatarHeightConfig")
|
||||
{
|
||||
MenuTitle = ModSettings.ASM_SettingsCategory,
|
||||
MenuSubtitle = "Everything Avatar Scaling!"
|
||||
};
|
||||
|
||||
_rootPageElementID = _asmRootPage.ElementID;
|
||||
QuickMenuAPI.OnTabChange += OnTabChange;
|
||||
QuickMenuAPI.UserJoin += OnUserJoinLeave;
|
||||
QuickMenuAPI.UserLeave += OnUserJoinLeave;
|
||||
QuickMenuAPI.OnWorldLeave += OnWorldLeave;
|
||||
|
||||
// Avatar Scale Mod
|
||||
Setup_AvatarScaleModCategory(_asmRootPage);
|
||||
|
||||
// Avatar Scale Tool
|
||||
Setup_AvatarScaleToolCategory(_asmRootPage);
|
||||
|
||||
// Universal Scaling Settings
|
||||
Setup_UniversalScalingSettings(_asmRootPage);
|
||||
|
||||
// Debug Options
|
||||
Setup_DebugOptionsCategory(_asmRootPage);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Player Count Display
|
||||
|
||||
private static void OnWorldLeave()
|
||||
=> UpdatePlayerCountDisplay();
|
||||
|
||||
private static void OnUserJoinLeave(CVRPlayerEntity _)
|
||||
=> UpdatePlayerCountDisplay();
|
||||
|
||||
private static void UpdatePlayerCountDisplay()
|
||||
{
|
||||
if (_asmRootPage == null)
|
||||
return;
|
||||
|
||||
int modUserCount = AvatarScaleManager.Instance.GetNetworkHeightScalerCount();
|
||||
int playerCount = CVRPlayerManager.Instance.NetworkPlayers.Count;
|
||||
_asmRootPage.MenuSubtitle = $"Everything Avatar Scaling! :: ({modUserCount}/{playerCount} players using ASM)";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Double-Click Reset Height
|
||||
|
||||
private static DateTime lastTime = DateTime.Now;
|
||||
|
||||
private static void OnTabChange(string newTab, string previousTab)
|
||||
{
|
||||
if (newTab == _rootPageElementID)
|
||||
{
|
||||
UpdatePlayerCountDisplay();
|
||||
TimeSpan timeDifference = DateTime.Now - lastTime;
|
||||
if (timeDifference.TotalSeconds <= 0.5)
|
||||
{
|
||||
AvatarScaleManager.Instance.Setting_UniversalScaling = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
lastTime = DateTime.Now;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using BTKUILib.UIObjects;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations;
|
||||
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static void Setup_AvatarScaleModCategory(Page page)
|
||||
{
|
||||
Category avScaleModCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_ASM_SettingsCategory);
|
||||
|
||||
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryScaleGestureEnabled);
|
||||
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryScaleKeybindingsEnabled);
|
||||
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryPersistentHeight);
|
||||
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryPersistThroughRestart);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations;
|
||||
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static void Setup_AvatarScaleToolCategory(Page page)
|
||||
{
|
||||
Category avScaleToolCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_AST_SettingsCategory);
|
||||
|
||||
AddMelonStringInput(ref avScaleToolCategory, ModSettings.EntryASTScaleParameter, "icon");
|
||||
AddMelonNumberInput(ref avScaleToolCategory, ModSettings.EntryASTMinHeight, "icon");
|
||||
AddMelonNumberInput(ref avScaleToolCategory, ModSettings.EntryASTMaxHeight, "icon");
|
||||
avScaleToolCategory.AddButton("Reset Overrides", "", "Reset to Avatar Scale Tool default values.", ButtonStyle.TextOnly)
|
||||
.OnPress += () =>{
|
||||
ModSettings.EntryASTScaleParameter.ResetToDefault();
|
||||
ModSettings.EntryASTMinHeight.ResetToDefault();
|
||||
ModSettings.EntryASTMaxHeight.ResetToDefault();
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using BTKUILib.UIObjects;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations;
|
||||
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static void Setup_DebugOptionsCategory(Page page)
|
||||
{
|
||||
Category debugCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_DEBUG_SettingsCategory);
|
||||
|
||||
AddMelonToggle(ref debugCategory, ModSettings.Debug_NetworkInbound);
|
||||
AddMelonToggle(ref debugCategory, ModSettings.Debug_NetworkOutbound);
|
||||
AddMelonToggle(ref debugCategory, ModSettings.Debug_ComponentSearchTime);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using System.Collections.Generic; // Added for list support
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations;
|
||||
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static readonly List<QMUIElement> USM_QmUiElements = new();
|
||||
|
||||
private static void Setup_UniversalScalingSettings(Page page)
|
||||
{
|
||||
Category uniScalingCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_USM_SettingsCategory);
|
||||
|
||||
SliderFloat scaleSlider = AddMelonSlider(ref uniScalingCategory, ModSettings.EntryHiddenAvatarHeight, AvatarScaleManager.DefaultMinHeight, AvatarScaleManager.DefaultMaxHeight);
|
||||
|
||||
Button resetHeightButton = uniScalingCategory.AddButton(ModSettings.EntryUseUniversalScaling.DisplayName, "icon", ModSettings.EntryUseUniversalScaling.Description, ButtonStyle.TextOnly);
|
||||
resetHeightButton.OnPress += () =>
|
||||
{
|
||||
if (ModSettings.EntryUseUniversalScaling.Value)
|
||||
{
|
||||
ModSettings.EntryUseUniversalScaling.Value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
QuickMenuAPI.ShowConfirm("Use Universal Scaling?",
|
||||
"Universal scaling only works when other users also have the mod! Are you sure you want to use Universal Scaling?",
|
||||
() => ModSettings.EntryUseUniversalScaling.Value = true);
|
||||
};
|
||||
|
||||
// Elements that should be disabled when universal scaling is disabled
|
||||
USM_QmUiElements.AddRange(new QMUIElement[]
|
||||
{
|
||||
scaleSlider,
|
||||
AddMelonToggle(ref uniScalingCategory, ModSettings.EntryScaleComponents),
|
||||
AddMelonToggle(ref uniScalingCategory, ModSettings.EntryAnimationScalingOverride)
|
||||
});
|
||||
|
||||
// Events for the slider
|
||||
scaleSlider.OnValueUpdated += OnAvatarHeightSliderChanged;
|
||||
scaleSlider.OnSliderReset += OnAvatarHeightSliderReset;
|
||||
AvatarScaleEvents.OnLocalAvatarAnimatedHeightChanged.AddListener((scaler) =>
|
||||
{
|
||||
scaleSlider.SetSliderValue(scaler.GetTargetHeight());
|
||||
scaleSlider.DefaultValue = scaler.GetAnimatedHeight();
|
||||
});
|
||||
|
||||
// Initial values
|
||||
OnUniversalScalingChanged(ModSettings.EntryUseUniversalScaling.Value);
|
||||
ModSettings.EntryUseUniversalScaling.OnEntryValueChanged.Subscribe((_, newValue) => OnUniversalScalingChanged(newValue));
|
||||
}
|
||||
|
||||
private static void OnUniversalScalingChanged(bool value)
|
||||
{
|
||||
foreach (QMUIElement uiElement in USM_QmUiElements)
|
||||
uiElement.Disabled = !value;
|
||||
}
|
||||
|
||||
#region Slider Events
|
||||
|
||||
private static void OnAvatarHeightSliderChanged(float height)
|
||||
{
|
||||
AvatarScaleManager.Instance.SetTargetHeight(height);
|
||||
}
|
||||
|
||||
private static void OnAvatarHeightSliderReset()
|
||||
{
|
||||
AvatarScaleManager.Instance.Setting_UniversalScaling = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations;
|
||||
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static Button _playerHasModElement;
|
||||
private static string _selectedPlayer;
|
||||
|
||||
private static void Setup_PlayerSelectPage()
|
||||
{
|
||||
QuickMenuAPI.OnPlayerSelected += OnPlayerSelected;
|
||||
|
||||
Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ModSettings.ASM_SettingsCategory, ModSettings.ModName);
|
||||
|
||||
_playerHasModElement = category.AddButton("PLAYER_HAS_MOD", "ASM_Icon_AvatarHeightCopy", "PLAYER_HAS_MOD_TOOLTIP");
|
||||
|
||||
Button button = category.AddButton("Copy Height", "ASM_Icon_AvatarHeightCopy", "Copy selected players Eye Height.");
|
||||
button.OnPress += OnCopyPlayerHeight;
|
||||
}
|
||||
|
||||
#region QM Events
|
||||
|
||||
private static void OnPlayerSelected(string _, string id)
|
||||
{
|
||||
_selectedPlayer = id;
|
||||
UpdatePlayerHasModIcon();
|
||||
}
|
||||
|
||||
private static void OnCopyPlayerHeight()
|
||||
{
|
||||
float networkHeight = AvatarScaleManager.Instance.GetNetworkHeight(_selectedPlayer);
|
||||
if (networkHeight < 0) return;
|
||||
AvatarScaleManager.Instance.SetTargetHeight(networkHeight);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static void UpdatePlayerHasModIcon()
|
||||
{
|
||||
if (_playerHasModElement == null)
|
||||
return;
|
||||
|
||||
if (AvatarScaleManager.Instance.DoesNetworkHeightScalerExist(_selectedPlayer))
|
||||
{
|
||||
_playerHasModElement.ButtonIcon = "ASM_Icon_AvatarHeightCopy";
|
||||
_playerHasModElement.ButtonText = "Player Has Mod";
|
||||
_playerHasModElement.ButtonTooltip = "This player has the Avatar Scale Mod installed!";
|
||||
}
|
||||
else
|
||||
{
|
||||
_playerHasModElement.ButtonIcon = "ASM_Icon_AvatarHeightConfig";
|
||||
_playerHasModElement.ButtonText = "Player Does Not Have Mod";
|
||||
_playerHasModElement.ButtonTooltip = "This player does not have the Avatar Scale Mod installed!";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
using System.Reflection;
|
||||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations;
|
||||
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
#region Melon Preference Helpers
|
||||
|
||||
private static ToggleButton AddMelonToggle(ref Category category, MelonPreferences_Entry<bool> entry)
|
||||
{
|
||||
ToggleButton toggle = category.AddToggle(entry.DisplayName, entry.Description, entry.Value);
|
||||
toggle.OnValueUpdated += b => entry.Value = b;
|
||||
return toggle;
|
||||
}
|
||||
|
||||
private static SliderFloat AddMelonSlider(ref Category category, MelonPreferences_Entry<float> entry, float min,
|
||||
float max, int decimalPlaces = 2, bool allowReset = true)
|
||||
{
|
||||
SliderFloat slider = category.AddSlider(entry.DisplayName, entry.Description,
|
||||
Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset);
|
||||
slider.OnValueUpdated += f => entry.Value = f;
|
||||
return slider;
|
||||
}
|
||||
|
||||
private static Button AddMelonStringInput(ref Category category, MelonPreferences_Entry<string> entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly)
|
||||
{
|
||||
Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle);
|
||||
button.OnPress += () => QuickMenuAPI.OpenKeyboard(entry.Value, s => entry.Value = s);
|
||||
return button;
|
||||
}
|
||||
|
||||
private static Button AddMelonNumberInput(ref Category category, MelonPreferences_Entry<float> entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly)
|
||||
{
|
||||
Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle);
|
||||
button.OnPress += () => QuickMenuAPI.OpenNumberInput(entry.DisplayName, entry.Value, f => entry.Value = f);
|
||||
return button;
|
||||
}
|
||||
|
||||
// private static SliderFloat AddMelonSlider(ref Page page, MelonPreferences_Entry<float> entry, float min, float max, int decimalPlaces = 2, bool allowReset = true)
|
||||
// {
|
||||
// SliderFloat slider = page.AddSlider(entry.DisplayName, entry.Description, Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset);
|
||||
// slider.OnValueUpdated += f => entry.Value = f;
|
||||
// return slider;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a category that saves its collapsed state to a MelonPreferences entry.
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
/// <param name="entry"></param>
|
||||
/// <param name="showHeader"></param>
|
||||
/// <returns></returns>
|
||||
private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry<bool> entry, bool showHeader = true)
|
||||
{
|
||||
Category category = page.AddCategory(entry.DisplayName, showHeader, true, entry.Value);
|
||||
category.OnCollapse += b => entry.Value = b;
|
||||
return category;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Icon Utils
|
||||
|
||||
private static Stream GetIconStream(string iconName)
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string assemblyName = assembly.GetName().Name;
|
||||
return assembly.GetManifestResourceStream($"{assemblyName}.resources.{iconName}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
52
.Deprecated/AvatarScaleMod/Main.cs
Normal file
52
.Deprecated/AvatarScaleMod/Main.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using MelonLoader;
|
||||
using NAK.AvatarScaleMod.InputHandling;
|
||||
using NAK.AvatarScaleMod.Networking;
|
||||
|
||||
namespace NAK.AvatarScaleMod;
|
||||
|
||||
public class AvatarScaleMod : MelonMod
|
||||
{
|
||||
internal static MelonLogger.Instance Logger;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
|
||||
ModNetwork.Subscribe();
|
||||
ModSettings.Initialize();
|
||||
|
||||
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
|
||||
ApplyPatches(typeof(HarmonyPatches.PuppetMasterPatches));
|
||||
ApplyPatches(typeof(HarmonyPatches.GesturePlaneTestPatches));
|
||||
|
||||
InitializeIntegration("BTKUILib", Integrations.BtkUiAddon.Initialize);
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
ModNetwork.Update();
|
||||
DebugKeybinds.DoDebugInput();
|
||||
}
|
||||
|
||||
private static void InitializeIntegration(string modName, Action integrationAction)
|
||||
{
|
||||
if (RegisteredMelons.All(it => it.Info.Name != modName))
|
||||
return;
|
||||
|
||||
Logger.Msg($"Initializing {modName} integration.");
|
||||
integrationAction.Invoke();
|
||||
}
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
HarmonyInstance.PatchAll(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||
LoggerInstance.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
122
.Deprecated/AvatarScaleMod/ModSettings.cs
Normal file
122
.Deprecated/AvatarScaleMod/ModSettings.cs
Normal file
|
@ -0,0 +1,122 @@
|
|||
using MelonLoader;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using NAK.AvatarScaleMod.Networking;
|
||||
|
||||
namespace NAK.AvatarScaleMod;
|
||||
|
||||
// i like this
|
||||
|
||||
internal static class ModSettings
|
||||
{
|
||||
// Constants
|
||||
internal const string ModName = nameof(AvatarScaleMod);
|
||||
internal const string ASM_SettingsCategory = "Avatar Scale Mod";
|
||||
internal const string AST_SettingsCategory = "Avatar Scale Tool Support";
|
||||
internal const string USM_SettingsCategory = "Universal Scaling (Mod Network)";
|
||||
internal const string DEBUG_SettingsCategory = "Debug Options";
|
||||
|
||||
private static readonly MelonPreferences_Category Category =
|
||||
MelonPreferences.CreateCategory(ModName);
|
||||
|
||||
#region Hidden Foldout Entries
|
||||
|
||||
// Avatar Scale Mod Foldout
|
||||
internal static readonly MelonPreferences_Entry<bool> Hidden_Foldout_ASM_SettingsCategory =
|
||||
Category.CreateEntry("hidden_foldout_asm", true, is_hidden: true, display_name: ASM_SettingsCategory, description: "Foldout state for Avatar Scale Mod settings.");
|
||||
|
||||
// Avatar Scale Tool Foldout
|
||||
internal static readonly MelonPreferences_Entry<bool> Hidden_Foldout_AST_SettingsCategory =
|
||||
Category.CreateEntry("hidden_foldout_ast", false, is_hidden: true, display_name: AST_SettingsCategory, description: "Foldout state for Avatar Scale Tool settings.");
|
||||
|
||||
// Universal Scaling (Mod Network) Foldout
|
||||
internal static readonly MelonPreferences_Entry<bool> Hidden_Foldout_USM_SettingsCategory =
|
||||
Category.CreateEntry("hidden_foldout_usm", false, is_hidden: true, display_name: USM_SettingsCategory, description: "Foldout state for Universal Scaling (Mod Network) settings.");
|
||||
|
||||
// Debug Options Foldout
|
||||
internal static readonly MelonPreferences_Entry<bool> Hidden_Foldout_DEBUG_SettingsCategory =
|
||||
Category.CreateEntry("hidden_foldout_debug", false, is_hidden: true, display_name: DEBUG_SettingsCategory, description: "Foldout state for Debug Options settings.");
|
||||
|
||||
// Player Select Page Foldout
|
||||
internal static readonly MelonPreferences_Entry<bool> Hidden_Foldout_PlayerSelectPage =
|
||||
Category.CreateEntry("hidden_foldout_player_select_page", true, is_hidden: true, display_name: ASM_SettingsCategory, description: "Foldout state for Player Select Page.");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Avatar Scale Mod Settings
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryScaleGestureEnabled =
|
||||
Category.CreateEntry("scale_gesture_enabled", true, display_name: "Scale Gesture", description: "Enable or disable scale gesture.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryScaleKeybindingsEnabled =
|
||||
Category.CreateEntry("scale_keybindings_enabled", true, display_name: "Scale Keybindings", description: "Enable or disable scale keybindings.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryPersistentHeight =
|
||||
Category.CreateEntry("persistent_height", false, display_name: "Persistent Height", description: "Should the avatar height persist between avatar switches?");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryPersistThroughRestart =
|
||||
Category.CreateEntry("persistent_height_through_restart", false, display_name: "Persist Through Restart", description: "Should the avatar height persist between game restarts?");
|
||||
|
||||
// stores the last avatar height as a melon pref
|
||||
public static readonly MelonPreferences_Entry<float> EntryHiddenAvatarHeight =
|
||||
Category.CreateEntry("hidden_avatar_height", -2f, is_hidden: true, display_name: "Avatar Height", description: "Set your avatar height.");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Avatar Scale Tool Settings
|
||||
|
||||
public static readonly MelonPreferences_Entry<string> EntryASTScaleParameter =
|
||||
Category.CreateEntry("override_scale_parameter", "AvatarScale", display_name: "Override Scale Parameter", description: "Override the scale parameter on the avatar.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<float> EntryASTMinHeight =
|
||||
Category.CreateEntry("override_min_height", 0.25f, display_name: "Override Min Height", description: "Override the minimum height.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<float> EntryASTMaxHeight =
|
||||
Category.CreateEntry("override_max_height", 2.5f, display_name: "Override Max Height", description: "Override the maximum height.");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Universal Scaling Settings
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryUseUniversalScaling =
|
||||
Category.CreateEntry("use_universal_scaling", false, display_name: "Use Universal Scaling", description: "Enable or disable universal scaling. This allows scaling to work on any avatar as well as networking via Mod Network.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryScaleComponents =
|
||||
Category.CreateEntry("scale_components", true, display_name: "Scale Components", description: "Scale components on the avatar. (Constraints, Audio Sources, etc.)");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryAnimationScalingOverride =
|
||||
Category.CreateEntry("allow_anim_clip_scale_override", true, display_name: "Animation-Clip Scaling Override", description: "Allow animation-clip scaling to override universal scaling.");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Settings
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> Debug_NetworkInbound =
|
||||
Category.CreateEntry("debug_inbound", false, display_name: "Debug Inbound", description: "Log inbound Mod Network height updates.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> Debug_NetworkOutbound =
|
||||
Category.CreateEntry("debug_outbound", false, display_name: "Debug Outbound", description: "Log outbound Mod Network height updates.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> Debug_ComponentSearchTime =
|
||||
Category.CreateEntry("debug_component_search_time", false, display_name: "Debug Search Time", description: "Log component search time.");
|
||||
|
||||
#endregion
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
// subscribe to all bool settings that aren't hidden
|
||||
foreach (MelonPreferences_Entry entry in Category.Entries.Where(entry => entry.GetReflectedType() == typeof(bool) && !entry.IsHidden))
|
||||
entry.OnEntryValueChangedUntyped.Subscribe(OnSettingsBoolChanged);
|
||||
}
|
||||
|
||||
private static void OnSettingsBoolChanged(object _, object __)
|
||||
{
|
||||
GestureReconizer.ScaleReconizer.Enabled = EntryScaleGestureEnabled.Value;
|
||||
ModNetwork.Debug_NetworkInbound = Debug_NetworkInbound.Value;
|
||||
ModNetwork.Debug_NetworkOutbound = Debug_NetworkOutbound.Value;
|
||||
|
||||
if (AvatarScaleManager.Instance == null) return;
|
||||
AvatarScaleManager.Instance.Setting_UniversalScaling = EntryUseUniversalScaling.Value;
|
||||
AvatarScaleManager.Instance.Setting_AnimationClipScalingOverride = EntryAnimationScalingOverride.Value;
|
||||
AvatarScaleManager.Instance.Setting_PersistentHeight = EntryPersistentHeight.Value;
|
||||
}
|
||||
}
|
283
.Deprecated/AvatarScaleMod/Networking/ModNetwork.cs
Normal file
283
.Deprecated/AvatarScaleMod/Networking/ModNetwork.cs
Normal file
|
@ -0,0 +1,283 @@
|
|||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Systems.ModNetwork;
|
||||
using DarkRift;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Networking;
|
||||
|
||||
// overcomplicated, but functional
|
||||
// a
|
||||
|
||||
public static class ModNetwork
|
||||
{
|
||||
public static bool Debug_NetworkInbound = false;
|
||||
public static bool Debug_NetworkOutbound = false;
|
||||
|
||||
private static bool _isSubscribedToModNetwork;
|
||||
|
||||
#region Constants
|
||||
|
||||
private const string ModId = "MelonMod.NAK.AvatarScaleMod";
|
||||
private const float SendRateLimit = 0.25f;
|
||||
private const float ReceiveRateLimit = 0.2f;
|
||||
private const int MaxWarnings = 2;
|
||||
private const float TimeoutDuration = 10f;
|
||||
|
||||
private class QueuedMessage
|
||||
{
|
||||
public MessageType Type { get; set; }
|
||||
public float Height { get; set; }
|
||||
public string TargetPlayer { get; set; }
|
||||
public string Sender { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private State
|
||||
|
||||
private static readonly Dictionary<string, QueuedMessage> OutboundQueue = new();
|
||||
private static float LastSentTime;
|
||||
|
||||
private static readonly Dictionary<string, QueuedMessage> InboundQueue = new();
|
||||
private static readonly Dictionary<string, float> LastReceivedTimes = new();
|
||||
|
||||
private static readonly Dictionary<string, int> UserWarnings = new();
|
||||
private static readonly Dictionary<string, float> UserTimeouts = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enums
|
||||
|
||||
private enum MessageType : byte
|
||||
{
|
||||
SyncHeight = 0, // just send height
|
||||
RequestHeight = 1 // send height, request height back
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Mod Network Internals
|
||||
|
||||
internal static void Subscribe()
|
||||
{
|
||||
ModNetworkManager.Subscribe(ModId, OnMessageReceived);
|
||||
|
||||
_isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId);
|
||||
if (!_isSubscribedToModNetwork)
|
||||
AvatarScaleMod.Logger.Error("Failed to subscribe to Mod Network! This is a critical error! Please report this to the mod author! (NAK) (ModNetwork.cs) (Subscribe) (Line 150) (AvatarScaleMod) (AvatarScale) (MelonLoader) (MelonLoader.Mods) (MelonLoader.Mods.MelonMod) (MelonLoader.MelonMod) (MelonLoader.MelonMod.MelonBaseMod) (MelonLoader.MelonMod.MelonMod) (MelonLoader.MelonMod.MelonModBase) (MelonLoader.MelonMod.MelonModBase`1) (MelonLoader.MelonMod.MelonModBase`1[[NAK.AvatarScaleMod.AvatarScaling.AvatarScaleMod, NAK.AvatarScaleMod, Version=123");
|
||||
|
||||
AvatarScaleEvents.OnLocalAvatarHeightChanged.AddListener(scaler => SendNetworkHeight(scaler.GetTargetHeight()));
|
||||
}
|
||||
|
||||
internal static void Update()
|
||||
{
|
||||
if (!_isSubscribedToModNetwork)
|
||||
return;
|
||||
|
||||
ProcessOutboundQueue();
|
||||
ProcessInboundQueue();
|
||||
}
|
||||
|
||||
private static void SendMessage(MessageType messageType, float height, string playerId = null)
|
||||
{
|
||||
if (!IsConnectedToGameNetwork())
|
||||
return;
|
||||
|
||||
if (!Enum.IsDefined(typeof(MessageType), messageType))
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrEmpty(playerId))
|
||||
{
|
||||
// to specific user
|
||||
using ModNetworkMessage modMsg = new(ModId, playerId);
|
||||
modMsg.Write((byte)messageType);
|
||||
modMsg.Write(height);
|
||||
modMsg.Send();
|
||||
}
|
||||
else
|
||||
{
|
||||
// to all users
|
||||
using ModNetworkMessage modMsg = new(ModId);
|
||||
modMsg.Write((byte)messageType);
|
||||
modMsg.Write(height);
|
||||
modMsg.Send();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnMessageReceived(ModNetworkMessage msg)
|
||||
{
|
||||
msg.Read(out byte msgTypeRaw);
|
||||
msg.Read(out float receivedHeight);
|
||||
|
||||
if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw))
|
||||
return;
|
||||
|
||||
// User is in timeout
|
||||
if (UserTimeouts.TryGetValue(msg.Sender, out var timeoutEnd) && Time.time < timeoutEnd)
|
||||
return;
|
||||
|
||||
if (IsRateLimited(msg.Sender))
|
||||
return;
|
||||
|
||||
QueuedMessage inboundMessage = new()
|
||||
{
|
||||
Type = (MessageType)msgTypeRaw,
|
||||
Height = receivedHeight,
|
||||
Sender = msg.Sender
|
||||
};
|
||||
|
||||
InboundQueue[msg.Sender] = inboundMessage;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public static void SendNetworkHeight(float newHeight)
|
||||
{
|
||||
OutboundQueue["global"] = new QueuedMessage { Type = MessageType.SyncHeight, Height = newHeight };
|
||||
}
|
||||
|
||||
public static void RequestHeightSync()
|
||||
{
|
||||
var myCurrentHeight = AvatarScaleManager.Instance.GetHeightForNetwork();
|
||||
OutboundQueue["global"] = new QueuedMessage { Type = MessageType.RequestHeight, Height = myCurrentHeight };
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Outbound Height Queue
|
||||
|
||||
private static void ProcessOutboundQueue()
|
||||
{
|
||||
if (OutboundQueue.Count == 0 || Time.time - LastSentTime < SendRateLimit)
|
||||
return;
|
||||
|
||||
foreach (QueuedMessage message in OutboundQueue.Values)
|
||||
{
|
||||
SendMessage(message.Type, message.Height, message.TargetPlayer);
|
||||
|
||||
if (Debug_NetworkOutbound)
|
||||
AvatarScaleMod.Logger.Msg(
|
||||
$"Sending message {message.Type.ToString()} to {(string.IsNullOrEmpty(message.TargetPlayer) ? "ALL" : message.TargetPlayer)}: {message.Height}");
|
||||
}
|
||||
|
||||
OutboundQueue.Clear();
|
||||
LastSentTime = Time.time;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inbound Height Queue
|
||||
|
||||
private static bool IsRateLimited(string userId)
|
||||
{
|
||||
// Rate-limit checking
|
||||
if (LastReceivedTimes.TryGetValue(userId, out var lastReceivedTime) &&
|
||||
Time.time - lastReceivedTime < ReceiveRateLimit)
|
||||
{
|
||||
if (UserWarnings.TryGetValue(userId, out var warnings))
|
||||
{
|
||||
warnings++;
|
||||
UserWarnings[userId] = warnings;
|
||||
|
||||
if (warnings >= MaxWarnings)
|
||||
{
|
||||
UserTimeouts[userId] = Time.time + TimeoutDuration;
|
||||
AvatarScaleMod.Logger.Warning($"User is sending height updates too fast! Applying 10s timeout... : {userId}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UserWarnings[userId] = 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LastReceivedTimes[userId] = Time.time;
|
||||
UserWarnings.Remove(userId); // Reset warnings
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ProcessInboundQueue()
|
||||
{
|
||||
foreach (QueuedMessage message in InboundQueue.Values)
|
||||
{
|
||||
switch (message.Type)
|
||||
{
|
||||
case MessageType.RequestHeight:
|
||||
{
|
||||
var myNetworkHeight = AvatarScaleManager.Instance.GetHeightForNetwork();
|
||||
OutboundQueue[message.Sender] = new QueuedMessage
|
||||
{
|
||||
Type = MessageType.SyncHeight,
|
||||
Height = myNetworkHeight,
|
||||
TargetPlayer = message.Sender
|
||||
};
|
||||
|
||||
AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(message.Sender, message.Height);
|
||||
break;
|
||||
}
|
||||
case MessageType.SyncHeight:
|
||||
AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(message.Sender, message.Height);
|
||||
break;
|
||||
default:
|
||||
AvatarScaleMod.Logger.Error($"Invalid message type received from: {message.Sender}");
|
||||
break;
|
||||
}
|
||||
|
||||
if (Debug_NetworkInbound)
|
||||
AvatarScaleMod.Logger.Msg($"Received message {message.Type.ToString()} from {message.Sender}: {message.Height}");
|
||||
}
|
||||
|
||||
InboundQueue.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static bool IsConnectedToGameNetwork()
|
||||
{
|
||||
return NetworkManager.Instance != null
|
||||
&& NetworkManager.Instance.GameNetwork != null
|
||||
&& NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Messages
|
||||
|
||||
private class AvatarHeightMessage
|
||||
{
|
||||
public float Height { get; set; }
|
||||
public bool IsUniversal { get; set; }
|
||||
}
|
||||
|
||||
private static void AddAvatarHeightMessageConverter()
|
||||
{
|
||||
ModNetworkMessage.AddConverter(Reader, Writer);
|
||||
return;
|
||||
|
||||
AvatarHeightMessage Reader(ModNetworkMessage msg)
|
||||
{
|
||||
AvatarHeightMessage avatarHeightMessage = new();
|
||||
msg.Read(out float height);
|
||||
avatarHeightMessage.Height = height;
|
||||
msg.Read(out bool isUniversal);
|
||||
avatarHeightMessage.IsUniversal = isUniversal;
|
||||
return avatarHeightMessage;
|
||||
}
|
||||
|
||||
void Writer(ModNetworkMessage msg, AvatarHeightMessage value)
|
||||
{
|
||||
msg.Write(value.Height);
|
||||
msg.Write(value.IsUniversal);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
33
.Deprecated/AvatarScaleMod/Properties/AssemblyInfo.cs
Normal file
33
.Deprecated/AvatarScaleMod/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using MelonLoader;
|
||||
using NAK.AvatarScaleMod.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.AvatarScaleMod))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.AvatarScaleMod))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.AvatarScaleMod.AvatarScaleMod),
|
||||
nameof(NAK.AvatarScaleMod),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AvatarScale"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonOptionalDependencies("Action Menu")]
|
||||
[assembly: MelonColor(255, 241, 200, 82)]
|
||||
[assembly: MelonAuthorColor(255, 114, 17, 25)]
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.AvatarScaleMod.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
28
.Deprecated/AvatarScaleMod/README.md
Normal file
28
.Deprecated/AvatarScaleMod/README.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# AvatarScaleMod
|
||||
|
||||
Proof of concept mod to add Avatar Scaling to any avatar. This is local-only, but I may toy with using Mod Network.
|
||||
|
||||
Legit threw this together in three hours. ChilloutVR handles all the hard stuff already with its existing animation-clip-based Avatar Scaling.
|
||||
|
||||
https://github.com/NotAKidoS/NAK_CVR_Mods/assets/37721153/7405cef5-fd68-4103-8c18-b3164029eab1
|
||||
|
||||
## Notes:
|
||||
* Constraint scaling partially conflicts with avatars run through my [Avatar Scale Tool](https://github.com/NotAKidoS/AvatarScaleTool).
|
||||
* This is local-only, at least unless I bother with Mod Network.
|
||||
* The entire thing is pretty messy and I am unsure of the performance impact, especially with scaling all lights, audio, & constraints.
|
||||
|
||||
## Relevant Feedback Posts:
|
||||
https://feedback.abinteractive.net/p/built-in-avatar-scaling-system
|
||||
|
||||
This mod is me creating the system I wanted when I wrote the above feedback post.
|
||||
|
||||
---
|
||||
|
||||
Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
|
||||
https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
|
||||
|
||||
> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
|
||||
|
||||
> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
|
||||
|
||||
> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
|
26
.Deprecated/AvatarScaleMod/Scripts.cs
Normal file
26
.Deprecated/AvatarScaleMod/Scripts.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
// https://github.com/SDraw/ml_mods_cvr/blob/master/ml_amt/Scripts.cs
|
||||
namespace NAK.AvatarScaleMod;
|
||||
|
||||
static class Scripts
|
||||
{
|
||||
public static string GetEmbeddedScript(string p_name)
|
||||
{
|
||||
string l_result = "";
|
||||
Assembly l_assembly = Assembly.GetExecutingAssembly();
|
||||
string l_assemblyName = l_assembly.GetName().Name;
|
||||
|
||||
try
|
||||
{
|
||||
Stream l_libraryStream = l_assembly.GetManifestResourceStream(l_assemblyName + ".resources." + p_name);
|
||||
StreamReader l_streadReader = new StreamReader(l_libraryStream);
|
||||
l_result = l_streadReader.ReadToEnd();
|
||||
}
|
||||
catch(Exception) { }
|
||||
|
||||
return l_result;
|
||||
}
|
||||
}
|
23
.Deprecated/AvatarScaleMod/format.json
Normal file
23
.Deprecated/AvatarScaleMod/format.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"_id": 126,
|
||||
"name": "AvatarScale",
|
||||
"modversion": "1.0.5",
|
||||
"gameversion": "2022r170p1",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "Fixes two issues with the Avatar Advanced Settings buffers when loading remote avatars. In simple terms, it means 'fewer wardrobe malfunctions'.\n\nEmpty buffer (all 0/false) will no longer be applied on load.\nReceived AAS data is ignored until the wearer has loaded into the expected avatar.\n(The avatar will sit in its default state until the wearer has loaded and started syncing correct AAS)\nAAS will no longer be sent while switching avatar.\n\nPlease view the GitHub README for links to relevant feedback posts.",
|
||||
"searchtags": [
|
||||
"aas",
|
||||
"sync",
|
||||
"naked",
|
||||
"buffer"
|
||||
],
|
||||
"requirements": [
|
||||
"None"
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r3/AvatarScale.dll",
|
||||
"sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AvatarScale/",
|
||||
"changelog": "",
|
||||
"embedcolor": "9b59b6"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
414
.Deprecated/AvatarScaleMod/resources/menu.js
Normal file
414
.Deprecated/AvatarScaleMod/resources/menu.js
Normal file
|
@ -0,0 +1,414 @@
|
|||
(function() {
|
||||
|
||||
/* -------------------------------
|
||||
* CSS Embedding
|
||||
* ------------------------------- */
|
||||
function injectCSS() {
|
||||
const embeddedCSS = `
|
||||
|
||||
// ass stands for Avatar Scale Slider
|
||||
|
||||
/* Container styles */
|
||||
.ass-flex-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ass Slider styles */
|
||||
.ass-slider-container {
|
||||
width: 100%;
|
||||
}
|
||||
.ass-slider-base {
|
||||
height: 3.25em;
|
||||
position: relative;
|
||||
background: #555;
|
||||
border-radius: 1.5em;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ass-slider-inner {
|
||||
height: 100%;
|
||||
background: #a9a9a9;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.ass-snap-point {
|
||||
height: 100%;
|
||||
width: 2px;
|
||||
background: white;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
.ass-slider-value {
|
||||
font-size: 2em;
|
||||
position: relative;
|
||||
left: 0.5em;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Category (label) styles */
|
||||
.ass-category-label {
|
||||
font-size: 2.2em;
|
||||
position: relative;
|
||||
left: 0.5em;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
/* Circle Button styles */
|
||||
.ass-circle-button {
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
border-radius: 50%;
|
||||
background-color: #555;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
font-size: 1.75em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.ass-circle-button:hover {
|
||||
background-color: #777;
|
||||
}
|
||||
|
||||
/* Custom Toggle styles */
|
||||
.ass-custom-toggle {
|
||||
width: 5em;
|
||||
height: 2.5em;
|
||||
background-color: #555;
|
||||
border-radius: 1.25em;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-65%);
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.ass-toggle-circle {
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
background-color: #a9a9a9;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: left 0.3s;
|
||||
}
|
||||
|
||||
.ass-custom-toggle.active .ass-toggle-circle {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
/* Label styles */
|
||||
.ass-label {
|
||||
font-size: 2em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.ass-toggle-setting {
|
||||
position: relative;
|
||||
height: 3.5em;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.type = 'text/css';
|
||||
styleElement.innerHTML = embeddedCSS;
|
||||
document.head.appendChild(styleElement);
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
* Main Content Element
|
||||
* ------------------------------- */
|
||||
class MainContent {
|
||||
constructor(targetId) {
|
||||
this.element = document.createElement('div');
|
||||
this.element.id = "AvatarScaleModContainer";
|
||||
const targetElement = document.getElementById(targetId);
|
||||
if (targetElement) {
|
||||
targetElement.appendChild(this.element);
|
||||
} else {
|
||||
console.warn(`Target element "${targetId}" not found!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
* Generic Element Class
|
||||
* ------------------------------- */
|
||||
class Element {
|
||||
constructor(tagName, parentElement) {
|
||||
this.element = document.createElement(tagName);
|
||||
parentElement.appendChild(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
* Container Object
|
||||
* ------------------------------- */
|
||||
class Container extends Element {
|
||||
constructor(parentElement, {
|
||||
width = '100%',
|
||||
padding = '0em',
|
||||
paddingTop = null,
|
||||
paddingRight = null,
|
||||
paddingBottom = null,
|
||||
paddingLeft = null,
|
||||
margin = '0em',
|
||||
marginTop = null,
|
||||
marginRight = null,
|
||||
marginBottom = null,
|
||||
marginLeft = null
|
||||
} = {}) {
|
||||
super('div', parentElement);
|
||||
this.element.className = "ass-container";
|
||||
this.element.style.width = width;
|
||||
this.element.style.padding = padding;
|
||||
this.element.style.margin = margin;
|
||||
|
||||
// padding values
|
||||
if (paddingTop) this.element.style.paddingTop = paddingTop;
|
||||
if (paddingRight) this.element.style.paddingRight = paddingRight;
|
||||
if (paddingBottom) this.element.style.paddingBottom = paddingBottom;
|
||||
if (paddingLeft) this.element.style.paddingLeft = paddingLeft;
|
||||
|
||||
// margin values
|
||||
if (marginTop) this.element.style.marginTop = marginTop;
|
||||
if (marginRight) this.element.style.marginRight = marginRight;
|
||||
if (marginBottom) this.element.style.marginBottom = marginBottom;
|
||||
if (marginLeft) this.element.style.marginLeft = marginLeft;
|
||||
|
||||
this.flexContainer = new Element('div', this.element);
|
||||
this.flexContainer.element.className = "ass-flex-container";
|
||||
}
|
||||
|
||||
appendElementToFlex(element) {
|
||||
this.flexContainer.element.appendChild(element);
|
||||
}
|
||||
|
||||
appendElement(element) {
|
||||
this.element.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
* Category (label) Class
|
||||
* ------------------------------- */
|
||||
class Category extends Element {
|
||||
constructor(parentElement, text) {
|
||||
super('span', parentElement);
|
||||
this.element.className = "ass-category-label";
|
||||
this.element.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
* Circle Button Class
|
||||
* ------------------------------- */
|
||||
class CircleButton extends Element {
|
||||
constructor(parentElement, text) {
|
||||
super('button', parentElement);
|
||||
this.element.className = "ass-circle-button";
|
||||
this.element.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
* Custom Toggle Class
|
||||
* ------------------------------- */
|
||||
class CustomToggle extends Element {
|
||||
constructor(parentElement) {
|
||||
super('div', parentElement);
|
||||
this.element.className = "ass-custom-toggle";
|
||||
|
||||
this.toggleCircle = new Element('div', this.element);
|
||||
this.toggleCircle.element.className = "ass-toggle-circle";
|
||||
this.state = false;
|
||||
|
||||
this.element.addEventListener('click', () => {
|
||||
this.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.state = !this.state;
|
||||
if (this.state) {
|
||||
this.toggleCircle.element.style.left = '50%';
|
||||
} else {
|
||||
this.toggleCircle.element.style.left = '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
* Label Class
|
||||
* ------------------------------- */
|
||||
class Label extends Element {
|
||||
constructor(parentElement, text) {
|
||||
super('span', parentElement);
|
||||
this.element.className = "ass-label";
|
||||
this.element.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
* ToggleSetting Class
|
||||
* ------------------------------- */
|
||||
class ToggleSetting extends Element {
|
||||
constructor(parentElement, labelText) {
|
||||
super('div', parentElement);
|
||||
this.element.className = "ass-toggle-setting";
|
||||
|
||||
const label = new Label(this.element, labelText);
|
||||
this.toggle = new CustomToggle(this.element);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------
|
||||
* Slider Object
|
||||
* ------------------------------- */
|
||||
class Slider extends Element {
|
||||
constructor(parentElement, min = 0.1, max = 5, initialValue = 1.8) {
|
||||
super('div', parentElement);
|
||||
this.element.className = "ass-slider-container";
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
|
||||
// Value display
|
||||
this.valueDisplay = new Element('span', this.element);
|
||||
this.valueDisplay.element.className = "ass-slider-value";
|
||||
|
||||
// Slider content
|
||||
this.sliderBase = new Element('div', this.element);
|
||||
this.sliderBase.element.className = "ass-slider-base";
|
||||
|
||||
this.trackInner = new Element('div', this.sliderBase.element);
|
||||
this.trackInner.element.className = "ass-slider-inner";
|
||||
|
||||
this.addEventListeners();
|
||||
|
||||
this.setInitialValue(initialValue);
|
||||
}
|
||||
|
||||
setInitialValue(value) {
|
||||
const percentage = (value - this.min) / (this.max - this.min);
|
||||
this.trackInner.element.style.width = `${percentage * 100}%`;
|
||||
this.valueDisplay.element.textContent = value.toFixed(2) + "m";
|
||||
this.addSnapPoint(percentage);
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
this.snapPoints = [];
|
||||
let isDragging = false;
|
||||
|
||||
this.element.addEventListener('mousedown', (e) => {
|
||||
isDragging = true;
|
||||
this.updateTrackWidth(e.clientX);
|
||||
});
|
||||
|
||||
window.addEventListener('mousemove', (e) => {
|
||||
if (!isDragging) return;
|
||||
this.updateTrackWidth(e.clientX);
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
});
|
||||
}
|
||||
|
||||
updateTrackWidth(clientX) {
|
||||
const rect = this.element.getBoundingClientRect();
|
||||
const paddingLeft = parseFloat(getComputedStyle(this.element).paddingLeft);
|
||||
const paddingRight = parseFloat(getComputedStyle(this.element).paddingRight);
|
||||
const effectiveWidth = rect.width - paddingLeft - paddingRight;
|
||||
let x = clientX - rect.left - paddingLeft;
|
||||
|
||||
x = Math.min(Math.max(0, x), effectiveWidth);
|
||||
|
||||
let percentage = x / effectiveWidth;
|
||||
const closestSnap = this.snapPoints.reduce((closest, snap) => {
|
||||
return Math.abs(closest - percentage) < Math.abs(snap - percentage) ? closest : snap;
|
||||
}, 1);
|
||||
|
||||
const SNAP_TOLERANCE = 0.01;
|
||||
if (Math.abs(closestSnap - percentage) <= SNAP_TOLERANCE) {
|
||||
x = closestSnap * effectiveWidth;
|
||||
percentage = closestSnap;
|
||||
}
|
||||
|
||||
this.trackInner.element.style.width = `${x}px`;
|
||||
|
||||
const value = this.min + (this.max - this.min) * percentage;
|
||||
this.valueDisplay.element.textContent = value.toFixed(2) + "m";
|
||||
|
||||
engine.call("asm-AvatarHeightUpdated", value);
|
||||
}
|
||||
|
||||
addSnapPoint(percentage) {
|
||||
if (percentage < 0 || percentage > 1) return;
|
||||
const snap = new Element('div', this.sliderBase.element);
|
||||
snap.element.className = 'ass-snap-point';
|
||||
snap.element.style.left = `${percentage * 100}%`;
|
||||
this.snapPoints.push(percentage);
|
||||
}
|
||||
|
||||
addEvenSnapPoints(count) {
|
||||
for (let i = 1; i <= count; i++) {
|
||||
this.addSnapPoint(i / (count + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialization
|
||||
injectCSS();
|
||||
const mainContent = new MainContent('btkUI-AvatarScaleMod-MainPage');
|
||||
if (mainContent.element) {
|
||||
|
||||
const mainContainer = new Container(mainContent.element, {
|
||||
width: '75%',
|
||||
marginTop: '2em',
|
||||
marginLeft: '2em',
|
||||
marginBottom: '1em'
|
||||
});
|
||||
|
||||
const slider = new Slider(mainContainer.flexContainer.element, 0.1, 3);
|
||||
const buttonContainer = new Container(mainContainer.flexContainer.element, {
|
||||
width: '20%',
|
||||
marginTop: '2.5em',
|
||||
marginLeft: '1em',
|
||||
});
|
||||
const circleButton1 = new CircleButton(buttonContainer.flexContainer.element, "+");
|
||||
const circleButton2 = new CircleButton(buttonContainer.flexContainer.element, "-");
|
||||
|
||||
const settingsContainer = new Container(mainContent.element, {
|
||||
width: '100%',
|
||||
marginTop: '1em',
|
||||
marginLeft: '1em',
|
||||
});
|
||||
const categoryLabel = new Category(settingsContainer.element, "Universal Scaling Settings:");
|
||||
|
||||
const settingsContainerInner = new Container(mainContent.element, {
|
||||
width: '90%',
|
||||
marginTop: '1em',
|
||||
marginLeft: '3em',
|
||||
});
|
||||
|
||||
const toggleSetting = new ToggleSetting(settingsContainerInner.element, "Universal Scaling (Mod Network)");
|
||||
const toggleSetting2 = new ToggleSetting(settingsContainerInner.element, "Recognize Scale Gesture");
|
||||
const toggleSetting3 = new ToggleSetting(settingsContainerInner.element, "Scale Components");
|
||||
}
|
||||
|
||||
})();
|
Loading…
Add table
Add a link
Reference in a new issue