Decompiled source of SoloMapPins v0.2.1

plugins\SoloMapPins.dll

Decompiled a month 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.Text;
using BepInEx;
using BepInEx.Configuration;
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("SoloMapPins")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("SoloMapPins")]
[assembly: AssemblyTitle("SoloMapPins")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace SoloMapPins
{
	[BepInPlugin("SoloMapPins", "Solo Map Pins", "0.2.1")]
	public sealed class SoloMapPinsPlugin : BaseUnityPlugin
	{
		private struct PendingLocation
		{
			public Vector3 Point;

			public string Name;
		}

		private static class DiscoverLocationPatch
		{
			public static bool Prefix(object?[] __args)
			{
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				//IL_0049: 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)
				if (!_blockFarSharedDiscoveries.Value || !TryGetWorldPoint(__args, out var point))
				{
					if (TryGetWorldPoint(__args, out point))
					{
						UnlockLocation(point, null);
					}
					return true;
				}
				if (IsLocalPlayerNear(point, _fallbackDiscoveryRange.Value))
				{
					UnlockLocation(point, null);
					return true;
				}
				DebugLog($"Blocked shared location discovery at {point}; local player is not nearby.");
				return false;
			}
		}

		private static class LocationIconsPatch
		{
			public static void Postfix()
			{
				PendingLocations.Clear();
				_nextPendingLocationCheck = 0f;
				ZoneSystem instance = ZoneSystem.instance;
				if ((Object)(object)instance != (Object)null)
				{
					Dictionary<Vector3, string> dictionary = new Dictionary<Vector3, string>();
					instance.GetLocationIcons(dictionary);
				}
				if ((Object)(object)Minimap.instance != (Object)null)
				{
					Traverse.Create((object)Minimap.instance).Field("m_updateLocationsTimer").SetValue((object)0f);
				}
			}
		}

		private static class GetLocationIconsPatch
		{
			public static void Postfix(Dictionary<Vector3, string> __0)
			{
				FilterLocationIcons(__0);
			}
		}

		public const string PluginGuid = "SoloMapPins";

		public const string PluginName = "Solo Map Pins";

		public const string PluginVersion = "0.2.1";

		private static ConfigEntry<bool> _blockFarSharedDiscoveries = null;

		private static ConfigEntry<bool> _filterGlobalLocationIcons = null;

		private static ConfigEntry<float> _fallbackDiscoveryRange = null;

		private static ConfigEntry<float> _locationIconRevealRange = null;

		private static ConfigEntry<string> _alwaysKeepLocationNames = null;

		private static ConfigEntry<float> _duplicatePinRadius = null;

		private static ConfigEntry<bool> _debugLogging = null;

		private static SoloMapPinsPlugin _instance = null;

		private const string UnlockedLocationsKey = "SoloMapPins.UnlockedLocationIcons";

		private const float PendingLocationCheckInterval = 1f;

		private static readonly Dictionary<string, PendingLocation> PendingLocations = new Dictionary<string, PendingLocation>();

		private static float _nextPendingLocationCheck;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Expected O, but got Unknown
			_instance = this;
			_blockFarSharedDiscoveries = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "BlockFarSharedDiscoveries", false, "Prevents direct location discovery pins from being accepted when the local player is not near the location. Disabled by default because mods like MWL Waystones intentionally reveal distant personal pins.");
			_filterGlobalLocationIcons = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "FilterGlobalLocationIcons", true, "Filters synced unique location icons before they are added unless the local player is close enough to that location.");
			_fallbackDiscoveryRange = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FallbackDiscoveryRange", 100f, "Distance used when guarding direct location discovery responses.");
			_locationIconRevealRange = ((BaseUnityPlugin)this).Config.Bind<float>("General", "LocationIconRevealRange", 250f, "Unique location icons within this many meters of the local player are allowed to stay on the map.");
			_alwaysKeepLocationNames = ((BaseUnityPlugin)this).Config.Bind<string>("General", "AlwaysKeepLocationNames", "StartTemple,BossStone", "Comma-separated location name fragments that are never removed from the synced location icon list.");
			_duplicatePinRadius = ((BaseUnityPlugin)this).Config.Bind<float>("General", "DuplicatePinRadius", 25f, "Existing location pins within this many meters are treated as the same discovery.");
			_debugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "Logs blocked or locally-added location pins.");
			_harmony = new Harmony("SoloMapPins");
			PatchIfPresent(typeof(Minimap), "DiscoverLocation", typeof(DiscoverLocationPatch));
			PatchIfPresent(typeof(Game), "RPC_DiscoverLocationResponse", typeof(DiscoverLocationPatch));
			PatchIfPresent(typeof(ZoneSystem), "RPC_LocationIcons", typeof(LocationIconsPatch), postfix: true);
			PatchIfPresent(typeof(ZoneSystem), "GetLocationIcons", typeof(GetLocationIconsPatch), postfix: true);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Solo Map Pins 0.2.1 loaded.");
		}

		private void Update()
		{
			ReevaluatePendingLocations();
		}

		private void OnDestroy()
		{
			PendingLocations.Clear();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void PatchIfPresent(Type targetType, string methodName, Type patchType, bool postfix = false)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			MethodInfo methodInfo = AccessTools.Method(targetType, methodName, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find " + targetType.Name + "." + methodName + "; this Valheim version may have renamed it."));
			}
			else
			{
				HarmonyMethod val = (postfix ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.Method(patchType, "Prefix", (Type[])null, (Type[])null)));
				HarmonyMethod val2 = ((!postfix) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.Method(patchType, "Postfix", (Type[])null, (Type[])null)));
				_harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void DebugLog(string message)
		{
			if (_debugLogging.Value)
			{
				((BaseUnityPlugin)_instance).Logger.LogInfo((object)message);
			}
		}

		private static bool IsLocalPlayerNear(Vector3 point, float range)
		{
			//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)
			Player localPlayer = Player.m_localPlayer;
			range = Mathf.Max(0f, range);
			if ((Object)(object)localPlayer != (Object)null)
			{
				return HorizontalDistanceSquared(((Component)localPlayer).transform.position, point) <= range * range;
			}
			return false;
		}

		private static bool ShouldKeepLocationIcon(Vector3 point)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			float num = Mathf.Max(0f, _locationIconRevealRange.Value);
			if ((Object)(object)localPlayer != (Object)null)
			{
				return HorizontalDistanceSquared(((Component)localPlayer).transform.position, point) <= num * num;
			}
			return false;
		}

		private static float HorizontalDistanceSquared(Vector3 first, Vector3 second)
		{
			//IL_0000: 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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			float num = first.x - second.x;
			float num2 = first.z - second.z;
			return num * num + num2 * num2;
		}

		private static string GetLegacyLocationKey(Vector3 point)
		{
			//IL_0005: 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)
			return $"{Mathf.RoundToInt(point.x)}:{Mathf.RoundToInt(point.z)}";
		}

		private static string GetLocationKey(Vector3 point, string? locationName)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: 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)
			if (string.IsNullOrWhiteSpace(locationName))
			{
				return GetLegacyLocationKey(point);
			}
			string s = locationName.Trim().ToLowerInvariant();
			string arg = Convert.ToBase64String(Encoding.UTF8.GetBytes(s)).TrimEnd(new char[1] { '=' }).Replace('+', '-')
				.Replace('/', '_');
			return $"v2:{Mathf.RoundToInt(point.x)}:{Mathf.RoundToInt(point.z)}:{arg}";
		}

		private static Dictionary<string, string> GetUnlockedLocations()
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return dictionary;
			}
			Dictionary<string, string> value = Traverse.Create((object)localPlayer).Field("m_customData").GetValue<Dictionary<string, string>>();
			if (value == null || !value.TryGetValue("SoloMapPins.UnlockedLocationIcons", out var value2) || string.IsNullOrWhiteSpace(value2))
			{
				return dictionary;
			}
			string[] array = value2.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split(new char[1] { '=' }, 2);
				if (array2.Length == 2 && !dictionary.ContainsKey(array2[0]))
				{
					dictionary.Add(array2[0], array2[1]);
				}
			}
			return dictionary;
		}

		private static void SaveUnlockedLocations(Dictionary<string, string> unlocked)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			Dictionary<string, string> value = Traverse.Create((object)localPlayer).Field("m_customData").GetValue<Dictionary<string, string>>();
			if (value != null)
			{
				value["SoloMapPins.UnlockedLocationIcons"] = string.Join("|", unlocked.Select<KeyValuePair<string, string>, string>((KeyValuePair<string, string> kvp) => kvp.Key + "=" + kvp.Value).ToArray());
			}
		}

		private static bool IsLocationUnlocked(Dictionary<string, string> unlocked, Vector3 point, string? locationName)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (!unlocked.ContainsKey(GetLegacyLocationKey(point)))
			{
				return unlocked.ContainsKey(GetLocationKey(point, locationName));
			}
			return true;
		}

		private static bool AddUnlockedLocation(Dictionary<string, string> unlocked, Vector3 point, string? locationName)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			string locationKey = GetLocationKey(point, locationName);
			if (unlocked.ContainsKey(locationKey))
			{
				return false;
			}
			unlocked[locationKey] = locationName ?? string.Empty;
			DebugLog($"Unlocked personal location icon '{locationName ?? locationKey}' at {point}.");
			return true;
		}

		private static void UnlockLocation(Vector3 point, string? locationName)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, string> unlockedLocations = GetUnlockedLocations();
			if (AddUnlockedLocation(unlockedLocations, point, locationName))
			{
				SaveUnlockedLocations(unlockedLocations);
			}
		}

		private static bool ShouldAlwaysKeepLocationName(object? locationName)
		{
			if (!(locationName is string text) || string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			string[] array = _alwaysKeepLocationNames.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text2 in array)
			{
				if (text.IndexOf(text2.Trim(), StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}

		private static void FilterLocationIcons(Dictionary<Vector3, string> icons)
		{
			//IL_0045: 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)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			if (!_filterGlobalLocationIcons.Value || (Object)(object)Player.m_localPlayer == (Object)null)
			{
				PendingLocations.Clear();
				return;
			}
			Dictionary<string, string> unlockedLocations = GetUnlockedLocations();
			bool flag = false;
			KeyValuePair<Vector3, string>[] array = icons.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<Vector3, string> keyValuePair = array[i];
				string locationKey = GetLocationKey(keyValuePair.Key, keyValuePair.Value);
				if (ShouldAlwaysKeepLocationName(keyValuePair.Value) || IsLocationUnlocked(unlockedLocations, keyValuePair.Key, keyValuePair.Value))
				{
					PendingLocations.Remove(locationKey);
					continue;
				}
				if (ShouldKeepLocationIcon(keyValuePair.Key))
				{
					flag |= AddUnlockedLocation(unlockedLocations, keyValuePair.Key, keyValuePair.Value);
					PendingLocations.Remove(locationKey);
					continue;
				}
				icons.Remove(keyValuePair.Key);
				PendingLocations[locationKey] = new PendingLocation
				{
					Point = keyValuePair.Key,
					Name = keyValuePair.Value
				};
			}
			if (flag)
			{
				SaveUnlockedLocations(unlockedLocations);
			}
		}

		private static void ReevaluatePendingLocations()
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			if (!_filterGlobalLocationIcons.Value || PendingLocations.Count == 0 || (Object)(object)Player.m_localPlayer == (Object)null || Time.unscaledTime < _nextPendingLocationCheck)
			{
				return;
			}
			_nextPendingLocationCheck = Time.unscaledTime + 1f;
			Dictionary<string, string> unlockedLocations = GetUnlockedLocations();
			bool flag = false;
			bool flag2 = false;
			KeyValuePair<string, PendingLocation>[] array = PendingLocations.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<string, PendingLocation> keyValuePair = array[i];
				PendingLocation value = keyValuePair.Value;
				if (ShouldAlwaysKeepLocationName(value.Name) || ShouldKeepLocationIcon(value.Point))
				{
					if (!ShouldAlwaysKeepLocationName(value.Name))
					{
						flag |= AddUnlockedLocation(unlockedLocations, value.Point, value.Name);
					}
					PendingLocations.Remove(keyValuePair.Key);
					flag2 = true;
				}
			}
			if (flag)
			{
				SaveUnlockedLocations(unlockedLocations);
			}
			if (flag2 && (Object)(object)Minimap.instance != (Object)null)
			{
				Traverse.Create((object)Minimap.instance).Field("m_updateLocationsTimer").SetValue((object)0f);
				DebugLog("Revealed pending synced location icons after the local player entered range.");
			}
		}

		private static bool TryGetWorldPoint(object?[] args, out Vector3 point)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//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)
			for (int i = 0; i < args.Length; i++)
			{
				if (args[i] is Vector3 val)
				{
					point = val;
					return true;
				}
			}
			point = default(Vector3);
			return false;
		}
	}
}