From 86828a94e2a1e48afc75fecf6b139b431b6768a2 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Mon, 1 Jan 2024 11:58:25 -0600 Subject: [PATCH] i dont rememebr --- .../IK/IKHandlers/IKHandlerDesktop.cs | 9 - .../IK/IKHandlers/IKHandlerHalfBody.cs | 9 - AlternateIKSystem/IK/IKManager.cs | 7 - AlternateIKSystem/Main.cs | 2 - AlternateIKSystem/ModSettings.cs | 2 +- .../AvatarScaling/AvatarScaleManager.cs | 200 +++++++++++--- .../AvatarScaling/Components/BaseScaler.cs | 256 +++++++++++------- .../AvatarScaling/Components/LocalScaler.cs | 62 ++--- .../AvatarScaling/Components/NetworkScaler.cs | 25 +- .../AvatarScaling/Events/AvatarScaleEvents.cs | 105 +++++++ AvatarScale/AvatarScaling/ScaledComponents.cs | 1 - AvatarScale/HarmonyPatches.cs | 2 + AvatarScale/Input/DebugKeybinds.cs | 4 +- AvatarScale/Input/ScaleReconizer.cs | 2 +- AvatarScale/Integrations/BTKUI/BTKUIAddon.cs | 137 ---------- AvatarScale/Integrations/BTKUI/BtkUiAddon.cs | 100 +++++++ .../BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs | 17 ++ .../BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs | 23 ++ .../BTKUI/BtkUiAddon_CAT_DebugOptions.cs | 16 ++ ...BtkUiAddon_CAT_UniversalScalingSettings.cs | 75 +++++ .../BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs | 65 +++++ .../Integrations/BTKUI/BtkUiAddon_Utils.cs | 78 ++++++ AvatarScale/ModSettings.cs | 114 ++++++-- AvatarScale/Networking/ModNetwork.cs | 44 ++- DesktopVRSwitch/HarmonyPatches.cs | 133 +-------- DesktopVRSwitch/Main.cs | 76 +----- DesktopVRSwitch/VRModeSwitchManager.cs | 206 +++++++++----- DesktopVRSwitch/XRHandler.cs | 118 ++++---- FuckVivox/FuckVivox.csproj | 6 + FuckVivox/HarmonyPatches.cs | 99 +++++++ FuckVivox/Main.cs | 34 +++ FuckVivox/Properties/AssemblyInfo.cs | 32 +++ FuckVivox/VivoxHelpers.cs | 38 +++ FuckVivox/format.json | 22 ++ .../LateInitComponentHelperHack.csproj | 6 + LateInitComponentHelperHack/Main.cs | 45 +++ .../Properties/AssemblyInfo.cs | 32 +++ LateInitComponentHelperHack/format.json | 22 ++ NAK_CVR_Mods.sln | 4 +- PortableCameraAdditions/HarmonyPatches.cs | 6 +- .../Properties/AssemblyInfo.cs | 2 +- .../VisualMods/CameraAdditions.cs | 44 ++- PortableCameraAdditions/format.json | 8 +- PropUndoButton/Main.cs | 158 +++++------ PropUndoButton/Properties/AssemblyInfo.cs | 2 +- SmoothRay/HarmonyPatches.cs | 5 +- SmoothRay/Main.cs | 23 -- copy_and_nstrip_dll.ps1 | 2 +- 48 files changed, 1637 insertions(+), 841 deletions(-) create mode 100644 AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs delete mode 100644 AvatarScale/Integrations/BTKUI/BTKUIAddon.cs create mode 100644 AvatarScale/Integrations/BTKUI/BtkUiAddon.cs create mode 100644 AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs create mode 100644 AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs create mode 100644 AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs create mode 100644 AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs create mode 100644 AvatarScale/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs create mode 100644 AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs create mode 100644 FuckVivox/FuckVivox.csproj create mode 100644 FuckVivox/HarmonyPatches.cs create mode 100644 FuckVivox/Main.cs create mode 100644 FuckVivox/Properties/AssemblyInfo.cs create mode 100644 FuckVivox/VivoxHelpers.cs create mode 100644 FuckVivox/format.json create mode 100644 LateInitComponentHelperHack/LateInitComponentHelperHack.csproj create mode 100644 LateInitComponentHelperHack/Main.cs create mode 100644 LateInitComponentHelperHack/Properties/AssemblyInfo.cs create mode 100644 LateInitComponentHelperHack/format.json diff --git a/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs b/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs index 088b42b..5e58b7e 100644 --- a/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs +++ b/AlternateIKSystem/IK/IKHandlers/IKHandlerDesktop.cs @@ -16,15 +16,6 @@ internal class IKHandlerDesktop : IKHandler 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); } diff --git a/AlternateIKSystem/IK/IKHandlers/IKHandlerHalfBody.cs b/AlternateIKSystem/IK/IKHandlers/IKHandlerHalfBody.cs index 9af02ac..fdc9680 100644 --- a/AlternateIKSystem/IK/IKHandlers/IKHandlerHalfBody.cs +++ b/AlternateIKSystem/IK/IKHandlers/IKHandlerHalfBody.cs @@ -16,15 +16,6 @@ internal class IKHandlerHalfBody : IKHandler 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); } diff --git a/AlternateIKSystem/IK/IKManager.cs b/AlternateIKSystem/IK/IKManager.cs index 263ff33..5a29836 100644 --- a/AlternateIKSystem/IK/IKManager.cs +++ b/AlternateIKSystem/IK/IKManager.cs @@ -16,7 +16,6 @@ public class IKManager : MonoBehaviour #region Variables public BodyControl BodyControl = new BodyControl(); - public WeightManipulatorManager WeightManipulator = new WeightManipulatorManager(); public static VRIK vrik => _vrik; private static VRIK _vrik; @@ -86,9 +85,6 @@ public class IKManager : MonoBehaviour _rightHandTarget = _rightController.Find("RightHandTarget"); _rightHandRotations = _rightHandTarget.Find("RightHandRotations"); - WeightManipulator.AddOverride(new TrackingControlManipulator()); - WeightManipulator.AddOverride(new DeviceControlManipulator()); - BodyControl.Start(); } @@ -99,9 +95,6 @@ public class IKManager : MonoBehaviour BodyControl.Update(); - if (vrik.solver != null) - WeightManipulator.UpdateWeights(vrik.solver); - _ikHandler?.UpdateWeights(); } diff --git a/AlternateIKSystem/Main.cs b/AlternateIKSystem/Main.cs index 37fa7ea..9d7a1e4 100644 --- a/AlternateIKSystem/Main.cs +++ b/AlternateIKSystem/Main.cs @@ -20,8 +20,6 @@ public class AlternateIKSystem : MelonMod ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); ApplyPatches(typeof(HarmonyPatches.IKSystemPatches)); - - InitializeIntegration("BTKUILib", Integrations.BTKUIAddon.Initialize); } private static void InitializeIntegration(string modName, Action integrationAction) diff --git a/AlternateIKSystem/ModSettings.cs b/AlternateIKSystem/ModSettings.cs index 25adcd5..c502653 100644 --- a/AlternateIKSystem/ModSettings.cs +++ b/AlternateIKSystem/ModSettings.cs @@ -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."); public static readonly MelonPreferences_Entry 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."); public static readonly MelonPreferences_Entry EntryResetFootstepsOnIdle = diff --git a/AvatarScale/AvatarScaling/AvatarScaleManager.cs b/AvatarScale/AvatarScaling/AvatarScaleManager.cs index aa39e3b..6f2e4c2 100644 --- a/AvatarScale/AvatarScaling/AvatarScaleManager.cs +++ b/AvatarScale/AvatarScaling/AvatarScaleManager.cs @@ -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.AvatarTracking; using ABI_RC.Core.UI; @@ -11,14 +12,29 @@ namespace NAK.AvatarScaleMod.AvatarScaling; public class AvatarScaleManager : MonoBehaviour { - // Universal Scaling Limits - public const float MinHeight = 0.1f; - public const float MaxHeight = 10f; - - public static AvatarScaleManager Instance; + public static AvatarScaleManager Instance { get; private set; } private LocalScaler _localAvatarScaler; private Dictionary _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; public bool Setting_UniversalScaling @@ -27,21 +43,25 @@ public class AvatarScaleManager : MonoBehaviour set { if (value != _settingUniversalScaling && value == false) - ResetHeight(); + ResetTargetHeight(); _settingUniversalScaling = value; + SetTargetHeight(_lastTargetHeight); // immediate height update } } - public bool Setting_PersistantHeight; + public bool Setting_AnimationClipScalingOverride; + public bool Setting_PersistentHeight; private float _lastTargetHeight = -1; - - #region Unity Methods + #endregion + + #region Unity Events private void Awake() { - if (Instance != null) + if (Instance != null + && Instance != this) { DestroyImmediate(this); return; @@ -53,22 +73,82 @@ public class AvatarScaleManager : MonoBehaviour private void Start() { + _localAvatarScaler = PlayerSetup.Instance.gameObject.AddComponent(); + _localAvatarScaler.Initialize(); + _settingUniversalScaling = ModSettings.EntryUseUniversalScaling.Value; + Setting_AnimationClipScalingOverride = ModSettings.EntryAnimationScalingOverride.Value; + Setting_PersistentHeight = ModSettings.EntryPersistentHeight.Value; + _lastTargetHeight = ModSettings.EntryPersistThroughRestart.Value + ? ModSettings.EntryHiddenAvatarHeight.Value : -1f; // -1f is default + + // listen for events + _localAvatarScaler.OnAnimatedHeightOverride += OnAnimationHeightOverride; CVRGameEventSystem.Instance.OnConnected.AddListener(OnInstanceConnected); } + + private void OnEnable() + { + if (_heightUpdateCoroutine != null) StopCoroutine(_heightUpdateCoroutine); + _heightUpdateCoroutine = StartCoroutine(HeightUpdateCoroutine()); + } + + private void OnDisable() + { + if (_heightUpdateCoroutine != null) StopCoroutine(_heightUpdateCoroutine); + _heightUpdateCoroutine = null; + } private void OnDestroy() { + _heightUpdateCoroutine = null; CVRGameEventSystem.Instance.OnConnected.RemoveListener(OnInstanceConnected); + + if (_localAvatarScaler != null) Destroy(_localAvatarScaler); + _localAvatarScaler = null; + + foreach (NetworkScaler scaler in _networkedScalers.Values) Destroy(scaler); + _networkedScalers.Clear(); + + if (Instance == this) + Instance = null; + } + + // only update the height per scaler once per frame, to prevent spam & jitter + // this is to ensure that the height is also set at correct time during frame, no matter when it is called + private IEnumerator HeightUpdateCoroutine() + { + while (enabled) + { + yield return _heightUpdateYield; + + // 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 - #region Events + #region Game Events public void OnInstanceConnected(string instanceId) { + // TODO: need to know if this causes issues when in a reconnection loop SchedulerSystem.AddJob(ModNetwork.RequestHeightSync, 2f, 1f, 1); } @@ -81,19 +161,13 @@ public class AvatarScaleManager : MonoBehaviour if (playerSetup._avatar == null) return; - if (_localAvatarScaler == null) - { - _localAvatarScaler = playerSetup.gameObject.AddComponent(); - _localAvatarScaler.Initialize(); - } - _localAvatarScaler.OnAvatarInstantiated(playerSetup._avatar, playerSetup._initialAvatarHeight, playerSetup.initialScale); if (!_settingUniversalScaling) return; - SetHeight(Setting_PersistantHeight ? _lastTargetHeight : -1f); + SetTargetHeight(_lastTargetHeight); } public void OnAvatarDestroyed(PlayerSetup playerSetup) @@ -102,35 +176,34 @@ public class AvatarScaleManager : MonoBehaviour _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) return; if (_localAvatarScaler == null) return; - _lastTargetHeight = targetHeight; - - _localAvatarScaler.SetTargetHeight(targetHeight); - ModNetwork.SendNetworkHeight(targetHeight); - - // immediately update play space scale - PlayerSetup.Instance.CheckUpdateAvatarScaleToPlaySpaceRelation(); + _localAvatarScaler.SetTargetHeight(_lastTargetHeight); + _localAvatarScaler.heightNeedsUpdate = true; // only local scaler forces update } - - public void ResetHeight() + + public void ResetTargetHeight() { if (_localAvatarScaler == null) return; - if (!_localAvatarScaler.IsHeightAdjustedFromInitial()) + if (!_localAvatarScaler.IsForcingHeight()) 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!", "Universal Scaling is now disabled."); - SetHeight(-1f); + SetTargetHeight(-1f); } public float GetHeight() @@ -138,10 +211,21 @@ public class AvatarScaleManager : MonoBehaviour if (_localAvatarScaler == null) return PlayerAvatarPoint.defaultAvatarHeight; - if (!_localAvatarScaler.IsHeightAdjustedFromInitial()) + if (!_localAvatarScaler.IsForcingHeight()) 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() @@ -152,10 +236,10 @@ public class AvatarScaleManager : MonoBehaviour if (_localAvatarScaler == null) return -1f; - if (!_localAvatarScaler.IsHeightAdjustedFromInitial()) + if (!_localAvatarScaler.IsForcingHeight()) return -1f; - return _localAvatarScaler.GetHeight(); + return _localAvatarScaler.GetTargetHeight(); } public float GetInitialHeight() @@ -168,17 +252,23 @@ public class AvatarScaleManager : MonoBehaviour public bool IsHeightAdjustedFromInitial() { - return _localAvatarScaler != null && _localAvatarScaler.IsHeightAdjustedFromInitial(); + return _localAvatarScaler != null && _localAvatarScaler.IsForcingHeight(); } #endregion #region Network Methods + public bool DoesNetworkHeightScalerExist(string playerId) + => _networkedScalers.ContainsKey(playerId); + + public int GetNetworkHeightScalerCount() + => _networkedScalers.Count; + public float GetNetworkHeight(string playerId) { if (_networkedScalers.TryGetValue(playerId, out NetworkScaler scaler)) - if (scaler.IsHeightAdjustedFromInitial()) return scaler.GetHeight(); + if (scaler.IsForcingHeight()) return scaler.GetTargetHeight(); //doesn't have mod or has no custom height, get from player avatar directly CVRPlayerEntity playerEntity = CVRPlayerManager.Instance.NetworkPlayers.Find((players) => players.Uuid == playerId); @@ -266,4 +356,40 @@ public class AvatarScaleManager : MonoBehaviour } #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 } \ No newline at end of file diff --git a/AvatarScale/AvatarScaling/Components/BaseScaler.cs b/AvatarScale/AvatarScaling/Components/BaseScaler.cs index 4f108ee..91cf4dc 100644 --- a/AvatarScale/AvatarScaling/Components/BaseScaler.cs +++ b/AvatarScale/AvatarScaling/Components/BaseScaler.cs @@ -1,5 +1,5 @@ -using ABI_RC.Core; -using ABI_RC.Core.Player; +using System.Diagnostics; +using ABI_RC.Core; using NAK.AvatarScaleMod.AvatarScaling; using NAK.AvatarScaleMod.ScaledComponents; using UnityEngine; @@ -12,143 +12,201 @@ public class BaseScaler : MonoBehaviour { #region Constants - public const string ScaleFactorParameterName = "ScaleFactor"; - public const string ScaleFactorParameterNameLocal = "#ScaleFactor"; + protected const string ScaleFactorParameterName = "ScaleFactor"; + protected const string ScaleFactorParameterNameLocal = "#" + ScaleFactorParameterName; #endregion + #region Events + + // OnAnimatedHeightChanged + public delegate void AnimatedHeightChangedDelegate(BaseScaler scaler); + public event AnimatedHeightChangedDelegate OnAnimatedHeightChanged; + + // OnAnimatedHeightOverride + public delegate void AnimatedHeightOverrideDelegate(BaseScaler scaler); + public event AnimatedHeightOverrideDelegate OnAnimatedHeightOverride; + + // OnTargetHeightChanged + public delegate void TargetHeightChangedDelegate(BaseScaler scaler); + public event TargetHeightChangedDelegate OnTargetHeightChanged; + + // OnHeightReset + public delegate void HeightResetDelegate(BaseScaler scaler); + public event HeightResetDelegate OnTargetHeightReset; + + // ------------------------------------------------ + + protected void InvokeAnimatedHeightChanged() + => OnAnimatedHeightChanged?.Invoke(this); + + protected void InvokeAnimatedHeightOverride() + => OnAnimatedHeightOverride?.Invoke(this); + + protected void InvokeTargetHeightChanged() + => OnTargetHeightChanged?.Invoke(this); + + protected void InvokeTargetHeightReset() + => OnTargetHeightReset?.Invoke(this); + + #endregion + #region Variables - internal bool _isAvatarInstantiated; - internal bool _isHeightAdjustedFromInitial; - internal bool _heightNeedsUpdate; + // Height update requested + public bool heightNeedsUpdate { get; internal set; } + + // 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 CVRAnimatorManager _animatorManager; - + + // Initial scaling internal float _initialHeight; internal Vector3 _initialScale; + // Forced scaling (Universal & Hidden Avatar) internal float _targetHeight = -1; internal Vector3 _targetScale = Vector3.one; internal float _scaleFactor = 1f; - - // detection for animation clip-based scaling - internal Vector3 _legacyAnimationScale; - - #endregion - - #region Public Methods + // AnimationClip-based scaling (Local Avatar) + internal float _animatedHeight; + internal Vector3 _animatedScale; + internal float _animatedScaleFactor = 1f; + + #endregion + + #region Avatar Events + public virtual void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale) { if (_isAvatarInstantiated) return; _isAvatarInstantiated = true; - _initialHeight = Mathf.Clamp(initialHeight, 0.01f, 100f); - _initialScale = initialScale; + _initialHeight = _animatedHeight = Mathf.Clamp(initialHeight, 0.01f, 100f); + _initialScale = _animatedScale = initialScale; + _animatedScaleFactor = 1f; + + if (!_shouldForceHeight) // not universal or hidden avatar + { + _targetHeight = _initialHeight; + _targetScale = _initialScale; + _scaleFactor = 1f; + } + _avatarTransform = avatarObject.transform; + + Stopwatch stopwatch = new(); + stopwatch.Start(); + + FindComponentsOfType(scalableComponentTypes); + + stopwatch.Stop(); + if (ModSettings.Debug_ComponentSearchTime.Value) + AvatarScaleMod.Logger.Msg($"({typeof(LocalScaler)}) Component search time for {avatarObject}: {stopwatch.ElapsedMilliseconds}ms"); } public void OnAvatarDestroyed() { if (!_isAvatarInstantiated) return; _isAvatarInstantiated = false; - + _avatarTransform = null; - _heightNeedsUpdate = false; + heightNeedsUpdate = false; 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) { - if (_isHeightAdjustedFromInitial - && Math.Abs(height - _targetHeight) < float.Epsilon) - return; - if (height < float.Epsilon) { - ResetHeight(); + ResetTargetHeight(); return; } - - if (!_isHeightAdjustedFromInitial) - _legacyAnimationScale = Vector3.zero; - - _isHeightAdjustedFromInitial = true; _targetHeight = Mathf.Clamp(height, AvatarScaleManager.MinHeight, AvatarScaleManager.MaxHeight); - _heightNeedsUpdate = true; - - UpdateScaleIfInstantiated(); - } - - public void ResetHeight() - { - if (!_isHeightAdjustedFromInitial) return; - _isHeightAdjustedFromInitial = false; - - if (Math.Abs(_initialHeight - _targetHeight) < float.Epsilon) - return; - - _legacyAnimationScale = Vector3.zero; + _scaleFactor = Mathf.Max(_targetHeight / _initialHeight, 0.01f); //safety + _targetScale = _initialScale * _scaleFactor; - _targetHeight = _initialHeight; - _heightNeedsUpdate = true; - - UpdateScaleIfInstantiated(); + InvokeTargetHeightChanged(); } - public float GetHeight() => _targetHeight; - public float GetInitialHeight() => _initialHeight; - public bool IsHeightAdjustedFromInitial() => _isHeightAdjustedFromInitial; + public void ResetTargetHeight() + { + // if (Math.Abs(_initialHeight - _targetHeight) < float.Epsilon) + // return; // no need to change, is close enough + + useTargetHeight = false; + + _targetHeight = _animatedHeight; + _targetScale = _animatedScale; + _scaleFactor = _animatedScaleFactor; + + InvokeTargetHeightReset(); + } + + public bool ApplyTargetHeight() + { + if (!_isAvatarInstantiated || _initialHeight == 0) + return false; + + if (_avatarTransform == null) + return false; + + heightNeedsUpdate = false; + + ScaleAvatarRoot(); + UpdateAnimatorParameter(); + ApplyComponentScaling(); + return true; + } #endregion - + #region Private Methods - internal void ScaleAvatarRoot() + private void ScaleAvatarRoot() { if (_avatarTransform == null) return; _avatarTransform.localScale = _targetScale; } - - internal virtual void UpdateAnimatorParameter() + + protected virtual void UpdateAnimatorParameter() { // 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 - - #region Unity Methods + + #region Unity Events public virtual void LateUpdate() { - if (!_isHeightAdjustedFromInitial) - return; + if (!_isAvatarInstantiated) + return; // no avatar - if (!_isAvatarInstantiated) - return; - - ScaleAvatarRoot(); // override animationclip-based scaling + if (!_shouldForceHeight) + return; // not universal scaling or hidden avatar + + ScaleAvatarRoot(); } internal virtual void OnDestroy() @@ -169,13 +227,13 @@ public class BaseScaler : MonoBehaviour typeof(PositionConstraint), typeof(ScaleConstraint) }; - - private readonly List _scaledLights = new List(); - private readonly List _scaledAudioSources = new List(); - private readonly List _scaledParentConstraints = new List(); - private readonly List _scaledPositionConstraints = new List(); - private readonly List _scaledScaleConstraints = new List(); - + + private readonly List _scaledLights = new(); + private readonly List _scaledAudioSources = new(); + private readonly List _scaledParentConstraints = new(); + private readonly List _scaledPositionConstraints = new(); + private readonly List _scaledScaleConstraints = new(); + private void ClearComponentLists() { _scaledLights.Clear(); @@ -184,10 +242,9 @@ public class BaseScaler : MonoBehaviour _scaledPositionConstraints.Clear(); _scaledScaleConstraints.Clear(); } - - internal async Task FindComponentsOfTypeAsync(Type[] types) + + internal void FindComponentsOfType(Type[] types) { - var tasks = new List(); var components = _avatarTransform.gameObject.GetComponentsInChildren(true); foreach (Component component in components) @@ -195,19 +252,12 @@ public class BaseScaler : MonoBehaviour if (this == null) break; if (component == null) continue; - tasks.Add(Task.Run(() => - { - Type componentType = component.GetType(); - if (types.Contains(componentType)) - { - AddScaledComponent(componentType, component); - } - })); + Type componentType = component.GetType(); + if (types.Contains(componentType)) + AddScaledComponent(componentType, component); } - - await Task.WhenAll(tasks); } - + private void AddScaledComponent(Type type, Component component) { switch (type) @@ -229,7 +279,7 @@ public class BaseScaler : MonoBehaviour break; } } - + private void ApplyComponentScaling() { // UpdateLightScales(); // might break dps @@ -273,6 +323,6 @@ public class BaseScaler : MonoBehaviour foreach (ScaledScaleConstraint scaleConstraint in _scaledScaleConstraints) scaleConstraint.Scale(_scaleFactor); } - + #endregion } \ No newline at end of file diff --git a/AvatarScale/AvatarScaling/Components/LocalScaler.cs b/AvatarScale/AvatarScaling/Components/LocalScaler.cs index 27ba198..e86b8d8 100644 --- a/AvatarScale/AvatarScaling/Components/LocalScaler.cs +++ b/AvatarScale/AvatarScaling/Components/LocalScaler.cs @@ -1,6 +1,5 @@ using ABI_RC.Core.Player; using ABI_RC.Core.UI; -using ABI.CCK.Components; using NAK.AvatarScaleMod.AvatarScaling; using UnityEngine; @@ -14,31 +13,23 @@ public class LocalScaler : BaseScaler { _animatorManager = GetComponentInParent().animatorManager; - _heightNeedsUpdate = false; + heightNeedsUpdate = false; _isAvatarInstantiated = false; - _isHeightAdjustedFromInitial = false; } #endregion #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) return; 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) return; @@ -49,14 +40,8 @@ public class LocalScaler : BaseScaler public override void LateUpdate() { - if (!_isHeightAdjustedFromInitial) - return; - - if (!_isAvatarInstantiated) - return; - if (!CheckForAnimationScaleChange()) - ScaleAvatarRoot(); + base.LateUpdate(); } #endregion @@ -65,30 +50,43 @@ public class LocalScaler : BaseScaler private bool CheckForAnimationScaleChange() { - if (_avatarTransform == null) return false; + if (_avatarTransform == null) + return false; - //scale matches last recorded animation scale - if (_avatarTransform.localScale == _legacyAnimationScale) + Vector3 localScale = _avatarTransform.localScale; + + // scale matches last recorded animation scale + if (localScale == _animatedScale) return false; // avatar may not have scale animation, check if it isn't equal to targetScale - if (_avatarTransform.localScale == _targetScale) + if (localScale == _targetScale) return false; - // scale was likely reset or not initiated - if (_legacyAnimationScale == Vector3.zero) + // this is the first time we've seen the avatar animated scale, record it! + if (_animatedScale == Vector3.zero) { - _legacyAnimationScale = _avatarTransform.localScale; + _animatedScale = localScale; 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; } - + #endregion } \ No newline at end of file diff --git a/AvatarScale/AvatarScaling/Components/NetworkScaler.cs b/AvatarScale/AvatarScaling/Components/NetworkScaler.cs index 842500b..039f159 100644 --- a/AvatarScale/AvatarScaling/Components/NetworkScaler.cs +++ b/AvatarScale/AvatarScaling/Components/NetworkScaler.cs @@ -1,4 +1,5 @@ -using ABI_RC.Core.Player; +using System.Diagnostics; +using ABI_RC.Core.Player; using NAK.AvatarScaleMod.AvatarScaling; using UnityEngine; @@ -16,28 +17,34 @@ public class NetworkScaler : BaseScaler _animatorManager = GetComponentInParent().animatorManager; - _heightNeedsUpdate = false; + heightNeedsUpdate = false; _isAvatarInstantiated = false; - _isHeightAdjustedFromInitial = false; } #endregion #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) return; base.OnAvatarInstantiated(avatarObject, initialHeight, initialScale); - await FindComponentsOfTypeAsync(scalableComponentTypes); + + Stopwatch stopwatch = new(); + stopwatch.Start(); + FindComponentsOfType(scalableComponentTypes); + stopwatch.Stop(); + if (ModSettings.Debug_ComponentSearchTime.Value) + AvatarScaleMod.Logger.Msg($"({typeof(NetworkScaler)}) Component search time for {avatarObject}: {stopwatch.ElapsedMilliseconds}ms"); - if (_isHeightAdjustedFromInitial && _heightNeedsUpdate) - UpdateScaleIfInstantiated(); + // 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); } diff --git a/AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs b/AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs new file mode 100644 index 0000000..e62c3a4 --- /dev/null +++ b/AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs @@ -0,0 +1,105 @@ +using NAK.AvatarScaleMod.Components; + +namespace NAK.AvatarScaleMod.AvatarScaling; + +public static class AvatarScaleEvents +{ + #region Local Avatar Scaling Events + + /// + /// Invoked when the local avatar's height changes for any reason. + /// + public static readonly AvatarScaleEvent OnLocalAvatarHeightChanged = new(); + + /// + /// Invoked when the local avatar's animated height changes. + /// + public static readonly AvatarScaleEvent OnLocalAvatarAnimatedHeightChanged = new(); + + /// + /// Invoked when the local avatar's target height changes. + /// + public static readonly AvatarScaleEvent OnLocalAvatarTargetHeightChanged = new(); + + /// + /// Invoked when the local avatar's height is reset. + /// + public static readonly AvatarScaleEvent OnLocalAvatarHeightReset = new(); + + #endregion + + + #region Avatar Scaling Events + + + + /// + /// Invoked when a remote avatar's height changes. + /// + public static readonly AvatarScaleEvent OnRemoteAvatarHeightChanged = new(); + + /// + /// Invoked when a remote avatar's height is reset. + /// + public static readonly AvatarScaleEvent OnRemoteAvatarHeightReset = new(); + + #endregion + + #region Event Classes + + public class AvatarScaleEvent + { + private Action _listener = arg => { }; + + public void AddListener(Action listener) => _listener += listener; + public void RemoveListener(Action listener) => _listener -= listener; + + public void Invoke(T arg) + { + var invokeList = _listener.GetInvocationList(); + foreach (Delegate method in invokeList) + { + if (method is not Action 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 + { + private Action _listener = (arg1, arg2) => { }; + + public void AddListener(Action listener) => _listener += listener; + public void RemoveListener(Action listener) => _listener -= listener; + + public void Invoke(T1 arg1, T2 arg2) + { + var invokeList = _listener.GetInvocationList(); + foreach (Delegate method in invokeList) + { + if (method is not Action 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 +} \ No newline at end of file diff --git a/AvatarScale/AvatarScaling/ScaledComponents.cs b/AvatarScale/AvatarScaling/ScaledComponents.cs index 05134df..a2c14eb 100644 --- a/AvatarScale/AvatarScaling/ScaledComponents.cs +++ b/AvatarScale/AvatarScaling/ScaledComponents.cs @@ -48,7 +48,6 @@ public class ScaledLight public void Reset() { Component.range = InitialRange; - } } diff --git a/AvatarScale/HarmonyPatches.cs b/AvatarScale/HarmonyPatches.cs index cafc3a3..7a9743c 100644 --- a/AvatarScale/HarmonyPatches.cs +++ b/AvatarScale/HarmonyPatches.cs @@ -46,6 +46,7 @@ internal class PlayerSetupPatches { try { + if (__instance == null) return; // this is called when the game is closed AvatarScaleManager.Instance.OnAvatarDestroyed(__instance); } catch (Exception e) @@ -80,6 +81,7 @@ internal class PuppetMasterPatches { try { + if (__instance == null) return; // this is called when the game is closed AvatarScaleManager.Instance.OnNetworkAvatarDestroyed(__instance); } catch (Exception e) diff --git a/AvatarScale/Input/DebugKeybinds.cs b/AvatarScale/Input/DebugKeybinds.cs index aa6835e..4777e41 100644 --- a/AvatarScale/Input/DebugKeybinds.cs +++ b/AvatarScale/Input/DebugKeybinds.cs @@ -30,14 +30,14 @@ internal static class DebugKeybinds { float currentHeight = AvatarScaleManager.Instance.GetHeight() + adjustment; currentHeight = Mathf.Max(0f, currentHeight); - AvatarScaleManager.Instance.SetHeight(currentHeight); + AvatarScaleManager.Instance.SetTargetHeight(currentHeight); AvatarScaleMod.Logger.Msg($"[Debug] Setting height: {currentHeight}"); } private static void ResetHeight() { - AvatarScaleManager.Instance.ResetHeight(); + AvatarScaleManager.Instance.ResetTargetHeight(); AvatarScaleMod.Logger.Msg("[Debug] Resetting height."); } } \ No newline at end of file diff --git a/AvatarScale/Input/ScaleReconizer.cs b/AvatarScale/Input/ScaleReconizer.cs index 7bf74de..c28d2f7 100644 --- a/AvatarScale/Input/ScaleReconizer.cs +++ b/AvatarScale/Input/ScaleReconizer.cs @@ -92,7 +92,7 @@ public static class ScaleReconizer float heightAdjustmentFactor = (modifierRatio > 1) ? 1 + (modifierRatio - 1) : 1 - (1 - modifierRatio); // 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) diff --git a/AvatarScale/Integrations/BTKUI/BTKUIAddon.cs b/AvatarScale/Integrations/BTKUI/BTKUIAddon.cs deleted file mode 100644 index 6e276c4..0000000 --- a/AvatarScale/Integrations/BTKUI/BTKUIAddon.cs +++ /dev/null @@ -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(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 entry) - { - category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b; - } - - internal static void AddMelonSlider(ref Page page, MelonPreferences_Entry 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 - } -} \ No newline at end of file diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon.cs b/AvatarScale/Integrations/BTKUI/BtkUiAddon.cs new file mode 100644 index 0000000..2196cdd --- /dev/null +++ b/AvatarScale/Integrations/BTKUI/BtkUiAddon.cs @@ -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 + } +} \ No newline at end of file diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs b/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs new file mode 100644 index 0000000..875668c --- /dev/null +++ b/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleMod.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs b/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs new file mode 100644 index 0000000..2eb6c57 --- /dev/null +++ b/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_AvatarScaleTool.cs @@ -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(); + }; + } + } +} \ No newline at end of file diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs b/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs new file mode 100644 index 0000000..b05dd94 --- /dev/null +++ b/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_DebugOptions.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs b/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs new file mode 100644 index 0000000..3dff0b4 --- /dev/null +++ b/AvatarScale/Integrations/BTKUI/BtkUiAddon_CAT_UniversalScalingSettings.cs @@ -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 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 + } +} \ No newline at end of file diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs b/AvatarScale/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs new file mode 100644 index 0000000..c83796d --- /dev/null +++ b/AvatarScale/Integrations/BTKUI/BtkUiAddon_PSP_AvatarScaleMod.cs @@ -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 + } +} \ No newline at end of file diff --git a/AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs b/AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs new file mode 100644 index 0000000..473dc45 --- /dev/null +++ b/AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs @@ -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 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 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 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 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 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; + // } + + /// + /// Helper method to create a category that saves its collapsed state to a MelonPreferences entry. + /// + /// + /// + /// + /// + private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry 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 + } +} \ No newline at end of file diff --git a/AvatarScale/ModSettings.cs b/AvatarScale/ModSettings.cs index f18cefd..16256c1 100644 --- a/AvatarScale/ModSettings.cs +++ b/AvatarScale/ModSettings.cs @@ -10,43 +10,113 @@ internal static class ModSettings { // Constants internal const string ModName = nameof(AvatarScaleMod); - internal const string SettingsCategory = "Avatar Scale Mod"; - - public static readonly MelonPreferences_Category Category = + internal const string ASM_SettingsCategory = "Avatar Scale Mod"; + internal const string AST_SettingsCategory = "Avatar Scale Tool Support"; + internal const string USM_SettingsCategory = "Universal Scaling (Mod Network)"; + internal const string DEBUG_SettingsCategory = "Debug Options"; + + private static readonly MelonPreferences_Category Category = MelonPreferences.CreateCategory(ModName); + + #region Hidden Foldout Entries + + // Avatar Scale Mod Foldout + internal static readonly MelonPreferences_Entry 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 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."); - public static readonly MelonPreferences_Entry EntryUseUniversalScaling = - Category.CreateEntry("use_universal_scaling", true, display_name: "Use Universal Scaling", description: "Enable or disable universal scaling."); + // Universal Scaling (Mod Network) Foldout + internal static readonly MelonPreferences_Entry 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."); - public static readonly MelonPreferences_Entry EntryPersistantHeight = - Category.CreateEntry("persistant_height", false, display_name: "Persistant Height", description: "Should the avatar height persist between avatar switches?"); + // Debug Options Foldout + internal static readonly MelonPreferences_Entry 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 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 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 EntryDebugNetworkInbound = + public static readonly MelonPreferences_Entry EntryScaleKeybindingsEnabled = + Category.CreateEntry("scale_keybindings_enabled", true, display_name: "Scale Keybindings", description: "Enable or disable scale keybindings."); + + public static readonly MelonPreferences_Entry EntryPersistentHeight = + Category.CreateEntry("persistent_height", false, display_name: "Persistent Height", description: "Should the avatar height persist between avatar switches?"); + + public static readonly MelonPreferences_Entry 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 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 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 EntryASTMinHeight = + Category.CreateEntry("override_min_height", 0.25f, display_name: "Override Min Height", description: "Override the minimum height."); + + public static readonly MelonPreferences_Entry 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 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 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 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 Debug_NetworkInbound = Category.CreateEntry("debug_inbound", false, display_name: "Debug Inbound", description: "Log inbound Mod Network height updates."); - public static readonly MelonPreferences_Entry EntryDebugNetworkOutbound = + public static readonly MelonPreferences_Entry Debug_NetworkOutbound = Category.CreateEntry("debug_outbound", false, display_name: "Debug Outbound", description: "Log outbound Mod Network height updates."); - public static readonly MelonPreferences_Entry EntryHiddenLastAvatarScale = - Category.CreateEntry("last_avatar_scale", -1f, is_hidden: true); - + public static readonly MelonPreferences_Entry Debug_ComponentSearchTime = + Category.CreateEntry("debug_component_search_time", false, display_name: "Debug Search Time", description: "Log component search time."); + + #endregion + public static void Initialize() { - foreach (MelonPreferences_Entry entry in Category.Entries) - entry.OnEntryValueChangedUntyped.Subscribe(OnSettingsChanged); + // subscribe to all bool settings that aren't hidden + foreach (MelonPreferences_Entry entry in Category.Entries.Where(entry => entry.GetReflectedType() == typeof(bool) && !entry.IsHidden)) + entry.OnEntryValueChangedUntyped.Subscribe(OnSettingsBoolChanged); } - private static void 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; + ModNetwork.Debug_NetworkInbound = Debug_NetworkInbound.Value; + ModNetwork.Debug_NetworkOutbound = Debug_NetworkOutbound.Value; - ModNetwork.Debug_NetworkInbound = EntryDebugNetworkInbound.Value; - ModNetwork.Debug_NetworkOutbound = EntryDebugNetworkOutbound.Value; + if (AvatarScaleManager.Instance == null) return; + AvatarScaleManager.Instance.Setting_UniversalScaling = EntryUseUniversalScaling.Value; + AvatarScaleManager.Instance.Setting_AnimationClipScalingOverride = EntryAnimationScalingOverride.Value; + AvatarScaleManager.Instance.Setting_PersistentHeight = EntryPersistentHeight.Value; } -} +} \ No newline at end of file diff --git a/AvatarScale/Networking/ModNetwork.cs b/AvatarScale/Networking/ModNetwork.cs index a94147d..32a0e18 100644 --- a/AvatarScale/Networking/ModNetwork.cs +++ b/AvatarScale/Networking/ModNetwork.cs @@ -1,7 +1,6 @@ using ABI_RC.Core.Networking; using ABI_RC.Systems.ModNetwork; using DarkRift; -using MelonLoader; using NAK.AvatarScaleMod.AvatarScaling; using UnityEngine; @@ -32,7 +31,7 @@ public static class ModNetwork public string TargetPlayer { get; set; } public string Sender { get; set; } } - + #endregion #region Private State @@ -62,10 +61,15 @@ public static class ModNetwork internal static void Subscribe() { - _isSubscribedToModNetwork = true; ModNetworkManager.Subscribe(ModId, OnMessageReceived); + + _isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId); + if (!_isSubscribedToModNetwork) + AvatarScaleMod.Logger.Error("Failed to subscribe to Mod Network! This is a critical error! Please report this to the mod author! (NAK) (ModNetwork.cs) (Subscribe) (Line 150) (AvatarScaleMod) (AvatarScale) (MelonLoader) (MelonLoader.Mods) (MelonLoader.Mods.MelonMod) (MelonLoader.MelonMod) (MelonLoader.MelonMod.MelonBaseMod) (MelonLoader.MelonMod.MelonMod) (MelonLoader.MelonMod.MelonModBase) (MelonLoader.MelonMod.MelonModBase`1) (MelonLoader.MelonMod.MelonModBase`1[[NAK.AvatarScaleMod.AvatarScaling.AvatarScaleMod, NAK.AvatarScaleMod, Version=123"); + + AvatarScaleEvents.OnLocalAvatarHeightChanged.AddListener(scaler => SendNetworkHeight(scaler.GetTargetHeight())); } - + internal static void Update() { if (!_isSubscribedToModNetwork) @@ -244,4 +248,36 @@ public static class ModNetwork } #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 } \ No newline at end of file diff --git a/DesktopVRSwitch/HarmonyPatches.cs b/DesktopVRSwitch/HarmonyPatches.cs index e32f5ca..078663b 100644 --- a/DesktopVRSwitch/HarmonyPatches.cs +++ b/DesktopVRSwitch/HarmonyPatches.cs @@ -1,137 +1,26 @@ -using ABI.CCK.Components; -using ABI_RC.Core.Savior; -using ABI_RC.Core.Util.Object_Behaviour; -using ABI_RC.Systems.IK; -using ABI_RC.Systems.IK.TrackingModules; -using cohtml.Net; +using ABI_RC.Core; +using ABI_RC.Systems.InputManagement; using HarmonyLib; -using NAK.DesktopVRSwitch.Patches; -using NAK.DesktopVRSwitch.VRModeTrackers; -using UnityEngine; -using Valve.VR; +using NativeVRModeSwitchManager = ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager; namespace NAK.DesktopVRSwitch.HarmonyPatches; -internal class CheckVRPatches +internal class CVRInputManagerPatches { [HarmonyPostfix] - [HarmonyPatch(typeof(CheckVR), nameof(CheckVR.Awake))] - private static void Postfix_CheckVR_Start(ref CheckVR __instance) + [HarmonyPatch(typeof(CVRInputManager), "OnPostVRModeSwitch")] + private static void Postfix_CVRInputManager_OnPostVRModeSwitch(bool inVr, UnityEngine.Camera playerCamera) { - try - { - __instance.gameObject.AddComponent(); - } - catch (Exception e) - { - DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_CheckVR_Start)}"); - DesktopVRSwitch.Logger.Error(e); - } + RootLogic.Instance.ToggleMouse(inVr); } } -internal class IKSystemPatches -{ - [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(); - } - catch (Exception e) - { - DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Postfix_CameraFacingObject_Start)}"); - DesktopVRSwitch.Logger.Error(e); - } - } -} - -internal class CVRPickupObjectPatches +internal class VRModeSwitchManagerPatches { [HarmonyPrefix] - [HarmonyPatch(typeof(CVRPickupObject), nameof(CVRPickupObject.Start))] - private static void Prefix_CVRPickupObject_Start(ref CVRPickupObject __instance) + [HarmonyPatch(typeof(NativeVRModeSwitchManager), "StartSwitchInternal")] + private static void Postfix_CVRInputManager_OnPostVRModeSwitch() { - try - { - 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(); - 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; + CVRInputManager.Instance.inputEnabled = false; } } \ No newline at end of file diff --git a/DesktopVRSwitch/Main.cs b/DesktopVRSwitch/Main.cs index 572acb0..162e902 100644 --- a/DesktopVRSwitch/Main.cs +++ b/DesktopVRSwitch/Main.cs @@ -1,84 +1,14 @@ - +using System; using MelonLoader; -using NAK.DesktopVRSwitch.VRModeTrackers; -using UnityEngine; namespace NAK.DesktopVRSwitch; public class DesktopVRSwitch : MelonMod { - internal static MelonLogger.Instance Logger; - public override void OnInitializeMelon() { - Logger = LoggerInstance; - - 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(); + ApplyPatches(typeof(HarmonyPatches.CVRInputManagerPatches)); + ApplyPatches(typeof(HarmonyPatches.VRModeSwitchManagerPatches)); } private void ApplyPatches(Type type) diff --git a/DesktopVRSwitch/VRModeSwitchManager.cs b/DesktopVRSwitch/VRModeSwitchManager.cs index d4ccc24..74b8fdd 100644 --- a/DesktopVRSwitch/VRModeSwitchManager.cs +++ b/DesktopVRSwitch/VRModeSwitchManager.cs @@ -1,32 +1,34 @@ using ABI_RC.Systems.UI; -using NAK.DesktopVRSwitch.VRModeTrackers; 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.Events; using UnityEngine.XR.Management; namespace NAK.DesktopVRSwitch; -public class VRModeSwitchManager : MonoBehaviour + public class VRModeSwitchManager : MonoBehaviour { - #region Static - 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 // Settings + public bool DesktopVRSwitchEnabled; public bool UseWorldTransition = true; public bool ReloadLocalAvatar = true; - + public bool SwitchInProgress { get; private set; } #endregion - + #region Unity Methods private void Awake() @@ -36,73 +38,125 @@ public class VRModeSwitchManager : MonoBehaviour DestroyImmediate(this); return; } + 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 #region Public Methods - private static bool IsInXR() => XRGeneralSettings.Instance.Manager.activeLoader != null; - - public void AttemptSwitch() => StartCoroutine(StartSwitchInternal()); - + public void AttemptSwitch() + { + if (SwitchInProgress) + return; + + // dont allow switching during world transfer, itll explode violently + if (CVRObjectLoader.Instance.IsLoadingWorldToJoin()) + return; + + StartCoroutine(StartSwitchInternal()); + } + #endregion - + #region Private Methods + + private void OnSettingsBoolChanged(string settingName, bool val) + { + if (settingName == "ExperimentalDesktopVRSwitch") + DesktopVRSwitchEnabled = val; + } private IEnumerator StartSwitchInternal() { - if (SwitchInProgress) + if (SwitchInProgress) yield break; - + + NotifyOnPreSwitch(); + bool useWorldTransition = UseWorldTransition; SwitchInProgress = true; - + yield return null; - + if (useWorldTransition) 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) yield return StartCoroutine(ContinueTransition()); 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() { - if (!ReloadLocalAvatar) + if (!ReloadLocalAvatar) return; - - Utils.ClearLocalAvatar(); - Utils.ReloadLocalAvatar(); + + // TODO: Is there a better way to reload only locally? + PlayerSetup.Instance.ClearAvatar(); + AssetManagement.Instance.LoadLocalAvatar(MetaPort.Instance.currentAvatarGuid); } - #endregion + private bool IsInXR() + { + return XRGeneralSettings.Instance.Manager.activeLoader != null; + } + + private UnityEngine.Camera GetPlayerCamera(bool isVr) + { + return (isVr ? PlayerSetup.Instance.vrCamera : PlayerSetup.Instance.desktopCamera) + .GetComponent(); + } + #endregion + #region Transition Coroutines private IEnumerator StartTransition() @@ -123,38 +177,46 @@ public class VRModeSwitchManager : MonoBehaviour #region Event Handling - public class VRModeEventArgs : EventArgs + private void InvokeOnPreSwitch(bool isUsingVr) { - public bool IsUsingVr { get; } - public Camera PlayerCamera { get; } + UnityEngine.Camera playerCamera = GetPlayerCamera(isUsingVr); - public VRModeEventArgs(bool isUsingVr, Camera playerCamera) - { - IsUsingVr = isUsingVr; - PlayerCamera = playerCamera; - } + ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager.OnPreSwitchInternal?.Invoke(isUsingVr, playerCamera); + CVRGameEventSystem.VRModeSwitch.OnPreSwitch?.Invoke(isUsingVr, playerCamera); } - - public static event EventHandler OnPreVRModeSwitch; - public static event EventHandler OnPostVRModeSwitch; - public static event EventHandler OnFailVRModeSwitch; - private void InvokeOnPreSwitch(bool isUsingVr) => SafeInvokeUnityEvent(OnPreVRModeSwitch, isUsingVr); - private void InvokeOnPostSwitch(bool isUsingVr) => SafeInvokeUnityEvent(OnPostVRModeSwitch, isUsingVr); - private void InvokeOnFailedSwitch(bool isUsingVr) => SafeInvokeUnityEvent(OnFailVRModeSwitch, isUsingVr); - - private void SafeInvokeUnityEvent(EventHandler switchEvent, bool isUsingVr) + private void InvokeOnPostSwitch(bool isUsingVr) { - try - { - Camera playerCamera = Utils.GetPlayerCameraObject(isUsingVr).GetComponent(); - switchEvent?.Invoke(this, new VRModeEventArgs(isUsingVr, playerCamera)); - } - catch (Exception e) - { - DesktopVRSwitch.Logger.Error($"Error in event handler: {e}"); - } + UnityEngine.Camera playerCamera = GetPlayerCamera(isUsingVr); + + ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager.OnPostSwitchInternal?.Invoke(isUsingVr, playerCamera); + CVRGameEventSystem.VRModeSwitch.OnPostSwitch?.Invoke(isUsingVr, playerCamera); } - + + private void InvokeOnFailedSwitch(bool isUsingVr) + { + UnityEngine.Camera playerCamera = GetPlayerCamera(isUsingVr); + + ABI_RC.Systems.VRModeSwitch.VRModeSwitchManager.OnFailedSwitchInternal?.Invoke(isUsingVr, playerCamera); + CVRGameEventSystem.VRModeSwitch.OnFailedSwitch?.Invoke(isUsingVr, playerCamera); + } + #endregion -} \ No newline at end of file + + #region Notifications + + private void NotifyOnPreSwitch() + { + 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 +} diff --git a/DesktopVRSwitch/XRHandler.cs b/DesktopVRSwitch/XRHandler.cs index d1219bf..af33587 100644 --- a/DesktopVRSwitch/XRHandler.cs +++ b/DesktopVRSwitch/XRHandler.cs @@ -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 ABI_RC.Core.Savior; using Unity.XR.OpenVR; @@ -6,62 +10,76 @@ using UnityEngine; using UnityEngine.XR.Management; using UnityEngine.XR.OpenXR; using Valve.VR; +using Object = UnityEngine.Object; -namespace NAK.DesktopVRSwitch; - -internal static class XRHandler +namespace ABI_RC.Systems.VRModeSwitch { - internal static IEnumerator StartXR() + internal static class XRHandler { - yield return XRGeneralSettings.Instance.Manager.InitializeLoader(); - - if (XRGeneralSettings.Instance.Manager.activeLoader != null) - XRGeneralSettings.Instance.Manager.StartSubsystems(); - else - yield return StopXR(); - - yield return null; - } - - internal static IEnumerator StopXR() - { - if (!XRGeneralSettings.Instance.Manager.isInitializationComplete) - yield break; - - // Forces SteamVR to reinitialize SteamVR_Input next switch - SteamVR_ActionSet_Manager.DisableAllActionSets(); - SteamVR_Input.initialized = false; - - // Remove SteamVR behaviour & render - UnityEngine.Object.DestroyImmediate(SteamVR_Behaviour.instance.gameObject); - SteamVR.enabled = false; // disposes SteamVR - Patches.SteamVRNullReferencePatch.DestroySteamVRInstancesImmediate(); - - // Disable UnityXR - XRGeneralSettings.Instance.Manager.StopSubsystems(); - XRGeneralSettings.Instance.Manager.DeinitializeLoader(); - - // We don't really need to wait a frame on Stop() - yield return null; - } - - internal static void SwitchLoader() - { - XRLoader item; - - if (!CheckVR.Instance.forceOpenXr) + private static async Task InitializeXRLoader() { - item = ScriptableObject.CreateInstance(); - DesktopVRSwitch.Logger.Msg("Using XR Loader: SteamVR"); + EnsureXRLoader(); + XRGeneralSettings.Instance.Manager.InitializeLoaderSync(); + await Task.Yield(); } - else + + internal static async Task StartXR() { - item = ScriptableObject.CreateInstance(); - DesktopVRSwitch.Logger.Msg("Using XR Loader: OpenXR"); + await InitializeXRLoader(); + + if (XRGeneralSettings.Instance.Manager.activeLoader != null) + XRGeneralSettings.Instance.Manager.StartSubsystems(); + else + await StopXR(); // assuming StopXR is now an async method. + + // 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. } - typeof(XRManagerSettings) - .GetField("m_Loaders", BindingFlags.Instance | BindingFlags.NonPublic) - ?.SetValue(XRGeneralSettings.Instance.Manager, new List { item }); + internal static Task StopXR() + { + if (!XRGeneralSettings.Instance.Manager.isInitializationComplete) + return Task.CompletedTask; + + // Forces SteamVR to reinitialize SteamVR_Input next switch + SteamVR_ActionSet_Manager.DisableAllActionSets(); + SteamVR_Input.initialized = false; + + // Remove SteamVR behaviour & render + Object.DestroyImmediate(SteamVR_Behaviour.instance.gameObject); + SteamVR.enabled = false; // disposes SteamVR + + // Disable UnityXR + XRGeneralSettings.Instance.Manager.StopSubsystems(); + XRGeneralSettings.Instance.Manager.DeinitializeLoader(); + return Task.CompletedTask; + + // If we need to wait for something specific (like a frame), we use Task.Delay or equivalent. + // 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. + } + + private static void EnsureXRLoader() + { + Type selectedLoaderType = !CheckVR.Instance.forceOpenXr ? typeof(OpenVRLoader) : typeof(OpenXRLoader); + + // dont do anything if we already have the loader selected + if (XRGeneralSettings.Instance.Manager.activeLoaders.Count > 0 + && XRGeneralSettings.Instance.Manager.activeLoaders[0].GetType() == selectedLoaderType) + return; + + XRLoader newLoaderInstance = (XRLoader)ScriptableObject.CreateInstance(selectedLoaderType); + FieldInfo field = typeof(XRManagerSettings).GetField("m_Loaders", + 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 currentLoaders) + foreach (XRLoader loader in currentLoaders.Where(loader => loader != null)) Object.Destroy(loader); + + field.SetValue(XRGeneralSettings.Instance.Manager, new List { newLoaderInstance }); + } } } +#endif \ No newline at end of file diff --git a/FuckVivox/FuckVivox.csproj b/FuckVivox/FuckVivox.csproj new file mode 100644 index 0000000..03314a8 --- /dev/null +++ b/FuckVivox/FuckVivox.csproj @@ -0,0 +1,6 @@ + + + + FuckMLA + + diff --git a/FuckVivox/HarmonyPatches.cs b/FuckVivox/HarmonyPatches.cs new file mode 100644 index 0000000..528b214 --- /dev/null +++ b/FuckVivox/HarmonyPatches.cs @@ -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; + } +} \ No newline at end of file diff --git a/FuckVivox/Main.cs b/FuckVivox/Main.cs new file mode 100644 index 0000000..3c7cae7 --- /dev/null +++ b/FuckVivox/Main.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/FuckVivox/Properties/AssemblyInfo.cs b/FuckVivox/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..371c0ff --- /dev/null +++ b/FuckVivox/Properties/AssemblyInfo.cs @@ -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"; +} \ No newline at end of file diff --git a/FuckVivox/VivoxHelpers.cs b/FuckVivox/VivoxHelpers.cs new file mode 100644 index 0000000..d48f210 --- /dev/null +++ b/FuckVivox/VivoxHelpers.cs @@ -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); + } +} \ No newline at end of file diff --git a/FuckVivox/format.json b/FuckVivox/format.json new file mode 100644 index 0000000..fb94300 --- /dev/null +++ b/FuckVivox/format.json @@ -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" +} \ No newline at end of file diff --git a/LateInitComponentHelperHack/LateInitComponentHelperHack.csproj b/LateInitComponentHelperHack/LateInitComponentHelperHack.csproj new file mode 100644 index 0000000..969348d --- /dev/null +++ b/LateInitComponentHelperHack/LateInitComponentHelperHack.csproj @@ -0,0 +1,6 @@ + + + + FixedDeltaTimeHack + + diff --git a/LateInitComponentHelperHack/Main.cs b/LateInitComponentHelperHack/Main.cs new file mode 100644 index 0000000..3687082 --- /dev/null +++ b/LateInitComponentHelperHack/Main.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/LateInitComponentHelperHack/Properties/AssemblyInfo.cs b/LateInitComponentHelperHack/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1255d9f --- /dev/null +++ b/LateInitComponentHelperHack/Properties/AssemblyInfo.cs @@ -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"; +} \ No newline at end of file diff --git a/LateInitComponentHelperHack/format.json b/LateInitComponentHelperHack/format.json new file mode 100644 index 0000000..fb94300 --- /dev/null +++ b/LateInitComponentHelperHack/format.json @@ -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" +} \ No newline at end of file diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln index 9ab0ffa..3230bf5 100644 --- a/NAK_CVR_Mods.sln +++ b/NAK_CVR_Mods.sln @@ -51,9 +51,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzCurls", "EzCurls\EzCurls. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzGrab", "EzGrab\EzGrab.csproj", "{8CBF5378-A4AE-4594-9A13-0066E5D3FE81}" 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 -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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/PortableCameraAdditions/HarmonyPatches.cs b/PortableCameraAdditions/HarmonyPatches.cs index a8740c6..3478534 100644 --- a/PortableCameraAdditions/HarmonyPatches.cs +++ b/PortableCameraAdditions/HarmonyPatches.cs @@ -11,15 +11,15 @@ internal class PortableCameraPatches private static void Postfix_PortableCamera_Start(ref PortableCamera __instance) { //run mod.Setup() instead of registering full mod with icon - VisualMods.CameraAdditions mainMod = new VisualMods.CameraAdditions(); + VisualMods.CameraAdditions mainMod = new (); mainMod.Setup(__instance); } [HarmonyPostfix] [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] diff --git a/PortableCameraAdditions/Properties/AssemblyInfo.cs b/PortableCameraAdditions/Properties/AssemblyInfo.cs index 1bcb633..e5af532 100644 --- a/PortableCameraAdditions/Properties/AssemblyInfo.cs +++ b/PortableCameraAdditions/Properties/AssemblyInfo.cs @@ -27,6 +27,6 @@ using System.Reflection; namespace NAK.PortableCameraAdditions.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.4"; + public const string Version = "1.0.5"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/PortableCameraAdditions/VisualMods/CameraAdditions.cs b/PortableCameraAdditions/VisualMods/CameraAdditions.cs index baf15fe..6a69e3f 100644 --- a/PortableCameraAdditions/VisualMods/CameraAdditions.cs +++ b/PortableCameraAdditions/VisualMods/CameraAdditions.cs @@ -7,12 +7,12 @@ public class CameraAdditions { public static CameraAdditions Instance; - public Camera referenceCamera; - public bool orthographicMode; + private Camera referenceCamera; + private bool orthographicMode; //Should I move these to MelonPrefs? - public bool CopyWorldNearClip = true; - public bool CopyWorldFarClip = true; + private bool CopyWorldNearClip = true; + private bool CopyWorldFarClip = true; private PortableCameraSetting setting_NearClip; private PortableCameraSetting setting_FarClip; @@ -118,29 +118,25 @@ public class CameraAdditions OnUpdateOptionsDisplay(); } - public void OnWorldLoaded(Camera playerCamera) + public void OnWorldLoaded(Camera refCamera) { orthographicMode = false; - referenceCamera = playerCamera; - if (referenceCamera != null) - { - if (CopyWorldNearClip) - { - setting_NearClip.Set(referenceCamera.nearClipPlane); - } - if (CopyWorldFarClip) - { - setting_FarClip.Set(referenceCamera.farClipPlane); - } - } + referenceCamera = refCamera; + if (referenceCamera == null) + return; + + if (CopyWorldNearClip) + setting_NearClip.Set(referenceCamera.nearClipPlane); + + if (CopyWorldFarClip) + setting_FarClip.Set(referenceCamera.farClipPlane); } public void OnUpdateOptionsDisplay(bool expertMode = true) { if (!expertMode) - { return; - } + setting_NearClip.settingsObject.SetActive(!orthographicMode); setting_FarClip.settingsObject.SetActive(!orthographicMode); setting_OrthographicSize.settingsObject.SetActive(orthographicMode); @@ -148,12 +144,11 @@ public class CameraAdditions setting_OrthographicFarClip.settingsObject.SetActive(orthographicMode); } - public void UpdateOrthographicMode() + private void UpdateOrthographicMode() { if (PortableCamera.Instance != null) - { PortableCamera.Instance.cameraComponent.orthographic = orthographicMode; - } + if (orthographicMode) { UpdateCameraSettingFloat("OrthographicNearClip", setting_OrthographicNearClip.Slider.value); @@ -164,10 +159,11 @@ public class CameraAdditions UpdateCameraSettingFloat("NearClip", setting_NearClip.Slider.value); UpdateCameraSettingFloat("FarClip", setting_FarClip.Slider.value); } + OnUpdateOptionsDisplay(); } - public void UpdateCameraSettingBool(string setting, bool value) + private void UpdateCameraSettingBool(string setting, bool value) { 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) { diff --git a/PortableCameraAdditions/format.json b/PortableCameraAdditions/format.json index dad83c5..341f4a5 100644 --- a/PortableCameraAdditions/format.json +++ b/PortableCameraAdditions/format.json @@ -1,8 +1,8 @@ { "_id": 123, "name": "PortableCameraAdditions", - "modversion": "1.0.4", - "gameversion": "2023r172", + "modversion": "1.0.5", + "gameversion": "2023r173", "loaderversion": "0.6.1", "modtype": "Mod", "author": "NotAKidoS", @@ -18,8 +18,8 @@ "requirements": [ "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/", - "changelog": "- Removed F11 bind to fullscreen Portable Camera.\nIt is now a native bind with 2023r172.", + "changelog": "- Fixes for 2023r173.", "embedcolor": "#ffd96a" } \ No newline at end of file diff --git a/PropUndoButton/Main.cs b/PropUndoButton/Main.cs index fae0f2a..f0c0c67 100644 --- a/PropUndoButton/Main.cs +++ b/PropUndoButton/Main.cs @@ -1,11 +1,13 @@ -using ABI.CCK.Components; +using System.Reflection; using ABI_RC.Core.AudioEffects; using ABI_RC.Core.Networking; using ABI_RC.Core.Savior; using ABI_RC.Core.Util; +using ABI_RC.Systems.InputManagement.InputModules; +using ABI.CCK.Components; using DarkRift; +using HarmonyLib; using MelonLoader; -using System.Reflection; using UnityEngine; namespace NAK.PropUndoButton; @@ -14,16 +16,17 @@ namespace NAK.PropUndoButton; public class PropUndoButton : MelonMod { - public static readonly MelonPreferences_Category Category = + public static readonly MelonPreferences_Category Category = MelonPreferences.CreateCategory(nameof(PropUndoButton)); public static readonly MelonPreferences_Entry EntryEnabled = Category.CreateEntry("Enabled", true, description: "Toggle Undo Prop Button."); public static readonly MelonPreferences_Entry EntryUseSFX = - Category.CreateEntry("Use SFX", true, description: "Toggle audio queues for prop spawn, undo, redo, and warning."); - - internal static List deletedProps = new List(); + Category.CreateEntry("Use SFX", true, + description: "Toggle audio queues for prop spawn, undo, redo, and warning."); + + internal static List deletedProps = new(); // audio clip names, InterfaceAudio adds "PropUndo_" prefix 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 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 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 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 - typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeletePropByInstanceId)), - postfix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnDeletePropByInstanceId), BindingFlags.NonPublic | BindingFlags.Static)) + typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteMyPropByInstanceIdOverNetwork)), + 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 - typeof(InputModuleMouseKeyboard).GetMethod(nameof(InputModuleMouseKeyboard.UpdateInput)), - postfix: new HarmonyLib.HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnUpdateInput), BindingFlags.NonPublic | BindingFlags.Static)) + typeof(CVRInputModule_Keyboard).GetMethod(nameof(CVRInputModule_Keyboard.UpdateInput)), + postfix: new HarmonyMethod(typeof(PropUndoButton).GetMethod(nameof(OnUpdateInput), + BindingFlags.NonPublic | BindingFlags.Static)) ); HarmonyInstance.Patch( // clear redo list on world change 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(); @@ -68,7 +77,7 @@ public class PropUndoButton : MelonMod private void SetupDefaultAudioClips() { // 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)) { Directory.CreateDirectory(path); @@ -77,14 +86,14 @@ public class PropUndoButton : MelonMod // 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" }; - 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)) { // read the clip data from embedded resources byte[] clipData = null; - string resourceName = "PropUndoButton.SFX." + clipName; + var resourceName = "PropUndoButton.SFX." + clipName; using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) { clipData = new byte[stream.Length]; @@ -92,7 +101,7 @@ public class PropUndoButton : MelonMod } // 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); } @@ -102,7 +111,10 @@ public class PropUndoButton : MelonMod } } - private static void OnWorldLoad() => deletedProps.Clear(); + private static void OnWorldLoad() + { + deletedProps.Clear(); + } private static void OnUpdateInput() { @@ -111,13 +123,8 @@ public class PropUndoButton : MelonMod if (Input.GetKey(KeyCode.LeftControl)) { if (Input.GetKey(KeyCode.LeftShift) && Input.GetKeyDown(KeyCode.Z)) - { 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; - var propData = GetPropByInstanceIdAndOwnerId(instanceId); + CVRSyncHelper.PropData propData = GetPropByInstanceIdAndOwnerId(instanceId); if (propData == null) return; AddDeletedProp(propData); @@ -150,7 +157,7 @@ public class PropUndoButton : MelonMod if (deletedProps.Count >= redoHistoryLimit) deletedProps.RemoveAt(0); - DeletedProp deletedProp = new DeletedProp(propData); + DeletedProp deletedProp = new(propData); deletedProps.Add(deletedProp); } @@ -159,7 +166,7 @@ public class PropUndoButton : MelonMod { if (!EntryEnabled.Value) return true; - List propsList = GetAllPropsByOwnerId(); + var propsList = GetAllPropsByOwnerId(); if (propsList.Count == 0) { @@ -167,7 +174,7 @@ public class PropUndoButton : MelonMod 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]; SafeDeleteProp(propData); @@ -181,17 +188,17 @@ public class PropUndoButton : MelonMod { if (!EntryEnabled.Value) return true; - CVRSyncHelper.PropData[] propsList = CVRSyncHelper.Props.ToArray(); + var propsList = CVRSyncHelper.Props.ToArray(); if (propsList.Length == 0) { PlayAudioModule(sfx_warn); 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]; - DeleteProp(propData); + SafeDeleteProp(propData); } return false; @@ -199,7 +206,7 @@ public class PropUndoButton : MelonMod private static void UndoProp() { - var propData = GetLatestPropByOwnerId(); + CVRSyncHelper.PropData propData = GetLatestPropByOwnerId(); if (propData == null) { PlayAudioModule(sfx_warn); @@ -211,7 +218,7 @@ public class PropUndoButton : MelonMod public static void RedoProp() { - int index = deletedProps.Count - 1; + var index = deletedProps.Count - 1; if (index < 0) { PlayAudioModule(sfx_warn); @@ -265,74 +272,44 @@ public class PropUndoButton : MelonMod public static void PlayAudioModule(string module) { - if (EntryUseSFX.Value) - { - 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(); - } + if (EntryUseSFX.Value) InterfaceAudio.PlayModule(module); } private static void SafeDeleteProp(CVRSyncHelper.PropData propData) { - //fixes getting props stuck in limbo state if spawn & delete fast - if (propData.Spawnable == null && propData.Wrapper != null) + if (propData.Spawnable == null) { - UnityEngine.Object.DestroyImmediate(propData.Wrapper); + // network delete prop manually, then delete prop data + CVRSyncHelper.DeleteMyPropByInstanceIdOverNetwork(propData.InstanceId); propData.Recycle(); - } - else if (propData.Spawnable != null) - { - propData.Spawnable.Delete(); - } - else - { - PlayAudioModule(sfx_deny); + return; } - //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...? + // delete prop, prop data, and network delete prop + propData.Spawnable.Delete(); } private static bool IsPropSpawnAllowed() { return MetaPort.Instance.worldAllowProps - && MetaPort.Instance.settings.GetSettingsBool("ContentFilterPropsEnabled", false) - && NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected; + && MetaPort.Instance.settings.GetSettingsBool("ContentFilterPropsEnabled") + && NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected; } - public static bool IsAtPropLimit(int limit = 20) + public static bool IsAtPropLimit(int limit = 21) { return GetAllPropsByOwnerId().Count >= limit; } 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() { + // return last prop spawned by owner in CVRSyncHelper.MySpawnedPropInstanceIds return CVRSyncHelper.Props.LastOrDefault(propData => propData.SpawnedBy == MetaPort.Instance.ownerId); } @@ -350,15 +327,24 @@ public class PropUndoButton : MelonMod public DeletedProp(CVRSyncHelper.PropData propData) { - // Offset spawn height so game can account for it later - Transform spawnable = propData.Spawnable.transform; - Vector3 position = spawnable.position; - position.y -= propData.Spawnable.spawnHeight; + if (propData.Spawnable == null) + { + // use original spawn position and rotation / last known position and rotation + 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; - this.position = position; - this.rotation = spawnable.rotation.eulerAngles; - this.timeDeleted = Time.time; + // Offset spawn height so game can account for it later + position.y -= propData.Spawnable.spawnHeight; + } + + propGuid = propData.ObjectId; + timeDeleted = Time.time; } } } \ No newline at end of file diff --git a/PropUndoButton/Properties/AssemblyInfo.cs b/PropUndoButton/Properties/AssemblyInfo.cs index cbfc391..aa7a30c 100644 --- a/PropUndoButton/Properties/AssemblyInfo.cs +++ b/PropUndoButton/Properties/AssemblyInfo.cs @@ -25,6 +25,6 @@ using System.Reflection; namespace NAK.PropUndoButton.Properties; internal static class AssemblyInfoParams { - public const string Version = "1.0.1"; + public const string Version = "1.0.2"; public const string Author = "NotAKidoS"; } \ No newline at end of file diff --git a/SmoothRay/HarmonyPatches.cs b/SmoothRay/HarmonyPatches.cs index 858304b..590def2 100644 --- a/SmoothRay/HarmonyPatches.cs +++ b/SmoothRay/HarmonyPatches.cs @@ -7,9 +7,8 @@ internal class PlayerSetupPatches { [HarmonyPostfix] [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().ray = __instance.leftRay; - __instance.vrRightHandTracker.gameObject.AddComponent().ray = __instance.rightRay; + } } \ No newline at end of file diff --git a/SmoothRay/Main.cs b/SmoothRay/Main.cs index e3d431d..0a591d6 100644 --- a/SmoothRay/Main.cs +++ b/SmoothRay/Main.cs @@ -8,29 +8,6 @@ namespace NAK.SmoothRay; public class SmoothRay : MelonMod { - public static readonly MelonPreferences_Category Category = - MelonPreferences.CreateCategory(nameof(SmoothRay)); - - public static readonly MelonPreferences_Entry EntryEnabled = - Category.CreateEntry("Enable Smoothing", true, - description: "Enable or disable smoothing."); - - public static readonly MelonPreferences_Entry 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 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 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 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() { ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches)); diff --git a/copy_and_nstrip_dll.ps1 b/copy_and_nstrip_dll.ps1 index 7c86d08..ef1d939 100644 --- a/copy_and_nstrip_dll.ps1 +++ b/copy_and_nstrip_dll.ps1 @@ -9,7 +9,7 @@ $cvrExecutable = "ChilloutVR.exe" $cvrDefaultPath = "E:\temp\CVR_Experimental" # 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 $modNames = @("BTKUILib", "BTKSAImmersiveHud", "ActionMenu", "MenuScalePatch", "ChatBox", "ml_prm")