Decompiled source of LinkedStations v1.0.1

LinkedStations.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using ConditionalConfigSync;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("LinkedStations")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1")]
[assembly: AssemblyProduct("LinkedStations")]
[assembly: AssemblyTitle("LinkedStations")]
[assembly: AssemblyVersion("1.0.1.0")]
[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 LinkedStations
{
	[BepInPlugin("com.dreich.linkedstations", "Linked Stations", "1.0.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class LinkedStationsPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.dreich.linkedstations";

		public const string PluginName = "Linked Stations";

		public const string PluginVersion = "1.0.1";

		internal const string ConfigSyncGuid = "_shudnal.ConditionalConfigSync";

		private const long ConfigReloadDelayTicks = 5000000L;

		private const long ConfigReloadIgnoreTicks = 20000000L;

		internal static bool ConfigSyncAvailable;

		private static ConfigEntry<bool> lockConfigurationSetting;

		internal static ConfigEntry<bool> LinkCoverage;

		internal static ConfigEntry<bool> LinkDiscovery;

		internal static ConfigEntry<bool> MergedOutline;

		private static Harmony harmony;

		private FileSystemWatcher configFileWatcher;

		private volatile bool isConfigReloadPending;

		private long lastConfigChangeTicks;

		private long ignoreConfigChangesUntilTicks;

		private void Awake()
		{
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Expected O, but got Unknown
			ConfigSyncAvailable = Chainloader.PluginInfos.ContainsKey("_shudnal.ConditionalConfigSync");
			if (ConfigSyncAvailable)
			{
				ConfigSyncCompat.Initialize("com.dreich.linkedstations", "Linked Stations", "1.0.1");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"ConditionalConfigSync not found; configuration will not be synced from the server.");
			}
			lockConfigurationSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Lock Configuration", true, "If on, the configuration is locked and can be changed by server admins only. Requires ConditionalConfigSync.");
			if (ConfigSyncAvailable)
			{
				ConfigSyncCompat.AddLockingConfigEntry(lockConfigurationSetting);
			}
			LinkCoverage = BindServerControlled("General", "LinkCoverage", defaultValue: true, "Any piece needing a station counts as in range anywhere inside the combined coverage.");
			LinkDiscovery = BindServerControlled("General", "LinkDiscovery", defaultValue: true, "Standing inside a linked group's coverage also discovers every station in that group, so pieces requiring them can be built.");
			MergedOutline = BindClientControlled("General", "MergedOutline", defaultValue: true, "Draw one merged outline for a linked group instead of a separate circle per station.");
			StartWatchingConfigFile();
			harmony = new Harmony("com.dreich.linkedstations");
			harmony.PatchAll();
		}

		private ConfigEntry<T> BindServerControlled<T>(string section, string key, T defaultValue, string description)
		{
			ConfigEntry<T> val = ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, description);
			if (ConfigSyncAvailable)
			{
				ConfigSyncCompat.AddConfigEntry<T>(val, serverControlled: true);
			}
			return val;
		}

		private ConfigEntry<T> BindClientControlled<T>(string section, string key, T defaultValue, string description)
		{
			ConfigEntry<T> val = ((BaseUnityPlugin)this).Config.Bind<T>(section, key, defaultValue, description);
			if (ConfigSyncAvailable)
			{
				ConfigSyncCompat.AddConfigEntry<T>(val, serverControlled: false);
			}
			return val;
		}

		private void StartWatchingConfigFile()
		{
			try
			{
				string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
				configFileWatcher = new FileSystemWatcher(Path.GetDirectoryName(configFilePath), Path.GetFileName(configFilePath));
				configFileWatcher.NotifyFilter = NotifyFilters.Size | NotifyFilters.LastWrite;
				configFileWatcher.Changed += OnConfigFileChanged;
				configFileWatcher.EnableRaisingEvents = true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Config hot reload unavailable: " + ex.Message));
			}
		}

		private void OnConfigFileChanged(object sender, FileSystemEventArgs e)
		{
			long ticks = DateTime.UtcNow.Ticks;
			if (ticks >= Interlocked.Read(in ignoreConfigChangesUntilTicks))
			{
				Interlocked.Exchange(ref lastConfigChangeTicks, ticks);
				isConfigReloadPending = true;
			}
		}

		private void Update()
		{
			if (!isConfigReloadPending || DateTime.UtcNow.Ticks - Interlocked.Read(in lastConfigChangeTicks) <= 5000000)
			{
				return;
			}
			isConfigReloadPending = false;
			Interlocked.Exchange(ref ignoreConfigChangesUntilTicks, DateTime.UtcNow.Ticks + 20000000);
			try
			{
				((BaseUnityPlugin)this).Config.Reload();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Config reloaded.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("Config reload failed: " + ex.Message));
			}
		}

		private void OnDestroy()
		{
			if (configFileWatcher != null)
			{
				configFileWatcher.EnableRaisingEvents = false;
				configFileWatcher.Dispose();
			}
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			harmony = null;
			MergedOutlineDisplay.DestroyInstance();
			StationGroups.Reset();
		}
	}
	internal static class ConfigSyncCompat
	{
		private static object configSync;

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static void Initialize(string guid, string name, string version)
		{
			//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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: 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_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			configSync = (object)new ConfigSync(guid)
			{
				DisplayName = name,
				CurrentVersion = version,
				MinimumRequiredVersion = version,
				ModRequired = false,
				ModRequirementMode = (ModRequirementMode)1
			};
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static void AddLockingConfigEntry(ConfigEntry<bool> entry)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			((ConditionalConfigSync)(ConfigSync)configSync).AddLockingConfigEntry<bool>(entry);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static void AddConfigEntry<T>(ConfigEntry<T> entry, bool serverControlled)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			((ConditionalConfigSync)(ConfigSync)configSync).AddConfigEntry<T>(entry, (ConfigSyncMode)((!serverControlled) ? 2 : 0));
		}
	}
	internal static class StationGroups
	{
		private const float RebuildIntervalSeconds = 1f;

		private static readonly FieldInfo allStationsField = AccessTools.Field(typeof(CraftingStation), "m_allStations");

		private static readonly Dictionary<CraftingStation, List<CraftingStation>> groupByStation = new Dictionary<CraftingStation, List<CraftingStation>>();

		private static readonly List<CraftingStation> emptyStations = new List<CraftingStation>();

		private static float lastRebuildTime = -100f;

		private static int stationCountAtLastRebuild = -1;

		public static void Reset()
		{
			groupByStation.Clear();
			lastRebuildTime = -100f;
			stationCountAtLastRebuild = -1;
		}

		public static List<CraftingStation> GetGroupOf(CraftingStation station)
		{
			RebuildIfStale();
			if (!groupByStation.TryGetValue(station, out var value))
			{
				return null;
			}
			return value;
		}

		public static IEnumerable<List<CraftingStation>> GetAllGroups()
		{
			RebuildIfStale();
			HashSet<List<CraftingStation>> alreadyReturned = new HashSet<List<CraftingStation>>();
			foreach (List<CraftingStation> value in groupByStation.Values)
			{
				if (alreadyReturned.Add(value))
				{
					yield return value;
				}
			}
		}

		public static float GetBuildRange(CraftingStation station)
		{
			return station.GetStationBuildRange();
		}

		public static float GetHorizontalDistance(Vector3 point, CraftingStation station)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: 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_001a: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)station).transform.position;
			point.y = position.y;
			return Vector3.Distance(point, position);
		}

		public static bool IsInBuildRange(Vector3 point, CraftingStation station)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			return GetHorizontalDistance(point, station) < GetBuildRange(station);
		}

		public static bool IsInsideAnyStationOf(Vector3 point, List<CraftingStation> group)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			foreach (CraftingStation item in group)
			{
				if ((Object)(object)item != (Object)null && IsInBuildRange(point, item))
				{
					return true;
				}
			}
			return false;
		}

		private static List<CraftingStation> GetAllStations()
		{
			return (allStationsField?.GetValue(null) as List<CraftingStation>) ?? emptyStations;
		}

		private static bool AreStationsLinked(CraftingStation a, CraftingStation b)
		{
			//IL_0006: 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)
			if (!IsInBuildRange(((Component)a).transform.position, b))
			{
				return IsInBuildRange(((Component)b).transform.position, a);
			}
			return true;
		}

		private static void RebuildIfStale()
		{
			List<CraftingStation> allStations = GetAllStations();
			if (allStations.Count == stationCountAtLastRebuild && !(Time.unscaledTime - lastRebuildTime >= 1f))
			{
				return;
			}
			lastRebuildTime = Time.unscaledTime;
			stationCountAtLastRebuild = allStations.Count;
			groupByStation.Clear();
			int count = allStations.Count;
			int[] parentOf = new int[count];
			for (int i = 0; i < count; i++)
			{
				parentOf[i] = i;
			}
			for (int j = 0; j < count; j++)
			{
				if ((Object)(object)allStations[j] == (Object)null)
				{
					continue;
				}
				for (int k = j + 1; k < count; k++)
				{
					if (!((Object)(object)allStations[k] == (Object)null) && AreStationsLinked(allStations[j], allStations[k]))
					{
						parentOf[FindRoot(j)] = FindRoot(k);
					}
				}
			}
			Dictionary<int, List<CraftingStation>> dictionary = new Dictionary<int, List<CraftingStation>>();
			for (int l = 0; l < count; l++)
			{
				if (!((Object)(object)allStations[l] == (Object)null))
				{
					int key = FindRoot(l);
					if (!dictionary.TryGetValue(key, out var value))
					{
						value = (dictionary[key] = new List<CraftingStation>());
					}
					value.Add(allStations[l]);
				}
			}
			foreach (List<CraftingStation> value2 in dictionary.Values)
			{
				foreach (CraftingStation item in value2)
				{
					groupByStation[item] = value2;
				}
			}
			int FindRoot(int index)
			{
				while (parentOf[index] != index)
				{
					parentOf[index] = parentOf[parentOf[index]];
					index = parentOf[index];
				}
				return index;
			}
		}
	}
	[HarmonyPatch(typeof(CraftingStation), "HaveBuildStationInRange")]
	internal static class LinkedCoveragePatch
	{
		private static void Postfix(string name, Vector3 point, ref CraftingStation __result)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__result != (Object)null || !LinkedStationsPlugin.LinkCoverage.Value)
			{
				return;
			}
			foreach (List<CraftingStation> allGroup in StationGroups.GetAllGroups())
			{
				if (allGroup.Count >= 2)
				{
					CraftingStation val = FindStationByName(allGroup, name);
					if (!((Object)(object)val == (Object)null) && StationGroups.IsInsideAnyStationOf(point, allGroup))
					{
						__result = val;
						break;
					}
				}
			}
		}

		private static CraftingStation FindStationByName(List<CraftingStation> group, string stationName)
		{
			foreach (CraftingStation item in group)
			{
				if ((Object)(object)item != (Object)null && item.m_name == stationName)
				{
					return item;
				}
			}
			return null;
		}
	}
	[HarmonyPatch(typeof(CraftingStation), "UpdateKnownStationsInRange")]
	internal static class LinkedDiscoveryPatch
	{
		private static void Postfix(Player player)
		{
			//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)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer || !LinkedStationsPlugin.LinkDiscovery.Value)
			{
				return;
			}
			Vector3 position = ((Component)player).transform.position;
			foreach (List<CraftingStation> allGroup in StationGroups.GetAllGroups())
			{
				if (allGroup.Count < 2 || !StationGroups.IsInsideAnyStationOf(position, allGroup))
				{
					continue;
				}
				foreach (CraftingStation item in allGroup)
				{
					if ((Object)(object)item != (Object)null)
					{
						player.AddKnownStation(item);
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(CraftingStation), "ShowAreaMarker")]
	internal static class MergedOutlinePatch
	{
		private static readonly FieldInfo areaMarkerCircleField = AccessTools.Field(typeof(CraftingStation), "m_areaMarkerCircle");

		private static CircleProjector GetAreaMarkerCircle(CraftingStation station)
		{
			object? obj = areaMarkerCircleField?.GetValue(station);
			return (CircleProjector)((obj is CircleProjector) ? obj : null);
		}

		private static bool Prefix(CraftingStation __instance)
		{
			if (!LinkedStationsPlugin.MergedOutline.Value)
			{
				return true;
			}
			List<CraftingStation> groupOf = StationGroups.GetGroupOf(__instance);
			if (groupOf == null || groupOf.Count < 2)
			{
				return true;
			}
			CircleProjector val = FindCircleTemplate(groupOf);
			if ((Object)(object)val == (Object)null || (Object)(object)val.m_prefab == (Object)null)
			{
				return true;
			}
			MergedOutlineDisplay.Show(groupOf, val);
			return false;
		}

		private static CircleProjector FindCircleTemplate(List<CraftingStation> group)
		{
			foreach (CraftingStation item in group)
			{
				CircleProjector val = (((Object)(object)item != (Object)null) ? GetAreaMarkerCircle(item) : null);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return null;
		}
	}
	internal class MergedOutlineDisplay : MonoBehaviour
	{
		private const float HideAfterSeconds = 0.5f;

		private const float MinimumTemplateRadius = 1f;

		private const float FallbackSegmentSpacing = 1.5f;

		private const int MinimumSegmentsPerCircle = 8;

		private const float AlreadyCoveredTolerance = 0.01f;

		private static MergedOutlineDisplay instance;

		private readonly List<GameObject> segments = new List<GameObject>();

		private GameObject segmentContainer;

		private GameObject segmentPrefab;

		private float lastShownTime;

		public static void DestroyInstance()
		{
			if (!((Object)(object)instance == (Object)null))
			{
				Object.Destroy((Object)(object)((Component)instance).gameObject);
				instance = null;
			}
		}

		public static void Show(List<CraftingStation> group, CircleProjector circleTemplate)
		{
			if ((Object)(object)instance == (Object)null)
			{
				instance = CreateInstance();
			}
			instance.UpdateSegments(group, circleTemplate);
		}

		private static MergedOutlineDisplay CreateInstance()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			GameObject val = new GameObject("LinkedStationsMarker");
			MergedOutlineDisplay mergedOutlineDisplay = val.AddComponent<MergedOutlineDisplay>();
			mergedOutlineDisplay.segmentContainer = new GameObject("Segments");
			mergedOutlineDisplay.segmentContainer.transform.SetParent(val.transform, false);
			return mergedOutlineDisplay;
		}

		private void UpdateSegments(List<CraftingStation> group, CircleProjector circleTemplate)
		{
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0134: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			lastShownTime = Time.time;
			segmentContainer.SetActive(true);
			if ((Object)(object)segmentPrefab != (Object)(object)circleTemplate.m_prefab)
			{
				segmentPrefab = circleTemplate.m_prefab;
				DestroyAllSegments();
			}
			float num = Mathf.Max(circleTemplate.m_radius, 1f);
			float num2 = ((circleTemplate.m_nrOfSegments > 0) ? ((float)Math.PI * 2f * num / (float)circleTemplate.m_nrOfSegments) : 1.5f);
			float num3 = circleTemplate.m_speed * num;
			float num4 = circleTemplate.m_start * ((float)Math.PI / 180f);
			int usedSegmentCount = 0;
			foreach (CraftingStation item in group)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				float buildRange = StationGroups.GetBuildRange(item);
				if (buildRange <= 0f)
				{
					continue;
				}
				Vector3 position = ((Component)item).transform.position;
				int num5 = Mathf.Max(8, Mathf.CeilToInt((float)Math.PI * 2f * buildRange / num2));
				float num6 = (float)Math.PI * 2f / (float)num5;
				float num7 = num4 + Time.time * num3 / buildRange;
				for (int i = 0; i < num5; i++)
				{
					float num8 = num7 + (float)i * num6;
					Vector3 val = position + new Vector3(Mathf.Sin(num8) * buildRange, 0f, Mathf.Cos(num8) * buildRange);
					if (!IsInsideAnotherStation(val, item, group))
					{
						if ((Object)(object)ZoneSystem.instance != (Object)null)
						{
							val.y = ZoneSystem.instance.GetSolidHeight(val);
						}
						Transform transform = GetOrCreateSegment(usedSegmentCount++).transform;
						transform.position = val;
						transform.rotation = Quaternion.LookRotation(new Vector3(Mathf.Cos(num8), 0f, 0f - Mathf.Sin(num8)), Vector3.up);
					}
				}
			}
			HideUnusedSegments(usedSegmentCount);
		}

		private static bool IsInsideAnotherStation(Vector3 point, CraftingStation ownStation, List<CraftingStation> group)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			foreach (CraftingStation item in group)
			{
				if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)ownStation) && StationGroups.GetHorizontalDistance(point, item) < StationGroups.GetBuildRange(item) - 0.01f)
				{
					return true;
				}
			}
			return false;
		}

		private GameObject GetOrCreateSegment(int index)
		{
			//IL_000e: 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)
			while (segments.Count <= index)
			{
				segments.Add(Object.Instantiate<GameObject>(segmentPrefab, Vector3.zero, Quaternion.identity, segmentContainer.transform));
			}
			GameObject val = segments[index];
			if (!val.activeSelf)
			{
				val.SetActive(true);
			}
			return val;
		}

		private void HideUnusedSegments(int usedSegmentCount)
		{
			for (int i = usedSegmentCount; i < segments.Count; i++)
			{
				if ((Object)(object)segments[i] != (Object)null && segments[i].activeSelf)
				{
					segments[i].SetActive(false);
				}
			}
		}

		private void DestroyAllSegments()
		{
			foreach (GameObject segment in segments)
			{
				if ((Object)(object)segment != (Object)null)
				{
					Object.Destroy((Object)(object)segment);
				}
			}
			segments.Clear();
		}

		private void Update()
		{
			if ((Object)(object)segmentContainer != (Object)null && segmentContainer.activeSelf && Time.time - lastShownTime > 0.5f)
			{
				segmentContainer.SetActive(false);
			}
		}
	}
}