From c7eb5715bac205c4a4bbef77c659aa150ac7b599 Mon Sep 17 00:00:00 2001 From: NotAKidoS <37721153+NotAKidOnSteam@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:26:38 -0500 Subject: [PATCH] [AvatarScaleMod] Test 2: Height Request Now requests initial height on instance join instead of syncing every 10s. Reworked inbould & outbound message queue. Fixed potential race condition where scalefactor could become 0. A lot of this is still incredibly jank. Just trying to get things working before cleanup. --- .../AvatarScaling/AvatarScaleManager.cs | 170 ++++++++++++----- AvatarScale/AvatarScaling/ScaledComponents.cs | 2 +- .../AvatarScaling/UniversalAvatarScaler.cs | 113 ++++++++++-- AvatarScale/HarmonyPatches.cs | 95 ++++++---- AvatarScale/ModSettings.cs | 17 +- AvatarScale/Networking/ModNetwork.cs | 171 ++++++++++++++---- AvatarScale/Networking/ModNetworkDebugger.cs | 18 +- 7 files changed, 440 insertions(+), 146 deletions(-) diff --git a/AvatarScale/AvatarScaling/AvatarScaleManager.cs b/AvatarScale/AvatarScaling/AvatarScaleManager.cs index c9972e9..642d895 100644 --- a/AvatarScale/AvatarScaling/AvatarScaleManager.cs +++ b/AvatarScale/AvatarScaling/AvatarScaleManager.cs @@ -1,4 +1,7 @@ -using ABI_RC.Core.Player; +using ABI_RC.Core.IO; +using ABI_RC.Core.Player; +using ABI_RC.Core.Player.AvatarTracking; +using ABI_RC.Systems.GameEventSystem; using NAK.AvatarScaleMod.Networking; using UnityEngine; @@ -8,9 +11,11 @@ public class AvatarScaleManager : MonoBehaviour { public static AvatarScaleManager Instance; + public bool Setting_PersistantHeight = false; + private Dictionary _networkedScalers; private UniversalAvatarScaler _localAvatarScaler; - + #region Unity Methods private void Awake() @@ -25,6 +30,27 @@ public class AvatarScaleManager : MonoBehaviour _networkedScalers = new Dictionary(); } + private void Start() + { + CVRGameEventSystem.Instance.OnConnected.AddListener(OnInstanceConnected); + //SchedulerSystem.AddJob(new SchedulerSystem.Job(ForceHeightUpdate), 0f, 10f, -1); + } + + private void OnDestroy() + { + CVRGameEventSystem.Instance.OnConnected.RemoveListener(OnInstanceConnected); + //SchedulerSystem.RemoveJob(new SchedulerSystem.Job(ForceHeightUpdate)); + } + + #endregion + + #region Game Events + + public void OnInstanceConnected(string instanceId) + { + SchedulerSystem.AddJob(ModNetwork.RequestHeightSync, 2f, 1f, 1); + } + #endregion #region Local Methods @@ -33,28 +59,37 @@ public class AvatarScaleManager : MonoBehaviour { if (playerSetup._avatar == null) return; - - if (_localAvatarScaler != null) - Destroy(_localAvatarScaler); - _localAvatarScaler = playerSetup._avatar.AddComponent(); - _localAvatarScaler.Initialize(playerSetup._initialAvatarHeight, playerSetup.initialScale); + if (_localAvatarScaler == null) + { + _localAvatarScaler = playerSetup.gameObject.AddComponent(); + _localAvatarScaler.Initialize(); + } + + _localAvatarScaler.OnAvatarInstantiated(playerSetup._avatar, playerSetup._initialAvatarHeight, + playerSetup.initialScale); + + if (Setting_PersistantHeight && _localAvatarScaler.IsValid()) + SchedulerSystem.AddJob(() => { ModNetwork.SendNetworkHeight(_localAvatarScaler.GetHeight()); }, 0.5f, 0f, 1); } - public void OnAvatarDestroyed() + public void OnAvatarDestroyed(PlayerSetup playerSetup) { if (_localAvatarScaler != null) - Destroy(_localAvatarScaler); + _localAvatarScaler.OnAvatarDestroyed(Setting_PersistantHeight); + + if (Setting_PersistantHeight && _localAvatarScaler.IsValid()) + SchedulerSystem.AddJob(() => { ModNetwork.SendNetworkHeight(_localAvatarScaler.GetHeight()); }, 0.5f, 0f, 1); } public void SetHeight(float targetHeight) { - if (_localAvatarScaler == null) + if (_localAvatarScaler == null) return; - - _localAvatarScaler.SetHeight(targetHeight); + + _localAvatarScaler.SetTargetHeight(targetHeight); ModNetwork.SendNetworkHeight(targetHeight); - + // immediately update play space scale PlayerSetup.Instance.CheckUpdateAvatarScaleToPlaySpaceRelation(); } @@ -64,49 +99,98 @@ public class AvatarScaleManager : MonoBehaviour if (_localAvatarScaler != null) _localAvatarScaler.ResetHeight(); } - + public float GetHeight() { - return (_localAvatarScaler != null) ? _localAvatarScaler.GetHeight() : -1f; + return _localAvatarScaler != null ? _localAvatarScaler.GetHeight() : -1f; } #endregion #region Network Methods - public void OnNetworkAvatarInstantiated(PuppetMaster puppetMaster) - { - if (puppetMaster.avatarObject == null) - return; - - string playerId = puppetMaster._playerDescriptor.ownerId; - - if (_networkedScalers.ContainsKey(playerId)) - _networkedScalers.Remove(playerId); - - UniversalAvatarScaler scaler = puppetMaster.avatarObject.AddComponent(); - scaler.Initialize(puppetMaster._initialAvatarHeight, puppetMaster.initialAvatarScale); - _networkedScalers[playerId] = scaler; - } - - public void OnNetworkAvatarDestroyed(string playerId) - { - if (_networkedScalers.ContainsKey(playerId)) - _networkedScalers.Remove(playerId); - } - - public void OnNetworkHeightUpdateReceived(string playerId, float targetHeight) - { - if (_networkedScalers.TryGetValue(playerId, out UniversalAvatarScaler scaler)) - scaler.SetHeight(targetHeight); - } - public float GetNetworkHeight(string playerId) { if (_networkedScalers.TryGetValue(playerId, out UniversalAvatarScaler scaler)) return scaler.GetHeight(); return -1f; } - + + // we will create a Universal Scaler for only users that send a height update + // this is sent at a rate of 10s locally! + + internal void OnNetworkHeightUpdateReceived(string playerId, float targetHeight) + { + if (_networkedScalers.TryGetValue(playerId, out UniversalAvatarScaler scaler)) + scaler.SetTargetHeight(targetHeight); + else + SetupHeightScalerForNetwork(playerId, targetHeight); + } + + internal void OnNetworkAvatarInstantiated(PuppetMaster puppetMaster) + { + var playerId = puppetMaster._playerDescriptor.ownerId; + if (_networkedScalers.TryGetValue(playerId, out UniversalAvatarScaler scaler)) + scaler.OnAvatarInstantiated(puppetMaster.avatarObject, puppetMaster._initialAvatarHeight, + puppetMaster.initialAvatarScale); + } + + internal void OnNetworkAvatarDestroyed(PuppetMaster puppetMaster) + { + // on disconnect + if (puppetMaster == null || puppetMaster._playerDescriptor == null) + return; + + var playerId = puppetMaster._playerDescriptor.ownerId; + if (_networkedScalers.TryGetValue(playerId, out UniversalAvatarScaler scaler)) + scaler.OnAvatarDestroyed(); + } + + internal void RemoveNetworkHeightScaler(string playerId) + { + if (_networkedScalers.ContainsKey(playerId)) + { + AvatarScaleMod.Logger.Msg( + $"Removed user height scaler! This is hopefully due to a disconnect or block. : {playerId}"); + + _networkedScalers.Remove(playerId); + return; + } + + AvatarScaleMod.Logger.Msg( + $"Failed to remove a user height scaler! This shouldn't happen. : {playerId}"); + } + + private void SetupHeightScalerForNetwork(string playerId, float targetHeight) + { + CVRPlayerEntity playerEntity = + CVRPlayerManager.Instance.NetworkPlayers.Find(players => players.Uuid == playerId); + + PuppetMaster puppetMaster = playerEntity?.PuppetMaster; + + if (playerEntity == null || puppetMaster == null) + { + AvatarScaleMod.Logger.Error( + $"Attempted to set up height scaler for user which does not exist! : {playerId}"); + return; + } + + AvatarScaleMod.Logger.Msg( + $"Setting up new height scaler for user which has sent a height update! : {playerId}"); + + if (_networkedScalers.ContainsKey(playerId)) + _networkedScalers.Remove(playerId); // ?? + + UniversalAvatarScaler scaler = puppetMaster.gameObject.AddComponent(); + scaler.Initialize(playerId); + + scaler.OnAvatarInstantiated(puppetMaster.avatarObject, puppetMaster._initialAvatarHeight, + puppetMaster.initialAvatarScale); + + _networkedScalers[playerId] = scaler; + + scaler.SetTargetHeight(targetHeight); // set initial height + } + #endregion } \ No newline at end of file diff --git a/AvatarScale/AvatarScaling/ScaledComponents.cs b/AvatarScale/AvatarScaling/ScaledComponents.cs index a59cb19..05134df 100644 --- a/AvatarScale/AvatarScaling/ScaledComponents.cs +++ b/AvatarScale/AvatarScaling/ScaledComponents.cs @@ -122,7 +122,7 @@ public class ScaledScaleConstraint public void Scale(float scaleFactor) { Component.scaleAtRest = InitialScaleAtRest * scaleFactor; - Component.scaleOffset = InitialScaleOffset * scaleFactor; + // Component.scaleOffset = InitialScaleOffset * scaleFactor; } public void Reset() diff --git a/AvatarScale/AvatarScaling/UniversalAvatarScaler.cs b/AvatarScale/AvatarScaling/UniversalAvatarScaler.cs index 25be860..a4858f7 100644 --- a/AvatarScale/AvatarScaling/UniversalAvatarScaler.cs +++ b/AvatarScale/AvatarScaling/UniversalAvatarScaler.cs @@ -1,5 +1,6 @@ using ABI_RC.Core; using ABI_RC.Core.Player; +using ABI.CCK.Components; using NAK.AvatarScaleMod.ScaledComponents; using UnityEngine; using UnityEngine.Animations; @@ -22,6 +23,12 @@ public class UniversalAvatarScaler : MonoBehaviour #region Variables + internal bool requestedInitial; + + [NonSerialized] + internal string ownerId; + + private Transform _avatarTransform; private CVRAnimatorManager _animatorManager; private float _initialHeight; @@ -33,52 +40,106 @@ public class UniversalAvatarScaler : MonoBehaviour private bool _isLocalAvatar; private bool _heightWasUpdated; + private bool _isAvatarInstantiated; + #endregion #region Unity Methods - private async void Start() - { - await FindComponentsOfTypeAsync(scalableComponentTypes); - } - private void LateUpdate() { ScaleAvatarRoot(); // override animation-based scaling } + private void OnDestroy() + { + ClearComponentLists(); + if (!_isLocalAvatar) AvatarScaleManager.Instance.RemoveNetworkHeightScaler(ownerId); + } + #endregion #region Public Methods - public void Initialize(float initialHeight, Vector3 initialScale) + public void Initialize(string playerId = null) { - _initialHeight = _targetHeight = initialHeight; - _initialScale = initialScale; - _scaleFactor = 1f; - + ownerId = playerId; + _isLocalAvatar = gameObject.layer == 8; _animatorManager = _isLocalAvatar ? GetComponentInParent().animatorManager : GetComponentInParent()._animatorManager; - + _heightWasUpdated = false; + _isAvatarInstantiated = false; } - public void SetHeight(float height) + public async void OnAvatarInstantiated(GameObject avatarObject, float initialHeight, Vector3 initialScale) { - _targetHeight = Mathf.Clamp(height, MinHeight, MaxHeight); + if (avatarObject == null) + { + AvatarScaleMod.Logger.Error("Avatar was somehow null?????"); + return; + } + AvatarScaleMod.Logger.Msg($"Avatar Object : {_avatarTransform} : {_avatarTransform == null}"); + + if (_isAvatarInstantiated) return; + _isAvatarInstantiated = true; + + // if we don't have a queued height update, apply initial scaling + if (!_heightWasUpdated) + _targetHeight = initialHeight; + + _initialHeight = initialHeight; + _initialScale = initialScale; _scaleFactor = _targetHeight / _initialHeight; + + _avatarTransform = avatarObject.transform; + await FindComponentsOfTypeAsync(scalableComponentTypes); + + ApplyScaling(); // apply queued scaling if avatar was loading + } + + public void OnAvatarDestroyed(bool shouldPersist = false) + { + if (!_isAvatarInstantiated) return; + _isAvatarInstantiated = false; + + AvatarScaleMod.Logger.Msg($"Destroying Avatar Object : {_avatarTransform} : {_avatarTransform == null}"); + + _avatarTransform = null; + _heightWasUpdated = shouldPersist; + ClearComponentLists(); + } + + public void SetTargetHeight(float height) + { + if (Math.Abs(height - _targetHeight) < float.Epsilon) + return; + + _targetHeight = Mathf.Clamp(height, MinHeight, MaxHeight); + _heightWasUpdated = true; + if (!_isAvatarInstantiated) + return; + + _scaleFactor = _targetHeight / _initialHeight; ApplyScaling(); } public void ResetHeight() { + if (Math.Abs(_initialHeight - _targetHeight) < float.Epsilon) + return; + _targetHeight = _initialHeight; - _scaleFactor = 1f; + _heightWasUpdated = true; + if (!_isAvatarInstantiated) + return; + + _scaleFactor = 1f; ApplyScaling(); } @@ -87,13 +148,21 @@ public class UniversalAvatarScaler : MonoBehaviour return _targetHeight; } + public bool IsValid() + { + return _isAvatarInstantiated; + } + #endregion #region Private Methods private void ScaleAvatarRoot() { - transform.localScale = _initialScale * _scaleFactor; + if (_avatarTransform == null) + return; + + _avatarTransform.localScale = _initialScale * _scaleFactor; } private void UpdateAnimatorParameter() @@ -109,8 +178,9 @@ public class UniversalAvatarScaler : MonoBehaviour private void ApplyScaling() { - if (!_heightWasUpdated) + if (_avatarTransform == null) return; + _heightWasUpdated = false; ScaleAvatarRoot(); @@ -138,10 +208,19 @@ public class UniversalAvatarScaler : MonoBehaviour private readonly List _scaledPositionConstraints = new List(); private readonly List _scaledScaleConstraints = new List(); + private void ClearComponentLists() + { + _scaledLights.Clear(); + _scaledAudioSources.Clear(); + _scaledParentConstraints.Clear(); + _scaledPositionConstraints.Clear(); + _scaledScaleConstraints.Clear(); + } + private async Task FindComponentsOfTypeAsync(Type[] types) { var tasks = new List(); - var components = GetComponentsInChildren(true); + var components = _avatarTransform.gameObject.GetComponentsInChildren(true); foreach (Component component in components) { diff --git a/AvatarScale/HarmonyPatches.cs b/AvatarScale/HarmonyPatches.cs index 453c225..cafc3a3 100644 --- a/AvatarScale/HarmonyPatches.cs +++ b/AvatarScale/HarmonyPatches.cs @@ -8,38 +8,53 @@ using Object = UnityEngine.Object; namespace NAK.AvatarScaleMod.HarmonyPatches; internal class PlayerSetupPatches -{ - [HarmonyPostfix] - [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))] - private static void Postfix_PlayerSetup_Start() - { - try - { - GameObject scaleManager = new(nameof(AvatarScaleManager), typeof(AvatarScaleManager)); - Object.DontDestroyOnLoad(scaleManager); - } - catch (Exception e) - { - AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_Start)}"); - AvatarScaleMod.Logger.Error(e); - } - } - - [HarmonyPostfix] - [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupAvatar))] - private static void Postfix_PlayerSetup_SetupAvatar(ref PlayerSetup __instance) - { - try - { - AvatarScaleManager.Instance.OnAvatarInstantiated(__instance); - } - catch (Exception e) - { - AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_SetupAvatar)}"); - AvatarScaleMod.Logger.Error(e); - } - } -} + { + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.Start))] + private static void Postfix_PlayerSetup_Start() + { + try + { + GameObject scaleManager = new (nameof(AvatarScaleManager), typeof(AvatarScaleManager)); + Object.DontDestroyOnLoad(scaleManager); + } + catch (Exception e) + { + AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_Start)}"); + AvatarScaleMod.Logger.Error(e); + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.SetupAvatar))] + private static void Postfix_PlayerSetup_SetupAvatar(ref PlayerSetup __instance) + { + try + { + AvatarScaleManager.Instance.OnAvatarInstantiated(__instance); + } + catch (Exception e) + { + AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_SetupAvatar)}"); + AvatarScaleMod.Logger.Error(e); + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(PlayerSetup), nameof(PlayerSetup.ClearAvatar))] + private static void Postfix_PlayerSetup_ClearAvatar(ref PlayerSetup __instance) + { + try + { + AvatarScaleManager.Instance.OnAvatarDestroyed(__instance); + } + catch (Exception e) + { + AvatarScaleMod.Logger.Error($"Error during the patched method {nameof(Postfix_PlayerSetup_ClearAvatar)}"); + AvatarScaleMod.Logger.Error(e); + } + } + } internal class PuppetMasterPatches { @@ -58,6 +73,22 @@ internal class PuppetMasterPatches AvatarScaleMod.Logger.Error(e); } } + + [HarmonyPostfix] + [HarmonyPatch(typeof(PuppetMaster), nameof(PuppetMaster.AvatarDestroyed))] + private static void Postfix_PuppetMaster_AvatarDestroyed(ref PuppetMaster __instance) + { + try + { + AvatarScaleManager.Instance.OnNetworkAvatarDestroyed(__instance); + } + catch (Exception e) + { + AvatarScaleMod.Logger.Error( + $"Error during the patched method {nameof(Postfix_PuppetMaster_AvatarDestroyed)}"); + AvatarScaleMod.Logger.Error(e); + } + } } internal class GesturePlaneTestPatches diff --git a/AvatarScale/ModSettings.cs b/AvatarScale/ModSettings.cs index a44f007..70eb4cf 100644 --- a/AvatarScale/ModSettings.cs +++ b/AvatarScale/ModSettings.cs @@ -1,4 +1,5 @@ using MelonLoader; +using NAK.AvatarScaleMod.AvatarScaling; namespace NAK.AvatarScaleMod; @@ -8,12 +9,12 @@ internal static class ModSettings { public static readonly MelonPreferences_Category Category = MelonPreferences.CreateCategory(nameof(AvatarScaleMod)); + + public static MelonPreferences_Entry PersistantHeight; // AvatarScaleTool supported scaling settings public static readonly MelonPreferences_Entry EntryEnabled = Category.CreateEntry("AvatarScaleTool Scaling", true, description: "Should there be persistant avatar scaling? This only works properly across supported avatars."); - public static readonly MelonPreferences_Entry EntryPersistAnyways = - Category.CreateEntry("Persist From Unsupported", true, description: "Should avatar scale persist even from unsupported avatars?"); // Universal scaling settings (Mod Network, requires others to have mod) public static readonly MelonPreferences_Entry EntryUniversalScaling = @@ -37,7 +38,6 @@ internal static class ModSettings { EntryEnabled.OnEntryValueChanged.Subscribe(OnEntryEnabledChanged); EntryUseScaleGesture.OnEntryValueChanged.Subscribe(OnEntryUseScaleGestureChanged); - EntryPersistAnyways.OnEntryValueChanged.Subscribe(OnEntryPersistAnywaysChanged); EntryUniversalScaling.OnEntryValueChanged.Subscribe(OnEntryUniversalScalingChanged); EntryScaleConstraints.OnEntryValueChanged.Subscribe(OnEntryScaleConstraintsChanged); } @@ -52,10 +52,7 @@ internal static class ModSettings //AvatarScaleGesture.GestureEnabled = newValue; } - private static void OnEntryPersistAnywaysChanged(bool oldValue, bool newValue) - { - } private static void OnEntryUniversalScalingChanged(bool oldValue, bool newValue) { @@ -69,7 +66,15 @@ internal static class ModSettings public static void InitializeModSettings() { + PersistantHeight = Category.CreateEntry("Persistant Height", false, description: "Should the avatar height persist between avatar switches?"); + PersistantHeight.OnEntryValueChanged.Subscribe(OnPersistantHeightChanged); + //AvatarScaleManager.UseUniversalScaling = EntryEnabled.Value; //AvatarScaleGesture.GestureEnabled = EntryUseScaleGesture.Value; } + + private static void OnPersistantHeightChanged(bool oldValue, bool newValue) + { + AvatarScaleManager.Instance.Setting_PersistantHeight = newValue; + } } \ No newline at end of file diff --git a/AvatarScale/Networking/ModNetwork.cs b/AvatarScale/Networking/ModNetwork.cs index dbc1bde..0c858bf 100644 --- a/AvatarScale/Networking/ModNetwork.cs +++ b/AvatarScale/Networking/ModNetwork.cs @@ -1,10 +1,15 @@ -using ABI_RC.Systems.ModNetwork; +using ABI_RC.Core.Networking; +using ABI_RC.Systems.ModNetwork; +using DarkRift; using MelonLoader; using NAK.AvatarScaleMod.AvatarScaling; using UnityEngine; namespace NAK.AvatarScaleMod.Networking; +// overcomplicated, but functional +// a + public static class ModNetwork { #region Constants @@ -14,19 +19,37 @@ public static class ModNetwork private const float ReceiveRateLimit = 0.2f; private const int MaxWarnings = 2; private const float TimeoutDuration = 10f; + + private class QueuedMessage + { + public MessageType Type { get; set; } + public float Height { get; set; } + public string TargetPlayer { get; set; } + public string Sender { get; set; } + } #endregion #region Private State - private static float? OutboundQueue; + private static readonly Dictionary OutboundQueue = new(); private static float LastSentTime; - private static readonly Dictionary InboundQueue = new Dictionary(); - private static readonly Dictionary LastReceivedTimes = new Dictionary(); + private static readonly Dictionary InboundQueue = new(); + private static readonly Dictionary LastReceivedTimes = new(); - private static readonly Dictionary UserWarnings = new Dictionary(); - private static readonly Dictionary UserTimeouts = new Dictionary(); + private static readonly Dictionary UserWarnings = new(); + private static readonly Dictionary UserTimeouts = new(); + + #endregion + + #region Enums + + private enum MessageType : byte + { + SyncHeight = 0, // just send height + RequestHeight = 1 // send height, request height back + } #endregion @@ -36,26 +59,63 @@ public static class ModNetwork { ModNetworkManager.Subscribe(ModId, OnMessageReceived); } - + internal static void Update() { ProcessOutboundQueue(); ProcessInboundQueue(); } - private static void SendMessageToAll(float height) + private static void SendMessage(MessageType messageType, float height, string playerId = null) { - using ModNetworkMessage modMsg = new ModNetworkMessage(ModId); - modMsg.Write(height); - modMsg.Send(); - //MelonLogger.Msg($"Sending height: {height}"); + if (!IsConnectedToGameNetwork()) + return; + + if (!string.IsNullOrEmpty(playerId)) + { + // to specific user + using ModNetworkMessage modMsg = new(ModId, playerId); + modMsg.Write((byte)messageType); + modMsg.Write(height); + modMsg.Send(); + } + else + { + // to all users + using ModNetworkMessage modMsg = new(ModId); + modMsg.Write((byte)messageType); + modMsg.Write(height); + modMsg.Send(); + } + + var typeDesc = messageType == MessageType.SyncHeight ? "height" : "height request"; + MelonLogger.Msg($"Sending {typeDesc}: {height}"); } private static void OnMessageReceived(ModNetworkMessage msg) { + msg.Read(out byte msgTypeRaw); msg.Read(out float receivedHeight); - ProcessReceivedHeight(msg.Sender, receivedHeight); - //MelonLogger.Msg($"Received height from {msg.Sender}: {receivedHeight}"); + + if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw)) + return; + + // User is in timeout + if (UserTimeouts.TryGetValue(msg.Sender, out var timeoutEnd) && Time.time < timeoutEnd) + return; + + if (IsRateLimited(msg.Sender)) + return; + + QueuedMessage inboundMessage = new() + { + Type = (MessageType)msgTypeRaw, + Height = receivedHeight, + Sender = msg.Sender + }; + + InboundQueue[msg.Sender] = inboundMessage; + MelonLogger.Msg($"Received message from {msg.Sender}: {receivedHeight}"); } #endregion @@ -64,7 +124,14 @@ public static class ModNetwork public static void SendNetworkHeight(float newHeight) { - OutboundQueue = newHeight; + OutboundQueue["global"] = new QueuedMessage { Type = MessageType.SyncHeight, Height = newHeight }; + } + + public static void RequestHeightSync() + { + var myCurrentHeight = AvatarScaleManager.Instance.GetHeight(); + if (myCurrentHeight > 0) + OutboundQueue["global"] = new QueuedMessage { Type = MessageType.RequestHeight, Height = myCurrentHeight }; } #endregion @@ -73,32 +140,27 @@ public static class ModNetwork private static void ProcessOutboundQueue() { - if (!OutboundQueue.HasValue) + if (OutboundQueue.Count == 0 || Time.time - LastSentTime < SendRateLimit) return; - if (!(Time.time - LastSentTime >= SendRateLimit)) - return; + foreach (QueuedMessage message in OutboundQueue.Values) + SendMessage(message.Type, message.Height, message.TargetPlayer); - SendMessageToAll(OutboundQueue.Value); + OutboundQueue.Clear(); LastSentTime = Time.time; - OutboundQueue = null; } #endregion #region Inbound Height Queue - private static void ProcessReceivedHeight(string userId, float receivedHeight) + private static bool IsRateLimited(string userId) { - // User is in timeout - if (UserTimeouts.TryGetValue(userId, out float timeoutEnd) && Time.time < timeoutEnd) - return; - // Rate-limit checking - if (LastReceivedTimes.TryGetValue(userId, out float lastReceivedTime) && + if (LastReceivedTimes.TryGetValue(userId, out var lastReceivedTime) && Time.time - lastReceivedTime < ReceiveRateLimit) { - if (UserWarnings.TryGetValue(userId, out int warnings)) + if (UserWarnings.TryGetValue(userId, out var warnings)) { warnings++; UserWarnings[userId] = warnings; @@ -107,34 +169,63 @@ public static class ModNetwork { UserTimeouts[userId] = Time.time + TimeoutDuration; MelonLogger.Msg($"User is sending height updates too fast! Applying 10s timeout... : {userId}"); - return; + return true; } } else { UserWarnings[userId] = 1; } - } - else - { - LastReceivedTimes[userId] = Time.time; - UserWarnings.Remove(userId); // Reset warnings - // MelonLogger.Msg($"Clearing timeout from user : {userId}"); + + return true; } - InboundQueue[userId] = receivedHeight; + LastReceivedTimes[userId] = Time.time; + UserWarnings.Remove(userId); // Reset warnings + // MelonLogger.Msg($"Clearing timeout from user : {userId}"); + return false; } private static void ProcessInboundQueue() { - foreach (var (userId, height) in InboundQueue) - { - MelonLogger.Msg($"Applying inbound queued height {height} from : {userId}"); - AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(userId, height); - } + foreach (QueuedMessage message in InboundQueue.Values) + switch (message.Type) + { + case MessageType.RequestHeight: + { + var myCurrentHeight = AvatarScaleManager.Instance.GetHeight(); + if (myCurrentHeight > 0) + OutboundQueue[message.Sender] = new QueuedMessage + { + Type = MessageType.SyncHeight, + Height = myCurrentHeight, + TargetPlayer = message.Sender + }; + + AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(message.Sender, message.Height); + break; + } + case MessageType.SyncHeight: + AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(message.Sender, message.Height); + break; + default: + AvatarScaleMod.Logger.Error($"Invalid message type received from: {message.Sender}"); + break; + } InboundQueue.Clear(); } #endregion + + #region Private Methods + + private static bool IsConnectedToGameNetwork() + { + return NetworkManager.Instance != null + && NetworkManager.Instance.GameNetwork != null + && NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected; + } + + #endregion } \ No newline at end of file diff --git a/AvatarScale/Networking/ModNetworkDebugger.cs b/AvatarScale/Networking/ModNetworkDebugger.cs index d3f0be5..198c518 100644 --- a/AvatarScale/Networking/ModNetworkDebugger.cs +++ b/AvatarScale/Networking/ModNetworkDebugger.cs @@ -16,22 +16,26 @@ public static class ModNetworkDebugger if (AvatarScaleManager.Instance == null) return; - float currentHeight = AvatarScaleManager.Instance.GetHeight(); + float currentHeight; const float step = 0.1f; if (Input.GetKeyDown(KeyCode.Equals) || Input.GetKeyDown(KeyCode.KeypadPlus)) { - currentHeight += step; - AvatarScaleMod.Logger.Msg($"Networking height: {currentHeight}"); + currentHeight = AvatarScaleManager.Instance.GetHeight(); + AvatarScaleManager.Instance.SetHeight(currentHeight + step); + currentHeight = AvatarScaleManager.Instance.GetHeight(); + ModNetwork.SendNetworkHeight(currentHeight); - AvatarScaleManager.Instance.SetHeight(currentHeight); + AvatarScaleMod.Logger.Msg($"Networking height: {currentHeight}"); } else if (Input.GetKeyDown(KeyCode.Minus) || Input.GetKeyDown(KeyCode.KeypadMinus)) { - currentHeight -= step; - AvatarScaleMod.Logger.Msg($"Networking height: {currentHeight}"); + currentHeight = AvatarScaleManager.Instance.GetHeight(); + AvatarScaleManager.Instance.SetHeight(currentHeight - step); + currentHeight = AvatarScaleManager.Instance.GetHeight(); + ModNetwork.SendNetworkHeight(currentHeight); - AvatarScaleManager.Instance.SetHeight(currentHeight); + AvatarScaleMod.Logger.Msg($"Networking height: {currentHeight}"); } else if (Input.GetKeyDown(KeyCode.Backspace)) {