Decompiled source of StationLimitsFixer v1.4.1

StationLimitsFixer.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
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: AssemblyTitle("StationLimitsFixer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("StationLimitsFixer")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("29ed45d8-f11d-4a1c-a98a-2abd302332ef")]
[assembly: AssemblyFileVersion("1.2.2.0")]
[assembly: AssemblyVersion("1.2.2.0")]
namespace StationLimitsFixer;

[BepInPlugin("com.custom.stationlimits", "Station Limits Fixer", "1.4.1")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class StationFixerPlugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	public static class ZNetScene_Patch
	{
		private static void Postfix()
		{
			ApplyChanges(updateActiveSceneObjects: false);
		}
	}

	[HarmonyPatch(typeof(Player), "UpdatePlacementGhost")]
	public static class BruteForcePlacementPatch
	{
		private delegate void SetPlacementGhostValid_Bool_Delegate(Player instance, bool isValid);

		private delegate void SetPlacementGhostValid_Void_Delegate(Player instance);

		private delegate void SetInvalidPlacementHighlight_Delegate(Piece instance, bool invalid);

		private static SetPlacementGhostValid_Bool_Delegate SetGhostValidBool;

		private static SetPlacementGhostValid_Void_Delegate SetGhostValidVoid;

		private static SetInvalidPlacementHighlight_Delegate SetHighlight;

		private static readonly HashSet<string> bulkyPieces = new HashSet<string> { "$piece_smelter", "$piece_charcoalkiln", "$piece_blastfurnace", "$piece_windmill" };

		[HarmonyPrepare]
		private static void Prepare()
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(Player), "SetPlacementGhostValid", new Type[1] { typeof(bool) }, (Type[])null);
			if (methodInfo != null)
			{
				SetGhostValidBool = AccessTools.MethodDelegate<SetPlacementGhostValid_Bool_Delegate>(methodInfo, (object)null, true);
			}
			else
			{
				MethodInfo methodInfo2 = AccessTools.Method(typeof(Player), "SetPlacementGhostValid", new Type[0], (Type[])null);
				if (methodInfo2 != null)
				{
					SetGhostValidVoid = AccessTools.MethodDelegate<SetPlacementGhostValid_Void_Delegate>(methodInfo2, (object)null, true);
				}
			}
			MethodInfo methodInfo3 = AccessTools.Method(typeof(Piece), "SetInvalidPlacementHeightlight", new Type[1] { typeof(bool) }, (Type[])null);
			if (methodInfo3 != null)
			{
				SetHighlight = AccessTools.MethodDelegate<SetInvalidPlacementHighlight_Delegate>(methodInfo3, (object)null, true);
			}
		}

		public static void Postfix(Player __instance, ref GameObject ___m_placementGhost, ref PlacementStatus ___m_placementStatus)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			if (!BruteForceSmelters.Value || (Object)(object)___m_placementGhost == (Object)null)
			{
				return;
			}
			Piece component = ___m_placementGhost.GetComponent<Piece>();
			RaycastHit val = default(RaycastHit);
			if (!((Object)(object)component != (Object)null) || !bulkyPieces.Contains(component.m_name) || !PrivateArea.CheckAccess(___m_placementGhost.transform.position, 0f, true, false) || (!AllowSmeltersOnWood.Value && component.m_groundOnly && Physics.Raycast(___m_placementGhost.transform.position + Vector3.up, Vector3.down, ref val, 2f, LayerMask.GetMask(new string[3] { "piece", "Default", "static_solid" })) && (Object)(object)((Component)((RaycastHit)(ref val)).collider).GetComponentInParent<Piece>() != (Object)null))
			{
				return;
			}
			try
			{
				___m_placementStatus = (PlacementStatus)0;
				if (SetGhostValidBool != null)
				{
					SetGhostValidBool(__instance, isValid: true);
				}
				else if (SetGhostValidVoid != null)
				{
					SetGhostValidVoid(__instance);
				}
				if (SetHighlight != null)
				{
					SetHighlight(component, invalid: false);
				}
			}
			catch (Exception ex)
			{
				StaticLogger.LogError((object)("[Station Limits Fixer] Failed to override placement for " + component.m_name + ": " + ex.Message));
			}
		}
	}

	[HarmonyPatch(typeof(CraftingStation), "Interact")]
	public static class ForceRecipeScanPatch
	{
		private delegate void UpdateKnownRecipes_Delegate(Player instance);

		private static UpdateKnownRecipes_Delegate UpdateRecipes;

		[HarmonyPrepare]
		private static void Prepare()
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(Player), "UpdateKnownRecipesList", (Type[])null, (Type[])null);
			if (methodInfo != null)
			{
				UpdateRecipes = AccessTools.MethodDelegate<UpdateKnownRecipes_Delegate>(methodInfo, (object)null, true);
			}
		}

		public static void Prefix(Humanoid user)
		{
			if (!AutoScanRecipes.Value || !((Object)(object)user != (Object)null) || !((Object)(object)user == (Object)(object)Player.m_localPlayer) || UpdateRecipes == null)
			{
				return;
			}
			try
			{
				UpdateRecipes(Player.m_localPlayer);
			}
			catch (Exception ex)
			{
				StaticLogger.LogError((object)("[Station Limits Fixer] Failed to force recipe scan: " + ex.Message));
			}
		}
	}

	[CompilerGenerated]
	private sealed class <DebouncedApplyChanges>d__15 : IEnumerator<object>, IDisposable, IEnumerator
	{
		private int <>1__state;

		private object <>2__current;

		public StationFixerPlugin <>4__this;

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

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

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

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

		private bool MoveNext()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			int num = <>1__state;
			StationFixerPlugin stationFixerPlugin = <>4__this;
			switch (num)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<>2__current = (object)new WaitForSeconds(0.5f);
				<>1__state = 1;
				return true;
			case 1:
				<>1__state = -1;
				ApplyChanges(updateActiveSceneObjects: true);
				stationFixerPlugin._applyChangesCoroutine = null;
				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();
		}
	}

	public static ConfigEntry<float> HitboxSize;

	public static ConfigEntry<float> MaxConnectionDistance;

	public static ConfigEntry<bool> RemoveRoofRequirement;

	public static ConfigEntry<bool> BruteForceSmelters;

	public static ConfigEntry<string> ExcludedHitboxPieces;

	public static ConfigEntry<bool> AllowSmeltersOnWood;

	public static ConfigEntry<bool> AutoScanRecipes;

	internal static ManualLogSource StaticLogger;

	private Coroutine _applyChangesCoroutine;

	private static Dictionary<Collider, Vector3> originalBoxSizes = new Dictionary<Collider, Vector3>();

	private static Dictionary<Collider, Vector2> originalCapsuleSizes = new Dictionary<Collider, Vector2>();

	private static Dictionary<Collider, float> originalSphereRadii = new Dictionary<Collider, float>();

	private static bool _azuWorkbenchPresent;

	private void Awake()
	{
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		StaticLogger = ((BaseUnityPlugin)this).Logger;
		_azuWorkbenchPresent = Chainloader.PluginInfos.ContainsKey("Azumatt.AzuWorkbenchTweaks");
		if (_azuWorkbenchPresent)
		{
			StaticLogger.LogWarning((object)"AzuWorkbenchTweaks detected. Skipping MaxConnectionDistance and RemoveRoofRequirement to avoid conflicts.");
		}
		HitboxSize = ((BaseUnityPlugin)this).Config.Bind<float>("General", "ImprovementHitboxSize", 0.6f, "Size of the physical footprint. 0.1 is tiny.");
		AutoScanRecipes = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "AutoScanRecipes", true, "If true, automatically scans for newly unlocked recipes every time you open a crafting station.");
		BruteForceSmelters = ((BaseUnityPlugin)this).Config.Bind<bool>("Advanced", "ForceSmelterPlacement", true, "If true, Smelters, Kilns, etc. can be placed on uneven terrain.");
		AllowSmeltersOnWood = ((BaseUnityPlugin)this).Config.Bind<bool>("Advanced", "AllowSmeltersOnWood", false, "If true, allows placing heavy Smelters on wooden floors.");
		ExcludedHitboxPieces = ((BaseUnityPlugin)this).Config.Bind<string>("Advanced", "ExcludedHitboxPieces", "blackforge_ext1,piece_workbench_ext2", "Comma-separated list of prefab names to skip when shrinking hitboxes. Applies on load.");
		HitboxSize.SettingChanged += delegate
		{
			RequestApplyChanges();
		};
		if (!_azuWorkbenchPresent)
		{
			MaxConnectionDistance = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MaxRange", 25f, "Maximum distance between a crafting station and its improvements.");
			RemoveRoofRequirement = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "NoRoofRequired", false, "If true, main crafting stations work even without a roof or in the rain.");
			MaxConnectionDistance.SettingChanged += delegate
			{
				RequestApplyChanges();
			};
			RemoveRoofRequirement.SettingChanged += delegate
			{
				RequestApplyChanges();
			};
		}
		new Harmony("com.custom.stationlimits").PatchAll();
		StaticLogger.LogInfo((object)"Station Limits Fixer initialized.");
	}

	private void RequestApplyChanges()
	{
		if (_applyChangesCoroutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(_applyChangesCoroutine);
		}
		_applyChangesCoroutine = ((MonoBehaviour)this).StartCoroutine(DebouncedApplyChanges());
	}

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

	public static void ApplyChanges(bool updateActiveSceneObjects)
	{
		//IL_011f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0126: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0178: Unknown result type (might be due to invalid IL or missing references)
		//IL_0193: Unknown result type (might be due to invalid IL or missing references)
		//IL_0165: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)ZNetScene.instance == (Object)null)
		{
			return;
		}
		int num = 0;
		HashSet<string> hashSet = new HashSet<string>(ExcludedHitboxPieces.Value.Replace(" ", "").Split(new char[1] { ',' }));
		CraftingStation val = default(CraftingStation);
		StationExtension val2 = default(StationExtension);
		Piece val3 = default(Piece);
		foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
		{
			prefab.TryGetComponent<CraftingStation>(ref val);
			prefab.TryGetComponent<StationExtension>(ref val2);
			if ((Object)(object)val == (Object)null && (Object)(object)val2 == (Object)null)
			{
				continue;
			}
			if (prefab.TryGetComponent<Piece>(ref val3))
			{
				val3.m_spaceRequirement = 0f;
			}
			if ((Object)(object)val2 != (Object)null && !hashSet.Contains(((Object)prefab).name))
			{
				Collider[] componentsInChildren = prefab.GetComponentsInChildren<Collider>();
				foreach (Collider val4 in componentsInChildren)
				{
					if (val4.isTrigger)
					{
						continue;
					}
					float value = HitboxSize.Value;
					BoxCollider val5 = (BoxCollider)(object)((val4 is BoxCollider) ? val4 : null);
					if (val5 != null)
					{
						if (!originalBoxSizes.ContainsKey((Collider)(object)val5))
						{
							originalBoxSizes[(Collider)(object)val5] = val5.size;
						}
						val5.size = originalBoxSizes[(Collider)(object)val5] * value;
						continue;
					}
					CapsuleCollider val6 = (CapsuleCollider)(object)((val4 is CapsuleCollider) ? val4 : null);
					if (val6 != null)
					{
						if (!originalCapsuleSizes.ContainsKey((Collider)(object)val6))
						{
							originalCapsuleSizes[(Collider)(object)val6] = new Vector2(val6.radius, val6.height);
						}
						val6.radius = originalCapsuleSizes[(Collider)(object)val6].x * value;
						val6.height = originalCapsuleSizes[(Collider)(object)val6].y * value;
						continue;
					}
					SphereCollider val7 = (SphereCollider)(object)((val4 is SphereCollider) ? val4 : null);
					if (val7 != null)
					{
						if (!originalSphereRadii.ContainsKey((Collider)(object)val7))
						{
							originalSphereRadii[(Collider)(object)val7] = val7.radius;
						}
						val7.radius = originalSphereRadii[(Collider)(object)val7] * value;
					}
				}
			}
			if ((Object)(object)val2 != (Object)null)
			{
				if (!_azuWorkbenchPresent)
				{
					val2.m_maxStationDistance = MaxConnectionDistance.Value;
				}
				val2.m_continousConnection = true;
			}
			if ((Object)(object)val != (Object)null && !_azuWorkbenchPresent)
			{
				val.m_craftRequireRoof = !RemoveRoofRequirement.Value;
			}
			num++;
		}
		if (updateActiveSceneObjects)
		{
			StationExtension[] array = Object.FindObjectsByType<StationExtension>((FindObjectsSortMode)0);
			foreach (StationExtension val8 in array)
			{
				if (!_azuWorkbenchPresent)
				{
					val8.m_maxStationDistance = MaxConnectionDistance.Value;
				}
				val8.m_continousConnection = true;
			}
			if (!_azuWorkbenchPresent)
			{
				CraftingStation[] array2 = Object.FindObjectsByType<CraftingStation>((FindObjectsSortMode)0);
				for (int i = 0; i < array2.Length; i++)
				{
					array2[i].m_craftRequireRoof = !RemoveRoofRequirement.Value;
				}
			}
		}
		StaticLogger.LogInfo((object)$"Successfully patched {num} prefabs.");
	}
}