NAK_CVR_Mods/.Deprecated/AvatarScaleMod/Networking/ModNetwork.cs

283 lines
No EOL
8.8 KiB
C#

using ABI_RC.Core.Networking;
using ABI_RC.Systems.ModNetwork;
using DarkRift;
using NAK.AvatarScaleMod.AvatarScaling;
using UnityEngine;
namespace NAK.AvatarScaleMod.Networking;
// overcomplicated, but functional
// a
public static class ModNetwork
{
public static bool Debug_NetworkInbound = false;
public static bool Debug_NetworkOutbound = false;
private static bool _isSubscribedToModNetwork;
#region Constants
private const string ModId = "MelonMod.NAK.AvatarScaleMod";
private const float SendRateLimit = 0.25f;
private const float ReceiveRateLimit = 0.2f;
private const int MaxWarnings = 2;
private const float TimeoutDuration = 10f;
private class QueuedMessage
{
public MessageType Type { get; set; }
public float Height { get; set; }
public string TargetPlayer { get; set; }
public string Sender { get; set; }
}
#endregion
#region Private State
private static readonly Dictionary<string, QueuedMessage> OutboundQueue = new();
private static float LastSentTime;
private static readonly Dictionary<string, QueuedMessage> InboundQueue = new();
private static readonly Dictionary<string, float> LastReceivedTimes = new();
private static readonly Dictionary<string, int> UserWarnings = new();
private static readonly Dictionary<string, float> UserTimeouts = new();
#endregion
#region Enums
private enum MessageType : byte
{
SyncHeight = 0, // just send height
RequestHeight = 1 // send height, request height back
}
#endregion
#region Mod Network Internals
internal static void Subscribe()
{
ModNetworkManager.Subscribe(ModId, OnMessageReceived);
_isSubscribedToModNetwork = ModNetworkManager.IsSubscribed(ModId);
if (!_isSubscribedToModNetwork)
AvatarScaleMod.Logger.Error("Failed to subscribe to Mod Network! This is a critical error! Please report this to the mod author! (NAK) (ModNetwork.cs) (Subscribe) (Line 150) (AvatarScaleMod) (AvatarScale) (MelonLoader) (MelonLoader.Mods) (MelonLoader.Mods.MelonMod) (MelonLoader.MelonMod) (MelonLoader.MelonMod.MelonBaseMod) (MelonLoader.MelonMod.MelonMod) (MelonLoader.MelonMod.MelonModBase) (MelonLoader.MelonMod.MelonModBase`1) (MelonLoader.MelonMod.MelonModBase`1[[NAK.AvatarScaleMod.AvatarScaling.AvatarScaleMod, NAK.AvatarScaleMod, Version=123");
AvatarScaleEvents.OnLocalAvatarHeightChanged.AddListener(scaler => SendNetworkHeight(scaler.GetTargetHeight()));
}
internal static void Update()
{
if (!_isSubscribedToModNetwork)
return;
ProcessOutboundQueue();
ProcessInboundQueue();
}
private static void SendMessage(MessageType messageType, float height, string playerId = null)
{
if (!IsConnectedToGameNetwork())
return;
if (!Enum.IsDefined(typeof(MessageType), messageType))
return;
if (!string.IsNullOrEmpty(playerId))
{
// to specific user
using ModNetworkMessage modMsg = new(ModId, playerId);
modMsg.Write((byte)messageType);
modMsg.Write(height);
modMsg.Send();
}
else
{
// to all users
using ModNetworkMessage modMsg = new(ModId);
modMsg.Write((byte)messageType);
modMsg.Write(height);
modMsg.Send();
}
}
private static void OnMessageReceived(ModNetworkMessage msg)
{
msg.Read(out byte msgTypeRaw);
msg.Read(out float receivedHeight);
if (!Enum.IsDefined(typeof(MessageType), msgTypeRaw))
return;
// User is in timeout
if (UserTimeouts.TryGetValue(msg.Sender, out var timeoutEnd) && Time.time < timeoutEnd)
return;
if (IsRateLimited(msg.Sender))
return;
QueuedMessage inboundMessage = new()
{
Type = (MessageType)msgTypeRaw,
Height = receivedHeight,
Sender = msg.Sender
};
InboundQueue[msg.Sender] = inboundMessage;
}
#endregion
#region Public Methods
public static void SendNetworkHeight(float newHeight)
{
OutboundQueue["global"] = new QueuedMessage { Type = MessageType.SyncHeight, Height = newHeight };
}
public static void RequestHeightSync()
{
var myCurrentHeight = AvatarScaleManager.Instance.GetHeightForNetwork();
OutboundQueue["global"] = new QueuedMessage { Type = MessageType.RequestHeight, Height = myCurrentHeight };
}
#endregion
#region Outbound Height Queue
private static void ProcessOutboundQueue()
{
if (OutboundQueue.Count == 0 || Time.time - LastSentTime < SendRateLimit)
return;
foreach (QueuedMessage message in OutboundQueue.Values)
{
SendMessage(message.Type, message.Height, message.TargetPlayer);
if (Debug_NetworkOutbound)
AvatarScaleMod.Logger.Msg(
$"Sending message {message.Type.ToString()} to {(string.IsNullOrEmpty(message.TargetPlayer) ? "ALL" : message.TargetPlayer)}: {message.Height}");
}
OutboundQueue.Clear();
LastSentTime = Time.time;
}
#endregion
#region Inbound Height Queue
private static bool IsRateLimited(string userId)
{
// Rate-limit checking
if (LastReceivedTimes.TryGetValue(userId, out var lastReceivedTime) &&
Time.time - lastReceivedTime < ReceiveRateLimit)
{
if (UserWarnings.TryGetValue(userId, out var warnings))
{
warnings++;
UserWarnings[userId] = warnings;
if (warnings >= MaxWarnings)
{
UserTimeouts[userId] = Time.time + TimeoutDuration;
AvatarScaleMod.Logger.Warning($"User is sending height updates too fast! Applying 10s timeout... : {userId}");
return true;
}
}
else
{
UserWarnings[userId] = 1;
}
return true;
}
LastReceivedTimes[userId] = Time.time;
UserWarnings.Remove(userId); // Reset warnings
return false;
}
private static void ProcessInboundQueue()
{
foreach (QueuedMessage message in InboundQueue.Values)
{
switch (message.Type)
{
case MessageType.RequestHeight:
{
var myNetworkHeight = AvatarScaleManager.Instance.GetHeightForNetwork();
OutboundQueue[message.Sender] = new QueuedMessage
{
Type = MessageType.SyncHeight,
Height = myNetworkHeight,
TargetPlayer = message.Sender
};
AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(message.Sender, message.Height);
break;
}
case MessageType.SyncHeight:
AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(message.Sender, message.Height);
break;
default:
AvatarScaleMod.Logger.Error($"Invalid message type received from: {message.Sender}");
break;
}
if (Debug_NetworkInbound)
AvatarScaleMod.Logger.Msg($"Received message {message.Type.ToString()} from {message.Sender}: {message.Height}");
}
InboundQueue.Clear();
}
#endregion
#region Private Methods
private static bool IsConnectedToGameNetwork()
{
return NetworkManager.Instance != null
&& NetworkManager.Instance.GameNetwork != null
&& NetworkManager.Instance.GameNetwork.ConnectionState == ConnectionState.Connected;
}
#endregion
#region Messages
private class AvatarHeightMessage
{
public float Height { get; set; }
public bool IsUniversal { get; set; }
}
private static void AddAvatarHeightMessageConverter()
{
ModNetworkMessage.AddConverter(Reader, Writer);
return;
AvatarHeightMessage Reader(ModNetworkMessage msg)
{
AvatarHeightMessage avatarHeightMessage = new();
msg.Read(out float height);
avatarHeightMessage.Height = height;
msg.Read(out bool isUniversal);
avatarHeightMessage.IsUniversal = isUniversal;
return avatarHeightMessage;
}
void Writer(ModNetworkMessage msg, AvatarHeightMessage value)
{
msg.Write(value.Height);
msg.Write(value.IsUniversal);
}
}
#endregion
}