Decompiled source of OrderTabletAssistantPlus v0.2.1

BepInEx/plugins/OrderTabletAssistantPlus/OrderTabletAssistantBoxFix.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Mirror;
using TMPro;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace OrderTabletAssistantBoxFix;

[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInPlugin("local.ordertabletassistant.physicalboxfix", "Order Tablet Assistant - Physical Box Fix", "0.2.1")]
public sealed class Plugin : BaseUnityPlugin
{
	private Harmony harmony;

	private static Plugin instance;

	private static readonly FieldInfo NpcEquipment = AccessTools.Field(typeof(NPC_Info), "equippedItem");

	private ConfigEntry<KeyCode> absentProductsKey;

	private FieldInfo originalInstance;

	private FieldInfo originalEnabled;

	private FieldInfo activeDevice;

	private FieldInfo scanInProgress;

	private MethodInfo getStock;

	private MethodInfo addToOrder;

	private MethodInfo refreshTotals;

	private MethodInfo showNotification;

	private FieldInfo panelText;

	private FieldInfo panelRect;

	private MethodInfo refreshPanelText;

	private bool absentFeatureReady;

	private float lastAbsentScan = float.NegativeInfinity;

	private void Awake()
	{
		//IL_0082: Unknown result type (might be due to invalid IL or missing references)
		//IL_008c: Expected O, but got Unknown
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Expected O, but got Unknown
		//IL_022b: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_02f3: Expected O, but got Unknown
		instance = this;
		Type type = AccessTools.TypeByName("OrderTabletAssistant.Plugin");
		MethodInfo methodInfo = ((type == null) ? null : AccessTools.DeclaredMethod(type, "CountEmptyLabelledStorageSlots", (Type[])null, (Type[])null));
		if (methodInfo == null || methodInfo.ReturnType != typeof(Dictionary<int, int>) || NpcEquipment == null)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"Box Fix: incompatible mod/game version; patch not applied.");
			return;
		}
		harmony = new Harmony("local.ordertabletassistant.physicalboxfix");
		harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "AdjustEmptyStorageSlots", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Box Fix active: non-empty physical boxes count as one storage slot each.");
		absentProductsKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Controls", "Absent products key", (KeyCode)110, "While the order tablet is open, add one box of each unlocked product with no stock anywhere. Purchases remain manual.");
		originalInstance = AccessTools.Field(type, "Instance");
		originalEnabled = AccessTools.Field(type, "Enabled");
		activeDevice = AccessTools.Field(type, "_activeDevice");
		scanInProgress = AccessTools.Field(type, "_scanInProgress");
		getStock = AccessTools.Method(typeof(ManagerBlackboard), "GetProductsExistences", new Type[1] { typeof(int) }, (Type[])null);
		addToOrder = AccessTools.DeclaredMethod(type, "AddProductToOrderWithoutRefresh", (Type[])null, (Type[])null);
		refreshTotals = AccessTools.DeclaredMethod(type, "RefreshOrderTotals", (Type[])null, (Type[])null);
		showNotification = AccessTools.DeclaredMethod(type, "ShowNativeNotification", (Type[])null, (Type[])null);
		absentFeatureReady = originalInstance != null && originalEnabled != null && activeDevice != null && scanInProgress != null && getStock != null && addToOrder != null && refreshTotals != null && showNotification != null;
		if (absentFeatureReady)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)string.Concat("Absent products: press ", absentProductsKey.Value, " with the tablet open."));
		}
		else
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"Absent products: incompatible API; hotkey disabled. Storage fix remains active.");
		}
		panelText = AccessTools.Field(type, "_nativePanelText");
		panelRect = AccessTools.Field(type, "_nativePanelRect");
		refreshPanelText = AccessTools.DeclaredMethod(type, "RefreshPanelText", (Type[])null, (Type[])null);
		if (absentFeatureReady && panelText != null && panelRect != null && refreshPanelText != null)
		{
			harmony.Patch((MethodBase)refreshPanelText, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "AppendAbsentProductsHint", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			absentProductsKey.SettingChanged += delegate
			{
				RefreshHint();
			};
			RefreshHint();
		}
		else
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)"Absent products: native panel hint could not be installed.");
		}
	}

	private void RefreshHint()
	{
		object value = originalInstance.GetValue(null);
		if (value != null)
		{
			refreshPanelText.Invoke(value, new object[0]);
		}
	}

	private static void AppendAbsentProductsHint(object __instance)
	{
		//IL_0091: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)instance == (Object)null || !instance.absentFeatureReady)
		{
			return;
		}
		object? value = instance.panelText.GetValue(__instance);
		TextMeshProUGUI val = (TextMeshProUGUI)((value is TextMeshProUGUI) ? value : null);
		object? value2 = instance.panelRect.GetValue(__instance);
		RectTransform val2 = (RectTransform)((value2 is RectTransform) ? value2 : null);
		if (!((Object)(object)val == (Object)null))
		{
			object text = ((TMP_Text)val).text;
			((TMP_Text)val).text = string.Concat(text, "\n", instance.absentProductsKey.Value, " : Nouveaux produits");
			if ((Object)(object)val2 != (Object)null)
			{
				Vector2 sizeDelta = val2.sizeDelta;
				val2.sizeDelta = new Vector2(sizeDelta.x, Math.Max(sizeDelta.y, 84f));
			}
		}
	}

	private void Update()
	{
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c4: Expected O, but got Unknown
		if (!absentFeatureReady || !Input.GetKeyDown(absentProductsKey.Value) || Time.unscaledTime - lastAbsentScan < 0.5f)
		{
			return;
		}
		OrderingDevice val = null;
		bool flag = false;
		int num = 0;
		try
		{
			ConfigEntry<bool> val2 = originalEnabled.GetValue(null) as ConfigEntry<bool>;
			object value = originalInstance.GetValue(null);
			if (val2 == null || !val2.Value || value == null)
			{
				return;
			}
			object? value2 = activeDevice.GetValue(value);
			val = (OrderingDevice)((value2 is OrderingDevice) ? value2 : null);
			if ((Object)(object)val == (Object)null || !((Behaviour)val).isActiveAndEnabled)
			{
				return;
			}
			if ((bool)scanInProgress.GetValue(value))
			{
				Notify("PRODUITS ABSENTS\nAttendez la fin du remplissage, puis reessayez.");
				return;
			}
			lastAbsentScan = Time.unscaledTime;
			ProductListing val3 = ProductListing.Instance;
			if ((Object)(object)val3 == (Object)null || (Object)(object)GameData.Instance == (Object)null || (Object)(object)NPC_Manager.Instance == (Object)null)
			{
				throw new InvalidOperationException("Game state is not ready.");
			}
			ManagerBlackboard component = ((Component)GameData.Instance).GetComponent<ManagerBlackboard>();
			if ((Object)(object)component == (Object)null || (Object)(object)component.shoppingListParent == (Object)null || component.idsToSpawn == null)
			{
				throw new InvalidOperationException("Shopping list / delivery state is not ready.");
			}
			val3.updateProductList();
			HashSet<int> hashSet = new HashSet<int>(component.idsToSpawn);
			HashSet<int> hashSet2 = new HashSet<int>();
			foreach (Transform item in component.shoppingListParent.transform)
			{
				Transform val4 = item;
				InteractableData component2 = ((Component)val4).GetComponent<InteractableData>();
				if ((Object)(object)component2 != (Object)null)
				{
					hashSet2.Add(component2.thisSkillIndex);
				}
			}
			OrderingListReferences[] componentsInChildren = ((Component)val).GetComponentsInChildren<OrderingListReferences>(true);
			foreach (OrderingListReferences val5 in componentsInChildren)
			{
				hashSet2.Add(val5.productID);
			}
			List<int> list = new List<int>();
			HashSet<int> hashSet3 = new HashSet<int>();
			bool flag2 = false;
			foreach (int availableProduct in val3.availableProducts)
			{
				if (!hashSet3.Add(availableProduct) || availableProduct < 0 || availableProduct >= val3.productsData.Length)
				{
					continue;
				}
				ProductData val6 = val3.productsData[availableProduct];
				if (val6 != null && val6.maxItemsPerBox > 0)
				{
					if (!flag2)
					{
						getStock.Invoke(component, new object[1] { availableProduct });
						flag2 = true;
					}
					int[] stock = getStock.Invoke(component, new object[1] { availableProduct }) as int[];
					if (MissingProductRules.ShouldAdd(stock, hashSet2.Contains(availableProduct), hashSet.Contains(availableProduct)))
					{
						list.Add(availableProduct);
					}
				}
			}
			foreach (int item2 in list)
			{
				flag = true;
				addToOrder.Invoke(null, new object[2] { val, item2 });
				num++;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Absent products: added one box for product " + item2));
			}
			Notify((num == 0) ? "PRODUITS ABSENTS\nAucun produit a ajouter." : ("PRODUITS ABSENTS\n" + num + " carton(s) ajoute(s).\nVerifiez puis validez votre achat."));
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Absent products scan failed after " + num + " additions: " + ex));
			Notify("PRODUITS ABSENTS\nErreur : verifiez le panier et le journal.");
		}
		finally
		{
			if (flag && (Object)(object)val != (Object)null)
			{
				try
				{
					refreshTotals.Invoke(null, new object[1] { val });
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("Absent products: total refresh failed: " + ex));
				}
			}
		}
	}

	private void Notify(string message)
	{
		((BaseUnityPlugin)this).Logger.LogInfo((object)message.Replace("\n", " | "));
		try
		{
			if (showNotification != null)
			{
				showNotification.Invoke(null, new object[1] { message });
			}
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Notification failed: " + ex.Message));
		}
	}

	private void OnDestroy()
	{
		if (harmony != null)
		{
			harmony.UnpatchSelf();
		}
		if ((Object)(object)instance == (Object)(object)this)
		{
			instance = null;
		}
	}

	private static void AdjustEmptyStorageSlots(ref Dictionary<int, int> __result)
	{
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Expected O, but got Unknown
		//IL_0155: Unknown result type (might be due to invalid IL or missing references)
		//IL_015b: Expected O, but got Unknown
		if (__result == null || __result.Count == 0)
		{
			return;
		}
		try
		{
			if ((Object)(object)GameData.Instance == (Object)null || (Object)(object)NPC_Manager.Instance == (Object)null)
			{
				throw new InvalidOperationException("Game state is not ready.");
			}
			ManagerBlackboard component = ((Component)GameData.Instance).GetComponent<ManagerBlackboard>();
			if ((Object)(object)component == (Object)null || (Object)(object)component.boxParent == (Object)null || (Object)(object)NPC_Manager.Instance.employeeParentOBJ == (Object)null)
			{
				throw new InvalidOperationException("Box/employee containers are not ready.");
			}
			Dictionary<int, int> dictionary = new Dictionary<int, int>();
			HashSet<int> hashSet = new HashSet<int>();
			foreach (Transform item in component.boxParent)
			{
				Transform val = item;
				BoxData component2 = ((Component)val).GetComponent<BoxData>();
				if ((Object)(object)component2 != (Object)null && hashSet.Add(((Object)component2).GetInstanceID()))
				{
					BoxCounts.AddNonEmptyBox(dictionary, component2.productID, component2.numberOfProducts);
				}
			}
			foreach (Transform item2 in NPC_Manager.Instance.employeeParentOBJ.transform)
			{
				Transform val = item2;
				NPC_Info component3 = ((Component)val).GetComponent<NPC_Info>();
				if ((Object)(object)component3 != (Object)null && hashSet.Add(((Object)component3).GetInstanceID()) && (int)NpcEquipment.GetValue(component3) == 1)
				{
					BoxCounts.AddNonEmptyBox(dictionary, component3.boxProductID, component3.boxNumberOfProducts);
				}
			}
			NetworkManager singleton = NetworkManager.singleton;
			CustomNetworkManager val2 = (CustomNetworkManager)(object)((singleton is CustomNetworkManager) ? singleton : null);
			if ((Object)(object)val2 != (Object)null)
			{
				foreach (PlayerObjectController gamePlayer in val2.GamePlayers)
				{
					if ((Object)(object)gamePlayer == (Object)null || !hashSet.Add(((Object)gamePlayer).GetInstanceID()))
					{
						continue;
					}
					PlayerNetwork component4 = ((Component)gamePlayer).GetComponent<PlayerNetwork>();
					if ((Object)(object)component4 == (Object)null || component4.equippedItem != 1)
					{
						continue;
					}
					if (((NetworkBehaviour)component4).isLocalPlayer)
					{
						BoxCounts.AddNonEmptyBox(dictionary, component4.extraParameter1, component4.extraParameter2);
						continue;
					}
					PlayerSyncCharacter component5 = ((Component)gamePlayer).GetComponent<PlayerSyncCharacter>();
					if ((Object)(object)component5 != (Object)null)
					{
						BoxCounts.AddNonEmptyBox(dictionary, component5.syncedProductID, component5.syncedNumberOfProducts);
					}
				}
			}
			Dictionary<int, int> dictionary2 = BoxCounts.SubtractAvailable(__result, dictionary);
			foreach (KeyValuePair<int, int> item3 in __result)
			{
				dictionary.TryGetValue(item3.Key, out var value);
				((BaseUnityPlugin)instance).Logger.LogInfo((object)$"Box Fix product {item3.Key}: empty slots={item3.Value}, available boxes={value}, storage order={dictionary2[item3.Key]}");
			}
			__result = dictionary2;
		}
		catch (Exception ex)
		{
			__result = new Dictionary<int, int>();
			((BaseUnityPlugin)instance).Logger.LogError((object)("Box Fix: storage fill skipped because stock could not be read: " + ex));
		}
	}
}
public static class BoxCounts
{
	public static void AddNonEmptyBox(Dictionary<int, int> counts, int productId, int units)
	{
		if (productId >= 0 && units > 0)
		{
			counts.TryGetValue(productId, out var value);
			counts[productId] = value + 1;
		}
	}

	public static Dictionary<int, int> SubtractAvailable(Dictionary<int, int> emptySlots, Dictionary<int, int> availableBoxes)
	{
		Dictionary<int, int> dictionary = new Dictionary<int, int>();
		foreach (KeyValuePair<int, int> emptySlot in emptySlots)
		{
			availableBoxes.TryGetValue(emptySlot.Key, out var value);
			dictionary[emptySlot.Key] = Math.Max(0, emptySlot.Value - value);
		}
		return dictionary;
	}
}
public static class MissingProductRules
{
	public static bool ShouldAdd(int[] stock, bool inCart, bool awaitingDelivery)
	{
		if (stock == null || stock.Length != 3)
		{
			throw new InvalidOperationException("The game's stock reader returned an invalid result.");
		}
		for (int i = 0; i < stock.Length; i++)
		{
			if (stock[i] < 0)
			{
				throw new InvalidOperationException("The game's stock count is negative.");
			}
		}
		return !inCart && !awaitingDelivery && stock[0] == 0 && stock[1] == 0 && stock[2] == 0;
	}
}