Decompiled source of RealTimeHeadChat v1.5.0

RealTimeHeadChat.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using BoneLib;
using BoneLib.BoneMenu;
using HarmonyLib;
using Il2CppTMPro;
using LabFusion.Data;
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;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(/*Could not decode attribute arguments.*/)]
[assembly: MelonInfo(typeof(RealTimeHeadChatMod), "Real-Time Head Chat", "1.5.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;

			public string Text;

			public float BaseFontSize;

			public TextMeshProUGUI TextInstance;
		}

		[CompilerGenerated]
		private static class _003C_003EO
		{
			public static Action _003C0_003E__CreateMenu;

			public static ServerEvent _003C1_003E__ClearAll;

			public static PlayerUpdate _003C2_003E__OnPlayerLeft;

			public static Action _003C3_003E__ClearLocalText;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class _003C_003Ec
		{
			public static readonly _003C_003Ec _003C_003E9 = new _003C_003Ec();

			public static Action<bool> _003C_003E9__35_0;

			public static Action<bool> _003C_003E9__35_1;

			public static Action<bool> _003C_003E9__35_2;

			public static Action _003C_003E9__35_3;

			public static Action<float> _003C_003E9__36_0;

			public static Action<float> _003C_003E9__36_1;

			public static Action<float> _003C_003E9__36_2;

			internal void _003CCreateMenu_003Eb__35_0(bool value)
			{
				_enabled.Value = value;
				MelonPreferences.Save();
				if (!value)
				{
					ClearLocalText();
				}
			}

			internal void _003CCreateMenu_003Eb__35_1(bool disabled)
			{
				SetInputCapture(!disabled);
			}

			internal void _003CCreateMenu_003Eb__35_2(bool value)
			{
				_showHud.Value = value;
				MelonPreferences.Save();
				if ((Object)(object)_hudRoot != (Object)null)
				{
					_hudRoot.SetActive(_enabled.Value && value);
				}
			}

			internal void _003CCreateMenu_003Eb__35_3()
			{
				Menu.DisplayDialog("REAL-TIME HEAD CHAT", "Head Chat Enabled is the master switch. Disable Text Input stops only local typing and paste capture; toggle the same setting anytime with F2 or Page Up. Show VR HUD controls only your headset preview. Backspace edits; Enter clears; Ctrl+V pastes. Text clears after five seconds without typing. Everyone who should see overhead text needs this mod.", (Texture2D)null, (Action)null, (Action)null);
			}

			internal void _003CBuildPlacementMenu_003Eb__36_0(float value)
			{
				_hudX.Value = value;
				MelonPreferences.Save();
				ApplyHudPlacement();
			}

			internal void _003CBuildPlacementMenu_003Eb__36_1(float value)
			{
				_hudY.Value = value;
				MelonPreferences.Save();
				ApplyHudPlacement();
			}

			internal void _003CBuildPlacementMenu_003Eb__36_2(float value)
			{
				_hudDistance.Value = value;
				MelonPreferences.Save();
				ApplyHudPlacement();
			}
		}

		[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__39_0;

			public static Action<bool> <>9__39_1;

			public static Action<bool> <>9__39_2;

			public static Action <>9__39_3;

			public static Action<float> <>9__40_0;

			public static Action<float> <>9__40_1;

			public static Action<float> <>9__40_2;

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

			internal void <CreateMenu>b__39_1(bool disabled)
			{
				SetInputCapture(!disabled);
			}

			internal void <CreateMenu>b__39_2(bool value)
			{
				_showHud.Value = value;
				MelonPreferences.Save();
				if ((Object)(object)_hudRoot != (Object)null)
				{
					_hudRoot.SetActive(_enabled.Value && value);
				}
			}

			internal void <CreateMenu>b__39_3()
			{
				Menu.DisplayDialog("REAL-TIME HEAD CHAT", "Head Chat Enabled is the master switch. Disable Text Input stops only local typing and paste capture; toggle the same setting anytime with F2 or Page Up. Show VR HUD controls only your headset preview. Backspace edits; Enter clears; Ctrl+V pastes. Text clears after five seconds without typing. Everyone who should see overhead text needs this mod.", (Texture2D)null, (Action)null, (Action)null);
			}

			internal void <BuildPlacementMenu>b__40_0(float value)
			{
				_hudX.Value = value;
				MelonPreferences.Save();
				ApplyHudPlacement();
			}

			internal void <BuildPlacementMenu>b__40_1(float value)
			{
				_hudY.Value = value;
				MelonPreferences.Save();
				ApplyHudPlacement();
			}

			internal void <BuildPlacementMenu>b__40_2(float value)
			{
				_hudDistance.Value = value;
				MelonPreferences.Save();
				ApplyHudPlacement();
			}
		}

		private static float _canonicalNametagFontSize;

		internal const int MaxCharacters = 160;

		internal const int MaxUtf8Bytes = 4096;

		private const int SecondLineThreshold = 80;

		private const int ThirdLineThreshold = 120;

		private const float SendInterval = 0.1f;

		private const float RemoteTimeout = 12f;

		private const float LocalIdleTimeout = 5f;

		private const float DefaultHudXCm = -10.8f;

		private const float DefaultHudYCm = -7.2f;

		private const float DefaultHudDistanceCm = 15f;

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

		private static MelonPreferences_Entry<bool> _enabled;

		private static MelonPreferences_Entry<bool> _captureInput;

		private static MelonPreferences_Entry<bool> _showHud;

		private static MelonPreferences_Entry<float> _hudX;

		private static MelonPreferences_Entry<float> _hudY;

		private static MelonPreferences_Entry<float> _hudDistance;

		private static string _pendingText = string.Empty;

		private static string _lastSentText = string.Empty;

		private static float _nextSendTime;

		private static float _lastKeyTime;

		private static bool _menuCreated;

		private static BoolElement _disableInputElement;

		private static GameObject _hudRoot;

		private static TMP_Text _hudText;

		public override void OnInitializeMelon()
		{
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Expected O, but got Unknown
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: Expected O, but got Unknown
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Expected O, but got Unknown
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Expected O, but got Unknown
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Expected O, but got Unknown
			MelonPreferences_Category val = MelonPreferences.CreateCategory("RealTimeHeadChat");
			_enabled = val.CreateEntry<bool>("Enabled", true, "Head Chat Enabled", "Controls keyboard capture and broadcasting overhead text to other modded players.", false, false, (ValueValidator)null, (string)null);
			_captureInput = val.CreateEntry<bool>("CaptureKeyboardInput", true, "Capture Keyboard Input", "Controls only local typing and paste capture. Toggle with F2 or Page Up.", false, false, (ValueValidator)null, (string)null);
			_showHud = val.CreateEntry<bool>("ShowTypingHud", true, "Show VR HUD", "Controls only the local lower-left headset preview. Head chat remains active when hidden.", false, false, (ValueValidator)null, (string)null);
			_hudX = val.CreateEntry<float>("HudHorizontalCm", -10.8f, "HUD Horizontal Position (cm)", "Negative values move left.", false, false, (ValueValidator)null, (string)null);
			_hudY = val.CreateEntry<float>("HudVerticalCm", -7.2f, "HUD Vertical Position (cm)", "Negative values move down.", false, false, (ValueValidator)null, (string)null);
			_hudDistance = val.CreateEntry<float>("HudDistanceCm", 15f, "HUD Distance (cm)", "Distance in centimeters from the headset.", false, false, (ValueValidator)null, (string)null);
			ModuleManager.RegisterModule<HeadChatModule>();
			object obj = _003C_003EO._003C0_003E__CreateMenu;
			if (obj == null)
			{
				object obj2 = <>O.<0>__CreateMenu;
				if (obj2 == null)
				{
					Action val2 = CreateMenu;
					<>O.<0>__CreateMenu = val2;
					obj2 = (object)val2;
				}
				obj = (_003C_003EO._003C0_003E__CreateMenu = (Action)obj2);
			}
			Hooking.OnUIRigCreated += (Action)obj;
			object obj3 = _003C_003EO._003C1_003E__ClearAll;
			if (obj3 == null)
			{
				object obj4 = <>O.<1>__ClearAll;
				if (obj4 == null)
				{
					ServerEvent val3 = ClearAll;
					<>O.<1>__ClearAll = val3;
					obj4 = (object)val3;
				}
				obj3 = (_003C_003EO._003C1_003E__ClearAll = (ServerEvent)obj4);
			}
			MultiplayerHooking.OnDisconnected += (ServerEvent)obj3;
			object obj5 = _003C_003EO._003C2_003E__OnPlayerLeft;
			if (obj5 == null)
			{
				object obj6 = <>O.<2>__OnPlayerLeft;
				if (obj6 == null)
				{
					PlayerUpdate val4 = OnPlayerLeft;
					<>O.<2>__OnPlayerLeft = val4;
					obj6 = (object)val4;
				}
				obj5 = (_003C_003EO._003C2_003E__OnPlayerLeft = (PlayerUpdate)obj6);
			}
			MultiplayerHooking.OnPlayerLeft += (PlayerUpdate)obj5;
		}

		public override void OnDeinitializeMelon()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			object obj = _003C_003EO._003C0_003E__CreateMenu;
			if (obj == null)
			{
				object obj2 = <>O.<0>__CreateMenu;
				if (obj2 == null)
				{
					Action val = CreateMenu;
					<>O.<0>__CreateMenu = val;
					obj2 = (object)val;
				}
				obj = (_003C_003EO._003C0_003E__CreateMenu = (Action)obj2);
			}
			Hooking.OnUIRigCreated -= (Action)obj;
			object obj3 = _003C_003EO._003C1_003E__ClearAll;
			if (obj3 == null)
			{
				object obj4 = <>O.<1>__ClearAll;
				if (obj4 == null)
				{
					ServerEvent val2 = ClearAll;
					<>O.<1>__ClearAll = val2;
					obj4 = (object)val2;
				}
				obj3 = (_003C_003EO._003C1_003E__ClearAll = (ServerEvent)obj4);
			}
			MultiplayerHooking.OnDisconnected -= (ServerEvent)obj3;
			object obj5 = _003C_003EO._003C2_003E__OnPlayerLeft;
			if (obj5 == null)
			{
				object obj6 = <>O.<2>__OnPlayerLeft;
				if (obj6 == null)
				{
					PlayerUpdate val3 = OnPlayerLeft;
					<>O.<2>__OnPlayerLeft = val3;
					obj6 = (object)val3;
				}
				obj5 = (_003C_003EO._003C2_003E__OnPlayerLeft = (PlayerUpdate)obj6);
			}
			MultiplayerHooking.OnPlayerLeft -= (PlayerUpdate)obj5;
			DestroyHud();
			ClearAll();
		}

		public override void OnUpdate()
		{
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			EnsureVrHud();
			if (IsInputTogglePressed())
			{
				SetInputCapture(!_captureInput.Value);
			}
			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;
					UpdateBubbleAppearance(current.Value);
					if (Time.unscaledTime - current.Value.LastUpdate > 12f)
					{
						if (val == null)
						{
							val = new List<byte>();
						}
						val.Add(current.Key);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator).Dispose();
			}
			if (val == null)
			{
				return;
			}
			Enumerator<byte> enumerator2 = val.GetEnumerator();
			try
			{
				while (enumerator2.MoveNext())
				{
					byte current2 = enumerator2.Current;
					RemoveBubble(current2);
				}
			}
			finally
			{
				((System.IDisposable)enumerator2).Dispose();
			}
		}

		public override void OnLateUpdate()
		{
			//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;
					StabilizeBubbleScale(current);
				}
			}
			finally
			{
				((System.IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void EnsureVrHud()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			if (_enabled != null && RigData.HasPlayer)
			{
				if ((Object)(object)_hudRoot == (Object)null)
				{
					CreateVrHud(RigData.Refs.Head);
				}
				if (!((Object)(object)_hudRoot == (Object)null) && !((Object)_hudText == (Object)null))
				{
					_hudRoot.SetActive(_enabled.Value && _showHud.Value);
					ApplyHudPlacement();
					bool flag = string.IsNullOrEmpty(_pendingText);
					_hudText.text = ((!_captureInput.Value) ? "Input disabled (F2 / Page Up)" : (flag ? "Start typing..." : _pendingText));
					_hudText.fontSize = 24f;
					((Graphic)_hudText).color = (flag ? new Color(0.65f, 0.68f, 0.72f, 1f) : new Color(0.88f, 0.97f, 1f, 1f));
				}
			}
		}

		private static void CreateVrHud(Transform head)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)head == (Object)null))
			{
				_hudRoot = new GameObject("Real-Time Head Chat VR HUD");
				_hudRoot.transform.SetParent(head, false);
				ApplyHudPlacement();
				_hudRoot.transform.localRotation = Quaternion.identity;
				_hudRoot.transform.localScale = Vector3.one * 0.000145f;
				Canvas val = _hudRoot.AddComponent<Canvas>();
				val.renderMode = (RenderMode)2;
				val.sortingOrder = 500;
				_hudRoot.GetComponent<RectTransform>().sizeDelta = new Vector2(480f, 92f);
				GameObject val2 = new GameObject("Background");
				val2.transform.SetParent(_hudRoot.transform, false);
				Image val3 = val2.AddComponent<Image>();
				((Graphic)val3).color = new Color(0.025f, 0.03f, 0.04f, 0.78f);
				((Graphic)val3).raycastTarget = false;
				SetRect(((Graphic)val3).rectTransform, Vector2.zero, new Vector2(480f, 92f));
				TMP_Text val4 = CreateHudText("HEAD CHAT", 17f, new Color(0.9f, 0.92f, 0.95f, 1f));
				SetRect(val4.rectTransform, new Vector2(0f, 27f), new Vector2(450f, 24f));
				_hudText = CreateHudText("Start typing...", 24f, new Color(0.65f, 0.68f, 0.72f, 1f));
				_hudText.enableWordWrapping = true;
				SetRect(_hudText.rectTransform, new Vector2(0f, -13f), new Vector2(450f, 52f));
			}
		}

		private static TMP_Text CreateHudText(string value, float size, Color color)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Text - " + value);
			val.transform.SetParent(_hudRoot.transform, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			if ((Object)(object)TMP_Settings.defaultFontAsset != (Object)null)
			{
				((TMP_Text)val2).font = TMP_Settings.defaultFontAsset;
			}
			((TMP_Text)val2).text = value;
			((TMP_Text)val2).fontSize = size;
			((TMP_Text)val2).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val2).richText = false;
			((Graphic)val2).color = color;
			((Graphic)val2).raycastTarget = false;
			return (TMP_Text)(object)val2;
		}

		private static void SetRect(RectTransform rect, Vector2 position, Vector2 size)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			rect.anchorMin = new Vector2(0.5f, 0.5f);
			rect.anchorMax = new Vector2(0.5f, 0.5f);
			rect.pivot = new Vector2(0.5f, 0.5f);
			rect.anchoredPosition = position;
			rect.sizeDelta = size;
			((Transform)rect).localScale = Vector3.one;
			((Transform)rect).localRotation = Quaternion.identity;
		}

		private static void DestroyHud()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			if ((Object)(object)_hudRoot != (Object)null)
			{
				Object.Destroy((Object)_hudRoot);
			}
			_hudRoot = null;
			_hudText = null;
		}

		private static void ApplyHudPlacement()
		{
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_hudRoot == (Object)null) && _hudX != null && _hudY != null && _hudDistance != null)
			{
				_hudRoot.transform.localPosition = new Vector3(Mathf.Clamp(_hudX.Value, -50f, 50f) / 100f, Mathf.Clamp(_hudY.Value, -50f, 50f) / 100f, Mathf.Clamp(_hudDistance.Value, 5f, 75f) / 100f);
			}
		}

		private static void CreateMenu()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Expected O, but got Unknown
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Expected O, but got Unknown
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Expected O, but got Unknown
			//IL_0178: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Expected O, but got Unknown
			if (_menuCreated)
			{
				return;
			}
			Page val = Page.Root.CreatePage("Real-Time Head Chat", Color.cyan, 0, true);
			val.CreateBool("Head Chat Enabled", Color.white, _enabled.Value, (Action<bool>)delegate(bool value)
			{
				_enabled.Value = value;
				MelonPreferences.Save();
				if (!value)
				{
					ClearLocalText();
				}
			});
			_disableInputElement = val.CreateBool("Disable Text Input", Color.yellow, !_captureInput.Value, (Action<bool>)delegate(bool disabled)
			{
				SetInputCapture(!disabled);
			});
			val.CreateBool("Show VR HUD", Color.cyan, _showHud.Value, (Action<bool>)delegate(bool value)
			{
				_showHud.Value = value;
				MelonPreferences.Save();
				if ((Object)(object)_hudRoot != (Object)null)
				{
					_hudRoot.SetActive(_enabled.Value && value);
				}
			});
			Page page = val.CreatePage("VR HUD Placement", Color.yellow, 0, true);
			BuildPlacementMenu(page);
			Color red = Color.red;
			object obj = _003C_003EO._003C3_003E__ClearLocalText;
			if (obj == null)
			{
				object obj2 = <>O.<3>__ClearLocalText;
				if (obj2 == null)
				{
					Action val2 = ClearLocalText;
					<>O.<3>__ClearLocalText = val2;
					obj2 = (object)val2;
				}
				obj = (_003C_003EO._003C3_003E__ClearLocalText = (Action)obj2);
			}
			val.CreateFunction("Clear my text", red, (Action)obj);
			Color gray = Color.gray;
			object obj3 = _003C_003Ec._003C_003E9__35_3;
			if (obj3 == null)
			{
				object obj4 = <>c.<>9__39_3;
				if (obj4 == null)
				{
					Action val3 = delegate
					{
						Menu.DisplayDialog("REAL-TIME HEAD CHAT", "Head Chat Enabled is the master switch. Disable Text Input stops only local typing and paste capture; toggle the same setting anytime with F2 or Page Up. Show VR HUD controls only your headset preview. Backspace edits; Enter clears; Ctrl+V pastes. Text clears after five seconds without typing. Everyone who should see overhead text needs this mod.", (Texture2D)null, (Action)null, (Action)null);
					};
					<>c.<>9__39_3 = val3;
					obj4 = (object)val3;
				}
				obj3 = (_003C_003Ec._003C_003E9__35_3 = (Action)obj4);
			}
			val.CreateFunction("How to use", gray, (Action)obj3);
			_menuCreated = true;
		}

		private static void BuildPlacementMenu(Page page)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Expected O, but got Unknown
			page.RemoveAll();
			page.CreateFloat("Horizontal (cm)", Color.white, _hudX.Value, 0.5f, -50f, 50f, (Action<float>)delegate(float value)
			{
				_hudX.Value = value;
				MelonPreferences.Save();
				ApplyHudPlacement();
			});
			page.CreateFloat("Vertical (cm)", Color.white, _hudY.Value, 0.5f, -50f, 50f, (Action<float>)delegate(float value)
			{
				_hudY.Value = value;
				MelonPreferences.Save();
				ApplyHudPlacement();
			});
			page.CreateFloat("Distance from headset (cm)", Color.white, _hudDistance.Value, 1f, 5f, 75f, (Action<float>)delegate(float value)
			{
				_hudDistance.Value = value;
				MelonPreferences.Save();
				ApplyHudPlacement();
			});
			page.CreateFunction("Reset placement", Color.red, (Action)delegate
			{
				_hudX.Value = -10.8f;
				_hudY.Value = -7.2f;
				_hudDistance.Value = 15f;
				MelonPreferences.Save();
				ApplyHudPlacement();
				BuildPlacementMenu(page);
			});
		}

		private static void CaptureKeyboardInput()
		{
			if (_enabled == null || !_enabled.Value || _captureInput == null || !_captureInput.Value || !NetworkInfo.HasServer)
			{
				return;
			}
			if (IsPastePressed())
			{
				string text = Normalize(GUIUtility.systemCopyBuffer);
				int num = 160 - GetTextElementCount(_pendingText);
				if (num > 0 && text.Length > 0)
				{
					_pendingText += TruncateTextElements(text, num);
					_lastKeyTime = Time.unscaledTime;
				}
				return;
			}
			string inputString = Input.inputString;
			if (string.IsNullOrEmpty(inputString))
			{
				return;
			}
			bool flag = false;
			string text2 = inputString;
			for (int i = 0; i < text2.Length; i++)
			{
				char c = text2[i];
				switch (c)
				{
				case '\b':
					if (_pendingText.Length > 0)
					{
						int[] array = StringInfo.ParseCombiningCharacters(_pendingText);
						_pendingText = ((array.Length <= 1) ? string.Empty : _pendingText.Substring(0, array[array.Length - 1]));
						flag = true;
					}
					continue;
				default:
					if (c != '\r')
					{
						if (!char.IsControl(c) && GetTextElementCount(_pendingText) < 160)
						{
							_pendingText += c;
							flag = true;
						}
						continue;
					}
					break;
				case '\n':
					break;
				}
				ClearLocalText();
				return;
			}
			if (flag)
			{
				_lastKeyTime = Time.unscaledTime;
			}
		}

		private static bool IsPastePressed()
		{
			if ((Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305)) && Input.GetKeyDown((KeyCode)118))
			{
				return true;
			}
			Keyboard current = Keyboard.current;
			return current != null && (((ButtonControl)current.leftCtrlKey).isPressed || ((ButtonControl)current.rightCtrlKey).isPressed) && ((ButtonControl)current.vKey).wasPressedThisFrame;
		}

		private static bool IsInputTogglePressed()
		{
			if (_captureInput == null)
			{
				return false;
			}
			if (Input.GetKeyDown((KeyCode)283) || Input.GetKeyDown((KeyCode)280))
			{
				return true;
			}
			Keyboard current = Keyboard.current;
			return current != null && (((ButtonControl)current.f2Key).wasPressedThisFrame || ((ButtonControl)current.pageUpKey).wasPressedThisFrame);
		}

		private static void SetInputCapture(bool enabled)
		{
			if (_captureInput != null)
			{
				_captureInput.Value = enabled;
				if (_disableInputElement != null && _disableInputElement.Value == enabled)
				{
					_disableInputElement.Value = !enabled;
				}
				MelonPreferences.Save();
				if (!enabled)
				{
					ClearLocalText();
				}
				MelonLogger.Msg("Keyboard input capture " + (enabled ? "enabled" : "disabled") + ".");
			}
		}

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

		private static void Publish(string text)
		{
			//IL_0025: 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.ReliableToClients);
			}
		}

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

		private static int GetTextElementCount(string text)
		{
			return (!string.IsNullOrEmpty(text)) ? StringInfo.ParseCombiningCharacters(text).Length : 0;
		}

		private static string TruncateTextElements(string text, int maximum)
		{
			if (string.IsNullOrEmpty(text) || maximum <= 0)
			{
				return string.Empty;
			}
			int[] array = StringInfo.ParseCombiningCharacters(text);
			if (array.Length <= maximum)
			{
				return text;
			}
			return text.Substring(0, array[maximum]);
		}

		private static List<string> GetTextElements(string text)
		{
			List<string> val = new List<string>();
			TextElementEnumerator textElementEnumerator = StringInfo.GetTextElementEnumerator(text ?? string.Empty);
			while (textElementEnumerator.MoveNext())
			{
				val.Add(textElementEnumerator.GetTextElement());
			}
			return val;
		}

		private static string FormatDisplayText(string text)
		{
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			int textElementCount = GetTextElementCount(text);
			if (string.IsNullOrEmpty(text) || textElementCount <= 80)
			{
				return text ?? string.Empty;
			}
			int num = ((textElementCount > 120) ? 3 : 2);
			StringBuilder val = new StringBuilder(text.Length + num - 1);
			List<string> textElements = GetTextElements(text);
			for (int num2 = num; num2 > 1; num2--)
			{
				int target = textElements.Count / num2;
				int num3 = FindWordSplit(textElements, target);
				for (int i = 0; i < num3; i++)
				{
					val.Append(textElements[i]);
				}
				while (val.Length > 0 && val[val.Length - 1] == ' ')
				{
					int length = val.Length;
					val.Length = length - 1;
				}
				val.Append('\n');
				textElements.RemoveRange(0, num3);
				while (textElements.Count > 0 && textElements[0] == " ")
				{
					textElements.RemoveAt(0);
				}
			}
			Enumerator<string> enumerator = textElements.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					string current = enumerator.Current;
					val.Append(current);
				}
			}
			finally
			{
				((System.IDisposable)enumerator).Dispose();
			}
			return ((object)val).ToString();
		}

		private static int FindWordSplit(List<string> elements, int target)
		{
			int num = Math.Max(1, target / 2);
			for (int num2 = Math.Min(target, elements.Count - 1); num2 >= num; num2--)
			{
				if (elements[num2] == " ")
				{
					return num2;
				}
			}
			for (int i = Math.Min(target, elements.Count - 1); i < elements.Count; i++)
			{
				if (elements[i] == " ")
				{
					return i;
				}
			}
			return Math.Min(target, elements.Count - 1);
		}

		private static void UpdateBubbleAppearance(Bubble bubble)
		{
			if (bubble == null || bubble.Element == null)
			{
				return;
			}
			StabilizeBubbleScale(bubble);
			string text = FormatDisplayText(bubble.Text);
			if (!string.Equals(bubble.Element.Username, text, (StringComparison)4))
			{
				bubble.Element.Username = text;
			}
			TextMeshProUGUI text2 = bubble.Element.Text;
			if ((Object)(object)text2 == (Object)null)
			{
				return;
			}
			if ((Object)(object)bubble.TextInstance != (Object)(object)text2)
			{
				bubble.TextInstance = text2;
				if (_canonicalNametagFontSize <= 0f)
				{
					_canonicalNametagFontSize = ((TMP_Text)text2).fontSize;
				}
				bubble.BaseFontSize = _canonicalNametagFontSize;
			}
			int textElementCount = GetTextElementCount(bubble.Text);
			bool flag = (((TMP_Text)text2).enableWordWrapping = textElementCount > 80);
			bool flag3 = flag;
			float num = ((textElementCount > 120) ? 0.74f : (flag3 ? 0.82f : 1f));
			((TMP_Text)text2).enableAutoSizing = false;
			((TMP_Text)text2).fontSize = bubble.BaseFontSize * num;
		}

		private static void StabilizeBubbleScale(Bubble bubble)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			if (bubble != null && bubble.Element != null && !((Object)(object)bubble.Element.Transform == (Object)null))
			{
				bubble.Element.Transform.localScale = Vector3.one;
			}
		}

		private static void RestoreBubbleFont(Bubble bubble)
		{
			if (bubble != null && !((Object)(object)bubble.TextInstance == (Object)null) && !(bubble.BaseFontSize <= 0f))
			{
				((TMP_Text)bubble.TextInstance).fontSize = bubble.BaseFontSize;
				StabilizeBubbleScale(bubble);
			}
		}

		internal static void Receive(byte sender, string text)
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Expected O, but got Unknown
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Expected O, but got Unknown
			text = Normalize(text);
			NetworkPlayer val = null;
			if (text.Length == 0)
			{
				RemoveBubble(sender);
			}
			else if (NetworkPlayerManager.TryGetPlayer(sender, ref val) && !val.NetworkEntity.IsOwner)
			{
				Bubble bubble = null;
				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 = FormatDisplayText(text)
					};
					val.HeadUI.RegisterElement((IPopupLayoutElement)val2);
					bubble = new Bubble
					{
						Player = val,
						Element = val2,
						Text = text
					};
					Bubbles[sender] = bubble;
				}
				else
				{
					bubble.Text = text;
				}
				UpdateBubbleAppearance(bubble);
				bubble.LastUpdate = Time.unscaledTime;
			}
		}

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

		private static void RemoveBubble(byte sender)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			Bubble bubble = null;
			if (Bubbles.TryGetValue(sender, ref bubble))
			{
				RestoreBubbleFont(bubble);
				if (bubble.Player != null && bubble.Player.HeadUI != null)
				{
					bubble.Player.HeadUI.UnregisterElement((IPopupLayoutElement)bubble.Element);
				}
				Bubbles.Remove(sender);
			}
		}

		private static void ClearAll()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			Enumerator<byte, Bubble> enumerator = Bubbles.Values.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					Bubble current = enumerator.Current;
					RestoreBubbleFont(current);
					if (current.Player != null && current.Player.HeadUI != null)
					{
						current.Player.HeadUI.UnregisterElement((IPopupLayoutElement)current.Element);
					}
				}
			}
			finally
			{
				((System.IDisposable)enumerator).Dispose();
			}
			Bubbles.Clear();
			_pendingText = string.Empty;
			_lastSentText = string.Empty;
		}
	}
	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)).Bytes != null && ((ReceivedMessage)(ref received)).Bytes.Length >= 4 && ((ReceivedMessage)(ref received)).Bytes.Length <= 4100;
		}

		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 <= 4100)
			{
				HeadChatData headChatData = ((ReceivedMessage)(ref received)).ReadData<HeadChatData>();
				RealTimeHeadChatMod.Receive(((ReceivedMessage)(ref received)).Sender.Value, headChatData.Text);
			}
		}
	}
	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, 5, 0);

		public override ConsoleColor Color => (ConsoleColor)11;

		protected override void OnModuleRegistered()
		{
			ModuleMessageManager.RegisterHandler<HeadChatMessage>();
		}
	}
	[HarmonyPatch(typeof(ModuleMessageManager), "PreRelayMessage")]
	internal static class ModulePreRelayHostDeliveryPatch
	{
		private static void Prefix(ReceivedMessage received)
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkInfo.IsHost || !((ReceivedMessage)(ref received)).Sender.HasValue || ((ReceivedMessage)(ref received)).Bytes == null || ((ReceivedMessage)(ref received)).Bytes.Length < 12)
			{
				return;
			}
			long? handlerTagByType = ModuleMessageManager.GetHandlerTagByType(typeof(HeadChatMessage));
			if (handlerTagByType.HasValue && ReadBigEndianInt64(((ReceivedMessage)(ref received)).Bytes) == handlerTagByType.Value)
			{
				int num = ((ReceivedMessage)(ref received)).Bytes.Length - 8;
				if (num >= 4 && num <= 4100)
				{
					byte[] array = new byte[num];
					Buffer.BlockCopy((System.Array)((ReceivedMessage)(ref received)).Bytes, 8, (System.Array)array, 0, num);
					ReceivedMessage val = default(ReceivedMessage);
					((ReceivedMessage)(ref val)).Sender = ((ReceivedMessage)(ref received)).Sender;
					((ReceivedMessage)(ref val)).Bytes = array;
					((ReceivedMessage)(ref val)).IsServerHandled = true;
					HeadChatData headChatData = ((ReceivedMessage)(ref val)).ReadData<HeadChatData>();
					RealTimeHeadChatMod.Receive(((ReceivedMessage)(ref received)).Sender.Value, headChatData.Text);
				}
			}
		}

		private static long ReadBigEndianInt64(byte[] bytes)
		{
			ulong num = 0uL;
			for (int i = 0; i < 8; i++)
			{
				num = (num << 8) | bytes[i];
			}
			return (long)num;
		}
	}
}