using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using LookWhatIGot.Patches; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LookWhatIGot")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LookWhatIGot")] [assembly: AssemblyTitle("LookWhatIGot")] [assembly: AssemblyVersion("1.0.0.0")] 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; } } } namespace LookWhatIGot { public static class HoldingLabelManager { private const bool UseAnchorLayer = false; private const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static FieldInfo grabbedObjectField; private static FieldInfo valueField; private static Font labelFont; private static bool initialized; private static readonly Dictionary labels = new Dictionary(); private static PhysGrabObject lastGrabbed; private static float nextCleanup; private static readonly HashSet UselessNames = new HashSet(StringComparer.OrdinalIgnoreCase) { "Rigidbody", "Physics", "Mesh", "Model", "Object", "Collider", "Colliders", "Body", "Root", "Visuals", "Graphics" }; private static readonly HashSet LevelPrefixes = new HashSet(StringComparer.OrdinalIgnoreCase) { "Museum", "Manor", "Wizard", "Arena", "Arctic", "Swamp", "Mansion", "Hotel", "Space", "Lab", "Fight", "Level", "Generic", "Valuable" }; private static bool OnlyShowValuables => LookWhatIGotPlugin.CfgOnlyShowValuables?.Value ?? true; private static bool DebugPinToCamera => LookWhatIGotPlugin.CfgDebugPinToCamera?.Value ?? false; private static float LabelHeight => LookWhatIGotPlugin.CfgLabelHeight?.Value ?? 2.3f; private static int LabelLayer => LookWhatIGotPlugin.CfgLabelLayer?.Value ?? 0; public static void Initialize() { if (!initialized) { initialized = true; CacheFont(); CacheReflection(); } } private static void CacheFont() { string[] array = new string[2] { "LegacyRuntime.ttf", "Arial.ttf" }; foreach (string text in array) { try { labelFont = Resources.GetBuiltinResource(text); if ((Object)(object)labelFont != (Object)null) { LookWhatIGotPlugin.Dbg("Using builtin font: " + text); return; } } catch (Exception) { } } labelFont = Font.CreateDynamicFontFromOSFont("Arial", 32); if ((Object)(object)labelFont == (Object)null) { LookWhatIGotPlugin.Log.LogError((object)"No font found - labels will be invisible."); } else { LookWhatIGotPlugin.Dbg("Using OS font: Arial"); } } private static void CacheReflection() { FieldInfo[] fields = typeof(PhysGrabber).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (typeof(PhysGrabObject).IsAssignableFrom(fieldInfo.FieldType)) { grabbedObjectField = fieldInfo; LookWhatIGotPlugin.Dbg("Grabbed-object field: " + fieldInfo.Name); break; } } if (grabbedObjectField == null) { LookWhatIGotPlugin.Log.LogError((object)"No PhysGrabObject field on PhysGrabber!"); DumpFields(typeof(PhysGrabber)); } string[] array = new string[3] { "dollarValueCurrent", "dollarValue", "valueCurrent" }; string[] array2 = array; foreach (string name in array2) { valueField = typeof(ValuableObject).GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (valueField != null) { LookWhatIGotPlugin.Dbg("Value field: " + valueField.Name); break; } } if (valueField == null) { FieldInfo[] fields2 = typeof(ValuableObject).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo2 in fields2) { string text = fieldInfo2.Name.ToLowerInvariant(); if ((fieldInfo2.FieldType == typeof(float) || fieldInfo2.FieldType == typeof(int)) && (text.Contains("dollar") || text.Contains("value"))) { valueField = fieldInfo2; LookWhatIGotPlugin.Dbg("Value field (fallback): " + fieldInfo2.Name); break; } } } if (valueField == null) { LookWhatIGotPlugin.Log.LogWarning((object)"No value field on ValuableObject."); DumpFields(typeof(ValuableObject)); } } private static void DumpFields(Type t) { StringBuilder stringBuilder = new StringBuilder("--- fields on " + t.Name + " ---\n"); FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { stringBuilder.AppendLine(" " + fieldInfo.FieldType.Name + " " + fieldInfo.Name); } LookWhatIGotPlugin.Log.LogInfo((object)stringBuilder.ToString()); } public static void Tick(PhysGrabber grabber) { PhysGrabObject grabbedObject = GetGrabbedObject(grabber); if ((Object)(object)grabbedObject != (Object)(object)lastGrabbed) { OnLocalGrabChanged(grabbedObject); lastGrabbed = grabbedObject; } UpdateBillboards(); PeriodicCleanup(); } private static void OnLocalGrabChanged(PhysGrabObject grabbedObj) { Transform val = ResolveLocalAnchor(); ValuableObject val2 = (((Object)(object)grabbedObj != (Object)null) ? FindValuable(grabbedObj) : null); if ((Object)(object)grabbedObj == (Object)null || (OnlyShowValuables && (Object)(object)val2 == (Object)null)) { if ((Object)(object)val != (Object)null) { HideLabel(val); } LabelNetwork.BroadcastHide(); return; } string itemName = CleanItemName(grabbedObj, val2); int value = ReadValue(val2); if ((Object)(object)val != (Object)null) { ShowLabel(val, itemName, value); } LabelNetwork.BroadcastShow(itemName, value); } private static PhysGrabObject GetGrabbedObject(PhysGrabber grabber) { if (grabbedObjectField == null) { return null; } object? value = grabbedObjectField.GetValue(grabber); return (PhysGrabObject)((value is PhysGrabObject) ? value : null); } private static ValuableObject FindValuable(PhysGrabObject obj) { ValuableObject component = ((Component)obj).GetComponent(); if ((Object)(object)component != (Object)null) { return component; } return ((Component)obj).GetComponentInParent(); } private static string CleanItemName(PhysGrabObject obj, ValuableObject valuable) { string text = (((Object)(object)valuable != (Object)null) ? ((Object)((Component)valuable).gameObject).name : ((Object)((Component)obj).gameObject).name); text = text.Replace("(Clone)", "").Trim(); if (UselessNames.Contains(text)) { Transform parent = ((Component)obj).transform.parent; while ((Object)(object)parent != (Object)null) { string text2 = ((Object)parent).name.Replace("(Clone)", "").Trim(); if (!UselessNames.Contains(text2)) { text = text2; break; } parent = parent.parent; } } text = SplitPascalCase(text); string[] array = text.Split(new char[3] { ' ', '_', '-' }, StringSplitOptions.RemoveEmptyEntries); int i; for (i = 0; i < array.Length - 1 && LevelPrefixes.Contains(array[i]); i++) { } StringBuilder stringBuilder = new StringBuilder(); for (int j = i; j < array.Length; j++) { if (stringBuilder.Length > 0) { stringBuilder.Append(' '); } stringBuilder.Append(TitleCaseWord(array[j])); } string text3 = stringBuilder.ToString().Trim(); return (text3.Length > 0) ? text3 : text; } private static string SplitPascalCase(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length + 8); for (int i = 0; i < s.Length; i++) { if (i > 0 && char.IsUpper(s[i]) && !char.IsUpper(s[i - 1]) && s[i - 1] != ' ') { stringBuilder.Append(' '); } stringBuilder.Append(s[i]); } return stringBuilder.ToString(); } private static string TitleCaseWord(string w) { if (w.Length == 0) { return w; } if (w.Length <= 3 && w.ToUpperInvariant() == w) { return w; } return char.ToUpper(w[0], CultureInfo.InvariantCulture) + w.Substring(1).ToLowerInvariant(); } private static int ReadValue(ValuableObject valuable) { if ((Object)(object)valuable == (Object)null || valueField == null) { return 0; } object value = valueField.GetValue(valuable); if (value is float num) { return Mathf.RoundToInt(num); } if (value is int result) { return result; } return 0; } private static Transform ResolveLocalAnchor() { if (DebugPinToCamera) { Camera camera = GetCamera(); return ((Object)(object)camera != (Object)null) ? ((Component)camera).transform : null; } PhysGrabber instance = PhysGrabber.instance; if ((Object)(object)instance == (Object)null) { return null; } PlayerAvatar componentInParent = ((Component)instance).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { return ((Component)componentInParent).transform; } return ((Component)instance).transform.root; } internal static Camera GetCamera() { if ((Object)(object)Camera.main != (Object)null) { return Camera.main; } if ((Object)(object)Camera.current != (Object)null) { return Camera.current; } return (Camera.allCamerasCount > 0) ? Camera.allCameras[0] : null; } private static void UpdateBillboards() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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 (labels.Count == 0 || DebugPinToCamera) { return; } Camera camera = GetCamera(); if ((Object)(object)camera == (Object)null) { return; } Quaternion rotation = ((Component)camera).transform.rotation; foreach (KeyValuePair label in labels) { if ((Object)(object)label.Value != (Object)null) { label.Value.transform.rotation = rotation; } } } private static void PeriodicCleanup() { if (Time.time < nextCleanup) { return; } nextCleanup = Time.time + 5f; List list = null; foreach (KeyValuePair label in labels) { if ((Object)(object)label.Key == (Object)null || (Object)(object)label.Value == (Object)null) { list = list ?? new List(); list.Add(label.Key); } } if (list == null) { return; } foreach (Transform item in list) { labels.Remove(item); } LookWhatIGotPlugin.Dbg($"Cleaned up {list.Count} stale label(s)"); } public static void ShowLabel(Transform anchor, string itemName, int value) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)anchor == (Object)null)) { HideLabel(anchor); GameObject val = new GameObject("LookWhatIGot_Label"); val.layer = LabelLayer; val.transform.SetParent(anchor, false); val.transform.localPosition = (DebugPinToCamera ? new Vector3(0f, 0f, 2f) : new Vector3(0f, LabelHeight, 0f)); val.transform.localRotation = Quaternion.identity; Vector3 lossyScale = anchor.lossyScale; val.transform.localScale = new Vector3(Mathf.Approximately(lossyScale.x, 0f) ? 1f : (1f / lossyScale.x), Mathf.Approximately(lossyScale.y, 0f) ? 1f : (1f / lossyScale.y), Mathf.Approximately(lossyScale.z, 0f) ? 1f : (1f / lossyScale.z)); TextMesh val2 = val.AddComponent(); val2.text = ((value > 0) ? $"Look what I got\n{itemName}\n${value}" : ("Look what I got\n" + itemName)); val2.font = labelFont; val2.fontSize = 64; val2.characterSize = 0.03f; val2.anchor = (TextAnchor)4; val2.alignment = (TextAlignment)1; val2.color = new Color(1f, 0.92f, 0.2f); val2.fontStyle = (FontStyle)1; MeshRenderer component = val.GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)labelFont != (Object)null) { ((Renderer)component).material = labelFont.material; ((Renderer)component).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)component).receiveShadows = false; } labels[anchor] = val; LookWhatIGotPlugin.Dbg($"Label shown -> {itemName} (${value}) parent={((Object)anchor).name}"); } } public static void HideLabel(Transform anchor) { if (!((Object)(object)anchor == (Object)null) && labels.TryGetValue(anchor, out GameObject value)) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } labels.Remove(anchor); } } } public static class LabelNetwork { private const byte EventCode = 173; private static bool subscribed; private static string lastSentName; private static int lastSentValue = -1; private static bool lastSentVisible; public static void Initialize() { if (subscribed) { return; } try { PhotonNetwork.NetworkingClient.EventReceived += OnEvent; subscribed = true; LookWhatIGotPlugin.Dbg($"Photon event handler registered (code {(byte)173})"); } catch (Exception ex) { LookWhatIGotPlugin.Log.LogError((object)("Could not register Photon handler: " + ex.Message)); } } public static void BroadcastShow(string itemName, int value) { if (!lastSentVisible || !(lastSentName == itemName) || lastSentValue != value) { lastSentVisible = true; lastSentName = itemName; lastSentValue = value; Send(visible: true, itemName, value); } } public static void BroadcastHide() { if (lastSentVisible) { lastSentVisible = false; lastSentName = null; lastSentValue = -1; Send(visible: false, string.Empty, 0); } } private static void Send(bool visible, string itemName, int value) { //IL_004a: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.IsConnected || !PhotonNetwork.InRoom) { return; } try { object[] array = new object[4] { PhotonNetwork.LocalPlayer.ActorNumber, visible, itemName, value }; RaiseEventOptions val = new RaiseEventOptions { Receivers = (ReceiverGroup)0 }; PhotonNetwork.RaiseEvent((byte)173, (object)array, val, SendOptions.SendReliable); } catch (Exception ex) { LookWhatIGotPlugin.Log.LogError((object)("RaiseEvent failed: " + ex.Message)); } } private static void OnEvent(EventData photonEvent) { if (photonEvent.Code != 173) { return; } try { if (!(photonEvent.CustomData is object[] array) || array.Length < 4) { LookWhatIGotPlugin.Log.LogWarning((object)"Malformed label event ignored."); return; } int num = (int)array[0]; bool flag = (bool)array[1]; string itemName = (string)array[2]; int value = (int)array[3]; if (num != PhotonNetwork.LocalPlayer.ActorNumber) { PlayerAvatar val = FindAvatarByActor(num); if ((Object)(object)val == (Object)null) { LookWhatIGotPlugin.Log.LogWarning((object)$"No PlayerAvatar found for actor {num}"); } else if (flag) { HoldingLabelManager.ShowLabel(((Component)val).transform, itemName, value); } else { HoldingLabelManager.HideLabel(((Component)val).transform); } } } catch (Exception arg) { LookWhatIGotPlugin.Log.LogError((object)$"Event handling failed: {arg}"); } } private static PlayerAvatar FindAvatarByActor(int actorNumber) { PlayerAvatar[] array = Object.FindObjectsOfType(); PlayerAvatar[] array2 = array; foreach (PlayerAvatar val in array2) { PhotonView val2 = ((Component)val).GetComponent() ?? ((Component)val).GetComponentInParent() ?? ((Component)val).GetComponentInChildren(); if ((Object)(object)val2 != (Object)null && val2.Owner != null && val2.Owner.ActorNumber == actorNumber) { return val; } } return null; } } [BepInPlugin("com.odinplus.lookwhatigot", "Look What I Got", "1.0.0")] public class LookWhatIGotPlugin : BaseUnityPlugin { private const string MyGuid = "com.odinplus.lookwhatigot"; private const string PluginName = "Look What I Got"; private const string VersionString = "1.0.0"; private const string BuildTag = "complete. Structural integrity questionable. Confidence level: 0%."; internal static ManualLogSource Log; private readonly Harmony harmony = new Harmony("com.odinplus.lookwhatigot"); internal static ConfigEntry CfgOnlyShowValuables; internal static ConfigEntry CfgLabelHeight; internal static ConfigEntry CfgLabelLayer; internal static ConfigEntry CfgVerbose; internal static ConfigEntry CfgDebugPinToCamera; internal static bool Verbose => CfgVerbose?.Value ?? false; private void BindConfig() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown CfgOnlyShowValuables = ((BaseUnityPlugin)this).Config.Bind("General", "Only Show Valuables", true, "Ignore doors, hinges, carts and other non-treasure. Turn off if you want every single grab announced."); CfgLabelHeight = ((BaseUnityPlugin)this).Config.Bind("Display", "Label Height", 2.3f, new ConfigDescription("How far above the player the text floats.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 6f), Array.Empty())); CfgLabelLayer = ((BaseUnityPlugin)this).Config.Bind("Display", "Label Layer", 0, new ConfigDescription("Render layer for the text. Only worth touching if labels vanish behind geometry.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 31), Array.Empty())); CfgVerbose = ((BaseUnityPlugin)this).Config.Bind("Debug", "Verbose Logging", false, "Full diagnostic output in the BepInEx console. Leave off unless something's broken."); CfgDebugPinToCamera = ((BaseUnityPlugin)this).Config.Bind("Debug", "Debug Pin To Camera", false, "Pins the label in front of your own eyes instead of on your avatar. Testing aid only."); } internal static void Dbg(string message) { if (Verbose && Log != null) { Log.LogInfo((object)message); } } private void Awake() { Log = ((BaseUnityPlugin)this).Logger; BindConfig(); Log.LogInfo((object)"Look What I Got v1.0.0 loaded! (build complete. Structural integrity questionable. Confidence level: 0%.)"); HoldingLabelManager.Initialize(); LabelNetwork.Initialize(); GrabPatches.ApplyPatches(harmony); } } } namespace LookWhatIGot.Patches { public static class GrabPatches { private const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; public static void ApplyPatches(Harmony harmony) { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown MethodInfo methodInfo = null; string[] array = new string[3] { "Update", "LateUpdate", "FixedUpdate" }; foreach (string text in array) { methodInfo = AccessTools.Method(typeof(PhysGrabber), text, (Type[])null, (Type[])null); if (methodInfo != null) { LookWhatIGotPlugin.Dbg("Patching PhysGrabber." + text); break; } } if (methodInfo == null) { LookWhatIGotPlugin.Log.LogError((object)"PhysGrabber has no Update/LateUpdate/FixedUpdate to patch!"); DumpMethods(typeof(PhysGrabber)); return; } try { MethodInfo methodInfo2 = AccessTools.Method(typeof(GrabPatches), "TickPostfix", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); LookWhatIGotPlugin.Dbg("Patch applied successfully."); } catch (Exception arg) { LookWhatIGotPlugin.Log.LogError((object)$"Patch failed: {arg}"); } } private static void TickPostfix(PhysGrabber __instance) { try { if (!((Object)(object)__instance != (Object)(object)PhysGrabber.instance)) { HoldingLabelManager.Tick(__instance); } } catch (Exception arg) { LookWhatIGotPlugin.Log.LogError((object)$"Tick threw: {arg}"); } } private static void DumpMethods(Type t) { StringBuilder stringBuilder = new StringBuilder("--- methods on " + t.Name + " ---\n"); MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { stringBuilder.AppendLine($" {methodInfo.ReturnType.Name} {methodInfo.Name}({methodInfo.GetParameters().Length} args)"); } LookWhatIGotPlugin.Log.LogInfo((object)stringBuilder.ToString()); } } }