Decompiled source of PingItems v1.3.2

PingItems.dll

Decompiled 11 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PingItems.Configuration;
using PingItems.Core;
using PingItems.Services;
using TMPro;
using UnityEngine;
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("PingItems")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.3.2.0")]
[assembly: AssemblyInformationalVersion("1.3.2")]
[assembly: AssemblyProduct("Ping Items")]
[assembly: AssemblyTitle("PingItems")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.3.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 PingItems
{
	[BepInPlugin("com.memiczny.peak.pingitems", "Ping Items", "1.3.2")]
	[BepInProcess("PEAK.exe")]
	public class Plugin : BaseUnityPlugin
	{
		public static class PluginInfo
		{
			public const string PLUGIN_GUID = "com.memiczny.peak.pingitems";

			public const string PLUGIN_NAME = "Ping Items";

			public const string PLUGIN_VERSION = "1.3.2";
		}

		public static ManualLogSource Logger { get; private set; }

		public static Plugin Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
			Logger = ((BaseUnityPlugin)this).Logger;
			try
			{
				PingItemsConfig.Initialize(((BaseUnityPlugin)this).Config);
				Logger.LogInfo((object)"Configuration initialized");
				ApplyHarmonyPatches();
				InitializeUISystem();
				PingService.Initialize();
				Logger.LogInfo((object)"Ping service initialized");
				Logger.LogInfo((object)"Ping Items v1.3.2 loaded successfully!");
			}
			catch (Exception arg)
			{
				Logger.LogError((object)$"Failed to initialize plugin: {arg}");
			}
		}

		private static void ApplyHarmonyPatches()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				new Harmony("com.memiczny.peak.pingitems").PatchAll();
				Logger.LogInfo((object)"Harmony patches applied successfully");
			}
			catch (Exception arg)
			{
				Logger.LogError((object)$"Failed to apply Harmony patches: {arg}");
				throw;
			}
		}

		private static void InitializeUISystem()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Expected O, but got Unknown
			try
			{
				if ((Object)(object)UIManager.Instance == (Object)null)
				{
					GameObject val = new GameObject("PingItems_UIManager");
					val.AddComponent<UIManager>();
					Object.DontDestroyOnLoad((Object)val);
				}
				Logger.LogInfo((object)"UI system initialized");
			}
			catch (Exception arg)
			{
				Logger.LogError((object)$"Failed to initialize UI system: {arg}");
				throw;
			}
		}
	}
}
namespace PingItems.Services
{
	public static class PingService
	{
		private static readonly List<PingHighlighter> _activeHighlights = new List<PingHighlighter>();

		private static readonly Dictionary<GameObject, PingHighlighter> _itemToGroupMapping = new Dictionary<GameObject, PingHighlighter>();

		public static void Initialize()
		{
			_activeHighlights.Clear();
			Application.quitting += Cleanup;
		}

		public static void Cleanup()
		{
			foreach (PingHighlighter item in from h in _activeHighlights.AsEnumerable()
				where (Object)(object)h != (Object)null
				select h)
			{
				item.Cleanup();
			}
			_activeHighlights.Clear();
			_itemToGroupMapping.Clear();
			Application.quitting -= Cleanup;
		}

		public static void ProcessPing(Vector3 pingPosition, Color playerColor, string playerName, Character pingingCharacter = null)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				List<IPingable> list = PingableFactory.FindPingablesForPing(pingPosition, pingingCharacter, PingItemsConfig.DetectionRadius.Value, PingItemsConfig.LuggageDetectionRadius.Value);
				if (list.Count != 0)
				{
					PingHighlighter pingHighlighter = FindExistingGroupForPingables(list);
					if ((Object)(object)pingHighlighter != (Object)null)
					{
						RefreshExistingGroup(pingHighlighter, list, pingPosition);
					}
					else if (PingItemsConfig.EnableItemGrouping.Value)
					{
						ProcessGroupedPings(list, pingPosition, playerColor, playerName);
					}
					else
					{
						ProcessIndividualPings(list, pingPosition, playerColor, playerName);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Error processing ping: " + ex.Message));
			}
		}

		public static void RemoveHighlight(PingHighlighter highlighter)
		{
			if ((Object)(object)highlighter == (Object)null)
			{
				return;
			}
			try
			{
				if (_activeHighlights.Remove(highlighter))
				{
					CleanupGroupMapping(highlighter);
					UIManager instance = UIManager.Instance;
					if ((Object)(object)instance != (Object)null)
					{
						highlighter.Cleanup();
						instance.ReleaseHighlighter(highlighter);
					}
					else
					{
						highlighter.Cleanup();
						Object.Destroy((Object)(object)((Component)highlighter).gameObject);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to remove highlighter: " + ex.Message));
			}
		}

		private static void ProcessGroupedPings(List<IPingable> pingables, Vector3 pingPosition, Color playerColor, string playerName)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			foreach (List<IPingable> item in GroupPingablesByType(ExpandItemClusters(pingables)))
			{
				CreateOrRefreshGroupedHighlight(item, pingPosition, playerColor, playerName);
			}
		}

		private static void ProcessIndividualPings(List<IPingable> pingables, Vector3 pingPosition, Color playerColor, string playerName)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			foreach (IPingable pingable in pingables)
			{
				CreateOrRefreshHighlight(pingable, pingPosition, playerColor, playerName);
			}
		}

		private static PingHighlighter FindExistingGroupForPingables(List<IPingable> pingables)
		{
			foreach (IPingable pingable in pingables)
			{
				GameObject gameObject = pingable.GetGameObject();
				if ((Object)(object)gameObject != (Object)null && _itemToGroupMapping.TryGetValue(gameObject, out var value))
				{
					return value;
				}
			}
			return null;
		}

		private static void RefreshExistingGroup(PingHighlighter existingGroup, List<IPingable> newPingables, Vector3 pingPosition)
		{
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			if (existingGroup == null || !existingGroup.IsGroupedPing)
			{
				return;
			}
			try
			{
				List<IPingable> currentGroupItems = GetGroupItems(existingGroup);
				List<IPingable> source = ExpandItemClusters(currentGroupItems.Concat(newPingables.Where((IPingable newItem) => !ContainsItem(currentGroupItems, newItem))).ToList());
				string itemType = source.FirstOrDefault()?.GetDisplayName();
				if (!string.IsNullOrEmpty(itemType))
				{
					List<IPingable> list = (from item in source
						where item.GetDisplayName() == itemType
						group item by item.GetGameObject() into @group
						select @group.First()).ToList();
					if (list.Count != currentGroupItems.Count)
					{
						UpdateGroupMapping(existingGroup, list);
						existingGroup.Init(list, pingPosition);
					}
					else
					{
						existingGroup.Refresh(pingPosition);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to refresh group ping: " + ex.Message));
			}
		}

		private static List<IPingable> ExpandItemClusters(List<IPingable> initialItems)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if (initialItems.Count == 0)
			{
				return initialItems;
			}
			try
			{
				List<IPingable> allItems = PingableFactory.FindPingablesNearPosition(Vector3.zero, float.MaxValue, float.MaxValue);
				List<IPingable> list = new List<IPingable>();
				HashSet<string> hashSet = new HashSet<string>();
				foreach (IPingable initialItem in initialItems)
				{
					string displayName = initialItem.GetDisplayName();
					if (hashSet.Add(displayName))
					{
						List<IPingable> collection = FindCompleteClusterForType(initialItem, allItems);
						list.AddRange(collection);
					}
				}
				return (from item in list
					group item by item.GetGameObject() into @group
					select @group.First()).ToList();
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Cluster expansion failed, using original items: " + ex.Message));
				return initialItems;
			}
		}

		private static List<IPingable> FindCompleteClusterForType(IPingable seedItem, List<IPingable> allItems)
		{
			HashSet<IPingable> processed = new HashSet<IPingable>();
			return BuildItemCluster(seedItem, allItems, processed);
		}

		private static List<IPingable> GetGroupItems(PingHighlighter groupHighlight)
		{
			List<IPingable> list = new List<IPingable>();
			foreach (KeyValuePair<GameObject, PingHighlighter> item in _itemToGroupMapping)
			{
				if ((Object)(object)item.Value == (Object)(object)groupHighlight)
				{
					IPingable pingable = CreatePingableFromGameObject(item.Key);
					if (pingable != null)
					{
						list.Add(pingable);
					}
				}
			}
			return list;
		}

		private static void UpdateGroupMapping(PingHighlighter groupHighlight, List<IPingable> items)
		{
			foreach (KeyValuePair<GameObject, PingHighlighter> item in _itemToGroupMapping.Where((KeyValuePair<GameObject, PingHighlighter> kvp) => (Object)(object)kvp.Value == (Object)(object)groupHighlight).ToList())
			{
				_itemToGroupMapping.Remove(item.Key);
			}
			foreach (IPingable item2 in items)
			{
				GameObject gameObject = item2.GetGameObject();
				if ((Object)(object)gameObject != (Object)null)
				{
					_itemToGroupMapping[gameObject] = groupHighlight;
				}
			}
		}

		private static IPingable CreatePingableFromGameObject(GameObject gameObject)
		{
			if ((Object)(object)gameObject == (Object)null)
			{
				return null;
			}
			Item component = gameObject.GetComponent<Item>();
			if ((Object)(object)component != (Object)null)
			{
				return new ItemPingableAdapter(component);
			}
			Luggage component2 = gameObject.GetComponent<Luggage>();
			if ((Object)(object)component2 != (Object)null)
			{
				return new LuggagePingableAdapter(component2);
			}
			MirageLuggage component3 = gameObject.GetComponent<MirageLuggage>();
			if ((Object)(object)component3 != (Object)null)
			{
				return new MirageLuggagePingableAdapter(component3);
			}
			return null;
		}

		private static bool ContainsItem(List<IPingable> items, IPingable targetItem)
		{
			GameObject targetGameObject = targetItem?.GetGameObject();
			if ((Object)(object)targetGameObject == (Object)null)
			{
				return false;
			}
			return items.Any((IPingable item) => (Object)(object)item?.GetGameObject() == (Object)(object)targetGameObject);
		}

		private static void CleanupGroupMapping(PingHighlighter highlighter)
		{
			foreach (KeyValuePair<GameObject, PingHighlighter> item in _itemToGroupMapping.Where((KeyValuePair<GameObject, PingHighlighter> kvp) => (Object)(object)kvp.Value == (Object)(object)highlighter).ToList())
			{
				_itemToGroupMapping.Remove(item.Key);
			}
		}

		private static void CreateOrRefreshHighlight(IPingable pingable, Vector3 pingPoint, Color playerColor, string playerName)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				GameObject gameObject = pingable.GetGameObject();
				if ((Object)(object)gameObject == (Object)null)
				{
					return;
				}
				PingHighlighter pingHighlighter = _activeHighlights.FirstOrDefault((PingHighlighter h) => (Object)(object)h?.TargetGameObject == (Object)(object)gameObject);
				if ((Object)(object)pingHighlighter != (Object)null)
				{
					pingHighlighter.Refresh(pingPoint);
					return;
				}
				PingHighlighter pingHighlighter2 = CreateNewHighlight(pingable, pingPoint, playerColor, playerName);
				if ((Object)(object)pingHighlighter2 != (Object)null)
				{
					_activeHighlights.Add(pingHighlighter2);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to create highlight for " + pingable?.GetDisplayName() + ": " + ex.Message));
			}
		}

		private static PingHighlighter CreateNewHighlight(IPingable pingable, Vector3 pingPoint, Color playerColor, string playerName)
		{
			//IL_0028: 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)
			try
			{
				UIManager instance = UIManager.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return null;
				}
				PingHighlighter pingHighlighter = instance.RentHighlighter();
				if ((Object)(object)pingHighlighter == (Object)null)
				{
					return null;
				}
				pingHighlighter.PlayerColor = playerColor;
				pingHighlighter.PlayerName = playerName;
				pingHighlighter.Init(pingable, pingPoint);
				return pingHighlighter;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to create highlight: " + ex.Message));
				return null;
			}
		}

		private static List<List<IPingable>> GroupPingablesByType(List<IPingable> pingables)
		{
			List<List<IPingable>> list = new List<List<IPingable>>();
			HashSet<IPingable> hashSet = new HashSet<IPingable>();
			foreach (IPingable pingable in pingables)
			{
				if (!hashSet.Contains(pingable))
				{
					List<IPingable> list2 = BuildItemCluster(pingable, pingables, hashSet);
					if (list2.Count > 0)
					{
						list.Add(list2);
					}
				}
			}
			return list;
		}

		private static List<IPingable> BuildItemCluster(IPingable startItem, List<IPingable> allItems, HashSet<IPingable> processed)
		{
			List<IPingable> list = new List<IPingable>();
			Queue<IPingable> queue = new Queue<IPingable>();
			string displayName = startItem.GetDisplayName();
			float value = PingItemsConfig.GroupingRadius.Value;
			queue.Enqueue(startItem);
			while (queue.Count > 0)
			{
				IPingable pingable = queue.Dequeue();
				if (processed.Contains(pingable))
				{
					continue;
				}
				processed.Add(pingable);
				list.Add(pingable);
				foreach (IPingable item in from n in FindItemNeighbors(pingable, allItems, displayName, value, processed)
					where !processed.Contains(n)
					select n)
				{
					queue.Enqueue(item);
				}
			}
			return list;
		}

		private static List<IPingable> FindItemNeighbors(IPingable centerItem, List<IPingable> allItems, string itemType, float radius, HashSet<IPingable> processed)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			List<IPingable> list = new List<IPingable>();
			Vector3 center = centerItem.GetCenter();
			foreach (IPingable allItem in allItems)
			{
				if (!processed.Contains(allItem) && allItem != centerItem && !(allItem.GetDisplayName() != itemType) && Vector3.Distance(center, allItem.GetCenter()) <= radius)
				{
					list.Add(allItem);
				}
			}
			return list;
		}

		private static void CreateOrRefreshGroupedHighlight(List<IPingable> group, Vector3 pingPoint, Color playerColor, string playerName)
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: 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)
			if (group != null && group.Count == 0)
			{
				return;
			}
			try
			{
				GameObject primaryGameObject = group[0].GetGameObject();
				if ((Object)(object)primaryGameObject == (Object)null)
				{
					return;
				}
				PingHighlighter pingHighlighter = _activeHighlights.FirstOrDefault((PingHighlighter h) => (Object)(object)h?.TargetGameObject == (Object)(object)primaryGameObject);
				if ((Object)(object)pingHighlighter != (Object)null)
				{
					pingHighlighter.Init(group, pingPoint);
					UpdateGroupMapping(pingHighlighter, group);
					return;
				}
				PingHighlighter pingHighlighter2 = CreateNewGroupedHighlight(group, pingPoint, playerColor, playerName);
				if ((Object)(object)pingHighlighter2 != (Object)null)
				{
					_activeHighlights.Add(pingHighlighter2);
					UpdateGroupMapping(pingHighlighter2, group);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to create grouped highlight: " + ex.Message));
			}
		}

		private static PingHighlighter CreateNewGroupedHighlight(List<IPingable> group, Vector3 pingPoint, Color playerColor, string playerName)
		{
			//IL_0028: 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)
			try
			{
				UIManager instance = UIManager.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return null;
				}
				PingHighlighter pingHighlighter = instance.RentHighlighter();
				if ((Object)(object)pingHighlighter == (Object)null)
				{
					return null;
				}
				pingHighlighter.PlayerColor = playerColor;
				pingHighlighter.PlayerName = playerName;
				pingHighlighter.Init(group, pingPoint);
				return pingHighlighter;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to create grouped highlight: " + ex.Message));
				return null;
			}
		}
	}
}
namespace PingItems.Patches
{
	[HarmonyPatch(typeof(GUIManager), "InitReticleList")]
	public static class GUIManagerPatch
	{
		[HarmonyPostfix]
		public static void Postfix(GUIManager __instance)
		{
			try
			{
				CaptureReticle(__instance);
				CaptureFont(__instance);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Error in GUIManager patch: {arg}");
			}
		}

		private static void CaptureReticle(GUIManager guiManager)
		{
			try
			{
				FieldInfo field = typeof(GUIManager).GetField("reticleDefault", BindingFlags.Instance | BindingFlags.Public);
				if (field != null)
				{
					object? value = field.GetValue(guiManager);
					GameObject val = (GameObject)((value is GameObject) ? value : null);
					if ((Object)(object)val != (Object)null)
					{
						Plugin.Logger.LogInfo((object)("Successfully captured reticle: " + ((Object)val).name));
						UIManager.Instance?.SetReticlePrefab(val);
						return;
					}
				}
				if ((Object)(object)guiManager.reticleDefault != (Object)null)
				{
					Plugin.Logger.LogInfo((object)("Successfully captured reticle via direct access: " + ((Object)guiManager.reticleDefault).name));
					UIManager.Instance?.SetReticlePrefab(guiManager.reticleDefault);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to capture reticle: " + ex.Message));
			}
		}

		private static void CaptureFont(GUIManager guiManager)
		{
			try
			{
				TMP_FontAsset val = (from textComponent in ((Component)guiManager).GetComponentsInChildren<TMP_Text>(true)
					select textComponent.font).FirstOrDefault((Func<TMP_FontAsset, bool>)((TMP_FontAsset f) => (Object)(object)f != (Object)null));
				if ((Object)(object)val != (Object)null)
				{
					UIManager.Instance?.SetGameFont(val);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to capture font: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(PointPinger), "ReceivePoint_Rpc")]
	public static class PointPingerPatch
	{
		[HarmonyPostfix]
		public static void Postfix(PointPinger __instance, Vector3 point, Vector3 hitNormal)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Color pingingPlayerColor = GetPingingPlayerColor(__instance);
				string pingingPlayerName = GetPingingPlayerName(__instance);
				Character pingingCharacter = __instance?.character;
				PingService.ProcessPing(point, pingingPlayerColor, pingingPlayerName, pingingCharacter);
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Error in PointPinger patch: {arg}");
			}
		}

		private static Color GetPingingPlayerColor(PointPinger pointPinger)
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)pointPinger?.character?.refs?.customization != (Object)null)
				{
					return pointPinger.character.refs.customization.PlayerColor;
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to get pinging player color: " + ex.Message));
			}
			return Color.cyan;
		}

		private static string GetPingingPlayerName(PointPinger pointPinger)
		{
			try
			{
				if (pointPinger != null)
				{
					Character character = pointPinger.character;
					if (((character != null) ? character.characterName : null) != null)
					{
						return pointPinger.character.characterName;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to get pinging player name: " + ex.Message));
			}
			return "Unknown";
		}
	}
}
namespace PingItems.Core
{
	public interface IPingable
	{
		Vector3 GetCenter();

		string GetDisplayName();

		bool CanBePinged();

		GameObject GetGameObject();
	}
	public class ItemPingableAdapter : IPingable
	{
		private readonly Item _item;

		public Item Item => _item;

		public ItemPingableAdapter(Item item)
		{
			_item = item ?? throw new ArgumentNullException("item");
		}

		public Vector3 GetCenter()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return _item.Center();
		}

		public string GetDisplayName()
		{
			try
			{
				return _item.GetItemName((ItemInstanceData)null);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to get item name for " + ((Object)_item).name + ": " + ex.Message));
				return ((Object)_item).name;
			}
		}

		public GameObject GetGameObject()
		{
			if ((Object)(object)_item == (Object)null)
			{
				return null;
			}
			try
			{
				if ((Object)(object)_item == (Object)null)
				{
					return null;
				}
				return ((Component)_item).gameObject;
			}
			catch
			{
				return null;
			}
		}

		public bool CanBePinged()
		{
			if ((Object)(object)_item == (Object)null)
			{
				return false;
			}
			try
			{
				if ((Object)(object)((Component)_item).gameObject == (Object)null || !((Component)_item).gameObject.activeInHierarchy)
				{
					return false;
				}
				if ((Object)(object)((Component)_item).transform == (Object)null)
				{
					return false;
				}
				Renderer component = ((Component)_item).GetComponent<Renderer>();
				if ((Object)(object)component != (Object)null && !component.enabled)
				{
					return false;
				}
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Error checking if item can be pinged: " + ex.Message));
				return false;
			}
		}

		public override bool Equals(object obj)
		{
			if (obj == null)
			{
				return false;
			}
			if (this == obj)
			{
				return true;
			}
			if (obj.GetType() != GetType())
			{
				return false;
			}
			ItemPingableAdapter itemPingableAdapter = (ItemPingableAdapter)obj;
			return _item == itemPingableAdapter._item;
		}

		public override int GetHashCode()
		{
			return ((object)_item)?.GetHashCode() ?? 0;
		}
	}
	public class LuggagePingableAdapter : IPingable
	{
		private readonly Luggage _luggage;

		public Luggage Luggage => _luggage;

		public LuggagePingableAdapter(Luggage luggage)
		{
			_luggage = luggage ?? throw new ArgumentNullException("luggage");
		}

		public Vector3 GetCenter()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return _luggage.Center();
		}

		public string GetDisplayName()
		{
			try
			{
				return _luggage.GetName();
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to get luggage name for " + ((Object)_luggage).name + ": " + ex.Message));
				return ((Object)_luggage).name;
			}
		}

		public bool CanBePinged()
		{
			if ((Object)(object)_luggage != (Object)null && ((Component)_luggage).gameObject.activeInHierarchy)
			{
				return _luggage.IsInteractible((Character)null);
			}
			return false;
		}

		public GameObject GetGameObject()
		{
			return ((Component)_luggage).gameObject;
		}

		public override bool Equals(object obj)
		{
			if (obj == null)
			{
				return false;
			}
			if (this == obj)
			{
				return true;
			}
			if (obj.GetType() != GetType())
			{
				return false;
			}
			LuggagePingableAdapter luggagePingableAdapter = (LuggagePingableAdapter)obj;
			return _luggage == luggagePingableAdapter._luggage;
		}

		public override int GetHashCode()
		{
			return ((object)_luggage)?.GetHashCode() ?? 0;
		}
	}
	public class MirageLuggagePingableAdapter : IPingable
	{
		private readonly MirageLuggage _mirageLuggage;

		public MirageLuggage MirageLuggage => _mirageLuggage;

		public MirageLuggagePingableAdapter(MirageLuggage mirageLuggage)
		{
			_mirageLuggage = mirageLuggage ?? throw new ArgumentNullException("mirageLuggage");
		}

		public Vector3 GetCenter()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			return ((Component)_mirageLuggage).transform.position;
		}

		public string GetDisplayName()
		{
			try
			{
				return LocalizedText.GetText("LUGGAGE", true) ?? "Luggage";
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to get mirage luggage name for " + ((Object)_mirageLuggage).name + ": " + ex.Message));
				return "Luggage";
			}
		}

		public bool CanBePinged()
		{
			if ((Object)(object)_mirageLuggage != (Object)null && ((Component)_mirageLuggage).gameObject.activeInHierarchy)
			{
				return ((Behaviour)_mirageLuggage).enabled;
			}
			return false;
		}

		public GameObject GetGameObject()
		{
			return ((Component)_mirageLuggage).gameObject;
		}

		public override bool Equals(object obj)
		{
			if (obj == null)
			{
				return false;
			}
			if (this == obj)
			{
				return true;
			}
			if (obj.GetType() != GetType())
			{
				return false;
			}
			MirageLuggagePingableAdapter mirageLuggagePingableAdapter = (MirageLuggagePingableAdapter)obj;
			return _mirageLuggage == mirageLuggagePingableAdapter._mirageLuggage;
		}

		public override int GetHashCode()
		{
			return ((object)_mirageLuggage)?.GetHashCode() ?? 0;
		}
	}
	public static class PingableFactory
	{
		public static List<IPingable> FindPingablesForPing(Vector3 pingPosition, Character pingingCharacter, float itemRadius, float luggageRadius)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			List<IPingable> allPingables = new List<IPingable>();
			List<IPingable> collection = FindPingablesUnderCursor();
			allPingables.AddRange(collection);
			List<IPingable> source = FindPingablesNearPosition(pingPosition, itemRadius, luggageRadius);
			allPingables.AddRange(source.Where((IPingable proximityPingable) => !ContainsSameObject(allPingables, proximityPingable)));
			return allPingables;
		}

		public static List<IPingable> FindPingablesNearPosition(Vector3 position, float itemRadius, float luggageRadius)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			List<IPingable> pingables = new List<IPingable>();
			(from item in Object.FindObjectsByType<Item>((FindObjectsSortMode)0).AsEnumerable()
				where IsObjectNearPosition(((Component)item).gameObject, position, itemRadius)
				select item).ToList().ForEach(delegate(Item item)
			{
				ItemPingableAdapter itemPingableAdapter = new ItemPingableAdapter(item);
				if (itemPingableAdapter.CanBePinged())
				{
					pingables.Add(itemPingableAdapter);
				}
			});
			Luggage.ALL_LUGGAGE.Where((Luggage luggage) => (Object)(object)luggage != (Object)null && IsObjectNearPosition(((Component)luggage).gameObject, position, luggageRadius)).ToList().ForEach(delegate(Luggage luggage)
			{
				LuggagePingableAdapter luggagePingableAdapter = new LuggagePingableAdapter(luggage);
				if (luggagePingableAdapter.CanBePinged())
				{
					pingables.Add(luggagePingableAdapter);
				}
			});
			(from mirageLuggage in Object.FindObjectsByType<MirageLuggage>((FindObjectsSortMode)0)
				where IsObjectNearPosition(((Component)mirageLuggage).gameObject, position, luggageRadius)
				select mirageLuggage).ToList().ForEach(delegate(MirageLuggage mirageLuggage)
			{
				MirageLuggagePingableAdapter mirageLuggagePingableAdapter = new MirageLuggagePingableAdapter(mirageLuggage);
				if (mirageLuggagePingableAdapter.CanBePinged())
				{
					pingables.Add(mirageLuggagePingableAdapter);
				}
			});
			return pingables;
		}

		private static List<IPingable> FindPingablesUnderCursor()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: 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)
			List<IPingable> list = new List<IPingable>();
			Camera main = Camera.main;
			if ((Object)(object)main == (Object)null)
			{
				return list;
			}
			try
			{
				Vector3 val = default(Vector3);
				((Vector3)(ref val))..ctor((float)Screen.width / 2f, (float)Screen.height / 2f, 0f);
				RaycastHit[] array = Physics.RaycastAll(main.ScreenPointToRay(val), PingItemsConfig.PingVisibilityDistance.Value);
				for (int i = 0; i < array.Length; i++)
				{
					IPingable pingable = CreatePingableFromHit(array[i]);
					if (pingable != null && pingable.CanBePinged())
					{
						list.Add(pingable);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Error during raycast detection: " + ex.Message));
			}
			return list;
		}

		private static IPingable CreatePingableFromHit(RaycastHit hit)
		{
			GameObject gameObject = ((Component)((RaycastHit)(ref hit)).collider).gameObject;
			Item componentInParent = gameObject.GetComponentInParent<Item>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				return new ItemPingableAdapter(componentInParent);
			}
			Luggage componentInParent2 = gameObject.GetComponentInParent<Luggage>();
			if ((Object)(object)componentInParent2 != (Object)null)
			{
				return new LuggagePingableAdapter(componentInParent2);
			}
			MirageLuggage componentInParent3 = gameObject.GetComponentInParent<MirageLuggage>();
			if ((Object)(object)componentInParent3 != (Object)null)
			{
				return new MirageLuggagePingableAdapter(componentInParent3);
			}
			return null;
		}

		private static bool ContainsSameObject(List<IPingable> pingables, IPingable targetPingable)
		{
			GameObject targetGameObject = targetPingable?.GetGameObject();
			if ((Object)(object)targetGameObject == (Object)null)
			{
				return false;
			}
			return pingables.Any((IPingable p) => (Object)(object)p?.GetGameObject() == (Object)(object)targetGameObject);
		}

		private static bool IsObjectNearPosition(GameObject gameObject, Vector3 position, float radius)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)gameObject == (Object)null || !gameObject.activeInHierarchy)
			{
				return false;
			}
			return Vector3.Distance(gameObject.transform.position, position) <= radius;
		}
	}
	public class PingHighlighter : MonoBehaviour
	{
		[CompilerGenerated]
		private sealed class <LifetimeCoroutine>d__45 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public PingHighlighter <>4__this;

			private float <elapsed>5__2;

			private float <displayDuration>5__3;

			private float <fadeOutDuration>5__4;

			private float <fadeElapsed>5__5;

			private float <startingDistanceAlpha>5__6;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LifetimeCoroutine>d__45(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				PingHighlighter pingHighlighter = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<elapsed>5__2 = 0f;
					<displayDuration>5__3 = PingItemsConfig.DisplayDuration.Value;
					<fadeOutDuration>5__4 = PingItemsConfig.FadeOutDuration.Value;
					goto IL_0092;
				case 1:
					<>1__state = -1;
					goto IL_0092;
				case 2:
					{
						<>1__state = -1;
						goto IL_0158;
					}
					IL_0092:
					if (<elapsed>5__2 < <displayDuration>5__3 && pingHighlighter._targetPingable != null && pingHighlighter._targetPingable.CanBePinged())
					{
						pingHighlighter.UpdateVisualElements();
						<elapsed>5__2 += Time.deltaTime;
						<>2__current = null;
						<>1__state = 1;
						return true;
					}
					if (pingHighlighter._targetPingable == null || !pingHighlighter._targetPingable.CanBePinged())
					{
						break;
					}
					pingHighlighter._isFadingOut = true;
					<fadeElapsed>5__5 = 0f;
					<startingDistanceAlpha>5__6 = pingHighlighter.CalculateDistanceBasedAlpha();
					goto IL_0158;
					IL_0158:
					if (<fadeElapsed>5__5 < <fadeOutDuration>5__4 && pingHighlighter._targetPingable != null && pingHighlighter._isFadingOut && pingHighlighter._targetPingable.CanBePinged())
					{
						pingHighlighter.UpdateVisualElementsPositionsOnly();
						float num2 = pingHighlighter.CalculateDistanceBasedAlpha();
						float num3 = <fadeElapsed>5__5 / <fadeOutDuration>5__4;
						float num4 = Mathf.Lerp(<startingDistanceAlpha>5__6, 0f, num3);
						float alpha = Mathf.Min(num2, num4);
						pingHighlighter.SetAlpha(alpha);
						<fadeElapsed>5__5 += Time.deltaTime;
						<>2__current = null;
						<>1__state = 2;
						return true;
					}
					break;
				}
				pingHighlighter.ReturnToPool();
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private IPingable _targetPingable;

		private GameObject _targetGameObject;

		private List<IPingable> _groupedPingables;

		private GameObject _reticleIndicator;

		private TextMeshProUGUI _label;

		private Coroutine _lifetimeCoroutine;

		private bool _isFadingOut;

		private bool _isCleanedUp;

		private float _lastKnownDistance = -1f;

		public Color PlayerColor { get; set; } = Color.cyan;


		public string PlayerName { get; internal set; }

		public GameObject TargetGameObject => _targetGameObject;

		public bool IsGroupedPing
		{
			get
			{
				if (_groupedPingables != null)
				{
					return _groupedPingables.Count > 1;
				}
				return false;
			}
		}

		public int GroupCount => _groupedPingables?.Count ?? 1;

		public void Init(IPingable pingable, Vector3 pingPoint)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			Init(new List<IPingable> { pingable }, pingPoint);
		}

		public void Init(List<IPingable> pingables, Vector3 pingPoint)
		{
			if (pingables == null || pingables.Count == 0)
			{
				Plugin.Logger.LogWarning((object)"Cannot initialize highlighter with empty pingable list");
				return;
			}
			IPingable pingable = pingables[0];
			GameObject val = pingable?.GetGameObject();
			if ((Object)(object)_targetGameObject == (Object)(object)val && _isFadingOut)
			{
				_groupedPingables = new List<IPingable>(pingables);
				ResetToFullVisibility();
				RestartLifetimeTimer();
				return;
			}
			Cleanup();
			_groupedPingables = new List<IPingable>(pingables);
			_targetPingable = pingable;
			_targetGameObject = val;
			_isFadingOut = false;
			_isCleanedUp = false;
			_lastKnownDistance = -1f;
			if (_groupedPingables != null)
			{
				foreach (IPingable groupedPingable in _groupedPingables)
				{
					if (groupedPingable is ItemPingableAdapter itemPingableAdapter)
					{
						Item item = itemPingableAdapter.Item;
						item.OnStateChange = (Action<ItemState>)Delegate.Combine(item.OnStateChange, new Action<ItemState>(OnItemStateChanged));
					}
				}
			}
			CreateVisualElements();
			RestartLifetimeTimer();
		}

		private void OnItemStateChanged(ItemState newState)
		{
			try
			{
				if (!IsGroupedPing)
				{
					PingService.RemoveHighlight(this);
				}
				else
				{
					UpdateGroupAfterItemChange();
				}
			}
			catch (Exception arg)
			{
				Plugin.Logger.LogError((object)$"Error in OnItemStateChanged: {arg}");
				try
				{
					if ((Object)(object)((Component)this).gameObject != (Object)null)
					{
						Object.Destroy((Object)(object)((Component)this).gameObject);
					}
				}
				catch
				{
				}
			}
		}

		private void UpdateGroupAfterItemChange()
		{
			if (_groupedPingables != null)
			{
				List<IPingable> list = _groupedPingables.Where((IPingable p) => p?.CanBePinged() ?? false).ToList();
				if (list.Count == 0)
				{
					PingService.RemoveHighlight(this);
					return;
				}
				_groupedPingables = list;
				_targetPingable = list[0];
				_targetGameObject = _targetPingable.GetGameObject();
				_lastKnownDistance = -1f;
				UpdateVisualElements();
			}
		}

		public void Refresh(Vector3 pingPoint)
		{
			if (_isFadingOut)
			{
				ResetToFullVisibility();
			}
			_lastKnownDistance = -1f;
			RestartLifetimeTimer();
			UpdateVisualElements();
		}

		public void Cleanup()
		{
			if (_isCleanedUp)
			{
				return;
			}
			_isCleanedUp = true;
			if (_groupedPingables != null)
			{
				foreach (IPingable groupedPingable in _groupedPingables)
				{
					if (groupedPingable is ItemPingableAdapter itemPingableAdapter)
					{
						try
						{
							Item item = itemPingableAdapter.Item;
							item.OnStateChange = (Action<ItemState>)Delegate.Remove(item.OnStateChange, new Action<ItemState>(OnItemStateChanged));
						}
						catch (Exception ex)
						{
							Plugin.Logger.LogWarning((object)("Error unsubscribing from item state change: " + ex.Message));
						}
					}
				}
			}
			StopLifetimeTimer();
			DestroyVisualElements();
			ResetState();
		}

		private void CreateVisualElements()
		{
			if (_targetPingable != null)
			{
				UIManager instance = UIManager.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					Plugin.Logger.LogError((object)"UIManager not available for creating visual elements");
					return;
				}
				CreateReticleIndicator(instance);
				CreateLabel(instance);
				UpdateVisualElements();
			}
		}

		private void CreateReticleIndicator(UIManager uiManager)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			_reticleIndicator = uiManager.CreateReticleIndicator(PlayerColor);
			if ((Object)(object)_reticleIndicator != (Object)null)
			{
				PositionReticleAboveItem();
			}
		}

		private void CreateLabel(UIManager uiManager)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			_label = uiManager.RentLabel();
			if ((Object)(object)_label != (Object)null)
			{
				((Graphic)_label).color = PlayerColor;
				UpdateLabelContent();
				PositionLabelOnScreen();
			}
			else
			{
				Plugin.Logger.LogWarning((object)("Label creation failed during game startup - showing reticle-only ping for " + (_targetPingable?.GetDisplayName() ?? "unknown item")));
			}
		}

		private void UpdateVisualElements()
		{
			if (_targetPingable != null)
			{
				float alpha = CalculateDistanceBasedAlpha();
				if ((Object)(object)_reticleIndicator != (Object)null)
				{
					PositionReticleAboveItem();
					ApplyAlphaToReticle(alpha);
				}
				if ((Object)(object)_label != (Object)null)
				{
					PositionLabelOnScreen();
					ApplyAlphaToLabel(alpha);
					UpdateLabelContentIfNeeded();
				}
			}
		}

		private void UpdateVisualElementsPositionsOnly()
		{
			if (_targetPingable != null)
			{
				if ((Object)(object)_reticleIndicator != (Object)null)
				{
					PositionReticleAboveItem();
				}
				if ((Object)(object)_label != (Object)null)
				{
					PositionLabelOnScreen();
					UpdateLabelContentIfNeeded();
				}
			}
		}

		private void PositionReticleAboveItem()
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_reticleIndicator == (Object)null || _targetPingable == null)
			{
				return;
			}
			Camera main = Camera.main;
			if ((Object)(object)main == (Object)null)
			{
				return;
			}
			Vector3 center = _targetPingable.GetCenter();
			Vector3 val = main.WorldToScreenPoint(center);
			if (val.z < 0f)
			{
				_reticleIndicator.SetActive(false);
				return;
			}
			RectTransform component = _reticleIndicator.GetComponent<RectTransform>();
			if ((Object)(object)component != (Object)null)
			{
				UIManager instance = UIManager.Instance;
				if ((Object)(object)instance?.CanvasRect != (Object)null)
				{
					Vector2 anchoredPosition = default(Vector2);
					RectTransformUtility.ScreenPointToLocalPointInRectangle(instance.CanvasRect, Vector2.op_Implicit(val), (Camera)null, ref anchoredPosition);
					anchoredPosition.y += 30f;
					component.anchoredPosition = anchoredPosition;
				}
			}
			_reticleIndicator.SetActive(true);
		}

		private void UpdateLabelContent()
		{
			if (!((Object)(object)_label == (Object)null) && _targetPingable != null)
			{
				string text = ((TMP_Text)_label).text;
				string text2 = BuildLabelText();
				if (text != text2)
				{
					((TMP_Text)_label).text = text2;
				}
			}
		}

		private void UpdateLabelContentWithDistance(float distance)
		{
			if (!((Object)(object)_label == (Object)null) && _targetPingable != null)
			{
				string text = ((TMP_Text)_label).text;
				string text2 = BuildLabelText(distance);
				if (text != text2)
				{
					((TMP_Text)_label).text = text2;
				}
			}
		}

		private void UpdateLabelContentIfNeeded()
		{
			if (!((Object)(object)_label == (Object)null) && _targetPingable != null)
			{
				float distanceToPlayer = GetDistanceToPlayer();
				float value = PingItemsConfig.DistanceUpdateThreshold.Value;
				if (_lastKnownDistance < 0f || Mathf.Abs(distanceToPlayer - _lastKnownDistance) >= value)
				{
					_lastKnownDistance = distanceToPlayer;
					UpdateLabelContentWithDistance(distanceToPlayer);
				}
			}
		}

		private string BuildLabelText(float? distance = null)
		{
			List<string> list = new List<string>();
			if (PingItemsConfig.ShowPlayerName.Value && !string.IsNullOrEmpty(PlayerName))
			{
				list.Add(PlayerName);
			}
			if (PingItemsConfig.ShowItemNames.Value)
			{
				string localizedItemName = GetLocalizedItemName();
				if (!string.IsNullOrEmpty(localizedItemName))
				{
					if (IsGroupedPing)
					{
						list.Add($"{GroupCount}x {localizedItemName}");
					}
					else
					{
						list.Add(localizedItemName);
					}
				}
			}
			if (PingItemsConfig.ShowDistance.Value)
			{
				float num = distance ?? GetDistanceToPlayer();
				list.Add($"{num:F1}m");
			}
			return string.Join("\\n", list);
		}

		private string GetLocalizedItemName()
		{
			if (_targetPingable == null)
			{
				return string.Empty;
			}
			try
			{
				return _targetPingable.GetDisplayName();
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to get localized object name: " + ex.Message));
				GameObject gameObject = _targetPingable.GetGameObject();
				return ((gameObject != null) ? ((Object)gameObject).name : null) ?? "Unknown";
			}
		}

		private float GetDistanceToPlayer()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			if (_targetPingable == null)
			{
				return 0f;
			}
			Vector3 center = _targetPingable.GetCenter();
			Vector3 localPlayerPosition = GetLocalPlayerPosition();
			return Vector3.Distance(center, localPlayerPosition);
		}

		private static Vector3 GetLocalPlayerPosition()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)localCharacter != (Object)null)
			{
				return localCharacter.Center;
			}
			Player localPlayer = Player.localPlayer;
			object obj;
			if (localPlayer == null)
			{
				obj = null;
			}
			else
			{
				Character character = localPlayer.character;
				obj = ((character != null) ? ((Component)character).transform : null);
			}
			if ((Object)obj != (Object)null)
			{
				return ((Component)localPlayer.character).transform.position;
			}
			Camera main = Camera.main;
			if ((Object)(object)main != (Object)null)
			{
				return ((Component)main).transform.position;
			}
			Plugin.Logger.LogWarning((object)"Unable to determine player position - using Vector3.zero");
			return Vector3.zero;
		}

		private void PositionLabelOnScreen()
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: 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_00a3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_label == (Object)null || _targetPingable == null)
			{
				return;
			}
			UIManager instance = UIManager.Instance;
			Camera main = Camera.main;
			if ((Object)(object)instance?.CanvasRect == (Object)null || (Object)(object)main == (Object)null)
			{
				((Component)_label).gameObject.SetActive(false);
				return;
			}
			Vector3 center = _targetPingable.GetCenter();
			Vector3 val = main.WorldToScreenPoint(center);
			if (val.z > 0f)
			{
				Vector2 anchoredPosition = default(Vector2);
				RectTransformUtility.ScreenPointToLocalPointInRectangle(instance.CanvasRect, Vector2.op_Implicit(val), (Camera)null, ref anchoredPosition);
				anchoredPosition.y -= 60f;
				((TMP_Text)_label).rectTransform.anchoredPosition = anchoredPosition;
				((Component)_label).gameObject.SetActive(true);
			}
			else
			{
				((Component)_label).gameObject.SetActive(false);
			}
		}

		private void RestartLifetimeTimer()
		{
			StopLifetimeTimer();
			_lifetimeCoroutine = ((MonoBehaviour)this).StartCoroutine(LifetimeCoroutine());
		}

		private void StopLifetimeTimer()
		{
			if (_lifetimeCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_lifetimeCoroutine);
				_lifetimeCoroutine = null;
			}
		}

		[IteratorStateMachine(typeof(<LifetimeCoroutine>d__45))]
		private IEnumerator LifetimeCoroutine()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LifetimeCoroutine>d__45(0)
			{
				<>4__this = this
			};
		}

		private void SetAlpha(float alpha)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_reticleIndicator != (Object)null)
			{
				Image[] componentsInChildren = _reticleIndicator.GetComponentsInChildren<Image>();
				foreach (Image obj in componentsInChildren)
				{
					Color color = ((Graphic)obj).color;
					color.a = alpha;
					((Graphic)obj).color = color;
				}
			}
			if ((Object)(object)_label != (Object)null)
			{
				Color color2 = ((Graphic)_label).color;
				color2.a = alpha;
				((Graphic)_label).color = color2;
			}
		}

		private void ResetToFullVisibility()
		{
			_isFadingOut = false;
			SetAlpha(1f);
		}

		private float CalculateDistanceBasedAlpha()
		{
			if (_targetPingable == null)
			{
				return 1f;
			}
			float distanceToPlayer = GetDistanceToPlayer();
			float value = PingItemsConfig.PingVisibilityDistance.Value;
			if (distanceToPlayer <= value * 0.8f)
			{
				return 1f;
			}
			if (distanceToPlayer >= value)
			{
				return 0f;
			}
			float num = value * 0.8f;
			float num2 = value - num;
			float num3 = (distanceToPlayer - num) / num2;
			return Mathf.SmoothStep(1f, 0f, num3);
		}

		private void ApplyAlphaToReticle(float alpha)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_reticleIndicator == (Object)null))
			{
				Image[] componentsInChildren = _reticleIndicator.GetComponentsInChildren<Image>();
				foreach (Image obj in componentsInChildren)
				{
					Color playerColor = PlayerColor;
					playerColor.a = alpha;
					((Graphic)obj).color = playerColor;
				}
			}
		}

		private void ApplyAlphaToLabel(float alpha)
		{
			//IL_0015: 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)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_label == (Object)null))
			{
				Color color = ((Graphic)_label).color;
				color.a = alpha;
				((Graphic)_label).color = color;
			}
		}

		private void DestroyVisualElements()
		{
			if ((Object)(object)_reticleIndicator != (Object)null)
			{
				Object.Destroy((Object)(object)_reticleIndicator);
				_reticleIndicator = null;
			}
			if ((Object)(object)_label != (Object)null)
			{
				UIManager instance = UIManager.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					instance.ReleaseLabel(_label);
				}
				else
				{
					Object.Destroy((Object)(object)((Component)_label).gameObject);
				}
				_label = null;
			}
		}

		private void ResetState()
		{
			_targetPingable = null;
			_targetGameObject = null;
			_isFadingOut = false;
			_isCleanedUp = false;
			_lastKnownDistance = -1f;
		}

		private void ReturnToPool()
		{
			PingService.RemoveHighlight(this);
		}

		private void OnDestroy()
		{
			Cleanup();
		}
	}
	internal class UIManager : MonoBehaviour
	{
		private readonly Queue<PingHighlighter> _highlighterPool = new Queue<PingHighlighter>();

		private readonly Queue<TextMeshProUGUI> _labelPool = new Queue<TextMeshProUGUI>();

		private TMP_FontAsset _cachedGameFont;

		private Canvas _overlayCanvas;

		private RectTransform _canvasRect;

		private GameObject _reticlePrefab;

		public static UIManager Instance { get; private set; }

		public RectTransform CanvasRect
		{
			get
			{
				EnsureCanvas();
				return _canvasRect;
			}
		}

		private void Awake()
		{
			if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this)
			{
				Object.Destroy((Object)(object)this);
				return;
			}
			Instance = this;
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
			InitializeUI();
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		private void InitializeUI()
		{
			EnsureCanvas();
			CaptureGameResources();
		}

		private void EnsureCanvas()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			if (!((Object)(object)_overlayCanvas != (Object)null))
			{
				GameObject val = GameObject.Find("PingItems_OverlayCanvas");
				if ((Object)(object)val == (Object)null)
				{
					GameObject val2 = new GameObject("PingItems_OverlayCanvas");
					_overlayCanvas = val2.AddComponent<Canvas>();
					_overlayCanvas.renderMode = (RenderMode)0;
					_overlayCanvas.sortingOrder = 1000;
					val2.AddComponent<CanvasScaler>();
					val2.AddComponent<GraphicRaycaster>();
				}
				else
				{
					_overlayCanvas = val.GetComponent<Canvas>() ?? val.AddComponent<Canvas>();
				}
				_canvasRect = ((Component)_overlayCanvas).GetComponent<RectTransform>();
			}
		}

		private void CaptureGameResources()
		{
		}

		public void SetReticlePrefab(GameObject reticle)
		{
			if ((Object)(object)reticle != (Object)null)
			{
				_reticlePrefab = reticle;
			}
		}

		public void SetGameFont(TMP_FontAsset font)
		{
			if ((Object)(object)font != (Object)null)
			{
				_cachedGameFont = font;
				Plugin.Logger.LogInfo((object)("Game font captured: " + ((Object)font).name));
			}
		}

		public PingHighlighter RentHighlighter()
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if (_highlighterPool.Count > 0)
			{
				PingHighlighter pingHighlighter = _highlighterPool.Dequeue();
				if (IsHighlighterValid(pingHighlighter))
				{
					((Component)pingHighlighter).gameObject.SetActive(true);
					return pingHighlighter;
				}
				Plugin.Logger.LogWarning((object)"Discarded invalid highlighter from pool during game startup");
			}
			return new GameObject("PingHighlighter").AddComponent<PingHighlighter>();
		}

		public void ReleaseHighlighter(PingHighlighter highlighter)
		{
			if (!((Object)(object)highlighter == (Object)null))
			{
				((Component)highlighter).gameObject.SetActive(false);
				_highlighterPool.Enqueue(highlighter);
			}
		}

		public GameObject CreateReticleIndicator(Color playerColor)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			EnsureCanvas();
			if ((Object)(object)_reticlePrefab != (Object)null)
			{
				return CreateGameReticle(playerColor);
			}
			return CreateFallbackReticle(playerColor);
		}

		public TextMeshProUGUI RentLabel()
		{
			EnsureCanvas();
			if (_labelPool.Count > 0)
			{
				TextMeshProUGUI val = _labelPool.Dequeue();
				if (IsLabelValid(val))
				{
					ResetLabel(val);
					return val;
				}
				Plugin.Logger.LogWarning((object)"Discarded invalid label from pool during game startup");
			}
			TextMeshProUGUI obj = CreateNewLabel();
			if ((Object)(object)obj == (Object)null)
			{
				Plugin.Logger.LogWarning((object)"Failed to create new label during game startup - ping may show only reticle");
			}
			return obj;
		}

		public void ReleaseLabel(TextMeshProUGUI label)
		{
			if (!((Object)(object)label == (Object)null))
			{
				((Component)label).gameObject.SetActive(false);
				((TMP_Text)label).text = string.Empty;
				_labelPool.Enqueue(label);
			}
		}

		private GameObject CreateGameReticle(Color playerColor)
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: 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)
			GameObject val = Object.Instantiate<GameObject>(_reticlePrefab);
			((Object)val).name = "PingReticle";
			val.transform.SetParent(((Component)_overlayCanvas).transform, false);
			RectTransform component = val.GetComponent<RectTransform>();
			if ((Object)(object)component != (Object)null)
			{
				Vector2 sizeDelta = component.sizeDelta;
				if (sizeDelta == Vector2.zero)
				{
					((Vector2)(ref sizeDelta))..ctor(50f, 50f);
				}
				component.sizeDelta = sizeDelta * PingItemsConfig.ReticleScale.Value;
			}
			else
			{
				val.transform.localScale = Vector3.one * PingItemsConfig.ReticleScale.Value;
			}
			ApplyReticleEffects(val, playerColor);
			return val;
		}

		private GameObject CreateFallbackReticle(Color playerColor)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("PingReticleFallback");
			val.transform.SetParent(((Component)_overlayCanvas).transform, false);
			val.AddComponent<RectTransform>().sizeDelta = new Vector2(50f, 50f) * PingItemsConfig.ReticleScale.Value;
			Image obj = val.AddComponent<Image>();
			((Graphic)obj).color = playerColor;
			TrySetCircleSprite(obj);
			if (PingItemsConfig.EnableOutlines.Value)
			{
				AddOutlineEffect(val);
			}
			return val;
		}

		private static void ApplyReticleEffects(GameObject reticle, Color playerColor)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			Image[] componentsInChildren = reticle.GetComponentsInChildren<Image>();
			foreach (Image val in componentsInChildren)
			{
				((Graphic)val).color = playerColor;
				if (PingItemsConfig.EnableOutlines.Value)
				{
					AddOutlineEffect(((Component)val).gameObject);
				}
			}
			Renderer[] componentsInChildren2 = reticle.GetComponentsInChildren<Renderer>();
			foreach (Renderer val2 in componentsInChildren2)
			{
				if ((Object)(object)val2.sharedMaterial != (Object)null)
				{
					Material val3 = new Material(val2.sharedMaterial);
					SetMaterialColor(val3, playerColor);
					val2.sharedMaterial = val3;
				}
			}
		}

		private TextMeshProUGUI CreateNewLabel()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				EnsureCanvas();
				if ((Object)(object)_canvasRect == (Object)null)
				{
					Plugin.Logger.LogError((object)"Cannot create label: Canvas rect is null during game startup");
					return null;
				}
				GameObject val = new GameObject("PingLabel");
				val.transform.SetParent((Transform)(object)_canvasRect, false);
				TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
				((TMP_Text)val2).alignment = (TextAlignmentOptions)514;
				((TMP_Text)val2).fontSize = PingItemsConfig.LabelFontSize.Value;
				((Graphic)val2).raycastTarget = false;
				if ((Object)(object)_cachedGameFont != (Object)null)
				{
					((TMP_Text)val2).font = _cachedGameFont;
				}
				if (PingItemsConfig.EnableOutlines.Value)
				{
					ApplyTextOutline(val2);
				}
				return val2;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Failed to create new label during game startup: " + ex.Message));
				return null;
			}
		}

		private void ResetLabel(TextMeshProUGUI label)
		{
			if ((Object)(object)label == (Object)null || (Object)(object)((Component)label).gameObject == (Object)null)
			{
				Plugin.Logger.LogWarning((object)"Attempted to reset null or destroyed label");
				return;
			}
			try
			{
				((Component)label).gameObject.SetActive(true);
				((TMP_Text)label).text = string.Empty;
				((TMP_Text)label).fontSize = PingItemsConfig.LabelFontSize.Value;
				if ((Object)(object)_cachedGameFont != (Object)null && (Object)(object)((TMP_Text)label).font != (Object)(object)_cachedGameFont)
				{
					((TMP_Text)label).font = _cachedGameFont;
				}
				if (PingItemsConfig.EnableOutlines.Value)
				{
					ApplyTextOutline(label);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Error resetting label: " + ex.Message));
				throw;
			}
		}

		private static bool IsLabelValid(TextMeshProUGUI label)
		{
			if ((Object)(object)label == (Object)null)
			{
				return false;
			}
			try
			{
				return (Object)(object)((Component)label).gameObject != (Object)null;
			}
			catch
			{
				return false;
			}
		}

		private static bool IsHighlighterValid(PingHighlighter highlighter)
		{
			if ((Object)(object)highlighter == (Object)null)
			{
				return false;
			}
			try
			{
				return (Object)(object)((Component)highlighter).gameObject != (Object)null;
			}
			catch
			{
				return false;
			}
		}

		private static void AddOutlineEffect(GameObject uiElement)
		{
			//IL_0018: 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)
			Outline val = uiElement.GetComponent<Outline>();
			if ((Object)(object)val == (Object)null)
			{
				val = uiElement.AddComponent<Outline>();
			}
			((Shadow)val).effectColor = Color.white;
			((Shadow)val).effectDistance = new Vector2(1.5f, -1.5f);
			((Shadow)val).useGraphicAlpha = true;
		}

		private static void ApplyTextOutline(TextMeshProUGUI textComponent)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)((TMP_Text)textComponent).fontMaterial == (Object)null))
			{
				Material val = new Material(((TMP_Text)textComponent).fontMaterial);
				if (val.HasProperty("_OutlineWidth"))
				{
					val.SetFloat("_OutlineWidth", PingItemsConfig.OutlineWidth.Value);
				}
				if (val.HasProperty("_OutlineColor"))
				{
					val.SetColor("_OutlineColor", Color.white);
				}
				if (val.HasProperty("_OutlineSoftness"))
				{
					val.SetFloat("_OutlineSoftness", 0.05f);
				}
				((TMP_Text)textComponent).fontMaterial = val;
			}
		}

		private static void TrySetCircleSprite(Image image)
		{
			try
			{
				Sprite[] array = Resources.FindObjectsOfTypeAll<Sprite>();
				foreach (Sprite val in array)
				{
					string text = ((Object)val).name.ToLower();
					if (text.Contains("circle") || text.Contains("dot") || text.Contains("round"))
					{
						image.sprite = val;
						break;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to find circle sprite: " + ex.Message));
			}
		}

		private static void SetMaterialColor(Material material, Color color)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (material.HasProperty("_Color"))
				{
					material.SetColor("_Color", color);
				}
				if (material.HasProperty("_BaseColor"))
				{
					material.SetColor("_BaseColor", color);
				}
				if (material.HasProperty("_MainColor"))
				{
					material.SetColor("_MainColor", color);
				}
				material.renderQueue = 4000;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Failed to set material color: " + ex.Message));
			}
		}
	}
}
namespace PingItems.Configuration
{
	public static class PingItemsConfig
	{
		private static string DISPLAY_SECTION = "Display";

		private static string DETECTION_SECTION = "Detection";

		private static string VISUAL_SECTION = "Visual";

		public static ConfigEntry<float> DisplayDuration { get; private set; }

		public static ConfigEntry<float> FadeOutDuration { get; private set; }

		public static ConfigEntry<float> ReticleScale { get; private set; }

		public static ConfigEntry<float> LabelFontSize { get; private set; }

		public static ConfigEntry<float> DetectionRadius { get; private set; }

		public static ConfigEntry<float> LuggageDetectionRadius { get; private set; }

		public static ConfigEntry<bool> ShowPlayerName { get; private set; }

		public static ConfigEntry<bool> ShowDistance { get; private set; }

		public static ConfigEntry<bool> ShowItemNames { get; private set; }

		public static ConfigEntry<float> DistanceUpdateThreshold { get; private set; }

		public static ConfigEntry<float> PingVisibilityDistance { get; private set; }

		public static ConfigEntry<bool> EnableItemGrouping { get; private set; }

		public static ConfigEntry<float> GroupingRadius { get; private set; }

		public static ConfigEntry<bool> EnableOutlines { get; private set; }

		public static ConfigEntry<float> OutlineWidth { get; private set; }

		public static void Initialize(ConfigFile config)
		{
			DisplayDuration = config.Bind<float>(DISPLAY_SECTION, "Duration", 10f, "How long the ping indicator remains visible (seconds)");
			FadeOutDuration = config.Bind<float>(DISPLAY_SECTION, "FadeOutDuration", 2f, "Duration of the fade-out animation (seconds)");
			ReticleScale = config.Bind<float>(DISPLAY_SECTION, "ReticleScale", 5f, "Scale multiplier for reticle size (1.0 = original size)");
			ShowPlayerName = config.Bind<bool>(DISPLAY_SECTION, "ShowPlayerName", true, "Show the name of the player who pinged in the label");
			ShowDistance = config.Bind<bool>(DISPLAY_SECTION, "ShowDistance", true, "Show distance to item in the label");
			ShowItemNames = config.Bind<bool>(DISPLAY_SECTION, "ShowItemNames", true, "Show localized item names in the label");
			LabelFontSize = config.Bind<float>(DISPLAY_SECTION, "LabelFontSize", 32f, "Font size for item labels");
			DetectionRadius = config.Bind<float>(DETECTION_SECTION, "Radius", 1.2f, "Maximum distance from ping to detect items (game units)");
			LuggageDetectionRadius = config.Bind<float>(DETECTION_SECTION, "LuggageRadius", 2.2f, "Maximum distance from ping to detect luggage objects (game units). Should be larger than Radius since luggage is bigger.");
			DistanceUpdateThreshold = config.Bind<float>(DETECTION_SECTION, "DistanceUpdateThreshold", 0.05f, "Distance change threshold to trigger label updates (meters). Lower = more sensitive, higher = less sensitive");
			PingVisibilityDistance = config.Bind<float>(DETECTION_SECTION, "PingVisibilityDistance", 100f, "Maximum distance from ping point to show the ping indicator (in game units).");
			EnableItemGrouping = config.Bind<bool>(DETECTION_SECTION, "EnableItemGrouping", true, "Group multiple items of the same type into a single ping with count (e.g., '3x Coconut')");
			GroupingRadius = config.Bind<float>(DETECTION_SECTION, "GroupingRadius", 5f, "Maximum distance between items of the same type to group them together (game units)");
			EnableOutlines = config.Bind<bool>(VISUAL_SECTION, "EnableOutlines", true, "Enable outline effects on text and reticles");
			OutlineWidth = config.Bind<float>(VISUAL_SECTION, "OutlineWidth", 0.065f, "Width of text outline effect");
		}
	}
}