Decompiled source of AtlyssChatBubbleFix v1.0.0

AtlyssChatBubbleFix.dll

Decompiled 17 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AtlyssChatBubbleFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("AtlyssChatBubbleFix")]
[assembly: AssemblyTitle("AtlyssChatBubbleFix")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace AtlyssChatBubbleFix
{
	internal enum BubbleAnchorMode
	{
		AutoHybrid,
		HeadBoneOnly,
		MeshBoundsOnly,
		ControllerOnly
	}
	internal enum BubbleAnchorPath
	{
		None,
		HeadBone,
		MeshBounds,
		Controller
	}
	internal static class BubbleAnchorResolver
	{
		internal static bool TryResolve(ChatBubbleState state, ChatBubbleConfig config, out Vector3 anchor, out BubbleAnchorPath path)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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_0093: 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)
			anchor = default(Vector3);
			path = BubbleAnchorPath.None;
			BubbleAnchorMode value = config.AnchorMode.Value;
			bool num = value == BubbleAnchorMode.AutoHybrid || value == BubbleAnchorMode.HeadBoneOnly;
			bool flag = value == BubbleAnchorMode.AutoHybrid || value == BubbleAnchorMode.MeshBoundsOnly;
			Bounds bounds = default(Bounds);
			bool flag2 = (flag || value == BubbleAnchorMode.AutoHybrid || value == BubbleAnchorMode.HeadBoneOnly) && PlayerRendererCache.TryGetBounds(state.Player, state.Renderers, out bounds);
			if (num && TryResolveHead(state.Player, config, flag2, bounds, out anchor))
			{
				path = BubbleAnchorPath.HeadBone;
				return true;
			}
			if (flag && flag2)
			{
				anchor = new Vector3(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).max.y + BoundsPadding(config), ((Bounds)(ref bounds)).center.z);
				path = BubbleAnchorPath.MeshBounds;
				return true;
			}
			if (TryResolveController(state.Player, config, out anchor))
			{
				path = BubbleAnchorPath.Controller;
				return true;
			}
			return false;
		}

		private static bool TryResolveHead(Player player, ChatBubbleConfig config, bool hasBounds, Bounds bounds, out Vector3 anchor)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: 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_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: 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_008a: Unknown result type (might be due to invalid IL or missing references)
			anchor = default(Vector3);
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			Transform val = null;
			try
			{
				PlayerVisual pVisual = player._pVisual;
				if ((Object)(object)pVisual != (Object)null && (Object)(object)pVisual._playerRaceModel != (Object)null)
				{
					val = pVisual._playerRaceModel._headBoneTransform;
				}
			}
			catch
			{
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Vector3 position;
			try
			{
				position = val.position;
			}
			catch
			{
				return false;
			}
			if (!IsUsable(position))
			{
				return false;
			}
			anchor = position + Vector3.up * HeadPadding(config);
			if (hasBounds)
			{
				float num = ((Bounds)(ref bounds)).max.y + BoundsPadding(config);
				if (anchor.y < num)
				{
					anchor.y = num;
				}
			}
			return true;
		}

		private static bool TryResolveController(Player player, ChatBubbleConfig config, out Vector3 anchor)
		{
			//IL_0001: 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_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: 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_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_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			anchor = default(Vector3);
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			CharacterController val = null;
			try
			{
				PlayerMove pMove = player._pMove;
				if ((Object)(object)pMove != (Object)null)
				{
					val = pMove._playerController;
				}
			}
			catch
			{
			}
			if ((Object)(object)val == (Object)null)
			{
				try
				{
					val = ((Component)player).GetComponent<CharacterController>();
				}
				catch
				{
					return false;
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			Transform transform;
			try
			{
				transform = ((Component)val).transform;
			}
			catch
			{
				return false;
			}
			Vector3 val2;
			try
			{
				val2 = transform.TransformPoint(val.center);
			}
			catch
			{
				val2 = transform.position;
			}
			Vector3 lossyScale = transform.lossyScale;
			float num = val.height * Mathf.Max(0.01f, Mathf.Abs(lossyScale.y));
			anchor = val2 + Vector3.up * (num * 0.5f + ControllerPadding(config));
			return IsUsable(anchor);
		}

		private static float HeadPadding(ChatBubbleConfig config)
		{
			return ClampHeight(config.HeadPaddingMeters.Value);
		}

		private static float BoundsPadding(ChatBubbleConfig config)
		{
			return ClampHeight(config.BoundsPaddingMeters.Value);
		}

		private static float ControllerPadding(ChatBubbleConfig config)
		{
			return ClampHeight(config.ControllerFallbackPaddingMeters.Value);
		}

		private static float ClampHeight(float value)
		{
			return Mathf.Clamp(value, 0.25f, 12f);
		}

		private static bool IsUsable(Vector3 value)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (IsUsable(value.x) && IsUsable(value.y))
			{
				return IsUsable(value.z);
			}
			return false;
		}

		private static bool IsUsable(float value)
		{
			if (!float.IsNaN(value))
			{
				return !float.IsInfinity(value);
			}
			return false;
		}
	}
	internal static class ChatBehaviourPatches
	{
		[HarmonyPatch(typeof(ChatBehaviour), "Awake")]
		[HarmonyPostfix]
		private static void AfterAwake(ChatBehaviour __instance)
		{
			try
			{
				ChatBubblePlugin.Registry?.Add(__instance);
			}
			catch (Exception ex)
			{
				ChatBubblePlugin.Log.LogWarning((object)("[Patch] ChatBehaviour.Awake failed: " + ex.Message));
			}
		}

		[HarmonyPatch(typeof(ChatBehaviour), "Update")]
		[HarmonyPostfix]
		private static void AfterUpdate(ChatBehaviour __instance)
		{
			try
			{
				ChatBubblePlugin.Registry?.Add(__instance);
			}
			catch
			{
			}
		}

		[HarmonyPatch(typeof(ChatBehaviour), "OnDestroy")]
		[HarmonyPostfix]
		private static void AfterDestroy(ChatBehaviour __instance)
		{
			try
			{
				ChatBubblePlugin.Registry?.Remove(__instance);
			}
			catch
			{
			}
		}
	}
	internal static class ChatBubbleAccess
	{
		private static readonly FieldInfo? BubbleObjectField = AccessTools.Field(typeof(ChatBehaviour), "_chatBubbleObject");

		private static readonly FieldInfo? PlayerField = AccessTools.Field(typeof(ChatBehaviour), "_player");

		private static readonly FieldInfo? MaxOnscreenField = AccessTools.Field(typeof(ChatBehaviour), "maxOnscreenMessages") ?? AccessTools.Field(typeof(ChatBehaviour), "_maxOnscreenMessages");

		internal static GameObject? GetBubbleObject(ChatBehaviour chat)
		{
			if ((Object)(object)chat == (Object)null || BubbleObjectField == null)
			{
				return null;
			}
			try
			{
				object? value = BubbleObjectField.GetValue(chat);
				return (GameObject?)((value is GameObject) ? value : null);
			}
			catch
			{
				return null;
			}
		}

		internal static Player? GetPlayer(ChatBehaviour chat)
		{
			if ((Object)(object)chat == (Object)null)
			{
				return null;
			}
			try
			{
				object? obj = PlayerField?.GetValue(chat);
				Player val = (Player)((obj is Player) ? obj : null);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			catch
			{
			}
			try
			{
				return ((Component)chat).GetComponent<Player>();
			}
			catch
			{
				return null;
			}
		}

		internal static void SendLocalMessage(ChatBehaviour chat, string message)
		{
			if ((Object)(object)chat == (Object)null)
			{
				return;
			}
			try
			{
				try
				{
					MaxOnscreenField?.SetValue(chat, 50);
				}
				catch
				{
				}
				chat.Init_GameLogicMessage(message);
			}
			catch (Exception ex)
			{
				ChatBubblePlugin.Log.LogWarning((object)("[Commands] Failed to print local message: " + ex.Message));
			}
		}

		internal static void CloseChat(ChatBehaviour chat)
		{
			if ((Object)(object)chat == (Object)null)
			{
				return;
			}
			try
			{
				string[] obj = new string[5] { "Init_ChatFocusFalse", "Set_ChatFocusFalse", "Close_Chat", "Deactivate_Chat", "Exit_Chat" };
				Type type = ((object)chat).GetType();
				string[] array = obj;
				foreach (string text in array)
				{
					MethodInfo methodInfo = AccessTools.Method(type, text, (Type[])null, (Type[])null);
					if (!(methodInfo == null))
					{
						methodInfo.Invoke(chat, null);
						return;
					}
				}
				AccessTools.Field(type, "_focusedInChat")?.SetValue(chat, false);
			}
			catch
			{
			}
		}
	}
	[HarmonyPatch(typeof(ChatBehaviour), "Send_ChatMessage")]
	internal static class ChatBubbleCommands
	{
		private static readonly string[] Prefixes = new string[4] { "/bubblefix", "/chatbubble", "/cbfix", "/bubble" };

		[HarmonyPrefix]
		private static bool Prefix(ChatBehaviour __instance, string _message)
		{
			try
			{
				if (string.IsNullOrWhiteSpace(_message))
				{
					return true;
				}
				string text = _message.Trim();
				string text2 = null;
				string[] prefixes = Prefixes;
				foreach (string text3 in prefixes)
				{
					if (text.StartsWith(text3, StringComparison.OrdinalIgnoreCase) && (text.Length == text3.Length || char.IsWhiteSpace(text[text3.Length])))
					{
						text2 = text3;
						break;
					}
				}
				if (text2 == null)
				{
					return true;
				}
				string text4 = text.Substring(text2.Length).Trim();
				Handle(__instance, string.IsNullOrEmpty(text4) ? "status" : text4);
				ChatBubbleAccess.CloseChat(__instance);
				return false;
			}
			catch (Exception ex)
			{
				ChatBubblePlugin.Log.LogWarning((object)("[Commands] Command failed: " + ex.Message));
				return false;
			}
		}

		private static void Handle(ChatBehaviour chat, string args)
		{
			string[] array = args.Split(new char[2] { ' ', '\t' }, 2, StringSplitOptions.RemoveEmptyEntries);
			string text = ((array.Length == 0) ? "status" : array[0].ToLowerInvariant());
			string rest = ((array.Length > 1) ? array[1].Trim() : string.Empty);
			switch (text)
			{
			case "enable":
			case "on":
				ChatBubblePlugin.ModConfig.Enabled.Value = true;
				((BaseUnityPlugin)ChatBubblePlugin.Instance).Config.Save();
				Reply(chat, "enabled.");
				break;
			case "off":
			case "disable":
				ChatBubblePlugin.ModConfig.Enabled.Value = false;
				((BaseUnityPlugin)ChatBubblePlugin.Instance).Config.Save();
				Reply(chat, "disabled.");
				break;
			case "toggle":
				ChatBubblePlugin.ModConfig.Enabled.Value = !ChatBubblePlugin.ModConfig.Enabled.Value;
				((BaseUnityPlugin)ChatBubblePlugin.Instance).Config.Save();
				Reply(chat, ChatBubblePlugin.ModConfig.Enabled.Value ? "enabled." : "disabled.");
				break;
			case "mode":
				HandleMode(chat, rest);
				break;
			case "accessories":
			case "accessory":
				HandleAccessories(chat, rest);
				break;
			case "offset":
			case "height":
			case "gap":
			case "clearance":
				HandleGap(chat, rest);
				break;
			case "bubble-size":
			case "bubblesize":
			case "scale":
			case "bubble-scale":
				HandleScale(chat, rest, "bubble-size", ChatBubblePlugin.ModConfig.BubbleScale);
				break;
			case "icon-size":
			case "icon-scale":
			case "iconsize":
				HandleScale(chat, rest, "icon-size", ChatBubblePlugin.ModConfig.IconScale);
				break;
			case "inspect":
				Reply(chat, ChatBubblePlugin.Registry.DescribeVisuals());
				break;
			case "debug":
				ChatBubblePlugin.ModConfig.DebugLogging.Value = !ChatBubblePlugin.ModConfig.DebugLogging.Value;
				((BaseUnityPlugin)ChatBubblePlugin.Instance).Config.Save();
				Reply(chat, ChatBubblePlugin.ModConfig.DebugLogging.Value ? "debug logging enabled." : "debug logging disabled.");
				break;
			case "status":
				Reply(chat, ChatBubblePlugin.Registry.Describe());
				break;
			case "help":
			case "?":
				Reply(chat, "commands: /bubblefix on|off|toggle|status|inspect|debug, /bubblefix mode auto|head|bounds|controller, /bubblefix accessories on|off, /bubblefix height 4.6, /bubblefix bubble-size 1.0, /bubblefix icon-size 1.0.");
				break;
			default:
				Reply(chat, "unknown command. Use /bubblefix help.");
				break;
			}
		}

		private static void HandleMode(ChatBehaviour chat, string rest)
		{
			if (string.IsNullOrWhiteSpace(rest))
			{
				Reply(chat, $"mode={ChatBubblePlugin.ModConfig.AnchorMode.Value}.");
				return;
			}
			BubbleAnchorMode bubbleAnchorMode;
			switch (rest.Trim().ToLowerInvariant())
			{
			case "auto":
			case "hybrid":
			case "autohybrid":
				bubbleAnchorMode = BubbleAnchorMode.AutoHybrid;
				break;
			case "head":
			case "bone":
			case "headbone":
				bubbleAnchorMode = BubbleAnchorMode.HeadBoneOnly;
				break;
			case "mesh":
			case "bounds":
			case "meshbounds":
				bubbleAnchorMode = BubbleAnchorMode.MeshBoundsOnly;
				break;
			case "hitbox":
			case "controller":
			case "vanilla":
				bubbleAnchorMode = BubbleAnchorMode.ControllerOnly;
				break;
			default:
				Reply(chat, "usage: /bubblefix mode auto|head|bounds|controller.");
				return;
			}
			ChatBubblePlugin.ModConfig.AnchorMode.Value = bubbleAnchorMode;
			((BaseUnityPlugin)ChatBubblePlugin.Instance).Config.Save();
			Reply(chat, $"mode set to {bubbleAnchorMode}.");
		}

		private static void HandleAccessories(ChatBehaviour chat, string rest)
		{
			if (string.IsNullOrWhiteSpace(rest))
			{
				Reply(chat, "accessories=" + (ChatBubblePlugin.ModConfig.IncludeAccessoriesInBounds.Value ? "on" : "off") + ".");
				return;
			}
			bool flag;
			switch (rest.Trim().ToLowerInvariant())
			{
			case "on":
			case "true":
			case "include":
				flag = true;
				break;
			case "off":
			case "false":
			case "exclude":
				flag = false;
				break;
			default:
				Reply(chat, "usage: /bubblefix accessories on|off.");
				return;
			}
			ChatBubblePlugin.ModConfig.IncludeAccessoriesInBounds.Value = flag;
			ChatBubblePlugin.Registry.Clear();
			((BaseUnityPlugin)ChatBubblePlugin.Instance).Config.Save();
			Reply(chat, flag ? "accessory bounds enabled." : "accessory bounds disabled.");
		}

		private static void HandleGap(ChatBehaviour chat, string rest)
		{
			ChatBubbleConfig modConfig = ChatBubblePlugin.ModConfig;
			if (string.IsNullOrWhiteSpace(rest))
			{
				Reply(chat, $"height head={modConfig.HeadPaddingMeters.Value:0.00}, bounds={modConfig.BoundsPaddingMeters.Value:0.00}, controller={modConfig.ControllerFallbackPaddingMeters.Value:0.00}.");
				return;
			}
			if (!float.TryParse(rest.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				Reply(chat, "usage: /bubblefix height 4.6");
				return;
			}
			float value = Clamp(result, 0.25f, 12f);
			modConfig.HeadPaddingMeters.Value = value;
			modConfig.BoundsPaddingMeters.Value = value;
			modConfig.ControllerFallbackPaddingMeters.Value = value;
			((BaseUnityPlugin)ChatBubblePlugin.Instance).Config.Save();
			Reply(chat, $"height set: head={modConfig.HeadPaddingMeters.Value:0.00}, bounds={modConfig.BoundsPaddingMeters.Value:0.00}, controller={modConfig.ControllerFallbackPaddingMeters.Value:0.00}.");
		}

		private static void HandleScale(ChatBehaviour chat, string rest, string label, ConfigEntry<float> entry)
		{
			if (string.IsNullOrWhiteSpace(rest))
			{
				Reply(chat, $"{label}={entry.Value:0.00}.");
				return;
			}
			if (!float.TryParse(rest.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				Reply(chat, "usage: /bubblefix " + label + " 1.0");
				return;
			}
			float value = Clamp(result, 0.25f, 3f);
			entry.Value = value;
			((BaseUnityPlugin)ChatBubblePlugin.Instance).Config.Save();
			Reply(chat, $"{label} set to {entry.Value:0.00}.");
		}

		private static void Reply(ChatBehaviour chat, string text)
		{
			ChatBubbleAccess.SendLocalMessage(chat, "<color=#80C8FF>[BubbleFix]</color> " + text);
		}

		private static float Clamp(float value, float min, float max)
		{
			if (value < min)
			{
				return min;
			}
			if (value > max)
			{
				return max;
			}
			return value;
		}
	}
	internal sealed class ChatBubbleConfig
	{
		internal const int CurrentSpacingMigrationVersion = 2;

		internal const float DefaultHeadPaddingMeters = 4.6f;

		internal const float DefaultBoundsPaddingMeters = 3.8f;

		internal const float DefaultControllerFallbackPaddingMeters = 5.4f;

		internal const float HeightMinMeters = 0.25f;

		internal const float HeightMaxMeters = 12f;

		internal const float DefaultBubbleScale = 1f;

		internal const float DefaultIconScale = 1f;

		internal const float ScaleMin = 0.25f;

		internal const float ScaleMax = 3f;

		internal ConfigEntry<bool> Enabled { get; private set; }

		internal ConfigEntry<BubbleAnchorMode> AnchorMode { get; private set; }

		internal ConfigEntry<int> SpacingMigrationVersion { get; private set; }

		internal ConfigEntry<float> HeadPaddingMeters { get; private set; }

		internal ConfigEntry<float> BoundsPaddingMeters { get; private set; }

		internal ConfigEntry<float> ControllerFallbackPaddingMeters { get; private set; }

		internal ConfigEntry<bool> IncludeAccessoriesInBounds { get; private set; }

		internal ConfigEntry<float> BubbleScale { get; private set; }

		internal ConfigEntry<float> IconScale { get; private set; }

		internal ConfigEntry<bool> ForceUpdateWhenOffscreen { get; private set; }

		internal ConfigEntry<float> RendererRecacheSeconds { get; private set; }

		internal ConfigEntry<float> SafetyScanSeconds { get; private set; }

		internal ConfigEntry<float> SmoothingSeconds { get; private set; }

		internal ConfigEntry<float> TeleportSnapDistance { get; private set; }

		internal ConfigEntry<bool> DebugLogging { get; private set; }

		internal static ChatBubbleConfig Bind(ConfigFile file)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Expected O, but got Unknown
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Expected O, but got Unknown
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Expected O, but got Unknown
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Expected O, but got Unknown
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Expected O, but got Unknown
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Expected O, but got Unknown
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Expected O, but got Unknown
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Expected O, but got Unknown
			ChatBubbleConfig obj = new ChatBubbleConfig
			{
				Enabled = file.Bind<bool>("General", "Enabled", true, "Master switch for local chat bubble position correction."),
				AnchorMode = file.Bind<BubbleAnchorMode>("Anchor", "AnchorMode", BubbleAnchorMode.AutoHybrid, "Anchor resolver mode: AutoHybrid, HeadBoneOnly, MeshBoundsOnly, or ControllerOnly."),
				SpacingMigrationVersion = file.Bind<int>("Anchor", "SpacingMigrationVersion", 0, "Internal marker for one-time spacing default migrations."),
				HeadPaddingMeters = file.Bind<float>("Anchor", "HeadPaddingMeters", 4.6f, new ConfigDescription("World-space clearance above the resolved head bone.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 12f), Array.Empty<object>())),
				BoundsPaddingMeters = file.Bind<float>("Anchor", "BoundsPaddingMeters", 3.8f, new ConfigDescription("World-space clearance above renderer bounds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 12f), Array.Empty<object>())),
				ControllerFallbackPaddingMeters = file.Bind<float>("Anchor", "ControllerFallbackPaddingMeters", 5.4f, new ConfigDescription("World-space clearance above the CharacterController fallback.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 12f), Array.Empty<object>())),
				IncludeAccessoriesInBounds = file.Bind<bool>("Visuals", "IncludeAccessoriesInBounds", false, "If true, hair, ears, horns, tails, wings, hats, and similar skinned accessories are included in bounds fallback."),
				BubbleScale = file.Bind<float>("Visuals", "BubbleScale", 1f, new ConfigDescription("Local scale multiplier for the whole overhead chat bubble object.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 3f), Array.Empty<object>())),
				IconScale = file.Bind<float>("Visuals", "IconScale", 1f, new ConfigDescription("Local scale multiplier for separate non-text icon renderers under the overhead chat bubble.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 3f), Array.Empty<object>())),
				ForceUpdateWhenOffscreen = file.Bind<bool>("Visuals", "ForceUpdateWhenOffscreen", true, "If true, cached SkinnedMeshRenderers keep updating bounds while culled/offscreen."),
				RendererRecacheSeconds = file.Bind<float>("Runtime", "RendererRecacheSeconds", 2f, new ConfigDescription("How often to refresh each player's cached SkinnedMeshRenderer list.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.25f, 10f), Array.Empty<object>())),
				SafetyScanSeconds = file.Bind<float>("Runtime", "SafetyScanSeconds", 0.5f, new ConfigDescription("How often to scan for missed ChatBehaviour instances.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>())),
				SmoothingSeconds = file.Bind<float>("Runtime", "SmoothingSeconds", 0.05f, new ConfigDescription("SmoothDamp time for visible bubble movement. Set 0 to snap instantly.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.5f), Array.Empty<object>())),
				TeleportSnapDistance = file.Bind<float>("Runtime", "TeleportSnapDistance", 1f, new ConfigDescription("If the corrected target moves this far in one frame, snap instead of smoothing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>())),
				DebugLogging = file.Bind<bool>("Debug", "DebugLogging", false, "Write verbose resolver diagnostics to the BepInEx log.")
			};
			if (ApplySpacingMigration(obj))
			{
				file.Save();
			}
			return obj;
		}

		private static bool ApplySpacingMigration(ChatBubbleConfig config)
		{
			if (config.SpacingMigrationVersion.Value >= 2)
			{
				return false;
			}
			_ = RaiseToDefault(config.HeadPaddingMeters, 4.6f) | RaiseToDefault(config.BoundsPaddingMeters, 3.8f);
			RaiseToDefault(config.ControllerFallbackPaddingMeters, 5.4f);
			config.SpacingMigrationVersion.Value = 2;
			return true;
		}

		private static bool RaiseToDefault(ConfigEntry<float> entry, float defaultValue)
		{
			if (entry.Value >= defaultValue)
			{
				return false;
			}
			entry.Value = defaultValue;
			return true;
		}
	}
	[BepInPlugin("dev.seth.atlysschatbubblefix", "ATLYSS Chat Bubble Fix", "1.0.0")]
	public sealed class ChatBubblePlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "dev.seth.atlysschatbubblefix";

		public const string PluginName = "ATLYSS Chat Bubble Fix";

		public const string PluginVersion = "1.0.0";

		private Harmony? _harmony;

		private ChatBubbleRuntime? _runtime;

		internal static ChatBubblePlugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal static ChatBubbleConfig ModConfig { get; private set; }

		internal static ChatBubbleRegistry Registry { get; private set; }

		private void Awake()
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig = ChatBubbleConfig.Bind(((BaseUnityPlugin)this).Config);
			Registry = new ChatBubbleRegistry(ModConfig);
			GameObject val = new GameObject("AtlyssChatBubbleFix_Runtime");
			Object.DontDestroyOnLoad((Object)(object)val);
			((Object)val).hideFlags = (HideFlags)61;
			_runtime = val.AddComponent<ChatBubbleRuntime>();
			_runtime.Initialize(Registry);
			_harmony = new Harmony("dev.seth.atlysschatbubblefix");
			_harmony.PatchAll(typeof(ChatBehaviourPatches));
			_harmony.PatchAll(typeof(PlayerVisualPatches));
			_harmony.PatchAll(typeof(ChatBubbleCommands));
			VerifyHarmonyPatch("ChatBehaviour.Awake", AccessTools.Method(typeof(ChatBehaviour), "Awake", (Type[])null, (Type[])null));
			VerifyHarmonyPatch("ChatBehaviour.Update", AccessTools.Method(typeof(ChatBehaviour), "Update", (Type[])null, (Type[])null));
			VerifyHarmonyPatch("ChatBehaviour.Send_ChatMessage", AccessTools.Method(typeof(ChatBehaviour), "Send_ChatMessage", (Type[])null, (Type[])null));
			VerifyHarmonyPatch("PlayerVisual.RenderPlayerModel", AccessTools.Method(typeof(PlayerVisual), "RenderPlayerModel", (Type[])null, (Type[])null));
			Log.LogInfo((object)"ATLYSS Chat Bubble Fix 1.0.0 loaded. Pure client-side mode. Use /bubblefix status for diagnostics.");
		}

		private void OnDestroy()
		{
			try
			{
				Harmony? harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
				Registry?.Clear();
				if ((Object)(object)_runtime != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)_runtime).gameObject);
					_runtime = null;
				}
			}
			catch
			{
			}
		}

		private static void VerifyHarmonyPatch(string label, MethodBase? target)
		{
			if (target == null)
			{
				Log.LogWarning((object)("[Patch] " + label + ": target missing."));
				return;
			}
			Patches patchInfo = Harmony.GetPatchInfo(target);
			if (HasOwnedPatch(patchInfo?.Prefixes) || HasOwnedPatch(patchInfo?.Postfixes) || HasOwnedPatch(patchInfo?.Finalizers) || HasOwnedPatch(patchInfo?.Transpilers))
			{
				Log.LogInfo((object)("[Patch] " + label + ": patched."));
			}
			else
			{
				Log.LogWarning((object)("[Patch] " + label + ": no dev.seth.atlysschatbubblefix patch found."));
			}
		}

		private static bool HasOwnedPatch(IEnumerable<Patch>? patches)
		{
			if (patches == null)
			{
				return false;
			}
			foreach (Patch patch in patches)
			{
				if (patch.owner == "dev.seth.atlysschatbubblefix")
				{
					return true;
				}
			}
			return false;
		}
	}
	internal sealed class ChatBubbleRegistry
	{
		private readonly ChatBubbleConfig _config;

		private readonly Dictionary<int, ChatBubbleState> _states = new Dictionary<int, ChatBubbleState>();

		private float _nextScanTime;

		private float _nextDebugSummaryTime;

		private bool _loggedFirstRegistration;

		private bool _loggedFirstCorrection;

		private bool _loggedFirstScan;

		private bool _loggedScanWithoutPlayers;

		internal IReadOnlyCollection<ChatBubbleState> States => _states.Values;

		internal ChatBubbleRegistry(ChatBubbleConfig config)
		{
			_config = config;
		}

		internal void Add(ChatBehaviour chat)
		{
			if ((Object)(object)chat == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)chat).GetInstanceID();
			if (_states.ContainsKey(instanceID))
			{
				return;
			}
			Player player = ChatBubbleAccess.GetPlayer(chat);
			if (!((Object)(object)player == (Object)null))
			{
				ChatBubbleState chatBubbleState = new ChatBubbleState(chat, player);
				RecacheRenderers(chatBubbleState);
				_states[instanceID] = chatBubbleState;
				if (_config.DebugLogging.Value)
				{
					ChatBubblePlugin.Log.LogInfo((object)$"[Registry] Add {chatBubbleState.DisplayName}, renderers={chatBubbleState.Renderers.Length}");
				}
				if (!_loggedFirstRegistration)
				{
					_loggedFirstRegistration = true;
					ChatBubblePlugin.Log.LogInfo((object)$"[Runtime] Tracking chat bubbles. firstRenderers={chatBubbleState.Renderers.Length}, mode={_config.AnchorMode.Value}");
				}
			}
		}

		internal void Remove(ChatBehaviour chat)
		{
			if ((Object)(object)chat == (Object)null)
			{
				return;
			}
			int instanceID = ((Object)chat).GetInstanceID();
			if (_states.TryGetValue(instanceID, out ChatBubbleState value))
			{
				RestoreVisualScale(value);
				_states.Remove(instanceID);
				if (_config.DebugLogging.Value)
				{
					ChatBubblePlugin.Log.LogInfo((object)$"[Registry] Remove {instanceID}");
				}
			}
		}

		internal void MarkVisualDirty(PlayerVisual visual)
		{
			if ((Object)(object)visual == (Object)null)
			{
				return;
			}
			foreach (ChatBubbleState value in _states.Values)
			{
				try
				{
					if ((Object)(object)value.Player != (Object)null && (Object)(object)value.Player._pVisual == (Object)(object)visual)
					{
						value.RendererCacheDirty = true;
					}
				}
				catch
				{
				}
			}
		}

		internal void Tick()
		{
			if (Time.time >= _nextScanTime)
			{
				_nextScanTime = Time.time + Mathf.Max(0.1f, _config.SafetyScanSeconds.Value);
				SafetyScan();
			}
			int num = 0;
			List<int> list = null;
			foreach (KeyValuePair<int, ChatBubbleState> state in _states)
			{
				ChatBubbleState value = state.Value;
				if ((Object)(object)value.Chat == (Object)null || (Object)(object)value.Player == (Object)null)
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(state.Key);
					continue;
				}
				if (value.RendererCacheDirty || Time.time >= value.NextRendererRecache || value.Renderers.Length == 0)
				{
					RecacheRenderers(value);
				}
				if (TryCorrect(value))
				{
					num++;
				}
			}
			if (list != null)
			{
				foreach (int item in list)
				{
					_states.Remove(item);
				}
			}
			if (_config.DebugLogging.Value && Time.time >= _nextDebugSummaryTime)
			{
				_nextDebugSummaryTime = Time.time + 3f;
				ChatBubblePlugin.Log.LogInfo((object)$"[Runtime] tracked={_states.Count}, active={num}, enabled={_config.Enabled.Value}, mode={_config.AnchorMode.Value}");
			}
		}

		internal string Describe()
		{
			int num = 0;
			int num2 = 0;
			Dictionary<BubbleAnchorPath, int> dictionary = new Dictionary<BubbleAnchorPath, int>();
			foreach (ChatBubbleState value2 in _states.Values)
			{
				if (value2.LastWasActive)
				{
					num++;
				}
				num2 += value2.CorrectionsApplied;
				dictionary.TryGetValue(value2.LastAnchorPath, out var value);
				dictionary[value2.LastAnchorPath] = value + 1;
			}
			return $"enabled={_config.Enabled.Value}, mode={_config.AnchorMode.Value}, tracked={_states.Count}, active={num}, corrections={num2}, paths={FormatPaths(dictionary)}";
		}

		internal string DescribeVisuals()
		{
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			foreach (ChatBubbleState value in _states.Values)
			{
				if (value.LastWasActive)
				{
					num++;
				}
				if ((Object)(object)value.BubbleRoot != (Object)null)
				{
					num2++;
				}
				num3 += CountLiveTransforms(value.IconScaleTargets);
			}
			string text = ((num3 > 0) ? "yes" : "no");
			return $"height head={_config.HeadPaddingMeters.Value:0.00}, bounds={_config.BoundsPaddingMeters.Value:0.00}, controller={_config.ControllerFallbackPaddingMeters.Value:0.00}; bubble-size={_config.BubbleScale.Value:0.00}, icon-size={_config.IconScale.Value:0.00}; tracked={_states.Count}, active={num}, bubbleRoots={num2}, iconTargets={num3}, separateIconTargets={text}.";
		}

		internal void Clear()
		{
			foreach (ChatBubbleState value in _states.Values)
			{
				RestoreVisualScale(value);
			}
			_states.Clear();
		}

		private void SafetyScan()
		{
			ChatBehaviour[] array;
			try
			{
				array = Object.FindObjectsOfType<ChatBehaviour>();
			}
			catch
			{
				return;
			}
			int num = 0;
			HashSet<int> hashSet = new HashSet<int>();
			ChatBehaviour[] array2 = array;
			foreach (ChatBehaviour val in array2)
			{
				if (!((Object)(object)val == (Object)null))
				{
					hashSet.Add(((Object)val).GetInstanceID());
					if ((Object)(object)ChatBubbleAccess.GetPlayer(val) == (Object)null)
					{
						num++;
					}
					Add(val);
				}
			}
			if (!_loggedFirstScan)
			{
				_loggedFirstScan = true;
				ChatBubblePlugin.Log.LogInfo((object)$"[Runtime] Safety scan active. chatBehaviours={array.Length}, tracked={_states.Count}, missingPlayer={num}");
			}
			if (!_loggedScanWithoutPlayers && array.Length != 0 && _states.Count == 0 && num == array.Length)
			{
				_loggedScanWithoutPlayers = true;
				ChatBubblePlugin.Log.LogInfo((object)$"[Runtime] ChatBehaviour objects found, but no Player owner was available yet. count={array.Length}");
			}
			List<int> list = null;
			foreach (KeyValuePair<int, ChatBubbleState> state in _states)
			{
				if (!hashSet.Contains(state.Key) || (Object)(object)state.Value.Chat == (Object)null || (Object)(object)state.Value.Player == (Object)null)
				{
					if (list == null)
					{
						list = new List<int>();
					}
					list.Add(state.Key);
				}
			}
			if (list == null)
			{
				return;
			}
			foreach (int item in list)
			{
				_states.Remove(item);
			}
		}

		private void RecacheRenderers(ChatBubbleState state)
		{
			state.Renderers = PlayerRendererCache.Collect(state.Player, _config);
			state.RendererCacheDirty = false;
			state.NextRendererRecache = Time.time + Mathf.Max(0.25f, _config.RendererRecacheSeconds.Value);
		}

		private bool TryCorrect(ChatBubbleState state)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//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_00aa: Unknown result type (might be due to invalid IL or missing references)
			//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_00b7: Unknown result type (might be due to invalid IL or missing references)
			GameObject bubbleObject = ChatBubbleAccess.GetBubbleObject(state.Chat);
			if ((Object)(object)bubbleObject == (Object)null)
			{
				state.LastWasActive = false;
				state.HasSmoothedWorldPosition = false;
				state.SmoothVelocity = Vector3.zero;
				return false;
			}
			if (!_config.Enabled.Value)
			{
				RestoreVisualScale(state);
				state.LastWasActive = false;
				state.HasSmoothedWorldPosition = false;
				state.SmoothVelocity = Vector3.zero;
				return false;
			}
			if (!bubbleObject.activeInHierarchy)
			{
				state.LastWasActive = false;
				state.HasSmoothedWorldPosition = false;
				state.SmoothVelocity = Vector3.zero;
				return false;
			}
			if (!BubbleAnchorResolver.TryResolve(state, _config, out var anchor, out var path))
			{
				state.LastWasActive = true;
				state.LastAnchorPath = BubbleAnchorPath.None;
				return false;
			}
			Vector3 worldPosition = SmoothWorldPosition(state, anchor);
			ApplyPosition(bubbleObject.transform, worldPosition);
			ApplyVisualScale(state, bubbleObject);
			state.LastWasActive = true;
			state.LastAnchorPath = path;
			state.CorrectionsApplied++;
			if (!_loggedFirstCorrection)
			{
				_loggedFirstCorrection = true;
				ChatBubblePlugin.Log.LogInfo((object)$"[Runtime] First chat bubble correction applied. path={path}, tracked={_states.Count}");
			}
			return true;
		}

		private Vector3 SmoothWorldPosition(ChatBubbleState state, Vector3 targetWorld)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//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_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: 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)
			float num = Mathf.Max(0f, _config.SmoothingSeconds.Value);
			float num2 = Mathf.Max(0f, _config.TeleportSnapDistance.Value);
			if (!state.HasSmoothedWorldPosition || num <= 0.0001f || (num2 > 0f && Vector3.Distance(state.SmoothedWorldPosition, targetWorld) >= num2))
			{
				state.HasSmoothedWorldPosition = true;
				state.SmoothedWorldPosition = targetWorld;
				state.SmoothVelocity = Vector3.zero;
				return targetWorld;
			}
			state.SmoothedWorldPosition = Vector3.SmoothDamp(state.SmoothedWorldPosition, targetWorld, ref state.SmoothVelocity, num);
			return state.SmoothedWorldPosition;
		}

		private static void ApplyPosition(Transform bubbleTransform, Vector3 worldPosition)
		{
			//IL_007d: 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: Invalid comparison between Unknown and I4
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Invalid comparison between Unknown and I4
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)bubbleTransform == (Object)null)
			{
				return;
			}
			RectTransform val = (RectTransform)(object)((bubbleTransform is RectTransform) ? bubbleTransform : null);
			Canvas val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponentInParent<Canvas>() : null);
			if ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null && (int)val2.renderMode != 2)
			{
				Camera val3 = (((int)val2.renderMode == 1) ? val2.worldCamera : Camera.main);
				if ((Object)(object)val3 == (Object)null)
				{
					val3 = Camera.main;
				}
				if ((Object)(object)val3 != (Object)null)
				{
					((Transform)val).position = val3.WorldToScreenPoint(worldPosition);
				}
			}
			else
			{
				bubbleTransform.position = worldPosition;
			}
		}

		private void ApplyVisualScale(ChatBubbleState state, GameObject bubble)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			Transform transform = bubble.transform;
			if ((Object)(object)transform == (Object)null)
			{
				return;
			}
			if ((Object)(object)state.BubbleRoot != (Object)(object)transform)
			{
				RestoreVisualScale(state);
				state.BubbleRoot = transform;
				state.OriginalBubbleScale = transform.localScale;
				state.IconScaleTargets = Array.Empty<Transform>();
				state.OriginalIconScales.Clear();
				state.VisualTargetsDirty = true;
			}
			if (state.VisualTargetsDirty || Time.time >= state.NextVisualRecache)
			{
				RefreshVisualTargets(state, transform);
				state.NextVisualRecache = Time.time + Mathf.Max(0.5f, _config.RendererRecacheSeconds.Value);
			}
			float num = Clamp(_config.BubbleScale.Value, 0.25f, 3f);
			transform.localScale = state.OriginalBubbleScale * num;
			float num2 = Clamp(_config.IconScale.Value, 0.25f, 3f);
			Transform[] iconScaleTargets = state.IconScaleTargets;
			foreach (Transform val in iconScaleTargets)
			{
				if (!((Object)(object)val == (Object)null))
				{
					int instanceID = ((Object)val).GetInstanceID();
					if (!state.OriginalIconScales.TryGetValue(instanceID, out var value))
					{
						value = val.localScale;
						state.OriginalIconScales[instanceID] = value;
					}
					val.localScale = value * num2;
				}
			}
		}

		private static void RefreshVisualTargets(ChatBubbleState state, Transform root)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			List<Transform> list = new List<Transform>();
			Transform[] componentsInChildren;
			try
			{
				componentsInChildren = ((Component)root).GetComponentsInChildren<Transform>(true);
			}
			catch
			{
				state.IconScaleTargets = Array.Empty<Transform>();
				state.VisualTargetsDirty = false;
				return;
			}
			Transform[] array = componentsInChildren;
			foreach (Transform val in array)
			{
				if (IsIconScaleTarget(root, val))
				{
					list.Add(val);
					int instanceID = ((Object)val).GetInstanceID();
					if (!state.OriginalIconScales.ContainsKey(instanceID))
					{
						state.OriginalIconScales[instanceID] = val.localScale;
					}
				}
			}
			state.IconScaleTargets = list.ToArray();
			state.VisualTargetsDirty = false;
		}

		private static bool IsIconScaleTarget(Transform root, Transform target)
		{
			if ((Object)(object)target == (Object)null || (Object)(object)target == (Object)(object)root)
			{
				return false;
			}
			if (HasTextComponent(target))
			{
				return false;
			}
			try
			{
				if ((Object)(object)((Component)target).GetComponent<Image>() != (Object)null)
				{
					return true;
				}
				if ((Object)(object)((Component)target).GetComponent<RawImage>() != (Object)null)
				{
					return true;
				}
				if ((Object)(object)((Component)target).GetComponent<SpriteRenderer>() != (Object)null)
				{
					return true;
				}
				if ((Object)(object)((Component)target).GetComponent<Renderer>() != (Object)null)
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static bool HasTextComponent(Transform target)
		{
			try
			{
				if ((Object)(object)((Component)target).GetComponent<TMP_Text>() != (Object)null)
				{
					return true;
				}
				if ((Object)(object)((Component)target).GetComponent<Text>() != (Object)null)
				{
					return true;
				}
				if ((Object)(object)((Component)target).GetComponent<InputField>() != (Object)null)
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static void RestoreVisualScale(ChatBubbleState state)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)state.BubbleRoot != (Object)null)
			{
				state.BubbleRoot.localScale = state.OriginalBubbleScale;
			}
			Transform[] iconScaleTargets = state.IconScaleTargets;
			foreach (Transform val in iconScaleTargets)
			{
				if (!((Object)(object)val == (Object)null))
				{
					int instanceID = ((Object)val).GetInstanceID();
					if (state.OriginalIconScales.TryGetValue(instanceID, out var value))
					{
						val.localScale = value;
					}
				}
			}
			state.BubbleRoot = null;
			state.IconScaleTargets = Array.Empty<Transform>();
			state.OriginalIconScales.Clear();
			state.VisualTargetsDirty = true;
		}

		private static int CountLiveTransforms(Transform[] transforms)
		{
			if (transforms == null || transforms.Length == 0)
			{
				return 0;
			}
			int num = 0;
			for (int i = 0; i < transforms.Length; i++)
			{
				if ((Object)(object)transforms[i] != (Object)null)
				{
					num++;
				}
			}
			return num;
		}

		private static float Clamp(float value, float min, float max)
		{
			if (value < min)
			{
				return min;
			}
			if (value > max)
			{
				return max;
			}
			return value;
		}

		private static string FormatPaths(Dictionary<BubbleAnchorPath, int> paths)
		{
			if (paths.Count == 0)
			{
				return "none";
			}
			string text = string.Empty;
			foreach (KeyValuePair<BubbleAnchorPath, int> path in paths)
			{
				if (path.Value > 0)
				{
					if (text.Length > 0)
					{
						text += ",";
					}
					text += $"{path.Key}:{path.Value}";
				}
			}
			if (text.Length != 0)
			{
				return text;
			}
			return "none";
		}
	}
	internal sealed class ChatBubbleRuntime : MonoBehaviour
	{
		private ChatBubbleRegistry? _registry;

		internal void Initialize(ChatBubbleRegistry registry)
		{
			_registry = registry;
		}

		private void OnEnable()
		{
			//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)
			SceneManager.activeSceneChanged += OnActiveSceneChanged;
			Scene activeScene = SceneManager.GetActiveScene();
			ChatBubblePlugin.Log.LogInfo((object)("[Runtime] Active scene: " + ((Scene)(ref activeScene)).name));
		}

		private void OnDisable()
		{
			SceneManager.activeSceneChanged -= OnActiveSceneChanged;
		}

		private void LateUpdate()
		{
			try
			{
				_registry?.Tick();
			}
			catch (Exception ex)
			{
				ChatBubblePlugin.Log.LogError((object)("[Runtime] LateUpdate failed: " + ex.Message));
			}
		}

		private static void OnActiveSceneChanged(Scene previous, Scene current)
		{
			ChatBubblePlugin.Log.LogInfo((object)("[Runtime] Active scene changed: " + ((Scene)(ref previous)).name + " -> " + ((Scene)(ref current)).name));
		}
	}
	internal sealed class ChatBubbleState
	{
		internal Vector3 SmoothVelocity;

		internal ChatBehaviour Chat { get; }

		internal Player Player { get; }

		internal int InstanceId { get; }

		internal SkinnedMeshRenderer[] Renderers { get; set; } = Array.Empty<SkinnedMeshRenderer>();

		internal float NextRendererRecache { get; set; }

		internal bool RendererCacheDirty { get; set; } = true;

		internal bool HasSmoothedWorldPosition { get; set; }

		internal Vector3 SmoothedWorldPosition { get; set; }

		internal BubbleAnchorPath LastAnchorPath { get; set; }

		internal bool LastWasActive { get; set; }

		internal int CorrectionsApplied { get; set; }

		internal Transform? BubbleRoot { get; set; }

		internal Vector3 OriginalBubbleScale { get; set; } = Vector3.one;

		internal Transform[] IconScaleTargets { get; set; } = Array.Empty<Transform>();

		internal Dictionary<int, Vector3> OriginalIconScales { get; } = new Dictionary<int, Vector3>();

		internal bool VisualTargetsDirty { get; set; } = true;

		internal float NextVisualRecache { get; set; }

		internal string DisplayName
		{
			get
			{
				try
				{
					string name = ((Object)Player).name;
					if (!string.IsNullOrWhiteSpace(name))
					{
						return name;
					}
				}
				catch
				{
				}
				return "Player";
			}
		}

		internal ChatBubbleState(ChatBehaviour chat, Player player)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			Chat = chat;
			Player = player;
			InstanceId = ((Object)chat).GetInstanceID();
		}
	}
	internal static class PlayerRendererCache
	{
		private static readonly string[] AccessoryNameFragments = new string[22]
		{
			"tail", "hair", "ear", "horn", "leaf", "wing", "antler", "fluff", "fur", "mane",
			"feather", "hat", "hood", "cap", "helmet", "crown", "headwear", "headgear", "scarf", "cape",
			"cloak", "mantle"
		};

		internal static SkinnedMeshRenderer[] Collect(Player player, ChatBubbleConfig config)
		{
			if ((Object)(object)player == (Object)null)
			{
				return Array.Empty<SkinnedMeshRenderer>();
			}
			SkinnedMeshRenderer[] componentsInChildren;
			try
			{
				componentsInChildren = ((Component)player).GetComponentsInChildren<SkinnedMeshRenderer>(true);
			}
			catch
			{
				return Array.Empty<SkinnedMeshRenderer>();
			}
			if (componentsInChildren == null || componentsInChildren.Length == 0)
			{
				return Array.Empty<SkinnedMeshRenderer>();
			}
			List<SkinnedMeshRenderer> list = new List<SkinnedMeshRenderer>(componentsInChildren.Length);
			bool value = config.IncludeAccessoriesInBounds.Value;
			bool value2 = config.ForceUpdateWhenOffscreen.Value;
			SkinnedMeshRenderer[] array = componentsInChildren;
			foreach (SkinnedMeshRenderer val in array)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)val.sharedMesh == (Object)null || (!value && IsAccessoryRenderer((Renderer)(object)val)))
				{
					continue;
				}
				if (value2)
				{
					try
					{
						val.updateWhenOffscreen = true;
					}
					catch
					{
					}
				}
				list.Add(val);
			}
			return list.ToArray();
		}

		internal static bool TryGetBounds(Player player, SkinnedMeshRenderer[] renderers, out Bounds bounds)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//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_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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_0085: 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_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: 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)
			bounds = default(Bounds);
			if ((Object)(object)player == (Object)null || renderers == null || renderers.Length == 0)
			{
				return false;
			}
			Vector3 position;
			try
			{
				position = ((Component)player).transform.position;
			}
			catch
			{
				return false;
			}
			bool flag = false;
			foreach (SkinnedMeshRenderer val in renderers)
			{
				if ((Object)(object)val == (Object)null || !((Renderer)val).enabled || !((Component)val).gameObject.activeInHierarchy)
				{
					continue;
				}
				Bounds bounds2;
				try
				{
					bounds2 = ((Renderer)val).bounds;
				}
				catch
				{
					continue;
				}
				Vector3 size = ((Bounds)(ref bounds2)).size;
				if (!IsUsable(size.x) || !IsUsable(size.y) || !IsUsable(size.z) || size.x > 50f || size.y > 50f || size.z > 50f)
				{
					continue;
				}
				Vector2 val2 = new Vector2(((Bounds)(ref bounds2)).center.x - position.x, ((Bounds)(ref bounds2)).center.z - position.z);
				if (!(((Vector2)(ref val2)).magnitude > 10f) && !(Mathf.Abs(((Bounds)(ref bounds2)).center.y - position.y) > 30f))
				{
					if (!flag)
					{
						bounds = bounds2;
						flag = true;
					}
					else
					{
						((Bounds)(ref bounds)).Encapsulate(((Bounds)(ref bounds2)).min);
						((Bounds)(ref bounds)).Encapsulate(((Bounds)(ref bounds2)).max);
					}
				}
			}
			return flag;
		}

		private static bool IsAccessoryRenderer(Renderer renderer)
		{
			string name = ((Object)renderer).name ?? string.Empty;
			string name2 = (((Object)(object)((Component)renderer).gameObject != (Object)null) ? ((Object)((Component)renderer).gameObject).name : string.Empty);
			if (!NameMatchesAccessory(name))
			{
				return NameMatchesAccessory(name2);
			}
			return true;
		}

		private static bool NameMatchesAccessory(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return false;
			}
			string text = name.ToLowerInvariant();
			string[] accessoryNameFragments = AccessoryNameFragments;
			foreach (string value in accessoryNameFragments)
			{
				if (text.Contains(value))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsUsable(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value))
			{
				return value > 0.001f;
			}
			return false;
		}
	}
	internal static class PlayerVisualPatches
	{
		[HarmonyPatch(typeof(PlayerVisual), "RenderPlayerModel")]
		[HarmonyPostfix]
		private static void AfterRenderPlayerModel(PlayerVisual __instance)
		{
			try
			{
				ChatBubblePlugin.Registry?.MarkVisualDirty(__instance);
			}
			catch (Exception ex)
			{
				ChatBubblePlugin.Log.LogWarning((object)("[Patch] PlayerVisual.RenderPlayerModel failed: " + ex.Message));
			}
		}
	}
}