Decompiled source of DSPCalculatorPlus v1.0.0

plugins\DSPCalculatorPlus.dll

Decompiled a day 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 System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("DSPCalculatorPlus")]
[assembly: AssemblyDescription("Dyson Sphere Program BepInEx mod.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DSPCalculatorPlus")]
[assembly: AssemblyCopyright("Copyright (c) 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("00000000-0000-0000-0000-000000000000")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = "")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace DSPCalculatorPlus;

internal static class DSPCalculatorPlusLog
{
	private const string Tag = "[DSPCalculatorPlus] ";

	private static ManualLogSource _logger;

	private static bool _debugEnabled;

	public static void Init(ManualLogSource src, ConfigEntry<bool> debug)
	{
		_logger = src;
		_debugEnabled = debug.Value;
		debug.SettingChanged += delegate
		{
			_debugEnabled = debug.Value;
		};
	}

	public static void Info(string msg)
	{
		if (_debugEnabled && _logger != null)
		{
			_logger.LogInfo((object)("[DSPCalculatorPlus] " + msg));
		}
	}

	public static void Warn(string msg)
	{
		ManualLogSource logger = _logger;
		if (logger != null)
		{
			logger.LogWarning((object)("[DSPCalculatorPlus] " + msg));
		}
	}

	public static void Error(string msg)
	{
		ManualLogSource logger = _logger;
		if (logger != null)
		{
			logger.LogError((object)("[DSPCalculatorPlus] " + msg));
		}
	}
}
internal static class OverflowFixPatch
{
	private static FieldInfo _fSolution;

	private static FieldInfo _fItemSumInfos;

	private static FieldInfo _fBeltsAvailable;

	private static FieldInfo _fUserPreference;

	private static FieldInfo _fTargets;

	private static FieldInfo _fItemConfigs;

	private static FieldInfo _fConsideredAsOre;

	private static ConstructorInfo _ctorItemConfig;

	private static FieldInfo _fSumNeedSpeed;

	private static FieldInfo _fSumItemId;

	private static FieldInfo _fSumNeedBeltId;

	private static FieldInfo _fBeltSpeed;

	private static FieldInfo _fBeltItemId;

	private static FieldInfo _fTargetItemId;

	private static FieldInfo _fTargetSpeed;

	private static FieldInfo _fForceNotOre;

	private static FieldInfo _fBpStackSetting;

	private static FieldInfo _fMaxStackSize;

	private static MethodInfo _mReSolve;

	private static MethodInfo _mGenerateFull;

	private static bool _ready;

	private static readonly HashSet<int> _pendingExternal = new HashSet<int>();

	private static readonly Dictionary<int, bool> _restoreOreValue = new Dictionary<int, bool>();

	private static readonly Dictionary<int, bool> _restoreForceNotOre = new Dictionary<int, bool>();

	private static readonly HashSet<int> _createdConfigs = new HashSet<int>();

	private static bool _inRetry;

	private static bool _finalizeUnderProvision;

	internal static Dictionary<int, int> LastRunLaneCounts = new Dictionary<int, int>();

	public static void Apply(Harmony harmony)
	{
		//IL_059f: Unknown result type (might be due to invalid IL or missing references)
		//IL_05ad: Expected O, but got Unknown
		//IL_040d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0412: Unknown result type (might be due to invalid IL or missing references)
		//IL_0425: Expected O, but got Unknown
		//IL_043c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0451: Unknown result type (might be due to invalid IL or missing references)
		//IL_0456: Unknown result type (might be due to invalid IL or missing references)
		//IL_0469: Expected O, but got Unknown
		//IL_0469: Expected O, but got Unknown
		//IL_04e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_04ed: Expected O, but got Unknown
		Type type = AccessTools.TypeByName("DSPCalculator.Bp.BpConnector");
		if (type == null)
		{
			DSPCalculatorPlusLog.Warn("[overflow] BpConnector not found - overflow fix disabled.");
			return;
		}
		MethodInfo methodInfo = AccessTools.Method(type, "CalcItemSumInfos", (Type[])null, (Type[])null);
		_mGenerateFull = AccessTools.Method(type, "GenerateFullBlueprint", (Type[])null, (Type[])null);
		if (methodInfo == null || _mGenerateFull == null)
		{
			DSPCalculatorPlusLog.Warn("[overflow] CalcItemSumInfos/GenerateFullBlueprint not found - overflow fix disabled.");
			return;
		}
		Type type2 = AccessTools.TypeByName("DSPCalculator.Bp.BpItemSumInfo");
		Type type3 = AccessTools.TypeByName("DSPCalculator.Bp.BpBeltInfo");
		Type type4 = AccessTools.TypeByName("DSPCalculator.Logic.ItemConfig");
		Type type5 = AccessTools.TypeByName("DSPCalculator.Logic.ItemTarget");
		_fSolution = AccessTools.Field(type, "solution");
		Type type6 = _fSolution?.FieldType;
		_fItemSumInfos = AccessTools.Field(type, "itemSumInfos");
		_fBeltsAvailable = ((type6 != null) ? AccessTools.Field(type6, "beltsAvailable") : null);
		_fUserPreference = ((type6 != null) ? AccessTools.Field(type6, "userPreference") : null);
		_fTargets = ((type6 != null) ? AccessTools.Field(type6, "targets") : null);
		_mReSolve = ((type6 != null) ? AccessTools.Method(type6, "ReSolve", new Type[1] { typeof(double) }, (Type[])null) : null);
		Type type7 = _fUserPreference?.FieldType;
		_fItemConfigs = ((type7 != null) ? AccessTools.Field(type7, "itemConfigs") : null);
		_fConsideredAsOre = ((type4 != null) ? AccessTools.Field(type4, "consideredAsOre") : null);
		_fForceNotOre = ((type4 != null) ? AccessTools.Field(type4, "forceNotOre") : null);
		_fBpStackSetting = ((type7 != null) ? AccessTools.Field(type7, "bpStackSetting") : null);
		Type type8 = AccessTools.TypeByName("DSPCalculator.Logic.CalcDB");
		_fMaxStackSize = ((type8 != null) ? AccessTools.Field(type8, "maxStackSize") : null);
		_ctorItemConfig = ((type4 != null) ? AccessTools.Constructor(type4, new Type[1] { typeof(int) }, false) : null);
		_fSumNeedSpeed = ((type2 != null) ? AccessTools.Field(type2, "needBeltSpeed") : null);
		_fSumItemId = ((type2 != null) ? AccessTools.Field(type2, "itemId") : null);
		_fSumNeedBeltId = ((type2 != null) ? AccessTools.Field(type2, "needBeltId") : null);
		_fBeltSpeed = ((type3 != null) ? AccessTools.Field(type3, "speedPerMin") : null);
		_fBeltItemId = ((type3 != null) ? AccessTools.Field(type3, "itemId") : null);
		_fTargetItemId = ((type5 != null) ? AccessTools.Field(type5, "itemId") : null);
		_fTargetSpeed = ((type5 != null) ? AccessTools.Field(type5, "speed") : null);
		if (_fSolution == null || _fItemSumInfos == null || _fBeltsAvailable == null || _fUserPreference == null || _fTargets == null || _mReSolve == null || _fItemConfigs == null || _fConsideredAsOre == null || _fForceNotOre == null || _ctorItemConfig == null || _fSumNeedSpeed == null || _fSumItemId == null || _fSumNeedBeltId == null || _fBeltSpeed == null || _fBeltItemId == null || _fTargetItemId == null || _fTargetSpeed == null)
		{
			DSPCalculatorPlusLog.Error("[overflow] one or more DSPCalculator members not found - overflow fix disabled.");
			return;
		}
		_ready = true;
		harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(OverflowFixPatch), "CalcPostfix", (Type[])null)
		{
			priority = 200
		}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		harmony.Patch((MethodBase)_mGenerateFull, new HarmonyMethod(typeof(OverflowFixPatch), "GenPrefix", (Type[])null), new HarmonyMethod(typeof(OverflowFixPatch), "GenPostfix", (Type[])null)
		{
			priority = 200
		}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		string[] array = new string[4] { "GenProcessors", "ArrangeBpBlocks", "PlaceBuildings", "ConnectBlocks" };
		foreach (string text in array)
		{
			MethodInfo methodInfo2 = AccessTools.Method(type, text, (Type[])null, (Type[])null);
			if (methodInfo2 != null && methodInfo2.ReturnType == typeof(bool))
			{
				harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(OverflowFixPatch), "StagePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}
		Type type9 = AccessTools.TypeByName("UIMessageBox");
		if (type9 != null)
		{
			int num = 0;
			MethodInfo[] methods = type9.GetMethods(BindingFlags.Static | BindingFlags.Public);
			foreach (MethodInfo methodInfo3 in methods)
			{
				if (methodInfo3.Name != "Show")
				{
					continue;
				}
				ParameterInfo[] parameters = methodInfo3.GetParameters();
				if (parameters.Length >= 2 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(string))
				{
					try
					{
						harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(OverflowFixPatch), "MsgBoxPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
					catch (Exception ex)
					{
						DSPCalculatorPlusLog.Warn("[overflow] could not patch a UIMessageBox.Show overload: " + ex.Message);
					}
				}
			}
			DSPCalculatorPlusLog.Info("[overflow] diagnostic: patched " + num + " UIMessageBox.Show overload(s).");
		}
		else
		{
			DSPCalculatorPlusLog.Warn("[overflow] UIMessageBox type not found - failure-dialog diagnostic unavailable.");
		}
		DSPCalculatorPlusLog.Info("[overflow] Group B (external-input) patch installed on CalcItemSumInfos + GenerateFullBlueprint.");
	}

	private static bool MsgBoxPrefix(string __0, string __1, ref object __result)
	{
		if (!_inRetry)
		{
			return true;
		}
		DSPCalculatorPlusLog.Info("[overflow][diag] DSPCalculator dialog during regenerate (suppressed): \"" + __0 + "\" | \"" + __1 + "\"");
		__result = null;
		return false;
	}

	private static int ReadMaxStack()
	{
		if (_fMaxStackSize != null)
		{
			try
			{
				int num = (int)Math.Round(Convert.ToDouble(_fMaxStackSize.GetValue(null)));
				if (num >= 1)
				{
					return num;
				}
			}
			catch
			{
			}
		}
		return 4;
	}

	private static void StagePostfix(bool __result, MethodBase __originalMethod)
	{
		if (_inRetry)
		{
			if (!__result)
			{
				DSPCalculatorPlusLog.Warn("[overflow][stage] " + __originalMethod.Name + " returned FALSE <- failing stage.");
			}
			else
			{
				DSPCalculatorPlusLog.Info("[overflow][stage] " + __originalMethod.Name + " ok.");
			}
		}
	}

	private static void GenPrefix()
	{
		if (!_inRetry)
		{
			_pendingExternal.Clear();
			LastRunLaneCounts.Clear();
		}
	}

	private static void CalcPostfix(object __instance, ref bool __result)
	{
		if (!_ready || __instance == null || !Plugin.Config.EnableMultiLaneOverflowFix.Value)
		{
			return;
		}
		object value = _fSolution.GetValue(__instance);
		if (value == null || !(_fBeltsAvailable.GetValue(value) is IList { Count: not 0 } list) || !(_fItemSumInfos.GetValue(__instance) is IDictionary dictionary))
		{
			return;
		}
		object obj = list[list.Count - 1];
		double num = (double)_fBeltSpeed.GetValue(obj);
		int fastestId = (int)_fBeltItemId.GetValue(obj);
		if (num <= 0.0)
		{
			return;
		}
		HashSet<int> targetItemIds = GetTargetItemIds(value);
		_pendingExternal.Clear();
		foreach (object value2 in dictionary.Values)
		{
			double num2 = (double)_fSumNeedSpeed.GetValue(value2);
			int num3 = (int)_fSumItemId.GetValue(value2);
			if (_finalizeUnderProvision)
			{
				if ((int)_fSumNeedBeltId.GetValue(value2) <= 0)
				{
					_fSumNeedBeltId.SetValue(value2, PickBelt(list, num2, fastestId));
				}
			}
			else if (!(num2 <= num))
			{
				LastRunLaneCounts[num3] = (int)Math.Ceiling(num2 / num - 1E-06);
				if (!targetItemIds.Contains(num3))
				{
					_pendingExternal.Add(num3);
				}
			}
		}
		if (_finalizeUnderProvision)
		{
			__result = true;
		}
	}

	private static void GenPostfix(object __instance, ref bool __result, int genLevel, bool forcePortOnLeft, bool orthogonalConnect)
	{
		if (!_ready || __instance == null || !Plugin.Config.EnableMultiLaneOverflowFix.Value || _inRetry || __result || _pendingExternal.Count == 0)
		{
			return;
		}
		object value = _fSolution.GetValue(__instance);
		object obj = ((value != null) ? _fUserPreference.GetValue(value) : null);
		IDictionary dictionary = ((obj != null) ? (_fItemConfigs.GetValue(obj) as IDictionary) : null);
		if (dictionary == null)
		{
			return;
		}
		double primaryTargetSpeed = GetPrimaryTargetSpeed(value);
		bool flag = false;
		int num = 0;
		_inRetry = true;
		_restoreOreValue.Clear();
		_restoreForceNotOre.Clear();
		_createdConfigs.Clear();
		List<int> list = new List<int>();
		HashSet<int> hashSet = new HashSet<int>();
		try
		{
			bool flag2 = false;
			int num2 = 0;
			int num3 = ReadMaxStack();
			if (Plugin.Config.PushBeltStackingOnOverflow.Value && _fBpStackSetting != null && obj != null && num3 > 1)
			{
				num = (int)_fBpStackSetting.GetValue(obj);
				if (num != num3)
				{
					_fBpStackSetting.SetValue(obj, num3);
					flag = true;
					DSPCalculatorPlusLog.Info("[overflow] raising belt-stacking to " + num3 + "x (was " + num + ") to lift the belt-capacity ceiling.");
					flag2 = (bool)_mGenerateFull.Invoke(__instance, new object[3] { genLevel, forcePortOnLeft, orthogonalConnect });
					if (flag2)
					{
						DSPCalculatorPlusLog.Info("[overflow] " + num3 + "x belt-stacking alone resolved it - no externalization needed.");
					}
				}
			}
			while (!flag2 && num2 < 40)
			{
				List<int> list2 = new List<int>();
				foreach (int item in _pendingExternal)
				{
					if (!hashSet.Contains(item))
					{
						list2.Add(item);
					}
				}
				if (list2.Count == 0)
				{
					break;
				}
				num2++;
				foreach (int item2 in list2)
				{
					MarkConsideredAsOre(dictionary, item2);
					hashSet.Add(item2);
					list.Add(item2);
				}
				_mReSolve.Invoke(value, new object[1] { primaryTargetSpeed });
				flag2 = (bool)_mGenerateFull.Invoke(__instance, new object[3] { genLevel, forcePortOnLeft, orthogonalConnect });
				if (flag2)
				{
					break;
				}
			}
			List<int> list3 = new List<int>(_pendingExternal);
			if (!flag2)
			{
				DSPCalculatorPlusLog.Info("[overflow] Phase B: finalizing (force past single-belt check) with " + list3.Count + " stuck item(s) under-provisioned [" + Join(list3) + "]...");
				_finalizeUnderProvision = true;
				flag2 = (bool)_mGenerateFull.Invoke(__instance, new object[3] { genLevel, forcePortOnLeft, orthogonalConnect });
				_finalizeUnderProvision = false;
				DSPCalculatorPlusLog.Info("[overflow] Phase B: generation returned " + flag2 + (flag2 ? "" : " (see any [overflow][diag] dialog line just above for the downstream reason; if there is none, a silent placement/connection stage failed at this scale).") + ".");
			}
			__result = flag2;
			DSPCalculatorPlusLog.Info("[overflow] marked " + list.Count + " item(s) as external over " + num2 + " pass(es) [" + Join(list) + "]; of those, " + list3.Count + " did NOT prune (stuck, under-provisioned) [" + Join(list3) + "] => " + (list.Count - list3.Count) + " genuinely externalized; generation " + (flag2 ? "succeeded" : "FAILED") + ((flag && flag2) ? (" (using " + num3 + "x belt-stacking - blueprint ASSUMES that cargo stacking; needs pile/proliferator tech to run at full rate)") : "") + ".");
			if (list3.Count > 0)
			{
				DSPCalculatorPlusLog.Warn("[overflow] stuck items (byproducts or targets that can't become inputs) are on a single belt and under-provisioned: [" + Join(list3) + "]. Add extra output/input belts for these manually.");
			}
			if (!flag2)
			{
				DSPCalculatorPlusLog.Warn("[overflow] could not generate even after externalizing" + (flag ? (" + " + num3 + "x belt-stacking") : "") + " - a byproduct's belt demand still exceeds capacity at this scale. Reduce the target quantity, or split into multiple smaller blueprints" + (flag ? (" (already at max " + num3 + "x stacking).") : (Plugin.Config.PushBeltStackingOnOverflow.Value ? "." : ", or enable PushBeltStackingOnOverflow to try higher stacking.")));
			}
		}
		catch (Exception ex)
		{
			DSPCalculatorPlusLog.Error("[overflow] externalize-and-regenerate failed: " + ex);
		}
		finally
		{
			_finalizeUnderProvision = false;
			if (flag)
			{
				try
				{
					_fBpStackSetting.SetValue(obj, num);
				}
				catch (Exception ex2)
				{
					DSPCalculatorPlusLog.Error("[overflow] restore bpStackSetting failed: " + ex2.Message);
				}
			}
			RestoreItemConfigs(dictionary);
			try
			{
				SetPrimaryTargetSpeed(value, primaryTargetSpeed);
				_mReSolve.Invoke(value, new object[1] { primaryTargetSpeed });
			}
			catch (Exception ex3)
			{
				DSPCalculatorPlusLog.Error("[overflow] restore re-solve failed: " + ex3.Message);
			}
			_pendingExternal.Clear();
			_inRetry = false;
		}
	}

	private static double GetPrimaryTargetSpeed(object solution)
	{
		if (!(_fTargets.GetValue(solution) is IList { Count: not 0 } list))
		{
			return 0.0;
		}
		return (double)_fTargetSpeed.GetValue(list[0]);
	}

	private static void SetPrimaryTargetSpeed(object solution, double speed)
	{
		if (_fTargets.GetValue(solution) is IList { Count: not 0 } list)
		{
			_fTargetSpeed.SetValue(list[0], speed);
		}
	}

	private static int PickBelt(IList belts, double need, int fastestId)
	{
		for (int i = 0; i < belts.Count; i++)
		{
			if ((double)_fBeltSpeed.GetValue(belts[i]) >= need)
			{
				return (int)_fBeltItemId.GetValue(belts[i]);
			}
		}
		return fastestId;
	}

	private static HashSet<int> GetTargetItemIds(object solution)
	{
		HashSet<int> hashSet = new HashSet<int>();
		if (!(_fTargets.GetValue(solution) is IList list))
		{
			return hashSet;
		}
		foreach (object item in list)
		{
			if (item != null)
			{
				hashSet.Add((int)_fTargetItemId.GetValue(item));
			}
		}
		return hashSet;
	}

	private static void MarkConsideredAsOre(IDictionary itemConfigs, int itemId)
	{
		object obj;
		if (itemConfigs.Contains(itemId))
		{
			obj = itemConfigs[itemId];
			if (!_restoreOreValue.ContainsKey(itemId))
			{
				_restoreOreValue[itemId] = (bool)_fConsideredAsOre.GetValue(obj);
				_restoreForceNotOre[itemId] = (bool)_fForceNotOre.GetValue(obj);
			}
		}
		else
		{
			obj = _ctorItemConfig.Invoke(new object[1] { itemId });
			itemConfigs[itemId] = obj;
			_createdConfigs.Add(itemId);
		}
		_fConsideredAsOre.SetValue(obj, true);
		_fForceNotOre.SetValue(obj, false);
	}

	private static void RestoreItemConfigs(IDictionary itemConfigs)
	{
		foreach (int createdConfig in _createdConfigs)
		{
			if (itemConfigs.Contains(createdConfig))
			{
				itemConfigs.Remove(createdConfig);
			}
		}
		foreach (KeyValuePair<int, bool> item in _restoreOreValue)
		{
			if (itemConfigs.Contains(item.Key))
			{
				object obj = itemConfigs[item.Key];
				_fConsideredAsOre.SetValue(obj, item.Value);
				if (_restoreForceNotOre.ContainsKey(item.Key))
				{
					_fForceNotOre.SetValue(obj, _restoreForceNotOre[item.Key]);
				}
			}
		}
	}

	private static string Join(ICollection<int> items)
	{
		string[] array = new string[items.Count];
		int num = 0;
		foreach (int item in items)
		{
			array[num++] = item.ToString();
		}
		return string.Join(",", array);
	}
}
internal static class PowerPolePatch
{
	private sealed class PoleField
	{
		private const double MinDist2 = 13.0;

		private const int Bkt = 4;

		private readonly Dictionary<long, List<int>> _b = new Dictionary<long, List<int>>();

		private readonly List<int> _x = new List<int>();

		private readonly List<int> _y = new List<int>();

		public void Add(int x, int y)
		{
			int count = _x.Count;
			_x.Add(x);
			_y.Add(y);
			long key = Key(FloorDiv(x, 4), FloorDiv(y, 4));
			if (!_b.TryGetValue(key, out var value))
			{
				value = new List<int>();
				_b[key] = value;
			}
			value.Add(count);
		}

		public bool TooClose(int x, int y)
		{
			int num = FloorDiv(x, 4);
			int num2 = FloorDiv(y, 4);
			for (int i = -1; i <= 1; i++)
			{
				for (int j = -1; j <= 1; j++)
				{
					if (!_b.TryGetValue(Key(num + i, num2 + j), out var value))
					{
						continue;
					}
					foreach (int item in value)
					{
						double num3 = x - _x[item];
						double num4 = y - _y[item];
						if (num3 * num3 + num4 * num4 < 13.0)
						{
							return true;
						}
					}
				}
			}
			return false;
		}
	}

	private const int TeslaTowerId = 2201;

	private const int WirelessPowerTowerId = 2202;

	private const int ConveyorBeltId = 2003;

	private const string DspCalcMarker = "DSPCalcBPTemp";

	private const double GridSafety = 0.85;

	private const double CoverSafety = 0.85;

	private const double RescueCoverSafety = 0.98;

	private const double ConnectFactor = 0.95;

	private const int BuiltGroundRadius = 3;

	private const int RowBoundMargin = 3;

	private static readonly ConditionalWeakTable<BlueprintData, object> _processed = new ConditionalWeakTable<BlueprintData, object>();

	private static readonly object Marker = new object();

	private static readonly Dictionary<int, int[]> _footCache = new Dictionary<int, int[]>();

	private static readonly Dictionary<int, float> _coverCache = new Dictionary<int, float>();

	private static readonly Dictionary<int, bool> _consumerCache = new Dictionary<int, bool>();

	private static readonly Dictionary<int, bool> _nodeCache = new Dictionary<int, bool>();

	private const int Bucket = 32;

	private static bool _diagLogged;

	public static void Apply(Harmony harmony)
	{
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_0077: Expected O, but got Unknown
		MethodInfo methodInfo = AccessTools.Method(typeof(PlayerController), "OpenBlueprintPasteMode", new Type[3]
		{
			typeof(BlueprintData),
			typeof(string),
			typeof(bool)
		}, (Type[])null);
		if (methodInfo == null)
		{
			DSPCalculatorPlusLog.Warn("[poles] PlayerController.OpenBlueprintPasteMode(BlueprintData,string,bool) not found - power-pole feature disabled.");
			return;
		}
		harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PowerPolePatch), "PastePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		DSPCalculatorPlusLog.Info("[poles] Group C (auto power poles) patch installed on PlayerController.OpenBlueprintPasteMode.");
	}

	private static void PastePrefix(BlueprintData blueprint, string fullPath)
	{
		try
		{
			PowerPoleType value = Plugin.Config.AutoPowerPoles.Value;
			if (value != PowerPoleType.Off && blueprint != null && blueprint.buildings != null && blueprint.buildings.Length != 0 && !string.IsNullOrEmpty(fullPath) && fullPath.IndexOf("DSPCalcBPTemp", StringComparison.OrdinalIgnoreCase) >= 0 && !_processed.TryGetValue(blueprint, out var _))
			{
				_processed.Add(blueprint, Marker);
				InjectPoles(blueprint, value);
			}
		}
		catch (Exception ex)
		{
			DSPCalculatorPlusLog.Error("[poles] injection failed (blueprint pasted without added poles): " + ex);
		}
	}

	private static void InjectPoles(BlueprintData bp, PowerPoleType mode)
	{
		LogPoleDiagnosticOnce();
		if (!IsUsable(2201))
		{
			DSPCalculatorPlusLog.Warn("[poles] Tesla Tower is unavailable or not unlocked - no poles added. Unlock it, or set AutoPowerPoles to Off.");
			return;
		}
		BlueprintBuilding[] buildings = bp.buildings;
		int num = buildings.Length;
		float num2 = PoleTopHeight(2201);
		float num3 = BeltUnderClearance();
		float clearanceZ = (Plugin.Config.PolesUnderRaisedBelts.Value ? (num2 + num3) : float.PositiveInfinity);
		PoleField poleField = new PoleField();
		HashSet<long> hashSet = new HashSet<long>();
		HashSet<long> hashSet2 = new HashSet<long>();
		float minX = float.MaxValue;
		float maxX = float.MinValue;
		float minY = float.MaxValue;
		float maxY = float.MinValue;
		int skippedElevated = 0;
		for (int i = 0; i < num; i++)
		{
			BlueprintBuilding val = buildings[i];
			ReserveFootprint(hashSet, val, clearanceZ, ref skippedElevated, hashSet2);
			AccumulateBounds(val, ref minX, ref maxX, ref minY, ref maxY);
			if (IsPowerNodeItem(val.itemId))
			{
				poleField.Add((int)Math.Round(val.localOffset_x), (int)Math.Round(val.localOffset_y));
			}
		}
		HashSet<long> hashSet3 = new HashSet<long>();
		foreach (long item in hashSet2)
		{
			if (!hashSet.Contains(item))
			{
				hashSet3.Add(item);
			}
		}
		if (minX > maxX || minY > maxY)
		{
			return;
		}
		HashSet<long> builtGround = new HashSet<long>(hashSet);
		if (skippedElevated > 0)
		{
			DSPCalculatorPlusLog.Info("[poles] freed " + skippedElevated + " tile(s) under raised belts for pole placement (poleTop=" + num2.ToString("0.##") + " + beltUnderside=" + num3.ToString("0.##") + " => belt must be at z>=" + clearanceZ.ToString("0.##") + " to build a pole under it).");
		}
		int num4 = (int)Math.Floor(minX);
		int num5 = (int)Math.Ceiling(maxX);
		int num6 = (int)Math.Floor(minY);
		int num7 = (int)Math.Ceiling(maxY);
		Dictionary<long, List<int>> dictionary = new Dictionary<long, List<int>>();
		List<int> list = new List<int>();
		List<int> list2 = new List<int>();
		for (int j = 0; j < num; j++)
		{
			BlueprintBuilding val2 = buildings[j];
			if (IsConsumer(val2.itemId))
			{
				int num8 = (int)Math.Round(val2.localOffset_x);
				int num9 = (int)Math.Round(val2.localOffset_y);
				int count = list.Count;
				list.Add(num8);
				list2.Add(num9);
				AddIdx(dictionary, Key(FloorDiv(num8, 32), FloorDiv(num9, 32)), count);
			}
		}
		List<BlueprintBuilding> list3 = new List<BlueprintBuilding>();
		PrefabDesc prefabDesc = ((ProtoSet<ItemProto>)(object)LDB.items).Select(2201).prefabDesc;
		int trulyUncovered;
		if (DetectBands(buildings, num, num4, num5, num6, num7, out var crossIsY, out var bands))
		{
			PlaceLineAligned(2201, prefabDesc, hashSet, builtGround, num, list3, poleField, hashSet3, dictionary, list, list2, crossIsY, bands, crossIsY ? num4 : num6, crossIsY ? num5 : num7, crossIsY ? num6 : num4, crossIsY ? num7 : num5);
			string[] obj = new string[5]
			{
				"[poles] line-aligned placement: rows run along ",
				crossIsY ? "X" : "Y",
				" (",
				null,
				null
			};
			trulyUncovered = bands.Count;
			obj[3] = trulyUncovered.ToString();
			obj[4] = " global block-line(s); machine rows detected LOCALLY per line-strip); one pole per machine line.";
			DSPCalculatorPlusLog.Info(string.Concat(obj));
		}
		else
		{
			PlaceGrid(2201, prefabDesc, hashSet, builtGround, num, list3, poleField, hashSet3, num4, num5, num6, num7);
		}
		int count2 = list3.Count;
		int num10 = 0;
		if (IsUsable(2201))
		{
			num10 = BackfillCoverage(buildings, num, hashSet, builtGround, list3, num, poleField, hashSet3, 2201, 0.85, placeMode: true, consumersOnly: true, num4, num5, num6, num7, out trulyUncovered);
			num10 += BackfillCoverage(buildings, num, hashSet, builtGround, list3, num, poleField, hashSet3, 2201, 0.98, placeMode: true, consumersOnly: true, num4, num5, num6, num7, out trulyUncovered);
		}
		BackfillCoverage(buildings, num, hashSet, builtGround, list3, num, poleField, hashSet3, 2201, 0.98, placeMode: false, consumersOnly: true, num4, num5, num6, num7, out var trulyUncovered2);
		if (list3.Count == 0)
		{
			DSPCalculatorPlusLog.Warn("[poles] no free tiles for poles in this blueprint - none added (machines packed with no gaps). Power it manually, or set AutoPowerPoles=Off to silence.");
			return;
		}
		int num11 = 0;
		BlueprintBuilding[] array = (BlueprintBuilding[])(object)new BlueprintBuilding[num + list3.Count];
		Array.Copy(buildings, array, num);
		for (int k = 0; k < list3.Count; k++)
		{
			array[num + k] = list3[k];
		}
		bp.buildings = array;
		ExpandArea(bp, list3);
		AuditPoleProximity(array);
		LogWorstCoverage(buildings, num, list3, hashSet3);
		LogConnectivity(list3, hashSet3);
		DSPCalculatorPlusLog.Info("[poles] mode=" + mode.ToString() + " added " + list3.Count + " pole(s) over " + num + " buildings: " + count2 + " Tesla Tower (grid)" + ((num10 > 0) ? (" + " + num10 + " Tesla Tower (backfill)") : "") + ((num11 > 0) ? (" - " + num11 + " redundant (pruned)") : "") + ((trulyUncovered2 > 0) ? ("; " + trulyUncovered2 + " machine area(s) still UNCOVERED (no free tile within range)") : "; full coverage") + ".");
		if (trulyUncovered2 > 0)
		{
			DSPCalculatorPlusLog.Warn("[poles] " + trulyUncovered2 + " machine area(s) still have no free tile within reach of any Tesla Tower - power those few by hand.");
		}
	}

	private static int PlaceGrid(int poleId, PrefabDesc pd, HashSet<long> occupied, HashSet<long> builtGround, int indexBase, List<BlueprintBuilding> outPoles, PoleField field, HashSet<long> underBelt, int bbMinX, int bbMaxX, int bbMinY, int bbMaxY)
	{
		float powerCoverRadius = pd.powerCoverRadius;
		float powerConnectDistance = pd.powerConnectDistance;
		if (powerCoverRadius <= 0.1f)
		{
			return 0;
		}
		double num = (double)powerCoverRadius * 0.85 * 1.15;
		if (powerConnectDistance > 0.1f)
		{
			num = Math.Min(num, (double)powerConnectDistance * 0.95);
		}
		if (num < 1.0)
		{
			num = 1.0;
		}
		int num2 = Math.Max(0, bbMaxX - bbMinX);
		int num3 = Math.Max(0, bbMaxY - bbMinY);
		int num4 = Math.Max(1, (int)Math.Ceiling((double)num2 / num));
		int num5 = Math.Max(1, (int)Math.Ceiling((double)num3 / num));
		double num6 = (double)num2 / (double)num4;
		double num7 = (double)num3 / (double)num5;
		int searchR = Math.Max((int)Math.Ceiling(Math.Max(num6, num7) * 0.6), (int)Math.Floor(powerCoverRadius));
		GetTileHalfExtents(pd, poleId, 0f, out var hx, out var hy);
		int num8 = 0;
		for (int i = 0; i < num4; i++)
		{
			int gx = bbMinX + (int)Math.Round(((double)i + 0.5) * num6);
			for (int j = 0; j < num5; j++)
			{
				int gy = bbMinY + (int)Math.Round(((double)j + 0.5) * num7);
				if (FindFreeTile(occupied, field, gx, gy, searchR, hx, hy, bbMinX, bbMaxX, bbMinY, bbMaxY, underBelt, out var px, out var py))
				{
					if (!IsNearBuiltGround(builtGround, px, py, 3))
					{
						num8++;
						continue;
					}
					outPoles.Add(MakePole(poleId, pd.modelIndex, px, py, indexBase + outPoles.Count));
					MarkOccupied(occupied, px, py, hx, hy);
					field.Add(px, py);
				}
				else
				{
					num8++;
				}
			}
		}
		return num8;
	}

	private static bool DetectBands(BlueprintBuilding[] buildings, int nBuildings, int bbMinX, int bbMaxX, int bbMinY, int bbMaxY, out bool crossIsY, out List<int> bands)
	{
		List<int> bands2;
		double num = HistoBands(buildings, nBuildings, bbMinX, bbMaxX, axisY: false, out bands2);
		List<int> bands3;
		double num2 = HistoBands(buildings, nBuildings, bbMinY, bbMaxY, axisY: true, out bands3);
		crossIsY = num2 >= num;
		bands = (crossIsY ? bands3 : bands2);
		if (bands.Count >= 2)
		{
			return bands.Count <= 4000;
		}
		return false;
	}

	private static double HistoBands(BlueprintBuilding[] buildings, int nBuildings, int lo, int hi, bool axisY, out List<int> bands)
	{
		bands = new List<int>();
		int num = hi - lo + 1;
		if (num <= 1)
		{
			return 0.0;
		}
		int[] array = new int[num];
		int num2 = 0;
		for (int i = 0; i < nBuildings; i++)
		{
			BlueprintBuilding val = buildings[i];
			if (IsConsumer(val.itemId))
			{
				int num3 = (axisY ? ((int)Math.Round(val.localOffset_y)) : ((int)Math.Round(val.localOffset_x))) - lo;
				if (num3 >= 0 && num3 < num)
				{
					array[num3]++;
					num2++;
				}
			}
		}
		if (num2 == 0)
		{
			return 0.0;
		}
		int num4 = 0;
		for (int j = 0; j < num; j++)
		{
			if (array[j] == 0)
			{
				num4++;
			}
		}
		int num5 = -1;
		for (int k = 0; k < num; k++)
		{
			if (array[k] > 0)
			{
				if (num5 < 0)
				{
					num5 = k;
				}
			}
			else if (num5 >= 0)
			{
				bands.Add(lo + (num5 + k - 1) / 2);
				num5 = -1;
			}
		}
		if (num5 >= 0)
		{
			bands.Add(lo + (num5 + num - 1) / 2);
		}
		return (double)num4 / (double)num;
	}

	private static int PlaceLineAligned(int poleId, PrefabDesc pd, HashSet<long> occupied, HashSet<long> builtGround, int indexBase, List<BlueprintBuilding> outPoles, PoleField field, HashSet<long> underBelt, Dictionary<long, List<int>> consHash, List<int> consX, List<int> consY, bool crossIsY, List<int> bands, int lineMin, int lineMax, int crossMin, int crossMax)
	{
		float powerCoverRadius = pd.powerCoverRadius;
		float powerConnectDistance = pd.powerConnectDistance;
		if (powerCoverRadius <= 0.1f)
		{
			return 0;
		}
		int searchR = Math.Max(3, (int)Math.Floor(powerCoverRadius));
		GetTileHalfExtents(pd, poleId, 0f, out var hx, out var hy);
		double num = (double)powerCoverRadius * 0.85 * 1.5;
		if (powerConnectDistance > 0.1f)
		{
			num = Math.Min(num, (double)powerConnectDistance * 0.95);
		}
		if (num < 4.0)
		{
			num = 4.0;
		}
		double num2 = (double)powerCoverRadius * 0.85 * 1.5;
		int num3 = Math.Max(1, lineMax - lineMin);
		int num4 = Math.Max(1, (int)Math.Ceiling((double)num3 / num));
		double num5 = (double)num3 / (double)num4;
		List<int>[] array = new List<int>[num4];
		for (int i = 0; i < num4; i++)
		{
			array[i] = new List<int>();
		}
		for (int j = 0; j < consX.Count; j++)
		{
			int num6 = (int)((double)((crossIsY ? consX[j] : consY[j]) - lineMin) / num5);
			if (num6 < 0)
			{
				num6 = 0;
			}
			else if (num6 >= num4)
			{
				num6 = num4 - 1;
			}
			array[num6].Add(j);
		}
		int num7 = 0;
		for (int k = 0; k < num4; k++)
		{
			List<int> list = array[k];
			if (list.Count == 0)
			{
				continue;
			}
			int num8 = lineMin + (int)Math.Round(((double)k + 0.5) * num5);
			int num9 = int.MaxValue;
			int num10 = int.MinValue;
			foreach (int item in list)
			{
				int num11 = (crossIsY ? consY[item] : consX[item]);
				if (num11 < num9)
				{
					num9 = num11;
				}
				if (num11 > num10)
				{
					num10 = num11;
				}
			}
			int num12 = num10 - num9 + 1;
			bool[] array2 = new bool[num12];
			foreach (int item2 in list)
			{
				int num13 = (crossIsY ? consY[item2] : consX[item2]);
				array2[num13 - num9] = true;
			}
			int num14 = Math.Max(crossMin, num9 - 3);
			int num15 = Math.Min(crossMax, num10 + 3);
			int num16 = -1;
			for (int l = 0; l <= num12; l++)
			{
				if (l < num12 && array2[l])
				{
					if (num16 < 0)
					{
						num16 = l;
					}
				}
				else
				{
					if (num16 < 0)
					{
						continue;
					}
					int num17 = num9 + num16;
					int num18 = num9 + (l - 1);
					int num19 = num18 - num17;
					int num20 = ((!((double)num19 < num2)) ? ((int)Math.Ceiling((double)num19 / num2)) : 0);
					double num21 = ((num20 > 0) ? ((double)num19 / (double)num20) : 0.0);
					for (int m = 0; m <= num20; m++)
					{
						int num22 = ((num20 == 0) ? ((num17 + num18) / 2) : (num17 + (int)Math.Round((double)m * num21)));
						int num23 = (crossIsY ? num8 : num22);
						int num24 = (crossIsY ? num22 : num8);
						if (!HasConsumerNear(consHash, consX, consY, num23, num24, powerCoverRadius))
						{
							continue;
						}
						if (FindFreeTile(occupied, field, num23, num24, searchR, hx, hy, crossIsY ? lineMin : num14, crossIsY ? lineMax : num15, crossIsY ? num14 : lineMin, crossIsY ? num15 : lineMax, underBelt, out var px, out var py))
						{
							if (HasConsumerNear(consHash, consX, consY, px, py, powerCoverRadius))
							{
								if (!IsNearBuiltGround(builtGround, px, py, 3))
								{
									num7++;
									continue;
								}
								outPoles.Add(MakePole(poleId, pd.modelIndex, px, py, indexBase + outPoles.Count));
								MarkOccupied(occupied, px, py, hx, hy);
								field.Add(px, py);
							}
						}
						else
						{
							num7++;
						}
					}
					num16 = -1;
				}
			}
		}
		return num7;
	}

	private static bool HasConsumerNear(Dictionary<long, List<int>> consHash, List<int> consX, List<int> consY, int x, int y, double radius)
	{
		if (consHash == null)
		{
			return true;
		}
		double num = radius * radius;
		int num2 = FloorDiv(x, 32);
		int num3 = FloorDiv(y, 32);
		for (int i = -1; i <= 1; i++)
		{
			for (int j = -1; j <= 1; j++)
			{
				if (!consHash.TryGetValue(Key(num2 + i, num3 + j), out var value))
				{
					continue;
				}
				foreach (int item in value)
				{
					double num4 = x - consX[item];
					double num5 = y - consY[item];
					if (num4 * num4 + num5 * num5 <= num)
					{
						return true;
					}
				}
			}
		}
		return false;
	}

	private static bool IsNearBuiltGround(HashSet<long> builtGround, int x, int y, int radius)
	{
		for (int i = -radius; i <= radius; i++)
		{
			for (int j = -radius; j <= radius; j++)
			{
				if (builtGround.Contains(Key(x + i, y + j)))
				{
					return true;
				}
			}
		}
		return false;
	}

	private static int BackfillCoverage(BlueprintBuilding[] buildings, int nBuildings, HashSet<long> occupied, HashSet<long> builtGround, List<BlueprintBuilding> poles, int startCount, PoleField field, HashSet<long> underBelt, int fillPoleId, double coverSafety, bool placeMode, bool consumersOnly, int bbMinX, int bbMaxX, int bbMinY, int bbMaxY, out int trulyUncovered)
	{
		trulyUncovered = 0;
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select(fillPoleId);
		if (val == null || val.prefabDesc == null)
		{
			return 0;
		}
		PrefabDesc prefabDesc = val.prefabDesc;
		float powerCoverRadius = prefabDesc.powerCoverRadius;
		if (powerCoverRadius <= 0.1f)
		{
			return 0;
		}
		GetTileHalfExtents(prefabDesc, fillPoleId, 0f, out var hx, out var hy);
		float num = (float)((double)powerCoverRadius * coverSafety);
		int searchR = Math.Max(3, (int)Math.Floor(num));
		Dictionary<long, List<int>> spatial = new Dictionary<long, List<int>>();
		List<int> poleX = new List<int>();
		List<int> poleY = new List<int>();
		List<float> poleCov = new List<float>();
		for (int i = 0; i < poles.Count; i++)
		{
			BlueprintBuilding val2 = poles[i];
			AddSpatial(spatial, poleX, poleY, poleCov, (int)val2.localOffset_x, (int)val2.localOffset_y, (float)((double)RawCoverOf(val2.itemId) * coverSafety));
		}
		HashSet<long> hashSet = new HashSet<long>();
		List<float> list = new List<float>();
		List<float> list2 = new List<float>();
		for (int j = 0; j < nBuildings; j++)
		{
			BlueprintBuilding val3 = buildings[j];
			if (!consumersOnly || IsConsumer(val3.itemId))
			{
				float localOffset_x = val3.localOffset_x;
				float localOffset_y = val3.localOffset_y;
				long item = Key((int)Math.Round(localOffset_x), (int)Math.Round(localOffset_y));
				if (hashSet.Add(item) && !CoveredBy(spatial, poleX, poleY, poleCov, localOffset_x, localOffset_y))
				{
					list.Add(localOffset_x);
					list2.Add(localOffset_y);
				}
			}
		}
		int num2 = 0;
		for (int k = 0; k < list.Count; k++)
		{
			float num3 = list[k];
			float num4 = list2[k];
			if (CoveredBy(spatial, poleX, poleY, poleCov, num3, num4))
			{
				continue;
			}
			if (!placeMode)
			{
				trulyUncovered++;
				continue;
			}
			int gx = (int)Math.Round(num3);
			int gy = (int)Math.Round(num4);
			if (FindFreeTile(occupied, field, gx, gy, searchR, hx, hy, bbMinX, bbMaxX, bbMinY, bbMaxY, underBelt, out var px, out var py))
			{
				if (!IsNearBuiltGround(builtGround, px, py, 3))
				{
					trulyUncovered++;
					continue;
				}
				poles.Add(MakePole(fillPoleId, prefabDesc.modelIndex, px, py, startCount + poles.Count));
				MarkOccupied(occupied, px, py, hx, hy);
				AddSpatial(spatial, poleX, poleY, poleCov, px, py, num);
				field.Add(px, py);
				num2++;
			}
			else
			{
				trulyUncovered++;
			}
		}
		return num2;
	}

	private static void AddSpatial(Dictionary<long, List<int>> spatial, List<int> poleX, List<int> poleY, List<float> poleCov, int x, int y, float cov)
	{
		int count = poleX.Count;
		poleX.Add(x);
		poleY.Add(y);
		poleCov.Add(cov);
		long key = Key(FloorDiv(x, 32), FloorDiv(y, 32));
		if (!spatial.TryGetValue(key, out var value))
		{
			value = (spatial[key] = new List<int>());
		}
		value.Add(count);
	}

	private static bool CoveredBy(Dictionary<long, List<int>> spatial, List<int> poleX, List<int> poleY, List<float> poleCov, double x, double y)
	{
		int num = FloorDiv((int)Math.Round(x), 32);
		int num2 = FloorDiv((int)Math.Round(y), 32);
		for (int i = -1; i <= 1; i++)
		{
			for (int j = -1; j <= 1; j++)
			{
				if (!spatial.TryGetValue(Key(num + i, num2 + j), out var value))
				{
					continue;
				}
				foreach (int item in value)
				{
					double num3 = poleCov[item];
					double num4 = x - (double)poleX[item];
					double num5 = y - (double)poleY[item];
					if (num4 * num4 + num5 * num5 <= num3 * num3)
					{
						return true;
					}
				}
			}
		}
		return false;
	}

	private static int FloorDiv(int a, int b)
	{
		int num = a / b;
		if (a % b != 0 && a < 0 != b < 0)
		{
			num--;
		}
		return num;
	}

	private static bool IsConsumer(int itemId)
	{
		if (_consumerCache.TryGetValue(itemId, out var value))
		{
			return value;
		}
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select(itemId);
		value = val != null && val.prefabDesc != null && val.prefabDesc.isPowerConsumer;
		_consumerCache[itemId] = value;
		return value;
	}

	private static bool IsPowerNodeItem(int itemId)
	{
		if (_nodeCache.TryGetValue(itemId, out var value))
		{
			return value;
		}
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select(itemId);
		value = val != null && val.prefabDesc != null && val.prefabDesc.isPowerNode;
		_nodeCache[itemId] = value;
		return value;
	}

	private static float RawCoverOf(int itemId)
	{
		if (_coverCache.TryGetValue(itemId, out var value))
		{
			return value;
		}
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select(itemId);
		value = ((val != null && val.prefabDesc != null) ? val.prefabDesc.powerCoverRadius : 0f);
		_coverCache[itemId] = value;
		return value;
	}

	private static bool IsUsable(int itemId)
	{
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select(itemId);
		if (val == null || val.prefabDesc == null || !val.prefabDesc.isPowerNode)
		{
			return false;
		}
		if (GameMain.history != null && !GameMain.history.ItemUnlocked(itemId))
		{
			return false;
		}
		return true;
	}

	private static void GetTileHalfExtents(PrefabDesc pd, int itemId, float yaw, out int hx, out int hy)
	{
		if (!_footCache.TryGetValue(itemId, out var value))
		{
			MeasureFootprint(pd, out var ex, out var ez);
			value = new int[2]
			{
				Math.Max(0, (int)Math.Floor(ex + 0.5f - 0.001f)),
				Math.Max(0, (int)Math.Floor(ez + 0.5f - 0.001f))
			};
			_footCache[itemId] = value;
		}
		hx = value[0];
		hy = value[1];
		int num = (int)Math.Round(yaw / 90f) & 3;
		if (num == 1 || num == 3)
		{
			int num2 = hx;
			hx = hy;
			hy = num2;
		}
	}

	private static void MeasureFootprint(PrefabDesc pd, out float ex, out float ez)
	{
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: 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)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		ex = 0f;
		ez = 0f;
		ColliderData[] buildColliders = pd.buildColliders;
		if (buildColliders != null)
		{
			foreach (ColliderData val in buildColliders)
			{
				float num = Math.Abs(val.pos.x) + Math.Max(val.ext.x, val.radius);
				float num2 = Math.Abs(val.pos.z) + Math.Max(val.ext.z, val.radius);
				if (num > ex)
				{
					ex = num;
				}
				if (num2 > ez)
				{
					ez = num2;
				}
			}
		}
		if (ex < 0.1f && ez < 0.1f)
		{
			ex = Math.Max(pd.buildCollider.ext.x, pd.buildCollider.radius);
			ez = Math.Max(pd.buildCollider.ext.z, pd.buildCollider.radius);
		}
		if (ex < 0.1f && ez < 0.1f)
		{
			ex = 0.5f;
			ez = 0.5f;
		}
	}

	private static void ReserveFootprint(HashSet<long> occupied, BlueprintBuilding b, float clearanceZ, ref int skippedElevated, HashSet<long> elevatedTiles)
	{
		int hx = 0;
		int hy = 0;
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select((int)b.itemId);
		if (val != null && val.prefabDesc != null)
		{
			GetTileHalfExtents(val.prefabDesc, b.itemId, b.yaw, out hx, out hy);
		}
		int num = (int)Math.Round(b.localOffset_x);
		int num2 = (int)Math.Round(b.localOffset_y);
		if (b.localOffset_z < clearanceZ)
		{
			MarkOccupied(occupied, num, num2, hx, hy);
		}
		else
		{
			skippedElevated++;
			elevatedTiles?.Add(Key(num, num2));
		}
		int num3 = (int)Math.Round(b.localOffset_x2);
		int num4 = (int)Math.Round(b.localOffset_y2);
		if (num3 != num || num4 != num2)
		{
			if (b.localOffset_z2 < clearanceZ)
			{
				MarkOccupied(occupied, num3, num4, hx, hy);
				return;
			}
			skippedElevated++;
			elevatedTiles?.Add(Key(num3, num4));
		}
	}

	private static float BeltUnderClearance()
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		float num = 0.5f;
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select(2003);
		if (val != null && val.prefabDesc != null)
		{
			ColliderData buildCollider = val.prefabDesc.buildCollider;
			float num2 = buildCollider.ext.y - buildCollider.pos.y;
			if (num2 > 0.05f)
			{
				num = num2;
			}
		}
		return num + 0.1f;
	}

	private static float PoleTopHeight(int itemId)
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: 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_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select(itemId);
		if (val == null || val.prefabDesc == null)
		{
			return 2f;
		}
		ColliderData buildCollider = val.prefabDesc.buildCollider;
		float num = buildCollider.pos.y + buildCollider.ext.y;
		if (!(num >= 0.5f))
		{
			return 2f;
		}
		return num;
	}

	private static void AccumulateBounds(BlueprintBuilding b, ref float minX, ref float maxX, ref float minY, ref float maxY)
	{
		float num = Math.Min(b.localOffset_x, b.localOffset_x2);
		float num2 = Math.Max(b.localOffset_x, b.localOffset_x2);
		float num3 = Math.Min(b.localOffset_y, b.localOffset_y2);
		float num4 = Math.Max(b.localOffset_y, b.localOffset_y2);
		if (num < minX)
		{
			minX = num;
		}
		if (num2 > maxX)
		{
			maxX = num2;
		}
		if (num3 < minY)
		{
			minY = num3;
		}
		if (num4 > maxY)
		{
			maxY = num4;
		}
	}

	private static bool FindFreeTile(HashSet<long> occupied, PoleField field, int gx, int gy, int searchR, int phx, int phy, int clampMinX, int clampMaxX, int clampMinY, int clampMaxY, HashSet<long> underBelt, out int px, out int py)
	{
		int num = 0;
		int num2 = 0;
		long num3 = long.MaxValue;
		for (int i = 0; i <= searchR; i++)
		{
			int num4 = 0;
			int num5 = 0;
			long num6 = long.MaxValue;
			for (int j = -i; j <= i; j++)
			{
				for (int k = -i; k <= i; k++)
				{
					if (Math.Max(Math.Abs(j), Math.Abs(k)) != i)
					{
						continue;
					}
					int num7 = gx + j;
					int num8 = gy + k;
					if (num7 >= clampMinX && num7 <= clampMaxX && num8 >= clampMinY && num8 <= clampMaxY && IsAreaFree(occupied, num7, num8, phx, phy) && !field.TooClose(num7, num8))
					{
						long num9 = (long)j * (long)j + (long)k * (long)k;
						if (num9 < num3)
						{
							num3 = num9;
							num = num7;
							num2 = num8;
						}
						if ((underBelt == null || !underBelt.Contains(Key(num7, num8))) && num9 < num6)
						{
							num6 = num9;
							num4 = num7;
							num5 = num8;
						}
					}
				}
			}
			if (num6 != long.MaxValue)
			{
				px = num4;
				py = num5;
				return true;
			}
		}
		if (num3 != long.MaxValue)
		{
			px = num;
			py = num2;
			return true;
		}
		px = gx;
		py = gy;
		return false;
	}

	private static bool IsAreaFree(HashSet<long> occupied, int cx, int cy, int hx, int hy)
	{
		for (int i = -hx; i <= hx; i++)
		{
			for (int j = -hy; j <= hy; j++)
			{
				if (occupied.Contains(Key(cx + i, cy + j)))
				{
					return false;
				}
			}
		}
		return true;
	}

	private static void MarkOccupied(HashSet<long> occupied, int cx, int cy, int hx, int hy)
	{
		for (int i = -hx; i <= hx; i++)
		{
			for (int j = -hy; j <= hy; j++)
			{
				occupied.Add(Key(cx + i, cy + j));
			}
		}
	}

	private static BlueprintBuilding MakePole(int poleId, int modelIndex, int x, int y, int index)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0005: 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_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: 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_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Expected O, but got Unknown
		return new BlueprintBuilding
		{
			index = index,
			areaIndex = 0,
			localOffset_x = x,
			localOffset_y = y,
			localOffset_z = 0f,
			localOffset_x2 = x,
			localOffset_y2 = y,
			localOffset_z2 = 0f,
			yaw = 0f,
			yaw2 = 0f,
			itemId = (short)poleId,
			modelIndex = (short)modelIndex,
			parameters = new int[0]
		};
	}

	private static void ExpandArea(BlueprintData bp, List<BlueprintBuilding> poles)
	{
		int num = 0;
		int num2 = 0;
		foreach (BlueprintBuilding pole in poles)
		{
			if (pole.localOffset_x + 1f > (float)num)
			{
				num = (int)pole.localOffset_x + 1;
			}
			if (pole.localOffset_y + 1f > (float)num2)
			{
				num2 = (int)pole.localOffset_y + 1;
			}
		}
		if (num > bp.dragBoxSize_x)
		{
			bp.dragBoxSize_x = num;
		}
		if (num2 > bp.dragBoxSize_y)
		{
			bp.dragBoxSize_y = num2;
		}
		if (bp.areas != null && bp.areas.Length != 0)
		{
			if (bp.dragBoxSize_x > bp.areas[0].width)
			{
				bp.areas[0].width = bp.dragBoxSize_x;
			}
			if (bp.dragBoxSize_y > bp.areas[0].height)
			{
				bp.areas[0].height = bp.dragBoxSize_y;
			}
		}
	}

	private static void AuditPoleProximity(BlueprintBuilding[] merged)
	{
		Dictionary<long, List<int>> dictionary = new Dictionary<long, List<int>>();
		List<int> list = new List<int>();
		List<int> list2 = new List<int>();
		int num = 0;
		int num2 = 0;
		int num3 = 0;
		foreach (BlueprintBuilding val in merged)
		{
			if (!IsPowerNodeItem(val.itemId))
			{
				continue;
			}
			int num4 = (int)Math.Round(val.localOffset_x);
			int num5 = (int)Math.Round(val.localOffset_y);
			int num6 = FloorDiv(num4, 4);
			int num7 = FloorDiv(num5, 4);
			for (int j = -1; j <= 1; j++)
			{
				for (int k = -1; k <= 1; k++)
				{
					if (!dictionary.TryGetValue(Key(num6 + j, num7 + k), out var value))
					{
						continue;
					}
					foreach (int item in value)
					{
						double num8 = num4 - list[item];
						double num9 = num5 - list2[item];
						double num10 = num8 * num8 + num9 * num9;
						if (num10 < 13.0)
						{
							num++;
							if (num10 < 1.5)
							{
								num2++;
							}
						}
					}
				}
			}
			int count = list.Count;
			list.Add(num4);
			list2.Add(num5);
			num3++;
			long key = Key(num6, num7);
			if (!dictionary.TryGetValue(key, out var value2))
			{
				value2 = (dictionary[key] = new List<int>());
			}
			value2.Add(count);
		}
		if (num > 0)
		{
			DSPCalculatorPlusLog.Warn("[poles][diag] proximity audit: " + num + " pole-pair(s) closer than DSP min distance out of " + num3 + " node(s) (" + num2 + " effectively stacked). In-game these overlap or fail to place - report this line.");
		}
		else
		{
			DSPCalculatorPlusLog.Info("[poles][diag] proximity audit: " + num3 + " node(s), none below DSP min distance (local coords clean; any visible overlap is sphere-seam).");
		}
	}

	private static void LogWorstCoverage(BlueprintBuilding[] buildings, int nBuildings, List<BlueprintBuilding> poles, HashSet<long> underBelt)
	{
		if (poles.Count == 0)
		{
			return;
		}
		Dictionary<long, List<int>> spatial = new Dictionary<long, List<int>>();
		Dictionary<long, List<int>> spatial2 = new Dictionary<long, List<int>>();
		List<int> list = new List<int>();
		List<int> list2 = new List<int>();
		List<bool> list3 = new List<bool>();
		int num = 0;
		for (int i = 0; i < poles.Count; i++)
		{
			BlueprintBuilding obj = poles[i];
			int num2 = (int)Math.Round(obj.localOffset_x);
			int num3 = (int)Math.Round(obj.localOffset_y);
			bool flag = underBelt == null || !underBelt.Contains(Key(num2, num3));
			if (!flag)
			{
				num++;
			}
			int count = list.Count;
			list.Add(num2);
			list2.Add(num3);
			list3.Add(flag);
			long bk = Key(FloorDiv(num2, 32), FloorDiv(num3, 32));
			AddIdx(spatial, bk, count);
			if (flag)
			{
				AddIdx(spatial2, bk, count);
			}
		}
		int consumers;
		double num4 = WorstDistance(buildings, nBuildings, spatial, list, list2, 0, out consumers);
		int consumers2;
		double num5 = WorstDistance(buildings, nBuildings, spatial2, list, list2, 0, out consumers2);
		DSPCalculatorPlusLog.Info("[poles][diag] worst consumer->nearest-pole distance over " + consumers + " consumer(s) (Tesla reach=10.5): " + num4.ToString("0.00") + " with all poles; " + num5.ToString("0.00") + " if the " + num + " under-belt pole(s) are dropped.");
		LogWorstConsumers(buildings, nBuildings, spatial, list, list2, 5);
	}

	private static void AddIdx(Dictionary<long, List<int>> spatial, long bk, int idx)
	{
		if (!spatial.TryGetValue(bk, out var value))
		{
			value = (spatial[bk] = new List<int>());
		}
		value.Add(idx);
	}

	private static double WorstDistance(BlueprintBuilding[] buildings, int nBuildings, Dictionary<long, List<int>> spatial, List<int> px, List<int> py, int point, out int consumers)
	{
		consumers = 0;
		double num = 0.0;
		for (int i = 0; i < nBuildings; i++)
		{
			BlueprintBuilding val = buildings[i];
			if (!IsConsumer(val.itemId))
			{
				continue;
			}
			consumers++;
			double num2 = val.localOffset_x;
			double num3 = val.localOffset_y;
			int num4 = FloorDiv((int)Math.Round(num2), 32);
			int num5 = FloorDiv((int)Math.Round(num3), 32);
			double num6 = double.MaxValue;
			for (int j = -1; j <= 1; j++)
			{
				for (int k = -1; k <= 1; k++)
				{
					if (!spatial.TryGetValue(Key(num4 + j, num5 + k), out var value))
					{
						continue;
					}
					foreach (int item in value)
					{
						double num7 = num2 - (double)px[item];
						double num8 = num3 - (double)py[item];
						double num9 = num7 * num7 + num8 * num8;
						if (num9 < num6)
						{
							num6 = num9;
						}
					}
				}
			}
			double num10 = ((num6 == double.MaxValue) ? 999.0 : Math.Sqrt(num6));
			if (num10 > num)
			{
				num = num10;
			}
		}
		return num;
	}

	private static void LogWorstConsumers(BlueprintBuilding[] buildings, int nBuildings, Dictionary<long, List<int>> spatial, List<int> px, List<int> py, int topN)
	{
		List<double> worst = new List<double>();
		List<float> list = new List<float>();
		List<float> list2 = new List<float>();
		List<int> list3 = new List<int>();
		for (int i = 0; i < nBuildings; i++)
		{
			BlueprintBuilding val = buildings[i];
			if (!IsConsumer(val.itemId))
			{
				continue;
			}
			float localOffset_x = val.localOffset_x;
			float localOffset_y = val.localOffset_y;
			int num = FloorDiv((int)Math.Round(localOffset_x), 32);
			int num2 = FloorDiv((int)Math.Round(localOffset_y), 32);
			double num3 = double.MaxValue;
			for (int j = -1; j <= 1; j++)
			{
				for (int k = -1; k <= 1; k++)
				{
					if (!spatial.TryGetValue(Key(num + j, num2 + k), out var value))
					{
						continue;
					}
					foreach (int item2 in value)
					{
						double num4 = localOffset_x - (float)px[item2];
						double num5 = localOffset_y - (float)py[item2];
						double num6 = num4 * num4 + num5 * num5;
						if (num6 < num3)
						{
							num3 = num6;
						}
					}
				}
			}
			double item = ((num3 == double.MaxValue) ? 999.0 : Math.Sqrt(num3));
			worst.Add(item);
			list.Add(localOffset_x);
			list2.Add(localOffset_y);
			list3.Add(val.itemId);
		}
		int[] array = new int[worst.Count];
		for (int l = 0; l < array.Length; l++)
		{
			array[l] = l;
		}
		Array.Sort(array, (int a, int c) => worst[c].CompareTo(worst[a]));
		int num7 = Math.Min(topN, array.Length);
		for (int num8 = 0; num8 < num7; num8++)
		{
			int index = array[num8];
			ItemProto val2 = ((ProtoSet<ItemProto>)(object)LDB.items).Select(list3[index]);
			string text = ((val2 != null && !string.IsNullOrEmpty(((Proto)val2).name)) ? ((Proto)val2).name : ("item " + list3[index]));
			DSPCalculatorPlusLog.Info("[poles][diag] worst consumer #" + (num8 + 1) + ": " + text + " (id " + list3[index] + ") at local(" + list[index].ToString("0.#") + "," + list2[index].ToString("0.#") + ") dist=" + worst[index].ToString("0.00") + " to nearest pole.");
		}
	}

	private static float ConnectOf(int itemId)
	{
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select(itemId);
		if (val == null || val.prefabDesc == null)
		{
			return 0f;
		}
		return val.prefabDesc.powerConnectDistance;
	}

	private static void LogConnectivity(List<BlueprintBuilding> poles, HashSet<long> underBelt)
	{
		int count = poles.Count;
		if (count < 2)
		{
			return;
		}
		int[] array = new int[count];
		int[] array2 = new int[count];
		float[] array3 = new float[count];
		bool[] array4 = new bool[count];
		Dictionary<long, List<int>> dictionary = new Dictionary<long, List<int>>();
		int num = 64;
		for (int i = 0; i < count; i++)
		{
			array[i] = (int)Math.Round(poles[i].localOffset_x);
			array2[i] = (int)Math.Round(poles[i].localOffset_y);
			array3[i] = ConnectOf(poles[i].itemId);
			array4[i] = underBelt == null || !underBelt.Contains(Key(array[i], array2[i]));
			long key = Key(FloorDiv(array[i], num), FloorDiv(array2[i], num));
			if (!dictionary.TryGetValue(key, out var value))
			{
				value = (dictionary[key] = new List<int>());
			}
			value.Add(i);
		}
		int largest;
		int num2 = CountComponents(count, array, array2, array3, dictionary, num, openOnly: false, array4, out largest);
		int largest2;
		int num3 = CountComponents(count, array, array2, array3, dictionary, num, openOnly: true, array4, out largest2);
		int num4 = 0;
		for (int j = 0; j < count; j++)
		{
			if (array4[j])
			{
				num4++;
			}
		}
		if (num2 <= 1)
		{
			DSPCalculatorPlusLog.Info("[poles][diag] connectivity: all " + count + " pole(s) form ONE network.");
		}
		else
		{
			DSPCalculatorPlusLog.Warn("[poles][diag] connectivity: poles split into " + num2 + " SEPARATE networks (largest " + largest + " of " + count + "). Needs bridging poles.");
		}
		if (num3 <= 1)
		{
			DSPCalculatorPlusLog.Info("[poles][diag] connectivity (open poles only, " + num4 + "): ONE network - safe against under-belt rejection.");
			return;
		}
		DSPCalculatorPlusLog.Warn("[poles][diag] connectivity (open poles only, " + num4 + "): " + num3 + " SEPARATE networks (largest " + largest2 + "). Under-belt poles are bridging these; if the game rejects them the pasted network SPLITS -> stranded clusters read as 'no power network'. This is the residual-unpowered cause.");
	}

	private static int CountComponents(int n, int[] px, int[] py, float[] conn, Dictionary<long, List<int>> spatial, int bkt, bool openOnly, bool[] open, out int largest)
	{
		int[] array = new int[n];
		for (int i = 0; i < n; i++)
		{
			array[i] = i;
		}
		for (int j = 0; j < n; j++)
		{
			if (openOnly && !open[j])
			{
				continue;
			}
			int num = FloorDiv(px[j], bkt);
			int num2 = FloorDiv(py[j], bkt);
			for (int k = -1; k <= 1; k++)
			{
				for (int l = -1; l <= 1; l++)
				{
					if (!spatial.TryGetValue(Key(num + k, num2 + l), out var value))
					{
						continue;
					}
					foreach (int item in value)
					{
						if (item > j && (!openOnly || open[item]))
						{
							double num3 = px[j] - px[item];
							double num4 = py[j] - py[item];
							double num5 = Math.Min(conn[j], conn[item]);
							if (num3 * num3 + num4 * num4 <= num5 * num5)
							{
								Union(array, j, item);
							}
						}
					}
				}
			}
		}
		int num6 = 0;
		largest = 0;
		Dictionary<int, int> dictionary = new Dictionary<int, int>();
		for (int m = 0; m < n; m++)
		{
			if (!openOnly || open[m])
			{
				int key = Find(array, m);
				dictionary.TryGetValue(key, out var value2);
				dictionary[key] = value2 + 1;
			}
		}
		foreach (KeyValuePair<int, int> item2 in dictionary)
		{
			num6++;
			if (item2.Value > largest)
			{
				largest = item2.Value;
			}
		}
		return num6;
	}

	private static int Find(int[] parent, int x)
	{
		while (parent[x] != x)
		{
			parent[x] = parent[parent[x]];
			x = parent[x];
		}
		return x;
	}

	private static void Union(int[] parent, int a, int b)
	{
		int num = Find(parent, a);
		int num2 = Find(parent, b);
		if (num != num2)
		{
			parent[num] = num2;
		}
	}

	private static int PrunePoles(BlueprintBuilding[] buildings, int nBuildings, List<BlueprintBuilding> poles, int startCount, HashSet<long> underBelt)
	{
		int count = poles.Count;
		if (count < 3)
		{
			return 0;
		}
		int[] array = new int[count];
		int[] array2 = new int[count];
		float[] array3 = new float[count];
		float[] array4 = new float[count];
		bool[] array5 = new bool[count];
		Dictionary<long, List<int>> dictionary = new Dictionary<long, List<int>>();
		for (int i = 0; i < count; i++)
		{
			array[i] = (int)Math.Round(poles[i].localOffset_x);
			array2[i] = (int)Math.Round(poles[i].localOffset_y);
			array3[i] = RawCoverOf(poles[i].itemId);
			array4[i] = ConnectOf(poles[i].itemId);
			array5[i] = underBelt == null || !underBelt.Contains(Key(array[i], array2[i]));
			AddIdx(dictionary, Key(FloorDiv(array[i], 32), FloorDiv(array2[i], 32)), i);
		}
		List<int> list = new List<int>();
		List<int> list2 = new List<int>();
		for (int j = 0; j < nBuildings; j++)
		{
			if (IsConsumer(buildings[j].itemId))
			{
				list.Add((int)Math.Round(buildings[j].localOffset_x));
				list2.Add((int)Math.Round(buildings[j].localOffset_y));
			}
		}
		int count2 = list.Count;
		Dictionary<long, List<int>> dictionary2 = new Dictionary<long, List<int>>();
		for (int k = 0; k < count2; k++)
		{
			AddIdx(dictionary2, Key(FloorDiv(list[k], 32), FloorDiv(list2[k], 32)), k);
		}
		List<int>[] array6 = new List<int>[count];
		int[] array7 = new int[count2];
		for (int l = 0; l < count; l++)
		{
			List<int> list3 = new List<int>();
			double num = (double)array3[l] * (double)array3[l];
			double num2 = (double)array3[l] * 0.86 * (double)array3[l] * 0.86;
			int num3 = FloorDiv(array[l], 32);
			int num4 = FloorDiv(array2[l], 32);
			for (int m = -1; m <= 1; m++)
			{
				for (int n = -1; n <= 1; n++)
				{
					if (!dictionary2.TryGetValue(Key(num3 + m, num4 + n), out var value))
					{
						continue;
					}
					foreach (int item in value)
					{
						double num5 = list[item] - array[l];
						double num6 = list2[item] - array2[l];
						double num7 = num5 * num5 + num6 * num6;
						if (num7 <= num)
						{
							list3.Add(item);
						}
						if (array5[l] && num7 <= num2)
						{
							array7[item]++;
						}
					}
				}
			}
			array6[l] = list3;
		}
		bool[] array8 = new bool[count];
		int num8 = 0;
		for (int num9 = count - 1; num9 >= 0; num9--)
		{
			double num10 = (double)array3[num9] * 0.86 * (double)array3[num9] * 0.86;
			bool flag = false;
			foreach (int item2 in array6[num9])
			{
				double num11 = list[item2] - array[num9];
				double num12 = list2[item2] - array2[num9];
				int num13 = ((array5[num9] && num11 * num11 + num12 * num12 <= num10) ? 1 : 0);
				if (array7[item2] - num13 <= 0)
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				bool flag2 = false;
				int num14 = 0;
				double num15 = (double)array4[num9] * (double)array4[num9];
				int num16 = FloorDiv(array[num9], 32);
				int num17 = FloorDiv(array2[num9], 32);
				for (int num18 = -1; num18 <= 1; num18++)
				{
					for (int num19 = -1; num19 <= 1; num19++)
					{
						if (!dictionary.TryGetValue(Key(num16 + num18, num17 + num19), out var value2))
						{
							continue;
						}
						foreach (int item3 in value2)
						{
							if (item3 != num9 && !array8[item3])
							{
								double num20 = array[item3] - array[num9];
								double num21 = array2[item3] - array2[num9];
								double num22 = num20 * num20 + num21 * num21;
								if (num22 <= num15)
								{
									num14++;
								}
								if (array5[item3] && num22 <= 49.0)
								{
									flag2 = true;
								}
							}
						}
					}
				}
				if (flag2 && num14 >= 2)
				{
					array8[num9] = true;
					num8++;
					foreach (int item4 in array6[num9])
					{
						double num23 = list[item4] - array[num9];
						double num24 = list2[item4] - array2[num9];
						if (array5[num9] && num23 * num23 + num24 * num24 <= num10)
						{
							array7[item4]--;
						}
					}
				}
			}
		}
		if (num8 == 0)
		{
			return 0;
		}
		List<BlueprintBuilding> list4 = new List<BlueprintBuilding>(count - num8);
		for (int num25 = 0; num25 < count; num25++)
		{
			if (!array8[num25])
			{
				poles[num25].index = startCount + list4.Count;
				list4.Add(poles[num25]);
			}
		}
		poles.Clear();
		poles.AddRange(list4);
		return num8;
	}

	private static void LogPoleDiagnosticOnce()
	{
		if (!_diagLogged)
		{
			_diagLogged = true;
			LogPole("Wireless Power Tower", 2202);
			LogPole("Tesla Tower", 2201);
		}
	}

	private static void LogPole(string label, int itemId)
	{
		ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items).Select(itemId);
		if (val == null || val.prefabDesc == null)
		{
			DSPCalculatorPlusLog.Info("[poles][diag] " + label + " (item " + itemId + ") proto not found.");
			return;
		}
		PrefabDesc prefabDesc = val.prefabDesc;
		GetTileHalfExtents(prefabDesc, itemId, 0f, out var hx, out var hy);
		bool flag = GameMain.history == null || GameMain.history.ItemUnlocked(itemId);
		DSPCalculatorPlusLog.Info("[poles][diag] " + label + " (item " + itemId + "): powerNode=" + prefabDesc.isPowerNode + " cover=" + prefabDesc.powerCoverRadius.ToString("0.#") + " connect=" + prefabDesc.powerConnectDistance.ToString("0.#") + " footprint=" + (2 * hx + 1) + "x" + (2 * hy + 1) + " unlocked=" + flag + ".");
	}

	private static long Key(int x, int y)
	{
		return (long)(x + 1000000) * 4000000L + (y + 1000000);
	}
}
internal static class StackingPlusCompat
{
	internal const string StackingPlusGuid = "com.zicarius.StackingPlus";

	private static FieldInfo _fMaxStackSize;

	private static bool _ready;

	private static bool _present;

	private static bool _loggedPresent;

	public static void Apply(Harmony harmony)
	{
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Expected O, but got Unknown
		Type type = AccessTools.TypeByName("DSPCalculator.Logic.CalcDB");
		_fMaxStackSize = ((type != null) ? AccessTools.Field(type, "maxStackSize") : null);
		if (_fMaxStackSize == null)
		{
			DSPCalculatorPlusLog.Warn("[stackcompat] CalcDB.maxStackSize not found - StackingPlus compat disabled.");
			return;
		}
		Type type2 = AccessTools.TypeByName("DSPCalculator.Bp.BpConnector");
		MethodInfo methodInfo = ((type2 != null) ? AccessTools.Method(type2, "GenerateFullBlueprint", (Type[])null, (Type[])null) : null);
		if (methodInfo == null)
		{
			DSPCalculatorPlusLog.Warn("[stackcompat] BpConnector.GenerateFullBlueprint not found - StackingPlus compat disabled.");
			return;
		}
		harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(StackingPlusCompat), "SyncPrefix", (Type[])null)
		{
			priority = 600
		}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		_ready = true;
		DSPCalculatorPlusLog.Info("[stackcompat] installed: syncs CalcDB.maxStackSize to the live cargo-stacking cap before generation.");
	}

	public static void Detect()
	{
		try
		{
			if ((_present = Chainloader.PluginInfos != null && Chainloader.PluginInfos.ContainsKey("com.zicarius.StackingPlus")) && !_loggedPresent)
			{
				_loggedPresent = true;
				DSPCalculatorPlusLog.Info("[stackcompat] StackingPlus detected - blueprints will plan against its raised stacking cap.");
			}
		}
		catch (Exception ex)
		{
			DSPCalculatorPlusLog.Warn("[stackcompat] detection failed: " + ex.Message);
		}
	}

	private static void SyncPrefix()
	{
		try
		{
			if (_ready && _present && Plugin.Config.EnableStackingPlusCompat.Value && GameMain.history != null)
			{
				int inserterStackOutput = GameMain.history.inserterStackOutput;
				int num = ((inserterStackOutput > 4) ? inserterStackOutput : 4);
				double a = Convert.ToDouble(_fMaxStackSize.GetValue(null));
				if ((int)Math.Round(a) != num)
				{
					_fMaxStackSize.SetValue(null, (double)num);
					DSPCalculatorPlusLog.Info("[stackcompat] CalcDB.maxStackSize " + a + " -> " + num + " (live cargo-stacking cap).");
				}
			}
		}
		catch (Exception ex)
		{
			DSPCalculatorPlusLog.Warn("[stackcompat] sync failed: " + ex.Message);
		}
	}
}
internal static class TierOverridePatch
{
	private static FieldInfo _fSolution;

	private static FieldInfo _fBeltsAvailable;

	private static FieldInfo _fSortersAvailable;

	private static FieldInfo _fBeltItemId;

	private static FieldInfo _fSorterGrade;

	private static FieldInfo _fBeltsAscending;

	private static bool _ready;

	private const int BeltMk1Id = 2001;

	private const int BeltMk2Id = 2002;

	private const int BeltMk3Id = 2003;

	private const int SorterMk1Id = 2011;

	private const int SorterMk2Id = 2012;

	private const int SorterMk3Id = 2013;

	private const int SorterMk4Id = 2014;

	private const string DspCalcMarker = "DSPCalcBPTemp";

	private static readonly ConditionalWeakTable<BlueprintData, object> _diagProcessed = new ConditionalWeakTable<BlueprintData, object>();

	private static readonly object DiagMarker = new object();

	private static bool _lastBeltFellBack;

	private static bool _lastSorterFellBack;

	public static void Apply(Harmony harmony)
	{
		//IL_0250: Unknown result type (might be due to invalid IL or missing references)
		//IL_0265: Unknown result type (might be due to invalid IL or missing references)
		//IL_026a: Unknown result type (might be due to invalid IL or missing references)
		//IL_027d: Expected O, but got Unknown
		//IL_027d: Expected O, but got Unknown
		//IL_02e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f7: Expected O, but got Unknown
		Type type = AccessTools.TypeByName("DSPCalculator.Bp.BpConnector");
		if (type == null)
		{
			DSPCalculatorPlusLog.Warn("[tier] BpConnector not found - tier override disabled (DSPCalculator missing or renamed).");
			return;
		}
		MethodInfo methodInfo = AccessTools.Method(type, "GenerateFullBlueprint", (Type[])null, (Type[])null);
		if (methodInfo == null)
		{
			DSPCalculatorPlusLog.Warn("[tier] BpConnector.GenerateFullBlueprint not found - tier override disabled.");
			return;
		}
		Type type2 = AccessTools.TypeByName("DSPCalculator.Bp.BpBeltInfo");
		Type type3 = AccessTools.TypeByName("DSPCalculator.Bp.BpSorterInfo");
		Type type4 = AccessTools.TypeByName("DSPCalculator.Bp.BpDB");
		_fSolution = AccessTools.Field(type, "solution");
		Type type5 = _fSolution?.FieldType;
		_fBeltsAvailable = ((type5 != null) ? AccessTools.Field(type5, "beltsAvailable") : null);
		_fSortersAvailable = ((type5 != null) ? AccessTools.Field(type5, "sortersAvailable") : null);
		_fBeltItemId = ((type2 != null) ? AccessTools.Field(type2, "itemId") : null);
		_fSorterGrade = ((type3 != null) ? AccessTools.Field(type3, "grade") : null);
		_fBeltsAscending = ((type4 != null) ? AccessTools.Field(type4, "beltsAscending") : null);
		if (_fSolution == null || _fBeltsAvailable == null || _fSortersAvailable == null || _fBeltItemId == null || _fSorterGrade == null || _fBeltsAscending == null)
		{
			DSPCalculatorPlusLog.Error("[tier] one or more DSPCalculator members not found - tier override disabled. solution=" + (_fSolution != null) + " belts=" + (_fBeltsAvailable != null) + " sorters=" + (_fSortersAvailable != null) + " beltItemId=" + (_fBeltItemId != null) + " sorterGrade=" + (_fSorterGrade != null) + " beltsAscending=" + (_fBeltsAscending != null));
		}
		else
		{
			_ready = true;
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(TierOverridePatch), "Prefix", (Type[])null), new HarmonyMethod(typeof(TierOverridePatch), "Postfix", (Type[])null)
			{
				priority = 700
			}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			DSPCalculatorPlusLog.Info("[tier] Group A patch installed on BpConnector.GenerateFullBlueprint.");
			MethodInfo methodInfo2 = AccessTools.Method(typeof(PlayerController), "OpenBlueprintPasteMode", new Type[3]
			{
				typeof(BlueprintData),
				typeof(string),
				typeof(bool)
			}, (Type[])null);
			if (methodInfo2 != null)
			{
				harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(TierOverridePatch), "DiagPastePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				DSPCalculatorPlusLog.Info("[tier] diagnostic installed on PlayerController.OpenBlueprintPasteMode (logs the actual belt/sorter tier distribution in every generated blueprint).");
			}
		}
	}

	private static void DiagPastePrefix(BlueprintData blueprint, string fullPath)
	{
		try
		{
			if (blueprint == null || blueprint.buildings == null || blueprint.buildings.Length == 0 || string.IsNullOrEmpty(fullPath) || fullPath.IndexOf("DSPCalcBPTemp", StringComparison.OrdinalIgnoreCase) < 0 || _diagProcessed.TryGetValue(blueprint, out var _))
			{
				return;
			}
			_diagProcessed.Add(blueprint, DiagMarker);
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			int num7 = 0;
			BlueprintBuilding[] buildings = blueprint.buildings;
			for (int i = 0; i < buildings.Length; i++)
			{
				switch (buildings[i].itemId)
				{
				case 2001:
					num++;
					break;
				case 2002:
					num2++;
					break;
				case 2003:
					num3++;
					break;
				case 2011:
					num4++;
					break;
				case 2012:
					num5++;
					break;
				case 2013:
					num6++;
					break;
				case 2014:
					num7++;
					break;
				}
			}
			PluginConfig config = Plugin.Config;
			BeltTier value2 = config.BeltTierOverride.Value;
			SorterTier value3 = config.SorterTierOverride.Value;
			DSPCalculatorPlusLog.Info("[tier][diag] config: BeltTierOverride=" + value2.ToString() + (_lastBeltFellBack ? " (UNAVAILABLE for this blueprint - correctly fell back to Auto instead of forcing a locked/missing tier)" : "") + " SorterTierOverride=" + value3.ToString() + (_lastSorterFellBack ? " (UNAVAILABLE for this blueprint - correctly fell back to Auto)" : "") + ". Final blueprint belts: Mk1=" + num + " Mk2=" + num2 + " Mk3=" + num3 + "; sorters: Mk1=" + num4 + " Mk2=" + num5 + " Mk3=" + num6 + " Mk4(pile)=" + num7 + ".");
			if (value2 != BeltTier.Auto && !_lastBeltFellBack && ((value2 != BeltTier.Mk1 && num > 0) || (value2 != BeltTier.Mk2 && num2 > 0) || (value2 != BeltTier.Mk3 && num3 > 0)))
			{
				DSPCalculatorPlusLog.Warn("[tier][diag] BeltTierOverride=" + value2.ToString() + " but belts of OTHER tiers are present in the final blueprint - the override did not fully apply. Report this line.");
			}
			if (value3 != SorterTier.Auto && !_lastSorterFellBack && ((value3 != SorterTier.Mk1 && num4 > 0) || (value3 != SorterTier.Mk2 && num5 > 0) || (value3 != SorterTier.Mk3 && num6 > 0) || (value3 != SorterTier.Mk4 && num7 > 0)))
			{
				DSPCalculatorPlusLog.Warn("[tier][diag] SorterTierOverride=" + value3.ToString() + " but sorters of OTHER tiers are present in the final blueprint - the override did not fully apply. Report this line.");
			}
		}
		catch (Exception ex)
		{
			DSPCalculatorPlusLog.Error("[tier][diag] tier-distribution diagnostic failed (harmless, informational only): " + ex);
		}
	}

	private static void Prefix(object __instance, out object[] __state)
	{
		__state = null;
		if (!_ready || __instance == null)
		{
			return;
		}
		PluginConfig config = Plugin.Config;
		BeltTier value = config.BeltTierOverride.Value;
		SorterTier value2 = config.SorterTierOverride.Value;
		_lastBeltFellBack = false;
		_lastSorterFellBack = false;
		if (value == BeltTier.Auto && value2 == SorterTier.Auto)
		{
			return;
		}
		object value3 = _fSolution.GetValue(__instance);
		if (value3 == null)
		{
			return;
		}
		object obj = null;
		object obj2 = null;
		if (value != BeltTier.Auto)
		{
			IList list = _fBeltsAvailable.GetValue(value3) as IList;
			IList list2 = FilterBeltsToTier(list, (int)value);
			if (list2 != null)
			{
				obj = list;
				_fBeltsAvailable.SetValue(value3, list2);
			}
			else
			{
				_lastBeltFellBack = true;
			}
		}
		if (value2 != SorterTier.Auto)
		{
			IList list3 = _fSortersAvailable.GetValue(value3) as IList;
			IList list4 = FilterSortersToGrade(list3, (int)value2);
			if (list4 != null)
			{
				obj2 = list3;
				_fSortersAvailable.SetValue(value3, list4);
			}
			else
			{
				_lastSorterFellBack = true;
			}
		}
		if (obj != null || obj2 != null)
		{
			__state = new object[3] { value3, obj, obj2 };
		}
	}

	private static void Postfix(object[] __state)
	{
		if (__state != null)
		{
			object obj = __state[0];
			object obj2 = __state[1];
			object obj3 = __state[2];
			if (obj2 != null)
			{
				_fBeltsAvailable.SetValue(obj, obj2);
			}
			if (obj3 != null)
			{
				_fSortersAvailable.SetValue(obj, obj3);
			}
		}
	}

	private static IList FilterBeltsToTier(IList current, int tier)
	{
		if (current == null || current.Count == 0)
		{
			return null;
		}
		IList list = _fBeltsAscending.GetValue(null) as IList;
		int num = tier - 1;
		if (list == null || num < 0 || num >= list.Count)
		{
			DSPCalculatorPlusLog.Warn("[tier] belt Mk" + tier + " is out of range (only " + (list?.Count ?? 0) + " belt tiers exist) - leaving belt tier on Auto.");
			return null;
		}
		int num2 = (int)_fBeltItemId.GetValue(list[num]);
		IList list2 = (IList)Activator.CreateInstance(current.GetType());
		foreach (object item in current)
		{
			if ((int)_fBeltItemId.GetValue(item) == num2)
			{
				list2.Add(item);
			}
		}
		if (list2.Count == 0)
		{
			DSPCalculatorPlusLog.Warn("[tier] belt Mk" + tier + " (itemId " + num2 + ") is not in the available belts (locked under tech limit?) - leaving belt tier on Auto.");
			return null;
		}
		DSPCalculatorPlusLog.Info("[tier] belt forced to Mk" + tier + " (itemId " + num2 + ").");
		return list2;
	}

	private static IList FilterSortersToGrade(IList current, int tier)
	{
		if (current == null || current.Count == 0)
		{
			return null;
		}
		IList list = (IList)Activator.CreateInstance(current.GetType());
		foreach (object item in current)
		{
			int num = (int)_fSorterGrade.GetValue(item);
			if ((tier >= 4) ? (num >= 4) : (num == tier))
			{
				list.Add(item);
			}
		}
		if (list.Count == 0)
		{
			DSPCalculatorPlusLog.Warn("[tier] sorter Mk" + tier + " is not in the available sorters (locked under tech limit?) - leaving sorter tier on Auto.");
			return null;
		}
		DSPCalculatorPlusLog.Info("[tier] sorter forced to Mk" + tier + ".");
		return list;
	}
}
[BepInPlugin("com.zicarius.DSPCalculatorPlus", "DSPCalculatorPlus", "1.0.0")]
[BepInProcess("DSPGAME.exe")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "com.zicarius.DSPCalculatorPlus";

	public const string PluginName = "DSPCalculatorPlus";

	public const string PluginVersion = "1.0.0";

	public const string DSPCalculatorGuid = "com.GniMaerd.DSPCalculator";

	internal static PluginConfig Config;

	private static Harmony _harmony;

	private void Awake()
	{
		//IL_0140: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Expected O, but got Unknown
		Config = new PluginConfig(((BaseUnityPlugin)this).Config);
		DSPCalculatorPlusLog.Init(((BaseUnityPlugin)this).Logger, Config.DebugLog);
		DSPCalculatorPlusLog.Info("[config] belt=" + Config.BeltTierOverride.Value.ToString() + ", sorter=" + Config.SorterTierOverride.Value.ToString() + ", overflowFix(externalize)=" + Config.EnableMultiLaneOverflowFix.Value + ", pushBeltStacking=" + Config.PushBeltStackingOnOverflow.Value + ", autoPowerPoles=" + Config.AutoPowerPoles.Value.ToString() + ", stackingPlusCompat=" + Config.EnableStackingPlusCompat.Value + ", debug=" + Config.DebugLog.Value);
		try
		{
			_harmony = new Harmony("com.zicarius.DSPCalculatorPlus");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			TierOverridePatch.Apply(_harmony);
			OverflowFixPatch.Apply(_harmony);
			PowerPolePatch.Apply(_harmony);
			if (Config.EnableStackingPlusCompat.Value)
			{
				StackingPlusCompat.Apply(_harmony);
				StackingPlusCompat.Detect();
			}
		}
		catch (Exception ex)
		{
			DSPCalculatorPlusLog.Error("Harmony PatchAll failed: " + ex);
		}
		DumpTargetMethodSignatures();
	}

	private static void DumpTargetMethodSignatures()
	{
		string[][] array = new string[2][]
		{
			new string[2] { "DSPCalculator.Bp.BpConnector", "GenerateFullBlueprint" },
			new string[2] { "DSPCalculator.Bp.BpConnector", "CalcItemSumInfos" }
		};
		foreach (string[] array2 in array)
		{
			try
			{
				Type type = AccessTools.TypeByName(array2[0]);
				if (type == null)
				{
					DSPCalculatorPlusLog.Warn("[diag] Type not found: " + array2[0] + " (DSPCalculator not installed, or renamed)");
					continue;
				}
				MethodInfo methodInfo = AccessTools.Method(type, array2[1], (Type[])null, (Type[])null);
				if (methodInfo == null)
				{
					DSPCalculatorPlusLog.Warn("[diag] Method not found: " + array2[0] + "." + array2[1]);
					continue;
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.Append("[diag] ").Append(array2[0]).Append(".")
					.Append(array2[1])
					.Append("(");
				for (int j = 0; j < parameters.Length; j++)
				{
					if (j > 0)
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append(parameters[j].ParameterType.Name).Append(" ").Append(parameters[j].Name);
				}
				stringBuilder.Append(") : ").Append(methodInfo.ReturnType.Name);
				DSPCalculatorPlusLog.Info(stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				DSPCalculatorPlusLog.Error("[diag] Failed to inspect " + array2[0] + "." + array2[1] + ": " + ex.Message);
			}
		}
	}

	private void Start()
	{
		if (Config.EnableStackingPlusCompat.Value)
		{
			StackingPlusCompat.Detect();
		}
	}

	private void OnDestroy()
	{
		Harmony harmony = _harmony;
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		_harmony = null;
	}
}
public enum BeltTier
{
	Auto,
	Mk1,
	Mk2,
	Mk3
}
public enum SorterTier
{
	Auto,
	Mk1,
	Mk2,
	Mk3,
	Mk4
}
public enum PowerPoleType
{
	Off,
	TeslaTower
}
internal sealed class PluginConfig
{
	public readonly ConfigEntry<BeltTier> BeltTierOverride;

	public readonly ConfigEntry<SorterTier> SorterTierOverride;

	public readonly ConfigEntry<bool> EnableMultiLaneOverflowFix;

	public readonly ConfigEntry<bool> PushBeltStackingOnOverflow;

	public readonly ConfigEntry<PowerPoleType> AutoPowerPoles;

	public readonly ConfigEntry<bool> PolesUnderRaisedBelts;

	public readonly ConfigEntry<bool> EnableStackingPlusCompat;

	public readonly ConfigEntry<bool> DebugLog;

	public PluginConfig(ConfigFile config)
	{
		BeltTierOverride = config.Bind<BeltTier>("General", "BeltTierOverride", BeltTier.Auto, "Force a specific belt tier in generated blueprints (applies to every item). Auto = defer to DSPCalculator's own 'highest belt' / tech-limit setting.");
		SorterTierOverride = config.Bind<SorterTier>("General", "SorterTierOverride", SorterTier.Auto, "Force a specific sorter tier in generated blueprints (applies to every item). Auto = defer to DSPCalculator's own setting. Forcing below Mk4 bypasses pile-sorter output stacking.");
		EnableMultiLaneOverflowFix = config.Bind<bool>("General", "EnableMultiLaneOverflowFix", true, "When an item's throughput exceeds one belt of the fastest tier (which stock DSPCalculator refuses to generate), supply that item as an EXTERNAL logistics input instead of failing. The item is no longer produced inside the blueprint - you feed it from your ILS/PLS network - which is how the blackbox blueprint model is meant to scale. Target outputs cannot be externalized and still fail cleanly.");
		PushBeltStackingOnOverflow = config.Bind<bool>("General", "PushBeltStackingOnOverflow", true, "Last resort when a blueprint still fails because a single block's output belt can't carry its rate (typically an un-externalizable BYPRODUCT like hydrogen at high quantity). Only activates on an otherwise-failing generation: it raises DSPCalculator's belt-stacking to the vanilla max (4x cargo) and regenerates, which ~4x's the throughput ceiling. The resulting blueprint ASSUMES that cargo stacking, so you need the pile/proliferator stacking tech to run it at full rate; without it, belts under-carry. Set false to keep failing cleanly instead. Caps at DSPCalculator's stacking cap (vanilla 4x; higher if the StackingPlus mod is installed - see EnableStackingPlusCompat); past that, reduce quantity or split the blueprint.");
		AutoPowerPoles = config.Bind<PowerPoleType>("General", "AutoPowerPoles", PowerPoleType.TeslaTower, "Auto-place electric poles over generated blueprints so the machines get power (DSPCalculator never adds any). Poles land only on empty tiles - never on a machine/belt and never closer than the game's minimum pole spacing - so they can't break the paste. TeslaTower (default) = fill with cheap 1x1 Tesla Towers (full coverage, fits any layout). Off = leave power to you, as stock DSPCalculator does.");
		PolesUnderRaisedBelts = config.Bind<bool>("General", "PolesUnderRaisedBelts", true, "Allow poles on ground tiles that are only occupied by a BELT raised above the pole's height (belt crossings). In dense blueprints DSPCalculator stacks belts overhead, and in-game you can build under a raised belt - so those ground tiles are actually free. This reclaims a large amount of space for poles in big/dense blueprints. If you ever see a pasted pole clipping a low belt, set this false to reserve every belt tile.");
		EnableStackingPlusCompat = config.Bind<bool>("Compatibility", "EnableStackingPlusCompat", true, "When the StackingPlus mod is installed, sync DSPCalculator's stacking cap (CalcDB.maxStackSize) to the live in-game cargo-stacking cap StackingPlus raises (GameMain.history.inserterStackOutput). This lets the overflow fix push belt-stacking past vanilla 4x (e.g. to 8x) so high-throughput byproducts like hydrogen fit on fewer belts. No effect if StackingPlus isn't installed (the live cap stays <=4). Disable to keep DSPCalculator's fixed vanilla 4x cap regardless.");
		DebugLog = config.Bind<bool>("Diagnostics", "DebugLog", false, "Enable verbose diagnostic logging to the BepInEx console.");
	}
}