Decompiled source of HandCraftedTrinkets v0.1.1

HandCraftedTrinkets.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyTitle("Hand Crafted Trinkets")]
[assembly: AssemblyCompany("Loki Moon Labs")]
[assembly: AssemblyProduct("Hand Crafted Trinkets")]
[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: InternalsVisibleTo("HandCraftedTrinkets.Tests")]
[assembly: AssemblyVersion("0.1.1.0")]
namespace LokiMoonLabs.HandCraftedTrinkets;

[BepInPlugin("LokiMoonLabs.HandCraftedTrinkets", "Hand Crafted Trinkets", "0.1.1")]
[BepInIncompatibility("Appun.DSP.plugin.DSPCompleteReplicatorQueue")]
public sealed class Plugin : BaseUnityPlugin
{
	[HarmonyPatch(typeof(UIReplicatorWindow), "_OnOpen")]
	private static class OpenReplicator
	{
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void Prefix()
		{
			if ((Object)(object)instance != (Object)null)
			{
				instance.EnableRecipes();
			}
		}

		[HarmonyPostfix]
		private static void Postfix(UIReplicatorWindow __instance)
		{
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				instance.button.Open(__instance);
			}
			catch (Exception ex)
			{
				instance.button.Dispose();
				((BaseUnityPlugin)instance).Logger.LogError((object)("Could not create Complete Queue button: " + ex));
			}
		}
	}

	[HarmonyPatch(typeof(UIReplicatorWindow), "_OnUpdate")]
	private static class UpdateReplicator
	{
		[HarmonyPostfix]
		private static void Postfix(UIReplicatorWindow __instance)
		{
			if ((Object)(object)instance != (Object)null)
			{
				instance.button.Refresh(__instance);
			}
		}
	}

	[HarmonyPatch(typeof(UIReplicatorWindow), "_OnFree")]
	private static class FreeReplicator
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			if ((Object)(object)instance != (Object)null)
			{
				instance.button.Dispose();
			}
		}
	}

	[HarmonyPatch(typeof(UIReplicatorWindow), "_OnDestroy")]
	private static class DestroyReplicator
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			if ((Object)(object)instance != (Object)null)
			{
				instance.button.Dispose();
			}
		}
	}

	public const string Guid = "LokiMoonLabs.HandCraftedTrinkets";

	private static Plugin instance;

	private readonly RecipeAccess recipes = new RecipeAccess();

	private Harmony harmony;

	private readonly QueueCompleter completion = new QueueCompleter();

	private ReplicatorButton button;

	private void Awake()
	{
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Expected O, but got Unknown
		instance = this;
		button = new ReplicatorButton(Complete, (MechaForge forge) => !completion.Busy && Current(forge));
		harmony = new Harmony("LokiMoonLabs.HandCraftedTrinkets");
		harmony.PatchAll(typeof(Plugin).Assembly);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Hand Crafted Trinkets ready. Open the normal replicator to hand craft unlocked recipes with their normal ingredients.");
	}

	private static bool Current(MechaForge forge)
	{
		if (forge != null && GameMain.isRunning && !GameMain.isLoading && !GameMain.isPaused && GameMain.mainPlayer != null && forge.player == GameMain.mainPlayer && forge.player.isAlive)
		{
			return forge.mecha != null;
		}
		return false;
	}

	private static void Advance(MechaForge forge, long tick)
	{
		forge.GameTick(tick, 0f);
	}

	private void Complete(MechaForge forge)
	{
		if (!completion.Busy && Current(forge))
		{
			CompletionResult completionResult = completion.Drain(forge, GameMain.gameTick, Advance, Current);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Complete Queue: " + completionResult.Cycles + " native production cycles completed."));
			if (completionResult.Reason != null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)completionResult.Reason);
				UIRealtimeTip.Popup(completionResult.Reason, false, 0);
			}
			if (completionResult.Error != null)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)completionResult.Error);
			}
		}
	}

	private void EnableRecipes()
	{
		RecipeProtoSet val = LDB.recipes;
		if (!((Object)(object)val == (Object)null))
		{
			int num = recipes.Enable(((ProtoSet<RecipeProto>)(object)val).dataArray);
			if (num > 0)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Enabled hand crafting for " + num + " additional recipes. Ingredients, outputs, crafting times and research requirements are unchanged."));
			}
		}
	}

	private void OnDestroy()
	{
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		recipes.Restore();
		if (button != null)
		{
			button.Dispose();
		}
		if ((Object)(object)instance == (Object)(object)this)
		{
			instance = null;
		}
	}
}
internal sealed class CompletionResult
{
	internal int Cycles;

	internal string Reason;

	internal Exception Error;
}
internal sealed class QueueCompleter
{
	internal bool Busy { get; private set; }

	internal static bool Valid(ForgeTask task)
	{
		if (task == null || task.count <= 0 || task.tickSpend < 0)
		{
			return false;
		}
		if (!Amounts(task.itemIds, task.itemCounts, task.served, output: false))
		{
			return false;
		}
		return Amounts(task.productIds, task.productCounts, task.produced, output: true);
	}

	private static bool Amounts(int[] ids, int[] amounts, int[] buffered, bool output)
	{
		if (ids == null || amounts == null || buffered == null || ids.Length != amounts.Length || ids.Length != buffered.Length)
		{
			return false;
		}
		for (int i = 0; i < ids.Length; i++)
		{
			if (ids[i] <= 0 || amounts[i] <= 0 || buffered[i] < 0 || (output && buffered[i] != 0))
			{
				return false;
			}
		}
		return true;
	}

	internal CompletionResult Drain(MechaForge forge, long tick, Action<MechaForge, long> advance, Func<MechaForge, bool> current)
	{
		CompletionResult completionResult = new CompletionResult();
		if (Busy)
		{
			completionResult.Reason = "Queue completion is already running.";
			return completionResult;
		}
		if (forge == null || forge.tasks == null || advance == null || current == null || !current(forge))
		{
			completionResult.Reason = "Load a game and resume play before completing the queue.";
			return completionResult;
		}
		Busy = true;
		try
		{
			Dictionary<ForgeTask, int> dictionary = new Dictionary<ForgeTask, int>();
			for (int i = 0; i < forge.tasks.Count; i++)
			{
				ForgeTask val = forge.tasks[i];
				if (!Valid(val) || dictionary.ContainsKey(val) || (val.parentTaskIndex >= 0 && (val.parentTaskIndex <= i || val.parentTaskIndex >= forge.tasks.Count)))
				{
					completionResult.Reason = "Invalid task state; the queue was left unchanged.";
					return completionResult;
				}
				dictionary.Add(val, val.count);
			}
			while (forge.tasks.Count > 0)
			{
				if (!current(forge))
				{
					completionResult.Reason = "Game state changed; remaining work was preserved.";
					break;
				}
				ForgeTask val2 = forge.tasks[0];
				if (val2 == null || !dictionary.TryGetValue(val2, out var value) || val2.count > value)
				{
					completionResult.Reason = "The queue changed; click Complete Queue again for new work.";
					break;
				}
				if (!Valid(val2) || !val2.itemEnough || (val2.parentTaskIndex >= 0 && (val2.parentTaskIndex == 0 || val2.parentTaskIndex >= forge.tasks.Count || !dictionary.ContainsKey(forge.tasks[val2.parentTaskIndex]))))
				{
					completionResult.Reason = "Missing inputs or invalid task state; remaining work was preserved.";
					break;
				}
				int count = val2.count;
				int tick2 = val2.tick;
				val2.tick = Math.Max(tick2, val2.tickSpend);
				try
				{
					advance(forge, tick);
				}
				catch (Exception error)
				{
					if (val2.count == count)
					{
						val2.tick = tick2;
					}
					completionResult.Error = error;
					completionResult.Reason = "Native crafting raised an error; completion stopped.";
					break;
				}
				if (val2.count != count - 1)
				{
					if (val2.count == count)
					{
						val2.tick = tick2;
					}
					completionResult.Reason = "Native crafting made no expected progress; remaining work was preserved.";
					break;
				}
				dictionary[val2] = val2.count;
				completionResult.Cycles++;
				if (completionResult.Cycles == int.MaxValue)
				{
					completionResult.Reason = "Cycle limit reached; remaining work was preserved.";
					break;
				}
			}
		}
		finally
		{
			Busy = false;
		}
		return completionResult;
	}
}
internal sealed class RecipeAccess
{
	private readonly HashSet<RecipeProto> changed = new HashSet<RecipeProto>();

	internal static bool Eligible(RecipeProto recipe)
	{
		if (recipe != null && ((Proto)recipe).ID > 0 && recipe.TimeSpend > 0 && recipe.TimeSpend <= 214748 && ValidAmounts(recipe.Items, recipe.ItemCounts))
		{
			return ValidAmounts(recipe.Results, recipe.ResultCounts);
		}
		return false;
	}

	private static bool ValidAmounts(int[] ids, int[] counts)
	{
		if (ids == null || counts == null || ids.Length == 0 || ids.Length != counts.Length)
		{
			return false;
		}
		for (int i = 0; i < ids.Length; i++)
		{
			if (ids[i] <= 0 || counts[i] <= 0)
			{
				return false;
			}
		}
		return true;
	}

	internal int Enable(RecipeProto[] recipes)
	{
		int num = 0;
		if (recipes == null)
		{
			return num;
		}
		foreach (RecipeProto val in recipes)
		{
			if (val != null && !val.Handcraft && Eligible(val))
			{
				changed.Add(val);
				val.Handcraft = true;
				num++;
			}
		}
		return num;
	}

	internal void Restore()
	{
		foreach (RecipeProto item in changed)
		{
			item.Handcraft = false;
		}
		changed.Clear();
	}
}
internal sealed class ReplicatorButton : IDisposable
{
	private static readonly FieldInfo ForgeField = AccessTools.Field(typeof(UIReplicatorWindow), "mechaForge");

	private readonly Action<MechaForge> complete;

	private readonly Func<MechaForge, bool> available;

	private UIReplicatorWindow owner;

	private Button button;

	internal ReplicatorButton(Action<MechaForge> complete, Func<MechaForge, bool> available)
	{
		this.complete = complete;
		this.available = available;
	}

	private static MechaForge Forge(UIReplicatorWindow window)
	{
		if (!((Object)(object)window == (Object)null) && !(ForgeField == null))
		{
			object? value = ForgeField.GetValue(window);
			return (MechaForge)((value is MechaForge) ? value : null);
		}
		return null;
	}

	internal void Open(UIReplicatorWindow window)
	{
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Expected O, but got Unknown
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00db: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0121: Unknown result type (might be due to invalid IL or missing references)
		//IL_0135: Unknown result type (might be due to invalid IL or missing references)
		//IL_0166: Unknown result type (might be due to invalid IL or missing references)
		//IL_016b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0183: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0201: Unknown result type (might be due to invalid IL or missing references)
		//IL_0233: Unknown result type (might be due to invalid IL or missing references)
		//IL_023a: Expected O, but got Unknown
		//IL_0287: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02be: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_02cb: Unknown result type (might be due to invalid IL or missing references)
		//IL_02eb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0329: Unknown result type (might be due to invalid IL or missing references)
		//IL_032b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0346: Unknown result type (might be due to invalid IL or missing references)
		//IL_034d: Expected O, but got Unknown
		UnityAction val = null;
		if ((Object)(object)owner != (Object)(object)window || (Object)(object)button == (Object)null)
		{
			Dispose();
			owner = window;
			Transform val2 = ((Component)window).transform.Find("panel-bg");
			if ((Object)(object)val2 == (Object)null)
			{
				val2 = ((Component)window).transform;
			}
			GameObject val3 = new GameObject("Hand Crafted Trinkets Complete Queue", new Type[3]
			{
				typeof(RectTransform),
				typeof(Image),
				typeof(Button)
			});
			val3.transform.SetParent(val2, false);
			RectTransform component = val3.GetComponent<RectTransform>();
			Transform obj = val2.Find("btn-box");
			RectTransform val4 = (RectTransform)(object)((obj is RectTransform) ? obj : null);
			component.anchorMin = (((Object)(object)val4 == (Object)null) ? Vector2.one : val4.anchorMin);
			component.anchorMax = (((Object)(object)val4 == (Object)null) ? Vector2.one : val4.anchorMax);
			component.pivot = new Vector2(1f, 0.5f);
			component.sizeDelta = new Vector2(235f, 30f);
			component.anchoredPosition = new Vector2(-28f, -65f);
			Image component2 = val3.GetComponent<Image>();
			((Graphic)component2).color = Color.white;
			((Graphic)component2).raycastTarget = true;
			button = val3.GetComponent<Button>();
			((Selectable)button).targetGraphic = (Graphic)(object)component2;
			ColorBlock colors = ((Selectable)button).colors;
			((ColorBlock)(ref colors)).normalColor = new Color(0.05f, 0.09f, 0.12f, 1f);
			((ColorBlock)(ref colors)).highlightedColor = new Color(0.16f, 0.25f, 0.3f, 1f);
			((ColorBlock)(ref colors)).pressedColor = new Color(0.1f, 0.35f, 0.4f, 1f);
			((ColorBlock)(ref colors)).disabledColor = new Color(0.045f, 0.06f, 0.07f, 1f);
			((ColorBlock)(ref colors)).selectedColor = ((ColorBlock)(ref colors)).normalColor;
			((Selectable)button).colors = colors;
			GameObject val5 = new GameObject("Label", new Type[2]
			{
				typeof(RectTransform),
				typeof(Text)
			});
			val5.transform.SetParent(val3.transform, false);
			Text component3 = val5.GetComponent<Text>();
			component3.text = "Complete Replication Queue";
			component3.font = window.multiValueText.font;
			component3.fontSize = 16;
			component3.alignment = (TextAnchor)4;
			((Graphic)component3).color = Color.white;
			((Graphic)component3).raycastTarget = false;
			RectTransform rectTransform = ((Graphic)component3).rectTransform;
			rectTransform.anchorMin = Vector2.zero;
			rectTransform.anchorMax = Vector2.one;
			Vector2 offsetMin = (rectTransform.offsetMax = Vector2.zero);
			rectTransform.offsetMin = offsetMin;
			UIButton val6 = val3.AddComponent<UIButton>();
			val6.button = button;
			val6.tips = new TipSettings
			{
				tipTitle = "Complete Replication Queue",
				tipText = "Immediately complete the current queue using its reserved ingredients. Very large queues may briefly pause the game.",
				width = 290,
				delay = 0.35f,
				corner = 3
			};
			ButtonClickedEvent onClick = button.onClick;
			if (val == null)
			{
				val = (UnityAction)delegate
				{
					complete(Forge(owner));
				};
			}
			((UnityEvent)onClick).AddListener(val);
		}
		Refresh(window);
	}

	internal void Refresh(UIReplicatorWindow window)
	{
		if (!((Object)(object)button == (Object)null) && !((Object)(object)owner != (Object)(object)window))
		{
			MechaForge val = Forge(window);
			((Selectable)button).interactable = val != null && val.tasks != null && val.tasks.Count > 0 && available(val);
		}
	}

	public void Dispose()
	{
		if ((Object)(object)button != (Object)null)
		{
			((UnityEventBase)button.onClick).RemoveAllListeners();
			((Component)button).gameObject.SetActive(false);
			Object.Destroy((Object)(object)((Component)button).gameObject);
		}
		button = null;
		owner = null;
	}
}