mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-01 22:09:23 +00:00
i dont rememebr
This commit is contained in:
parent
374ab6c11e
commit
86828a94e2
48 changed files with 1637 additions and 841 deletions
|
@ -16,15 +16,6 @@ internal class IKHandlerDesktop : IKHandler
|
|||
|
||||
public override void OnInitializeIk()
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<bool> EntryPlantFeet =
|
||||
Category.CreateEntry("Enforce Plant Feet", true,
|
||||
Category.CreateEntry("Enforce Plant Feet", false ,
|
||||
description: "Forces VRIK Plant Feet enabled to prevent hovering when stopping movement.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryResetFootstepsOnIdle =
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using ABI_RC.Core.IO;
|
||||
using System.Collections;
|
||||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Player.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<string, NetworkScaler> _networkedScalers;
|
||||
|
||||
private Coroutine _heightUpdateCoroutine;
|
||||
private readonly YieldInstruction _heightUpdateYield = new WaitForEndOfFrame();
|
||||
|
||||
#region Universal Scaling Limits
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// To match AvatarScaleTool: https://github.com/NotAKidOnSteam/AvatarScaleTool/tree/main
|
||||
public const float DefaultMinHeight = 0.25f;
|
||||
public const float DefaultMaxHeight = 2.50f;
|
||||
// ReSharper restore MemberCanBePrivate.Global
|
||||
|
||||
// Universal Scaling Limits
|
||||
public static float MinHeight { get; private set; } = DefaultMinHeight;
|
||||
public static float MaxHeight { get; private set; } = DefaultMaxHeight;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Settings
|
||||
|
||||
private bool _settingUniversalScaling;
|
||||
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<LocalScaler>();
|
||||
_localAvatarScaler.Initialize();
|
||||
|
||||
_settingUniversalScaling = ModSettings.EntryUseUniversalScaling.Value;
|
||||
Setting_AnimationClipScalingOverride = ModSettings.EntryAnimationScalingOverride.Value;
|
||||
Setting_PersistentHeight = ModSettings.EntryPersistentHeight.Value;
|
||||
_lastTargetHeight = ModSettings.EntryPersistThroughRestart.Value
|
||||
? ModSettings.EntryHiddenAvatarHeight.Value : -1f; // -1f is default
|
||||
|
||||
// listen for events
|
||||
_localAvatarScaler.OnAnimatedHeightOverride += OnAnimationHeightOverride;
|
||||
|
||||
CVRGameEventSystem.Instance.OnConnected.AddListener(OnInstanceConnected);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_heightUpdateCoroutine != null) StopCoroutine(_heightUpdateCoroutine);
|
||||
_heightUpdateCoroutine = StartCoroutine(HeightUpdateCoroutine());
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_heightUpdateCoroutine != null) StopCoroutine(_heightUpdateCoroutine);
|
||||
_heightUpdateCoroutine = null;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_heightUpdateCoroutine = null;
|
||||
CVRGameEventSystem.Instance.OnConnected.RemoveListener(OnInstanceConnected);
|
||||
|
||||
if (_localAvatarScaler != null) Destroy(_localAvatarScaler);
|
||||
_localAvatarScaler = null;
|
||||
|
||||
foreach (NetworkScaler scaler in _networkedScalers.Values) Destroy(scaler);
|
||||
_networkedScalers.Clear();
|
||||
|
||||
if (Instance == this)
|
||||
Instance = null;
|
||||
}
|
||||
|
||||
// only update the height per scaler once per frame, to prevent spam & jitter
|
||||
// this is to ensure that the height is also set at correct time during frame, no matter when it is called
|
||||
private IEnumerator HeightUpdateCoroutine()
|
||||
{
|
||||
while (enabled)
|
||||
{
|
||||
yield return _heightUpdateYield;
|
||||
|
||||
// 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<LocalScaler>();
|
||||
_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
|
||||
}
|
|
@ -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<ScaledLight> _scaledLights = new List<ScaledLight>();
|
||||
private readonly List<ScaledAudioSource> _scaledAudioSources = new List<ScaledAudioSource>();
|
||||
private readonly List<ScaledParentConstraint> _scaledParentConstraints = new List<ScaledParentConstraint>();
|
||||
private readonly List<ScaledPositionConstraint> _scaledPositionConstraints = new List<ScaledPositionConstraint>();
|
||||
private readonly List<ScaledScaleConstraint> _scaledScaleConstraints = new List<ScaledScaleConstraint>();
|
||||
|
||||
|
||||
private readonly List<ScaledLight> _scaledLights = new();
|
||||
private readonly List<ScaledAudioSource> _scaledAudioSources = new();
|
||||
private readonly List<ScaledParentConstraint> _scaledParentConstraints = new();
|
||||
private readonly List<ScaledPositionConstraint> _scaledPositionConstraints = new();
|
||||
private readonly List<ScaledScaleConstraint> _scaledScaleConstraints = new();
|
||||
|
||||
private void ClearComponentLists()
|
||||
{
|
||||
_scaledLights.Clear();
|
||||
|
@ -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<Task>();
|
||||
var components = _avatarTransform.gameObject.GetComponentsInChildren<Component>(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
|
||||
}
|
|
@ -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<PlayerSetup>().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
|
||||
}
|
|
@ -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<PuppetMaster>().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);
|
||||
}
|
||||
|
|
105
AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs
Normal file
105
AvatarScale/AvatarScaling/Events/AvatarScaleEvents.cs
Normal file
|
@ -0,0 +1,105 @@
|
|||
using NAK.AvatarScaleMod.Components;
|
||||
|
||||
namespace NAK.AvatarScaleMod.AvatarScaling;
|
||||
|
||||
public static class AvatarScaleEvents
|
||||
{
|
||||
#region Local Avatar Scaling Events
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the local avatar's height changes for any reason.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<LocalScaler> OnLocalAvatarHeightChanged = new();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the local avatar's animated height changes.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<LocalScaler> OnLocalAvatarAnimatedHeightChanged = new();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the local avatar's target height changes.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<LocalScaler> OnLocalAvatarTargetHeightChanged = new();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the local avatar's height is reset.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<LocalScaler> OnLocalAvatarHeightReset = new();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Avatar Scaling Events
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a remote avatar's height changes.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<string, NetworkScaler> OnRemoteAvatarHeightChanged = new();
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a remote avatar's height is reset.
|
||||
/// </summary>
|
||||
public static readonly AvatarScaleEvent<string, NetworkScaler> OnRemoteAvatarHeightReset = new();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Classes
|
||||
|
||||
public class AvatarScaleEvent<T>
|
||||
{
|
||||
private Action<T> _listener = arg => { };
|
||||
|
||||
public void AddListener(Action<T> listener) => _listener += listener;
|
||||
public void RemoveListener(Action<T> listener) => _listener -= listener;
|
||||
|
||||
public void Invoke(T arg)
|
||||
{
|
||||
var invokeList = _listener.GetInvocationList();
|
||||
foreach (Delegate method in invokeList)
|
||||
{
|
||||
if (method is not Action<T> action)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
action(arg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error($"Unable to invoke listener, an exception was thrown and not handled: {e}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AvatarScaleEvent<T1, T2>
|
||||
{
|
||||
private Action<T1, T2> _listener = (arg1, arg2) => { };
|
||||
|
||||
public void AddListener(Action<T1, T2> listener) => _listener += listener;
|
||||
public void RemoveListener(Action<T1, T2> listener) => _listener -= listener;
|
||||
|
||||
public void Invoke(T1 arg1, T2 arg2)
|
||||
{
|
||||
var invokeList = _listener.GetInvocationList();
|
||||
foreach (Delegate method in invokeList)
|
||||
{
|
||||
if (method is not Action<T1, T2> action)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
action(arg1, arg2);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
AvatarScaleMod.Logger.Error($"Unable to invoke listener, an exception was thrown and not handled: {e}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
|
@ -48,7 +48,6 @@ public class ScaledLight
|
|||
public void Reset()
|
||||
{
|
||||
Component.range = InitialRange;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
using System.IO;
|
||||
using System.Reflection;
|
||||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.IO;
|
||||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using MelonLoader;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations
|
||||
{
|
||||
public static class BtkUiAddon
|
||||
{
|
||||
private static string _selectedPlayer;
|
||||
|
||||
#region Initialization
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
PrepareIcons();
|
||||
SetupRootPage();
|
||||
SetupPlayerSelectPage();
|
||||
RegisterEventHandlers();
|
||||
}
|
||||
|
||||
private static void PrepareIcons()
|
||||
{
|
||||
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightConfig", GetIconStream("ASM_Icon_AvatarHeightConfig.png"));
|
||||
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightCopy", GetIconStream("ASM_Icon_AvatarHeightCopy.png"));
|
||||
}
|
||||
|
||||
private static void SetupRootPage()
|
||||
{
|
||||
// we only need the page, as we inject our own elements into it aa
|
||||
Page rootPage = new Page(ModSettings.ModName, ModSettings.SettingsCategory, true, "ASM_Icon_AvatarHeightConfig")
|
||||
{
|
||||
MenuTitle = ModSettings.SettingsCategory,
|
||||
MenuSubtitle = "Universal Scaling Settings"
|
||||
};
|
||||
}
|
||||
|
||||
private static void SetupPlayerSelectPage()
|
||||
{
|
||||
// what other things would be worth adding here?
|
||||
Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ModSettings.SettingsCategory, ModSettings.ModName);
|
||||
Button button = category.AddButton("Copy Height", "ASM_Icon_AvatarHeightCopy", "Copy selected players Eye Height.");
|
||||
button.OnPress += OnCopyPlayerHeight;
|
||||
}
|
||||
|
||||
private static void RegisterEventHandlers()
|
||||
{
|
||||
QuickMenuAPI.OnPlayerSelected += (_, id) => _selectedPlayer = id;
|
||||
QuickMenuAPI.OnMenuRegenerate += _ => ScheduleMenuInjection();
|
||||
QuickMenuAPI.OnTabChange += OnTabChange;
|
||||
}
|
||||
|
||||
private static void ScheduleMenuInjection()
|
||||
{
|
||||
CVR_MenuManager.Instance.quickMenu.View.BindCall("asm-AvatarHeightUpdated", new Action<float>(OnAvatarHeightUpdated));
|
||||
SchedulerSystem.AddJob(InjectMenu, 1f, 1f, 1);
|
||||
}
|
||||
|
||||
private static void InjectMenu()
|
||||
{
|
||||
AvatarScaleMod.Logger.Msg("Injecting into our BTKUI AvatarScaleMod page!");
|
||||
string menuJsPath = Path.Combine(Application.streamingAssetsPath, "Cohtml", "UIResources", "AvatarScaleMod", "menu.js");
|
||||
string menuJsContent = File.Exists(menuJsPath) ? File.ReadAllText(menuJsPath) : string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(menuJsContent))
|
||||
{
|
||||
AvatarScaleMod.Logger.Msg("Injecting embedded menu.js included with mod!");
|
||||
CVR_MenuManager.Instance.quickMenu.View._view.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
|
||||
}
|
||||
else
|
||||
{
|
||||
AvatarScaleMod.Logger.Msg($"Injecting development menu.js found in: {menuJsPath}");
|
||||
CVR_MenuManager.Instance.quickMenu.View._view.ExecuteScript(menuJsContent);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static void OnCopyPlayerHeight()
|
||||
{
|
||||
float networkHeight = AvatarScaleManager.Instance.GetNetworkHeight(_selectedPlayer);
|
||||
if (networkHeight < 0) return;
|
||||
AvatarScaleManager.Instance.SetHeight(networkHeight);
|
||||
}
|
||||
|
||||
private static void OnAvatarHeightUpdated(float height)
|
||||
{
|
||||
AvatarScaleManager.Instance.SetHeight(height);
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static DateTime lastTime = DateTime.Now;
|
||||
private static void OnTabChange(string newTab, string previousTab)
|
||||
{
|
||||
if (newTab == "btkUI-AvatarScaleMod-MainPage")
|
||||
{
|
||||
TimeSpan timeDifference = DateTime.Now - lastTime;
|
||||
if (timeDifference.TotalSeconds <= 0.5)
|
||||
{
|
||||
AvatarScaleManager.Instance.ResetHeight();
|
||||
return;
|
||||
}
|
||||
}
|
||||
lastTime = DateTime.Now;
|
||||
}
|
||||
|
||||
private static Stream GetIconStream(string iconName)
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string assemblyName = assembly.GetName().Name;
|
||||
return assembly.GetManifestResourceStream($"{assemblyName}.resources.{iconName}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Melon Pref Helpers
|
||||
|
||||
internal static void AddMelonToggle(ref Category category, MelonPreferences_Entry<bool> entry)
|
||||
{
|
||||
category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b;
|
||||
}
|
||||
|
||||
internal static void AddMelonSlider(ref Page page, MelonPreferences_Entry<float> entry, float min, float max, int decimalPlaces = 2)
|
||||
{
|
||||
page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max, decimalPlaces).OnValueUpdated += f => entry.Value = f;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
100
AvatarScale/Integrations/BTKUI/BtkUiAddon.cs
Normal file
100
AvatarScale/Integrations/BTKUI/BtkUiAddon.cs
Normal file
|
@ -0,0 +1,100 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static Page _asmRootPage;
|
||||
private static string _rootPageElementID;
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
Prepare_Icons();
|
||||
Setup_AvatarScaleModTab();
|
||||
Setup_PlayerSelectPage();
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
private static void Prepare_Icons()
|
||||
{
|
||||
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightConfig",
|
||||
GetIconStream("ASM_Icon_AvatarHeightConfig.png"));
|
||||
QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightCopy",
|
||||
GetIconStream("ASM_Icon_AvatarHeightCopy.png"));
|
||||
}
|
||||
|
||||
private static void Setup_AvatarScaleModTab()
|
||||
{
|
||||
_asmRootPage = new Page(ModSettings.ModName, ModSettings.ASM_SettingsCategory, true, "ASM_Icon_AvatarHeightConfig")
|
||||
{
|
||||
MenuTitle = ModSettings.ASM_SettingsCategory,
|
||||
MenuSubtitle = "Everything Avatar Scaling!"
|
||||
};
|
||||
|
||||
_rootPageElementID = _asmRootPage.ElementID;
|
||||
QuickMenuAPI.OnTabChange += OnTabChange;
|
||||
QuickMenuAPI.UserJoin += OnUserJoinLeave;
|
||||
QuickMenuAPI.UserLeave += OnUserJoinLeave;
|
||||
QuickMenuAPI.OnWorldLeave += OnWorldLeave;
|
||||
|
||||
// Avatar Scale Mod
|
||||
Setup_AvatarScaleModCategory(_asmRootPage);
|
||||
|
||||
// Avatar Scale Tool
|
||||
Setup_AvatarScaleToolCategory(_asmRootPage);
|
||||
|
||||
// Universal Scaling Settings
|
||||
Setup_UniversalScalingSettings(_asmRootPage);
|
||||
|
||||
// Debug Options
|
||||
Setup_DebugOptionsCategory(_asmRootPage);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Player Count Display
|
||||
|
||||
private static void OnWorldLeave()
|
||||
=> UpdatePlayerCountDisplay();
|
||||
|
||||
private static void OnUserJoinLeave(CVRPlayerEntity _)
|
||||
=> UpdatePlayerCountDisplay();
|
||||
|
||||
private static void UpdatePlayerCountDisplay()
|
||||
{
|
||||
if (_asmRootPage == null)
|
||||
return;
|
||||
|
||||
int modUserCount = AvatarScaleManager.Instance.GetNetworkHeightScalerCount();
|
||||
int playerCount = CVRPlayerManager.Instance.NetworkPlayers.Count;
|
||||
_asmRootPage.MenuSubtitle = $"Everything Avatar Scaling! :: ({modUserCount}/{playerCount} players using ASM)";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Double-Click Reset Height
|
||||
|
||||
private static DateTime lastTime = DateTime.Now;
|
||||
|
||||
private static void OnTabChange(string newTab, string previousTab)
|
||||
{
|
||||
if (newTab == _rootPageElementID)
|
||||
{
|
||||
UpdatePlayerCountDisplay();
|
||||
TimeSpan timeDifference = DateTime.Now - lastTime;
|
||||
if (timeDifference.TotalSeconds <= 0.5)
|
||||
{
|
||||
AvatarScaleManager.Instance.ResetTargetHeight();
|
||||
return;
|
||||
}
|
||||
}
|
||||
lastTime = DateTime.Now;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using BTKUILib.UIObjects;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static void Setup_AvatarScaleModCategory(Page page)
|
||||
{
|
||||
Category avScaleModCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_ASM_SettingsCategory);
|
||||
|
||||
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryScaleGestureEnabled);
|
||||
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryScaleKeybindingsEnabled);
|
||||
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryPersistentHeight);
|
||||
AddMelonToggle(ref avScaleModCategory, ModSettings.EntryPersistThroughRestart);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static void Setup_AvatarScaleToolCategory(Page page)
|
||||
{
|
||||
Category avScaleToolCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_AST_SettingsCategory);
|
||||
|
||||
AddMelonStringInput(ref avScaleToolCategory, ModSettings.EntryASTScaleParameter, "icon");
|
||||
AddMelonNumberInput(ref avScaleToolCategory, ModSettings.EntryASTMinHeight, "icon");
|
||||
AddMelonNumberInput(ref avScaleToolCategory, ModSettings.EntryASTMaxHeight, "icon");
|
||||
avScaleToolCategory.AddButton("Reset Overrides", "", "Reset to Avatar Scale Tool default values.", ButtonStyle.TextOnly)
|
||||
.OnPress += () =>{
|
||||
ModSettings.EntryASTScaleParameter.ResetToDefault();
|
||||
ModSettings.EntryASTMinHeight.ResetToDefault();
|
||||
ModSettings.EntryASTMaxHeight.ResetToDefault();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using BTKUILib.UIObjects;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static void Setup_DebugOptionsCategory(Page page)
|
||||
{
|
||||
Category debugCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_DEBUG_SettingsCategory);
|
||||
|
||||
AddMelonToggle(ref debugCategory, ModSettings.Debug_NetworkInbound);
|
||||
AddMelonToggle(ref debugCategory, ModSettings.Debug_NetworkOutbound);
|
||||
AddMelonToggle(ref debugCategory, ModSettings.Debug_ComponentSearchTime);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
using System.Collections.Generic; // Added for list support
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static readonly List<QMUIElement> USM_QmUiElements = new();
|
||||
|
||||
private static void Setup_UniversalScalingSettings(Page page)
|
||||
{
|
||||
Category uniScalingCategory = AddMelonCategory(ref page, ModSettings.Hidden_Foldout_USM_SettingsCategory);
|
||||
|
||||
SliderFloat scaleSlider = AddMelonSlider(ref uniScalingCategory, ModSettings.EntryHiddenAvatarHeight, AvatarScaleManager.DefaultMinHeight, AvatarScaleManager.DefaultMaxHeight);
|
||||
|
||||
Button resetHeightButton = uniScalingCategory.AddButton(ModSettings.EntryUseUniversalScaling.DisplayName, "icon", ModSettings.EntryUseUniversalScaling.Description, ButtonStyle.TextOnly);
|
||||
resetHeightButton.OnPress += () =>
|
||||
{
|
||||
if (ModSettings.EntryUseUniversalScaling.Value)
|
||||
{
|
||||
ModSettings.EntryUseUniversalScaling.Value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
QuickMenuAPI.ShowConfirm("Use Universal Scaling?",
|
||||
"Universal scaling only works when other users also have the mod! Are you sure you want to use Universal Scaling?",
|
||||
() => ModSettings.EntryUseUniversalScaling.Value = true);
|
||||
};
|
||||
|
||||
// Elements that should be disabled when universal scaling is disabled
|
||||
USM_QmUiElements.AddRange(new QMUIElement[]
|
||||
{
|
||||
scaleSlider,
|
||||
AddMelonToggle(ref uniScalingCategory, ModSettings.EntryScaleComponents),
|
||||
AddMelonToggle(ref uniScalingCategory, ModSettings.EntryAnimationScalingOverride)
|
||||
});
|
||||
|
||||
// Events for the slider
|
||||
scaleSlider.OnValueUpdated += OnAvatarHeightSliderChanged;
|
||||
scaleSlider.OnSliderReset += OnAvatarHeightSliderReset;
|
||||
AvatarScaleEvents.OnLocalAvatarAnimatedHeightChanged.AddListener((scaler) =>
|
||||
{
|
||||
scaleSlider.SetSliderValue(scaler.GetTargetHeight());
|
||||
scaleSlider.DefaultValue = scaler.GetAnimatedHeight();
|
||||
});
|
||||
|
||||
// Initial values
|
||||
OnUniversalScalingChanged(ModSettings.EntryUseUniversalScaling.Value);
|
||||
ModSettings.EntryUseUniversalScaling.OnEntryValueChanged.Subscribe((_, newValue) => OnUniversalScalingChanged(newValue));
|
||||
}
|
||||
|
||||
private static void OnUniversalScalingChanged(bool value)
|
||||
{
|
||||
foreach (QMUIElement uiElement in USM_QmUiElements)
|
||||
uiElement.Disabled = !value;
|
||||
}
|
||||
|
||||
#region Slider Events
|
||||
|
||||
private static void OnAvatarHeightSliderChanged(float height)
|
||||
{
|
||||
AvatarScaleManager.Instance.SetTargetHeight(height);
|
||||
}
|
||||
|
||||
private static void OnAvatarHeightSliderReset()
|
||||
{
|
||||
AvatarScaleManager.Instance.ResetTargetHeight();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using NAK.AvatarScaleMod.AvatarScaling;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
private static Button _playerHasModElement;
|
||||
private static string _selectedPlayer;
|
||||
|
||||
private static void Setup_PlayerSelectPage()
|
||||
{
|
||||
QuickMenuAPI.OnPlayerSelected += OnPlayerSelected;
|
||||
|
||||
Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ModSettings.ASM_SettingsCategory, ModSettings.ModName);
|
||||
|
||||
_playerHasModElement = category.AddButton("PLAYER_HAS_MOD", "ASM_Icon_AvatarHeightCopy", "PLAYER_HAS_MOD_TOOLTIP");
|
||||
|
||||
Button button = category.AddButton("Copy Height", "ASM_Icon_AvatarHeightCopy", "Copy selected players Eye Height.");
|
||||
button.OnPress += OnCopyPlayerHeight;
|
||||
}
|
||||
|
||||
#region QM Events
|
||||
|
||||
private static void OnPlayerSelected(string _, string id)
|
||||
{
|
||||
_selectedPlayer = id;
|
||||
UpdatePlayerHasModIcon();
|
||||
}
|
||||
|
||||
private static void OnCopyPlayerHeight()
|
||||
{
|
||||
float networkHeight = AvatarScaleManager.Instance.GetNetworkHeight(_selectedPlayer);
|
||||
if (networkHeight < 0) return;
|
||||
AvatarScaleManager.Instance.SetTargetHeight(networkHeight);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static void UpdatePlayerHasModIcon()
|
||||
{
|
||||
if (_playerHasModElement == null)
|
||||
return;
|
||||
|
||||
if (AvatarScaleManager.Instance.DoesNetworkHeightScalerExist(_selectedPlayer))
|
||||
{
|
||||
_playerHasModElement.ButtonIcon = "ASM_Icon_AvatarHeightCopy";
|
||||
_playerHasModElement.ButtonText = "Player Has Mod";
|
||||
_playerHasModElement.ButtonTooltip = "This player has the Avatar Scale Mod installed!";
|
||||
}
|
||||
else
|
||||
{
|
||||
_playerHasModElement.ButtonIcon = "ASM_Icon_AvatarHeightConfig";
|
||||
_playerHasModElement.ButtonText = "Player Does Not Have Mod";
|
||||
_playerHasModElement.ButtonTooltip = "This player does not have the Avatar Scale Mod installed!";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
78
AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs
Normal file
78
AvatarScale/Integrations/BTKUI/BtkUiAddon_Utils.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using System.Reflection;
|
||||
using BTKUILib;
|
||||
using BTKUILib.UIObjects;
|
||||
using BTKUILib.UIObjects.Components;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.AvatarScaleMod.Integrations
|
||||
{
|
||||
public static partial class BtkUiAddon
|
||||
{
|
||||
#region Melon Preference Helpers
|
||||
|
||||
private static ToggleButton AddMelonToggle(ref Category category, MelonPreferences_Entry<bool> entry)
|
||||
{
|
||||
ToggleButton toggle = category.AddToggle(entry.DisplayName, entry.Description, entry.Value);
|
||||
toggle.OnValueUpdated += b => entry.Value = b;
|
||||
return toggle;
|
||||
}
|
||||
|
||||
private static SliderFloat AddMelonSlider(ref Category category, MelonPreferences_Entry<float> entry, float min,
|
||||
float max, int decimalPlaces = 2, bool allowReset = true)
|
||||
{
|
||||
SliderFloat slider = category.AddSlider(entry.DisplayName, entry.Description,
|
||||
Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset);
|
||||
slider.OnValueUpdated += f => entry.Value = f;
|
||||
return slider;
|
||||
}
|
||||
|
||||
private static Button AddMelonStringInput(ref Category category, MelonPreferences_Entry<string> entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly)
|
||||
{
|
||||
Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle);
|
||||
button.OnPress += () => QuickMenuAPI.OpenKeyboard(entry.Value, s => entry.Value = s);
|
||||
return button;
|
||||
}
|
||||
|
||||
private static Button AddMelonNumberInput(ref Category category, MelonPreferences_Entry<float> entry, string buttonIcon = "", ButtonStyle buttonStyle = ButtonStyle.TextOnly)
|
||||
{
|
||||
Button button = category.AddButton(entry.DisplayName, buttonIcon, entry.Description, buttonStyle);
|
||||
button.OnPress += () => QuickMenuAPI.OpenNumberInput(entry.DisplayName, entry.Value, f => entry.Value = f);
|
||||
return button;
|
||||
}
|
||||
|
||||
// private static SliderFloat AddMelonSlider(ref Page page, MelonPreferences_Entry<float> entry, float min, float max, int decimalPlaces = 2, bool allowReset = true)
|
||||
// {
|
||||
// SliderFloat slider = page.AddSlider(entry.DisplayName, entry.Description, Mathf.Clamp(entry.Value, min, max), min, max, decimalPlaces, entry.DefaultValue, allowReset);
|
||||
// slider.OnValueUpdated += f => entry.Value = f;
|
||||
// return slider;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a category that saves its collapsed state to a MelonPreferences entry.
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
/// <param name="entry"></param>
|
||||
/// <param name="showHeader"></param>
|
||||
/// <returns></returns>
|
||||
private static Category AddMelonCategory(ref Page page, MelonPreferences_Entry<bool> entry, bool showHeader = true)
|
||||
{
|
||||
Category category = page.AddCategory(entry.DisplayName, showHeader, true, entry.Value);
|
||||
category.OnCollapse += b => entry.Value = b;
|
||||
return category;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Icon Utils
|
||||
|
||||
private static Stream GetIconStream(string iconName)
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string assemblyName = assembly.GetName().Name;
|
||||
return assembly.GetManifestResourceStream($"{assemblyName}.resources.{iconName}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -10,43 +10,113 @@ internal static class ModSettings
|
|||
{
|
||||
// Constants
|
||||
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<bool> Hidden_Foldout_ASM_SettingsCategory =
|
||||
Category.CreateEntry("hidden_foldout_asm", true, is_hidden: true, display_name: ASM_SettingsCategory, description: "Foldout state for Avatar Scale Mod settings.");
|
||||
|
||||
// Avatar Scale Tool Foldout
|
||||
internal static readonly MelonPreferences_Entry<bool> Hidden_Foldout_AST_SettingsCategory =
|
||||
Category.CreateEntry("hidden_foldout_ast", false, is_hidden: true, display_name: AST_SettingsCategory, description: "Foldout state for Avatar Scale Tool settings.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> 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<bool> Hidden_Foldout_USM_SettingsCategory =
|
||||
Category.CreateEntry("hidden_foldout_usm", false, is_hidden: true, display_name: USM_SettingsCategory, description: "Foldout state for Universal Scaling (Mod Network) settings.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> 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<bool> Hidden_Foldout_DEBUG_SettingsCategory =
|
||||
Category.CreateEntry("hidden_foldout_debug", false, is_hidden: true, display_name: DEBUG_SettingsCategory, description: "Foldout state for Debug Options settings.");
|
||||
|
||||
// Player Select Page Foldout
|
||||
internal static readonly MelonPreferences_Entry<bool> Hidden_Foldout_PlayerSelectPage =
|
||||
Category.CreateEntry("hidden_foldout_player_select_page", true, is_hidden: true, display_name: ASM_SettingsCategory, description: "Foldout state for Player Select Page.");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Avatar Scale Mod Settings
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryScaleGestureEnabled =
|
||||
Category.CreateEntry("scale_gesture_enabled", true, display_name: "Scale Gesture Enabled", description: "Enable or disable scale gesture.");
|
||||
Category.CreateEntry("scale_gesture_enabled", true, display_name: "Scale Gesture", description: "Enable or disable scale gesture.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryDebugNetworkInbound =
|
||||
public static readonly MelonPreferences_Entry<bool> EntryScaleKeybindingsEnabled =
|
||||
Category.CreateEntry("scale_keybindings_enabled", true, display_name: "Scale Keybindings", description: "Enable or disable scale keybindings.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryPersistentHeight =
|
||||
Category.CreateEntry("persistent_height", false, display_name: "Persistent Height", description: "Should the avatar height persist between avatar switches?");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryPersistThroughRestart =
|
||||
Category.CreateEntry("persistent_height_through_restart", false, display_name: "Persist Through Restart", description: "Should the avatar height persist between game restarts?");
|
||||
|
||||
// stores the last avatar height as a melon pref
|
||||
public static readonly MelonPreferences_Entry<float> EntryHiddenAvatarHeight =
|
||||
Category.CreateEntry("hidden_avatar_height", -2f, is_hidden: true, display_name: "Avatar Height", description: "Set your avatar height.");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Avatar Scale Tool Settings
|
||||
|
||||
public static readonly MelonPreferences_Entry<string> EntryASTScaleParameter =
|
||||
Category.CreateEntry("override_scale_parameter", "AvatarScale", display_name: "Override Scale Parameter", description: "Override the scale parameter on the avatar.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<float> EntryASTMinHeight =
|
||||
Category.CreateEntry("override_min_height", 0.25f, display_name: "Override Min Height", description: "Override the minimum height.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<float> EntryASTMaxHeight =
|
||||
Category.CreateEntry("override_max_height", 2.5f, display_name: "Override Max Height", description: "Override the maximum height.");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Universal Scaling Settings
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryUseUniversalScaling =
|
||||
Category.CreateEntry("use_universal_scaling", false, display_name: "Use Universal Scaling", description: "Enable or disable universal scaling. This allows scaling to work on any avatar as well as networking via Mod Network.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryScaleComponents =
|
||||
Category.CreateEntry("scale_components", true, display_name: "Scale Components", description: "Scale components on the avatar. (Constraints, Audio Sources, etc.)");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryAnimationScalingOverride =
|
||||
Category.CreateEntry("allow_anim_clip_scale_override", true, display_name: "Animation-Clip Scaling Override", description: "Allow animation-clip scaling to override universal scaling.");
|
||||
|
||||
#endregion
|
||||
|
||||
#region Debug Settings
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> Debug_NetworkInbound =
|
||||
Category.CreateEntry("debug_inbound", false, display_name: "Debug Inbound", description: "Log inbound Mod Network height updates.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryDebugNetworkOutbound =
|
||||
public static readonly MelonPreferences_Entry<bool> Debug_NetworkOutbound =
|
||||
Category.CreateEntry("debug_outbound", false, display_name: "Debug Outbound", description: "Log outbound Mod Network height updates.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<float> EntryHiddenLastAvatarScale =
|
||||
Category.CreateEntry("last_avatar_scale", -1f, is_hidden: true);
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> Debug_ComponentSearchTime =
|
||||
Category.CreateEntry("debug_component_search_time", false, display_name: "Debug Search Time", description: "Log component search time.");
|
||||
|
||||
#endregion
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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<VRModeSwitchManager>();
|
||||
}
|
||||
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<CameraFacingObjectTracker>();
|
||||
}
|
||||
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<CVRPickupObjectTracker>();
|
||||
tracker._pickupObject = __instance;
|
||||
tracker._storedGripOrigin = (!MetaPort.Instance.isUsingVr ? vrOrigin : desktopOrigin);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DesktopVRSwitch.Logger.Error($"Error during the patched method {nameof(Prefix_CVRPickupObject_Start)}");
|
||||
DesktopVRSwitch.Logger.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class SteamVRBehaviourPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(SteamVR_Behaviour), nameof(SteamVR_Behaviour.OnQuit))]
|
||||
private static bool Prefix_SteamVR_Behaviour_OnQuit()
|
||||
{
|
||||
if (!ModSettings.EntrySwitchToDesktopOnExit.Value)
|
||||
return true;
|
||||
|
||||
// If we don't switch fast enough, SteamVR will force close.
|
||||
// World Transition might cause issues. Might need to override.
|
||||
if (VRModeSwitchManager.Instance != null)
|
||||
VRModeSwitchManager.Instance.AttemptSwitch();
|
||||
|
||||
return false;
|
||||
CVRInputManager.Instance.inputEnabled = false;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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<UnityEngine.Camera>();
|
||||
}
|
||||
|
||||
#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<VRModeEventArgs> OnPreVRModeSwitch;
|
||||
public static event EventHandler<VRModeEventArgs> OnPostVRModeSwitch;
|
||||
public static event EventHandler<VRModeEventArgs> 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<VRModeEventArgs> switchEvent, bool isUsingVr)
|
||||
private void InvokeOnPostSwitch(bool isUsingVr)
|
||||
{
|
||||
try
|
||||
{
|
||||
Camera playerCamera = Utils.GetPlayerCameraObject(isUsingVr).GetComponent<Camera>();
|
||||
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
|
||||
}
|
||||
|
||||
#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
|
||||
}
|
||||
|
|
|
@ -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<OpenVRLoader>();
|
||||
DesktopVRSwitch.Logger.Msg("Using XR Loader: SteamVR");
|
||||
EnsureXRLoader();
|
||||
XRGeneralSettings.Instance.Manager.InitializeLoaderSync();
|
||||
await Task.Yield();
|
||||
}
|
||||
else
|
||||
|
||||
internal static async Task StartXR()
|
||||
{
|
||||
item = ScriptableObject.CreateInstance<OpenXRLoader>();
|
||||
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<XRLoader> { 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<XRLoader> currentLoaders)
|
||||
foreach (XRLoader loader in currentLoaders.Where(loader => loader != null)) Object.Destroy(loader);
|
||||
|
||||
field.SetValue(XRGeneralSettings.Instance.Manager, new List<XRLoader> { newLoaderInstance });
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
6
FuckVivox/FuckVivox.csproj
Normal file
6
FuckVivox/FuckVivox.csproj
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>FuckMLA</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
99
FuckVivox/HarmonyPatches.cs
Normal file
99
FuckVivox/HarmonyPatches.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
using System.ComponentModel;
|
||||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Systems.Communications;
|
||||
using DarkRift.Client;
|
||||
using FuckMLA;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using Unity.Services.Vivox;
|
||||
|
||||
namespace NAK.FuckVivox.HarmonyPatches;
|
||||
|
||||
internal class VivoxServiceInternalPatches
|
||||
{
|
||||
// This is to catch some dumb issue where channel might not exist. There is no return even though the error is logged... -_-
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(VivoxServiceInternal), nameof(VivoxServiceInternal.Set3DPosition),
|
||||
typeof(Vector3), typeof(Vector3), typeof(Vector3),
|
||||
typeof(Vector3), typeof(string), typeof(bool))]
|
||||
private static void Prefix_VivoxServiceInternal_Set3DPosition(
|
||||
Vector3 speakerPos, Vector3 listenerPos, Vector3 listenerAtOrient, Vector3 listenerUpOrient,
|
||||
string channelName, bool allowPanning,
|
||||
ref ILoginSession ___m_LoginSession,
|
||||
ref bool __runOriginal)
|
||||
{
|
||||
__runOriginal = true;
|
||||
|
||||
try
|
||||
{
|
||||
IChannelSession channelSession = ___m_LoginSession.ChannelSessions.FirstOrDefault(channel =>
|
||||
channel.Channel.Type == ChannelType.Positional && channel.Channel.Name == channelName);
|
||||
|
||||
if (channelSession != null)
|
||||
return; // no~ fuck you
|
||||
|
||||
__runOriginal = false;
|
||||
FuckVivox.Logger.Msg("Caught an unhandled VivoxServiceInternal error.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
FuckVivox.Logger.Error(e.ToString());
|
||||
__runOriginal = false;
|
||||
}
|
||||
}
|
||||
|
||||
// This is to prevent a race condition between OnLoggedOut and OnConnectionFailedToRecover
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(VivoxServiceManager), nameof(VivoxServiceManager.OnConnectionFailedToRecover))]
|
||||
private static void Prefix_VivoxServiceInternal_OnConnectionFailedToRecover(ref bool __runOriginal)
|
||||
{
|
||||
__runOriginal = false;
|
||||
FuckVivox.Logger.Msg("(OnConnectionFailedToRecover) Possibly prevented a double re-login attempt!");
|
||||
}
|
||||
|
||||
// This is to log us out until our connection stabilizes
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(NetworkManager), nameof(NetworkManager.ReconnectToGameServer))]
|
||||
private static void Prefix_NetworkManager_ReconnectToGameServer()
|
||||
{
|
||||
//FuckVivox.Logger.Msg("CONNECTION UNSTABLE, PANIC LOGOUT!!!");
|
||||
//VivoxHelpers.AttemptLogout();
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(NetworkManager), nameof(NetworkManager.OnGameNetworkConnected))]
|
||||
private static void Prefix_NetworkManager_OnGameNetworkConnected()
|
||||
{
|
||||
if (VivoxServiceManager.Instance.IsLoggedIn())
|
||||
return;
|
||||
|
||||
//FuckVivox.Logger.Msg("(OnGameNetworkConnected) Not logged into Vivox. Connection is potentially stable now, so attempting to login.");
|
||||
//VivoxHelpers.AttemptLogin();
|
||||
}
|
||||
|
||||
// This is to potentially fix an issue where on quick restart, we are in a channel before the bind attempts to add it???
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(VivoxServiceInternal), nameof(VivoxServiceInternal.OnChannelPropertyChanged))]
|
||||
private static void Prefix_VivoxServiceInternal_OnChannelPropertyChanged(
|
||||
object sender, PropertyChangedEventArgs args,
|
||||
ref VivoxServiceInternal __instance,
|
||||
ref bool __runOriginal)
|
||||
{
|
||||
__runOriginal = true;
|
||||
|
||||
IChannelSession channelSession = (IChannelSession)sender;
|
||||
|
||||
if (args.PropertyName != "ChannelState" || channelSession.ChannelState != ConnectionState.Connected)
|
||||
return;
|
||||
|
||||
if (!__instance.m_ActiveChannels.ContainsKey(channelSession.Channel.Name))
|
||||
return;
|
||||
|
||||
FuckVivox.Logger.Warning($"Active Channel already contains key! :: + {channelSession.Channel.Name}");
|
||||
__runOriginal = false;
|
||||
}
|
||||
}
|
34
FuckVivox/Main.cs
Normal file
34
FuckVivox/Main.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using FuckMLA;
|
||||
using MelonLoader;
|
||||
using UnityEngine.Windows;
|
||||
|
||||
namespace NAK.FuckVivox;
|
||||
|
||||
public class FuckVivox : MelonMod
|
||||
{
|
||||
internal static MelonLogger.Instance Logger;
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
Logger = LoggerInstance;
|
||||
ApplyPatches(typeof(HarmonyPatches.VivoxServiceInternalPatches));
|
||||
}
|
||||
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if (UnityEngine.Input.GetKeyDown(UnityEngine.KeyCode.F11))
|
||||
VivoxHelpers.PleaseReLoginThankYou();
|
||||
}
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
HarmonyInstance.PatchAll(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||
LoggerInstance.Error(e);
|
||||
}
|
||||
}
|
||||
}
|
32
FuckVivox/Properties/AssemblyInfo.cs
Normal file
32
FuckVivox/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using MelonLoader;
|
||||
using NAK.FuckVivox.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.FuckVivox))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.FuckVivox))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.FuckVivox.FuckVivox),
|
||||
nameof(NAK.FuckVivox),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FuckVivox"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 155, 89, 182)]
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)]
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.FuckVivox.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
38
FuckVivox/VivoxHelpers.cs
Normal file
38
FuckVivox/VivoxHelpers.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using ABI_RC.Core.IO;
|
||||
using ABI_RC.Core.Networking;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.Communications;
|
||||
using NAK.FuckVivox;
|
||||
|
||||
namespace FuckMLA;
|
||||
|
||||
public static class VivoxHelpers
|
||||
{
|
||||
public static void AttemptLogin()
|
||||
{
|
||||
if (!AuthManager.IsAuthenticated)
|
||||
{
|
||||
FuckVivox.Logger.Msg("Attempted to log in without being authenticated!");
|
||||
return;
|
||||
}
|
||||
VivoxServiceManager.Instance.Login(MetaPort.Instance.ownerId, MetaPort.Instance.blockedUserIds);
|
||||
}
|
||||
|
||||
public static void AttemptLogout()
|
||||
{
|
||||
if (!VivoxServiceManager.Instance.IsLoggedIn())
|
||||
{
|
||||
FuckVivox.Logger.Msg("Attempted to log out when not logged in.");
|
||||
return;
|
||||
}
|
||||
VivoxServiceManager.Instance.Logout();
|
||||
}
|
||||
|
||||
public static void PleaseReLoginThankYou()
|
||||
{
|
||||
FuckVivox.Logger.Msg("PleaseReLoginThankYou!!!");
|
||||
|
||||
AttemptLogout();
|
||||
SchedulerSystem.AddJob(AttemptLogin, 3f, 1, 1);
|
||||
}
|
||||
}
|
22
FuckVivox/format.json
Normal file
22
FuckVivox/format.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "FuckMLA",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2023r172",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "A simple mod that does three basic things:\n\n- Destroys MOUSELOCKALPHA, the script that locks your mouse on startup.\n- Forces mouse to be initially unlocked when launching the game in VR.\n- Fixes mouse rotating player when cursor is unlocked.",
|
||||
"searchtags": [
|
||||
"mouse",
|
||||
"lock",
|
||||
"no",
|
||||
"bad"
|
||||
],
|
||||
"requirements": [
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r21/FuckMLA.dll",
|
||||
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FuckMLA/",
|
||||
"changelog": "- Initial CVRMG release",
|
||||
"embedcolor": "#ffc800"
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>FixedDeltaTimeHack</RootNamespace>
|
||||
</PropertyGroup>
|
||||
</Project>
|
45
LateInitComponentHelperHack/Main.cs
Normal file
45
LateInitComponentHelperHack/Main.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using ABI_RC.Core.Player;
|
||||
using ABI_RC.Core.Util.AssetFiltering;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NAK.LateInitComponentHelperHack;
|
||||
|
||||
public class LateInitComponentHelperHack : MelonMod
|
||||
{
|
||||
private static bool _hasLoggedIn;
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
ApplyPatches(typeof(HarmonyPatches));
|
||||
}
|
||||
|
||||
private void ApplyPatches(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
HarmonyInstance.PatchAll(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LoggerInstance.Msg($"Failed while patching {type.Name}!");
|
||||
LoggerInstance.Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class HarmonyPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(ComponentHelper), nameof(ComponentHelper.Initialize))]
|
||||
private static bool Prefix_ComponentHelper_Initialize() => _hasLoggedIn;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))]
|
||||
private static void Postfix_PlayerSetup_Start()
|
||||
{
|
||||
_hasLoggedIn = true;
|
||||
ComponentHelper.Initialize();
|
||||
}
|
||||
}
|
||||
}
|
32
LateInitComponentHelperHack/Properties/AssemblyInfo.cs
Normal file
32
LateInitComponentHelperHack/Properties/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using MelonLoader;
|
||||
using NAK.LateInitComponentHelperHack.Properties;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
|
||||
[assembly: AssemblyTitle(nameof(NAK.LateInitComponentHelperHack))]
|
||||
[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
|
||||
[assembly: AssemblyProduct(nameof(NAK.LateInitComponentHelperHack))]
|
||||
|
||||
[assembly: MelonInfo(
|
||||
typeof(NAK.LateInitComponentHelperHack.LateInitComponentHelperHack),
|
||||
nameof(NAK.LateInitComponentHelperHack),
|
||||
AssemblyInfoParams.Version,
|
||||
AssemblyInfoParams.Author,
|
||||
downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/LateInitComponentHelperHack"
|
||||
)]
|
||||
|
||||
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
|
||||
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
|
||||
[assembly: MelonColor(255, 155, 89, 182)]
|
||||
[assembly: MelonAuthorColor(255, 158, 21, 32)]
|
||||
[assembly: HarmonyDontPatchAll]
|
||||
|
||||
namespace NAK.LateInitComponentHelperHack.Properties;
|
||||
internal static class AssemblyInfoParams
|
||||
{
|
||||
public const string Version = "1.0.0";
|
||||
public const string Author = "NotAKidoS";
|
||||
}
|
22
LateInitComponentHelperHack/format.json
Normal file
22
LateInitComponentHelperHack/format.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"_id": -1,
|
||||
"name": "FuckMLA",
|
||||
"modversion": "1.0.0",
|
||||
"gameversion": "2023r172",
|
||||
"loaderversion": "0.6.1",
|
||||
"modtype": "Mod",
|
||||
"author": "NotAKidoS",
|
||||
"description": "A simple mod that does three basic things:\n\n- Destroys MOUSELOCKALPHA, the script that locks your mouse on startup.\n- Forces mouse to be initially unlocked when launching the game in VR.\n- Fixes mouse rotating player when cursor is unlocked.",
|
||||
"searchtags": [
|
||||
"mouse",
|
||||
"lock",
|
||||
"no",
|
||||
"bad"
|
||||
],
|
||||
"requirements": [
|
||||
],
|
||||
"downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r21/FuckMLA.dll",
|
||||
"sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/FuckMLA/",
|
||||
"changelog": "- Initial CVRMG release",
|
||||
"embedcolor": "#ffc800"
|
||||
}
|
|
@ -51,9 +51,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EzCurls", "EzCurls\EzCurls.
|
|||
EndProject
|
||||
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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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";
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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<bool> EntryEnabled =
|
||||
Category.CreateEntry("Enabled", true, description: "Toggle Undo Prop Button.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryUseSFX =
|
||||
Category.CreateEntry("Use SFX", true, description: "Toggle audio queues for prop spawn, undo, redo, and warning.");
|
||||
|
||||
internal static List<DeletedProp> deletedProps = new List<DeletedProp>();
|
||||
Category.CreateEntry("Use SFX", true,
|
||||
description: "Toggle audio queues for prop spawn, undo, redo, and warning.");
|
||||
|
||||
internal static List<DeletedProp> 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<CVRSyncHelper.PropData> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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<SmoothRayer>().ray = __instance.leftRay;
|
||||
__instance.vrRightHandTracker.gameObject.AddComponent<SmoothRayer>().ray = __instance.rightRay;
|
||||
|
||||
}
|
||||
}
|
|
@ -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<bool> EntryEnabled =
|
||||
Category.CreateEntry("Enable Smoothing", true,
|
||||
description: "Enable or disable smoothing.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<bool> EntryMenuOnly =
|
||||
Category.CreateEntry("Menu Only", true,
|
||||
description: "Only use smoothing on Main Menu and Quick Menu. This will be fine for most users, but it may be desired on pickups & Unity UI elements too.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<float> EntryPositionSmoothing =
|
||||
Category.CreateEntry("Position Smoothing", 3f,
|
||||
description: "How much to smooth position changes by. Use the slider to adjust the position smoothing factor. Range: 0 to 20.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<float> EntryRotationSmoothing =
|
||||
Category.CreateEntry("Rotation Smoothing", 12f,
|
||||
description: "How much to smooth rotation changes by. Use the slider to adjust the rotation smoothing factor. Range: 0 to 20.");
|
||||
|
||||
public static readonly MelonPreferences_Entry<float> EntrySmallMovementThresholdAngle =
|
||||
Category.CreateEntry("Small Angle Threshold", 6f,
|
||||
description: "Angle difference to consider a 'small' movement. The less shaky your hands are, the lower you probably want to set this. This is probably the primary value you want to tweak. Use the slider to adjust the threshold angle. Range: 4 to 15.");
|
||||
|
||||
public override void OnInitializeMelon()
|
||||
{
|
||||
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue