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));
}
}
}
}