Decompiled source of DSPTaskMaster v1.2.0
DSPTaskMaster.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
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