Decompiled source of PinRemoval v1.0.0

PinRemoval.dll

Decompiled 3 days ago
using System;
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 UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("PinRemoval")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Remove or deduplicate Valheim map pins by name (e.g. AutoMapPins Twitcher spam).")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+fe4f25eca1e3ff1cbdbc77787ceebda1b37c6e40")]
[assembly: AssemblyProduct("PinRemoval")]
[assembly: AssemblyTitle("PinRemoval")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace PinRemoval
{
	[HarmonyPatch(typeof(Chat), "InputText")]
	internal static class ChatSlashPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Chat __instance)
		{
			Traverse val = Traverse.Create((object)__instance).Field("m_input");
			if (val.GetValue() == null)
			{
				return;
			}
			string value = val.Property("text", (object[])null).GetValue<string>();
			if (string.IsNullOrEmpty(value) || value[0] != '/')
			{
				return;
			}
			string text = value.Substring(1).TrimStart(Array.Empty<char>());
			if (text.Length != 0 && text.StartsWith("pinremoval", StringComparison.OrdinalIgnoreCase))
			{
				string text2 = "/" + text;
				if (text2 != value)
				{
					val.Property("text", (object[])null).SetValue((object)text2);
				}
			}
		}
	}
	internal static class ConsoleCommands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__1_0;

			public static ConsoleEvent <>9__1_1;

			public static ConsoleEvent <>9__1_2;

			public static ConsoleEvent <>9__1_3;

			internal void <Register>b__1_0(ConsoleEventArgs args)
			{
				Run(args, null);
			}

			internal void <Register>b__1_1(ConsoleEventArgs args)
			{
				Run(args, PinService.Mode.RemoveAll);
			}

			internal void <Register>b__1_2(ConsoleEventArgs args)
			{
				Run(args, PinService.Mode.Deduplicate);
			}

			internal void <Register>b__1_3(ConsoleEventArgs args)
			{
				Run(args, PinService.Mode.Deduplicate);
			}
		}

		private static bool _registered;

		internal static void Register()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//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_005f: Expected O, but got Unknown
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Expected O, but got Unknown
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			object obj = <>c.<>9__1_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					Run(args, null);
				};
				<>c.<>9__1_0 = val;
				obj = (object)val;
			}
			RegisterCommand("pinremoval", "/pinremoval [remove|duplicate] [names] [radius] — clean map pins by name (defaults from config)", (ConsoleEvent)obj);
			object obj2 = <>c.<>9__1_1;
			if (obj2 == null)
			{
				ConsoleEvent val2 = delegate(ConsoleEventArgs args)
				{
					Run(args, PinService.Mode.RemoveAll);
				};
				<>c.<>9__1_1 = val2;
				obj2 = (object)val2;
			}
			RegisterCommand("pinremoval_remove", "/pinremoval_remove [names] — remove all matching pins", (ConsoleEvent)obj2);
			object obj3 = <>c.<>9__1_2;
			if (obj3 == null)
			{
				ConsoleEvent val3 = delegate(ConsoleEventArgs args)
				{
					Run(args, PinService.Mode.Deduplicate);
				};
				<>c.<>9__1_2 = val3;
				obj3 = (object)val3;
			}
			RegisterCommand("pinremoval_duplicate", "/pinremoval_duplicate [names] [radius] — keep one matching pin per radius (default 5m)", (ConsoleEvent)obj3);
			object obj4 = <>c.<>9__1_3;
			if (obj4 == null)
			{
				ConsoleEvent val4 = delegate(ConsoleEventArgs args)
				{
					Run(args, PinService.Mode.Deduplicate);
				};
				<>c.<>9__1_3 = val4;
				obj4 = (object)val4;
			}
			RegisterCommand("pinremoval_dedupe", "/pinremoval_dedupe [names] [radius] — alias for pinremoval_duplicate", (ConsoleEvent)obj4);
			_registered = true;
			PinRemovalPlugin.Log.LogInfo((object)"Registered commands: /pinremoval_remove, /pinremoval_duplicate (aliases: pinremoval, pinremoval_dedupe)");
		}

		private static void RegisterCommand(string name, string description, ConsoleEvent action)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			new ConsoleCommand(name, description, action, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private static void Run(ConsoleEventArgs args, PinService.Mode? preferredMode)
		{
			string[] array = args.Args.Skip(1).ToArray();
			PinService.Mode mode = (PinService.Mode)(((int?)preferredMode) ?? ((!ModConfig.IsRemoveAllMode()) ? 1 : 0));
			float num = ModConfig.DeduplicateRadiusMeters.Value;
			List<string> list = ModConfig.GetConfiguredNames().ToList();
			int num2 = 0;
			if (!preferredMode.HasValue && array.Length != 0)
			{
				if (string.Equals(array[0], "remove", StringComparison.OrdinalIgnoreCase) || string.Equals(array[0], "removeall", StringComparison.OrdinalIgnoreCase))
				{
					mode = PinService.Mode.RemoveAll;
					num2 = 1;
				}
				else if (IsDuplicateToken(array[0]))
				{
					mode = PinService.Mode.Deduplicate;
					num2 = 1;
				}
			}
			if (array.Length > num2)
			{
				if (mode == PinService.Mode.Deduplicate && float.TryParse(array[^1], out var result))
				{
					num = result;
					string text = string.Join(" ", array.Skip(num2).Take(array.Length - num2 - 1));
					if (!string.IsNullOrWhiteSpace(text))
					{
						list = ModConfig.ParseNames(text).ToList();
					}
				}
				else
				{
					string text2 = string.Join(" ", array.Skip(num2));
					if (!string.IsNullOrWhiteSpace(text2))
					{
						list = ModConfig.ParseNames(text2).ToList();
					}
				}
			}
			if (list.Count == 0)
			{
				Tell(args, "PinRemoval: no pin names configured. Set General.PinNames or pass names on the command.");
				return;
			}
			Tell(args, string.Format("PinRemoval: {0} [{1}] radius={2:0.#}m", mode, string.Join(", ", list), num));
			PinService.Result result2 = PinService.Process(list, mode, num);
			Tell(args, $"PinRemoval: matched={result2.Matched} removed={result2.Removed} kept={result2.Kept}");
		}

		private static bool IsDuplicateToken(string token)
		{
			if (!string.Equals(token, "duplicate", StringComparison.OrdinalIgnoreCase) && !string.Equals(token, "dedupe", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(token, "deduplicate", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static void Tell(ConsoleEventArgs args, string message)
		{
			args.Context.AddString(message);
			MessageHud instance = MessageHud.instance;
			if (instance != null)
			{
				instance.ShowMessage((MessageType)1, message, 0, (Sprite)null, false, true);
			}
		}
	}
	internal static class ModConfig
	{
		internal static ConfigEntry<string> PinNames;

		internal static ConfigEntry<float> DeduplicateRadiusMeters;

		internal static ConfigEntry<string> DefaultMode;

		internal static ConfigEntry<bool> MatchContains;

		internal static ConfigEntry<bool> CaseInsensitive;

		internal static void Bind(ConfigFile config)
		{
			PinNames = config.Bind<string>("General", "PinNames", "Twitcher", "Comma-separated pin name filters. Used by default when a console command omits the name.");
			DeduplicateRadiusMeters = config.Bind<float>("General", "DeduplicateRadiusMeters", 5f, "When using /pinremoval_duplicate, keep one pin per name within this many meters.");
			DefaultMode = config.Bind<string>("General", "DefaultMode", "Deduplicate", "Default mode when using pinremoval with no arguments: RemoveAll or Deduplicate.");
			MatchContains = config.Bind<bool>("General", "MatchContains", true, "If true, pin names that contain a filter match. If false, require an exact name match.");
			CaseInsensitive = config.Bind<bool>("General", "CaseInsensitive", true, "Ignore case when matching pin names.");
		}

		internal static IReadOnlyList<string> GetConfiguredNames()
		{
			return ParseNames(PinNames.Value);
		}

		internal static IReadOnlyList<string> ParseNames(string raw)
		{
			if (string.IsNullOrWhiteSpace(raw))
			{
				return Array.Empty<string>();
			}
			return (from s in raw.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim() into s
				where s.Length > 0
				select s).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList();
		}

		internal static bool IsRemoveAllMode()
		{
			return string.Equals(DefaultMode.Value?.Trim(), "RemoveAll", StringComparison.OrdinalIgnoreCase);
		}
	}
	internal static class ModConstants
	{
		public const string ModGuid = "Hardwire99.PinRemoval";

		public const string ModName = "PinRemoval";

		public const string ModVersion = "1.0.0";
	}
	[BepInPlugin("Hardwire99.PinRemoval", "PinRemoval", "1.0.0")]
	public class PinRemovalPlugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("Hardwire99.PinRemoval");
			_harmony.PatchAll();
			try
			{
				ConsoleCommands.Register();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"PinRemoval failed to register console commands — chat slash patch remains active. {arg}");
			}
			Log.LogInfo((object)"PinRemoval 1.0.0 loaded. Chat: /pinremoval_remove, /pinremoval_duplicate");
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal static class PinService
	{
		internal enum Mode
		{
			RemoveAll,
			Deduplicate
		}

		internal readonly struct Result
		{
			public readonly int Matched;

			public readonly int Removed;

			public readonly int Kept;

			public Result(int matched, int removed, int kept)
			{
				Matched = matched;
				Removed = removed;
				Kept = kept;
			}
		}

		private static readonly FieldRef<Minimap, List<PinData>> PinsRef = AccessTools.FieldRefAccess<Minimap, List<PinData>>("m_pins");

		internal static bool TryGetMinimap(out Minimap minimap)
		{
			minimap = Minimap.instance;
			return (Object)(object)minimap != (Object)null;
		}

		internal static Result Process(IReadOnlyList<string> nameFilters, Mode mode, float radiusMeters)
		{
			if (!TryGetMinimap(out var minimap))
			{
				PinRemovalPlugin.Log.LogWarning((object)"Minimap is not ready yet. Open the map once or wait until you are in-world.");
				return new Result(0, 0, 0);
			}
			if (nameFilters == null || nameFilters.Count == 0)
			{
				PinRemovalPlugin.Log.LogWarning((object)"No pin name filters provided.");
				return new Result(0, 0, 0);
			}
			List<PinData> list = PinsRef.Invoke(minimap);
			if (list == null || list.Count == 0)
			{
				PinRemovalPlugin.Log.LogInfo((object)"No map pins found.");
				return new Result(0, 0, 0);
			}
			List<PinData> list2 = list.Where((PinData p) => p != null && MatchesAny(p.m_name, nameFilters)).ToList();
			if (list2.Count == 0)
			{
				PinRemovalPlugin.Log.LogInfo((object)("No pins matched filters: " + string.Join(", ", nameFilters)));
				return new Result(0, 0, 0);
			}
			if (mode == Mode.RemoveAll)
			{
				return RemoveAll(minimap, list2);
			}
			return Deduplicate(minimap, list2, Math.Max(0f, radiusMeters));
		}

		private static Result RemoveAll(Minimap minimap, List<PinData> matched)
		{
			int num = 0;
			foreach (PinData item in matched)
			{
				try
				{
					minimap.RemovePin(item);
					num++;
				}
				catch (Exception ex)
				{
					PinRemovalPlugin.Log.LogWarning((object)("Failed to remove pin '" + item.m_name + "': " + ex.Message));
				}
			}
			PinRemovalPlugin.Log.LogInfo((object)$"Removed {num} pin(s) (matched {matched.Count}).");
			return new Result(matched.Count, num, matched.Count - num);
		}

		private static Result Deduplicate(Minimap minimap, List<PinData> matched, float radiusMeters)
		{
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			float radiusSq = radiusMeters * radiusMeters;
			int num = 0;
			int num2 = 0;
			foreach (IGrouping<string, PinData> item in matched.GroupBy<PinData, string>((PinData p) => (p.m_name ?? string.Empty).Trim(), StringComparer.OrdinalIgnoreCase))
			{
				List<Vector3> list = new List<Vector3>();
				foreach (PinData pin in item)
				{
					if (list.Any(delegate(Vector3 k)
					{
						//IL_0000: Unknown result type (might be due to invalid IL or missing references)
						//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_0011: Unknown result type (might be due to invalid IL or missing references)
						Vector3 val = k - pin.m_pos;
						return ((Vector3)(ref val)).sqrMagnitude <= radiusSq;
					}))
					{
						try
						{
							minimap.RemovePin(pin);
							num++;
						}
						catch (Exception ex)
						{
							PinRemovalPlugin.Log.LogWarning((object)("Failed to remove pin '" + pin.m_name + "': " + ex.Message));
						}
					}
					else
					{
						list.Add(pin.m_pos);
						num2++;
					}
				}
			}
			PinRemovalPlugin.Log.LogInfo((object)$"Deduplicated within {radiusMeters:0.#}m: removed {num}, kept {num2} (matched {matched.Count}).");
			return new Result(matched.Count, num, num2);
		}

		private static bool MatchesAny(string pinName, IReadOnlyList<string> filters)
		{
			if (string.IsNullOrEmpty(pinName))
			{
				return false;
			}
			StringComparison comparisonType = (ModConfig.CaseInsensitive.Value ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
			foreach (string filter in filters)
			{
				if (string.IsNullOrEmpty(filter))
				{
					continue;
				}
				if (ModConfig.MatchContains.Value)
				{
					if (pinName.IndexOf(filter, comparisonType) >= 0)
					{
						return true;
					}
				}
				else if (string.Equals(pinName, filter, comparisonType))
				{
					return true;
				}
			}
			return false;
		}
	}
}