mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 06:19:22 +00:00
i dont rememebr
This commit is contained in:
parent
374ab6c11e
commit
86828a94e2
48 changed files with 1637 additions and 841 deletions
|
@ -16,15 +16,6 @@ internal class IKHandlerDesktop : IKHandler
|
||||||
|
|
||||||
public override void OnInitializeIk()
|
public override void OnInitializeIk()
|
||||||
{
|
{
|
||||||
// Default tracking for Desktop
|
|
||||||
DeviceControlManipulator.shouldTrackHead = true;
|
|
||||||
DeviceControlManipulator.shouldTrackLeftArm = false;
|
|
||||||
DeviceControlManipulator.shouldTrackRightArm = false;
|
|
||||||
DeviceControlManipulator.shouldTrackLeftLeg = false;
|
|
||||||
DeviceControlManipulator.shouldTrackRightLeg = false;
|
|
||||||
DeviceControlManipulator.shouldTrackPelvis = false;
|
|
||||||
DeviceControlManipulator.shouldTrackLocomotion = true;
|
|
||||||
|
|
||||||
_vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateDesktop);
|
_vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateDesktop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,15 +16,6 @@ internal class IKHandlerHalfBody : IKHandler
|
||||||
|
|
||||||
public override void OnInitializeIk()
|
public override void OnInitializeIk()
|
||||||
{
|
{
|
||||||
// Default tracking for HalfBody
|
|
||||||
DeviceControlManipulator.shouldTrackHead = true;
|
|
||||||
DeviceControlManipulator.shouldTrackLeftArm = true;
|
|
||||||
DeviceControlManipulator.shouldTrackRightArm = true;
|
|
||||||
DeviceControlManipulator.shouldTrackLeftLeg = false;
|
|
||||||
DeviceControlManipulator.shouldTrackRightLeg = false;
|
|
||||||
DeviceControlManipulator.shouldTrackPelvis = false;
|
|
||||||
DeviceControlManipulator.shouldTrackLocomotion = true;
|
|
||||||
|
|
||||||
_vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateHalfBody);
|
_vrik.onPreSolverUpdate.AddListener(OnPreSolverUpdateHalfBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ public class IKManager : MonoBehaviour
|
||||||
#region Variables
|
#region Variables
|
||||||
|
|
||||||
public BodyControl BodyControl = new BodyControl();
|
public BodyControl BodyControl = new BodyControl();
|
||||||
public WeightManipulatorManager WeightManipulator = new WeightManipulatorManager();
|
|
||||||
|
|
||||||
public static VRIK vrik => _vrik;
|
public static VRIK vrik => _vrik;
|
||||||
private static VRIK _vrik;
|
private static VRIK _vrik;
|
||||||
|
@ -86,9 +85,6 @@ public class IKManager : MonoBehaviour
|
||||||
_rightHandTarget = _rightController.Find("RightHandTarget");
|
_rightHandTarget = _rightController.Find("RightHandTarget");
|
||||||
_rightHandRotations = _rightHandTarget.Find("RightHandRotations");
|
_rightHandRotations = _rightHandTarget.Find("RightHandRotations");
|
||||||
|
|
||||||
WeightManipulator.AddOverride(new TrackingControlManipulator());
|
|
||||||
WeightManipulator.AddOverride(new DeviceControlManipulator());
|
|
||||||
|
|
||||||
BodyControl.Start();
|
BodyControl.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,9 +95,6 @@ public class IKManager : MonoBehaviour
|
||||||
|
|
||||||
BodyControl.Update();
|
BodyControl.Update();
|
||||||
|
|
||||||
if (vrik.solver != null)
|
|
||||||
WeightManipulator.UpdateWeights(vrik.solver);
|
|
||||||
|
|
||||||
_ikHandler?.UpdateWeights();
|
_ikHandler?.UpdateWeights();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,6 @@ public class AlternateIKSystem : MelonMod
|
||||||
|
|
||||||
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
|
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
|
||||||
ApplyPatches(typeof(HarmonyPatches.IKSystemPatches));
|
ApplyPatches(typeof(HarmonyPatches.IKSystemPatches));
|
||||||
|
|
||||||
InitializeIntegration("BTKUILib", Integrations.BTKUIAddon.Initialize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void InitializeIntegration(string modName, Action integrationAction)
|
private static void InitializeIntegration(string modName, Action integrationAction)
|
||||||
|
|
|
@ -21,7 +21,7 @@ public static class ModSettings
|
||||||
"Determines if VRIK uses humanoid toes for IK solving, which can cause feet to idle behind the avatar.");
|
"Determines if VRIK uses humanoid toes for IK solving, which can cause feet to idle behind the avatar.");
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<bool> EntryPlantFeet =
|
public static readonly MelonPreferences_Entry<bool> EntryPlantFeet =
|
||||||
Category.CreateEntry("Enforce Plant Feet", true,
|
Category.CreateEntry("Enforce Plant Feet", false ,
|
||||||
description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
|
description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<bool> EntryResetFootstepsOnIdle =
|
public static readonly MelonPreferences_Entry<bool> EntryResetFootstepsOnIdle =
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using ABI_RC.Core.IO;
|
using System.Collections;
|
||||||
|
using ABI_RC.Core.IO;
|
||||||
using ABI_RC.Core.Player;
|
using ABI_RC.Core.Player;
|
||||||
using ABI_RC.Core.Player.AvatarTracking;
|
using ABI_RC.Core.Player.AvatarTracking;
|
||||||
using ABI_RC.Core.UI;
|
using ABI_RC.Core.UI;
|
||||||
|
@ -11,15 +12,30 @@ namespace NAK.AvatarScaleMod.AvatarScaling;
|
||||||
|
|
||||||
public class AvatarScaleManager : MonoBehaviour
|
public class AvatarScaleManager : MonoBehaviour
|
||||||
{
|
{
|
||||||
// Universal Scaling Limits
|
public static AvatarScaleManager Instance { get; private set; }
|
||||||
public const float MinHeight = 0.1f;
|
|
||||||
public const float MaxHeight = 10f;
|
|
||||||
|
|
||||||
public static AvatarScaleManager Instance;
|
|
||||||
|
|
||||||
private LocalScaler _localAvatarScaler;
|
private LocalScaler _localAvatarScaler;
|
||||||
private Dictionary<string, NetworkScaler> _networkedScalers;
|
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/NotAKidOnSteam/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;
|
private bool _settingUniversalScaling;
|
||||||
public bool Setting_UniversalScaling
|
public bool Setting_UniversalScaling
|
||||||
{
|
{
|
||||||
|
@ -27,21 +43,25 @@ public class AvatarScaleManager : MonoBehaviour
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value != _settingUniversalScaling && value == false)
|
if (value != _settingUniversalScaling && value == false)
|
||||||
ResetHeight();
|
ResetTargetHeight();
|
||||||
|
|
||||||
_settingUniversalScaling = value;
|
_settingUniversalScaling = value;
|
||||||
|
SetTargetHeight(_lastTargetHeight); // immediate height update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Setting_PersistantHeight;
|
public bool Setting_AnimationClipScalingOverride;
|
||||||
|
public bool Setting_PersistentHeight;
|
||||||
private float _lastTargetHeight = -1;
|
private float _lastTargetHeight = -1;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Unity Methods
|
#region Unity Events
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
if (Instance != null)
|
if (Instance != null
|
||||||
|
&& Instance != this)
|
||||||
{
|
{
|
||||||
DestroyImmediate(this);
|
DestroyImmediate(this);
|
||||||
return;
|
return;
|
||||||
|
@ -53,22 +73,82 @@ public class AvatarScaleManager : MonoBehaviour
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
|
_localAvatarScaler = PlayerSetup.Instance.gameObject.AddComponent<LocalScaler>();
|
||||||
|
_localAvatarScaler.Initialize();
|
||||||
|
|
||||||
_settingUniversalScaling = ModSettings.EntryUseUniversalScaling.Value;
|
_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);
|
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()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
|
_heightUpdateCoroutine = null;
|
||||||
CVRGameEventSystem.Instance.OnConnected.RemoveListener(OnInstanceConnected);
|
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;
|
||||||
|
|
||||||
|
// update local scaler
|
||||||
|
if (_localAvatarScaler != null && _localAvatarScaler.heightNeedsUpdate)
|
||||||
|
{
|
||||||
|
if (_localAvatarScaler.ApplyTargetHeight())
|
||||||
|
AvatarScaleEvents.OnLocalAvatarHeightChanged.Invoke(_localAvatarScaler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update networked scalers (probably a better way to do this)
|
||||||
|
foreach (var netScaler in _networkedScalers)
|
||||||
|
{
|
||||||
|
if (!netScaler.Value.heightNeedsUpdate) continue;
|
||||||
|
if (netScaler.Value.ApplyTargetHeight())
|
||||||
|
AvatarScaleEvents.OnRemoteAvatarHeightChanged.Invoke(netScaler.Key, netScaler.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once IteratorNeverReturns
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Events
|
#region Game Events
|
||||||
|
|
||||||
public void OnInstanceConnected(string instanceId)
|
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);
|
SchedulerSystem.AddJob(ModNetwork.RequestHeightSync, 2f, 1f, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,19 +161,13 @@ public class AvatarScaleManager : MonoBehaviour
|
||||||
if (playerSetup._avatar == null)
|
if (playerSetup._avatar == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_localAvatarScaler == null)
|
|
||||||
{
|
|
||||||
_localAvatarScaler = playerSetup.gameObject.AddComponent<LocalScaler>();
|
|
||||||
_localAvatarScaler.Initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
_localAvatarScaler.OnAvatarInstantiated(playerSetup._avatar, playerSetup._initialAvatarHeight,
|
_localAvatarScaler.OnAvatarInstantiated(playerSetup._avatar, playerSetup._initialAvatarHeight,
|
||||||
playerSetup.initialScale);
|
playerSetup.initialScale);
|
||||||
|
|
||||||
if (!_settingUniversalScaling)
|
if (!_settingUniversalScaling)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SetHeight(Setting_PersistantHeight ? _lastTargetHeight : -1f);
|
SetTargetHeight(_lastTargetHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnAvatarDestroyed(PlayerSetup playerSetup)
|
public void OnAvatarDestroyed(PlayerSetup playerSetup)
|
||||||
|
@ -102,35 +176,34 @@ public class AvatarScaleManager : MonoBehaviour
|
||||||
_localAvatarScaler.OnAvatarDestroyed();
|
_localAvatarScaler.OnAvatarDestroyed();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetHeight(float targetHeight)
|
public void SetTargetHeight(float targetHeight)
|
||||||
{
|
{
|
||||||
|
_lastTargetHeight = targetHeight; // save for persistent height
|
||||||
|
ModSettings.EntryHiddenAvatarHeight.Value = targetHeight; // save for restart
|
||||||
|
|
||||||
if (!_settingUniversalScaling)
|
if (!_settingUniversalScaling)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_localAvatarScaler == null)
|
if (_localAvatarScaler == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_lastTargetHeight = targetHeight;
|
_localAvatarScaler.SetTargetHeight(_lastTargetHeight);
|
||||||
|
_localAvatarScaler.heightNeedsUpdate = true; // only local scaler forces update
|
||||||
_localAvatarScaler.SetTargetHeight(targetHeight);
|
|
||||||
ModNetwork.SendNetworkHeight(targetHeight);
|
|
||||||
|
|
||||||
// immediately update play space scale
|
|
||||||
PlayerSetup.Instance.CheckUpdateAvatarScaleToPlaySpaceRelation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetHeight()
|
public void ResetTargetHeight()
|
||||||
{
|
{
|
||||||
if (_localAvatarScaler == null)
|
if (_localAvatarScaler == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!_localAvatarScaler.IsHeightAdjustedFromInitial())
|
if (!_localAvatarScaler.IsForcingHeight())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// TODO: doesnt work when hitting Reset on slider in BTK UI (is it on main thread?)
|
||||||
CohtmlHud.Instance.ViewDropTextImmediate("(Local) AvatarScaleMod", "Avatar Scale Reset!",
|
CohtmlHud.Instance.ViewDropTextImmediate("(Local) AvatarScaleMod", "Avatar Scale Reset!",
|
||||||
"Universal Scaling is now disabled.");
|
"Universal Scaling is now disabled.");
|
||||||
|
|
||||||
SetHeight(-1f);
|
SetTargetHeight(-1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetHeight()
|
public float GetHeight()
|
||||||
|
@ -138,10 +211,21 @@ public class AvatarScaleManager : MonoBehaviour
|
||||||
if (_localAvatarScaler == null)
|
if (_localAvatarScaler == null)
|
||||||
return PlayerAvatarPoint.defaultAvatarHeight;
|
return PlayerAvatarPoint.defaultAvatarHeight;
|
||||||
|
|
||||||
if (!_localAvatarScaler.IsHeightAdjustedFromInitial())
|
if (!_localAvatarScaler.IsForcingHeight())
|
||||||
return PlayerSetup.Instance.GetAvatarHeight();
|
return PlayerSetup.Instance.GetAvatarHeight();
|
||||||
|
|
||||||
return _localAvatarScaler.GetHeight();
|
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()
|
public float GetHeightForNetwork()
|
||||||
|
@ -152,10 +236,10 @@ public class AvatarScaleManager : MonoBehaviour
|
||||||
if (_localAvatarScaler == null)
|
if (_localAvatarScaler == null)
|
||||||
return -1f;
|
return -1f;
|
||||||
|
|
||||||
if (!_localAvatarScaler.IsHeightAdjustedFromInitial())
|
if (!_localAvatarScaler.IsForcingHeight())
|
||||||
return -1f;
|
return -1f;
|
||||||
|
|
||||||
return _localAvatarScaler.GetHeight();
|
return _localAvatarScaler.GetTargetHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetInitialHeight()
|
public float GetInitialHeight()
|
||||||
|
@ -168,17 +252,23 @@ public class AvatarScaleManager : MonoBehaviour
|
||||||
|
|
||||||
public bool IsHeightAdjustedFromInitial()
|
public bool IsHeightAdjustedFromInitial()
|
||||||
{
|
{
|
||||||
return _localAvatarScaler != null && _localAvatarScaler.IsHeightAdjustedFromInitial();
|
return _localAvatarScaler != null && _localAvatarScaler.IsForcingHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Network Methods
|
#region Network Methods
|
||||||
|
|
||||||
|
public bool DoesNetworkHeightScalerExist(string playerId)
|
||||||
|
=> _networkedScalers.ContainsKey(playerId);
|
||||||
|
|
||||||
|
public int GetNetworkHeightScalerCount()
|
||||||
|
=> _networkedScalers.Count;
|
||||||
|
|
||||||
public float GetNetworkHeight(string playerId)
|
public float GetNetworkHeight(string playerId)
|
||||||
{
|
{
|
||||||
if (_networkedScalers.TryGetValue(playerId, out NetworkScaler scaler))
|
if (_networkedScalers.TryGetValue(playerId, out NetworkScaler scaler))
|
||||||
if (scaler.IsHeightAdjustedFromInitial()) return scaler.GetHeight();
|
if (scaler.IsForcingHeight()) return scaler.GetTargetHeight();
|
||||||
|
|
||||||
//doesn't have mod or has no custom height, get from player avatar directly
|
//doesn't have mod or has no custom height, get from player avatar directly
|
||||||
CVRPlayerEntity playerEntity = CVRPlayerManager.Instance.NetworkPlayers.Find((players) => players.Uuid == playerId);
|
CVRPlayerEntity playerEntity = CVRPlayerManager.Instance.NetworkPlayers.Find((players) => players.Uuid == playerId);
|
||||||
|
@ -266,4 +356,40 @@ public class AvatarScaleManager : MonoBehaviour
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
using ABI_RC.Core;
|
using System.Diagnostics;
|
||||||
using ABI_RC.Core.Player;
|
using ABI_RC.Core;
|
||||||
using NAK.AvatarScaleMod.AvatarScaling;
|
using NAK.AvatarScaleMod.AvatarScaling;
|
||||||
using NAK.AvatarScaleMod.ScaledComponents;
|
using NAK.AvatarScaleMod.ScaledComponents;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
@ -12,42 +12,107 @@ public class BaseScaler : MonoBehaviour
|
||||||
{
|
{
|
||||||
#region Constants
|
#region Constants
|
||||||
|
|
||||||
public const string ScaleFactorParameterName = "ScaleFactor";
|
protected const string ScaleFactorParameterName = "ScaleFactor";
|
||||||
public const string ScaleFactorParameterNameLocal = "#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
|
#endregion
|
||||||
|
|
||||||
#region Variables
|
#region Variables
|
||||||
|
|
||||||
internal bool _isAvatarInstantiated;
|
// Height update requested
|
||||||
internal bool _isHeightAdjustedFromInitial;
|
public bool heightNeedsUpdate { get; internal set; }
|
||||||
internal bool _heightNeedsUpdate;
|
|
||||||
|
|
||||||
|
// Config variables
|
||||||
|
public bool avatarIsHidden { get; set; }
|
||||||
|
public bool useTargetHeight { get; set; }
|
||||||
|
public bool overrideAnimationHeight { get; set; }
|
||||||
|
|
||||||
|
// State variables
|
||||||
|
internal bool _isAvatarInstantiated;
|
||||||
|
internal bool _shouldForceHeight => useTargetHeight || avatarIsHidden; // universal or hidden avatar
|
||||||
|
|
||||||
|
// Avatar info
|
||||||
internal Transform _avatarTransform;
|
internal Transform _avatarTransform;
|
||||||
internal CVRAnimatorManager _animatorManager;
|
internal CVRAnimatorManager _animatorManager;
|
||||||
|
|
||||||
|
// Initial scaling
|
||||||
internal float _initialHeight;
|
internal float _initialHeight;
|
||||||
internal Vector3 _initialScale;
|
internal Vector3 _initialScale;
|
||||||
|
|
||||||
|
// Forced scaling (Universal & Hidden Avatar)
|
||||||
internal float _targetHeight = -1;
|
internal float _targetHeight = -1;
|
||||||
internal Vector3 _targetScale = Vector3.one;
|
internal Vector3 _targetScale = Vector3.one;
|
||||||
internal float _scaleFactor = 1f;
|
internal float _scaleFactor = 1f;
|
||||||
|
|
||||||
// detection for animation clip-based scaling
|
// AnimationClip-based scaling (Local Avatar)
|
||||||
internal Vector3 _legacyAnimationScale;
|
internal float _animatedHeight;
|
||||||
|
internal Vector3 _animatedScale;
|
||||||
|
internal float _animatedScaleFactor = 1f;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Public Methods
|
#region Avatar Events
|
||||||
|
|
||||||
public virtual void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale)
|
public virtual void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale)
|
||||||
{
|
{
|
||||||
if (_isAvatarInstantiated) return;
|
if (_isAvatarInstantiated) return;
|
||||||
_isAvatarInstantiated = true;
|
_isAvatarInstantiated = true;
|
||||||
|
|
||||||
_initialHeight = Mathf.Clamp(initialHeight, 0.01f, 100f);
|
_initialHeight = _animatedHeight = Mathf.Clamp(initialHeight, 0.01f, 100f);
|
||||||
_initialScale = initialScale;
|
_initialScale = _animatedScale = initialScale;
|
||||||
|
_animatedScaleFactor = 1f;
|
||||||
|
|
||||||
|
if (!_shouldForceHeight) // not universal or hidden avatar
|
||||||
|
{
|
||||||
|
_targetHeight = _initialHeight;
|
||||||
|
_targetScale = _initialScale;
|
||||||
|
_scaleFactor = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
_avatarTransform = avatarObject.transform;
|
_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()
|
public void OnAvatarDestroyed()
|
||||||
|
@ -56,99 +121,92 @@ public class BaseScaler : MonoBehaviour
|
||||||
_isAvatarInstantiated = false;
|
_isAvatarInstantiated = false;
|
||||||
|
|
||||||
_avatarTransform = null;
|
_avatarTransform = null;
|
||||||
_heightNeedsUpdate = false;
|
heightNeedsUpdate = false;
|
||||||
ClearComponentLists();
|
ClearComponentLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Public Methods
|
||||||
|
|
||||||
|
public float GetInitialHeight() => _initialHeight;
|
||||||
|
public float GetTargetHeight() => _targetHeight;
|
||||||
|
public float GetAnimatedHeight() => _animatedHeight;
|
||||||
|
public bool IsForcingHeight() => _shouldForceHeight;
|
||||||
|
|
||||||
public void SetTargetHeight(float height)
|
public void SetTargetHeight(float height)
|
||||||
{
|
{
|
||||||
if (_isHeightAdjustedFromInitial
|
|
||||||
&& Math.Abs(height - _targetHeight) < float.Epsilon)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (height < float.Epsilon)
|
if (height < float.Epsilon)
|
||||||
{
|
{
|
||||||
ResetHeight();
|
ResetTargetHeight();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isHeightAdjustedFromInitial)
|
|
||||||
_legacyAnimationScale = Vector3.zero;
|
|
||||||
|
|
||||||
_isHeightAdjustedFromInitial = true;
|
|
||||||
|
|
||||||
_targetHeight = Mathf.Clamp(height, AvatarScaleManager.MinHeight, AvatarScaleManager.MaxHeight);
|
_targetHeight = Mathf.Clamp(height, AvatarScaleManager.MinHeight, AvatarScaleManager.MaxHeight);
|
||||||
_heightNeedsUpdate = true;
|
_scaleFactor = Mathf.Max(_targetHeight / _initialHeight, 0.01f); //safety
|
||||||
|
_targetScale = _initialScale * _scaleFactor;
|
||||||
|
|
||||||
UpdateScaleIfInstantiated();
|
InvokeTargetHeightChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetHeight()
|
public void ResetTargetHeight()
|
||||||
{
|
{
|
||||||
if (!_isHeightAdjustedFromInitial) return;
|
// if (Math.Abs(_initialHeight - _targetHeight) < float.Epsilon)
|
||||||
_isHeightAdjustedFromInitial = false;
|
// return; // no need to change, is close enough
|
||||||
|
|
||||||
if (Math.Abs(_initialHeight - _targetHeight) < float.Epsilon)
|
useTargetHeight = false;
|
||||||
return;
|
|
||||||
|
|
||||||
_legacyAnimationScale = Vector3.zero;
|
_targetHeight = _animatedHeight;
|
||||||
|
_targetScale = _animatedScale;
|
||||||
|
_scaleFactor = _animatedScaleFactor;
|
||||||
|
|
||||||
_targetHeight = _initialHeight;
|
InvokeTargetHeightReset();
|
||||||
_heightNeedsUpdate = true;
|
|
||||||
|
|
||||||
UpdateScaleIfInstantiated();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetHeight() => _targetHeight;
|
public bool ApplyTargetHeight()
|
||||||
public float GetInitialHeight() => _initialHeight;
|
{
|
||||||
public bool IsHeightAdjustedFromInitial() => _isHeightAdjustedFromInitial;
|
if (!_isAvatarInstantiated || _initialHeight == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_avatarTransform == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
heightNeedsUpdate = false;
|
||||||
|
|
||||||
|
ScaleAvatarRoot();
|
||||||
|
UpdateAnimatorParameter();
|
||||||
|
ApplyComponentScaling();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Private Methods
|
#region Private Methods
|
||||||
|
|
||||||
internal void ScaleAvatarRoot()
|
private void ScaleAvatarRoot()
|
||||||
{
|
{
|
||||||
if (_avatarTransform == null) return;
|
if (_avatarTransform == null) return;
|
||||||
_avatarTransform.localScale = _targetScale;
|
_avatarTransform.localScale = _targetScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal virtual void UpdateAnimatorParameter()
|
protected virtual void UpdateAnimatorParameter()
|
||||||
{
|
{
|
||||||
// empty
|
// empty
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void UpdateScaleIfInstantiated()
|
|
||||||
{
|
|
||||||
if (!_isAvatarInstantiated || _initialHeight == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_avatarTransform == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_scaleFactor = Mathf.Max(_targetHeight / _initialHeight, 0.01f); //safety
|
|
||||||
|
|
||||||
_heightNeedsUpdate = false;
|
|
||||||
_targetScale = _initialScale * _scaleFactor;
|
|
||||||
|
|
||||||
ScaleAvatarRoot();
|
|
||||||
UpdateAnimatorParameter();
|
|
||||||
ApplyComponentScaling();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Unity Methods
|
#region Unity Events
|
||||||
|
|
||||||
public virtual void LateUpdate()
|
public virtual void LateUpdate()
|
||||||
{
|
{
|
||||||
if (!_isHeightAdjustedFromInitial)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_isAvatarInstantiated)
|
if (!_isAvatarInstantiated)
|
||||||
return;
|
return; // no avatar
|
||||||
|
|
||||||
ScaleAvatarRoot(); // override animationclip-based scaling
|
if (!_shouldForceHeight)
|
||||||
|
return; // not universal scaling or hidden avatar
|
||||||
|
|
||||||
|
ScaleAvatarRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal virtual void OnDestroy()
|
internal virtual void OnDestroy()
|
||||||
|
@ -170,11 +228,11 @@ public class BaseScaler : MonoBehaviour
|
||||||
typeof(ScaleConstraint)
|
typeof(ScaleConstraint)
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly List<ScaledLight> _scaledLights = new List<ScaledLight>();
|
private readonly List<ScaledLight> _scaledLights = new();
|
||||||
private readonly List<ScaledAudioSource> _scaledAudioSources = new List<ScaledAudioSource>();
|
private readonly List<ScaledAudioSource> _scaledAudioSources = new();
|
||||||
private readonly List<ScaledParentConstraint> _scaledParentConstraints = new List<ScaledParentConstraint>();
|
private readonly List<ScaledParentConstraint> _scaledParentConstraints = new();
|
||||||
private readonly List<ScaledPositionConstraint> _scaledPositionConstraints = new List<ScaledPositionConstraint>();
|
private readonly List<ScaledPositionConstraint> _scaledPositionConstraints = new();
|
||||||
private readonly List<ScaledScaleConstraint> _scaledScaleConstraints = new List<ScaledScaleConstraint>();
|
private readonly List<ScaledScaleConstraint> _scaledScaleConstraints = new();
|
||||||
|
|
||||||
private void ClearComponentLists()
|
private void ClearComponentLists()
|
||||||
{
|
{
|
||||||
|
@ -185,9 +243,8 @@ public class BaseScaler : MonoBehaviour
|
||||||
_scaledScaleConstraints.Clear();
|
_scaledScaleConstraints.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task FindComponentsOfTypeAsync(Type[] types)
|
internal void FindComponentsOfType(Type[] types)
|
||||||
{
|
{
|
||||||
var tasks = new List<Task>();
|
|
||||||
var components = _avatarTransform.gameObject.GetComponentsInChildren<Component>(true);
|
var components = _avatarTransform.gameObject.GetComponentsInChildren<Component>(true);
|
||||||
|
|
||||||
foreach (Component component in components)
|
foreach (Component component in components)
|
||||||
|
@ -195,17 +252,10 @@ public class BaseScaler : MonoBehaviour
|
||||||
if (this == null) break;
|
if (this == null) break;
|
||||||
if (component == null) continue;
|
if (component == null) continue;
|
||||||
|
|
||||||
tasks.Add(Task.Run(() =>
|
|
||||||
{
|
|
||||||
Type componentType = component.GetType();
|
Type componentType = component.GetType();
|
||||||
if (types.Contains(componentType))
|
if (types.Contains(componentType))
|
||||||
{
|
|
||||||
AddScaledComponent(componentType, component);
|
AddScaledComponent(componentType, component);
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.WhenAll(tasks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddScaledComponent(Type type, Component component)
|
private void AddScaledComponent(Type type, Component component)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using ABI_RC.Core.Player;
|
using ABI_RC.Core.Player;
|
||||||
using ABI_RC.Core.UI;
|
using ABI_RC.Core.UI;
|
||||||
using ABI.CCK.Components;
|
|
||||||
using NAK.AvatarScaleMod.AvatarScaling;
|
using NAK.AvatarScaleMod.AvatarScaling;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
|
@ -14,31 +13,23 @@ public class LocalScaler : BaseScaler
|
||||||
{
|
{
|
||||||
_animatorManager = GetComponentInParent<PlayerSetup>().animatorManager;
|
_animatorManager = GetComponentInParent<PlayerSetup>().animatorManager;
|
||||||
|
|
||||||
_heightNeedsUpdate = false;
|
heightNeedsUpdate = false;
|
||||||
_isAvatarInstantiated = false;
|
_isAvatarInstantiated = false;
|
||||||
_isHeightAdjustedFromInitial = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Overrides
|
#region Overrides
|
||||||
|
|
||||||
public override async void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale)
|
public override void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale)
|
||||||
{
|
{
|
||||||
if (avatarObject == null)
|
if (avatarObject == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
base.OnAvatarInstantiated(avatarObject, initialHeight, initialScale);
|
base.OnAvatarInstantiated(avatarObject, initialHeight, initialScale);
|
||||||
await FindComponentsOfTypeAsync(scalableComponentTypes);
|
|
||||||
|
|
||||||
_targetHeight = initialHeight;
|
|
||||||
_scaleFactor = 1f;
|
|
||||||
|
|
||||||
_isHeightAdjustedFromInitial = false;
|
|
||||||
_legacyAnimationScale = Vector3.zero;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void UpdateAnimatorParameter()
|
protected override void UpdateAnimatorParameter()
|
||||||
{
|
{
|
||||||
if (_animatorManager == null)
|
if (_animatorManager == null)
|
||||||
return;
|
return;
|
||||||
|
@ -49,14 +40,8 @@ public class LocalScaler : BaseScaler
|
||||||
|
|
||||||
public override void LateUpdate()
|
public override void LateUpdate()
|
||||||
{
|
{
|
||||||
if (!_isHeightAdjustedFromInitial)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!_isAvatarInstantiated)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!CheckForAnimationScaleChange())
|
if (!CheckForAnimationScaleChange())
|
||||||
ScaleAvatarRoot();
|
base.LateUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -65,28 +50,41 @@ public class LocalScaler : BaseScaler
|
||||||
|
|
||||||
private bool CheckForAnimationScaleChange()
|
private bool CheckForAnimationScaleChange()
|
||||||
{
|
{
|
||||||
if (_avatarTransform == null) return false;
|
if (_avatarTransform == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
//scale matches last recorded animation scale
|
Vector3 localScale = _avatarTransform.localScale;
|
||||||
if (_avatarTransform.localScale == _legacyAnimationScale)
|
|
||||||
|
// scale matches last recorded animation scale
|
||||||
|
if (localScale == _animatedScale)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// avatar may not have scale animation, check if it isn't equal to targetScale
|
// avatar may not have scale animation, check if it isn't equal to targetScale
|
||||||
if (_avatarTransform.localScale == _targetScale)
|
if (localScale == _targetScale)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// scale was likely reset or not initiated
|
// this is the first time we've seen the avatar animated scale, record it!
|
||||||
if (_legacyAnimationScale == Vector3.zero)
|
if (_animatedScale == Vector3.zero)
|
||||||
{
|
{
|
||||||
_legacyAnimationScale = _avatarTransform.localScale;
|
_animatedScale = localScale;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_legacyAnimationScale = _avatarTransform.localScale;
|
// animation scale changed, record it!
|
||||||
|
Vector3 scaleDifference = PlayerSetup.DivideVectors(localScale - _initialScale, _initialScale);
|
||||||
|
_animatedScaleFactor = scaleDifference.y;
|
||||||
|
_animatedHeight = (_initialHeight * _animatedScaleFactor) + _initialHeight;
|
||||||
|
_animatedScale = localScale;
|
||||||
|
InvokeAnimatedHeightChanged();
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
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.");
|
|
||||||
AvatarScaleManager.Instance.ResetHeight(); // disable mod, user used a scale slider
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using ABI_RC.Core.Player;
|
using System.Diagnostics;
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
using NAK.AvatarScaleMod.AvatarScaling;
|
using NAK.AvatarScaleMod.AvatarScaling;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
|
@ -16,28 +17,34 @@ public class NetworkScaler : BaseScaler
|
||||||
|
|
||||||
_animatorManager = GetComponentInParent<PuppetMaster>().animatorManager;
|
_animatorManager = GetComponentInParent<PuppetMaster>().animatorManager;
|
||||||
|
|
||||||
_heightNeedsUpdate = false;
|
heightNeedsUpdate = false;
|
||||||
_isAvatarInstantiated = false;
|
_isAvatarInstantiated = false;
|
||||||
_isHeightAdjustedFromInitial = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Overrides
|
#region Overrides
|
||||||
|
|
||||||
public override async void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale)
|
public override void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale)
|
||||||
{
|
{
|
||||||
if (avatarObject == null)
|
if (avatarObject == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
base.OnAvatarInstantiated(avatarObject, initialHeight, initialScale);
|
base.OnAvatarInstantiated(avatarObject, initialHeight, initialScale);
|
||||||
await FindComponentsOfTypeAsync(scalableComponentTypes);
|
|
||||||
|
|
||||||
if (_isHeightAdjustedFromInitial && _heightNeedsUpdate)
|
Stopwatch stopwatch = new();
|
||||||
UpdateScaleIfInstantiated();
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void UpdateAnimatorParameter()
|
protected override void UpdateAnimatorParameter()
|
||||||
{
|
{
|
||||||
_animatorManager?.SetAnimatorParameter(ScaleFactorParameterNameLocal, _scaleFactor);
|
_animatorManager?.SetAnimatorParameter(ScaleFactorParameterNameLocal, _scaleFactor);
|
||||||
}
|
}
|
||||||
|
|
105
AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs
Normal file
105
AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -48,7 +48,6 @@ public class ScaledLight
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
Component.range = InitialRange;
|
Component.range = InitialRange;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ internal class PlayerSetupPatches
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (__instance == null) return; // this is called when the game is closed
|
||||||
AvatarScaleManager.Instance.OnAvatarDestroyed(__instance);
|
AvatarScaleManager.Instance.OnAvatarDestroyed(__instance);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -80,6 +81,7 @@ internal class PuppetMasterPatches
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (__instance == null) return; // this is called when the game is closed
|
||||||
AvatarScaleManager.Instance.OnNetworkAvatarDestroyed(__instance);
|
AvatarScaleManager.Instance.OnNetworkAvatarDestroyed(__instance);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
@ -30,14 +30,14 @@ internal static class DebugKeybinds
|
||||||
{
|
{
|
||||||
float currentHeight = AvatarScaleManager.Instance.GetHeight() + adjustment;
|
float currentHeight = AvatarScaleManager.Instance.GetHeight() + adjustment;
|
||||||
currentHeight = Mathf.Max(0f, currentHeight);
|
currentHeight = Mathf.Max(0f, currentHeight);
|
||||||
AvatarScaleManager.Instance.SetHeight(currentHeight);
|
AvatarScaleManager.Instance.SetTargetHeight(currentHeight);
|
||||||
|
|
||||||
AvatarScaleMod.Logger.Msg($"[Debug] Setting height: {currentHeight}");
|
AvatarScaleMod.Logger.Msg($"[Debug] Setting height: {currentHeight}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ResetHeight()
|
private static void ResetHeight()
|
||||||
{
|
{
|
||||||
AvatarScaleManager.Instance.ResetHeight();
|
AvatarScaleManager.Instance.ResetTargetHeight();
|
||||||
AvatarScaleMod.Logger.Msg("[Debug] Resetting height.");
|
AvatarScaleMod.Logger.Msg("[Debug] Resetting height.");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -92,7 +92,7 @@ public static class ScaleReconizer
|
||||||
float heightAdjustmentFactor = (modifierRatio > 1) ? 1 + (modifierRatio - 1) : 1 - (1 - modifierRatio);
|
float heightAdjustmentFactor = (modifierRatio > 1) ? 1 + (modifierRatio - 1) : 1 - (1 - modifierRatio);
|
||||||
|
|
||||||
// Apply the adjustment to the target height
|
// Apply the adjustment to the target height
|
||||||
AvatarScaleManager.Instance.SetHeight(_initialTargetHeight * heightAdjustmentFactor);
|
AvatarScaleManager.Instance.SetTargetHeight(_initialTargetHeight * heightAdjustmentFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnScaleEnd(float modifier, Transform transform1, Transform transform2)
|
private static void OnScaleEnd(float modifier, Transform transform1, Transform transform2)
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
using ABI_RC.Core.InteractionSystem;
|
|
||||||
using ABI_RC.Core.IO;
|
|
||||||
using BTKUILib;
|
|
||||||
using BTKUILib.UIObjects;
|
|
||||||
using BTKUILib.UIObjects.Components;
|
|
||||||
using MelonLoader;
|
|
||||||
using NAK.AvatarScaleMod.AvatarScaling;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace NAK.AvatarScaleMod.Integrations
|
|
||||||
{
|
|
||||||
public static class BtkUiAddon
|
|
||||||
{
|
|
||||||
private static string _selectedPlayer;
|
|
||||||
|
|
||||||
#region Initialization
|
|
||||||
|
|
||||||
public static void Initialize()
|
|
||||||
{
|
|
||||||
PrepareIcons();
|
|
||||||
SetupRootPage();
|
|
||||||
SetupPlayerSelectPage();
|
|
||||||
RegisterEventHandlers();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PrepareIcons()
|
|
||||||
{
|
|
||||||
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 SetupRootPage()
|
|
||||||
{
|
|
||||||
// we only need the page, as we inject our own elements into it aa
|
|
||||||
Page rootPage = new Page(ModSettings.ModName, ModSettings.SettingsCategory, true, "ASM_Icon_AvatarHeightConfig")
|
|
||||||
{
|
|
||||||
MenuTitle = ModSettings.SettingsCategory,
|
|
||||||
MenuSubtitle = "Universal Scaling Settings"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SetupPlayerSelectPage()
|
|
||||||
{
|
|
||||||
// what other things would be worth adding here?
|
|
||||||
Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ModSettings.SettingsCategory, ModSettings.ModName);
|
|
||||||
Button button = category.AddButton("Copy Height", "ASM_Icon_AvatarHeightCopy", "Copy selected players Eye Height.");
|
|
||||||
button.OnPress += OnCopyPlayerHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RegisterEventHandlers()
|
|
||||||
{
|
|
||||||
QuickMenuAPI.OnPlayerSelected += (_, id) => _selectedPlayer = id;
|
|
||||||
QuickMenuAPI.OnMenuRegenerate += _ => ScheduleMenuInjection();
|
|
||||||
QuickMenuAPI.OnTabChange += OnTabChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ScheduleMenuInjection()
|
|
||||||
{
|
|
||||||
CVR_MenuManager.Instance.quickMenu.View.BindCall("asm-AvatarHeightUpdated", new Action<float>(OnAvatarHeightUpdated));
|
|
||||||
SchedulerSystem.AddJob(InjectMenu, 1f, 1f, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void InjectMenu()
|
|
||||||
{
|
|
||||||
AvatarScaleMod.Logger.Msg("Injecting into our BTKUI AvatarScaleMod page!");
|
|
||||||
string menuJsPath = Path.Combine(Application.streamingAssetsPath, "Cohtml", "UIResources", "AvatarScaleMod", "menu.js");
|
|
||||||
string menuJsContent = File.Exists(menuJsPath) ? File.ReadAllText(menuJsPath) : string.Empty;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(menuJsContent))
|
|
||||||
{
|
|
||||||
AvatarScaleMod.Logger.Msg("Injecting embedded menu.js included with mod!");
|
|
||||||
CVR_MenuManager.Instance.quickMenu.View._view.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AvatarScaleMod.Logger.Msg($"Injecting development menu.js found in: {menuJsPath}");
|
|
||||||
CVR_MenuManager.Instance.quickMenu.View._view.ExecuteScript(menuJsContent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private static void OnCopyPlayerHeight()
|
|
||||||
{
|
|
||||||
float networkHeight = AvatarScaleManager.Instance.GetNetworkHeight(_selectedPlayer);
|
|
||||||
if (networkHeight < 0) return;
|
|
||||||
AvatarScaleManager.Instance.SetHeight(networkHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void OnAvatarHeightUpdated(float height)
|
|
||||||
{
|
|
||||||
AvatarScaleManager.Instance.SetHeight(height);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private static DateTime lastTime = DateTime.Now;
|
|
||||||
private static void OnTabChange(string newTab, string previousTab)
|
|
||||||
{
|
|
||||||
if (newTab == "btkUI-AvatarScaleMod-MainPage")
|
|
||||||
{
|
|
||||||
TimeSpan timeDifference = DateTime.Now - lastTime;
|
|
||||||
if (timeDifference.TotalSeconds <= 0.5)
|
|
||||||
{
|
|
||||||
AvatarScaleManager.Instance.ResetHeight();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastTime = DateTime.Now;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Stream GetIconStream(string iconName)
|
|
||||||
{
|
|
||||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
|
||||||
string assemblyName = assembly.GetName().Name;
|
|
||||||
return assembly.GetManifestResourceStream($"{assemblyName}.resources.{iconName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Melon Pref Helpers
|
|
||||||
|
|
||||||
internal static void AddMelonToggle(ref Category category, MelonPreferences_Entry<bool> entry)
|
|
||||||
{
|
|
||||||
category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static void AddMelonSlider(ref Page page, MelonPreferences_Entry<float> entry, float min, float max, int decimalPlaces = 2)
|
|
||||||
{
|
|
||||||
page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max, decimalPlaces).OnValueUpdated += f => entry.Value = f;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
100
AvatarScale/Integrations/BTKUI/BtkUiAddon.cs
Normal file
100
AvatarScale/Integrations/BTKUI/BtkUiAddon.cs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
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.ResetTargetHeight();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
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,23 @@
|
||||||
|
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,16 @@
|
||||||
|
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,75 @@
|
||||||
|
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.ResetTargetHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
78
AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs
Normal file
78
AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,43 +10,113 @@ internal static class ModSettings
|
||||||
{
|
{
|
||||||
// Constants
|
// Constants
|
||||||
internal const string ModName = nameof(AvatarScaleMod);
|
internal const string ModName = nameof(AvatarScaleMod);
|
||||||
internal const string SettingsCategory = "Avatar Scale Mod";
|
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";
|
||||||
|
|
||||||
public static readonly MelonPreferences_Category Category =
|
private static readonly MelonPreferences_Category Category =
|
||||||
MelonPreferences.CreateCategory(ModName);
|
MelonPreferences.CreateCategory(ModName);
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<bool> EntryUseUniversalScaling =
|
#region Hidden Foldout Entries
|
||||||
Category.CreateEntry("use_universal_scaling", true, display_name: "Use Universal Scaling", description: "Enable or disable universal scaling.");
|
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<bool> EntryPersistantHeight =
|
// Avatar Scale Mod Foldout
|
||||||
Category.CreateEntry("persistant_height", false, display_name: "Persistant Height", description: "Should the avatar height persist between avatar switches?");
|
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 =
|
public static readonly MelonPreferences_Entry<bool> EntryScaleGestureEnabled =
|
||||||
Category.CreateEntry("scale_gesture_enabled", true, display_name: "Scale Gesture Enabled", description: "Enable or disable scale gesture.");
|
Category.CreateEntry("scale_gesture_enabled", true, display_name: "Scale Gesture", description: "Enable or disable scale gesture.");
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<bool> EntryDebugNetworkInbound =
|
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.");
|
Category.CreateEntry("debug_inbound", false, display_name: "Debug Inbound", description: "Log inbound Mod Network height updates.");
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<bool> EntryDebugNetworkOutbound =
|
public static readonly MelonPreferences_Entry<bool> Debug_NetworkOutbound =
|
||||||
Category.CreateEntry("debug_outbound", false, display_name: "Debug Outbound", description: "Log outbound Mod Network height updates.");
|
Category.CreateEntry("debug_outbound", false, display_name: "Debug Outbound", description: "Log outbound Mod Network height updates.");
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<float> EntryHiddenLastAvatarScale =
|
public static readonly MelonPreferences_Entry<bool> Debug_ComponentSearchTime =
|
||||||
Category.CreateEntry("last_avatar_scale", -1f, is_hidden: true);
|
Category.CreateEntry("debug_component_search_time", false, display_name: "Debug Search Time", description: "Log component search time.");
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
foreach (MelonPreferences_Entry entry in Category.Entries)
|
// subscribe to all bool settings that aren't hidden
|
||||||
entry.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged);
|
foreach (MelonPreferences_Entry entry in Category.Entries.Where(entry => entry.GetReflectedType() == typeof(bool) && !entry.IsHidden))
|
||||||
|
entry.OnEntryValueChangedUntyped.Subscribe(OnSettingsBoolChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnSettingsChanged(object _, object __)
|
private static void OnSettingsBoolChanged(object _, object __)
|
||||||
{
|
{
|
||||||
AvatarScaleManager.Instance.Setting_UniversalScaling = EntryUseUniversalScaling.Value;
|
|
||||||
AvatarScaleManager.Instance.Setting_PersistantHeight = EntryPersistantHeight.Value;
|
|
||||||
|
|
||||||
GestureReconizer.ScaleReconizer.Enabled = EntryScaleGestureEnabled.Value;
|
GestureReconizer.ScaleReconizer.Enabled = EntryScaleGestureEnabled.Value;
|
||||||
|
ModNetwork.Debug_NetworkInbound = Debug_NetworkInbound.Value;
|
||||||
|
ModNetwork.Debug_NetworkOutbound = Debug_NetworkOutbound.Value;
|
||||||
|
|
||||||
ModNetwork.Debug_NetworkInbound = EntryDebugNetworkInbound.Value;
|
if (AvatarScaleManager.Instance == null) return;
|
||||||
ModNetwork.Debug_NetworkOutbound = EntryDebugNetworkOutbound.Value;
|
AvatarScaleManager.Instance.Setting_UniversalScaling = EntryUseUniversalScaling.Value;
|
||||||
|
AvatarScaleManager.Instance.Setting_AnimationClipScalingOverride = EntryAnimationScalingOverride.Value;
|
||||||
|
AvatarScaleManager.Instance.Setting_PersistentHeight = EntryPersistentHeight.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
using ABI_RC.Core.Networking;
|
using ABI_RC.Core.Networking;
|
||||||
using ABI_RC.Systems.ModNetwork;
|
using ABI_RC.Systems.ModNetwork;
|
||||||
using DarkRift;
|
using DarkRift;
|
||||||
using MelonLoader;
|
|
||||||
using NAK.AvatarScaleMod.AvatarScaling;
|
using NAK.AvatarScaleMod.AvatarScaling;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
|
@ -62,8 +61,13 @@ public static class ModNetwork
|
||||||
|
|
||||||
internal static void Subscribe()
|
internal static void Subscribe()
|
||||||
{
|
{
|
||||||
_isSubscribedToModNetwork = true;
|
|
||||||
ModNetworkManager.Subscribe(ModId, OnMessageReceived);
|
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()
|
internal static void Update()
|
||||||
|
@ -244,4 +248,36 @@ public static class ModNetwork
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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
|
||||||
}
|
}
|
|
@ -1,137 +1,26 @@
|
||||||
using ABI.CCK.Components;
|
using ABI_RC.Core;
|
||||||
using ABI_RC.Core.Savior;
|
using ABI_RC.Systems.InputManagement;
|
||||||
using ABI_RC.Core.Util.Object_Behaviour;
|
|
||||||
using ABI_RC.Systems.IK;
|
|
||||||
using ABI_RC.Systems.IK.TrackingModules;
|
|
||||||
using cohtml.Net;
|
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using NAK.DesktopVRSwitch.Patches;
|
using NativeVRModeSwitchManager = ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager;
|
||||||
using NAK.DesktopVRSwitch.VRModeTrackers;
|
|
||||||
using UnityEngine;
|
|
||||||
using Valve.VR;
|
|
||||||
|
|
||||||
namespace NAK.DesktopVRSwitch.HarmonyPatches;
|
namespace NAK.DesktopVRSwitch.HarmonyPatches;
|
||||||
|
|
||||||
internal class CheckVRPatches
|
internal class CVRInputManagerPatches
|
||||||
{
|
{
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
[HarmonyPatch(typeof(CheckVR), nameof(CheckVR.Awake))]
|
[HarmonyPatch(typeof(CVRInputManager), "OnPostVRModeSwitch")]
|
||||||
private static void Postfix_CheckVR_Start(ref CheckVR __instance)
|
private static void Postfix_CVRInputManager_OnPostVRModeSwitch(bool inVr, UnityEngine.Camera playerCamera)
|
||||||
{
|
{
|
||||||
try
|
RootLogic.Instance.ToggleMouse(inVr);
|
||||||
{
|
|
||||||
__instance.gameObject.AddComponent<VRModeSwitchManager>();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_CheckVR_Start)}");
|
|
||||||
DesktopVRSwitch.Logger.Error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class IKSystemPatches
|
internal class VRModeSwitchManagerPatches
|
||||||
{
|
|
||||||
[HarmonyPostfix] //lazy fix so device indices can change properly
|
|
||||||
[HarmonyPatch(typeof(SteamVRTrackingModule), nameof(SteamVRTrackingModule.ModuleDestroy))]
|
|
||||||
private static void Postfix_SteamVRTrackingModule_ModuleDestroy(ref SteamVRTrackingModule __instance)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (TrackingPoint t in __instance.TrackingPoints)
|
|
||||||
{
|
|
||||||
UnityEngine.Object.Destroy(t.referenceGameObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
__instance.TrackingPoints.Clear();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_SteamVRTrackingModule_ModuleDestroy)}");
|
|
||||||
DesktopVRSwitch.Logger.Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CVRWorldPatches
|
|
||||||
{
|
|
||||||
[HarmonyPostfix]
|
|
||||||
[HarmonyPatch(typeof(CVRWorld), nameof(CVRWorld.SetDefaultCamValues))]
|
|
||||||
[HarmonyPatch(typeof(CVRWorld), nameof(CVRWorld.CopyRefCamValues))]
|
|
||||||
private static void Postfix_CVRWorld_HandleCamValues()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ReferenceCameraPatch.OnWorldLoad();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_CVRWorld_HandleCamValues)}");
|
|
||||||
DesktopVRSwitch.Logger.Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CameraFacingObjectPatches
|
|
||||||
{
|
|
||||||
[HarmonyPostfix]
|
|
||||||
[HarmonyPatch(typeof(CameraFacingObject), nameof(CameraFacingObject.Start))]
|
|
||||||
private static void Postfix_CameraFacingObject_Start(ref CameraFacingObject __instance)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
__instance.gameObject.AddComponent<CameraFacingObjectTracker>();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_CameraFacingObject_Start)}");
|
|
||||||
DesktopVRSwitch.Logger.Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CVRPickupObjectPatches
|
|
||||||
{
|
{
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch(typeof(CVRPickupObject), nameof(CVRPickupObject.Start))]
|
[HarmonyPatch(typeof(NativeVRModeSwitchManager), "StartSwitchInternal")]
|
||||||
private static void Prefix_CVRPickupObject_Start(ref CVRPickupObject __instance)
|
private static void Postfix_CVRInputManager_OnPostVRModeSwitch()
|
||||||
{
|
{
|
||||||
try
|
CVRInputManager.Instance.inputEnabled = false;
|
||||||
{
|
|
||||||
if (__instance.gripType == CVRPickupObject.GripType.Free)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Transform vrOrigin = __instance.gripOrigin;
|
|
||||||
Transform desktopOrigin = vrOrigin != null ? vrOrigin.Find("[Desktop]") : null;
|
|
||||||
if (vrOrigin != null && desktopOrigin != null)
|
|
||||||
{
|
|
||||||
CVRPickupObjectTracker tracker = __instance.gameObject.AddComponent<CVRPickupObjectTracker>();
|
|
||||||
tracker._pickupObject = __instance;
|
|
||||||
tracker._storedGripOrigin = (!MetaPort.Instance.isUsingVr ? vrOrigin : desktopOrigin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Prefix_CVRPickupObject_Start)}");
|
|
||||||
DesktopVRSwitch.Logger.Error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class SteamVRBehaviourPatches
|
|
||||||
{
|
|
||||||
[HarmonyPrefix]
|
|
||||||
[HarmonyPatch(typeof(SteamVR_Behaviour), nameof(SteamVR_Behaviour.OnQuit))]
|
|
||||||
private static bool Prefix_SteamVR_Behaviour_OnQuit()
|
|
||||||
{
|
|
||||||
if (!ModSettings.EntrySwitchToDesktopOnExit.Value)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// If we don't switch fast enough, SteamVR will force close.
|
|
||||||
// World Transition might cause issues. Might need to override.
|
|
||||||
if (VRModeSwitchManager.Instance != null)
|
|
||||||
VRModeSwitchManager.Instance.AttemptSwitch();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,84 +1,14 @@
|
||||||
|
using System;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using NAK.DesktopVRSwitch.VRModeTrackers;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace NAK.DesktopVRSwitch;
|
namespace NAK.DesktopVRSwitch;
|
||||||
|
|
||||||
public class DesktopVRSwitch : MelonMod
|
public class DesktopVRSwitch : MelonMod
|
||||||
{
|
{
|
||||||
internal static MelonLogger.Instance Logger;
|
|
||||||
|
|
||||||
public override void OnInitializeMelon()
|
public override void OnInitializeMelon()
|
||||||
{
|
{
|
||||||
Logger = LoggerInstance;
|
ApplyPatches(typeof(HarmonyPatches.CVRInputManagerPatches));
|
||||||
|
ApplyPatches(typeof(HarmonyPatches.VRModeSwitchManagerPatches));
|
||||||
RegisterVRModeTrackers();
|
|
||||||
|
|
||||||
// main manager
|
|
||||||
ApplyPatches(typeof(HarmonyPatches.CheckVRPatches));
|
|
||||||
// nameplate fixes
|
|
||||||
ApplyPatches(typeof(HarmonyPatches.CameraFacingObjectPatches));
|
|
||||||
// pickup fixes
|
|
||||||
ApplyPatches(typeof(HarmonyPatches.CVRPickupObjectPatches));
|
|
||||||
// lazy fix to reset iksystem
|
|
||||||
ApplyPatches(typeof(HarmonyPatches.IKSystemPatches));
|
|
||||||
// post processing fixes
|
|
||||||
ApplyPatches(typeof(HarmonyPatches.CVRWorldPatches));
|
|
||||||
|
|
||||||
// prevent steamvr behaviour from closing game
|
|
||||||
ApplyPatches(typeof(HarmonyPatches.SteamVRBehaviourPatches));
|
|
||||||
|
|
||||||
InitializeIntegration("BTKUILib", Integrations.BTKUIAddon.Initialize);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnUpdate()
|
|
||||||
{
|
|
||||||
if (!Input.GetKeyDown(KeyCode.F6) || !Input.GetKey(KeyCode.LeftControl))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (VRModeSwitchManager.Instance != null)
|
|
||||||
VRModeSwitchManager.Instance.AttemptSwitch();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void RegisterVRModeTrackers()
|
|
||||||
{
|
|
||||||
// Core trackers
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new CheckVRTracker());
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new MetaPortTracker());
|
|
||||||
|
|
||||||
// HUD trackers
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new CohtmlHudTracker());
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new HudOperationsTracker());
|
|
||||||
|
|
||||||
// Player trackers
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new PlayerSetupTracker());
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new MovementSystemTracker());
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new IKSystemTracker());
|
|
||||||
|
|
||||||
// Menu trackers
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new CVR_MenuManagerTracker());
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new ViewManagerTracker());
|
|
||||||
|
|
||||||
// Interaction trackers
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new CVRInputManagerTracker());
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new CVR_InteractableManagerTracker());
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new CVRGestureRecognizerTracker());
|
|
||||||
|
|
||||||
// Portable camera tracker
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new PortableCameraTracker());
|
|
||||||
|
|
||||||
// CVRWorld tracker - Must come after PlayerSetupTracker
|
|
||||||
VRModeSwitchManager.RegisterVRModeTracker(new CVRWorldTracker());
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
private void ApplyPatches(Type type)
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
using ABI_RC.Systems.UI;
|
using ABI_RC.Systems.UI;
|
||||||
using NAK.DesktopVRSwitch.VRModeTrackers;
|
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using ABI_RC.Core.EventSystem;
|
||||||
|
using ABI_RC.Core.IO;
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using ABI_RC.Core.Savior;
|
||||||
|
using ABI_RC.Core.UI;
|
||||||
|
using ABI_RC.Systems.GameEventSystem;
|
||||||
|
using ABI_RC.Systems.InputManagement;
|
||||||
|
using ABI_RC.Systems.VRModeSwitch;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
using UnityEngine.XR.Management;
|
using UnityEngine.XR.Management;
|
||||||
|
|
||||||
namespace NAK.DesktopVRSwitch;
|
namespace NAK.DesktopVRSwitch;
|
||||||
|
|
||||||
public class VRModeSwitchManager : MonoBehaviour
|
public class VRModeSwitchManager : MonoBehaviour
|
||||||
{
|
{
|
||||||
#region Static
|
|
||||||
|
|
||||||
public static VRModeSwitchManager Instance { get; private set; }
|
public static VRModeSwitchManager Instance { get; private set; }
|
||||||
|
|
||||||
public static void RegisterVRModeTracker(VRModeTracker observer) => observer.TrackerInit();
|
|
||||||
public static void UnregisterVRModeTracker(VRModeTracker observer) => observer.TrackerDestroy();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Variables
|
#region Variables
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
|
public bool DesktopVRSwitchEnabled;
|
||||||
public bool UseWorldTransition = true;
|
public bool UseWorldTransition = true;
|
||||||
public bool ReloadLocalAvatar = true;
|
public bool ReloadLocalAvatar = true;
|
||||||
|
|
||||||
|
@ -36,26 +38,58 @@ public class VRModeSwitchManager : MonoBehaviour
|
||||||
DestroyImmediate(this);
|
DestroyImmediate(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Instance = this;
|
Instance = this;
|
||||||
|
|
||||||
|
DesktopVRSwitchEnabled = MetaPort.Instance.settings.GetSettingsBool("ExperimentalDesktopVRSwitch");
|
||||||
|
MetaPort.Instance.settings.settingBoolChanged.AddListener(OnSettingsBoolChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!DesktopVRSwitchEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (SwitchInProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (CVRInputManager.Instance.switchMode)
|
||||||
|
AttemptSwitch();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Public Methods
|
#region Public Methods
|
||||||
|
|
||||||
private static bool IsInXR() => XRGeneralSettings.Instance.Manager.activeLoader != null;
|
public void AttemptSwitch()
|
||||||
|
{
|
||||||
|
if (SwitchInProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
public void AttemptSwitch() => StartCoroutine(StartSwitchInternal());
|
// dont allow switching during world transfer, itll explode violently
|
||||||
|
if (CVRObjectLoader.Instance.IsLoadingWorldToJoin())
|
||||||
|
return;
|
||||||
|
|
||||||
|
StartCoroutine(StartSwitchInternal());
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Private Methods
|
#region Private Methods
|
||||||
|
|
||||||
|
private void OnSettingsBoolChanged(string settingName, bool val)
|
||||||
|
{
|
||||||
|
if (settingName == "ExperimentalDesktopVRSwitch")
|
||||||
|
DesktopVRSwitchEnabled = val;
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerator StartSwitchInternal()
|
private IEnumerator StartSwitchInternal()
|
||||||
{
|
{
|
||||||
if (SwitchInProgress)
|
if (SwitchInProgress)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
|
NotifyOnPreSwitch();
|
||||||
|
|
||||||
bool useWorldTransition = UseWorldTransition;
|
bool useWorldTransition = UseWorldTransition;
|
||||||
SwitchInProgress = true;
|
SwitchInProgress = true;
|
||||||
|
|
||||||
|
@ -64,11 +98,35 @@ public class VRModeSwitchManager : MonoBehaviour
|
||||||
if (useWorldTransition)
|
if (useWorldTransition)
|
||||||
yield return StartCoroutine(StartTransition());
|
yield return StartCoroutine(StartTransition());
|
||||||
|
|
||||||
bool isUsingVr = IsInXR();
|
var wasInXr = IsInXR();
|
||||||
|
|
||||||
InvokeOnPreSwitch(isUsingVr);
|
InvokeOnPreSwitch(!wasInXr);
|
||||||
|
|
||||||
yield return StartCoroutine(XRAndReloadAvatar(!isUsingVr));
|
// Note: this assumes that wasInXr has been correctly set earlier in your method.
|
||||||
|
Task xrTask = wasInXr ? XRHandler.StopXR() : XRHandler.StartXR();
|
||||||
|
|
||||||
|
// Wait for the task to complete. This makes the coroutine wait here until the above thread is done.
|
||||||
|
yield return new WaitUntil(() => xrTask.IsCompleted || xrTask.IsFaulted);
|
||||||
|
|
||||||
|
// Check task status, handle any fault that occurred during the execution of the task.
|
||||||
|
if (xrTask.IsFaulted)
|
||||||
|
{
|
||||||
|
// Log and/or handle exceptions that occurred within the task.
|
||||||
|
Exception innerException = xrTask.Exception.InnerException; // The Exception that caused the Task to enter the faulted state
|
||||||
|
MelonLoader.MelonLogger.Error("Encountered an error while executing the XR task: " + innerException.Message);
|
||||||
|
// Handle the exception appropriately.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasInXr != IsInXR())
|
||||||
|
{
|
||||||
|
ReloadAvatar();
|
||||||
|
InvokeOnPostSwitch(!wasInXr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NotifyOnFailedSwitch();
|
||||||
|
InvokeOnFailedSwitch(!wasInXr);
|
||||||
|
}
|
||||||
|
|
||||||
if (useWorldTransition)
|
if (useWorldTransition)
|
||||||
yield return StartCoroutine(ContinueTransition());
|
yield return StartCoroutine(ContinueTransition());
|
||||||
|
@ -76,29 +134,25 @@ public class VRModeSwitchManager : MonoBehaviour
|
||||||
SwitchInProgress = false;
|
SwitchInProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator XRAndReloadAvatar(bool start)
|
|
||||||
{
|
|
||||||
yield return StartCoroutine(start ? XRHandler.StartXR() : XRHandler.StopXR());
|
|
||||||
|
|
||||||
bool isUsingVr = IsInXR();
|
|
||||||
if (isUsingVr == start)
|
|
||||||
{
|
|
||||||
ReloadAvatar();
|
|
||||||
InvokeOnPostSwitch(start);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
InvokeOnFailedSwitch(start);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReloadAvatar()
|
private void ReloadAvatar()
|
||||||
{
|
{
|
||||||
if (!ReloadLocalAvatar)
|
if (!ReloadLocalAvatar)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Utils.ClearLocalAvatar();
|
// TODO: Is there a better way to reload only locally?
|
||||||
Utils.ReloadLocalAvatar();
|
PlayerSetup.Instance.ClearAvatar();
|
||||||
|
AssetManagement.Instance.LoadLocalAvatar(MetaPort.Instance.currentAvatarGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsInXR()
|
||||||
|
{
|
||||||
|
return XRGeneralSettings.Instance.Manager.activeLoader != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UnityEngine.Camera GetPlayerCamera(bool isVr)
|
||||||
|
{
|
||||||
|
return (isVr ? PlayerSetup.Instance.vrCamera : PlayerSetup.Instance.desktopCamera)
|
||||||
|
.GetComponent<UnityEngine.Camera>();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -123,37 +177,45 @@ public class VRModeSwitchManager : MonoBehaviour
|
||||||
|
|
||||||
#region Event Handling
|
#region Event Handling
|
||||||
|
|
||||||
public class VRModeEventArgs : EventArgs
|
private void InvokeOnPreSwitch(bool isUsingVr)
|
||||||
{
|
{
|
||||||
public bool IsUsingVr { get; }
|
UnityEngine.Camera playerCamera = GetPlayerCamera(isUsingVr);
|
||||||
public Camera PlayerCamera { get; }
|
|
||||||
|
|
||||||
public VRModeEventArgs(bool isUsingVr, Camera playerCamera)
|
ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager.OnPreSwitchInternal?.Invoke(isUsingVr, playerCamera);
|
||||||
{
|
CVRGameEventSystem.VRModeSwitch.OnPreSwitch?.Invoke(isUsingVr, playerCamera);
|
||||||
IsUsingVr = isUsingVr;
|
|
||||||
PlayerCamera = playerCamera;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static event EventHandler<VRModeEventArgs> OnPreVRModeSwitch;
|
private void InvokeOnPostSwitch(bool isUsingVr)
|
||||||
public static event EventHandler<VRModeEventArgs> OnPostVRModeSwitch;
|
{
|
||||||
public static event EventHandler<VRModeEventArgs> OnFailVRModeSwitch;
|
UnityEngine.Camera playerCamera = GetPlayerCamera(isUsingVr);
|
||||||
|
|
||||||
private void InvokeOnPreSwitch(bool isUsingVr) => SafeInvokeUnityEvent(OnPreVRModeSwitch, isUsingVr);
|
ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager.OnPostSwitchInternal?.Invoke(isUsingVr, playerCamera);
|
||||||
private void InvokeOnPostSwitch(bool isUsingVr) => SafeInvokeUnityEvent(OnPostVRModeSwitch, isUsingVr);
|
CVRGameEventSystem.VRModeSwitch.OnPostSwitch?.Invoke(isUsingVr, playerCamera);
|
||||||
private void InvokeOnFailedSwitch(bool isUsingVr) => SafeInvokeUnityEvent(OnFailVRModeSwitch, isUsingVr);
|
}
|
||||||
|
|
||||||
private void SafeInvokeUnityEvent(EventHandler<VRModeEventArgs> switchEvent, bool isUsingVr)
|
private void InvokeOnFailedSwitch(bool isUsingVr)
|
||||||
{
|
{
|
||||||
try
|
UnityEngine.Camera playerCamera = GetPlayerCamera(isUsingVr);
|
||||||
{
|
|
||||||
Camera playerCamera = Utils.GetPlayerCameraObject(isUsingVr).GetComponent<Camera>();
|
ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager.OnFailedSwitchInternal?.Invoke(isUsingVr, playerCamera);
|
||||||
switchEvent?.Invoke(this, new VRModeEventArgs(isUsingVr, playerCamera));
|
CVRGameEventSystem.VRModeSwitch.OnFailedSwitch?.Invoke(isUsingVr, playerCamera);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Notifications
|
||||||
|
|
||||||
|
private void NotifyOnPreSwitch()
|
||||||
{
|
{
|
||||||
DesktopVRSwitch.Logger.Error($"Error in event handler: {e}");
|
CohtmlHud.Instance.ViewDropTextImmediate("(Local) Client",
|
||||||
|
"VR Mode Switch", "Switching to " + (IsInXR() ? "Desktop" : "VR") + " Mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NotifyOnFailedSwitch()
|
||||||
|
{
|
||||||
|
// TODO: Can we get reason it failed?
|
||||||
|
CohtmlHud.Instance.ViewDropTextImmediate("(Local) Client",
|
||||||
|
"VR Mode Switch", "Switch failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
using System.Collections;
|
#if !PLATFORM_ANDROID
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using ABI_RC.Core.Savior;
|
using ABI_RC.Core.Savior;
|
||||||
using Unity.XR.OpenVR;
|
using Unity.XR.OpenVR;
|
||||||
|
@ -6,62 +10,76 @@ using UnityEngine;
|
||||||
using UnityEngine.XR.Management;
|
using UnityEngine.XR.Management;
|
||||||
using UnityEngine.XR.OpenXR;
|
using UnityEngine.XR.OpenXR;
|
||||||
using Valve.VR;
|
using Valve.VR;
|
||||||
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace NAK.DesktopVRSwitch;
|
namespace ABI_RC.Systems.VRModeSwitch
|
||||||
|
|
||||||
internal static class XRHandler
|
|
||||||
{
|
{
|
||||||
internal static IEnumerator StartXR()
|
internal static class XRHandler
|
||||||
{
|
{
|
||||||
yield return XRGeneralSettings.Instance.Manager.InitializeLoader();
|
private static async Task InitializeXRLoader()
|
||||||
|
{
|
||||||
|
EnsureXRLoader();
|
||||||
|
XRGeneralSettings.Instance.Manager.InitializeLoaderSync();
|
||||||
|
await Task.Yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static async Task StartXR()
|
||||||
|
{
|
||||||
|
await InitializeXRLoader();
|
||||||
|
|
||||||
if (XRGeneralSettings.Instance.Manager.activeLoader != null)
|
if (XRGeneralSettings.Instance.Manager.activeLoader != null)
|
||||||
XRGeneralSettings.Instance.Manager.StartSubsystems();
|
XRGeneralSettings.Instance.Manager.StartSubsystems();
|
||||||
else
|
else
|
||||||
yield return StopXR();
|
await StopXR(); // assuming StopXR is now an async method.
|
||||||
|
|
||||||
yield return null;
|
// Await a delay or equivalent method to wait for a frame.
|
||||||
|
await Task.Yield(); // This line is to simulate "waiting for the next frame" in an async way.
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static IEnumerator StopXR()
|
internal static Task StopXR()
|
||||||
{
|
{
|
||||||
if (!XRGeneralSettings.Instance.Manager.isInitializationComplete)
|
if (!XRGeneralSettings.Instance.Manager.isInitializationComplete)
|
||||||
yield break;
|
return Task.CompletedTask;
|
||||||
|
|
||||||
// Forces SteamVR to reinitialize SteamVR_Input next switch
|
// Forces SteamVR to reinitialize SteamVR_Input next switch
|
||||||
SteamVR_ActionSet_Manager.DisableAllActionSets();
|
SteamVR_ActionSet_Manager.DisableAllActionSets();
|
||||||
SteamVR_Input.initialized = false;
|
SteamVR_Input.initialized = false;
|
||||||
|
|
||||||
// Remove SteamVR behaviour & render
|
// Remove SteamVR behaviour & render
|
||||||
UnityEngine.Object.DestroyImmediate(SteamVR_Behaviour.instance.gameObject);
|
Object.DestroyImmediate(SteamVR_Behaviour.instance.gameObject);
|
||||||
SteamVR.enabled = false; // disposes SteamVR
|
SteamVR.enabled = false; // disposes SteamVR
|
||||||
Patches.SteamVRNullReferencePatch.DestroySteamVRInstancesImmediate();
|
|
||||||
|
|
||||||
// Disable UnityXR
|
// Disable UnityXR
|
||||||
XRGeneralSettings.Instance.Manager.StopSubsystems();
|
XRGeneralSettings.Instance.Manager.StopSubsystems();
|
||||||
XRGeneralSettings.Instance.Manager.DeinitializeLoader();
|
XRGeneralSettings.Instance.Manager.DeinitializeLoader();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
// We don't really need to wait a frame on Stop()
|
// If we need to wait for something specific (like a frame), we use Task.Delay or equivalent.
|
||||||
yield return null;
|
// In this case, it seems like you don't need to wait after stopping XR,
|
||||||
|
// so we don't necessarily need an equivalent to 'yield return null' here.
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void SwitchLoader()
|
private static void EnsureXRLoader()
|
||||||
{
|
{
|
||||||
XRLoader item;
|
Type selectedLoaderType = !CheckVR.Instance.forceOpenXr ? typeof(OpenVRLoader) : typeof(OpenXRLoader);
|
||||||
|
|
||||||
if (!CheckVR.Instance.forceOpenXr)
|
// dont do anything if we already have the loader selected
|
||||||
{
|
if (XRGeneralSettings.Instance.Manager.activeLoaders.Count > 0
|
||||||
item = ScriptableObject.CreateInstance<OpenVRLoader>();
|
&& XRGeneralSettings.Instance.Manager.activeLoaders[0].GetType() == selectedLoaderType)
|
||||||
DesktopVRSwitch.Logger.Msg("Using XR Loader: SteamVR");
|
return;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item = ScriptableObject.CreateInstance<OpenXRLoader>();
|
|
||||||
DesktopVRSwitch.Logger.Msg("Using XR Loader: OpenXR");
|
|
||||||
}
|
|
||||||
|
|
||||||
typeof(XRManagerSettings)
|
XRLoader newLoaderInstance = (XRLoader)ScriptableObject.CreateInstance(selectedLoaderType);
|
||||||
.GetField("m_Loaders", BindingFlags.Instance | BindingFlags.NonPublic)
|
FieldInfo field = typeof(XRManagerSettings).GetField("m_Loaders",
|
||||||
?.SetValue(XRGeneralSettings.Instance.Manager, new List<XRLoader> { item });
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
if (field == null) return;
|
||||||
|
|
||||||
|
// destroy old loaders, set the new laoder
|
||||||
|
// this should not happen normally, but changing loader during runtime sounds funni
|
||||||
|
if (field.GetValue(XRGeneralSettings.Instance.Manager) is List<XRLoader> currentLoaders)
|
||||||
|
foreach (XRLoader loader in currentLoaders.Where(loader => loader != null)) Object.Destroy(loader);
|
||||||
|
|
||||||
|
field.SetValue(XRGeneralSettings.Instance.Manager, new List<XRLoader> { newLoaderInstance });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
6
FuckVivox/FuckVivox.csproj
Normal file
6
FuckVivox/FuckVivox.csproj
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<RootNamespace>FuckMLA</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
99
FuckVivox/HarmonyPatches.cs
Normal file
99
FuckVivox/HarmonyPatches.cs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
using ABI_RC.Core.Networking;
|
||||||
|
using ABI_RC.Systems.Communications;
|
||||||
|
using DarkRift.Client;
|
||||||
|
using FuckMLA;
|
||||||
|
using HarmonyLib;
|
||||||
|
using UnityEngine;
|
||||||
|
using Unity.Services.Vivox;
|
||||||
|
|
||||||
|
namespace NAK.FuckVivox.HarmonyPatches;
|
||||||
|
|
||||||
|
internal class VivoxServiceInternalPatches
|
||||||
|
{
|
||||||
|
// This is to catch some dumb issue where channel might not exist. There is no return even though the error is logged... -_-
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(VivoxServiceInternal), nameof(VivoxServiceInternal.Set3DPosition),
|
||||||
|
typeof(Vector3), typeof(Vector3), typeof(Vector3),
|
||||||
|
typeof(Vector3), typeof(string), typeof(bool))]
|
||||||
|
private static void Prefix_VivoxServiceInternal_Set3DPosition(
|
||||||
|
Vector3 speakerPos, Vector3 listenerPos, Vector3 listenerAtOrient, Vector3 listenerUpOrient,
|
||||||
|
string channelName, bool allowPanning,
|
||||||
|
ref ILoginSession ___m_LoginSession,
|
||||||
|
ref bool __runOriginal)
|
||||||
|
{
|
||||||
|
__runOriginal = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IChannelSession channelSession = ___m_LoginSession.ChannelSessions.FirstOrDefault(channel =>
|
||||||
|
channel.Channel.Type == ChannelType.Positional && channel.Channel.Name == channelName);
|
||||||
|
|
||||||
|
if (channelSession != null)
|
||||||
|
return; // no~ fuck you
|
||||||
|
|
||||||
|
__runOriginal = false;
|
||||||
|
FuckVivox.Logger.Msg("Caught an unhandled VivoxServiceInternal error.");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
FuckVivox.Logger.Error(e.ToString());
|
||||||
|
__runOriginal = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to prevent a race condition between OnLoggedOut and OnConnectionFailedToRecover
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(VivoxServiceManager), nameof(VivoxServiceManager.OnConnectionFailedToRecover))]
|
||||||
|
private static void Prefix_VivoxServiceInternal_OnConnectionFailedToRecover(ref bool __runOriginal)
|
||||||
|
{
|
||||||
|
__runOriginal = false;
|
||||||
|
FuckVivox.Logger.Msg("(OnConnectionFailedToRecover) Possibly prevented a double re-login attempt!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to log us out until our connection stabilizes
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(NetworkManager), nameof(NetworkManager.ReconnectToGameServer))]
|
||||||
|
private static void Prefix_NetworkManager_ReconnectToGameServer()
|
||||||
|
{
|
||||||
|
//FuckVivox.Logger.Msg("CONNECTION UNSTABLE, PANIC LOGOUT!!!");
|
||||||
|
//VivoxHelpers.AttemptLogout();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(NetworkManager), nameof(NetworkManager.OnGameNetworkConnected))]
|
||||||
|
private static void Prefix_NetworkManager_OnGameNetworkConnected()
|
||||||
|
{
|
||||||
|
if (VivoxServiceManager.Instance.IsLoggedIn())
|
||||||
|
return;
|
||||||
|
|
||||||
|
//FuckVivox.Logger.Msg("(OnGameNetworkConnected) Not logged into Vivox. Connection is potentially stable now, so attempting to login.");
|
||||||
|
//VivoxHelpers.AttemptLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to potentially fix an issue where on quick restart, we are in a channel before the bind attempts to add it???
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(VivoxServiceInternal), nameof(VivoxServiceInternal.OnChannelPropertyChanged))]
|
||||||
|
private static void Prefix_VivoxServiceInternal_OnChannelPropertyChanged(
|
||||||
|
object sender, PropertyChangedEventArgs args,
|
||||||
|
ref VivoxServiceInternal __instance,
|
||||||
|
ref bool __runOriginal)
|
||||||
|
{
|
||||||
|
__runOriginal = true;
|
||||||
|
|
||||||
|
IChannelSession channelSession = (IChannelSession)sender;
|
||||||
|
|
||||||
|
if (args.PropertyName != "ChannelState" || channelSession.ChannelState != ConnectionState.Connected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!__instance.m_ActiveChannels.ContainsKey(channelSession.Channel.Name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
FuckVivox.Logger.Warning($"Active Channel already contains key! :: + {channelSession.Channel.Name}");
|
||||||
|
__runOriginal = false;
|
||||||
|
}
|
||||||
|
}
|
34
FuckVivox/Main.cs
Normal file
34
FuckVivox/Main.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using FuckMLA;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine.Windows;
|
||||||
|
|
||||||
|
namespace NAK.FuckVivox;
|
||||||
|
|
||||||
|
public class FuckVivox : MelonMod
|
||||||
|
{
|
||||||
|
internal static MelonLogger.Instance Logger;
|
||||||
|
public override void OnInitializeMelon()
|
||||||
|
{
|
||||||
|
Logger = LoggerInstance;
|
||||||
|
ApplyPatches(typeof(HarmonyPatches.VivoxServiceInternalPatches));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnUpdate()
|
||||||
|
{
|
||||||
|
if (UnityEngine.Input.GetKeyDown(UnityEngine.KeyCode.F11))
|
||||||
|
VivoxHelpers.PleaseReLoginThankYou();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyPatches(Type type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HarmonyInstance.PatchAll(type);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||||
|
LoggerInstance.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
FuckVivox/Properties/AssemblyInfo.cs
Normal file
32
FuckVivox/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using MelonLoader;
|
||||||
|
using NAK.FuckVivox.Properties;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyTitle(nameof(NAK.FuckVivox))]
|
||||||
|
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||||
|
[assembly: AssemblyProduct(nameof(NAK.FuckVivox))]
|
||||||
|
|
||||||
|
[assembly: MelonInfo(
|
||||||
|
typeof(NAK.FuckVivox.FuckVivox),
|
||||||
|
nameof(NAK.FuckVivox),
|
||||||
|
AssemblyInfoParams.Version,
|
||||||
|
AssemblyInfoParams.Author,
|
||||||
|
downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FuckVivox"
|
||||||
|
)]
|
||||||
|
|
||||||
|
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||||
|
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||||
|
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||||
|
[assembly: MelonColor(255, 155, 89, 182)]
|
||||||
|
[assembly: MelonAuthorColor(255, 158, 21, 32)]
|
||||||
|
[assembly: HarmonyDontPatchAll]
|
||||||
|
|
||||||
|
namespace NAK.FuckVivox.Properties;
|
||||||
|
internal static class AssemblyInfoParams
|
||||||
|
{
|
||||||
|
public const string Version = "1.0.0";
|
||||||
|
public const string Author = "NotAKidoS";
|
||||||
|
}
|
38
FuckVivox/VivoxHelpers.cs
Normal file
38
FuckVivox/VivoxHelpers.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using ABI_RC.Core.IO;
|
||||||
|
using ABI_RC.Core.Networking;
|
||||||
|
using ABI_RC.Core.Savior;
|
||||||
|
using ABI_RC.Systems.Communications;
|
||||||
|
using NAK.FuckVivox;
|
||||||
|
|
||||||
|
namespace FuckMLA;
|
||||||
|
|
||||||
|
public static class VivoxHelpers
|
||||||
|
{
|
||||||
|
public static void AttemptLogin()
|
||||||
|
{
|
||||||
|
if (!AuthManager.IsAuthenticated)
|
||||||
|
{
|
||||||
|
FuckVivox.Logger.Msg("Attempted to log in without being authenticated!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VivoxServiceManager.Instance.Login(MetaPort.Instance.ownerId, MetaPort.Instance.blockedUserIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AttemptLogout()
|
||||||
|
{
|
||||||
|
if (!VivoxServiceManager.Instance.IsLoggedIn())
|
||||||
|
{
|
||||||
|
FuckVivox.Logger.Msg("Attempted to log out when not logged in.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VivoxServiceManager.Instance.Logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void PleaseReLoginThankYou()
|
||||||
|
{
|
||||||
|
FuckVivox.Logger.Msg("PleaseReLoginThankYou!!!");
|
||||||
|
|
||||||
|
AttemptLogout();
|
||||||
|
SchedulerSystem.AddJob(AttemptLogin, 3f, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
22
FuckVivox/format.json
Normal file
22
FuckVivox/format.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"_id": -1,
|
||||||
|
"name": "FuckMLA",
|
||||||
|
"modversion": "1.0.0",
|
||||||
|
"gameversion": "2023r172",
|
||||||
|
"loaderversion": "0.6.1",
|
||||||
|
"modtype": "Mod",
|
||||||
|
"author": "NotAKidoS",
|
||||||
|
"description": "A simple mod that does three basic things:\n\n- Destroys MOUSELOCKALPHA, the script that locks your mouse on startup.\n- Forces mouse to be initially unlocked when launching the game in VR.\n- Fixes mouse rotating player when cursor is unlocked.",
|
||||||
|
"searchtags": [
|
||||||
|
"mouse",
|
||||||
|
"lock",
|
||||||
|
"no",
|
||||||
|
"bad"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
],
|
||||||
|
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r21/FuckMLA.dll",
|
||||||
|
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FuckMLA/",
|
||||||
|
"changelog": "- Initial CVRMG release",
|
||||||
|
"embedcolor": "#ffc800"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<RootNamespace>FixedDeltaTimeHack</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
45
LateInitComponentHelperHack/Main.cs
Normal file
45
LateInitComponentHelperHack/Main.cs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
using ABI_RC.Core.Player;
|
||||||
|
using ABI_RC.Core.Util.AssetFiltering;
|
||||||
|
using HarmonyLib;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace NAK.LateInitComponentHelperHack;
|
||||||
|
|
||||||
|
public class LateInitComponentHelperHack : MelonMod
|
||||||
|
{
|
||||||
|
private static bool _hasLoggedIn;
|
||||||
|
|
||||||
|
public override void OnInitializeMelon()
|
||||||
|
{
|
||||||
|
ApplyPatches(typeof(HarmonyPatches));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyPatches(Type type)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HarmonyInstance.PatchAll(type);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||||
|
LoggerInstance.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class HarmonyPatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(ComponentHelper), nameof(ComponentHelper.Initialize))]
|
||||||
|
private static bool Prefix_ComponentHelper_Initialize() => _hasLoggedIn;
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
|
||||||
|
private static void Postfix_PlayerSetup_Start()
|
||||||
|
{
|
||||||
|
_hasLoggedIn = true;
|
||||||
|
ComponentHelper.Initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
LateInitComponentHelperHack/Properties/AssemblyInfo.cs
Normal file
32
LateInitComponentHelperHack/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using MelonLoader;
|
||||||
|
using NAK.LateInitComponentHelperHack.Properties;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||||
|
[assembly: AssemblyTitle(nameof(NAK.LateInitComponentHelperHack))]
|
||||||
|
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||||
|
[assembly: AssemblyProduct(nameof(NAK.LateInitComponentHelperHack))]
|
||||||
|
|
||||||
|
[assembly: MelonInfo(
|
||||||
|
typeof(NAK.LateInitComponentHelperHack.LateInitComponentHelperHack),
|
||||||
|
nameof(NAK.LateInitComponentHelperHack),
|
||||||
|
AssemblyInfoParams.Version,
|
||||||
|
AssemblyInfoParams.Author,
|
||||||
|
downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LateInitComponentHelperHack"
|
||||||
|
)]
|
||||||
|
|
||||||
|
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||||
|
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||||
|
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||||
|
[assembly: MelonColor(255, 155, 89, 182)]
|
||||||
|
[assembly: MelonAuthorColor(255, 158, 21, 32)]
|
||||||
|
[assembly: HarmonyDontPatchAll]
|
||||||
|
|
||||||
|
namespace NAK.LateInitComponentHelperHack.Properties;
|
||||||
|
internal static class AssemblyInfoParams
|
||||||
|
{
|
||||||
|
public const string Version = "1.0.0";
|
||||||
|
public const string Author = "NotAKidoS";
|
||||||
|
}
|
22
LateInitComponentHelperHack/format.json
Normal file
22
LateInitComponentHelperHack/format.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"_id": -1,
|
||||||
|
"name": "FuckMLA",
|
||||||
|
"modversion": "1.0.0",
|
||||||
|
"gameversion": "2023r172",
|
||||||
|
"loaderversion": "0.6.1",
|
||||||
|
"modtype": "Mod",
|
||||||
|
"author": "NotAKidoS",
|
||||||
|
"description": "A simple mod that does three basic things:\n\n- Destroys MOUSELOCKALPHA, the script that locks your mouse on startup.\n- Forces mouse to be initially unlocked when launching the game in VR.\n- Fixes mouse rotating player when cursor is unlocked.",
|
||||||
|
"searchtags": [
|
||||||
|
"mouse",
|
||||||
|
"lock",
|
||||||
|
"no",
|
||||||
|
"bad"
|
||||||
|
],
|
||||||
|
"requirements": [
|
||||||
|
],
|
||||||
|
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r21/FuckMLA.dll",
|
||||||
|
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FuckMLA/",
|
||||||
|
"changelog": "- Initial CVRMG release",
|
||||||
|
"embedcolor": "#ffc800"
|
||||||
|
}
|
|
@ -51,9 +51,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzCurls", "EzCurls\EzCurls.
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzGrab", "EzGrab\EzGrab.csproj", "{8CBF5378-A4AE-4594-9A13-0066E5D3FE81}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzGrab", "EzGrab\EzGrab.csproj", "{8CBF5378-A4AE-4594-9A13-0066E5D3FE81}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckMLA", "FuckMLA\FuckMLA.csproj", "{41A84C81-E9FD-471D-9A68-4823C9F67CD7}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckVivox", "FuckVivox\FuckVivox.csproj", "{41A84C81-E9FD-471D-9A68-4823C9F67CD7}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FixedDeltaTimeHack", "FixedDeltaTimeHack\FixedDeltaTimeHack.csproj", "{4845010F-B76C-4D6C-97E1-7D9C468BAD93}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LateInitComponentHelperHack", "LateInitComponentHelperHack\LateInitComponentHelperHack.csproj", "{4845010F-B76C-4D6C-97E1-7D9C468BAD93}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|
|
@ -11,15 +11,15 @@ internal class PortableCameraPatches
|
||||||
private static void Postfix_PortableCamera_Start(ref PortableCamera __instance)
|
private static void Postfix_PortableCamera_Start(ref PortableCamera __instance)
|
||||||
{
|
{
|
||||||
//run mod.Setup() instead of registering full mod with icon
|
//run mod.Setup() instead of registering full mod with icon
|
||||||
VisualMods.CameraAdditions mainMod = new VisualMods.CameraAdditions();
|
VisualMods.CameraAdditions mainMod = new ();
|
||||||
mainMod.Setup(__instance);
|
mainMod.Setup(__instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
[HarmonyPatch(typeof(PortableCamera), nameof(PortableCamera.OnWorldLoaded))]
|
[HarmonyPatch(typeof(PortableCamera), nameof(PortableCamera.OnWorldLoaded))]
|
||||||
private static void Postfix_PortableCamera_OnWorldLoaded(Camera worldCamera)
|
private static void Postfix_PortableCamera_OnWorldLoaded(Camera refCamera)
|
||||||
{
|
{
|
||||||
VisualMods.CameraAdditions.Instance?.OnWorldLoaded(worldCamera);
|
VisualMods.CameraAdditions.Instance?.OnWorldLoaded(refCamera);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
|
|
|
@ -27,6 +27,6 @@ using System.Reflection;
|
||||||
namespace NAK.PortableCameraAdditions.Properties;
|
namespace NAK.PortableCameraAdditions.Properties;
|
||||||
internal static class AssemblyInfoParams
|
internal static class AssemblyInfoParams
|
||||||
{
|
{
|
||||||
public const string Version = "1.0.4";
|
public const string Version = "1.0.5";
|
||||||
public const string Author = "NotAKidoS";
|
public const string Author = "NotAKidoS";
|
||||||
}
|
}
|
|
@ -7,12 +7,12 @@ public class CameraAdditions
|
||||||
{
|
{
|
||||||
public static CameraAdditions Instance;
|
public static CameraAdditions Instance;
|
||||||
|
|
||||||
public Camera referenceCamera;
|
private Camera referenceCamera;
|
||||||
public bool orthographicMode;
|
private bool orthographicMode;
|
||||||
|
|
||||||
//Should I move these to MelonPrefs?
|
//Should I move these to MelonPrefs?
|
||||||
public bool CopyWorldNearClip = true;
|
private bool CopyWorldNearClip = true;
|
||||||
public bool CopyWorldFarClip = true;
|
private bool CopyWorldFarClip = true;
|
||||||
|
|
||||||
private PortableCameraSetting setting_NearClip;
|
private PortableCameraSetting setting_NearClip;
|
||||||
private PortableCameraSetting setting_FarClip;
|
private PortableCameraSetting setting_FarClip;
|
||||||
|
@ -118,29 +118,25 @@ public class CameraAdditions
|
||||||
OnUpdateOptionsDisplay();
|
OnUpdateOptionsDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnWorldLoaded(Camera playerCamera)
|
public void OnWorldLoaded(Camera refCamera)
|
||||||
{
|
{
|
||||||
orthographicMode = false;
|
orthographicMode = false;
|
||||||
referenceCamera = playerCamera;
|
referenceCamera = refCamera;
|
||||||
if (referenceCamera != null)
|
if (referenceCamera == null)
|
||||||
{
|
return;
|
||||||
|
|
||||||
if (CopyWorldNearClip)
|
if (CopyWorldNearClip)
|
||||||
{
|
|
||||||
setting_NearClip.Set(referenceCamera.nearClipPlane);
|
setting_NearClip.Set(referenceCamera.nearClipPlane);
|
||||||
}
|
|
||||||
if (CopyWorldFarClip)
|
if (CopyWorldFarClip)
|
||||||
{
|
|
||||||
setting_FarClip.Set(referenceCamera.farClipPlane);
|
setting_FarClip.Set(referenceCamera.farClipPlane);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnUpdateOptionsDisplay(bool expertMode = true)
|
public void OnUpdateOptionsDisplay(bool expertMode = true)
|
||||||
{
|
{
|
||||||
if (!expertMode)
|
if (!expertMode)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
setting_NearClip.settingsObject.SetActive(!orthographicMode);
|
setting_NearClip.settingsObject.SetActive(!orthographicMode);
|
||||||
setting_FarClip.settingsObject.SetActive(!orthographicMode);
|
setting_FarClip.settingsObject.SetActive(!orthographicMode);
|
||||||
setting_OrthographicSize.settingsObject.SetActive(orthographicMode);
|
setting_OrthographicSize.settingsObject.SetActive(orthographicMode);
|
||||||
|
@ -148,12 +144,11 @@ public class CameraAdditions
|
||||||
setting_OrthographicFarClip.settingsObject.SetActive(orthographicMode);
|
setting_OrthographicFarClip.settingsObject.SetActive(orthographicMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateOrthographicMode()
|
private void UpdateOrthographicMode()
|
||||||
{
|
{
|
||||||
if (PortableCamera.Instance != null)
|
if (PortableCamera.Instance != null)
|
||||||
{
|
|
||||||
PortableCamera.Instance.cameraComponent.orthographic = orthographicMode;
|
PortableCamera.Instance.cameraComponent.orthographic = orthographicMode;
|
||||||
}
|
|
||||||
if (orthographicMode)
|
if (orthographicMode)
|
||||||
{
|
{
|
||||||
UpdateCameraSettingFloat("OrthographicNearClip", setting_OrthographicNearClip.Slider.value);
|
UpdateCameraSettingFloat("OrthographicNearClip", setting_OrthographicNearClip.Slider.value);
|
||||||
|
@ -164,10 +159,11 @@ public class CameraAdditions
|
||||||
UpdateCameraSettingFloat("NearClip", setting_NearClip.Slider.value);
|
UpdateCameraSettingFloat("NearClip", setting_NearClip.Slider.value);
|
||||||
UpdateCameraSettingFloat("FarClip", setting_FarClip.Slider.value);
|
UpdateCameraSettingFloat("FarClip", setting_FarClip.Slider.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
OnUpdateOptionsDisplay();
|
OnUpdateOptionsDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCameraSettingBool(string setting, bool value)
|
private void UpdateCameraSettingBool(string setting, bool value)
|
||||||
{
|
{
|
||||||
if (referenceCamera != null)
|
if (referenceCamera != null)
|
||||||
{
|
{
|
||||||
|
@ -193,7 +189,7 @@ public class CameraAdditions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateCameraSettingFloat(string setting, float value)
|
private void UpdateCameraSettingFloat(string setting, float value)
|
||||||
{
|
{
|
||||||
if (PortableCamera.Instance != null)
|
if (PortableCamera.Instance != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"_id": 123,
|
"_id": 123,
|
||||||
"name": "PortableCameraAdditions",
|
"name": "PortableCameraAdditions",
|
||||||
"modversion": "1.0.4",
|
"modversion": "1.0.5",
|
||||||
"gameversion": "2023r172",
|
"gameversion": "2023r173",
|
||||||
"loaderversion": "0.6.1",
|
"loaderversion": "0.6.1",
|
||||||
"modtype": "Mod",
|
"modtype": "Mod",
|
||||||
"author": "NotAKidoS",
|
"author": "NotAKidoS",
|
||||||
|
@ -18,8 +18,8 @@
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"None"
|
"None"
|
||||||
],
|
],
|
||||||
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r21/PortableCameraAdditions.dll",
|
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r22/PortableCameraAdditions.dll",
|
||||||
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PortableCameraAdditions/",
|
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PortableCameraAdditions/",
|
||||||
"changelog": "- Removed F11 bind to fullscreen Portable Camera.\nIt is now a native bind with 2023r172.",
|
"changelog": "- Fixes for 2023r173.",
|
||||||
"embedcolor": "#ffd96a"
|
"embedcolor": "#ffd96a"
|
||||||
}
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
using ABI.CCK.Components;
|
using System.Reflection;
|
||||||
using ABI_RC.Core.AudioEffects;
|
using ABI_RC.Core.AudioEffects;
|
||||||
using ABI_RC.Core.Networking;
|
using ABI_RC.Core.Networking;
|
||||||
using ABI_RC.Core.Savior;
|
using ABI_RC.Core.Savior;
|
||||||
using ABI_RC.Core.Util;
|
using ABI_RC.Core.Util;
|
||||||
|
using ABI_RC.Systems.InputManagement.InputModules;
|
||||||
|
using ABI.CCK.Components;
|
||||||
using DarkRift;
|
using DarkRift;
|
||||||
|
using HarmonyLib;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using System.Reflection;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace NAK.PropUndoButton;
|
namespace NAK.PropUndoButton;
|
||||||
|
@ -21,9 +23,10 @@ public class PropUndoButton : MelonMod
|
||||||
Category.CreateEntry("Enabled", true, description: "Toggle Undo Prop Button.");
|
Category.CreateEntry("Enabled", true, description: "Toggle Undo Prop Button.");
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<bool> EntryUseSFX =
|
public static readonly MelonPreferences_Entry<bool> EntryUseSFX =
|
||||||
Category.CreateEntry("Use SFX", true, description: "Toggle audio queues for prop spawn, undo, redo, and warning.");
|
Category.CreateEntry("Use SFX", true,
|
||||||
|
description: "Toggle audio queues for prop spawn, undo, redo, and warning.");
|
||||||
|
|
||||||
internal static List<DeletedProp> deletedProps = new List<DeletedProp>();
|
internal static List<DeletedProp> deletedProps = new();
|
||||||
|
|
||||||
// audio clip names, InterfaceAudio adds "PropUndo_" prefix
|
// audio clip names, InterfaceAudio adds "PropUndo_" prefix
|
||||||
private const string sfx_spawn = "PropUndo_sfx_spawn";
|
private const string sfx_spawn = "PropUndo_sfx_spawn";
|
||||||
|
@ -39,27 +42,33 @@ public class PropUndoButton : MelonMod
|
||||||
{
|
{
|
||||||
HarmonyInstance.Patch( // delete my props in reverse order for redo
|
HarmonyInstance.Patch( // delete my props in reverse order for redo
|
||||||
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteMyProps)),
|
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteMyProps)),
|
||||||
prefix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnDeleteMyProps), BindingFlags.NonPublic | BindingFlags.Static))
|
new HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnDeleteMyProps),
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static))
|
||||||
);
|
);
|
||||||
HarmonyInstance.Patch( // delete all props in reverse order for redo
|
HarmonyInstance.Patch( // delete all props in reverse order for redo
|
||||||
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteAllProps)),
|
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteAllProps)),
|
||||||
prefix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnDeleteAllProps), BindingFlags.NonPublic | BindingFlags.Static))
|
new HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnDeleteAllProps),
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static))
|
||||||
);
|
);
|
||||||
HarmonyInstance.Patch( // prop spawn sfx
|
HarmonyInstance.Patch( // prop spawn sfx
|
||||||
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnProp)),
|
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnProp)),
|
||||||
postfix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnSpawnProp), BindingFlags.NonPublic | BindingFlags.Static))
|
postfix: new HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnSpawnProp),
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static))
|
||||||
);
|
);
|
||||||
HarmonyInstance.Patch( // prop delete sfx, log for possible redo
|
HarmonyInstance.Patch( // prop delete sfx, log for possible redo
|
||||||
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeletePropByInstanceId)),
|
typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteMyPropByInstanceIdOverNetwork)),
|
||||||
postfix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnDeletePropByInstanceId), BindingFlags.NonPublic | BindingFlags.Static))
|
postfix: new HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnDeletePropByInstanceId),
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static))
|
||||||
);
|
);
|
||||||
HarmonyInstance.Patch( // desktop input patch so we don't run in menus/gui
|
HarmonyInstance.Patch( // desktop input patch so we don't run in menus/gui
|
||||||
typeof(InputModuleMouseKeyboard).GetMethod(nameof(InputModuleMouseKeyboard.UpdateInput)),
|
typeof(CVRInputModule_Keyboard).GetMethod(nameof(CVRInputModule_Keyboard.UpdateInput)),
|
||||||
postfix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnUpdateInput), BindingFlags.NonPublic | BindingFlags.Static))
|
postfix: new HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnUpdateInput),
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static))
|
||||||
);
|
);
|
||||||
HarmonyInstance.Patch( // clear redo list on world change
|
HarmonyInstance.Patch( // clear redo list on world change
|
||||||
typeof(CVRWorld).GetMethod(nameof(CVRWorld.ConfigureWorld)),
|
typeof(CVRWorld).GetMethod(nameof(CVRWorld.ConfigureWorld)),
|
||||||
postfix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnWorldLoad), BindingFlags.NonPublic | BindingFlags.Static))
|
postfix: new HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnWorldLoad),
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static))
|
||||||
);
|
);
|
||||||
|
|
||||||
SetupDefaultAudioClips();
|
SetupDefaultAudioClips();
|
||||||
|
@ -68,7 +77,7 @@ public class PropUndoButton : MelonMod
|
||||||
private void SetupDefaultAudioClips()
|
private void SetupDefaultAudioClips()
|
||||||
{
|
{
|
||||||
// PropUndo and audio folders do not exist, create them if dont exist yet
|
// PropUndo and audio folders do not exist, create them if dont exist yet
|
||||||
string path = Application.streamingAssetsPath + "/Cohtml/UIResources/GameUI/mods/PropUndo/audio/";
|
var path = Application.streamingAssetsPath + "/Cohtml/UIResources/GameUI/mods/PropUndo/audio/";
|
||||||
if (!Directory.Exists(path))
|
if (!Directory.Exists(path))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(path);
|
Directory.CreateDirectory(path);
|
||||||
|
@ -77,14 +86,14 @@ public class PropUndoButton : MelonMod
|
||||||
|
|
||||||
// copy embedded resources to this folder if they do not exist
|
// copy embedded resources to this folder if they do not exist
|
||||||
string[] clipNames = { "sfx_spawn.wav", "sfx_undo.wav", "sfx_redo.wav", "sfx_warn.wav", "sfx_deny.wav" };
|
string[] clipNames = { "sfx_spawn.wav", "sfx_undo.wav", "sfx_redo.wav", "sfx_warn.wav", "sfx_deny.wav" };
|
||||||
foreach (string clipName in clipNames)
|
foreach (var clipName in clipNames)
|
||||||
{
|
{
|
||||||
string clipPath = Path.Combine(path, clipName);
|
var clipPath = Path.Combine(path, clipName);
|
||||||
if (!File.Exists(clipPath))
|
if (!File.Exists(clipPath))
|
||||||
{
|
{
|
||||||
// read the clip data from embedded resources
|
// read the clip data from embedded resources
|
||||||
byte[] clipData = null;
|
byte[] clipData = null;
|
||||||
string resourceName = "PropUndoButton.SFX." + clipName;
|
var resourceName = "PropUndoButton.SFX." + clipName;
|
||||||
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
|
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
|
||||||
{
|
{
|
||||||
clipData = new byte[stream.Length];
|
clipData = new byte[stream.Length];
|
||||||
|
@ -92,7 +101,7 @@ public class PropUndoButton : MelonMod
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the clip data to the file
|
// write the clip data to the file
|
||||||
using (FileStream fileStream = new FileStream(clipPath, FileMode.CreateNew))
|
using (FileStream fileStream = new(clipPath, FileMode.CreateNew))
|
||||||
{
|
{
|
||||||
fileStream.Write(clipData, 0, clipData.Length);
|
fileStream.Write(clipData, 0, clipData.Length);
|
||||||
}
|
}
|
||||||
|
@ -102,7 +111,10 @@ public class PropUndoButton : MelonMod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnWorldLoad() => deletedProps.Clear();
|
private static void OnWorldLoad()
|
||||||
|
{
|
||||||
|
deletedProps.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
private static void OnUpdateInput()
|
private static void OnUpdateInput()
|
||||||
{
|
{
|
||||||
|
@ -111,13 +123,8 @@ public class PropUndoButton : MelonMod
|
||||||
if (Input.GetKey(KeyCode.LeftControl))
|
if (Input.GetKey(KeyCode.LeftControl))
|
||||||
{
|
{
|
||||||
if (Input.GetKey(KeyCode.LeftShift) && Input.GetKeyDown(KeyCode.Z))
|
if (Input.GetKey(KeyCode.LeftShift) && Input.GetKeyDown(KeyCode.Z))
|
||||||
{
|
|
||||||
RedoProp();
|
RedoProp();
|
||||||
}
|
else if (Input.GetKeyDown(KeyCode.Z)) UndoProp();
|
||||||
else if (Input.GetKeyDown(KeyCode.Z))
|
|
||||||
{
|
|
||||||
UndoProp();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +145,7 @@ public class PropUndoButton : MelonMod
|
||||||
{
|
{
|
||||||
if (!EntryEnabled.Value || string.IsNullOrEmpty(instanceId)) return;
|
if (!EntryEnabled.Value || string.IsNullOrEmpty(instanceId)) return;
|
||||||
|
|
||||||
var propData = GetPropByInstanceIdAndOwnerId(instanceId);
|
CVRSyncHelper.PropData propData = GetPropByInstanceIdAndOwnerId(instanceId);
|
||||||
if (propData == null) return;
|
if (propData == null) return;
|
||||||
|
|
||||||
AddDeletedProp(propData);
|
AddDeletedProp(propData);
|
||||||
|
@ -150,7 +157,7 @@ public class PropUndoButton : MelonMod
|
||||||
if (deletedProps.Count >= redoHistoryLimit)
|
if (deletedProps.Count >= redoHistoryLimit)
|
||||||
deletedProps.RemoveAt(0);
|
deletedProps.RemoveAt(0);
|
||||||
|
|
||||||
DeletedProp deletedProp = new DeletedProp(propData);
|
DeletedProp deletedProp = new(propData);
|
||||||
deletedProps.Add(deletedProp);
|
deletedProps.Add(deletedProp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +166,7 @@ public class PropUndoButton : MelonMod
|
||||||
{
|
{
|
||||||
if (!EntryEnabled.Value) return true;
|
if (!EntryEnabled.Value) return true;
|
||||||
|
|
||||||
List<CVRSyncHelper.PropData> propsList = GetAllPropsByOwnerId();
|
var propsList = GetAllPropsByOwnerId();
|
||||||
|
|
||||||
if (propsList.Count == 0)
|
if (propsList.Count == 0)
|
||||||
{
|
{
|
||||||
|
@ -167,7 +174,7 @@ public class PropUndoButton : MelonMod
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = propsList.Count - 1; i >= 0; i--)
|
for (var i = propsList.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
CVRSyncHelper.PropData propData = propsList[i];
|
CVRSyncHelper.PropData propData = propsList[i];
|
||||||
SafeDeleteProp(propData);
|
SafeDeleteProp(propData);
|
||||||
|
@ -181,17 +188,17 @@ public class PropUndoButton : MelonMod
|
||||||
{
|
{
|
||||||
if (!EntryEnabled.Value) return true;
|
if (!EntryEnabled.Value) return true;
|
||||||
|
|
||||||
CVRSyncHelper.PropData[] propsList = CVRSyncHelper.Props.ToArray();
|
var propsList = CVRSyncHelper.Props.ToArray();
|
||||||
if (propsList.Length == 0)
|
if (propsList.Length == 0)
|
||||||
{
|
{
|
||||||
PlayAudioModule(sfx_warn);
|
PlayAudioModule(sfx_warn);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = propsList.Length - 1; i >= 0; i--)
|
for (var i = propsList.Length - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
CVRSyncHelper.PropData propData = propsList[i];
|
CVRSyncHelper.PropData propData = propsList[i];
|
||||||
DeleteProp(propData);
|
SafeDeleteProp(propData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -199,7 +206,7 @@ public class PropUndoButton : MelonMod
|
||||||
|
|
||||||
private static void UndoProp()
|
private static void UndoProp()
|
||||||
{
|
{
|
||||||
var propData = GetLatestPropByOwnerId();
|
CVRSyncHelper.PropData propData = GetLatestPropByOwnerId();
|
||||||
if (propData == null)
|
if (propData == null)
|
||||||
{
|
{
|
||||||
PlayAudioModule(sfx_warn);
|
PlayAudioModule(sfx_warn);
|
||||||
|
@ -211,7 +218,7 @@ public class PropUndoButton : MelonMod
|
||||||
|
|
||||||
public static void RedoProp()
|
public static void RedoProp()
|
||||||
{
|
{
|
||||||
int index = deletedProps.Count - 1;
|
var index = deletedProps.Count - 1;
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
PlayAudioModule(sfx_warn);
|
PlayAudioModule(sfx_warn);
|
||||||
|
@ -265,74 +272,44 @@ public class PropUndoButton : MelonMod
|
||||||
|
|
||||||
public static void PlayAudioModule(string module)
|
public static void PlayAudioModule(string module)
|
||||||
{
|
{
|
||||||
if (EntryUseSFX.Value)
|
if (EntryUseSFX.Value) InterfaceAudio.PlayModule(module);
|
||||||
{
|
|
||||||
InterfaceAudio.PlayModule(module);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DeleteProp(CVRSyncHelper.PropData propData)
|
|
||||||
{
|
|
||||||
if (propData.Spawnable != null)
|
|
||||||
{
|
|
||||||
propData.Spawnable.Delete();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (propData.Wrapper != null)
|
|
||||||
UnityEngine.Object.DestroyImmediate(propData.Wrapper);
|
|
||||||
propData.Recycle();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SafeDeleteProp(CVRSyncHelper.PropData propData)
|
private static void SafeDeleteProp(CVRSyncHelper.PropData propData)
|
||||||
{
|
{
|
||||||
//fixes getting props stuck in limbo state if spawn & delete fast
|
if (propData.Spawnable == null)
|
||||||
if (propData.Spawnable == null && propData.Wrapper != null)
|
|
||||||
{
|
{
|
||||||
UnityEngine.Object.DestroyImmediate(propData.Wrapper);
|
// network delete prop manually, then delete prop data
|
||||||
|
CVRSyncHelper.DeleteMyPropByInstanceIdOverNetwork(propData.InstanceId);
|
||||||
propData.Recycle();
|
propData.Recycle();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (propData.Spawnable != null)
|
|
||||||
{
|
// delete prop, prop data, and network delete prop
|
||||||
propData.Spawnable.Delete();
|
propData.Spawnable.Delete();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
PlayAudioModule(sfx_deny);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if an undo attempt is made right after spawning a prop, the
|
|
||||||
//spawnable & wrapper will both be null, so the delete request
|
|
||||||
//will be ignored- same with Delete All button in Menu now
|
|
||||||
|
|
||||||
//so the bug causing props to get stuck is due to the propData
|
|
||||||
//being wiped before the prop spawnable & wrapper were created
|
|
||||||
|
|
||||||
//i am unsure if i should attempt an undo of second in line prop,
|
|
||||||
//just in case some issue happens and causes the mod to lock up here.
|
|
||||||
//It should be fine though, as reloading world should clear propData...?
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsPropSpawnAllowed()
|
private static bool IsPropSpawnAllowed()
|
||||||
{
|
{
|
||||||
return MetaPort.Instance.worldAllowProps
|
return MetaPort.Instance.worldAllowProps
|
||||||
&& MetaPort.Instance.settings.GetSettingsBool("ContentFilterPropsEnabled", false)
|
&& MetaPort.Instance.settings.GetSettingsBool("ContentFilterPropsEnabled")
|
||||||
&& NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected;
|
&& NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsAtPropLimit(int limit = 20)
|
public static bool IsAtPropLimit(int limit = 21)
|
||||||
{
|
{
|
||||||
return GetAllPropsByOwnerId().Count >= limit;
|
return GetAllPropsByOwnerId().Count >= limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CVRSyncHelper.PropData GetPropByInstanceIdAndOwnerId(string instanceId)
|
private static CVRSyncHelper.PropData GetPropByInstanceIdAndOwnerId(string instanceId)
|
||||||
{
|
{
|
||||||
return CVRSyncHelper.Props.Find(propData => propData.InstanceId == instanceId && propData.SpawnedBy == MetaPort.Instance.ownerId);
|
return CVRSyncHelper.Props.Find(propData =>
|
||||||
|
propData.InstanceId == instanceId && propData.SpawnedBy == MetaPort.Instance.ownerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CVRSyncHelper.PropData GetLatestPropByOwnerId()
|
private static CVRSyncHelper.PropData GetLatestPropByOwnerId()
|
||||||
{
|
{
|
||||||
|
// return last prop spawned by owner in CVRSyncHelper.MySpawnedPropInstanceIds
|
||||||
return CVRSyncHelper.Props.LastOrDefault(propData => propData.SpawnedBy == MetaPort.Instance.ownerId);
|
return CVRSyncHelper.Props.LastOrDefault(propData => propData.SpawnedBy == MetaPort.Instance.ownerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,15 +327,24 @@ public class PropUndoButton : MelonMod
|
||||||
|
|
||||||
public DeletedProp(CVRSyncHelper.PropData propData)
|
public DeletedProp(CVRSyncHelper.PropData propData)
|
||||||
{
|
{
|
||||||
// Offset spawn height so game can account for it later
|
if (propData.Spawnable == null)
|
||||||
Transform spawnable = propData.Spawnable.transform;
|
{
|
||||||
Vector3 position = spawnable.position;
|
// use original spawn position and rotation / last known position and rotation
|
||||||
position.y -= propData.Spawnable.spawnHeight;
|
position = new Vector3(propData.PositionX, propData.PositionY, propData.PositionZ);
|
||||||
|
rotation = new Vector3(propData.RotationX, propData.RotationY, propData.RotationZ);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Transform spawnableTransform = propData.Spawnable.transform;
|
||||||
|
position = spawnableTransform.position;
|
||||||
|
rotation = spawnableTransform.rotation.eulerAngles;
|
||||||
|
|
||||||
this.propGuid = propData.ObjectId;
|
// Offset spawn height so game can account for it later
|
||||||
this.position = position;
|
position.y -= propData.Spawnable.spawnHeight;
|
||||||
this.rotation = spawnable.rotation.eulerAngles;
|
}
|
||||||
this.timeDeleted = Time.time;
|
|
||||||
|
propGuid = propData.ObjectId;
|
||||||
|
timeDeleted = Time.time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -25,6 +25,6 @@ using System.Reflection;
|
||||||
namespace NAK.PropUndoButton.Properties;
|
namespace NAK.PropUndoButton.Properties;
|
||||||
internal static class AssemblyInfoParams
|
internal static class AssemblyInfoParams
|
||||||
{
|
{
|
||||||
public const string Version = "1.0.1";
|
public const string Version = "1.0.2";
|
||||||
public const string Author = "NotAKidoS";
|
public const string Author = "NotAKidoS";
|
||||||
}
|
}
|
|
@ -7,9 +7,8 @@ internal class PlayerSetupPatches
|
||||||
{
|
{
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
|
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
|
||||||
private static void Post_PlayerSetup_Start(ref PlayerSetup __instance)
|
private static void Postfix_PlayerSetup_Start(ref PlayerSetup __instance)
|
||||||
{
|
{
|
||||||
__instance.vrLeftHandTracker.gameObject.AddComponent<SmoothRayer>().ray = __instance.leftRay;
|
|
||||||
__instance.vrRightHandTracker.gameObject.AddComponent<SmoothRayer>().ray = __instance.rightRay;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,29 +8,6 @@ namespace NAK.SmoothRay;
|
||||||
|
|
||||||
public class SmoothRay : MelonMod
|
public class SmoothRay : MelonMod
|
||||||
{
|
{
|
||||||
public static readonly MelonPreferences_Category Category =
|
|
||||||
MelonPreferences.CreateCategory(nameof(SmoothRay));
|
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<bool> EntryEnabled =
|
|
||||||
Category.CreateEntry("Enable Smoothing", true,
|
|
||||||
description: "Enable or disable smoothing.");
|
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<bool> EntryMenuOnly =
|
|
||||||
Category.CreateEntry("Menu Only", true,
|
|
||||||
description: "Only use smoothing on Main Menu and Quick Menu. This will be fine for most users, but it may be desired on pickups & Unity UI elements too.");
|
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<float> EntryPositionSmoothing =
|
|
||||||
Category.CreateEntry("Position Smoothing", 3f,
|
|
||||||
description: "How much to smooth position changes by. Use the slider to adjust the position smoothing factor. Range: 0 to 20.");
|
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<float> EntryRotationSmoothing =
|
|
||||||
Category.CreateEntry("Rotation Smoothing", 12f,
|
|
||||||
description: "How much to smooth rotation changes by. Use the slider to adjust the rotation smoothing factor. Range: 0 to 20.");
|
|
||||||
|
|
||||||
public static readonly MelonPreferences_Entry<float> EntrySmallMovementThresholdAngle =
|
|
||||||
Category.CreateEntry("Small Angle Threshold", 6f,
|
|
||||||
description: "Angle difference to consider a 'small' movement. The less shaky your hands are, the lower you probably want to set this. This is probably the primary value you want to tweak. Use the slider to adjust the threshold angle. Range: 4 to 15.");
|
|
||||||
|
|
||||||
public override void OnInitializeMelon()
|
public override void OnInitializeMelon()
|
||||||
{
|
{
|
||||||
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
|
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
|
||||||
|
|
|
@ -9,7 +9,7 @@ $cvrExecutable = "ChilloutVR.exe"
|
||||||
$cvrDefaultPath = "E:\temp\CVR_Experimental"
|
$cvrDefaultPath = "E:\temp\CVR_Experimental"
|
||||||
|
|
||||||
# Array with the dlls to strip
|
# Array with the dlls to strip
|
||||||
$dllsToStrip = @('Assembly-CSharp.dll','Assembly-CSharp-firstpass.dll','AVProVideo.Runtime.dll', 'Unity.TextMeshPro.dll', 'MagicaCloth.dll')
|
$dllsToStrip = @('Assembly-CSharp.dll','Assembly-CSharp-firstpass.dll','AVProVideo.Runtime.dll', 'Unity.TextMeshPro.dll', 'MagicaCloth.dll', 'Unity.Services.Vivox.dll')
|
||||||
|
|
||||||
# Array with the mods to grab
|
# Array with the mods to grab
|
||||||
$modNames = @("BTKUILib", "BTKSAImmersiveHud", "ActionMenu", "MenuScalePatch", "ChatBox", "ml_prm")
|
$modNames = @("BTKUILib", "BTKSAImmersiveHud", "ActionMenu", "MenuScalePatch", "ChatBox", "ml_prm")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue