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(); } 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(); } 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 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, "[BubbleFix] " + 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 Enabled { get; private set; } internal ConfigEntry AnchorMode { get; private set; } internal ConfigEntry SpacingMigrationVersion { get; private set; } internal ConfigEntry HeadPaddingMeters { get; private set; } internal ConfigEntry BoundsPaddingMeters { get; private set; } internal ConfigEntry ControllerFallbackPaddingMeters { get; private set; } internal ConfigEntry IncludeAccessoriesInBounds { get; private set; } internal ConfigEntry BubbleScale { get; private set; } internal ConfigEntry IconScale { get; private set; } internal ConfigEntry ForceUpdateWhenOffscreen { get; private set; } internal ConfigEntry RendererRecacheSeconds { get; private set; } internal ConfigEntry SafetyScanSeconds { get; private set; } internal ConfigEntry SmoothingSeconds { get; private set; } internal ConfigEntry TeleportSnapDistance { get; private set; } internal ConfigEntry 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("General", "Enabled", true, "Master switch for local chat bubble position correction."), AnchorMode = file.Bind("Anchor", "AnchorMode", BubbleAnchorMode.AutoHybrid, "Anchor resolver mode: AutoHybrid, HeadBoneOnly, MeshBoundsOnly, or ControllerOnly."), SpacingMigrationVersion = file.Bind("Anchor", "SpacingMigrationVersion", 0, "Internal marker for one-time spacing default migrations."), HeadPaddingMeters = file.Bind("Anchor", "HeadPaddingMeters", 4.6f, new ConfigDescription("World-space clearance above the resolved head bone.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 12f), Array.Empty())), BoundsPaddingMeters = file.Bind("Anchor", "BoundsPaddingMeters", 3.8f, new ConfigDescription("World-space clearance above renderer bounds.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 12f), Array.Empty())), ControllerFallbackPaddingMeters = file.Bind("Anchor", "ControllerFallbackPaddingMeters", 5.4f, new ConfigDescription("World-space clearance above the CharacterController fallback.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 12f), Array.Empty())), IncludeAccessoriesInBounds = file.Bind("Visuals", "IncludeAccessoriesInBounds", false, "If true, hair, ears, horns, tails, wings, hats, and similar skinned accessories are included in bounds fallback."), BubbleScale = file.Bind("Visuals", "BubbleScale", 1f, new ConfigDescription("Local scale multiplier for the whole overhead chat bubble object.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 3f), Array.Empty())), IconScale = file.Bind("Visuals", "IconScale", 1f, new ConfigDescription("Local scale multiplier for separate non-text icon renderers under the overhead chat bubble.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 3f), Array.Empty())), ForceUpdateWhenOffscreen = file.Bind("Visuals", "ForceUpdateWhenOffscreen", true, "If true, cached SkinnedMeshRenderers keep updating bounds while culled/offscreen."), RendererRecacheSeconds = file.Bind("Runtime", "RendererRecacheSeconds", 2f, new ConfigDescription("How often to refresh each player's cached SkinnedMeshRenderer list.", (AcceptableValueBase)(object)new AcceptableValueRange(0.25f, 10f), Array.Empty())), SafetyScanSeconds = file.Bind("Runtime", "SafetyScanSeconds", 0.5f, new ConfigDescription("How often to scan for missed ChatBehaviour instances.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f), Array.Empty())), SmoothingSeconds = file.Bind("Runtime", "SmoothingSeconds", 0.05f, new ConfigDescription("SmoothDamp time for visible bubble movement. Set 0 to snap instantly.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 0.5f), Array.Empty())), TeleportSnapDistance = file.Bind("Runtime", "TeleportSnapDistance", 1f, new ConfigDescription("If the corrected target moves this far in one frame, snap instead of smoothing.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 10f), Array.Empty())), DebugLogging = file.Bind("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 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(); _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? 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 _states = new Dictionary(); private float _nextScanTime; private float _nextDebugSummaryTime; private bool _loggedFirstRegistration; private bool _loggedFirstCorrection; private bool _loggedFirstScan; private bool _loggedScanWithoutPlayers; internal IReadOnlyCollection 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 list = null; foreach (KeyValuePair 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(); } 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 dictionary = new Dictionary(); 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(); } catch { return; } int num = 0; HashSet hashSet = new HashSet(); 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 list = null; foreach (KeyValuePair 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(); } 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() : 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(); 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 list = new List(); Transform[] componentsInChildren; try { componentsInChildren = ((Component)root).GetComponentsInChildren(true); } catch { state.IconScaleTargets = Array.Empty(); 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() != (Object)null) { return true; } if ((Object)(object)((Component)target).GetComponent() != (Object)null) { return true; } if ((Object)(object)((Component)target).GetComponent() != (Object)null) { return true; } if ((Object)(object)((Component)target).GetComponent() != (Object)null) { return true; } } catch { } return false; } private static bool HasTextComponent(Transform target) { try { if ((Object)(object)((Component)target).GetComponent() != (Object)null) { return true; } if ((Object)(object)((Component)target).GetComponent() != (Object)null) { return true; } if ((Object)(object)((Component)target).GetComponent() != (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(); 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 paths) { if (paths.Count == 0) { return "none"; } string text = string.Empty; foreach (KeyValuePair 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(); 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(); internal Dictionary OriginalIconScales { get; } = new Dictionary(); 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[] componentsInChildren; try { componentsInChildren = ((Component)player).GetComponentsInChildren(true); } catch { return Array.Empty(); } if (componentsInChildren == null || componentsInChildren.Length == 0) { return Array.Empty(); } List list = new List(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)); } } } }