Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of NewtSortingChests v1.0.0
plugins/NewtSortingChests.dll
Decompiled 2 weeks agousing 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 NewtSortingChests.Core; 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("NewtSortingChests")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+a296644ca389b5a74346379345fc80d106869244")] [assembly: AssemblyProduct("NewtSortingChests")] [assembly: AssemblyTitle("NewtSortingChests")] [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 NewtSortingChests { internal static class ChestSort { private sealed class Info { public string Type; public string Stack; public Category Cat; public bool Excluded; } private sealed class Chest { public Container Container; public Inventory Inv; public ZNetView View; public float Dist; public Vector3 Pos; public float Yaw; public int Slots; public bool Protected; public bool Ok { get { if ((Object)(object)View != (Object)null) { return View.IsValid(); } return false; } } } private const float ProtectedShare = 0.5f; private const int ProtectedMinStacks = 2; private const int PushDepth = 3; private const double ChainBudgetMs = 4.0; private static readonly Dictionary<ItemData, Info> s_info = new Dictionary<ItemData, Info>(); private static Stopwatch s_frame; private const int MaxPasses = 400; private const double BudgetMs = 2.5; private static List<RowLayout.Row> s_rows = new List<RowLayout.Row>(); private static readonly MethodInfo s_save = AccessTools.Method(typeof(Container), "Save", (Type[])null, (Type[])null); private static Info InfoOf(ItemData it) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected I4, but got Unknown if (!s_info.TryGetValue(it, out var value)) { string text = (((Object)(object)it.m_dropPrefab != (Object)null) ? ((Object)it.m_dropPrefab).name : it.m_shared.m_name); Category category = Categories.Of(text, (int)it.m_shared.m_itemType, it.m_shared.m_food); Info info = new Info(); info.Type = text; info.Stack = it.m_shared.m_name + "|" + it.m_quality + "|" + it.m_worldLevel + "|" + it.m_variant; info.Cat = category; info.Excluded = Plugin.ExcludedCats.Contains(category); value = info; s_info[it] = value; } return value; } public static IEnumerator Run() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)localPlayer == (Object)null) { yield break; } Stopwatch sw = (s_frame = Stopwatch.StartNew()); s_info.Clear(); List<Chest> chests = Gather(localPlayer, Plugin.Range); if (chests.Count == 0) { Say($"no chests to sort within {Plugin.Range:0} m."); yield break; } Log.Info($"NewtSortingChests: sorting {chests.Count} chests in {s_rows.Count} rows on {s_rows.Select((RowLayout.Row r) => r.Wall).Distinct().Count()} walls " + string.Format("({0}) - rows read {1}, {2}", string.Join(",", s_rows.Select((RowLayout.Row r) => r.Ids.Count)), Plugin.RowDir, Plugin.RowOrd)); if (Plugin.DebugDumps) { DumpChests(chests, localPlayer); } yield return null; sw.Restart(); foreach (Chest item in chests) { try { item.View.ClaimOwnership(); } catch (Exception) { } } foreach (Chest item2 in chests) { if (item2.Ok) { MergeStacks(item2.Inv); } if (sw.Elapsed.TotalMilliseconds > 2.5) { yield return null; sw.Restart(); } } Dictionary<string, Category> typeCat = new Dictionary<string, Category>(StringComparer.Ordinal); Dictionary<string, List<SortPlan.Piece>> typePieces = new Dictionary<string, List<SortPlan.Piece>>(StringComparer.Ordinal); List<int> list = new List<int>(chests.Count); int num = 0; int protectedCount = 0; foreach (Chest item3 in chests) { int num2 = 0; int num3 = 0; int num4 = 0; HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (ItemData allItem in item3.Inv.GetAllItems()) { num3++; if (Excluded(allItem)) { num2++; if (Plugin.ProtectCats.Contains(CatOf(allItem))) { num4++; hashSet.Add(TypeKey(allItem)); } continue; } string key = TypeKey(allItem); typeCat[key] = CatOf(allItem); if (!typePieces.TryGetValue(key, out var value)) { value = new List<SortPlan.Piece>(); typePieces[key] = value; } value.Add(new SortPlan.Piece(StackKey(allItem), allItem.m_stack, Math.Max(1, allItem.m_shared.m_maxStackSize))); num++; } item3.Protected = num4 >= 2 && (float)num4 >= (float)num3 * 0.5f; if (item3.Protected) { protectedCount++; Log.Info(string.Format("NewtSortingChests: protected chest at ({0:0.0}, {1:0.0}, {2:0.0}): {3} of {4} slots are {5}", item3.Pos.x, item3.Pos.y, item3.Pos.z, num4, num3, string.Join(", ", hashSet))); } list.Add((!item3.Protected) ? Math.Max(0, item3.Slots - num2) : 0); } if (protectedCount > 0) { Log.Info($"NewtSortingChests: {protectedCount} dedicated chest(s) protected - nothing is moved into them."); } if (num == 0) { Say($"the {chests.Count} chest(s) in range are empty."); yield break; } List<SortPlan.TypeGroup> list2 = (from name in typePieces.Keys select new SortPlan.TypeGroup(typeCat[name], name, SortPlan.SlotsNeeded(typePieces[name])) into g orderby Categories.Order(g.Cat) select g).ThenBy<SortPlan.TypeGroup, string>((SortPlan.TypeGroup g) => g.Key, StringComparer.Ordinal).ToList(); SortPlan.Packing plan = SortPlan.PackBest(list, list2); string text = ((plan.Used == SortPlan.Boundary.Category) ? "one chest per category" : ((plan.Used == SortPlan.Boundary.Family) ? "fresh chest per family only (not enough room for one per category)" : "continuous shelf (not enough room for fresh chests)")); Log.Info($"NewtSortingChests: plan needs {list2.Sum((SortPlan.TypeGroup g) => g.Slots)} slots, {list.Sum()} usable in {list.Count((int x) => x > 0)} chests; layout: {text}; " + $"{plan.Overflow.Count} item type(s) do not fit anywhere" + ((plan.Overflow.Count > 0) ? (": " + string.Join(", ", plan.Overflow.Take(12))) : "")); int moved = 0; int parked = 0; int passes = 0; bool progress = true; while (progress && passes < 400) { progress = false; passes++; for (int si = 0; si < chests.Count; si++) { if (!chests[si].Ok) { continue; } foreach (ItemData item4 in MisplacedIn(chests, si, plan)) { if (TryHome(chests, plan, si, item4, 3, new HashSet<int>(), ref moved)) { progress = true; } if (sw.Elapsed.TotalMilliseconds > 2.5) { yield return null; sw.Restart(); } } } if (progress) { continue; } for (int si = 0; si < chests.Count; si++) { if (progress) { break; } Chest src = chests[si]; if (!src.Ok) { continue; } foreach (ItemData item5 in MisplacedIn(chests, si, plan)) { string text2 = TypeKey(item5); foreach (int item6 in plan.Chests[text2]) { Chest chest = chests[item6]; if (item6 == si || !chest.Ok || !HasQuotaRoom(chest.Inv, text2, plan.Quota(text2, item6), item5)) { continue; } ItemData val = MisplacedIn(chests, item6, plan).FirstOrDefault(); if (val == null) { continue; } int num5 = ParkingFor(chests, val, item6); if (num5 < 0) { continue; } int stack = val.m_stack; try { chests[num5].Inv.MoveItemToThis(chest.Inv, val); } catch (Exception) { } if (chest.Inv.ContainsItem(val) && val.m_stack == stack) { continue; } parked++; if (chest.Inv.CanAddItem(item5, item5.m_stack)) { int stack2 = item5.m_stack; try { chest.Inv.MoveItemToThis(src.Inv, item5); } catch (Exception) { } if (!src.Inv.ContainsItem(item5) || item5.m_stack != stack2) { moved++; } } progress = true; break; } if (progress) { break; } if (sw.Elapsed.TotalMilliseconds > 2.5) { yield return null; sw.Restart(); } } } } if (passes >= 400) { Log.Warn($"NewtSortingChests: stopped at the {400}-pass safety cap while still making progress."); } foreach (Chest item7 in chests) { if (item7.Ok) { if (!item7.Protected) { MergeStacks(item7.Inv); Reorder(item7.Inv); } Save(item7); } if (sw.Elapsed.TotalMilliseconds > 2.5) { yield return null; sw.Restart(); } } int num6 = 0; int num7 = 0; for (int num8 = 0; num8 < chests.Count; num8++) { if (!chests[num8].Ok) { continue; } foreach (ItemData item8 in MisplacedIn(chests, num8, plan)) { num6++; if (plan.Chests[TypeKey(item8)].Count == 0) { num7++; } } } string text3 = ((num6 > 0) ? $" {num6} item(s) stayed put ({num7} with no room in any chest, {num6 - num7} blocked)." : ""); string text4 = ((protectedCount > 0) ? $" {protectedCount} potion chest(s) left alone." : ""); Say($"<color=#9ecbff>NewtSortingChests</color> organised {chests.Count} chest(s): {moved} move(s).{text3}{text4}"); Log.Info($"NewtSortingChests: sorted {chests.Count} chests, {moved} moves, {parked} parked, {passes} passes, {num6} left behind ({num7} no room anywhere, {num6 - num7} blocked), {protectedCount} protected."); } private static bool TryHome(List<Chest> chests, SortPlan.Packing plan, int si, ItemData it, int depth, HashSet<int> busy, ref int moved) { Chest chest = chests[si]; string text = TypeKey(it); foreach (int item in plan.Chests[text]) { Chest chest2 = chests[item]; if (item == si || !chest2.Ok || busy.Contains(item) || !HasQuotaRoom(chest2.Inv, text, plan.Quota(text, item), it)) { continue; } if (!chest2.Inv.CanAddItem(it, it.m_stack) && depth > 0 && s_frame.Elapsed.TotalMilliseconds < 4.0) { busy.Add(item); int num = 0; foreach (ItemData item2 in MisplacedIn(chests, item, plan)) { if (num++ >= 6) { break; } TryHome(chests, plan, item, item2, depth - 1, busy, ref moved); if (chest2.Inv.CanAddItem(it, it.m_stack)) { break; } } busy.Remove(item); } if (chest2.Inv.CanAddItem(it, it.m_stack)) { int stack = it.m_stack; try { chest2.Inv.MoveItemToThis(chest.Inv, it); } catch (Exception) { } if (!chest.Inv.ContainsItem(it) || it.m_stack != stack) { moved++; return true; } } } return false; } private static List<ItemData> MisplacedIn(List<Chest> chests, int ci, SortPlan.Packing plan) { List<ItemData> list = new List<ItemData>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); List<ItemData> list2 = new List<ItemData>(chests[ci].Inv.GetAllItems()); list2.Sort((ItemData a, ItemData b) => (a.m_gridPos.y * 64 + a.m_gridPos.x).CompareTo(b.m_gridPos.y * 64 + b.m_gridPos.x)); foreach (ItemData item in list2) { Info info = InfoOf(item); if (!info.Excluded && plan.Chests.ContainsKey(info.Type)) { dictionary.TryGetValue(info.Type, out var value); dictionary[info.Type] = value + 1; if (value + 1 > plan.Quota(info.Type, ci)) { list.Add(item); } } } return list; } private static bool HasQuotaRoom(Inventory inv, string t, int quota, ItemData incoming) { if (quota <= 0) { return false; } int num = 0; bool flag = false; string stack = InfoOf(incoming).Stack; foreach (ItemData allItem in inv.GetAllItems()) { Info info = InfoOf(allItem); if (!(info.Type != t)) { num++; if (allItem.m_stack < allItem.m_shared.m_maxStackSize && info.Stack == stack) { flag = true; } } } return num < quota || flag; } private static int ParkingFor(List<Chest> chests, ItemData it, int not) { for (int num = chests.Count - 1; num >= 0; num--) { if (num != not && chests[num].Ok && !chests[num].Protected && chests[num].Inv.CanAddItem(it, it.m_stack)) { return num; } } return -1; } private static void MergeStacks(Inventory inv) { Dictionary<string, List<ItemData>> dictionary = new Dictionary<string, List<ItemData>>(StringComparer.Ordinal); foreach (ItemData allItem in inv.GetAllItems()) { if (!Excluded(allItem) && allItem.m_shared.m_maxStackSize > 1) { string key = StackKey(allItem); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<ItemData>()); } value.Add(allItem); } } foreach (List<ItemData> value2 in dictionary.Values) { if (value2.Count < 2) { continue; } value2.Sort((ItemData a, ItemData b) => b.m_stack.CompareTo(a.m_stack)); for (int num = 0; num < value2.Count; num++) { int maxStackSize = value2[num].m_shared.m_maxStackSize; int num2 = value2.Count - 1; while (num2 > num && value2[num].m_stack < maxStackSize) { if (value2[num2].m_stack > 0) { int num3 = Math.Min(maxStackSize - value2[num].m_stack, value2[num2].m_stack); ItemData obj = value2[num]; obj.m_stack += num3; ItemData obj2 = value2[num2]; obj2.m_stack -= num3; if (value2[num2].m_stack == 0) { try { inv.RemoveItem(value2[num2]); } catch (Exception) { } value2.RemoveAt(num2); } } num2--; } } } } private static List<Chest> Gather(Player player, float range) { //IL_0010: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_006f: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: 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) List<Chest> list = new List<Chest>(); float num = range * range; Vector3 position = ((Component)player).transform.position; Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0); foreach (Container val in array) { if ((Object)(object)val == (Object)null || !Usable(val)) { continue; } Vector3 position2 = ((Component)val).transform.position; Vector3 val2 = position2 - position; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude > num || !PrivateArea.CheckAccess(position2, 0f, false, false)) { continue; } ZNetView component = ((Component)val).GetComponent<ZNetView>(); if (!((Object)(object)component == (Object)null) && component.IsValid()) { Inventory inventory = val.GetInventory(); if (inventory != null) { list.Add(new Chest { Container = val, Inv = inventory, View = component, Dist = Mathf.Sqrt(sqrMagnitude), Pos = position2, Yaw = ((Component)val).transform.eulerAngles.y, Slots = inventory.GetWidth() * inventory.GetHeight() }); } } } list.Sort((Chest a, Chest b) => string.CompareOrdinal(Id(a), Id(b))); List<RowLayout.Spot> list2 = new List<RowLayout.Spot>(list.Count); for (int num2 = 0; num2 < list.Count; num2++) { list2.Add(new RowLayout.Spot(num2, list[num2].Pos.x, list[num2].Pos.y, list[num2].Pos.z, list[num2].Yaw, list[num2].Dist)); } s_rows = RowLayout.Rows(list2, position.x, position.z, Plugin.RowDir, Plugin.RowOrd); List<Chest> list3 = new List<Chest>(list.Count); foreach (RowLayout.Row s_row in s_rows) { foreach (int id in s_row.Ids) { list3.Add(list[id]); } } return list3; } private static void DumpChests(List<Chest> chests, Player player) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) try { StringBuilder stringBuilder = new StringBuilder(); Vector3 position = ((Component)player).transform.position; stringBuilder.AppendLine($"# player\t{position.x:0.00}\t{position.y:0.00}\t{position.z:0.00}\tyaw\t{((Component)player).transform.eulerAngles.y:0}"); stringBuilder.AppendLine("order\trow\tx\ty\tz\tyaw\tfacing\tdist\tslots\tused\tname\twall\taxis"); int num = 0; for (int i = 0; i < s_rows.Count; i++) { foreach (int id in s_rows[i].Ids) { _ = id; Chest chest = chests[num]; stringBuilder.AppendLine($"{num}\t{i}\t{chest.Pos.x:0.00}\t{chest.Pos.y:0.00}\t{chest.Pos.z:0.00}\t{chest.Yaw:0.0}\t{RowLayout.Facing(chest.Yaw)}\t{chest.Dist:0.0}\t{chest.Slots}\t{chest.Inv.GetAllItems().Count}\t{((Object)chest.Container).name}\t{s_rows[i].Wall}\t{s_rows[i].Axis}"); num++; } } File.WriteAllText(Path.Combine(Paths.BepInExRootPath, "NewtSortingChests-chests.txt"), stringBuilder.ToString()); } catch (Exception ex) { Log.Warn("chest dump: " + ex.Message); } } private static bool Usable(Container c) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 if ((Object)(object)((Component)c).GetComponent<TombStone>() != (Object)null) { return false; } if ((Object)(object)((Component)c).GetComponentInParent<Ship>() != (Object)null || (Object)(object)((Component)c).GetComponentInParent<Vagon>() != (Object)null) { return false; } if (((Object)c).name.IndexOf("chest", StringComparison.OrdinalIgnoreCase) < 0) { return false; } if (c.IsInUse()) { return false; } Piece component = ((Component)c).GetComponent<Piece>(); if ((Object)(object)component != (Object)null && !component.IsPlacedByPlayer()) { return false; } if ((int)c.m_privacy == 0) { if ((Object)(object)component != (Object)null && (Object)(object)Player.m_localPlayer != (Object)null) { return component.GetCreator() == Player.m_localPlayer.GetPlayerID(); } return false; } if ((int)c.m_privacy == 1) { return false; } return true; } private static string Id(Chest c) { try { return ((object)Unsafe.As<ZDOID, ZDOID>(ref c.View.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString(); } catch (Exception) { return ((Object)c.Container).GetInstanceID().ToString(); } } private static string StackKey(ItemData it) { return InfoOf(it).Stack; } private static string TypeKey(ItemData it) { return InfoOf(it).Type; } private static Category CatOf(ItemData it) { return InfoOf(it).Cat; } private static bool Excluded(ItemData it) { return InfoOf(it).Excluded; } private static void Reorder(Inventory inv) { //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) List<ItemData> allItems = inv.GetAllItems(); if (allItems.Count == 0) { return; } int width = inv.GetWidth(); HashSet<int> hashSet = new HashSet<int>(); foreach (ItemData item in allItems) { if (Excluded(item)) { hashSet.Add(item.m_gridPos.y * width + item.m_gridPos.x); } } List<ItemData> list = (from i in allItems where !Excluded(i) orderby Categories.Order(CatOf(i)) select i).ThenBy<ItemData, string>((ItemData i) => TypeKey(i), StringComparer.Ordinal).ThenByDescending((ItemData i) => i.m_quality).ThenByDescending((ItemData i) => i.m_stack) .ToList(); int num = 0; foreach (ItemData item2 in list) { for (; hashSet.Contains(num); num++) { } hashSet.Add(num); item2.m_gridPos = new Vector2i(num % width, num / width); num++; } } private static void Save(Chest c) { try { s_save?.Invoke(c.Container, null); } catch (Exception) { } } private static void Say(string line) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, line, 0, (Sprite)null, false); } } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class DumpCommandPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__1_0; internal void <Postfix>b__1_0(ConsoleEventArgs a) { string text = DumpToFile(); Terminal context = a.Context; if (context != null) { context.AddString(text); } } } private static bool s_done; private static void Postfix() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown if (s_done) { return; } s_done = true; try { object obj = <>c.<>9__1_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs a) { string text = DumpToFile(); Terminal context = a.Context; if (context != null) { context.AddString(text); } }; <>c.<>9__1_0 = val; obj = (object)val; } new ConsoleCommand("nscdump", "write every game item + its ItemType + the category NewtSortingChests assigns, to BepInEx/NewtSortingChests-items.txt", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } catch (Exception ex) { Log.Warn("nscdump register: " + ex.Message); } } public static string DumpToFile() { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected I4, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items == null) { return "ObjectDB not ready; load a world first."; } List<string> list = new List<string>(); foreach (GameObject item in ObjectDB.instance.m_items) { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null); SharedData val2 = (((Object)(object)val != (Object)null && val.m_itemData != null) ? val.m_itemData.m_shared : null); if (val2 != null) { Category category = Categories.Of(((Object)item).name, (int)val2.m_itemType, val2.m_food); list.Add($"{((Object)item).name}\t{val2.m_itemType}\t{val2.m_food:0.##}\t{Categories.Label(category)}\t{category}\t{val2.m_name}"); } } list.Sort(StringComparer.Ordinal); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("prefab\titemType\tfood\tcategoryLabel\tcategory\tdisplayToken"); foreach (string item2 in list) { stringBuilder.AppendLine(item2); } string text = Path.Combine(Paths.BepInExRootPath, "NewtSortingChests-items.txt"); File.WriteAllText(text, stringBuilder.ToString()); string text2 = $"NewtSortingChests: dumped {list.Count} items to {text}"; Log.Info(text2); return text2; } catch (Exception ex) { Log.Warn("nscdump: " + ex); return "nscdump failed: " + ex.Message; } } } internal static class Log { internal static ManualLogSource Src; public static void Info(string m) { ManualLogSource src = Src; if (src != null) { src.LogInfo((object)m); } } public static void Warn(string m) { ManualLogSource src = Src; if (src != null) { src.LogWarning((object)m); } } } [BepInPlugin("com.anattalabs.newtsortingchests", "NewtSortingChests", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string Guid = "com.anattalabs.newtsortingchests"; private const string SargamGuid = "com.sargam.valheim.autostore"; private ConfigEntry<float> _range; private ConfigEntry<KeyboardShortcut> _key; private ConfigEntry<string> _exclude; private ConfigEntry<string> _protect; private ConfigEntry<RowDirection> _rowDir; private ConfigEntry<RowOrder> _rowOrd; private ConfigEntry<bool> _debugDumps; public static float Range = 30f; public static bool DebugDumps; public static RowDirection RowDir = RowDirection.LeftToRight; public static RowOrder RowOrd = RowOrder.TopFirst; public static readonly HashSet<Category> ExcludedCats = new HashSet<Category>(); public static readonly HashSet<Category> ProtectCats = new HashSet<Category>(); public static bool SargamPresent; private static bool s_running; private static bool s_dumped; private void Awake() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) Log.Src = ((BaseUnityPlugin)this).Logger; _range = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Range", 30f, new ConfigDescription("How far around you the sort key looks for chests to organise.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 256f), Array.Empty<object>())); _key = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "SortKey", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), "Press this to organise every chest around you by category (SargamAutoStore uses F8 and F9, so this is F10 by default). Hold modifiers are supported, e.g. LeftControl + F10."); _exclude = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ExcludeCategories", "", "Comma-separated category names left exactly where they are - never moved or reordered. Category names: Weapons, Shields, Bows, Ammunition, Armor, Utility, Trinkets, Tools, Torch, Fishing, FoodRaw, FoodCooked, FarmCrops, Seeds, Meads, Wood, Ore, Metal, Cores, StoneMinerals, HidesScales, MonsterParts, Materials, TreeSeeds, Valuables, Trophies, MiscKeys. Empty by default: everything is sorted. Example: \"Meads, FoodCooked\" keeps potions and cooked food where you put them."); _protect = ((BaseUnityPlugin)this).Config.Bind<string>("General", "ProtectChestCategories", "", "A chest whose contents are mostly these categories (at least 2 stacks, and at least half of its used slots) is a dedicated chest, e.g. a potion chest: it is left alone entirely - nothing is moved into it, only items that do not belong there are taken out. Only categories that are also in ExcludeCategories count. Empty by default. Example: \"Meads, FoodCooked\" keeps your potion and cooked-food chests exactly as they are."); _rowDir = ((BaseUnityPlugin)this).Config.Bind<RowDirection>("Layout", "RowDirection", RowDirection.LeftToRight, "Which way each row of chests is read when categories are laid out along it, as seen standing in front of the chests. Flip this if your categories run the wrong way along a row."); _rowOrd = ((BaseUnityPlugin)this).Config.Bind<RowOrder>("Layout", "RowOrder", RowOrder.TopFirst, "When chests are stacked on shelves, whether the top or the bottom shelf is filled first. Rows are detected by facing, height and line; the nearest wall is read first."); _debugDumps = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugDumps", false, "Write diagnostic files to the BepInEx folder: every game item with the category it gets (NewtSortingChests-items.txt, on load) and every chest's position and row (NewtSortingChests-chests.txt, on each sort). Off by default; the nscdump console command writes the item file any time."); Range = _range.Value; ApplyExclude(_exclude.Value); ApplyCsv(_protect.Value, ProtectCats); DebugDumps = _debugDumps.Value; RowDir = _rowDir.Value; RowOrd = _rowOrd.Value; _range.SettingChanged += delegate { Range = _range.Value; }; _exclude.SettingChanged += delegate { ApplyExclude(_exclude.Value); }; _protect.SettingChanged += delegate { ApplyCsv(_protect.Value, ProtectCats); }; _rowDir.SettingChanged += delegate { RowDir = _rowDir.Value; }; _rowOrd.SettingChanged += delegate { RowOrd = _rowOrd.Value; }; _debugDumps.SettingChanged += delegate { DebugDumps = _debugDumps.Value; }; SargamPresent = Chainloader.PluginInfos.ContainsKey("com.sargam.valheim.autostore"); new Harmony("com.anattalabs.newtsortingchests").PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("NewtSortingChests {0} loaded (SargamAutoStore {1}); sort key {2}.", "1.0.0", SargamPresent ? "found" : "MISSING", _key.Value)); } private static void ApplyExclude(string csv) { ApplyCsv(csv, ExcludedCats); } private static void ApplyCsv(string csv, HashSet<Category> into) { into.Clear(); if (string.IsNullOrEmpty(csv)) { return; } string[] array = csv.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { if (Enum.TryParse<Category>(array[i].Trim(), ignoreCase: true, out var result)) { into.Add(result); } } } private void Update() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) if (DebugDumps && !s_dumped && (Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_items != null && ObjectDB.instance.m_items.Count > 0) { s_dumped = true; try { DumpCommandPatch.DumpToFile(); } catch (Exception) { } } if (SargamPresent && !((Object)(object)ZNet.instance == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null) && !s_running && KeyDown(_key.Value) && TakesKeys()) { ((MonoBehaviour)this).StartCoroutine(SortRoutine()); } } private IEnumerator SortRoutine() { s_running = true; try { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, "Organising chests...", 0, (Sprite)null, false); } IEnumerator e = ChestSort.Run(); while (MoveNextSafe(e)) { yield return e.Current; } } finally { s_running = false; } } private static bool MoveNextSafe(IEnumerator e) { try { return e.MoveNext(); } catch (Exception ex) { Log.Warn("sort failed: " + ex); Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)1, "NewtSortingChests error: " + ex.Message, 0, (Sprite)null, false); } return false; } } private static bool KeyDown(KeyboardShortcut k) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref k)).MainKey == 0 || !UnityInput.Current.GetKeyDown(((KeyboardShortcut)(ref k)).MainKey)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref k)).Modifiers) { if (!UnityInput.Current.GetKey(modifier)) { return false; } } return true; } private static bool TakesKeys() { if (!Console.IsVisible() && ((Object)(object)Chat.instance == (Object)null || !Chat.instance.HasFocus()) && !TextInput.IsVisible() && ((Object)(object)Minimap.instance == (Object)null || !Minimap.InTextInput())) { return !Menu.IsVisible(); } return false; } } internal static class BuildInfo { public const string Version = "1.0.0"; } } namespace NewtSortingChests.Core { public enum Category { Weapons, Shields, Bows, Ammunition, Armor, Utility, Trinkets, Tools, Torch, Fishing, FoodRaw, FoodCooked, FarmCrops, Seeds, Meads, Wood, Ore, Metal, Cores, StoneMinerals, HidesScales, MonsterParts, Materials, TreeSeeds, Valuables, Trophies, MiscKeys } public static class Categories { public const int None = 0; public const int Material = 1; public const int Consumable = 2; public const int OneHandedWeapon = 3; public const int Bow = 4; public const int Shield = 5; public const int Helmet = 6; public const int Chest = 7; public const int Ammo = 9; public const int Customization = 10; public const int Legs = 11; public const int Hands = 12; public const int Trophy = 13; public const int TwoHandedWeapon = 14; public const int Torch = 15; public const int Misc = 16; public const int Shoulder = 17; public const int Utility = 18; public const int Tool = 19; public const int Attach_Atgeir = 20; public const int Fish = 21; public const int TwoHandedWeaponLeft = 22; public const int AmmoNonEquipable = 23; public const int Trinket = 24; private static readonly Dictionary<string, Category> s_exact = Build(); public static int Order(Category c) { return (int)c; } public static int Family(Category c) { switch (c) { case Category.Weapons: case Category.Shields: case Category.Bows: case Category.Armor: case Category.Utility: case Category.Trinkets: case Category.Tools: case Category.Torch: case Category.Fishing: return 0; case Category.Ammunition: return 1; case Category.FoodRaw: case Category.FoodCooked: case Category.FarmCrops: case Category.Seeds: case Category.Meads: return 2; case Category.Trophies: return 4; case Category.Valuables: case Category.MiscKeys: return 5; default: return 3; } } public static Category Of(string prefab, int itemType, float food) { string text = (prefab ?? "").ToLowerInvariant(); if (text.StartsWith("mold") || text.StartsWith("fw_") || text.StartsWith("sp_") || text.Contains("_shoot")) { return Category.Materials; } if (text.EndsWith("uncooked")) { if (!text.Contains("gold") && !text.Contains("mold") && !IsWeaponName(text) && !text.StartsWith("armor") && !text.StartsWith("bow") && !text.StartsWith("crossbow") && !text.StartsWith("shield") && !text.StartsWith("keys") && !text.StartsWith("smallparts")) { return Category.FoodCooked; } return Category.Materials; } if (s_exact.TryGetValue(text, out var value)) { return value; } if (text.StartsWith("trophy")) { return Category.Trophies; } if (text.StartsWith("mead") || text.StartsWith("barleywine")) { return Category.Meads; } if (text.StartsWith("cape") || text.StartsWith("helmet") || text.StartsWith("armor")) { return Category.Armor; } if (text.StartsWith("shield")) { return Category.Shields; } if (text.StartsWith("bow") || text.StartsWith("crossbow")) { return Category.Bows; } if (text.StartsWith("arrow") || text.StartsWith("bolt") || text.StartsWith("turretbolt") || text.StartsWith("catapult_ammo")) { return Category.Ammunition; } if (text.StartsWith("trinket")) { return Category.Trinkets; } if (text.StartsWith("fishingbait") || text == "fishingrod") { return Category.Fishing; } if (text.StartsWith("pickaxe")) { return Category.Tools; } if (text.StartsWith("mushroom")) { return Category.FoodRaw; } if (text.StartsWith("cooked") || text.StartsWith("smoked") || text.StartsWith("feast")) { return Category.FoodCooked; } if (IsWeaponName(text)) { return Category.Weapons; } return Fallback(itemType); } private static bool IsWeaponName(string p) { if (!(p == "club") && !p.StartsWith("sword") && !p.StartsWith("thsword") && !p.StartsWith("knife") && !p.StartsWith("battleaxe") && !p.StartsWith("mace") && !p.StartsWith("spear") && !p.StartsWith("atgeir") && !p.StartsWith("sledge") && !p.StartsWith("fist") && !p.StartsWith("staff")) { return p.StartsWith("axe"); } return true; } private static Category Fallback(int itemType) { switch (itemType) { case 3: case 14: case 20: case 22: return Category.Weapons; case 4: return Category.Bows; case 5: return Category.Shields; case 6: case 7: case 11: case 17: return Category.Armor; case 9: case 23: return Category.Ammunition; case 13: return Category.Trophies; case 15: return Category.Torch; case 18: return Category.Utility; case 19: return Category.Tools; case 21: return Category.FoodRaw; case 24: return Category.Trinkets; case 2: return Category.FoodCooked; case 10: return Category.Armor; case 16: return Category.MiscKeys; default: return Category.Materials; } } public static string Label(Category c) { return c switch { Category.Weapons => "weapons", Category.Shields => "shields", Category.Bows => "bows", Category.Ammunition => "ammunition", Category.Armor => "armor", Category.Utility => "utility", Category.Trinkets => "trinkets", Category.Tools => "tools", Category.Torch => "torches", Category.Fishing => "fishing", Category.FoodRaw => "raw food", Category.FoodCooked => "cooked food", Category.FarmCrops => "crops", Category.Seeds => "seeds", Category.Meads => "meads", Category.Wood => "wood", Category.Ore => "ore", Category.Metal => "metal", Category.Cores => "cores", Category.StoneMinerals => "stone", Category.HidesScales => "hides", Category.MonsterParts => "monster parts", Category.TreeSeeds => "tree seeds", Category.Valuables => "valuables", Category.Trophies => "trophies", Category.MiscKeys => "keys", _ => "materials", }; } private static Dictionary<string, Category> Build() { Dictionary<string, Category> m = new Dictionary<string, Category>(StringComparer.Ordinal); Add(Category.Wood, new string[7] { "Wood", "FineWood", "RoundLog", "ElderBark", "YggdrasilWood", "Frostwood", "BarkaBranch" }); Add(Category.Ore, new string[10] { "CopperOre", "TinOre", "IronScrap", "SilverOre", "BlackMetalScrap", "CopperScrap", "IronOre", "FlametalOre", "FlametalOreNew", "GoldOre" }); Add(Category.Metal, new string[11] { "Copper", "Tin", "Bronze", "BronzeNails", "Iron", "IronNails", "Silver", "BlackMetal", "Flametal", "FlametalNew", "Gold" }); Add(Category.Cores, new string[7] { "SurtlingCore", "BlackCore", "FrostCore", "MoltenCore", "DvergrNeedle", "MechanicalSpring", "CharredCogwheel" }); Add(Category.StoneMinerals, new string[12] { "Stone", "Flint", "Obsidian", "Crystal", "Coal", "Tar", "SulfurStone", "Grausten", "BlackMarble", "Ice", "ProustitePowder", "MemorialCoal" }); Add(Category.HidesScales, new string[12] { "DeerHide", "LeatherScraps", "TrollHide", "WolfPelt", "LoxPelt", "ScaleHide", "Chitin", "Carapace", "AskHide", "SealHide", "MooseHide", "SerpentScale" }); Add(Category.MonsterParts, new string[26] { "GreydwarfEye", "Ooze", "Guck", "Entrails", "Bloodbag", "WolfFang", "HardAntler", "BoneFragments", "Needle", "Softtissue", "Mandible", "BonemawSerpentTooth", "AskBladder", "MooseSinew", "MoleClaws", "OozeMork", "FrozenKingDrop", "HatefulBlood", "Ectoplasm", "Voidplasm", "ElakingHairBundle", "NornThread", "WrithanRoots", "FrozenFuel", "WolfHairBundle", "Root" }); Add(Category.Materials, new string[11] { "Resin", "CharcoalResin", "Chain", "Thunderstone", "Eitr", "Sap", "Wisp", "Leatherstraps", "OrbFrostFire", "OrbThunderBlood", "YmirRemains" }); Add(Category.FoodRaw, new string[42] { "RawMeat", "DeerMeat", "NeckTail", "WolfMeat", "BjornMeat", "ChickenMeat", "HareMeat", "LoxMeat", "SerpentMeat", "MooseMeat", "SealBlubber", "BugMeat", "VoltureMeat", "BoneMawSerpentMeat", "AsksvinMeat", "Fish1", "Fish2", "Fish3", "Fish4_cave", "Fish5", "Fish6", "Fish7", "Fish8", "Fish9", "Fish10", "Fish11", "Fish12", "FishRaw", "FishAnglerRaw", "Blueberries", "Raspberry", "Cloudberry", "Lingonberry", "Vineberry", "Honey", "Dandelion", "Thistle", "RoyalJelly", "Fiddleheadfern", "BreadDough", "BarleyFlour", "OatFlour" }); Add(Category.FoodCooked, new string[17] { "FishCooked", "SmokedFish", "Bread", "Sausages", "BloodPudding", "CarrotSoup", "TurnipStew", "SerpentStew", "FishWraps", "LoxPie", "QueensJam", "DeerStew", "MinceMeatSauce", "NeckTailGrilled", "SerpentMeatCooked", "SpiceDeepNorth", "SpiceMistlands" }); Add(Category.FarmCrops, new string[8] { "Carrot", "Turnip", "Onion", "Barley", "Flax", "Kale", "Oat", "Poteitr" }); Add(Category.Seeds, new string[7] { "CarrotSeeds", "TurnipSeeds", "OnionSeeds", "KaleSeeds", "OatSeeds", "PoteitrSeeds", "VineberrySeeds" }); Add(Category.Utility, new string[4] { "BeltStrength", "Megingjord", "Wishbone", "Demister" }); Add(Category.Tools, new string[12] { "Hammer", "Hoe", "Cultivator", "AxeStone", "AxeFlint", "AxeBronze", "AxeIron", "AxeBlackMetal", "AxeWood", "Shovel", "FishingRod", "GrapplingHook" }); Add(Category.Torch, new string[1] { "Torch" }); Add(Category.TreeSeeds, new string[7] { "BeechSeeds", "BirchSeeds", "FirCone", "PineCone", "Acorn", "FirConeFrost", "VineGreenSeeds" }); Add(Category.Valuables, new string[11] { "Coins", "AncientCoin", "Amber", "AmberPearl", "Ruby", "GemstoneRed", "CrownJewel", "AncientGemstoneBlack", "AncientGemstoneGreen", "AncientGemstoneOrange", "AncientGemstonePurple" }); Add(Category.MiscKeys, new string[9] { "CryptKey", "DvergrKey", "DvergrKeyFragment", "BloodGoldKey", "AncientSeed", "Tankard", "TankardAnniversary", "TankardOdin", "Tankard_dvergr" }); return m; void Add(Category c, string[] names) { foreach (string text in names) { m[text.ToLowerInvariant()] = c; } } } } public enum RowDirection { LeftToRight, RightToLeft } public enum RowOrder { TopFirst, BottomFirst } public static class RowLayout { public readonly struct Spot { public readonly int Id; public readonly float X; public readonly float Y; public readonly float Z; public readonly float Yaw; public readonly float Dist; public Spot(int id, float x, float y, float z, float yaw, float dist) { Id = id; X = x; Y = y; Z = z; Yaw = yaw; Dist = dist; } } public sealed class Row { public readonly List<int> Ids = new List<int>(); public int Axis; public float Level; public float Line; public float Dist; public float AlongMin; public float AlongMax; public int Wall; } public const float LevelTolerance = 0.4f; public const float LineTolerance = 0.6f; public const float WallTolerance = 2.5f; public static List<Row> Rows(IReadOnlyList<Spot> spots, float playerX, float playerZ, RowDirection dir, RowOrder order) { List<Row> list = new List<Row>(); if (spots == null || spots.Count == 0) { return list; } foreach (IGrouping<int, Spot> item in from s in spots group s by Axis(s.Yaw)) { int axis = item.Key; Func<Spot, float> func = (Spot s) => (axis != 0) ? s.Z : s.X; Func<Spot, float> along = (Spot s) => (axis != 0) ? s.X : s.Z; foreach (List<Spot> item2 in Cluster(item.ToList(), (Spot s) => s.Y, 0.4f)) { foreach (List<Spot> item3 in Cluster(item2, func, 0.6f)) { Row row = new Row { Axis = axis }; row.Level = item3.Average((Spot s) => s.Y); row.Line = item3.Average(func); row.Dist = item3.Min((Spot s) => s.Dist); row.AlongMin = item3.Min(along); row.AlongMax = item3.Max(along); float num = Math.Sign(((axis == 0) ? playerX : playerZ) - row.Line); if (num == 0f) { num = 1f; } float rightSign = ((axis == 0) ? num : (0f - num)); if (dir == RowDirection.RightToLeft) { rightSign = 0f - rightSign; } foreach (Spot item4 in item3.OrderBy((Spot s) => along(s) * rightSign)) { row.Ids.Add(item4.Id); } list.Add(row); } } } int num2 = 0; List<List<Row>> list2 = new List<List<Row>>(); foreach (Row r in from row2 in list orderby row2.Axis, row2.Line select row2) { List<Row> list3 = null; foreach (List<Row> item5 in list2) { if (item5[0].Axis == r.Axis && item5.All((Row o) => Math.Abs(o.Line - r.Line) <= 2.5f && r.AlongMin <= o.AlongMax + 2.5f && o.AlongMin <= r.AlongMax + 2.5f)) { list3 = item5; break; } } if (list3 == null) { list3 = new List<Row>(); list2.Add(list3); } list3.Add(r); } foreach (List<Row> item6 in list2) { int wall = num2++; foreach (Row item7 in item6) { item7.Wall = wall; } } Dictionary<int, float> wallDist = (from row2 in list group row2 by row2.Wall).ToDictionary((IGrouping<int, Row> g) => g.Key, (IGrouping<int, Row> g) => g.Min((Row row2) => row2.Dist)); List<Row> list4 = (from row2 in list orderby wallDist[row2.Wall], row2.Wall, (order != RowOrder.TopFirst) ? row2.Level : (0f - row2.Level), row2.Dist select row2).ToList(); int num3 = 0; Dictionary<int, int> dictionary = new Dictionary<int, int>(); foreach (Row item8 in list4) { if (!dictionary.TryGetValue(item8.Wall, out var value)) { value = num3++; dictionary[item8.Wall] = value; } item8.Wall = value; } return list4; } public static List<int> Order(IReadOnlyList<Spot> spots, float playerX, float playerZ, RowDirection dir, RowOrder order) { List<int> list = new List<int>(); foreach (Row item in Rows(spots, playerX, playerZ, dir, order)) { list.AddRange(item.Ids); } return list; } public static int Facing(float yaw) { int num = (int)Math.Round((double)yaw / 90.0) % 4; if (num >= 0) { return num; } return num + 4; } public static int Axis(float yaw) { return Facing(yaw) % 2; } private static List<List<Spot>> Cluster(List<Spot> items, Func<Spot, float> key, float tol) { List<List<Spot>> list = new List<List<Spot>>(); float num = 0f; foreach (Spot item in items.OrderBy(key)) { float num2 = key(item); if (list.Count == 0 || num2 - num > tol) { list.Add(new List<Spot>()); } list[list.Count - 1].Add(item); num = num2; } return list; } } public static class SortPlan { public readonly struct Piece { public readonly string StackKey; public readonly int Stack; public readonly int MaxStack; public Piece(string stackKey, int stack, int maxStack) { StackKey = stackKey ?? ""; Stack = ((stack >= 0) ? stack : 0); MaxStack = ((maxStack < 1) ? 1 : maxStack); } } public readonly struct TypeGroup { public readonly Category Cat; public readonly string Key; public readonly int Slots; public TypeGroup(Category cat, string key, int slots) { Cat = cat; Key = key ?? ""; Slots = ((slots >= 0) ? slots : 0); } } public sealed class Packing { public readonly Dictionary<string, List<int>> Chests = new Dictionary<string, List<int>>(StringComparer.Ordinal); public readonly List<string> Overflow = new List<string>(); public Boundary Used; private readonly Dictionary<string, int> _quota = new Dictionary<string, int>(StringComparer.Ordinal); private static string QKey(string type, int chest) { return chest + "\u001f" + type; } internal void SetQuota(string type, int chest, int slots) { _quota[QKey(type, chest)] = slots; } public int Quota(string type, int chest) { if (!_quota.TryGetValue(QKey(type, chest), out var value)) { return 0; } return value; } public bool IsHome(string type, int chest) { return Quota(type, chest) > 0; } } public enum Boundary { Category, Family, None } public static int SlotsNeeded(IEnumerable<Piece> pieces) { Dictionary<string, long> dictionary = new Dictionary<string, long>(StringComparer.Ordinal); Dictionary<string, int> dictionary2 = new Dictionary<string, int>(StringComparer.Ordinal); foreach (Piece piece in pieces) { if (piece.Stack > 0) { dictionary.TryGetValue(piece.StackKey, out var value); dictionary[piece.StackKey] = value + piece.Stack; dictionary2[piece.StackKey] = piece.MaxStack; } } int num = 0; foreach (KeyValuePair<string, long> item in dictionary) { num += (int)((item.Value + dictionary2[item.Key] - 1) / dictionary2[item.Key]); } return num; } public static Packing PackBest(IReadOnlyList<int> caps, IReadOnlyList<TypeGroup> groups) { Packing result = null; Boundary[] array = new Boundary[3] { Boundary.Category, Boundary.Family, Boundary.None }; foreach (Boundary boundary in array) { Packing packing = Pack(caps, groups, boundary); if (packing.Overflow.Count == 0) { return packing; } result = packing; } return result; } public static Packing Pack(IReadOnlyList<int> caps, IReadOnlyList<TypeGroup> groups) { return Pack(caps, groups, Boundary.None); } public static Packing Pack(IReadOnlyList<int> caps, IReadOnlyList<TypeGroup> groups, Boundary boundary) { Packing packing = new Packing { Used = boundary }; if (caps == null || groups == null || caps.Count == 0) { if (groups != null) { foreach (TypeGroup group in groups) { if (group.Slots > 0) { packing.Overflow.Add(group.Key); } } } return packing; } int count = caps.Count; int num = 0; int num2 = Math.Max(0, caps[0]); bool flag = false; bool flag2 = false; Category category = Category.Weapons; foreach (TypeGroup group2 in groups) { List<int> list = new List<int>(); packing.Chests[group2.Key] = list; if (group2.Slots <= 0) { continue; } if (flag2 && flag) { bool num3; if (boundary != Boundary.Category) { if (boundary != Boundary.Family) { goto IL_012b; } num3 = Categories.Family(group2.Cat) != Categories.Family(category); } else { num3 = !object.Equals(group2.Cat, category); } if (num3) { num++; flag = false; num2 = ((num < count) ? Math.Max(0, caps[num]) : 0); } } goto IL_012b; IL_012b: flag2 = true; category = group2.Cat; int num4 = group2.Slots; while (num4 > 0 && num < count) { if (num2 <= 0) { num++; if (num >= count) { break; } num2 = Math.Max(0, caps[num]); } else { int num5 = Math.Min(num4, num2); list.Add(num); packing.SetQuota(group2.Key, num, num5); flag = true; num4 -= num5; num2 -= num5; } } if (num4 > 0) { packing.Overflow.Add(group2.Key); } } return packing; } } }