Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of LinkedStations v1.0.1
LinkedStations.dll
Decompiled a day agousing 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); } } } }