Decompiled source of StorageSorter v1.0.0

plugins/StorageSorter.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using UnityEngine;

[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("StorageSorter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("StorageSorter")]
[assembly: AssemblyTitle("StorageSorter")]
[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 StorageSorter
{
	internal static class Loc
	{
		public const string Auto = "Auto";

		public const string Fallback = "en";

		private static readonly Dictionary<string, Dictionary<string, string>> Tables = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase)
		{
			["en"] = new Dictionary<string, string>
			{
				["host_only"] = "Only the host can sort the storage.",
				["no_shelves"] = "No storage shelves found.",
				["aborted"] = "Storage sort aborted (check the BepInEx log).",
				["already_sorted"] = "Storage is already sorted.",
				["error"] = "An error occurred while sorting the storage (check the BepInEx log).",
				["sorted"] = "Storage sorted{1}: {0} slot(s) updated{2}",
				["sorted.category"] = " (category shelves first)",
				["sorted.merged"] = ", boxes {0} → {1}",
				["lost"] = "Warning: {0} box(es) could not be placed. Check the BepInEx log."
			},
			["ja"] = new Dictionary<string, string>
			{
				["host_only"] = "倉庫の並び替えはホストのみ実行できます。",
				["no_shelves"] = "倉庫の棚が見つかりません。",
				["aborted"] = "倉庫の並び替えを中止しました(BepInExのログを確認してください)。",
				["already_sorted"] = "倉庫はすでに整理されています。",
				["error"] = "倉庫の並び替え中にエラーが発生しました(BepInExのログを確認してください)。",
				["sorted"] = "倉庫を並び替えました{1}: {0} スロット更新{2}",
				["sorted.category"] = "(カテゴリ棚優先)",
				["sorted.merged"] = "、箱 {0}→{1}",
				["lost"] = "注意: {0} 箱を配置できませんでした。BepInExのログを確認してください。"
			}
		};

		public static IEnumerable<string> Available => Tables.Keys.OrderBy<string, string>((string k) => k, StringComparer.OrdinalIgnoreCase);

		public static string Current
		{
			get
			{
				string text = Plugin.Language?.Value ?? "Auto";
				if (!string.Equals(text, "Auto", StringComparison.OrdinalIgnoreCase) && Tables.ContainsKey(text))
				{
					return text;
				}
				if (!GameIsJapanese())
				{
					return "en";
				}
				return "ja";
			}
		}

		public static void LoadExternal(string pluginDir)
		{
			string path = Path.Combine(pluginDir, "lang");
			if (!Directory.Exists(path))
			{
				return;
			}
			string[] files = Directory.GetFiles(path, "*.txt");
			foreach (string text in files)
			{
				string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
				if (fileNameWithoutExtension.StartsWith("_") || fileNameWithoutExtension.Length == 0)
				{
					continue;
				}
				try
				{
					if (!Tables.TryGetValue(fileNameWithoutExtension, out var value))
					{
						value = (Tables[fileNameWithoutExtension] = new Dictionary<string, string>());
					}
					int num = 0;
					string[] array = File.ReadAllLines(text, Encoding.UTF8);
					for (int j = 0; j < array.Length; j++)
					{
						string text2 = array[j].Trim();
						if (text2.Length == 0 || text2.StartsWith("#"))
						{
							continue;
						}
						int num2 = text2.IndexOf('=');
						if (num2 > 0)
						{
							string key = text2.Substring(0, num2).Trim();
							string text3 = text2.Substring(num2 + 1).Trim();
							if (text3.Length >= 2 && text3[0] == '"' && text3[text3.Length - 1] == '"')
							{
								text3 = text3.Substring(1, text3.Length - 2);
							}
							value[key] = text3.Replace("\\n", "\n");
							num++;
						}
					}
					Plugin.Log.LogInfo((object)$"Loaded language file '{fileNameWithoutExtension}' ({num} entries).");
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Could not read language file " + text + ": " + ex.Message));
				}
			}
		}

		public static string T(string key, params object[] args)
		{
			string value = null;
			if (Tables.TryGetValue(Current, out var value2))
			{
				value2.TryGetValue(key, out value);
			}
			if (value == null)
			{
				Tables["en"].TryGetValue(key, out value);
			}
			if (value == null)
			{
				return key;
			}
			if (args == null || args.Length == 0)
			{
				return value;
			}
			try
			{
				return string.Format(value, args);
			}
			catch (FormatException)
			{
				return value;
			}
		}

		private static bool GameIsJapanese()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Invalid comparison between Unknown and I4
			LocalizationManager instance = LocalizationManager.instance;
			if ((Object)(object)instance == (Object)null || instance.LocalizationDictionary == null || instance.LocalizationDictionary.Count == 0)
			{
				return (int)Application.systemLanguage == 22;
			}
			int num = 0;
			foreach (string value in instance.LocalizationDictionary.Values)
			{
				if (value != null && value.Any((char c) => c >= '\u3040' && c <= 'ヿ'))
				{
					return true;
				}
				if (++num >= 300)
				{
					break;
				}
			}
			return false;
		}
	}
	internal static class Notifier
	{
		private static FieldInfo _cooldownField;

		private static bool _resolved;

		public static void Show(string message)
		{
			Plugin.Log.LogInfo((object)("[Notify] " + message));
			GameCanvas instance = GameCanvas.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				if (!_resolved)
				{
					_resolved = true;
					_cooldownField = typeof(GameCanvas).GetField("inCooldown", BindingFlags.Instance | BindingFlags.NonPublic);
				}
				_cooldownField?.SetValue(instance, false);
				instance.CreateCanvasNotification("`" + message);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Notification failed: " + ex.Message));
			}
		}
	}
	internal static class OaoBridge
	{
		public const string OaoGuid = "com.ddqminik.supermarkettogether.orderandorganize";

		private static bool _resolved;

		private static object _manager;

		private static MethodInfo _getCategory;

		public static bool Available
		{
			get
			{
				Resolve();
				if (_manager != null)
				{
					return _getCategory != null;
				}
				return false;
			}
		}

		public static int? GetCategory(Data_Container container)
		{
			if (!Available)
			{
				return null;
			}
			try
			{
				return (int?)_getCategory.Invoke(_manager, new object[1] { container });
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("OrderAndOrganize category lookup failed: " + ex.Message));
				return null;
			}
		}

		private static void Resolve()
		{
			if (_resolved && _manager != null)
			{
				return;
			}
			_resolved = true;
			try
			{
				if (!Chainloader.PluginInfos.TryGetValue("com.ddqminik.supermarkettogether.orderandorganize", out var value) || (Object)(object)value.Instance == (Object)null)
				{
					return;
				}
				_manager = AccessTools.Field(((object)value.Instance).GetType(), "_categoryShelfManager")?.GetValue(value.Instance);
				if (_manager != null)
				{
					_getCategory = AccessTools.Method(_manager.GetType(), "GetCategory", new Type[1] { typeof(Data_Container) }, (Type[])null);
					if (_getCategory != null)
					{
						Plugin.Log.LogInfo((object)"OrderAndOrganize category shelves detected.");
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not link to OrderAndOrganize: " + ex.Message));
				_manager = null;
			}
		}
	}
	[BepInPlugin("penguin4glte.supermarkettogether.storagesorter", "StorageSorter", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "penguin4glte.supermarkettogether.storagesorter";

		public const string PluginName = "StorageSorter";

		public const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<KeyboardShortcut> SortHotkey;

		internal static ConfigEntry<bool> MergeBoxes;

		internal static ConfigEntry<bool> UseCategoryShelves;

		internal static ConfigEntry<string> Language;

		private Harmony _harmony;

		private float _lastSortTime = -999f;

		private void Awake()
		{
			//IL_003a: 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_00d9: Expected O, but got Unknown
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected O, but got Unknown
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			Loc.LoadExternal(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location));
			SortHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "SortHotkey", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "Key that sorts the storage (host only).\n倉庫の並び替えを実行するキー(ホストのみ)。");
			MergeBoxes = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "MergeBoxes", true, "Combine partially filled boxes of the same product up to the per-box limit. The total item count never changes.\n同じ商品で中身が中途半端な箱を、1箱の上限まで寄せてまとめます。商品の総数は変わりません。");
			UseCategoryShelves = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UseCategoryShelves", true, "Fill shelves that have an OrderAndOrganize category with that category first.\nOrderAndOrganize でカテゴリを設定した棚に、そのカテゴリの商品を優先して入れます。");
			Language = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Language", "Auto", new ConfigDescription("Message language. Auto = Japanese if the game is in Japanese, otherwise English. Add more languages as lang/<code>.txt in the plugin folder.\nメッセージの言語。Auto = ゲームが日本語なら日本語、それ以外は英語。", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[1] { "Auto" }.Concat(Loc.Available).ToArray()), Array.Empty<object>()));
			_harmony = new Harmony("penguin4glte.supermarkettogether.storagesorter");
			_harmony.PatchAll(typeof(SortGuardPatches));
			Log.LogInfo((object)string.Format("{0} {1} loaded. Hotkey: {2}", "StorageSorter", "1.0.0", SortHotkey.Value));
		}

		private void OnDestroy()
		{
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Invalid comparison between Unknown and I4
			KeyboardShortcut value = SortHotkey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown() && (int)Cursor.lockState == 1 && !StorageSortRunner.IsSorting && !(Time.unscaledTime - _lastSortTime < 2f) && !((Object)(object)NPC_Manager.Instance == (Object)null) && !((Object)(object)ProductListing.Instance == (Object)null) && !((Object)(object)NPC_Manager.Instance.storageOBJ == (Object)null))
			{
				if (!NetworkServer.active)
				{
					Notifier.Show(Loc.T("host_only"));
					return;
				}
				_lastSortTime = Time.unscaledTime;
				((MonoBehaviour)this).StartCoroutine(StorageSortRunner.Run());
			}
		}
	}
	[HarmonyPatch]
	internal static class SortGuardPatches
	{
		[HarmonyPatch(typeof(NPC_Manager), "GetFreeStorageRow")]
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void FreeRowPostfix(ref int __result)
		{
			if (StorageSortRunner.IsSorting)
			{
				__result = -1;
			}
		}

		[HarmonyPatch(typeof(NPC_Manager), "GetStorageRowWithBoxToMerge")]
		[HarmonyPostfix]
		[HarmonyPriority(0)]
		private static void MergeRowPostfix(ref int __result)
		{
			if (StorageSortRunner.IsSorting)
			{
				__result = -1;
			}
		}

		[HarmonyPatch(typeof(Data_Container), "GetStorageBox")]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static bool GetStorageBoxPrefix()
		{
			return !StorageSortRunner.IsSorting;
		}

		[HarmonyPatch(typeof(Data_Container), "BoxItemFromStorageTray")]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static bool TrayPrefix()
		{
			return !StorageSortRunner.IsSorting;
		}
	}
	public sealed class SlotInfo
	{
		public int ShelfOrder;

		public int SlotIndex;

		public int? Category;

		public int Pid;

		public int Count;

		public bool Locked;

		public object Tag;

		public bool HasBox
		{
			get
			{
				if (Pid >= 0)
				{
					return Count >= 0;
				}
				return false;
			}
		}
	}
	public struct SlotContent : IEquatable<SlotContent>
	{
		public int Pid;

		public int Count;

		public static readonly SlotContent Empty = new SlotContent(-1, -1);

		public bool IsEmpty
		{
			get
			{
				if (Pid == -1)
				{
					return Count == -1;
				}
				return false;
			}
		}

		public SlotContent(int pid, int count)
		{
			Pid = pid;
			Count = count;
		}

		public bool Equals(SlotContent o)
		{
			if (Pid == o.Pid)
			{
				return Count == o.Count;
			}
			return false;
		}

		public override bool Equals(object o)
		{
			if (o is SlotContent o2)
			{
				return Equals(o2);
			}
			return false;
		}

		public override int GetHashCode()
		{
			return (Pid * 397) ^ Count;
		}

		public override string ToString()
		{
			if (!IsEmpty)
			{
				return $"P{Pid}x{Count}";
			}
			return "(empty)";
		}
	}
	public sealed class PlanResult
	{
		public Dictionary<SlotInfo, SlotContent> Target = new Dictionary<SlotInfo, SlotContent>();

		public int BoxesBefore;

		public int BoxesAfter;

		public int UnitsBefore;

		public int UnitsAfter;

		public int ChangedSlots;

		public string Error;
	}
	public static class StoragePlanner
	{
		private sealed class Box
		{
			public int Pid;

			public int Count;

			public int Group;
		}

		public static PlanResult Plan(IList<SlotInfo> slots, Func<int, int> groupOf, Func<int, int> maxPerBox, bool merge)
		{
			PlanResult result = new PlanResult();
			List<SlotInfo> list = slots.Where((SlotInfo s) => !s.Locked).ToList();
			List<Box> list2 = new List<Box>();
			foreach (SlotInfo item in list.Where((SlotInfo s) => s.HasBox))
			{
				list2.Add(new Box
				{
					Pid = item.Pid,
					Count = item.Count,
					Group = groupOf(item.Pid)
				});
			}
			result.BoxesBefore = list2.Count;
			result.UnitsBefore = list2.Sum((Box b) => b.Count);
			if (merge)
			{
				List<Box> list3 = new List<Box>();
				foreach (IGrouping<int, Box> item2 in from b in list2
					group b by b.Pid)
				{
					int num = Math.Max(1, maxPerBox(item2.Key));
					int num2 = item2.Where((Box b) => b.Count > 0).Sum((Box b) => b.Count);
					int num3 = item2.First().Group;
					int num4 = item2.Count((Box b) => b.Count > 0);
					if ((num2 + num - 1) / num > num4)
					{
						list3.AddRange(item2.Where((Box b) => b.Count > 0));
					}
					else
					{
						while (num2 > 0)
						{
							int num5 = Math.Min(num, num2);
							list3.Add(new Box
							{
								Pid = item2.Key,
								Count = num5,
								Group = num3
							});
							num2 -= num5;
						}
					}
					list3.AddRange(item2.Where((Box b) => b.Count == 0));
				}
				list2 = list3;
			}
			list2 = (from b in list2
				orderby b.Group, b.Pid, b.Count descending
				select b).ToList();
			result.BoxesAfter = list2.Count;
			result.UnitsAfter = list2.Sum((Box b) => b.Count);
			List<List<SlotInfo>> list4 = (from s in list
				group s by s.ShelfOrder into g
				orderby g.Key
				select g.OrderBy((SlotInfo s) => s.SlotIndex).ToList()).ToList();
			foreach (SlotInfo item3 in list)
			{
				result.Target[item3] = SlotContent.Empty;
			}
			LinkedList<Box> linkedList = new LinkedList<Box>(list2);
			Dictionary<List<SlotInfo>, int> freeSlots = new Dictionary<List<SlotInfo>, int>();
			foreach (List<SlotInfo> item4 in list4)
			{
				freeSlots[item4] = 0;
			}
			foreach (List<SlotInfo> item5 in list4.Where((List<SlotInfo> sh) => sh[0].Category.HasValue))
			{
				int value = item5[0].Category.Value;
				LinkedListNode<Box> linkedListNode = linkedList.First;
				while (linkedListNode != null && freeSlots[item5] < item5.Count)
				{
					LinkedListNode<Box>? next = linkedListNode.Next;
					if (linkedListNode.Value.Group == value)
					{
						Put(item5, linkedListNode.Value);
						linkedList.Remove(linkedListNode);
					}
					linkedListNode = next;
				}
			}
			bool[] array = new bool[2] { false, true };
			foreach (bool pass in array)
			{
				foreach (List<SlotInfo> item6 in list4.Where((List<SlotInfo> sh) => sh[0].Category.HasValue == pass))
				{
					while (linkedList.Count > 0 && freeSlots[item6] < item6.Count)
					{
						Put(item6, linkedList.First.Value);
						linkedList.RemoveFirst();
					}
				}
			}
			if (linkedList.Count > 0)
			{
				result.Error = $"{linkedList.Count} box(es) did not fit (this should not happen).";
				return result;
			}
			if (result.UnitsAfter != result.UnitsBefore)
			{
				result.Error = $"Unit count mismatch {result.UnitsBefore} -> {result.UnitsAfter}.";
				return result;
			}
			result.ChangedSlots = list.Count((SlotInfo s) => !result.Target[s].Equals(new SlotContent(s.Pid, s.Count)));
			return result;
			void Put(List<SlotInfo> shelf, Box b)
			{
				int index = freeSlots[shelf]++;
				result.Target[shelf[index]] = new SlotContent(b.Pid, b.Count);
			}
		}
	}
	internal static class StorageSortRunner
	{
		private sealed class SlotRef
		{
			public Data_Container Container;

			public int Index;
		}

		public static bool IsSorting { get; private set; }

		public static IEnumerator Run()
		{
			IsSorting = true;
			List<KeyValuePair<SlotInfo, SlotContent>> toFill = null;
			PlanResult plan = null;
			bool flag = false;
			List<SlotInfo> list = new List<SlotInfo>();
			try
			{
				List<SlotInfo> list2 = Snapshot();
				if (list2.Count == 0)
				{
					Notifier.Show(Loc.T("no_shelves"));
					yield break;
				}
				Dictionary<int, int> groups = BuildProductGroups();
				ProductData[] products = ProductListing.Instance.productsData;
				plan = StoragePlanner.Plan(list2, (int pid) => (!groups.TryGetValue(pid, out var value)) ? int.MaxValue : value, (int pid) => products[pid].maxItemsPerBox, Plugin.MergeBoxes.Value);
				if (plan.Error != null)
				{
					Plugin.Log.LogError((object)("Sort aborted: " + plan.Error));
					Notifier.Show(Loc.T("aborted"));
					yield break;
				}
				if (plan.ChangedSlots == 0)
				{
					Notifier.Show(Loc.T("already_sorted"));
					yield break;
				}
				toFill = new List<KeyValuePair<SlotInfo, SlotContent>>();
				foreach (KeyValuePair<SlotInfo, SlotContent> item in plan.Target)
				{
					SlotInfo key = item.Key;
					SlotContent o = new SlotContent(key.Pid, key.Count);
					if (!item.Value.Equals(o))
					{
						if (!o.IsEmpty)
						{
							list.Add(key);
							Write(key, SlotContent.Empty);
						}
						if (!item.Value.IsEmpty)
						{
							toFill.Add(item);
						}
					}
				}
				flag = true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Sort failed: " + ex));
				foreach (SlotInfo item2 in list)
				{
					try
					{
						Write(item2, new SlotContent(item2.Pid, item2.Count));
					}
					catch (Exception ex2)
					{
						Plugin.Log.LogError((object)("Restore failed: " + ex2.Message));
					}
				}
				Notifier.Show(Loc.T("error"));
			}
			finally
			{
				if (!flag)
				{
					IsSorting = false;
				}
			}
			if (!flag)
			{
				yield break;
			}
			yield return (object)new WaitForSecondsRealtime(0.3f);
			try
			{
				List<SlotContent> list3 = new List<SlotContent>();
				foreach (KeyValuePair<SlotInfo, SlotContent> item3 in toFill)
				{
					if (Read(item3.Key).IsEmpty)
					{
						Write(item3.Key, item3.Value);
					}
					else
					{
						list3.Add(item3.Value);
					}
				}
				int num = 0;
				if (list3.Count > 0)
				{
					List<SlotInfo> list4 = plan.Target.Keys.Where((SlotInfo s) => Read(s).IsEmpty).ToList();
					foreach (SlotContent item4 in list3)
					{
						if (list4.Count == 0)
						{
							num++;
							continue;
						}
						Write(list4[0], item4);
						list4.RemoveAt(0);
					}
					Plugin.Log.LogWarning((object)$"{list3.Count} box(es) re-homed because their slot was taken during the sort; {num} could not be placed.");
				}
				string text = ((plan.BoxesAfter < plan.BoxesBefore) ? Loc.T("sorted.merged", plan.BoxesBefore, plan.BoxesAfter) : "");
				string text2 = ((Plugin.UseCategoryShelves.Value && OaoBridge.Available) ? Loc.T("sorted.category") : "");
				Notifier.Show(Loc.T("sorted", plan.ChangedSlots, text2, text));
				Plugin.Log.LogInfo((object)$"Sorted storage: boxes {plan.BoxesBefore}->{plan.BoxesAfter}, units {plan.UnitsBefore}, changed {plan.ChangedSlots}, lost {num}");
				if (num > 0)
				{
					Notifier.Show(Loc.T("lost", num));
				}
			}
			catch (Exception ex3)
			{
				Plugin.Log.LogError((object)("Sort failed during refill: " + ex3));
				Notifier.Show(Loc.T("error"));
			}
			finally
			{
				IsSorting = false;
			}
		}

		private static List<SlotInfo> Snapshot()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			List<SlotInfo> list = new List<SlotInfo>();
			NPC_Manager instance = NPC_Manager.Instance;
			Transform transform = instance.storageOBJ.transform;
			Vector3 refPoint = instance.storageDistanceReferencePoint;
			int num = ProductListing.Instance.productsData.Length;
			bool value = Plugin.UseCategoryShelves.Value;
			List<Data_Container> list2 = new List<Data_Container>();
			for (int i = 0; i < transform.childCount; i++)
			{
				Data_Container component = ((Component)transform.GetChild(i)).GetComponent<Data_Container>();
				if ((Object)(object)component != (Object)null && component.containerClass == 69 && (Object)(object)((Component)component).transform.Find("BoxContainer") != (Object)null)
				{
					list2.Add(component);
				}
			}
			list2 = list2.OrderBy((Data_Container c) => Vector3.Distance(refPoint, ((Component)c).transform.position)).ToList();
			for (int num2 = 0; num2 < list2.Count; num2++)
			{
				Data_Container val = list2[num2];
				int? category = (value ? OaoBridge.GetCategory(val) : ((int?)null));
				int[] productInfoArray = val.productInfoArray;
				Transform val2 = ((Component)val).transform.Find("BoxContainer");
				int num3 = Math.Min(productInfoArray.Length / 2, val2.childCount);
				for (int num4 = 0; num4 < num3; num4++)
				{
					int num5 = productInfoArray[num4 * 2];
					int count = productInfoArray[num4 * 2 + 1];
					list.Add(new SlotInfo
					{
						ShelfOrder = num2,
						SlotIndex = num4,
						Category = category,
						Pid = num5,
						Count = count,
						Locked = (num5 >= num || num5 < -1),
						Tag = new SlotRef
						{
							Container = val,
							Index = num4
						}
					});
				}
			}
			return list;
		}

		private static Dictionary<int, int> BuildProductGroups()
		{
			Dictionary<int, int> dictionary = new Dictionary<int, int>();
			ProductListing instance = ProductListing.Instance;
			if (instance.tiers == null || instance.productGroups == null)
			{
				return dictionary;
			}
			for (int i = 0; i < instance.tiers.Length && i < instance.productGroups.Length; i++)
			{
				string[] array = (instance.tiers[i] ?? "").Split(new char[1] { '-' });
				if (array.Length != 2 || !int.TryParse(array[0], out var result) || !int.TryParse(array[1], out var result2))
				{
					continue;
				}
				int num = instance.productGroups[i];
				if (num >= 0)
				{
					for (int j = result; j <= result2; j++)
					{
						dictionary[j] = num;
					}
				}
			}
			return dictionary;
		}

		private static SlotContent Read(SlotInfo s)
		{
			SlotRef slotRef = (SlotRef)s.Tag;
			if ((Object)(object)slotRef.Container == (Object)null)
			{
				return new SlotContent(int.MinValue, int.MinValue);
			}
			int[] productInfoArray = slotRef.Container.productInfoArray;
			return new SlotContent(productInfoArray[slotRef.Index * 2], productInfoArray[slotRef.Index * 2 + 1]);
		}

		private static void Write(SlotInfo s, SlotContent v)
		{
			SlotRef slotRef = (SlotRef)s.Tag;
			if (!((Object)(object)slotRef.Container == (Object)null))
			{
				slotRef.Container.EmployeeUpdateArrayValuesStorage(slotRef.Index * 2, v.Pid, v.Count);
			}
		}
	}
}