Decompiled source of DSPTaskMaster v1.2.0

DSPTaskMaster.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;
using crecheng.DSPModSave;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("DSPTaskMaster")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+b46163e8fe60e81ff065399abf54042db0778227")]
[assembly: AssemblyProduct("DSPTaskMaster")]
[assembly: AssemblyTitle("DSPTaskMaster")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace DSPTaskMaster
{
	public enum AlertState
	{
		Below,
		Met,
		Behind
	}
	public struct AlertChange
	{
		public TodoItem Item;

		public AlertState From;

		public AlertState To;
	}
	public class Alerts
	{
		private class Track
		{
			public AlertState Confirmed;

			public AlertState Pending;

			public float PendingSince;
		}

		public const float ReachedDelay = 2f;

		public const float ProblemDelay = 10f;

		private readonly Dictionary<TodoItem, Track> _tracks = new Dictionary<TodoItem, Track>();

		private float _clock;

		public void Reset()
		{
			_tracks.Clear();
		}

		public void Forget(TodoItem item)
		{
			_tracks.Remove(item);
		}

		public static AlertState StateOf(TodoItem item)
		{
			if (item.IsCompleted)
			{
				if (!item.IsBehind())
				{
					return AlertState.Met;
				}
				return AlertState.Behind;
			}
			return AlertState.Below;
		}

		public List<AlertChange> Check(List<TodoItem> items)
		{
			_clock += 1f;
			List<AlertChange> list = new List<AlertChange>();
			if (_tracks.Count > 0)
			{
				HashSet<TodoItem> hashSet = new HashSet<TodoItem>(items);
				List<TodoItem> list2 = new List<TodoItem>();
				foreach (TodoItem key in _tracks.Keys)
				{
					if (!hashSet.Contains(key))
					{
						list2.Add(key);
					}
				}
				foreach (TodoItem item in list2)
				{
					_tracks.Remove(item);
				}
			}
			foreach (TodoItem item2 in items)
			{
				if (item2.Type == TaskType.Manual)
				{
					continue;
				}
				AlertState alertState = StateOf(item2);
				if (!_tracks.TryGetValue(item2, out var value))
				{
					_tracks[item2] = new Track
					{
						Confirmed = alertState,
						Pending = alertState,
						PendingSince = _clock
					};
					continue;
				}
				if (alertState == value.Confirmed)
				{
					value.Pending = alertState;
					continue;
				}
				if (alertState != value.Pending)
				{
					value.Pending = alertState;
					value.PendingSince = _clock;
				}
				float num = ((alertState == AlertState.Met) ? 2f : 10f);
				if (!(_clock - value.PendingSince < num))
				{
					list.Add(new AlertChange
					{
						Item = item2,
						From = value.Confirmed,
						To = alertState
					});
					value.Confirmed = alertState;
				}
			}
			return list;
		}

		public static string Message(AlertChange change, bool alertBehind)
		{
			TodoItem item = change.Item;
			AlertState alertState = change.From;
			AlertState to = change.To;
			if (to == AlertState.Met && alertState == AlertState.Below)
			{
				if (item.Type != TaskType.Research)
				{
					return Loc.T("alert.reached", TaskText.Title(item), TaskText.ShortValue(item));
				}
				return Loc.T("alert.done", TaskText.Title(item));
			}
			if (!alertBehind)
			{
				return null;
			}
			if (item.Type == TaskType.Surplus)
			{
				if (to == AlertState.Below)
				{
					return Loc.T("alert.droppedBelow", TaskText.Title(item), TaskText.FmtSigned(item.CurrentAmount));
				}
			}
			else if (item.Type == TaskType.Power)
			{
				if (to == AlertState.Below)
				{
					return Loc.T("alert.powerBelow", TaskText.Title(item), TaskText.FmtPowerSigned(item.CurrentAmount));
				}
			}
			else if (item.Type == TaskType.ProductionTarget)
			{
				if (to == AlertState.Met && alertState == AlertState.Behind)
				{
					return Loc.T("alert.backOnTrack", TaskText.Title(item));
				}
				switch (to)
				{
				case AlertState.Behind:
					return Loc.T("alert.behind", TaskText.Title(item), TaskText.Fmt(item.ActualAmount), TaskText.Fmt(item.TargetAmount));
				case AlertState.Below:
					return Loc.T("alert.droppedBelow", TaskText.Title(item), TaskText.ShortValue(item));
				}
			}
			return null;
		}

		public static void Announce(List<AlertChange> changes)
		{
			Plugin instance = Plugin.Instance;
			if (changes.Count == 0 || !instance.AlertsEnabled.Value)
			{
				return;
			}
			foreach (AlertChange change in changes)
			{
				string text = Message(change, instance.AlertBehind.Value);
				if (text != null)
				{
					try
					{
						UIRealtimeTip.Popup(text, true, 0);
					}
					catch (Exception ex)
					{
						instance.Log.LogWarning((object)("Could not show alert: " + ex.Message));
					}
				}
			}
		}
	}
	public static class GameData
	{
		private static readonly Dictionary<int, Sprite> ItemIcons = new Dictionary<int, Sprite>();

		public static string ItemName(int itemId)
		{
			ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items)?.Select(itemId);
			if (val == null)
			{
				return null;
			}
			try
			{
				string text = Localization.Translate(((Proto)val).name);
				if (!string.IsNullOrEmpty(text))
				{
					return text;
				}
			}
			catch
			{
			}
			if (!string.IsNullOrEmpty(((Proto)val).name))
			{
				return ((Proto)val).name;
			}
			return null;
		}

		public static string TechName(int techId)
		{
			TechProto val = ((ProtoSet<TechProto>)(object)LDB.techs)?.Select(techId);
			if (val == null)
			{
				return null;
			}
			return TechName(val);
		}

		public static string TechName(TechProto proto)
		{
			string text = Localization.Translate(((Proto)proto).name);
			if (proto.Level <= 0)
			{
				return text;
			}
			return $"{text} {proto.Level}";
		}

		public static string PlanetName(int planetId)
		{
			if (planetId == 0)
			{
				return null;
			}
			try
			{
				GameData data = GameMain.data;
				object result;
				if (data == null)
				{
					result = null;
				}
				else
				{
					GalaxyData galaxy = data.galaxy;
					if (galaxy == null)
					{
						result = null;
					}
					else
					{
						PlanetData obj = galaxy.PlanetById(planetId);
						result = ((obj != null) ? obj.displayName : null);
					}
				}
				return (string)result;
			}
			catch
			{
				return null;
			}
		}

		public static string StarName(int starId)
		{
			GameData data = GameMain.data;
			if (data == null)
			{
				return null;
			}
			GalaxyData galaxy = data.galaxy;
			if (galaxy == null)
			{
				return null;
			}
			StarData obj = galaxy.StarById(starId);
			if (obj == null)
			{
				return null;
			}
			return obj.displayName;
		}

		public static Sprite ItemIcon(int itemId)
		{
			if (ItemIcons.TryGetValue(itemId, out var value))
			{
				return value;
			}
			ItemProto val = ((ProtoSet<ItemProto>)(object)LDB.items)?.Select(itemId);
			if (val == null)
			{
				return null;
			}
			Sprite val2 = val.iconSprite;
			if ((Object)(object)val2 == (Object)null && !string.IsNullOrEmpty(val.IconPath))
			{
				val2 = Resources.Load<Sprite>(val.IconPath);
			}
			if ((Object)(object)val2 != (Object)null)
			{
				ItemIcons[itemId] = val2;
			}
			return val2;
		}

		public static Sprite TechIcon(int techId)
		{
			TechProtoSet techs = LDB.techs;
			if (techs == null)
			{
				return null;
			}
			TechProto obj = ((ProtoSet<TechProto>)(object)techs).Select(techId);
			if (obj == null)
			{
				return null;
			}
			return obj.iconSprite;
		}

		public static IEnumerable<PlanetFactory> GetFactories(int planetId)
		{
			GameData data = GameMain.data;
			if (data == null || data.factories == null)
			{
				yield break;
			}
			for (int i = 0; i < data.factories.Length && i < data.factoryCount; i++)
			{
				PlanetFactory val = data.factories[i];
				if (val != null && (planetId == 0 || val.planetId == planetId))
				{
					yield return val;
				}
			}
		}

		public static FactoryProductionStat GetStat(PlanetFactory factory)
		{
			FactoryProductionStat[] array = GameMain.data?.statistics?.production?.factoryStatPool;
			if (array == null || factory.index < 0 || factory.index >= array.Length)
			{
				return null;
			}
			return array[factory.index];
		}

		public static void GetMadeAndUsed(int planetId, int itemId, out long made, out long used)
		{
			made = 0L;
			used = 0L;
			foreach (PlanetFactory factory in GetFactories(planetId))
			{
				FactoryProductionStat stat = GetStat(factory);
				if (stat != null)
				{
					made += GetProducedLastMinute(stat, itemId);
					used += GetConsumedLastMinute(stat, itemId);
				}
			}
		}

		public static long GetProducedLastMinute(FactoryProductionStat stat, int itemId)
		{
			return GetStatTotal(stat, itemId, 1);
		}

		public static long GetConsumedLastMinute(FactoryProductionStat stat, int itemId)
		{
			return GetStatTotal(stat, itemId, 8);
		}

		private static long GetStatTotal(FactoryProductionStat stat, int itemId, int totalIndex)
		{
			int[] productIndices = stat.productIndices;
			if (productIndices == null || itemId <= 0 || itemId >= productIndices.Length)
			{
				return 0L;
			}
			int num = productIndices[itemId];
			if (num <= 0 || stat.productPool == null || num >= stat.productPool.Length)
			{
				return 0L;
			}
			ProductStat val = stat.productPool[num];
			if (val == null || val.total == null || val.total.Length <= totalIndex)
			{
				return 0L;
			}
			return val.total[totalIndex];
		}

		public static void GetPowerMW(int planetId, out long generationMW, out long demandMW)
		{
			long num = 0L;
			long num2 = 0L;
			if (planetId != 0)
			{
				foreach (PlanetFactory factory in GetFactories(planetId))
				{
					PowerSystem powerSystem = factory.powerSystem;
					if (powerSystem?.netPool == null)
					{
						continue;
					}
					for (int i = 1; i < powerSystem.netCursor && i < powerSystem.netPool.Length; i++)
					{
						PowerNetwork val = powerSystem.netPool[i];
						if (val != null && val.id == i)
						{
							num += val.energyCapacity;
							num2 += val.energyRequired;
						}
					}
				}
			}
			generationMW = num * 60 / 1000000;
			demandMW = num2 * 60 / 1000000;
		}

		public static DysonSphere GetDysonSphere(int starId)
		{
			DysonSphere[] array = GameMain.data?.dysonSpheres;
			GameData data = GameMain.data;
			object obj;
			if (data == null)
			{
				obj = null;
			}
			else
			{
				GalaxyData galaxy = data.galaxy;
				obj = ((galaxy != null) ? galaxy.StarById(starId) : null);
			}
			StarData val = (StarData)obj;
			if (array == null || val == null || val.index < 0 || val.index >= array.Length)
			{
				return null;
			}
			return array[val.index];
		}

		public static long SpherePowerMW(int starId)
		{
			DysonSphere dysonSphere = GetDysonSphere(starId);
			if (dysonSphere == null)
			{
				return 0L;
			}
			return dysonSphere.energyGenCurrentTick * 60 / 1000000;
		}
	}
	public static class GameStyle
	{
		public static readonly Color OverlayPanel = new Color(0.08f, 0.1f, 0.14f, 1f);

		public static readonly Color ButtonColor = new Color(1f, 1f, 1f, 0.12f);

		public static readonly Color SubtleButtonColor = new Color(1f, 1f, 1f, 0.07f);

		public static readonly Color AccentButtonColor = new Color(0.35f, 0.72f, 1f, 0.45f);

		public static readonly Color FieldColor = new Color(0f, 0f, 0f, 0.35f);

		public static readonly Color RowColor = new Color(0f, 0f, 0f, 0.28f);

		public static readonly Color RowDoneColor = new Color(0f, 0f, 0f, 0.14f);

		public static readonly Color BarBackground = new Color(0f, 0f, 0f, 0.45f);

		public static readonly Color TextColor = new Color(0.92f, 0.95f, 1f, 1f);

		public static readonly Color MutedText = new Color(0.62f, 0.68f, 0.76f, 1f);

		public static readonly Color IconColor = new Color(0.85f, 0.9f, 1f, 0.9f);

		public static readonly Color LinkHover = new Color(0.55f, 0.82f, 1f, 1f);

		public static readonly Color StatusBelow = new Color(0.55f, 0.65f, 0.8f, 1f);

		public static readonly Color StatusMet = new Color(0.35f, 0.8f, 0.4f, 1f);

		public static readonly Color StatusLagging = new Color(0.95f, 0.6f, 0.2f, 1f);

		public static readonly Color StatusProblem = new Color(0.9f, 0.35f, 0.31f, 1f);

		public static readonly Color ActualText = new Color(0.8f, 0.87f, 0.93f, 1f);

		private static bool _initialized;

		private static Font _fallbackFont;

		private static Sprite _arrowSprite;

		private static bool _naviSearched;

		private static Sprite _naviSprite;

		public static Font Font { get; private set; }

		public static Sprite ButtonSprite { get; private set; }

		public static Sprite CloseSprite { get; private set; }

		public static string Hex(Color c)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			return "#" + ColorUtility.ToHtmlStringRGB(c);
		}

		public static void Init()
		{
			if (_initialized)
			{
				return;
			}
			_initialized = true;
			try
			{
				UIGame val = UIRoot.instance?.uiGame;
				if ((Object)(object)val != (Object)null)
				{
					Text[] componentsInChildren = ((Component)val).GetComponentsInChildren<Text>(true);
					foreach (Text val2 in componentsInChildren)
					{
						if ((Object)(object)val2.font != (Object)null && ((Object)val2.font).name != "Arial")
						{
							Font = val2.font;
							break;
						}
					}
					UIItemPicker itemPicker = val.itemPicker;
					object buttonSprite;
					if (itemPicker == null)
					{
						buttonSprite = null;
					}
					else
					{
						UIButton typeButton = itemPicker.typeButton1;
						if (typeButton == null)
						{
							buttonSprite = null;
						}
						else
						{
							Image component = ((Component)typeButton).GetComponent<Image>();
							buttonSprite = ((component != null) ? component.sprite : null);
						}
					}
					ButtonSprite = (Sprite)buttonSprite;
					FindWindowAssets((Component)(object)val.controlPanelWindow);
					if ((Object)(object)CloseSprite == (Object)null)
					{
						FindWindowAssets((Component)(object)val.statWindow);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Log.LogWarning((object)("GameStyle: could not read game UI assets: " + ex.Message));
			}
			ManualLogSource log = Plugin.Instance.Log;
			string[] obj = new string[6] { "GameStyle: font=", null, null, null, null, null };
			Font font = Font;
			obj[1] = ((font != null) ? ((Object)font).name : null) ?? "Arial (fallback)";
			obj[2] = ", button=";
			Sprite buttonSprite2 = ButtonSprite;
			obj[3] = ((buttonSprite2 != null) ? ((Object)buttonSprite2).name : null) ?? "none";
			obj[4] = ", close=";
			Sprite closeSprite = CloseSprite;
			obj[5] = ((closeSprite != null) ? ((Object)closeSprite).name : null) ?? "none";
			log.LogInfo((object)string.Concat(obj));
		}

		private static void FindWindowAssets(Component root)
		{
			if ((Object)(object)root == (Object)null || !((Object)(object)CloseSprite == (Object)null))
			{
				return;
			}
			Button[] componentsInChildren = root.GetComponentsInChildren<Button>(true);
			foreach (Button val in componentsInChildren)
			{
				if (((Object)val).name.IndexOf("close", StringComparison.OrdinalIgnoreCase) < 0)
				{
					continue;
				}
				Image[] componentsInChildren2 = ((Component)val).GetComponentsInChildren<Image>(true);
				foreach (Image val2 in componentsInChildren2)
				{
					if ((Object)(object)val2.sprite != (Object)null)
					{
						CloseSprite = val2.sprite;
						break;
					}
				}
				if ((Object)(object)CloseSprite != (Object)null)
				{
					break;
				}
			}
		}

		public static Image CreateWindowBackground(GameObject go)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			Image obj = go.AddComponent<Image>();
			((Graphic)obj).color = OverlayPanel;
			((Graphic)obj).raycastTarget = true;
			return obj;
		}

		public static void StyleIconButton(Button btn, float iconSize)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: 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_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			Image component = ((Component)btn).GetComponent<Image>();
			component.sprite = null;
			((Graphic)component).color = new Color(0f, 0f, 0f, 0f);
			Text componentInChildren = ((Component)btn).GetComponentInChildren<Text>();
			if ((Object)(object)CloseSprite != (Object)null)
			{
				if ((Object)(object)componentInChildren != (Object)null)
				{
					((Behaviour)componentInChildren).enabled = false;
				}
				GameObject val = new GameObject("Icon");
				val.transform.SetParent(((Component)btn).transform, false);
				Image val2 = val.AddComponent<Image>();
				val2.sprite = CloseSprite;
				((Graphic)val2).color = IconColor;
				((Graphic)val2).raycastTarget = false;
				RectTransform rectTransform = ((Graphic)val2).rectTransform;
				Vector2 val3 = default(Vector2);
				((Vector2)(ref val3))..ctor(0.5f, 0.5f);
				rectTransform.pivot = val3;
				Vector2 anchorMin = (rectTransform.anchorMax = val3);
				rectTransform.anchorMin = anchorMin;
				rectTransform.sizeDelta = new Vector2(iconSize, iconSize);
				((Selectable)btn).targetGraphic = (Graphic)(object)val2;
			}
			else if ((Object)(object)componentInChildren != (Object)null)
			{
				componentInChildren.text = "×";
				componentInChildren.fontSize = Mathf.RoundToInt(iconSize * 1.4f);
				((Graphic)componentInChildren).color = IconColor;
				((Selectable)btn).targetGraphic = (Graphic)(object)componentInChildren;
			}
		}

		public static Sprite GetArrowSprite()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_arrowSprite != (Object)null)
			{
				return _arrowSprite;
			}
			Texture2D val = new Texture2D(32, 32, (TextureFormat)4, false);
			((Texture)val).wrapMode = (TextureWrapMode)1;
			((Texture)val).filterMode = (FilterMode)1;
			Color32[] array = (Color32[])(object)new Color32[1024];
			for (int i = 0; i < 32; i++)
			{
				float num = (float)(31 - i) / 2f + 0.5f;
				for (int j = 0; j < 32; j++)
				{
					float num2 = Mathf.Abs((float)j + 0.5f - 16f);
					float num3 = Mathf.Clamp01(num - num2 + 0.5f);
					array[i * 32 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(num3 * 255f));
				}
			}
			val.SetPixels32(array);
			val.Apply();
			_arrowSprite = Sprite.Create(val, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f));
			return _arrowSprite;
		}

		public static Sprite GetNavigationSprite()
		{
			if (_naviSearched)
			{
				return _naviSprite;
			}
			_naviSearched = true;
			try
			{
				UIControlPanelPlanetEntry[] array = Resources.FindObjectsOfTypeAll<UIControlPanelPlanetEntry>();
				for (int i = 0; i < array.Length; i++)
				{
					UIButton navigationButton = array[i].navigationButton;
					if ((Object)(object)navigationButton == (Object)null)
					{
						continue;
					}
					Image[] componentsInChildren = ((Component)navigationButton).GetComponentsInChildren<Image>(true);
					foreach (Image val in componentsInChildren)
					{
						if (!((Object)(object)((Component)val).gameObject == (Object)(object)((Component)navigationButton).gameObject) && !((Object)(object)val.sprite == (Object)null))
						{
							_naviSprite = val.sprite;
							break;
						}
					}
					if ((Object)(object)_naviSprite != (Object)null)
					{
						break;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Log.LogWarning((object)("GameStyle: could not find the navigation icon: " + ex.Message));
			}
			ManualLogSource log = Plugin.Instance.Log;
			Sprite naviSprite = _naviSprite;
			log.LogInfo((object)("GameStyle: navigation icon=" + (((naviSprite != null) ? ((Object)naviSprite).name : null) ?? "none (using text)")));
			return _naviSprite;
		}

		public static Font GetFont()
		{
			if ((Object)(object)Font != (Object)null)
			{
				return Font;
			}
			if ((Object)(object)_fallbackFont == (Object)null)
			{
				_fallbackFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
			}
			return _fallbackFont;
		}

		public static void ApplyButton(Image img)
		{
			//IL_0028: 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)
			if (!((Object)(object)img == (Object)null) && !((Object)(object)ButtonSprite == (Object)null))
			{
				img.sprite = ButtonSprite;
				img.type = (Type)((ButtonSprite.border != Vector4.zero) ? 1 : 0);
			}
		}
	}
	public struct InputNeed
	{
		public int ItemId;

		public double PerMinute;

		public bool ImportedOnly;

		public InputNeed(int itemId, double perMinute, bool importedOnly)
		{
			ItemId = itemId;
			PerMinute = perMinute;
			ImportedOnly = importedOnly;
		}
	}
	public struct ChainInput
	{
		public int ItemId;

		public double PerProduct;

		public ChainInput(int itemId, double perProduct)
		{
			ItemId = itemId;
			PerProduct = perProduct;
		}
	}
	public class ChainItem
	{
		public bool ImportedOnly;

		public readonly List<ChainInput> Inputs = new List<ChainInput>();
	}
	public class InputTrackingPlan
	{
		public readonly List<TodoItem> Added = new List<TodoItem>();

		public int AlreadyTracked;

		public int Imported;
	}
	public static class InputTracking
	{
		public const int MaxChainDepth = 10;

		public static InputTrackingPlan Plan(IEnumerable<TodoItem> tasks, TodoItem parent, IEnumerable<InputNeed> inputs)
		{
			InputTrackingPlan inputTrackingPlan = new InputTrackingPlan();
			HashSet<int> hashSet = new HashSet<int>(from t in tasks
				where t.Type == TaskType.ProductionTarget && t.PlanetId == parent.PlanetId
				select t.ItemId);
			foreach (InputNeed input in inputs)
			{
				if (input.ItemId > 0 && !(input.PerMinute <= 0.0))
				{
					if (hashSet.Contains(input.ItemId))
					{
						inputTrackingPlan.AlreadyTracked++;
						continue;
					}
					if (input.ImportedOnly)
					{
						inputTrackingPlan.Imported++;
						continue;
					}
					inputTrackingPlan.Added.Add(new TodoItem(null, TaskType.ProductionTarget, input.ItemId, Target(input.PerMinute), parent.PlanetId));
					hashSet.Add(input.ItemId);
				}
			}
			return inputTrackingPlan;
		}

		public static InputTrackingPlan PlanChain(IEnumerable<TodoItem> tasks, TodoItem parent, Func<int, ChainItem> lookup)
		{
			InputTrackingPlan inputTrackingPlan = new InputTrackingPlan();
			ChainItem chainItem = lookup(parent.ItemId);
			if (chainItem == null || parent.TargetAmount <= 0)
			{
				return inputTrackingPlan;
			}
			HashSet<int> tracked = new HashSet<int>(from t in tasks
				where t.Type == TaskType.ProductionTarget && t.PlanetId == parent.PlanetId
				select t.ItemId);
			HashSet<int> stoppedTracked = new HashSet<int>();
			HashSet<int> stoppedImported = new HashSet<int>();
			Dictionary<int, List<ChainInput>> edges = new Dictionary<int, List<ChainInput>>();
			HashSet<int> onPath = new HashSet<int>();
			List<int> postOrder = new List<int>();
			Visit(parent.ItemId, chainItem, 0);
			postOrder.Reverse();
			Dictionary<int, double> dictionary = new Dictionary<int, double> { [parent.ItemId] = parent.TargetAmount };
			foreach (int item3 in postOrder)
			{
				double num = dictionary[item3];
				foreach (ChainInput item4 in edges[item3])
				{
					dictionary[item4.ItemId] = (dictionary.TryGetValue(item4.ItemId, out var value) ? value : 0.0) + num * item4.PerProduct;
				}
				if (item3 != parent.ItemId)
				{
					inputTrackingPlan.Added.Add(new TodoItem(null, TaskType.ProductionTarget, item3, Target(num), parent.PlanetId));
				}
			}
			inputTrackingPlan.AlreadyTracked = stoppedTracked.Count;
			inputTrackingPlan.Imported = stoppedImported.Count;
			return inputTrackingPlan;
			void Visit(int num2, ChainItem item, int depth)
			{
				onPath.Add(num2);
				List<ChainInput> list = new List<ChainInput>();
				edges[num2] = list;
				if (item != null && depth < 10)
				{
					for (int num3 = item.Inputs.Count - 1; num3 >= 0; num3--)
					{
						ChainInput item2 = item.Inputs[num3];
						if (item2.ItemId > 0 && !(item2.PerProduct <= 0.0) && !onPath.Contains(item2.ItemId))
						{
							if (tracked.Contains(item2.ItemId))
							{
								stoppedTracked.Add(item2.ItemId);
							}
							else
							{
								ChainItem chainItem2 = lookup(item2.ItemId);
								if (chainItem2 != null && chainItem2.ImportedOnly)
								{
									stoppedImported.Add(item2.ItemId);
								}
								else
								{
									list.Add(item2);
									if (!edges.ContainsKey(item2.ItemId))
									{
										Visit(item2.ItemId, chainItem2, depth + 1);
									}
								}
							}
						}
					}
				}
				onPath.Remove(num2);
				postOrder.Add(num2);
			}
		}

		public static bool SameTasks(InputTrackingPlan a, InputTrackingPlan b)
		{
			if (a.Added.Count != b.Added.Count)
			{
				return false;
			}
			Dictionary<int, int> targets = a.Added.ToDictionary((TodoItem t) => t.ItemId, (TodoItem t) => t.TargetAmount);
			int value;
			return b.Added.All((TodoItem t) => targets.TryGetValue(t.ItemId, out value) && value == t.TargetAmount);
		}

		public static int Target(double perMinute)
		{
			double num = Math.Ceiling(perMinute - 0.001);
			if (num >= 2147483647.0)
			{
				return int.MaxValue;
			}
			return Math.Max(1, (int)num);
		}

		public static void Insert(List<TodoItem> list, TodoItem parent, List<TodoItem> added)
		{
			int num = list.IndexOf(parent);
			if (num < 0)
			{
				list.AddRange(added);
			}
			else
			{
				list.InsertRange(num + 1, added);
			}
		}
	}
	public static class LangFile
	{
		private static readonly Regex Placeholder = new Regex("(?<!\\{)\\{(\\d+)(?:[,:][^}]*)?\\}");

		public static Dictionary<string, string> Parse(string content, List<string> errors = null)
		{
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			string[] array = content.Replace("\r\n", "\n").Split(new char[1] { '\n' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length == 0 || text.StartsWith("#"))
				{
					continue;
				}
				int num = text.IndexOf('=');
				if (num <= 0)
				{
					errors?.Add($"line {i + 1}: expected \"key = text\"");
					continue;
				}
				string text2 = text.Substring(0, num).Trim();
				string text3 = text.Substring(num + 1).Trim().Replace("\\n", "\n");
				if (dictionary.ContainsKey(text2))
				{
					errors?.Add($"line {i + 1}: '{text2}' appears twice");
				}
				if (text3.Length > 0)
				{
					dictionary[text2] = text3;
				}
			}
			return dictionary;
		}

		public static SortedSet<int> Placeholders(string text)
		{
			SortedSet<int> sortedSet = new SortedSet<int>();
			foreach (Match item in Placeholder.Matches(text))
			{
				sortedSet.Add(int.Parse(item.Groups[1].Value));
			}
			return sortedSet;
		}

		public static List<string> Check(IDictionary<string, string> english, IDictionary<string, string> translated)
		{
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, string> item in translated)
			{
				if (!english.TryGetValue(item.Key, out var value))
				{
					list.Add("'" + item.Key + "' is not a known key");
				}
				else if (!Placeholders(value).SetEquals(Placeholders(item.Value)))
				{
					list.Add("'" + item.Key + "' must use the placeholders " + Describe(value) + ", has " + Describe(item.Value));
				}
			}
			return list;
		}

		private static string Describe(string text)
		{
			SortedSet<int> sortedSet = Placeholders(text);
			if (sortedSet.Count != 0)
			{
				return string.Join(" ", sortedSet.Select((int n) => "{" + n + "}").ToArray());
			}
			return "none";
		}

		public static Dictionary<string, string> Usable(IDictionary<string, string> english, IDictionary<string, string> translated)
		{
			string value;
			return translated.Where((KeyValuePair<string, string> kv) => english.TryGetValue(kv.Key, out value) && Placeholders(value).SetEquals(Placeholders(kv.Value))).ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> kv) => kv.Key, (KeyValuePair<string, string> kv) => kv.Value, StringComparer.Ordinal);
		}
	}
	public static class Loc
	{
		private static Dictionary<string, string> _translated = new Dictionary<string, string>();

		private static bool _resolved;

		public static string Language { get; private set; } = "en";

		public static string T(string key)
		{
			EnsureLoaded();
			if (_translated.TryGetValue(key, out var value))
			{
				return value;
			}
			if (Strings.English.TryGetValue(key, out var value2))
			{
				return value2;
			}
			return key;
		}

		public static string T(string key, params object[] args)
		{
			string format = T(key);
			try
			{
				return string.Format(CultureInfo.InvariantCulture, format, args);
			}
			catch (FormatException)
			{
				string value;
				return Strings.English.TryGetValue(key, out value) ? string.Format(CultureInfo.InvariantCulture, value, args) : key;
			}
		}

		private static void EnsureLoaded()
		{
			if (_resolved)
			{
				return;
			}
			Plugin instance = Plugin.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				_resolved = true;
				return;
			}
			string text = (instance.Language.Value ?? "").Trim();
			string text2;
			if (text.Length == 0 || text.Equals("auto", StringComparison.OrdinalIgnoreCase))
			{
				text2 = GameLanguageCode();
				if (text2 == null)
				{
					return;
				}
			}
			else
			{
				text2 = text;
			}
			_resolved = true;
			if (text2.StartsWith("en", StringComparison.OrdinalIgnoreCase))
			{
				instance.Log.LogInfo((object)("Language: English (" + text2 + ")"));
				return;
			}
			string text3 = Path.Combine(Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)instance).Info.Location) ?? "", "lang"), text2 + ".txt");
			if (!File.Exists(text3))
			{
				instance.Log.LogInfo((object)("Language: no translation file " + text3 + ", using English"));
				return;
			}
			try
			{
				List<string> list = new List<string>();
				Dictionary<string, string> translated = LangFile.Parse(File.ReadAllText(text3), list);
				list.AddRange(LangFile.Check(Strings.English, translated));
				_translated = LangFile.Usable(Strings.English, translated);
				Language = text2;
				instance.Log.LogInfo((object)$"Language: {text2}, {_translated.Count} of {Strings.English.Count} texts translated");
				foreach (string item in list)
				{
					instance.Log.LogWarning((object)(Path.GetFileName(text3) + ": " + item));
				}
			}
			catch (Exception ex)
			{
				instance.Log.LogWarning((object)("Could not read " + text3 + ", using English: " + ex.Message));
			}
		}

		private static string GameLanguageCode()
		{
			try
			{
				if (!Localization.Loaded)
				{
					return null;
				}
				Language currentLanguage = Localization.CurrentLanguage;
				return (currentLanguage != null && !string.IsNullOrEmpty(currentLanguage.abbr)) ? currentLanguage.abbr : null;
			}
			catch
			{
				return null;
			}
		}
	}
	public class PerfLog
	{
		private class Section
		{
			public double TotalMs;

			public double MaxMs;

			public int Count;
		}

		private const float ReportSeconds = 10f;

		private readonly Dictionary<string, Section> _sections = new Dictionary<string, Section>();

		private string _note;

		private float _elapsed;

		public static long Start()
		{
			return Stopwatch.GetTimestamp();
		}

		public static double ElapsedMs(long start)
		{
			return (double)(Stopwatch.GetTimestamp() - start) * 1000.0 / (double)Stopwatch.Frequency;
		}

		public void Add(string name, long start)
		{
			AddMs(name, ElapsedMs(start));
		}

		public void AddMs(string name, double ms)
		{
			if (!_sections.TryGetValue(name, out var value))
			{
				value = (_sections[name] = new Section());
			}
			value.TotalMs += ms;
			value.Count++;
			if (ms > value.MaxMs)
			{
				value.MaxMs = ms;
			}
		}

		public void Note(string text)
		{
			_note = text;
		}

		public string Tick(float seconds)
		{
			_elapsed += seconds;
			if (_elapsed < 10f)
			{
				return null;
			}
			_elapsed = 0f;
			string arg = string.Join("; ", _sections.Select((KeyValuePair<string, Section> kv) => $"{kv.Key} avg {kv.Value.TotalMs / (double)kv.Value.Count:0.00} ms, max {kv.Value.MaxMs:0.00} ms, {kv.Value.Count}x"));
			string arg2 = ((_note != null) ? (" | " + _note) : "");
			_sections.Clear();
			_note = null;
			return $"Timings (last {10f:0} s): {arg}{arg2}";
		}
	}
	[BepInPlugin("com.jules.dsp.taskmaster", "DSP TaskMaster", "1.2.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin, IModCanSave
	{
		public static Plugin Instance;

		private List<TodoItem> _todoItems = new List<TodoItem>();

		private float _checkTimer;

		private int _lastPlanetId;

		private int _lastLocalId = -1;

		public ConfigEntry<KeyboardShortcut> ToggleKey;

		public ConfigEntry<KeyboardShortcut> ToggleKeyAlt;

		public ConfigEntry<string> Language;

		public ConfigEntry<float> WindowX;

		public ConfigEntry<float> WindowY;

		public ConfigEntry<float> WindowWidth;

		public ConfigEntry<float> WindowHeight;

		public ConfigEntry<bool> AlertsEnabled;

		public ConfigEntry<bool> AlertBehind;

		public ConfigEntry<bool> OverlayEnabled;

		public ConfigEntry<float> OverlayX;

		public ConfigEntry<float> OverlayY;

		public ConfigEntry<float> ScanBudgetMs;

		public ConfigEntry<bool> LogTimings;

		private readonly Alerts _alerts = new Alerts();

		private readonly TaskTracker _tracker = new TaskTracker();

		private readonly PerfLog _perf = new PerfLog();

		public ManualLogSource Log => ((BaseUnityPlugin)this).Logger;

		private void Awake()
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Language = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Language", "auto", "Language of the mod's own texts. auto = the game's language if the lang folder has a translation for it, otherwise English. en = always English. Any other value is a file name from the lang folder (without .txt). Takes effect after restarting the game.");
			ToggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Window", "Toggle key", new KeyboardShortcut((KeyCode)116, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Opens and closes the task window.");
			ToggleKeyAlt = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Window", "Toggle key (alternative)", new KeyboardShortcut((KeyCode)116, (KeyCode[])(object)new KeyCode[1] { (KeyCode)307 }), "Second key for the task window (a shortcut matches only its exact modifier keys). Clear it to disable.");
			WindowX = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "X", 0f, "Left edge relative to the screen center (UI units). Set Width to 0 to reset.");
			WindowY = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "Y", 0f, "Top edge relative to the screen center (UI units).");
			WindowWidth = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "Width", 0f, "Window width in UI units. 0 = automatic (fits screen size).");
			WindowHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Window", "Height", 0f, "Window height in UI units. 0 = automatic (fits screen size).");
			AlertsEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Alerts", "Enabled", true, "Show an on-screen message when a tracked task reaches its target.");
			AlertBehind = ((BaseUnityPlugin)this).Config.Bind<bool>("Alerts", "Behind", true, "Also alert when a production task falls behind or drops below its target.");
			OverlayEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Overlay", "Enabled", true, "Show a small list of open tasks at the screen edge while the window is closed.");
			OverlayX = ((BaseUnityPlugin)this).Config.Bind<float>("Overlay", "X", 0f, "Right edge offset from the screen's right edge (UI units, negative = left). 0/0 = default position.");
			OverlayY = ((BaseUnityPlugin)this).Config.Bind<float>("Overlay", "Y", 0f, "Top edge offset from the screen's top edge (UI units, negative = down). 0/0 = default position.");
			ScanBudgetMs = ((BaseUnityPlugin)this).Config.Bind<float>("Performance", "Scan budget (ms)", 2f, "Time per second for re-reading the machines of planets you are not on or viewing. The current and viewed planet are always read every second. Higher = capacity and building counts of other planets update faster.");
			LogTimings = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "Log timings", false, "Write how long the per-second update takes to the BepInEx log every 10 seconds.");
			((Component)this).gameObject.AddComponent<UIManager>();
			DSPModSavePlugin.AddModSaveManually((BaseUnityPlugin)(object)this);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin com.jules.dsp.taskmaster is loaded!");
		}

		public int GetCurrentPlanetId()
		{
			if (GameMain.localPlanet == null)
			{
				return 0;
			}
			return GameMain.localPlanet.id;
		}

		public bool IsOnPlanet()
		{
			return GameMain.localPlanet != null;
		}

		public int GetDisplayPlanetId()
		{
			int currentPlanetId = GetCurrentPlanetId();
			if (currentPlanetId == 0)
			{
				return _lastPlanetId;
			}
			return currentPlanetId;
		}

		public void Export(BinaryWriter w)
		{
			SaveManager.Write(w, _todoItems, GetDisplayPlanetId());
		}

		public void Import(BinaryReader r)
		{
			try
			{
				int lastPlanetId;
				List<TodoItem> list = SaveManager.Read(r, out lastPlanetId);
				SetTodoItems(list, lastPlanetId);
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Loaded {list.Count} tasks from save.");
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("Could not read tasks from save: " + ex.Message));
				SetTodoItems(new List<TodoItem>());
			}
		}

		public void IntoOtherSave()
		{
			SetTodoItems(new List<TodoItem>());
		}

		public List<TodoItem> GetTodoItems()
		{
			return _todoItems;
		}

		public void OnTargetEdited(TodoItem item)
		{
			_alerts.Forget(item);
		}

		public void SetTodoItems(List<TodoItem> items, int lastPlanetId = 0)
		{
			_todoItems = items ?? new List<TodoItem>();
			_lastPlanetId = lastPlanetId;
			_lastLocalId = -1;
			_alerts.Reset();
			_tracker.Reset();
			if ((Object)(object)UIManager.Instance != (Object)null)
			{
				UIManager.Instance.RefreshUI();
			}
		}

		private void Update()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = ToggleKey.Value;
			if (!((KeyboardShortcut)(ref value)).IsDown())
			{
				value = ToggleKeyAlt.Value;
				if (!((KeyboardShortcut)(ref value)).IsDown())
				{
					goto IL_0041;
				}
			}
			if ((Object)(object)UIManager.Instance != (Object)null)
			{
				UIManager.Instance.ToggleWindow();
			}
			goto IL_0041;
			IL_0041:
			int currentPlanetId = GetCurrentPlanetId();
			if (currentPlanetId != _lastLocalId)
			{
				_lastLocalId = currentPlanetId;
				if (currentPlanetId != 0)
				{
					_lastPlanetId = currentPlanetId;
				}
				if ((Object)(object)UIManager.Instance != (Object)null)
				{
					UIManager.Instance.OnLocalPlanetChanged();
				}
				UpdateTasks();
				if ((Object)(object)UIManager.Instance != (Object)null)
				{
					UIManager.Instance.RefreshUI();
				}
			}
			_checkTimer += Time.deltaTime;
			if (!(_checkTimer >= 1f))
			{
				return;
			}
			_checkTimer = 0f;
			bool value2 = LogTimings.Value;
			long start = PerfLog.Start();
			UpdateTasks();
			if (value2)
			{
				_perf.Add("tasks", start);
				_perf.Note($"planets read {_tracker.LastScanned} of {_tracker.LastNeeded}");
			}
			Alerts.Announce(_alerts.Check(_todoItems));
			if ((Object)(object)UIManager.Instance != (Object)null)
			{
				start = PerfLog.Start();
				UIManager.Instance.UpdateValues();
				if (value2)
				{
					_perf.Add("window", start);
				}
				start = PerfLog.Start();
				UIManager.Instance.UpdateOverlay();
				if (value2)
				{
					_perf.Add("overlay", start);
				}
			}
			string text = (value2 ? _perf.Tick(1f) : null);
			if (text != null)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)text);
			}
		}

		public void AddTiming(string section, long start)
		{
			if (LogTimings != null && LogTimings.Value)
			{
				_perf.Add(section, start);
			}
		}

		private void UpdateTasks()
		{
			HashSet<int> hashSet = new HashSet<int> { GetDisplayPlanetId() };
			if ((Object)(object)UIManager.Instance != (Object)null)
			{
				hashSet.Add(UIManager.Instance.ViewedPlanetId);
			}
			_tracker.ScanBudgetMs = Math.Max(0.1, ScanBudgetMs.Value);
			_tracker.Update(_todoItems, hashSet);
		}
	}
	public class GeneratorGroup
	{
		public int ItemId;

		public int Count;

		public double CapacityW;
	}
	public class GeneratorOption
	{
		public int ItemId;

		public double PerBuildingW;

		public bool Daytime;

		public bool FromPlaced;

		public int Needed;
	}
	public class PowerReport
	{
		public readonly List<GeneratorGroup> Placed = new List<GeneratorGroup>();

		public readonly List<GeneratorOption> Options = new List<GeneratorOption>();
	}
	public static class PowerInsight
	{
		public static PowerReport Analyze(int planetId, double missingW)
		{
			PowerReport powerReport = new PowerReport();
			GameData data = GameMain.data;
			object obj;
			if (data == null)
			{
				obj = null;
			}
			else
			{
				GalaxyData galaxy = data.galaxy;
				obj = ((galaxy != null) ? galaxy.PlanetById(planetId) : null);
			}
			PlanetData val = (PlanetData)obj;
			Dictionary<int, GeneratorGroup> dictionary = new Dictionary<int, GeneratorGroup>();
			foreach (PlanetFactory factory in GameData.GetFactories(planetId))
			{
				PowerSystem powerSystem = factory.powerSystem;
				EntityData[] entityPool = factory.entityPool;
				if (powerSystem?.genPool == null || entityPool == null)
				{
					continue;
				}
				for (int i = 1; i < powerSystem.genCursor && i < powerSystem.genPool.Length; i++)
				{
					ref PowerGeneratorComponent reference = ref powerSystem.genPool[i];
					if (reference.id == i && reference.entityId > 0 && reference.entityId < entityPool.Length)
					{
						int protoId = entityPool[reference.entityId].protoId;
						if (!dictionary.TryGetValue(protoId, out var value))
						{
							value = (dictionary[protoId] = new GeneratorGroup
							{
								ItemId = protoId
							});
						}
						value.Count++;
						value.CapacityW += (double)reference.capacityCurrentTick * 60.0;
					}
				}
			}
			powerReport.Placed.AddRange(dictionary.Values.OrderByDescending((GeneratorGroup p) => p.CapacityW));
			if (missingW <= 0.0 || ((ProtoSet<ItemProto>)(object)LDB.items)?.dataArray == null)
			{
				return powerReport;
			}
			GameHistoryData history = GameMain.history;
			ItemProto[] dataArray = ((ProtoSet<ItemProto>)(object)LDB.items).dataArray;
			foreach (ItemProto val2 in dataArray)
			{
				PrefabDesc val3 = val2?.prefabDesc;
				if (val3 == null || !val3.isPowerGen || !val2.CanBuild || val3.genEnergyPerTick <= 0 || (history != null && !history.ItemUnlocked(((Proto)val2).ID)))
				{
					continue;
				}
				GeneratorOption generatorOption = new GeneratorOption
				{
					ItemId = ((Proto)val2).ID
				};
				double num2 = (double)val3.genEnergyPerTick * 60.0;
				if (val3.gammaRayReceiver || val3.geothermal)
				{
					if (!dictionary.TryGetValue(((Proto)val2).ID, out var value2) || value2.Count == 0)
					{
						continue;
					}
					generatorOption.PerBuildingW = value2.CapacityW / (double)value2.Count;
					generatorOption.FromPlaced = true;
				}
				else if (val3.windForcedPower)
				{
					generatorOption.PerBuildingW = num2 * (double)(val?.windStrength ?? 1f);
				}
				else if (val3.photovoltaic)
				{
					generatorOption.PerBuildingW = num2 * (double)(val?.luminosity ?? 1f);
					generatorOption.Daytime = true;
				}
				else
				{
					generatorOption.PerBuildingW = num2;
				}
				generatorOption.Needed = BuildingsNeeded(missingW, generatorOption.PerBuildingW);
				if (generatorOption.Needed > 0)
				{
					powerReport.Options.Add(generatorOption);
				}
			}
			powerReport.Options.Sort((GeneratorOption a, GeneratorOption b) => a.Needed.CompareTo(b.Needed));
			return powerReport;
		}

		public static int BuildingsNeeded(double missingW, double perBuildingW)
		{
			if (missingW <= 0.0 || perBuildingW <= 0.0)
			{
				return 0;
			}
			double num = Math.Ceiling(missingW / perBuildingW - 1E-06);
			if (!(num >= 2147483647.0))
			{
				return (int)num;
			}
			return int.MaxValue;
		}
	}
	public class MachineGroup
	{
		public int BuildingId;

		public int Count;

		public double TotalRate;

		public double ExtraRate;

		public RecipeExecuteData Recipe;

		public int Veins;

		public bool Planned;

		public double RatePerMachine
		{
			get
			{
				if (Count <= 0)
				{
					return 0.0;
				}
				return TotalRate / (double)Count;
			}
		}

		public double RatePerVein
		{
			get
			{
				if (Veins <= 0)
				{
					return 0.0;
				}
				return TotalRate / (double)Veins;
			}
		}

		public double InputShare
		{
			get
			{
				if (!(TotalRate > 0.0))
				{
					return 1.0;
				}
				return (TotalRate - ExtraRate) / TotalRate;
			}
		}
	}
	public class InputReport
	{
		public int ItemId;

		public double NeededPerMin;

		public long MadePerMin;

		public long UsedPerMin;

		public int ImportStations;

		public long ImportStock;

		public long ImportIncoming;

		public MachineGroup Makers;

		public int MakersPlaced;

		public bool IsImported => ImportStations > 0;

		public long AvailablePerMin => Math.Max(MadePerMin, UsedPerMin);
	}
	public class ProductionReport
	{
		public int Machines;

		public int Working;

		public int MissingInput;

		public int OutputFull;

		public int LowPower;

		public int NoPower;

		public int Idle;

		public int Depleted;

		public readonly Dictionary<int, int> MissingItems = new Dictionary<int, int>();

		public MachineGroup Main;

		public RecipeProto PlannedRecipe;

		public readonly List<InputReport> Inputs = new List<InputReport>();

		public readonly Dictionary<int, ChainItem> Chain = new Dictionary<int, ChainItem>();

		public ChainItem ChainLookup(int itemId)
		{
			if (!Chain.TryGetValue(itemId, out var value))
			{
				return null;
			}
			return value;
		}
	}
	public static class ProductionInsight
	{
		private class ImportInfo
		{
			public int Stations;

			public long Stock;

			public long Incoming;
		}

		public static ProductionReport Analyze(TodoItem task)
		{
			//IL_02eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f1: Invalid comparison between Unknown and I4
			ProductionReport productionReport = new ProductionReport();
			List<PlanetFactory> list = GameData.GetFactories(task.PlanetId).ToList();
			Dictionary<int, Dictionary<long, MachineGroup>> dictionary = new Dictionary<int, Dictionary<long, MachineGroup>>();
			Dictionary<int, int> dictionary2 = new Dictionary<int, int>();
			foreach (PlanetFactory item in list)
			{
				FactorySystem factorySystem = item.factorySystem;
				if (factorySystem == null)
				{
					continue;
				}
				if (factorySystem.assemblerPool != null)
				{
					for (int i = 1; i < factorySystem.assemblerCursor; i++)
					{
						ref AssemblerComponent reference = ref factorySystem.assemblerPool[i];
						if (reference.id == i && reference.recipeId > 0 && reference.recipeExecuteData != null)
						{
							int buildingId = GetBuildingId(item, reference.entityId);
							dictionary2[buildingId] = ((!dictionary2.TryGetValue(buildingId, out var value)) ? 1 : (value + 1));
							AddMachine(dictionary, buildingId, reference.recipeId, reference.recipeExecuteData, Math.Max(reference.speed, reference.speedOverride), reference.extraSpeed);
							if (Makes(reference.recipeExecuteData, task.ItemId))
							{
								Classify(productionReport, item, reference.recipeExecuteData, reference.replicating, reference.time, reference.served, reference.pcId);
							}
						}
					}
				}
				if (factorySystem.labPool != null)
				{
					for (int j = 1; j < factorySystem.labCursor; j++)
					{
						ref LabComponent reference2 = ref factorySystem.labPool[j];
						if (reference2.id == j && !reference2.researchMode && reference2.recipeId > 0 && reference2.recipeExecuteData != null)
						{
							int buildingId2 = GetBuildingId(item, reference2.entityId);
							dictionary2[buildingId2] = ((!dictionary2.TryGetValue(buildingId2, out var value2)) ? 1 : (value2 + 1));
							AddMachine(dictionary, buildingId2, reference2.recipeId, reference2.recipeExecuteData, Math.Max(reference2.speed, reference2.speedOverride), reference2.extraSpeed);
							if (Makes(reference2.recipeExecuteData, task.ItemId))
							{
								Classify(productionReport, item, reference2.recipeExecuteData, reference2.replicating, reference2.time, reference2.served, reference2.pcId);
							}
						}
					}
				}
				if (factorySystem.minerPool == null)
				{
					continue;
				}
				float miningSpeed = ((GameMain.history != null) ? GameMain.history.miningSpeedScale : 1f);
				for (int k = 1; k < factorySystem.minerCursor; k++)
				{
					ref MinerComponent reference3 = ref factorySystem.minerPool[k];
					if (reference3.id != k)
					{
						continue;
					}
					int productId;
					double num = ProductionMath.MinerRatePerMinute(ref reference3, item.veinPool, miningSpeed, out productId);
					if (productId > 0)
					{
						int buildingId3 = GetBuildingId(item, reference3.entityId);
						MachineGroup machineGroup = GetGroup(dictionary, productId, buildingId3, 0, null);
						machineGroup.Count++;
						machineGroup.TotalRate += num;
						if ((int)reference3.type == 2)
						{
							machineGroup.Veins += reference3.veinCount;
						}
						if (productId == task.ItemId)
						{
							ClassifyMiner(productionReport, item, ref reference3);
						}
					}
				}
			}
			productionReport.Main = MainGroup(dictionary, task.ItemId);
			if (productionReport.Main == null && productionReport.Machines == 0 && task.CurrentAmount <= 0)
			{
				productionReport.PlannedRecipe = PickRecipe(task.ItemId);
				productionReport.Main = PlannedGroup(productionReport.PlannedRecipe, task.ItemId, dictionary2);
				if (productionReport.Main == null)
				{
					productionReport.PlannedRecipe = null;
				}
			}
			if (productionReport.Main == null || task.TargetAmount <= 0)
			{
				return productionReport;
			}
			RecipeExecuteData recipe = productionReport.Main.Recipe;
			if (recipe == null)
			{
				return productionReport;
			}
			int num2 = ProductCount(recipe, task.ItemId);
			if (num2 <= 0 || recipe.requires == null || recipe.requireCounts == null)
			{
				return productionReport;
			}
			double inputShare = productionReport.Main.InputShare;
			Dictionary<int, ImportInfo> dictionary3 = CollectImports(list);
			for (int l = 0; l < recipe.requires.Length && l < recipe.requireCounts.Length; l++)
			{
				int num3 = recipe.requires[l];
				InputReport inputReport = new InputReport
				{
					ItemId = num3,
					NeededPerMin = (double)task.TargetAmount * (double)recipe.requireCounts[l] / (double)num2 * inputShare,
					Makers = MainGroup(dictionary, num3)
				};
				if (dictionary.TryGetValue(num3, out var value3))
				{
					inputReport.MakersPlaced = value3.Values.Sum((MachineGroup g) => g.Count);
				}
				foreach (PlanetFactory item2 in list)
				{
					FactoryProductionStat stat = GameData.GetStat(item2);
					if (stat != null)
					{
						inputReport.MadePerMin += GameData.GetProducedLastMinute(stat, num3);
						inputReport.UsedPerMin += GameData.GetConsumedLastMinute(stat, num3);
					}
				}
				if (dictionary3.TryGetValue(num3, out var value4))
				{
					inputReport.ImportStations = value4.Stations;
					inputReport.ImportStock = value4.Stock;
					inputReport.ImportIncoming = value4.Incoming;
				}
				if (inputReport.Makers == null && !inputReport.IsImported)
				{
					inputReport.Makers = PlannedGroup(PickRecipe(num3), num3, dictionary2);
				}
				productionReport.Inputs.Add(inputReport);
			}
			BuildChain(productionReport, task.ItemId, dictionary, dictionary3);
			return productionReport;
		}

		private static Dictionary<int, ImportInfo> CollectImports(List<PlanetFactory> factories)
		{
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Invalid comparison between Unknown and I4
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<int, ImportInfo> dictionary = new Dictionary<int, ImportInfo>();
			foreach (PlanetFactory factory in factories)
			{
				StationComponent[] array = factory.transport?.stationPool;
				if (array == null)
				{
					continue;
				}
				for (int i = 1; i < factory.transport.stationCursor && i < array.Length; i++)
				{
					StationComponent val = array[i];
					if (val == null || val.id != i || !val.isStellar || val.storage == null)
					{
						continue;
					}
					StationStore[] storage = val.storage;
					foreach (StationStore val2 in storage)
					{
						if (val2.itemId > 0 && (int)val2.remoteLogic == 2)
						{
							if (!dictionary.TryGetValue(val2.itemId, out var value))
							{
								value = new ImportInfo();
								dictionary[val2.itemId] = value;
							}
							value.Stations++;
							value.Stock += val2.count;
							if (val2.remoteOrder > 0)
							{
								value.Incoming += val2.remoteOrder;
							}
						}
					}
				}
			}
			return dictionary;
		}

		private static void BuildChain(ProductionReport report, int itemId, Dictionary<int, Dictionary<long, MachineGroup>> makers, Dictionary<int, ImportInfo> imports)
		{
			List<int> list = new List<int> { itemId };
			for (int i = 0; i <= 10; i++)
			{
				if (list.Count <= 0)
				{
					break;
				}
				List<int> list2 = new List<int>();
				foreach (int item in list)
				{
					if (report.Chain.ContainsKey(item))
					{
						continue;
					}
					ChainItem chainItem = new ChainItem
					{
						ImportedOnly = (imports.ContainsKey(item) && !makers.ContainsKey(item))
					};
					report.Chain[item] = chainItem;
					if (chainItem.ImportedOnly && item != itemId)
					{
						continue;
					}
					double num = 1.0;
					MachineGroup machineGroup = ((item == itemId) ? report.Main : MainGroup(makers, item));
					int[] array;
					int[] array2;
					int num2;
					if (machineGroup != null)
					{
						if (machineGroup.Recipe == null)
						{
							continue;
						}
						array = machineGroup.Recipe.requires;
						array2 = machineGroup.Recipe.requireCounts;
						num2 = ProductCount(machineGroup.Recipe, item);
						num = machineGroup.InputShare;
					}
					else
					{
						RecipeProto val = PickRecipe(item);
						if (val == null)
						{
							continue;
						}
						array = val.Items;
						array2 = val.ItemCounts;
						num2 = ProductCount(val.Results, val.ResultCounts, item);
					}
					if (num2 > 0 && array != null && array2 != null)
					{
						for (int j = 0; j < array.Length && j < array2.Length; j++)
						{
							chainItem.Inputs.Add(new ChainInput(array[j], (double)array2[j] / (double)num2 * num));
							list2.Add(array[j]);
						}
					}
				}
				list = list2;
			}
		}

		private static RecipeProto PickRecipe(int itemId)
		{
			ItemProto item = ((ProtoSet<ItemProto>)(object)LDB.items)?.Select(itemId);
			if (item == null)
			{
				return null;
			}
			List<RecipeProto> list = new List<RecipeProto>();
			if (item.maincraft != null)
			{
				list.Add(item.maincraft);
			}
			if (item.recipes != null)
			{
				list.AddRange(item.recipes.Where((RecipeProto r) => r != null && r != item.maincraft));
			}
			list = list.Where((RecipeProto r) => RunsInMachine(r.Type) && ProductCount(r.Results, r.ResultCounts, itemId) > 0).ToList();
			if (list.Count == 0)
			{
				return null;
			}
			GameHistoryData history = GameMain.history;
			return ((IEnumerable<RecipeProto>)list).FirstOrDefault((Func<RecipeProto, bool>)((RecipeProto r) => history == null || history.RecipeUnlocked(((Proto)r).ID))) ?? list[0];
		}

		private static MachineGroup PlannedGroup(RecipeProto recipe, int itemId, Dictionary<int, int> buildings)
		{
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Expected O, but got Unknown
			if (recipe == null || (Object)(object)LDB.items == (Object)null)
			{
				return null;
			}
			ItemProto val = (from kv in buildings
				select (proto: ((ProtoSet<ItemProto>)(object)LDB.items).Select(kv.Key), count: kv.Value) into b
				where BuildingSpeed(b.proto, recipe.Type) > 0
				orderby b.count descending
				select b.proto).FirstOrDefault();
			if (val == null && ((ProtoSet<ItemProto>)(object)LDB.items).dataArray != null)
			{
				GameHistoryData history = GameMain.history;
				List<ItemProto> source = ((ProtoSet<ItemProto>)(object)LDB.items).dataArray.Where((ItemProto p) => p != null && p.CanBuild && BuildingSpeed(p, recipe.Type) > 0).ToList();
				val = (from p in source
					where history == null || history.ItemUnlocked(((Proto)p).ID)
					orderby BuildingSpeed(p, recipe.Type) descending
					select p).FirstOrDefault() ?? source.OrderBy((ItemProto p) => BuildingSpeed(p, recipe.Type)).FirstOrDefault();
			}
			if (val == null)
			{
				return null;
			}
			if (recipe.TimeSpend <= 0)
			{
				return null;
			}
			double num = 3600.0 * (double)BuildingSpeed(val, recipe.Type) / ((double)recipe.TimeSpend * 10000.0);
			RecipeExecuteData value;
			return new MachineGroup
			{
				BuildingId = ((Proto)val).ID,
				Count = 1,
				Planned = true,
				Recipe = (RecipeExecuteData)((RecipeProto.recipeExecuteData != null && RecipeProto.recipeExecuteData.TryGetValue(((Proto)recipe).ID, out value)) ? ((object)value) : ((object)new RecipeExecuteData(recipe.Items, recipe.ItemCounts, recipe.Results, recipe.ResultCounts, recipe.TimeSpend * 10000, 0, recipe.productive))),
				TotalRate = num * (double)ProductCount(recipe.Results, recipe.ResultCounts, itemId)
			};
		}

		private static bool RunsInMachine(ERecipeType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Invalid comparison between Unknown and I4
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			if ((int)type != 1 && (int)type != 2 && (int)type != 3 && (int)type != 4 && (int)type != 5)
			{
				return (int)type == 15;
			}
			return true;
		}

		private static int BuildingSpeed(ItemProto building, ERecipeType type)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Invalid comparison between Unknown and I4
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			PrefabDesc val = building?.prefabDesc;
			if (val == null)
			{
				return 0;
			}
			if ((int)type == 15)
			{
				if (!val.isLab)
				{
					return 0;
				}
				return val.labAssembleSpeed;
			}
			if (!val.isAssembler || val.assemblerRecipeType != type)
			{
				return 0;
			}
			return val.assemblerSpeed;
		}

		public static int BuildingsNeeded(double perMinute, MachineGroup group)
		{
			if (group == null || group.RatePerMachine <= 0.0)
			{
				return 0;
			}
			return (int)Math.Ceiling(perMinute / group.RatePerMachine - 0.001);
		}

		private static int GetBuildingId(PlanetFactory factory, int entityId)
		{
			EntityData[] entityPool = factory.entityPool;
			if (entityPool == null || entityId <= 0 || entityId >= entityPool.Length)
			{
				return 0;
			}
			return entityPool[entityId].protoId;
		}

		private static bool Makes(RecipeExecuteData recipe, int itemId)
		{
			return ProductCount(recipe, itemId) > 0;
		}

		private static int ProductCount(RecipeExecuteData recipe, int itemId)
		{
			return ProductCount(recipe.products, recipe.productCounts, itemId);
		}

		private static int ProductCount(int[] products, int[] productCounts, int itemId)
		{
			if (products == null || productCounts == null)
			{
				return 0;
			}
			for (int i = 0; i < products.Length && i < productCounts.Length; i++)
			{
				if (products[i] == itemId)
				{
					return productCounts[i];
				}
			}
			return 0;
		}

		private static void AddMachine(Dictionary<int, Dictionary<long, MachineGroup>> makers, int buildingId, int recipeId, RecipeExecuteData recipe, int speed, int extraSpeed)
		{
			if (recipe.products != null && recipe.productCounts != null)
			{
				double num = ProductionMath.CyclesPerMinute(recipe, speed, extraSpeed);
				double num2 = num - ProductionMath.CyclesPerMinute(recipe, speed, 0);
				for (int i = 0; i < recipe.products.Length && i < recipe.productCounts.Length; i++)
				{
					MachineGroup machineGroup = GetGroup(makers, recipe.products[i], buildingId, recipeId, recipe);
					machineGroup.Count++;
					machineGroup.TotalRate += num * (double)recipe.productCounts[i];
					machineGroup.ExtraRate += num2 * (double)recipe.productCounts[i];
				}
			}
		}

		private static MachineGroup GetGroup(Dictionary<int, Dictionary<long, MachineGroup>> makers, int itemId, int buildingId, int recipeId, RecipeExecuteData recipe)
		{
			long key = ((long)buildingId << 32) | (uint)recipeId;
			if (!makers.TryGetValue(itemId, out var value))
			{
				value = (makers[itemId] = new Dictionary<long, MachineGroup>());
			}
			if (!value.TryGetValue(key, out var value2))
			{
				MachineGroup obj = new MachineGroup
				{
					BuildingId = buildingId,
					Recipe = recipe
				};
				value2 = (value[key] = obj);
			}
			return value2;
		}

		private static void ClassifyMiner(ProductionReport report, PlanetFactory factory, ref MinerComponent m)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Invalid comparison between Unknown and I4
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Invalid comparison between Unknown and I4
			report.Machines++;
			double num = PowerRatio(factory, m.pcId);
			if ((int)m.type != 1 && m.veinCount == 0)
			{
				report.Depleted++;
			}
			else if ((int)m.workstate == -2 || m.speedDamper < 0.99f)
			{
				report.OutputFull++;
			}
			else if (num <= 0.1)
			{
				report.NoPower++;
			}
			else if (num < 1.0)
			{
				report.LowPower++;
			}
			else
			{
				report.Working++;
			}
		}

		private static MachineGroup MainGroup(Dictionary<int, Dictionary<long, MachineGroup>> makers, int itemId)
		{
			if (!makers.TryGetValue(itemId, out var value) || value.Count == 0)
			{
				return null;
			}
			return value.Values.OrderByDescending((MachineGroup g) => g.Count).First();
		}

		private static void Classify(ProductionReport report, PlanetFactory factory, RecipeExecuteData recipe, bool replicating, int time, int[] served, int pcId)
		{
			report.Machines++;
			double num = PowerRatio(factory, pcId);
			if (replicating)
			{
				if (num >= 1.0)
				{
					report.Working++;
				}
				else if (num > 0.1)
				{
					report.LowPower++;
				}
				else
				{
					report.NoPower++;
				}
				return;
			}
			if (time >= recipe.timeSpend)
			{
				report.OutputFull++;
				return;
			}
			bool flag = false;
			if (served != null && recipe.requires != null && recipe.requireCounts != null)
			{
				for (int i = 0; i < recipe.requireCounts.Length && i < served.Length && i < recipe.requires.Length; i++)
				{
					if (served[i] < recipe.requireCounts[i])
					{
						flag = true;
						int key = recipe.requires[i];
						report.MissingItems[key] = ((!report.MissingItems.TryGetValue(key, out var value)) ? 1 : (value + 1));
					}
				}
			}
			if (flag)
			{
				report.MissingInput++;
			}
			else if (num >= 1.0)
			{
				report.Idle++;
			}
			else if (num > 0.1)
			{
				report.LowPower++;
			}
			else
			{
				report.NoPower++;
			}
		}

		private static double PowerRatio(PlanetFactory factory, int pcId)
		{
			PowerSystem powerSystem = factory.powerSystem;
			if (powerSystem == null || powerSystem.consumerPool == null || pcId <= 0 || pcId >= powerSystem.consumerPool.Length)
			{
				return 0.0;
			}
			int networkId = powerSystem.consumerPool[pcId].networkId;
			if (networkId <= 0 || powerSystem.netPool == null || networkId >= powerSystem.netPool.Length || powerSystem.netPool[networkId] == null)
			{
				return 0.0;
			}
			return powerSystem.netPool[networkId].consumerRatio;
		}
	}
	public static class ProductionMath
	{
		public static Dictionary<int, long> CalculateCapacity(PlanetFactory factory)
		{
			Dictionary<int, double> dictionary = new Dictionary<int, double>();
			FactorySystem factorySystem = factory.factorySystem;
			if (factorySystem == null)
			{
				return new Dictionary<int, long>();
			}
			if (factorySystem.assemblerPool != null)
			{
				for (int i = 1; i < factorySystem.assemblerCursor; i++)
				{
					ref AssemblerComponent reference = ref factorySystem.assemblerPool[i];
					if (reference.id == i && reference.recipeId > 0 && reference.recipeExecuteData != null)
					{
						AddRecipeOutput(dictionary, reference.recipeExecuteData, Math.Max(reference.speed, reference.speedOverride), reference.extraSpeed);
					}
				}
			}
			if (factorySystem.labPool != null)
			{
				for (int j = 1; j < factorySystem.labCursor; j++)
				{
					ref LabComponent reference2 = ref factorySystem.labPool[j];
					if (reference2.id == j && !reference2.researchMode && reference2.recipeId > 0 && reference2.recipeExecuteData != null)
					{
						AddRecipeOutput(dictionary, reference2.recipeExecuteData, Math.Max(reference2.speed, reference2.speedOverride), reference2.extraSpeed);
					}
				}
			}
			if (factorySystem.minerPool != null)
			{
				float miningSpeed = ((GameMain.history != null) ? GameMain.history.miningSpeedScale : 1f);
				for (int k = 1; k < factorySystem.minerCursor; k++)
				{
					ref MinerComponent reference3 = ref factorySystem.minerPool[k];
					if (reference3.id == k)
					{
						int productId;
						double perMinute = MinerRatePerMinute(ref reference3, factory.veinPool, miningSpeed, out productId);
						AddRate(dictionary, productId, perMinute);
					}
				}
			}
			BeltComponent[] array = factory.cargoTraffic?.beltPool;
			if (factorySystem.fractionatorPool != null && array != null)
			{
				for (int l = 1; l < factorySystem.fractionatorCursor; l++)
				{
					ref FractionatorComponent reference4 = ref factorySystem.fractionatorPool[l];
					if (reference4.id == l && reference4.productId > 0)
					{
						int num = 0;
						if (reference4.belt0 > 0 && !reference4.isOutput0 && reference4.belt0 < array.Length)
						{
							num = Math.Max(num, array[reference4.belt0].speed);
						}
						if (reference4.belt1 > 0 && !reference4.isOutput1 && reference4.belt1 < array.Length)
						{
							num = Math.Max(num, array[reference4.belt1].speed);
						}
						if (reference4.belt2 > 0 && !reference4.isOutput2 && reference4.belt2 < array.Length)
						{
							num = Math.Max(num, array[reference4.belt2].speed);
						}
						if (num > 0)
						{
							double num2 = Math.Min((double)num * 6.0, 30.0);
							double num3 = ((reference4.fluidInputCount > 0 && reference4.fluidInputCargoCount > 0.0001f) ? Math.Max(1.0, (double)reference4.fluidInputCount / (double)reference4.fluidInputCargoCount) : 1.0);
							int num4 = ((reference4.fluidInputCount > 0) ? (reference4.fluidInputInc / reference4.fluidInputCount) : 0);
							double num5 = Cargo.accTableMilli[Mathf.Clamp(num4, 0, 10)];
							AddRate(dictionary, reference4.productId, num2 * num3 * 60.0 * (double)reference4.produceProb * (1.0 + num5));
						}
					}
				}
			}
			Dictionary<int, long> dictionary2 = new Dictionary<int, long>(dictionary.Count);
			foreach (KeyValuePair<int, double> item in dictionary)
			{
				long num6 = (long)(item.Value + 0.5);
				if (num6 > 0)
				{
					dictionary2[item.Key] = num6;
				}
			}
			return dictionary2;
		}

		private static void AddRate(Dictionary<int, double> totals, int itemId, double perMinute)
		{
			if (itemId > 0 && !(perMinute <= 0.0))
			{
				if (totals.ContainsKey(itemId))
				{
					totals[itemId] += perMinute;
				}
				else
				{
					totals[itemId] = perMinute;
				}
			}
		}

		private static void AddRecipeOutput(Dictionary<int, double> totals, RecipeExecuteData recipe, int speed, int extraSpeed)
		{
			if (recipe.products == null || recipe.productCounts == null)
			{
				return;
			}
			double num = CyclesPerMinute(recipe, speed, extraSpeed);
			if (!(num <= 0.0))
			{
				for (int i = 0; i < recipe.products.Length && i < recipe.productCounts.Length; i++)
				{
					AddRate(totals, recipe.products[i], num * (double)recipe.productCounts[i]);
				}
			}
		}

		public static double MinerRatePerMinute(ref MinerComponent m, VeinData[] veinPool, float miningSpeed, out int productId)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected I4, but got Unknown
			productId = m.productId;
			if (m.period <= 0)
			{
				return 0.0;
			}
			int num = ((m.veinCount > 0 && m.veins != null && m.veins.Length != 0) ? m.veins[0] : 0);
			bool flag = veinPool != null && num > 0 && num < veinPool.Length;
			if (productId == 0 && flag)
			{
				productId = veinPool[num].productId;
			}
			if (productId <= 0)
			{
				return 0.0;
			}
			EMinerType type = m.type;
			double factor;
			switch (type - 1)
			{
			case 1:
				factor = m.veinCount;
				break;
			case 2:
				factor = (flag ? ((float)veinPool[num].amount * VeinData.oilSpeedMultiplier) : 0f);
				break;
			case 0:
				factor = 1.0;
				break;
			default:
				return 0.0;
			}
			return MinerRatePerMinute(m.speed, miningSpeed, factor, m.period);
		}

		public static double MinerRatePerMinute(int speed, float miningSpeed, double factor, int period)
		{
			if (period <= 0)
			{
				return 0.0;
			}
			return (double)speed * (double)miningSpeed * factor / (double)period * 3600.0;
		}

		public static double CyclesPerMinute(RecipeExecuteData recipe, int speed, int extraSpeed)
		{
			if (recipe == null)
			{
				return 0.0;
			}
			return CyclesPerMinute(recipe.timeSpend, recipe.extraTimeSpend, speed, extraSpeed);
		}

		public static double CyclesPerMinute(int timeSpend, int extraTimeSpend, int speed, int extraSpeed)
		{
			if (timeSpend <= 0 || speed <= 0)
			{
				return 0.0;
			}
			double num = 3600.0 * (double)speed / (double)timeSpend;
			if (extraSpeed > 0 && extraTimeSpend > 0)
			{
				num += 3600.0 * (double)extraSpeed / (double)extraTimeSpend;
			}
			return num;
		}
	}
	public class ReasonScheduler
	{
		public const float PlanetSeconds = 3f;

		public const float GlobalSeconds = 10f;

		private readonly Dictionary<TodoItem, float> _last = new Dictionary<TodoItem, float>();

		public static float Interval(TodoItem task)
		{
			if (task.PlanetId != 0)
			{
				return 3f;
			}
			return 10f;
		}

		public TodoItem PickDue(IEnumerable<TodoItem> tasks, float now)
		{
			TodoItem todoItem = null;
			float num = 0f;
			foreach (TodoItem task in tasks)
			{
				if (!_last.TryGetValue(task, out var value))
				{
					return task;
				}
				float num2 = now - value - Interval(task);
				if (num2 >= 0f && (todoItem == null || num2 > num))
				{
					todoItem = task;
					num = num2;
				}
			}
			return todoItem;
		}

		public void Analyzed(TodoItem task, float now)
		{
			_last[task] = now;
		}

		public void Forget(TodoItem task)
		{
			_last.Remove(task);
		}
	}
	public static class SaveManager
	{
		private const int FormatVersion = 1;

		public static void Write(BinaryWriter w, List<TodoItem> tasks, int lastPlanetId)
		{
			w.Write(1);
			w.Write(lastPlanetId);
			w.Write(tasks.Count);
			foreach (TodoItem task in tasks)
			{
				w.Write(task.Description ?? "");
				w.Write(task.IsCompleted);
				w.Write((int)task.Type);
				w.Write(task.ItemId);
				w.Write(task.TargetAmount);
				w.Write(task.CurrentAmount);
				w.Write(task.ActualAmount);
				w.Write(task.PlanetId);
			}
		}

		public static List<TodoItem> Read(BinaryReader r, out int lastPlanetId)
		{
			int num = r.ReadInt32();
			if (num < 1 || num > 1)
			{
				throw new InvalidDataException($"Unsupported task data version {num}");
			}
			lastPlanetId = r.ReadInt32();
			int num2 = r.ReadInt32();
			List<TodoItem> list = new List<TodoItem>(num2);
			for (int i = 0; i < num2; i++)
			{
				string desc = r.ReadString();
				bool isCompleted = r.ReadBoolean();
				TaskType type = (TaskType)r.ReadInt32();
				int itemId = r.ReadInt32();
				int target = r.ReadInt32();
				TodoItem item = new TodoItem(desc, type, itemId, target)
				{
					IsCompleted = isCompleted,
					CurrentAmount = r.ReadInt64(),
					ActualAmount = r.ReadInt64(),
					PlanetId = r.ReadInt32()
				};
				list.Add(item);
			}
			return list;
		}
	}
	public class ScanScheduler
	{
		private int _cursor;

		public void Plan(IList<int> needed, ICollection<int> priority, ICollection<int> scanned, List<int> must, List<int> optional)
		{
			must.Clear();
			optional.Clear();
			List<int> list = new List<int>();
			foreach (int item in needed)
			{
				if (!scanned.Contains(item) || priority.Contains(item))
				{
					must.Add(item);
				}
				else
				{
					list.Add(item);
				}
			}
			if (list.Count != 0)
			{
				int num = _cursor % list.Count;
				for (int i = 0; i < list.Count; i++)
				{
					optional.Add(list[(num + i) % list.Count]);
				}
			}
		}

		public void Advance(int optionalScanned)
		{
			_cursor += optionalScanned;
			if (_cursor > 1000000)
			{
				_cursor = 0;
			}
		}
	}
	public static class Strings
	{
		public static readonly Dictionary<string, string> English = new Dictionary<string, string>(StringComparer.Ordinal)
		{
			["common.add"] = "Add",
			["common.hideDone"] = "Hide done",
			["common.planetCount.one"] = "1 planet",
			["common.planetCount"] = "{0} planets",
			["title.produce"] = "Produce {0}",
			["title.surplus"] = "Surplus {0}",
			["title.obtain"] = "Obtain {0}",
			["title.research"] = "Research {0}",
			["title.build"] = "Build {0} {1}",
			["title.sphere"] = "Sphere: {0}",
			["title.surplusShort"] = "{0} surplus",
			["title.power"] = "Spare power on {0}",
			["title.powerShort"] = "{0} power",
			["name.unknownItem"] = "Unknown item",
			["name.planet"] = "Planet {0}",
			["name.star"] = "Star {0}",
			["progress.noTarget"] = "No target set",
			["progress.production"] = "Cap {0} / {1} /min [{2}%]",
			["progress.unlocked"] = "Unlocked",
			["progress.notStarted"] = "Not started",
			["progress.research"] = "{0} / {1} hashes [{2}%]",
			["progress.building"] = "{0} / {1} placed [{2}%]",
			["progress.surplus"] = "Surplus {0} / {1} /min [{2}%]",
			["progress.power"] = "{0} / {1} [{2}%]",
			["value.rate"] = "{0} / {1} /min",
			["value.placed"] = "{0} / {1} placed",
			["value.perMin"] = "{0}/min",
			["research.done"] = "Done",
			["research.researching"] = "Researching",
			["research.inQueue"] = "In queue",
			["research.notQueued"] = "Not queued",
			["alert.done"] = "TaskMaster: {0} done",
			["alert.reached"] = "TaskMaster: {0} reached ({1})",
			["alert.droppedBelow"] = "TaskMaster: {0} dropped below target ({1} /min)",
			["alert.powerBelow"] = "TaskMaster: {0} dropped below target ({1})",
			["alert.backOnTrack"] = "TaskMaster: {0} is back on track",
			["alert.behind"] = "TaskMaster: {0} is behind: {1} / {2} /min",
			["alert.navigating"] = "TaskMaster: navigating to {0}",
			["alert.navigationCleared"] = "TaskMaster: navigation to {0} cleared",
			["window.reminderPlaceholder"] = "Add a reminder...",
			["window.track"] = "Track...",
			["window.noPlanetTasks"] = "No tasks for this planet yet.",
			["window.allHidden"] = "All {0} tasks done (hidden).",
			["window.completedHidden"] = "{0} completed hidden",
			["window.landOnPlanet"] = "Land on a planet to see its task list.",
			["window.global"] = "Global ({0} open / {1})",
			["window.madeUsed"] = "Made {0}  Used {1}",
			["window.actual"] = "Actual {0}/min",
			["window.genDemand"] = "Gen {0}  Use {1}",
			["window.noPlanet"] = "No planet",
			["window.viewing"] = "{0} (viewing)",
			["window.lastVisited"] = "{0} (last visited)",
			["window.planetsButton"] = "planets",
			["window.showDone"] = "Show done",
			["window.addGlobal"] = "Add: Global",
			["window.addPlanet"] = "Add: Planet",
			["window.editTarget"] = "Enter saves, Esc cancels",
			["window.editTargetMW"] = "MW. Enter saves, Esc cancels",
			["window.deleted"] = "Deleted: {0}",
			["window.undo"] = "Undo",
			["overlay.title"] = "TASKS",
			["overlay.global"] = "Global",
			["overlay.header"] = "{0} · {1} open",
			["overlay.actual"] = "{0} actual",
			["overlay.madeUsed"] = "{0} made, {1} used",
			["overlay.genDemand"] = "{0} gen, {1} demand",
			["overlay.toGo"] = "{0} to go",
			["overlay.etaUnderMinute"] = "<1 min left",
			["overlay.etaMinutes"] = "~{0} min left",
			["overlay.etaHours"] = "~{0} h left",
			["status.done"] = "Done",
			["status.noMachines"] = "No machines",
			["status.producing"] = "Producing",
			["status.building"] = "Building",
			["status.deficit"] = "Deficit",
			["status.surplus"] = "Surplus",
			["status.sphere"] = "Sphere",
			["status.power"] = "Power",
			["status.powerShort"] = "Not enough power",
			["status.collecting"] = "Collecting",
			["status.slow"] = "Slow",
			["status.missingInput"] = "Missing input",
			["status.backedUp"] = "Backed up",
			["status.noPower"] = "No power",
			["status.lowPower"] = "Low power",
			["status.depleted"] = "Ore depleted",
			["details.readFailed"] = "Could not read the machines on this planet.",
			["details.whyBehind"] = "Why it's behind",
			["details.machines"] = "Machines",
			["details.noMakers"] = "No assemblers, labs or miners make this item here.",
			["details.otherBuildings"] = "Capacity comes from other buildings (e.g. fractionators).",
			["details.plannedWith"] = "Planned with {0}, {1}/min each",
			["details.missingInput"] = "{0} of {1} missing input",
			["details.waitingFor"] = "Waiting for {0}",
			["details.outputFull"] = "{0} of {1} have a full output",
			["details.outputSlow"] = "Output isn't taken away fast enough, so they slow down",
			["details.outputNotTaken"] = "Output isn't being taken away",
			["details.depleted"] = "{0} of {1} have no ore left",
			["details.noPower"] = "{0} of {1} have no power",
			["details.lowPower"] = "{0} of {1} have low power",
			["details.standby"] = "{0} of {1} on standby",
			["details.working"] = "{0} of {1} working normally",
			["details.inputsFor"] = "Inputs needed for {0}/min",
			["details.extraProducts"] = "{0}% less thanks to proliferator extra products",
			["details.used"] = "{0} used",
			["details.made"] = "{0} made",
			["details.neededSupply"] = "{0} needed   {1}",
			["details.stationOne"] = "1 station",
			["details.stations"] = "{0} stations",
			["details.onTheWay"] = ", {0} on the way",
			["details.imported"] = "Imported: {0} in {1}{2}",
			["details.notMadeOrImported"] = "Not made or imported here",
			["details.trackInputs"] = "Track inputs ({0})",
			["details.inputsTracked"] = "Inputs tracked",
			["details.wholeChain"] = "Whole chain ({0})",
			["details.alreadyTracked"] = "{0} already tracked",
			["details.importedSkipped"] = "{0} imported",
			["details.buildingsFor"] = "Buildings for {0}/min",
			["details.neededPlaced"] = "{0} needed   {1} placed",
			["details.veins"] = "Ore veins covered ({0}/min each)",
			["details.neededCovered"] = "{0} needed   {1} covered",
			["details.importedShort"] = "imported",
			["details.notMadeHere"] = "not made here",
			["details.notePlanned"] = "Based on the machines placed here, or the item's own recipe where nothing makes it yet. Proliferator is not counted.",
			["details.noteCurrent"] = "Based on the machines placed here, at their current speed. Extra products from proliferator are not counted for inputs.",
			["details.perPlanet"] = "Per planet (last minute)",
			["details.noPlanet"] = "No planet makes or uses this item yet.",
			["details.netMade"] = "{0}   ({1} made)",
			["details.morePlanets"] = "{0} more planets in between",
			["details.readFailedPower"] = "Could not read the generators on this planet.",
			["details.generatorsHere"] = "Generators on this planet",
			["details.noGenerators"] = "No generators here yet.",
			["details.genPlaced"] = "{0} placed   {1}",
			["details.powerTarget"] = "Target",
			["details.powerTargetMet"] = "Enough spare power for the target.",
			["details.toReach"] = "{0} missing: buildings needed",
			["details.noGeneratorUnlocked"] = "No generator unlocked yet.",
			["details.daytime"] = "{0} (daytime)",
			["details.averagePlaced"] = "{0} (avg. of placed)",
			["details.buildingsTimes"] = "{0} x {1}",
			["details.powerNote"] = "Per building on this planet: wind by its wind strength, solar at full sun by its luminosity (none at night). Fuel generators need fuel. Accumulators are not counted.",
			["details.surplusNote"] = "Surplus = made minus used during the last minute. Stored buffers are not counted.",
			["planets.title"] = "Planets with tasks",
			["planets.showDone"] = "Show done ({0})",
			["planets.none"] = "No planet has tasks yet. Tasks added with \"Add: Planet\" show up here.",
			["planets.allDone"] = "All planets are done. \"Show done\" lists them.",
			["planets.follow"] = "Show the planet I'm on",
			["planets.unknownSystem"] = "Unknown system",
			["planets.thisSystem"] = "{0}  (this system)",
			["planets.open"] = "{0} open",
			["planets.behind"] = "{0} behind",
			["planets.youAreHere"] = "{0}  (you are here)",
			["planets.allDoneCount"] = "all {0} done",
			["planets.view"] = "View",
			["planets.viewing"] = "Viewing",
			["track.tabItem"] = "Item",
			["track.tabResearch"] = "Research",
			["track.tabBuildings"] = "Buildings",
			["track.tabPower"] = "Power",
			["track.pickItem"] = "Pick item...",
			["track.target"] = "Target",
			["track.produce"] = "Produce /min",
			["track.surplus"] = "Surplus /min",
			["track.inventory"] = "In inventory",
			["track.itemHint"] = "Produce: machine capacity. Surplus: made minus used per minute. Both go to the list chosen in the header (Planet or Global). Inventory targets are always global.",
			["track.noItem"] = "No item picked",
			["track.pickBuilding"] = "Pick building...",
			["track.count"] = "Count",
			["track.buildingHint"] = "Counts placed buildings on this planet, or on all planets when adding to Global.",
			["track.notABuilding"] = "{0} is not a building. Pick a building.",
			["track.noBuilding"] = "No building picked",
			["track.searchResearch"] = "Search research...",
			["track.researchNeedsGame"] = "Research list is available in a running game.",
			["track.available"] = "Available",
			["track.locked"] = "Locked",
			["track.noResearchMatch"] = "No research matches.",
			["track.nothingToResearch"] = "Nothing left to research.",
			["track.moreResearch"] = "{0} more, type to narrow down.",
			["track.star"] = "Star",
			["track.sphere"] = "Sphere",
			["track.planet"] = "Planet",
			["track.spare"] = "Spare",
			["track.planetPower"] = "{0}  (gen {1}, demand {2})",
			["track.powerHint"] = "Spare power = generation capacity minus demand of this planet's power networks. Power tasks always belong to the planet.",
			["track.powerNeedsPlanet"] = "Land on or view a planet to track its power.",
			["track.noStar"] = "No star",
			["track.sphereHint"] = "Power the Dyson sphere (and swarm) around this star generates. Sphere tasks are always global."
		};
	}
	public static class TaskList
	{
		public static bool TryParseTarget(string text, out int target)
		{
			if (int.TryParse(text?.Trim(), out target))
			{
				return target > 0;
			}
			return false;
		}

		public static bool CanMove(List<TodoItem> list, TodoItem item, int direction)
		{
			return Neighbor(list, item, direction) >= 0;
		}

		public static bool Move(List<TodoItem> list, TodoItem item, int direction)
		{
			int num = Neighbor(list, item, direction);
			if (num < 0)
			{
				return false;
			}
			int index = list.IndexOf(item);
			list[index] = list[num];
			list[num] = item;
			return true;
		}

		private static int Neighbor(List<TodoItem> list, TodoItem item, int direction)
		{
			if (direction == 0)
			{
				return -1;
			}
			int num = list.IndexOf(item);
			if (num < 0)
			{
				return -1;
			}
			int num2 = ((direction > 0) ? 1 : (-1));
			for (int i = num + num2; i >= 0 && i < list.Count; i += num2)
			{
				if (list[i].PlanetId == item.PlanetId && list[i].IsCompleted == item.IsCompleted)
				{
					return i;
				}
			}
			return -1;
		}

		public static List<TodoItem> OverlayOrder(IEnumerable<TodoItem> tasks, int planetId)
		{
			return (from t in tasks
				orderby t.HasProblem() descending, (planetId == 0 || t.PlanetId != planetId) ? 1 : 0
				select t).ToList();
		}
	}
	public class UndoDelete
	{
		public const float Seconds = 6f;

		private List<TodoItem> _list;

		private int _index;

		private float _expires;

		public TodoItem Item { get; private set; }

		public int Index => _index;

		public void Delete(List<TodoItem> list, TodoItem item, float now)
		{
			int num = list.IndexOf(item);
			if (num >= 0)
			{
				list.RemoveAt(num);
				_list = list;
				_index = num;
				_expires = now + 6f;
				Item = item;
			}
		}

		public bool IsPendingFor(List<TodoItem> list)
		{
			if (Item != null)
			{
				return list == _list;
			}
			return false;
		}

		public bool Expire(float now)
		{
			if (Item == null || now < _expires)
			{
				return false;
			}
			Clear();
			return true;
		}

		public bool Undo(List<TodoItem> list)
		{
			if (!IsPendingFor(list))
			{
				return false;
			}
			if (!list.Contains(Item))
			{
				list.Insert(Math.Min(_index, list.Count), Item);
			}
			Clear();
			return true;
		}

		public void Clear()
		{
			Item = null;
			_list = null;
		}
	}
	public enum TaskType
	{
		Manual,
		InventoryTarget,
		ProductionTarget,
		Research,
		Building,
		Sphere,
		Surplus,
		Power
	}
	public enum ResearchStatus
	{
		NotQueued,
		InQueue,
		Researching,
		Done
	}
	public class TodoItem
	{
		public string Description;

		public bool IsCompleted;

		public TaskType Type;

		public int ItemId;

		public int TargetAmount;

		public long CurrentAmount;

		public long ActualAmount;

		public int PlanetId;

		public long TotalAmount;

		public ResearchStatus Research;

		public const float ActualOkRatio = 0.9f;

		public bool IsDeficit
		{
			get
			{
				if (Type == TaskType.Surplus || Type == TaskType.Power)
				{
					return CurrentAmount < 0;
				}
				return false;
			}
		}

		public bool HasEditableTarget
		{
			get
			{
				TaskType type = Type;
				if ((uint)(type - 1) <= 1u || (uint)(type - 4) <= 3u)
				{
					return true;
				}
				return false;
			}
		}

		public bool IsBehind()
		{
			if (Type == TaskType.ProductionTarget && IsCompleted)
			{
				return (float)ActualAmount < (float)TargetAmount * 0.9f;
			}
			return false;
		}

		public bool IsBelowCapacity()
		{
			if (Type == TaskType.ProductionTarget && CurrentAmount > 0)
			{
				return (float)ActualAmount < (float)Math.Min(CurrentAmount, TargetAmount) * 0.9f;
			}
			return false;
		}

		public bool HasProblem()
		{
			if (IsCompleted)
			{
				return false;
			}
			if (Type == TaskType.ProductionTarget)
			{
				if (CurrentAmount > 0)
				{
					return IsBelowCapacity();
				}
				return true;
			}
			return IsDeficit;
		}

		public void SetTarget(int target)
		{
			if (HasEditableTarget)
			{
				TargetAmount = target;
				IsCompleted = TargetAmount > 0 && CurrentAmount >= TargetAmount;
			}
		}

		public TodoItem(string desc, TaskType type, int itemId, int target, int planetId = 0)
		{
			Description = desc;
			Type = type;
			ItemId = itemId;
			TargetAmount = target;
			CurrentAmount = 0L;
			PlanetId = planetId;
		}
	}
	public class TaskOverlay
	{
		private enum Tone
		{
			Info,
			Problem,
			Done,
			Muted
		}

		private class Row
		{
			public TodoItem Task;

			public GameObject Go;

			public CanvasGroup Group;

			public Image Edge;

			public Image Icon;

			public Text Name;

			public Text Status;

			public RectTransform Fill;

			public Image FillImage;

			public RectTransform Tick;

			public Text Left;

			public Text Right;

			public bool Problem;

			public float DoneSince;
		}

		private const int MaxRows = 5;

		private const float Width = 270f;

		private const float HeaderHeight = 22f;

		private const float RowHeight = 42f;

		private const float DoneShowSeconds = 4f;

		private const float DoneFadeSeconds = 1f;

		private static readonly Color Accent = new Color(0.35f, 0.72f, 1f, 0.9f);

		private static readonly Color AccentSoft = new Color(0.35f, 0.72f, 1f, 0.16f);

		private static readonly Color HeaderText = new Color(0.81f, 0.9f, 1f, 1f);

		private static readonly Color Line = new Color(0.63f, 0.8f, 1f, 0.18f);

		private static readonly Color RowSeparator = new Color(1f, 1f, 1f, 0.05f);

		private static readonly Color ResearchColor = new Color(0.88f, 0.76f, 0.25f, 1f);

		private GameObject _root;

		private RectTransform _rootRt;

		private Canvas _canvas;

		private bool _dragged;

		private Text _headerRight;

		private readonly Dictionary<TodoItem, Row> _rows = new Dictionary<TodoItem, Row>();

		private readonly Dictionary<TodoItem, (long value, float time, double rate)> _research = new Dictionary<TodoItem, (long, float, double)>();

		private readonly Dictionary<TodoItem, string> _reasons = new Dictionary<TodoItem, string>();

		private readonly ReasonScheduler _reasonScheduler = new ReasonScheduler();

		public void Hide()
		{
			if ((Object)(object)_root != (Object)null && _root.activeSelf)
			{
				_root.SetActive(false);
			}
		}

		public void Update()
		{
			Plugin instance = Plugin.Instance;
			if (!instance.OverlayEnabled.Value || !IsInGame() || UIManager.Instance.IsWindowOpen)
			{
				Hide();
				ClearDoneRows();
				return;
			}
			int planetId = instance.GetDisplayPlanetId();
			List<TodoItem> visible = (from t in instance.GetTodoItems()
				where t.Type != TaskType.Manual
				where t.PlanetId == 0 || (planetId != 0 && t.PlanetId == planetId)
				select t).ToList();
			List<TodoItem> list = TaskList.OverlayOrder(visible.Where((TodoItem t) => !t.IsCompleted), planetId);
			foreach (TodoItem item in _reasons.Keys.Where((TodoItem t) => !visible.Contains(t)).ToList())
			{
				_reasons.Remove(item);
				_reasonScheduler.Forget(item);
			}
			foreach (TodoItem item2 in _research.Keys.Where((TodoItem t) => !visible.Contains(t)).ToList())
			{
				_research.Remove(item2);
			}
			List<TodoItem> shown = list.Take(5).ToList();
			foreach (Row value2 in _rows.Values)
			{
				if (value2.Task.IsCompleted && visible.Contains(value2.Task) && !shown.Contains(value2.Task))
				{
					shown.Add(value2.Task);
				}
			}
			if (shown.Count == 0)
			{
				Hide();
			}
			else
			{
				if ((Object)(object)_root == (Object)null && !Build())
				{
					return;
				}
				if (!_root.activeSelf)
				{
					_root.SetActive(true);
				}
				foreach (TodoItem item3 in _rows.Keys.Where((TodoItem t) => !shown.Contains(t)).ToList())
				{
					Object.Destroy((Object)(object)_rows[item3].Go);
					_rows.Remove(item3);
				}
				TodoItem todoItem = _reasonScheduler.PickDue(shown.Where(NeedsReason), Time.unscaledTime);
				if (todoItem != null)
				{
					RefreshReason(todoItem);
				}
				int num = 1;
				foreach (TodoItem item4 in shown)
				{
					if (!_rows.TryGetValue(item4, out var value))
					{
						value = CreateRow(item4);
						_rows[item4] = value;
					}
					value.Go.transform.SetSiblingIndex(num++);
					UpdateRow(value);
				}
				string text = ((planetId != 0) ? TaskText.PlanetName(planetId) : Loc.T("overlay.global"));
				_headerRight.text = Loc.T("overlay.header", text, list.Count);
			}
		}

		private void ClearDoneRows()
		{
			foreach (TodoItem item in _rows.Keys.Where((TodoItem t) => t.IsCompleted).ToList())
			{
				Object.Destroy((Object)(object)_rows[item].Go);
				_rows.Remove(item);
			}
		}

		private static bool IsInGame()
		{
			if (DSPGame.IsMenuDemo || !GameMain.isRunning || GameMain.mainPlayer == null)
			{
				return false;
			}
			UIGame val = UIRoot.instance?.uiGame;
			if ((Object)(object)val == (Object)null || val.hideAllUI0 || val.hideAllUI1)
			{
				return false;
			}
			if ((Object)(object)val.starmap != (Object)null && ((ManualBehaviour)val.starmap).active)
			{
				return false;
			}
			if ((Object)(object)val.dysonEditor != (Object)null && ((ManualBehaviour)val.dysonEditor).active)
			{
				return false;
			}
			return !UIItemPicker.isOpened;
		}

		private void UpdateRow(Row row)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_0161: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			TodoItem task = row.Task;
			float unscaledTime = Time.unscaledTime;
			if (task.IsCompleted && row.DoneSince <= 0f)
			{
				row.DoneSince = unscaledTime;
			}
			if (!task.IsCompleted)
			{
				row.DoneSince = 0f;
			}
			float num = TaskText.Progress(task);
			row.Fill.anchorMax = new Vector2(num, 1f);
			row.Right.text = TaskText.OverlayValue(task);
			row.Left.text = LeftText(task);
			bool flag = task.Type == TaskType.ProductionTarget && task.TargetAmount > 0 && task.IsBelowCapacity();
			((Component)row.Tick).gameObject.SetActive(flag);
			if (flag)
			{
				float num2 = Mathf.Clamp01((float)task.ActualAmount / (float)task.TargetAmount);
				row.Tick.anchorMin = new Vector2(num2, 0f);
				row.Tick.anchorMax = new Vector2(num2, 1f);
			}
			string text;
			Tone tone;
			if (!task.IsCompleted)
			{
				(text, tone) = StatusTag(task);
			}
			else
			{
				text = Loc.T("status.done");
				tone = Tone.Done;
			}
			row.Problem = tone == Tone.Problem;
			SetStatus(row, text, tone);
			((Graphic)row.FillImage).color = (task.IsCompleted ? GameStyle.StatusMet : ((task.Type == TaskType.Research) ? ResearchColor : (row.Problem ? GameStyle.StatusLagging : GameStyle.StatusBelow)));
			((Behaviour)row.Edge).enabled = row.Problem;
		}

		private string LeftText(TodoItem task)
		{
			switch (task.Type)
			{
			case TaskType.ProductionTarget:
				return Loc.T("overlay.actual", TaskText.Fmt(task.ActualAmount));
			case TaskType.Research:
				return ResearchEta(task);
			case TaskType.Surplus:
				return Loc.T("overlay.madeUsed", TaskText.Fmt(task.ActualAmount), TaskText.Fmt(task.TotalAmount));
			case TaskType.Power:
				return Loc.T("overlay.genDemand", TaskText.FmtPower(task.ActualAmount), TaskText.FmtPower(task.TotalAmount));
			case TaskType.Building:
			{
				long num = task.TargetAmount - task.CurrentAmount;
				if (num <= 0)
				{
					return "";
				}
				return Loc.T("overlay.toGo", TaskText.Fmt(num));
			}
			default:
				return "";
			}
		}

		private (string, Tone) StatusTag(TodoItem task)
		{
			switch (task.Type)
			{
			case TaskType.ProductionTarget:
			{
				if (task.CurrentAmount <= 0)
				{
					return (Loc.T("status.noMachines"), Tone.Problem);
				}
				if (!task.IsBelowCapacity())
				{
					return (Loc.T("status.producing"), Tone.Info);
				}
				string value;
				return (_reasons.TryGetValue(task, out value) ? value : Loc.T("status.slow"), Tone.Problem);
			}
			case TaskType.Research:
				return (TaskText.ResearchStatusText(task.Research), (task.Research != ResearchStatus.Researching) ? Tone.Muted : Tone.Info);
			case TaskType.Building:
				return (Loc.T("status.building"), Tone.Info);
			case TaskType.Surplus:
				if (task.CurrentAmount >= 0)
				{
					return (Loc.T("status.surplus"), Tone.Info);
				}
				return (Loc.T("status.deficit"), Tone.Problem);
			case TaskType.Sphere:
				return (Loc.T("status.sphere"), Tone.Info);
			case TaskType.Power:
				if (!task.IsDeficit)
				{
					return (Loc.T("status.power"), Tone.Info);
				}
				return (Loc.T("status.powerShort"), Tone.Problem);
			default:
				return (Loc.T("status.collecting"), Tone.Info);
			}
		}

		private static bool NeedsReason(TodoItem task)
		{
			if (task.Type == TaskType.ProductionTarget && !task.IsCompleted && task.CurrentAmount > 0)
			{
				return task.IsBelowCapacity();
			}
			return false;
		}

		private void RefreshReason(TodoItem task)
		{
			float unscaledTime = Time.unscaledTime;
			string value = Loc.T("status.slow");
			try
			{
				long start = PerfLog.Start();
				ProductionReport productionReport = ProductionInsight.Analyze(task);
				Plugin.Instance.AddTiming("overlay analysis", start);
				(int, string) tuple = new List<(int, string)>
				{
					(productionReport.MissingInput, Loc.T("status.missingInput")),
					(productionReport.OutputFull, Loc.T("status.backedUp")),
					(productionReport.NoPower, Loc.T("status.noPower")),
					(productionReport.LowPower, Loc.T("status.lowPower")),
					(productionReport.Depleted, Loc.T("status.depleted"))
				}.OrderByDescending<(int, string), int>(((int count, string tag) x) => x.count).First();
				if (tuple.Item1 > 0)
				{
					value = tuple.Item2;
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Log.LogWarning((object)("Overlay: could not analyze '" + TaskText.Title(task) + "': " + ex.Message));
			}
			_reasons[task] = value;
			_reasonScheduler.Analyzed(task, unscaledTime);
		}

		private string ResearchEta(TodoItem task)
		{
			float unscaledTime = Time.unscaledTime;
			if (task.Research != ResearchStatus.Researching || task.TotalAmount <= 0)
			{
				_research.Remove(task);
				return "";
			}
			if (_research.TryGetValue(task, out (long, float, double) value))
			{
				float num = unscaledTime - value.Item2;
				if (num < 0.5f)
				{
					return FormatEta(task, value.Item3);
				}
				double num2 = (double)(task.CurrentAmount - value.Item1) / (double)num;
				if (num2 < 0.0)
				{
					num2 = 0.0;
				}
				double num3 = ((value.Item3 > 0.0) ? (value.Item3 * 0.8 + num2 * 0.2) : num2);
				_research[task] = (task.CurrentAmount, unscaledTime, num3);
				return FormatEta(task, num3);
			}
			_research[task] = (task.CurrentAmount, unscaledTime, 0.0);
			return "";
		}

		private static string FormatEta(TodoItem task, double hashesPerSecond)
		{
			if (hashesPerSecond <= 0.0)
			{
				return "";
			}
			double num = (double)(task.Tot