Decompiled source of RealTimeHeadChat v1.0.0

RealTimeHeadChat.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using BoneLib;
using BoneLib.BoneMenu;
using LabFusion.Entities;
using LabFusion.Network;
using LabFusion.Network.Serialization;
using LabFusion.Player;
using LabFusion.SDK.Modules;
using LabFusion.UI;
using LabFusion.Utilities;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using RealTimeHeadChat;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(/*Could not decode attribute arguments.*/)]
[assembly: MelonInfo(typeof(RealTimeHeadChatMod), "Real-Time Head Chat", "1.0.0", "Local Utility", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : System.Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(/*Could not decode attribute arguments.*/)]
	internal sealed class RefSafetyRulesAttribute : System.Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace RealTimeHeadChat
{
	public sealed class RealTimeHeadChatMod : MelonMod
	{
		private sealed class Bubble
		{
			public NetworkPlayer Player;

			public RigNameTag Element;

			public float LastUpdate;
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static Action <0>__CreateMenu;

			public static ServerEvent <1>__ClearAll;

			public static PlayerUpdate <2>__OnPlayerLeft;

			public static Action <3>__ClearLocalText;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static Action<bool> <>9__16_0;

			public static Action <>9__16_1;

			internal void <CreateMenu>b__16_0(bool value)
			{
				_enabled.Value = value;
				MelonPreferences.Save();
				if (!value)
				{
					ClearLocalText();
				}
			}

			internal void <CreateMenu>b__16_1()
			{
				Menu.DisplayDialog("REAL-TIME HEAD CHAT", "It is enabled by default. Just type on your physical keyboard during Fusion play. Backspace edits the text; Enter clears it. It also clears after five seconds without typing. Every player who should see it needs this mod.", (Texture2D)null, (Action)null, (Action)null);
			}
		}

		internal const int MaxCharacters = 160;

		internal const int MaxUtf8Bytes = 640;

		private const float SendInterval = 0.1f;

		private const float RemoteTimeout = 12f;

		private const float LocalIdleTimeout = 5f;

		private static readonly Dictionary<byte, Bubble> Bubbles = new Dictionary<byte, Bubble>();

		private static MelonPreferences_Entry<bool> _enabled;

		private static string _pendingText = string.Empty;

		private static string _lastSentText = string.Empty;

		private static float _nextSendTime;

		private static float _lastKeyTime;

		private static bool _menuCreated;

		public override void OnInitializeMelon()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Expected O, but got Unknown
			MelonPreferences_Category val = MelonPreferences.CreateCategory("RealTimeHeadChat");
			_enabled = val.CreateEntry<bool>("Enabled", true, "Enabled", "When enabled, physical keyboard typing is shown above your head in Fusion.", false, false, (ValueValidator)null, (string)null);
			ModuleManager.RegisterModule<HeadChatModule>();
			object obj = <>O.<0>__CreateMenu;
			if (obj == null)
			{
				Action val2 = CreateMenu;
				<>O.<0>__CreateMenu = val2;
				obj = (object)val2;
			}
			Hooking.OnUIRigCreated += (Action)obj;
			object obj2 = <>O.<1>__ClearAll;
			if (obj2 == null)
			{
				ServerEvent val3 = ClearAll;
				<>O.<1>__ClearAll = val3;
				obj2 = (object)val3;
			}
			MultiplayerHooking.OnDisconnected += (ServerEvent)obj2;
			object obj3 = <>O.<2>__OnPlayerLeft;
			if (obj3 == null)
			{
				PlayerUpdate val4 = OnPlayerLeft;
				<>O.<2>__OnPlayerLeft = val4;
				obj3 = (object)val4;
			}
			MultiplayerHooking.OnPlayerLeft += (PlayerUpdate)obj3;
		}

		public override void OnDeinitializeMelon()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			object obj = <>O.<0>__CreateMenu;
			if (obj == null)
			{
				Action val = CreateMenu;
				<>O.<0>__CreateMenu = val;
				obj = (object)val;
			}
			Hooking.OnUIRigCreated -= (Action)obj;
			object obj2 = <>O.<1>__ClearAll;
			if (obj2 == null)
			{
				ServerEvent val2 = ClearAll;
				<>O.<1>__ClearAll = val2;
				obj2 = (object)val2;
			}
			MultiplayerHooking.OnDisconnected -= (ServerEvent)obj2;
			object obj3 = <>O.<2>__OnPlayerLeft;
			if (obj3 == null)
			{
				PlayerUpdate val3 = OnPlayerLeft;
				<>O.<2>__OnPlayerLeft = val3;
				obj3 = (object)val3;
			}
			MultiplayerHooking.OnPlayerLeft -= (PlayerUpdate)obj3;
			ClearAll();
		}

		public override void OnUpdate()
		{
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			CaptureKeyboardInput();
			if (_pendingText.Length != 0 && Time.unscaledTime - _lastKeyTime >= 5f)
			{
				ClearLocalText();
			}
			if (Time.unscaledTime >= _nextSendTime && _pendingText != _lastSentText)
			{
				Publish(_pendingText);
				_nextSendTime = Time.unscaledTime + 0.1f;
			}
			if (Bubbles.Count == 0)
			{
				return;
			}
			List<byte> val = null;
			Enumerator<byte, Bubble> enumerator = Bubbles.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					KeyValuePair<byte, Bubble> current = enumerator.Current;
					if (Time.unscaledTime - current.Value.LastUpdate > 12f)
					{
						if (val == null)
						{
							val = new List<byte>();
						}
						val.Add(current.Key);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			if (val == null)
			{
				return;
			}
			Enumerator<byte> enumerator2 = val.GetEnumerator();
			try
			{
				while (enumerator2.MoveNext())
				{
					byte current2 = enumerator2.Current;
					RemoveBubble(current2);
				}
			}
			finally
			{
				((System.IDisposable)enumerator2/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void CreateMenu()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Expected O, but got Unknown
			if (_menuCreated)
			{
				return;
			}
			Page val = Page.Root.CreatePage("Real-Time Head Chat", Color.cyan, 0, true);
			val.CreateBool("Enabled", Color.white, _enabled.Value, (Action<bool>)delegate(bool value)
			{
				_enabled.Value = value;
				MelonPreferences.Save();
				if (!value)
				{
					ClearLocalText();
				}
			});
			Color red = Color.red;
			object obj = <>O.<3>__ClearLocalText;
			if (obj == null)
			{
				Action val2 = ClearLocalText;
				<>O.<3>__ClearLocalText = val2;
				obj = (object)val2;
			}
			val.CreateFunction("Clear my text", red, (Action)obj);
			Color gray = Color.gray;
			object obj2 = <>c.<>9__16_1;
			if (obj2 == null)
			{
				Action val3 = delegate
				{
					Menu.DisplayDialog("REAL-TIME HEAD CHAT", "It is enabled by default. Just type on your physical keyboard during Fusion play. Backspace edits the text; Enter clears it. It also clears after five seconds without typing. Every player who should see it needs this mod.", (Texture2D)null, (Action)null, (Action)null);
				};
				<>c.<>9__16_1 = val3;
				obj2 = (object)val3;
			}
			val.CreateFunction("How to use", gray, (Action)obj2);
			_menuCreated = true;
		}

		private static void CaptureKeyboardInput()
		{
			if (_enabled == null || !_enabled.Value || !NetworkInfo.HasServer)
			{
				return;
			}
			string inputString = Input.inputString;
			if (string.IsNullOrEmpty(inputString))
			{
				return;
			}
			bool flag = false;
			string text = inputString;
			for (int i = 0; i < text.Length; i++)
			{
				char c = text[i];
				switch (c)
				{
				case '\b':
					if (_pendingText.Length > 0)
					{
						_pendingText = _pendingText.Substring(0, _pendingText.Length - 1);
						flag = true;
					}
					break;
				default:
					if (c != '\r')
					{
						if (!char.IsControl(c) && _pendingText.Length < 160)
						{
							_pendingText += c;
							flag = true;
						}
						break;
					}
					goto case '\n';
				case '\n':
					ClearLocalText();
					return;
				}
			}
			if (flag)
			{
				_lastKeyTime = Time.unscaledTime;
			}
		}

		private static void ClearLocalText()
		{
			_pendingText = string.Empty;
			if (_lastSentText.Length != 0)
			{
				Publish(string.Empty);
			}
		}

		private static void Publish(string text)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			text = Normalize(text);
			_lastSentText = text;
			if (NetworkInfo.HasServer)
			{
				MessageRelay.RelayModule<HeadChatMessage, HeadChatData>(new HeadChatData
				{
					Text = text
				}, CommonMessageRoutes.UnreliableToOtherClients);
			}
		}

		private static string Normalize(string text)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			if (string.IsNullOrEmpty(text))
			{
				return string.Empty;
			}
			text = text.Replace('\r', ' ').Replace('\n', ' ').Replace('\t', ' ');
			if (text.Length > 160)
			{
				text = text.Substring(0, 160);
			}
			StringBuilder val = new StringBuilder(text.Length);
			string text2 = text;
			foreach (char c in text2)
			{
				if (!char.IsControl(c))
				{
					val.Append(c);
				}
			}
			return ((object)val).ToString();
		}

		internal static void Receive(byte sender, string text)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Expected O, but got Unknown
			text = Normalize(text);
			NetworkPlayer val = default(NetworkPlayer);
			if (text.Length == 0)
			{
				RemoveBubble(sender);
			}
			else if (NetworkPlayerManager.TryGetPlayer(sender, ref val) && !val.NetworkEntity.IsOwner)
			{
				Bubble bubble = default(Bubble);
				if (!Bubbles.TryGetValue(sender, ref bubble) || bubble.Player != val)
				{
					RemoveBubble(sender);
					RigNameTag val2 = new RigNameTag
					{
						CrownVisible = false,
						Color = new Color(0.88f, 0.97f, 1f, 1f),
						Visible = true,
						Username = text
					};
					val.HeadUI.RegisterElement((IPopupLayoutElement)(object)val2);
					bubble = new Bubble
					{
						Player = val,
						Element = val2
					};
					Bubbles[sender] = bubble;
				}
				else
				{
					bubble.Element.Username = text;
				}
				bubble.LastUpdate = Time.unscaledTime;
			}
		}

		private static void OnPlayerLeft(PlayerID player)
		{
			if (player != null)
			{
				RemoveBubble(player.SmallID);
			}
		}

		private static void RemoveBubble(byte sender)
		{
			Bubble bubble = default(Bubble);
			if (Bubbles.TryGetValue(sender, ref bubble))
			{
				if (bubble.Player != null && bubble.Player.HeadUI != null)
				{
					bubble.Player.HeadUI.UnregisterElement((IPopupLayoutElement)(object)bubble.Element);
				}
				Bubbles.Remove(sender);
			}
		}

		private static void ClearAll()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			Enumerator<byte, Bubble> enumerator = Bubbles.Values.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					Bubble current = enumerator.Current;
					if (current.Player != null && current.Player.HeadUI != null)
					{
						current.Player.HeadUI.UnregisterElement((IPopupLayoutElement)(object)current.Element);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			Bubbles.Clear();
			_pendingText = string.Empty;
			_lastSentText = string.Empty;
		}
	}
	public sealed class HeadChatModule : Module
	{
		public override string Name => "Real-Time Head Chat";

		public override string Author => "Local Utility";

		public override Version Version => new Version(1, 0, 0);

		public override ConsoleColor Color => (ConsoleColor)11;

		protected override void OnModuleRegistered()
		{
			ModuleMessageManager.RegisterHandler<HeadChatMessage>();
		}
	}
	public sealed class HeadChatData : INetSerializable
	{
		public string Text = string.Empty;

		public void Serialize(INetSerializer serializer)
		{
			serializer.SerializeValue(ref Text);
		}
	}
	public sealed class HeadChatMessage : ModuleMessageHandler
	{
		protected override bool OnPreRelayMessage(ReceivedMessage received)
		{
			return ((ReceivedMessage)(ref received)).Sender.HasValue && ((ReceivedMessage)(ref received)).Bytes != null && ((ReceivedMessage)(ref received)).Bytes.Length >= 4 && ((ReceivedMessage)(ref received)).Bytes.Length <= 644;
		}

		protected override void OnHandleMessage(ReceivedMessage received)
		{
			if (((ReceivedMessage)(ref received)).Sender.HasValue && ((ReceivedMessage)(ref received)).Bytes != null && ((ReceivedMessage)(ref received)).Bytes.Length >= 4 && ((ReceivedMessage)(ref received)).Bytes.Length <= 644)
			{
				HeadChatData headChatData = ((ReceivedMessage)(ref received)).ReadData<HeadChatData>();
				RealTimeHeadChatMod.Receive(((ReceivedMessage)(ref received)).Sender.Value, headChatData.Text);
			}
		}
	}
}