Decompiled source of ChestDecor v1.0.0

ChestDecor.dll

Decompiled 5 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Managers;
using Jotunn.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("kydldev")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) kydldev")]
[assembly: AssemblyDescription("Decorate Valheim chests: colour them, attach items and position them.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("Chest Decor")]
[assembly: AssemblyTitle("ChestDecor")]
[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 ChestDecor
{
	internal static class AttachCatalog
	{
		private sealed class CastInfo
		{
			public AttachCategory Category;

			public string Why;
		}

		private static readonly HashSet<string> WeaponTypes = new HashSet<string> { "OneHandedWeapon", "TwoHandedWeapon", "TwoHandedWeaponLeft", "Bow", "Shield", "Attach_Atgeir" };

		private static readonly HashSet<string> ArmorTypes = new HashSet<string> { "Helmet", "Chest", "Legs", "Shoulder", "Cape", "Trinket" };

		private static readonly HashSet<string> ValuableNames = new HashSet<string> { "amber", "amberpearl", "ruby", "silvernecklace", "ancientcoin", "grimvarn", "solryth", "veydris", "draumyx" };

		private static readonly string[] FoodWords = new string[3] { "meat", "mead", "flour" };

		private const string NonPhysicalWord = "pocket";

		private static readonly List<string> Excluded = new List<string>();

		private static ObjectDB _builtFor;

		private static ZNetScene _builtForScene;

		private static List<AttachEntry> _entries = new List<AttachEntry>();

		private static Dictionary<string, AttachEntry> _byName = new Dictionary<string, AttachEntry>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, GameObject> PrefabCache = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);

		private static readonly string[] HiddenNames = new string[16]
		{
			"DeerWhite", "CargoCrate", "Cargo", "Skeleton_Bow_Meadows", "KnifeVoid", "Draugr_Axe", "piece_groundtorch_mist", "TorchMist", "GoblinTorch", "GoblinTorchDeepNorth",
			"Elaking_AttackLantern", "DvergerStaffFire", "DvergerStaffHeal", "DvergerStaffIce", "MushroomBlue", "piece_barber"
		};

		private static string[] _hiddenTokens;

		public static IReadOnlyList<AttachEntry> Entries
		{
			get
			{
				EnsureBuilt();
				return _entries;
			}
		}

		public static bool IsBuilt
		{
			get
			{
				ObjectDB instance = ObjectDB.instance;
				if ((Object)(object)instance != (Object)null && (Object)(object)_builtFor == (Object)(object)instance && _byName.Count > 0)
				{
					return SceneReady();
				}
				return false;
			}
		}

		public static AttachEntry Find(string prefab)
		{
			if (string.IsNullOrEmpty(prefab))
			{
				return null;
			}
			EnsureBuilt();
			if (!_byName.TryGetValue(prefab, out var value))
			{
				return null;
			}
			return value;
		}

		public static AttachEntry FindIfBuilt(string prefab)
		{
			if (string.IsNullOrEmpty(prefab) || !IsBuilt)
			{
				return null;
			}
			if (!_byName.TryGetValue(prefab, out var value))
			{
				return null;
			}
			return value;
		}

		public static bool IsTestEntry(AttachEntry entry)
		{
			if (entry == null)
			{
				return false;
			}
			if (!Contains(entry.Prefab, "test"))
			{
				return Contains(entry.DisplayName, "test");
			}
			return true;
		}

		public static bool IsHiddenEntry(AttachEntry entry)
		{
			if (entry == null)
			{
				return false;
			}
			string[] hiddenNames;
			if (_hiddenTokens == null)
			{
				List<string> list = new List<string>(HiddenNames.Length);
				hiddenNames = HiddenNames;
				for (int i = 0; i < hiddenNames.Length; i++)
				{
					string text = Normalize(hiddenNames[i]);
					if (text.Length > 0)
					{
						list.Add(text);
					}
				}
				_hiddenTokens = list.ToArray();
			}
			string text2 = Normalize(entry.Prefab);
			string text3 = Normalize(entry.DisplayName);
			hiddenNames = _hiddenTokens;
			foreach (string value in hiddenNames)
			{
				if (text2.Contains(value) || text3.Contains(value))
				{
					return true;
				}
			}
			return false;
		}

		private static bool Contains(string text, string part)
		{
			if (!string.IsNullOrEmpty(text))
			{
				return text.IndexOf(part, StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return false;
		}

		public static void Invalidate()
		{
			_builtFor = null;
			_builtForScene = null;
			_entries = new List<AttachEntry>();
			_byName = new Dictionary<string, AttachEntry>(StringComparer.OrdinalIgnoreCase);
			PrefabCache.Clear();
			AttachCost.ClearCache();
		}

		public static GameObject ResolvePrefab(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return null;
			}
			if (PrefabCache.TryGetValue(name, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			GameObject val = null;
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance != (Object)null)
			{
				val = instance.GetPrefab(name);
			}
			if ((Object)(object)val == (Object)null && (Object)(object)ObjectDB.instance != (Object)null && ObjectDB.instance.m_items != null)
			{
				foreach (GameObject item in ObjectDB.instance.m_items)
				{
					if ((Object)(object)item != (Object)null && string.Equals(((Object)item).name, name, StringComparison.OrdinalIgnoreCase))
					{
						val = item;
						break;
					}
				}
			}
			if ((Object)(object)val != (Object)null)
			{
				PrefabCache[name] = val;
			}
			return val;
		}

		private static bool SceneReady()
		{
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return (Object)(object)instance == (Object)(object)_builtForScene;
			}
			return false;
		}

		private static void EnsureBuilt()
		{
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null || instance.m_items == null || instance.m_items.Count == 0 || ((Object)(object)_builtFor == (Object)(object)instance && _byName.Count > 0 && ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)(object)_builtForScene)))
			{
				return;
			}
			_builtFor = instance;
			_builtForScene = ZNetScene.instance;
			PrefabCache.Clear();
			AttachVisuals.ClearCache();
			AttachCost.ClearCache();
			Excluded.Clear();
			List<AttachEntry> list = new List<AttachEntry>();
			Dictionary<string, AttachEntry> dictionary = new Dictionary<string, AttachEntry>(StringComparer.OrdinalIgnoreCase);
			try
			{
				Dictionary<string, List<string>> sellers;
				Dictionary<string, int> prices = CollectTraderPrices(out sellers);
				Dictionary<string, Recipe> recipes = CollectRecipes(instance);
				Dictionary<string, AttachPieceInfo> dictionary2 = CollectBuildPieces(instance, recipes);
				Dictionary<string, List<string>> mobDrops = CollectMobDrops();
				Dictionary<string, CastInfo> castItems;
				Dictionary<string, string> rawFood = CollectRawFood(instance, out castItems);
				ProduceInfo produceInfo = ProduceCatalog.Collect(instance, Excluded);
				foreach (GameObject item in instance.m_items)
				{
					AttachEntry attachEntry = MakeItemEntry(item, prices, recipes, mobDrops, sellers, produceInfo, rawFood, castItems);
					if (attachEntry != null && attachEntry.Categories != AttachCategory.None && !dictionary.ContainsKey(attachEntry.Prefab))
					{
						list.Add(attachEntry);
						dictionary[attachEntry.Prefab] = attachEntry;
					}
				}
				foreach (AttachPieceInfo value2 in dictionary2.Values)
				{
					if ((value2.Furniture || value2.Light) && !dictionary.ContainsKey(((Object)value2.Object).name))
					{
						AttachEntry attachEntry2 = MakePieceEntry(value2.Object, value2.Listed);
						if (attachEntry2 != null)
						{
							list.Add(attachEntry2);
							dictionary[attachEntry2.Prefab] = attachEntry2;
						}
					}
				}
				foreach (PlantInfo plant in produceInfo.Plants)
				{
					if (!((Object)(object)plant.Object == (Object)null) && !dictionary.ContainsKey(((Object)plant.Object).name))
					{
						AttachEntry attachEntry3 = MakePlantEntry(plant);
						list.Add(attachEntry3);
						dictionary[attachEntry3.Prefab] = attachEntry3;
					}
				}
				AddExtras(list, dictionary, prices, recipes, dictionary2);
			}
			catch (Exception arg)
			{
				ChestDecorPlugin.Log.LogWarning((object)$"Building the attachment list failed: {arg}");
			}
			foreach (AttachEntry item2 in list)
			{
				item2.DisplayName = Localize(item2.SharedName, item2.Prefab);
				if (item2.IsPlant)
				{
					item2.DisplayName += " (plant)";
				}
			}
			list.Sort(delegate(AttachEntry a, AttachEntry b)
			{
				int num12 = string.Compare(a.DisplayName, b.DisplayName, StringComparison.CurrentCultureIgnoreCase);
				return (num12 == 0) ? string.Compare(a.Prefab, b.Prefab, StringComparison.OrdinalIgnoreCase) : num12;
			});
			bool flag = ChestDecorPlugin.ShowTestItems != null && ChestDecorPlugin.ShowTestItems.Value;
			List<AttachEntry> list2 = new List<AttachEntry>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (AttachEntry item3 in list)
			{
				if (item3.Listed)
				{
					if (IsHiddenEntry(item3))
					{
						item3.Listed = false;
						Excluded.Add(item3.Prefab + ": hidden item, never listed");
					}
					else if (!flag && IsTestEntry(item3))
					{
						item3.Listed = false;
						Excluded.Add(item3.Prefab + ": test item (listed only when the 'Show Test Items' setting is on)");
					}
					else if (!AttachVisuals.GetModel(item3.Object).Valid)
					{
						item3.Listed = false;
						Excluded.Add(item3.Prefab + ": no physical model to show");
					}
					else if (!item3.Qualified && !hashSet.Add(RowSignature(item3)))
					{
						item3.Listed = false;
					}
					else
					{
						list2.Add(item3);
					}
				}
			}
			list = list2;
			Dictionary<string, int> dictionary3 = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			foreach (AttachEntry item4 in list)
			{
				item4.RowLabel = item4.DisplayName;
				dictionary3[item4.DisplayName] = ((!dictionary3.TryGetValue(item4.DisplayName, out var value)) ? 1 : (value + 1));
			}
			foreach (AttachEntry item5 in list)
			{
				if (item5.Qualified && dictionary3[item5.DisplayName] > 1)
				{
					item5.RowLabel = item5.DisplayName + " (" + item5.Prefab + ")";
				}
			}
			_entries = list;
			_byName = dictionary;
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			int num7 = 0;
			int num8 = 0;
			int num9 = 0;
			int num10 = 0;
			int num11 = 0;
			foreach (AttachEntry item6 in list)
			{
				if ((item6.Categories & AttachCategory.Trophy) != AttachCategory.None)
				{
					num++;
				}
				if ((item6.Categories & AttachCategory.Weapon) != AttachCategory.None)
				{
					num2++;
				}
				if ((item6.Categories & AttachCategory.Armor) != AttachCategory.None)
				{
					num3++;
				}
				if ((item6.Categories & AttachCategory.Trader) != AttachCategory.None)
				{
					num4++;
				}
				if ((item6.Categories & AttachCategory.Decor) != AttachCategory.None)
				{
					num5++;
				}
				if ((item6.Categories & AttachCategory.Produce) != AttachCategory.None)
				{
					num9++;
				}
				if (item6.IsPlant)
				{
					num10++;
				}
				if ((item6.Categories & AttachCategory.Ammo) != AttachCategory.None)
				{
					num6++;
				}
				if ((item6.Categories & AttachCategory.Food) != AttachCategory.None)
				{
					num7++;
				}
				if ((item6.Categories & AttachCategory.Food) != AttachCategory.None && !string.IsNullOrEmpty(item6.Sources) && item6.Sources.IndexOf("food preparation table", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					num11++;
				}
				if ((item6.Categories & AttachCategory.Drop) != AttachCategory.None)
				{
					num8++;
				}
			}
			ChestDecorPlugin.Log.LogInfo((object)($"Attachment list: {list.Count} entries ({num} trophies, {num4} trader items / valuables, " + $"{num9} produce incl. {num10} plants, " + $"{num6} arrows / bolts, {num7} food ({num11} from the food preparation table), {num8} mob drops, {num2} weapons, {num3} armor, {num5} decorative); " + $"{Excluded.Count} candidates left out"));
			WriteDump(list);
		}

		private static string Localize(string key, string fallback)
		{
			if (string.IsNullOrEmpty(key))
			{
				return fallback;
			}
			try
			{
				string text = Localization.instance.Localize(key);
				return string.IsNullOrEmpty(text) ? fallback : text;
			}
			catch
			{
				return fallback;
			}
		}

		private static void WriteDump(List<AttachEntry> entries)
		{
			if (ChestDecorPlugin.DumpCatalog == null || !ChestDecorPlugin.DumpCatalog.Value)
			{
				return;
			}
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine($"Chest Decor attachment list, {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
				stringBuilder.AppendLine($"{entries.Count} listed entries, {Excluded.Count} candidates left out.");
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("== Listed ==   prefab | shown name | categories | cost | where it comes from");
				foreach (AttachEntry entry in entries)
				{
					stringBuilder.Append(entry.Prefab).Append(" | ").Append(entry.DisplayName)
						.Append(" | ")
						.Append(entry.Categories)
						.Append(" | ");
					switch (entry.Cost)
					{
					case CostKind.Coins:
						stringBuilder.Append(entry.CoinPrice).Append(" coins");
						break;
					case CostKind.Item:
						stringBuilder.Append("the item itself");
						break;
					case CostKind.Material:
						stringBuilder.Append(entry.CostAmount).Append("x ").Append(((Object)(object)entry.CostItem != (Object)null) ? ((Object)entry.CostItem).name : "?");
						break;
					case CostKind.Piece:
						stringBuilder.Append("build cost");
						break;
					default:
						stringBuilder.Append("recipe");
						break;
					}
					if (!string.IsNullOrEmpty(entry.Sources))
					{
						stringBuilder.Append(" | ").Append(entry.Sources);
					}
					stringBuilder.AppendLine();
				}
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("== Left out ==");
				foreach (string item in Excluded)
				{
					stringBuilder.AppendLine(item);
				}
				string text = Path.Combine(Paths.CachePath, "ChestDecorDump");
				Directory.CreateDirectory(text);
				string text2 = Path.Combine(text, "AttachCatalog.txt");
				File.WriteAllText(text2, stringBuilder.ToString());
				ChestDecorPlugin.Log.LogInfo((object)("Attachment list written to " + text2));
			}
			catch (Exception ex)
			{
				ChestDecorPlugin.Log.LogWarning((object)("Could not write the attachment list dump: " + ex.Message));
			}
		}

		private static string RowSignature(AttachEntry entry)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(entry.DisplayName).Append('|');
			stringBuilder.Append(((Object)(object)entry.Icon != (Object)null) ? ((Object)entry.Icon).GetInstanceID().ToString() : entry.Prefab).Append('|');
			Requirement[] requirements = entry.Requirements;
			if (requirements != null)
			{
				Requirement[] array = requirements;
				foreach (Requirement val in array)
				{
					if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0 && !IgnoredIngredients.IsIgnored(val))
					{
						stringBuilder.Append(((Object)((Component)val.m_resItem).gameObject).name).Append('x').Append(val.m_amount)
							.Append(',');
					}
				}
			}
			return stringBuilder.ToString();
		}

		private static Dictionary<string, int> CollectTraderPrices(out Dictionary<string, List<string>> sellers)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			sellers = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null || instance.m_prefabs == null)
			{
				return dictionary;
			}
			foreach (GameObject prefab in instance.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				Trader componentInChildren = prefab.GetComponentInChildren<Trader>(true);
				if ((Object)(object)componentInChildren == (Object)null || componentInChildren.m_items == null)
				{
					continue;
				}
				foreach (TradeItem item in componentInChildren.m_items)
				{
					if (item == null)
					{
						continue;
					}
					if ((Object)(object)item.m_prefab == (Object)null)
					{
						Excluded.Add(((Object)prefab).name + " sells '" + item.m_name + "': not an item (no prefab)");
						continue;
					}
					string name = ((Object)((Component)item.m_prefab).gameObject).name;
					if (IsNonPhysicalTradeItem(item, name))
					{
						Excluded.Add(name + " (sold by " + ((Object)prefab).name + "): not a physical item (inventory upgrade)");
						continue;
					}
					int num = Mathf.Max(1, Mathf.CeilToInt((float)item.m_price / (float)Mathf.Max(1, item.m_stack)));
					if (!dictionary.TryGetValue(name, out var value) || num < value)
					{
						dictionary[name] = num;
					}
					if (!sellers.TryGetValue(name, out var value2))
					{
						value2 = (sellers[name] = new List<string>());
					}
					if (!value2.Contains(((Object)prefab).name))
					{
						value2.Add(((Object)prefab).name);
					}
				}
			}
			return dictionary;
		}

		private static bool IsNonPhysicalTradeItem(TradeItem item, string prefabName)
		{
			if (Normalize(item.m_name).Contains("pocket"))
			{
				return true;
			}
			if (Normalize(prefabName).Contains("pocket"))
			{
				return true;
			}
			try
			{
				SharedData val = ((item.m_prefab.m_itemData != null) ? item.m_prefab.m_itemData.m_shared : null);
				if (val != null && Normalize(val.m_name).Contains("pocket"))
				{
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static Dictionary<string, List<string>> CollectMobDrops()
		{
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance == (Object)null || instance.m_prefabs == null)
			{
				return dictionary;
			}
			foreach (GameObject prefab in instance.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				CharacterDrop componentInChildren = prefab.GetComponentInChildren<CharacterDrop>(true);
				if ((Object)(object)componentInChildren == (Object)null || componentInChildren.m_drops == null)
				{
					continue;
				}
				foreach (Drop drop in componentInChildren.m_drops)
				{
					if (drop != null && !((Object)(object)drop.m_prefab == (Object)null) && !(drop.m_chance <= 0f) && drop.m_amountMax > 0 && !((Object)(object)drop.m_prefab.GetComponent<ItemDrop>() == (Object)null))
					{
						string name = ((Object)drop.m_prefab).name;
						if (!dictionary.TryGetValue(name, out var value))
						{
							value = (dictionary[name] = new List<string>());
						}
						if (!value.Contains(((Object)prefab).name))
						{
							value.Add(((Object)prefab).name);
						}
					}
				}
			}
			return dictionary;
		}

		private static Dictionary<string, Recipe> CollectRecipes(ObjectDB db)
		{
			Dictionary<string, Recipe> dictionary = new Dictionary<string, Recipe>(StringComparer.OrdinalIgnoreCase);
			if (db.m_recipes == null)
			{
				return dictionary;
			}
			foreach (Recipe recipe in db.m_recipes)
			{
				if (IsCraftable(recipe))
				{
					string name = ((Object)((Component)recipe.m_item).gameObject).name;
					if (!dictionary.ContainsKey(name))
					{
						dictionary[name] = recipe;
					}
				}
			}
			return dictionary;
		}

		private static bool IsCraftable(Recipe r)
		{
			if ((Object)(object)r == (Object)null || !r.m_enabled || (Object)(object)r.m_item == (Object)null)
			{
				return false;
			}
			if (!HasIngredients(r.m_resources))
			{
				return false;
			}
			SharedData val = ((r.m_item.m_itemData != null) ? r.m_item.m_itemData.m_shared : null);
			if (val == null || val.m_icons == null || val.m_icons.Length == 0)
			{
				return false;
			}
			try
			{
				CraftingStation requiredStation = r.GetRequiredStation(1);
				if ((Object)(object)requiredStation != (Object)null)
				{
					Piece component = ((Component)requiredStation).GetComponent<Piece>();
					if ((Object)(object)component != (Object)null && !component.m_enabled)
					{
						return false;
					}
				}
			}
			catch
			{
			}
			return true;
		}

		private static bool HasIngredients(Requirement[] requirements)
		{
			if (requirements == null)
			{
				return false;
			}
			foreach (Requirement val in requirements)
			{
				if (val != null && (Object)(object)val.m_resItem != (Object)null && val.m_amount > 0 && !IgnoredIngredients.IsIgnored(val))
				{
					return true;
				}
			}
			return false;
		}

		private static Dictionary<string, AttachPieceInfo> CollectBuildPieces(ObjectDB db, Dictionary<string, Recipe> recipes)
		{
			Dictionary<PieceTable, bool> dictionary = new Dictionary<PieceTable, bool>();
			foreach (GameObject item in db.m_items)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				ItemDrop component = item.GetComponent<ItemDrop>();
				if (!((Object)(object)component == (Object)null) && component.m_itemData != null && component.m_itemData.m_shared != null)
				{
					PieceTable buildPieces = component.m_itemData.m_shared.m_buildPieces;
					if (!((Object)(object)buildPieces == (Object)null) && buildPieces.m_pieces != null)
					{
						bool flag = recipes.ContainsKey(((Object)item).name);
						dictionary[buildPieces] = (dictionary.TryGetValue(buildPieces, out var value) ? (value || flag) : flag);
					}
				}
			}
			Dictionary<string, AttachPieceInfo> dictionary2 = new Dictionary<string, AttachPieceInfo>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<PieceTable, bool> item2 in dictionary)
			{
				foreach (GameObject piece in item2.Key.m_pieces)
				{
					if ((Object)(object)piece == (Object)null)
					{
						continue;
					}
					Piece component2 = piece.GetComponent<Piece>();
					if (!((Object)(object)component2 == (Object)null) && component2.m_enabled)
					{
						bool flag2 = item2.Value && HasIngredients(component2.m_resources);
						bool flag3 = ((object)Unsafe.As<PieceCategory, PieceCategory>(ref component2.m_category)/*cast due to .constrained prefix*/).ToString() == "Furniture" && (Object)(object)piece.GetComponentInChildren<Container>(true) == (Object)null;
						bool flag4 = !flag3 && LightSources.IsLightPiece(piece, Localize(component2.m_name, ((Object)piece).name));
						if (dictionary2.TryGetValue(((Object)piece).name, out var value2))
						{
							value2.Listed |= flag2;
							value2.Furniture |= flag3;
							value2.Light |= flag4;
						}
						else
						{
							dictionary2[((Object)piece).name] = new AttachPieceInfo
							{
								Object = piece,
								Furniture = flag3,
								Light = flag4,
								Listed = flag2
							};
						}
					}
				}
			}
			return dictionary2;
		}

		private static AttachEntry MakeItemEntry(GameObject go, Dictionary<string, int> prices, Dictionary<string, Recipe> recipes, Dictionary<string, List<string>> mobDrops, Dictionary<string, List<string>> sellers, ProduceInfo produce, Dictionary<string, string> rawFood, Dictionary<string, CastInfo> castItems)
		{
			if ((Object)(object)go == (Object)null)
			{
				return null;
			}
			ItemDrop component = go.GetComponent<ItemDrop>();
			if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null)
			{
				return null;
			}
			SharedData shared = component.m_itemData.m_shared;
			AttachCategory attachCategory = AttachCategory.None;
			string item = ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString();
			if (WeaponTypes.Contains(item))
			{
				attachCategory |= AttachCategory.Weapon;
			}
			else if (ArmorTypes.Contains(item))
			{
				attachCategory |= AttachCategory.Armor;
			}
			AttachCategory attachCategory2 = AttachCategory.None;
			List<string> list = new List<string>();
			if (Contains(((Object)go).name, "carrion"))
			{
				attachCategory2 |= AttachCategory.Trophy;
			}
			if (Contains(((Object)go).name, "trophy"))
			{
				attachCategory2 |= AttachCategory.Trophy;
			}
			if (Contains(((Object)go).name, "gem") || IsTraderValuable(go, shared))
			{
				attachCategory2 |= AttachCategory.Trader;
				list.Add("valuable");
			}
			int value = 0;
			if (prices.TryGetValue(((Object)go).name, out value))
			{
				attachCategory2 |= AttachCategory.Trader;
				if (sellers.TryGetValue(((Object)go).name, out var value2))
				{
					list.Add("sold by " + string.Join("/", value2));
				}
			}
			if (IsCraftableArrowOrBolt(((Object)go).name, shared, recipes))
			{
				attachCategory2 |= AttachCategory.Ammo;
			}
			string value3 = null;
			bool num = (attachCategory2 & AttachCategory.Trophy) != 0;
			CastInfo value4 = null;
			bool flag = castItems?.TryGetValue(((Object)go).name, out value4) ?? false;
			if (flag)
			{
				attachCategory2 |= value4.Category;
				list.Add("cast item (" + value4.Why + ")");
			}
			bool num2 = num || flag;
			bool flag2 = !num2 && rawFood != null && rawFood.TryGetValue(((Object)go).name, out value3);
			if (!num2 && (flag2 || IsFood(((Object)go).name, shared)))
			{
				attachCategory2 |= AttachCategory.Food;
			}
			if (flag2)
			{
				list.Add("uncooked base food (" + value3 + ")");
			}
			if (LightSources.IsLightItem(go, shared, Localize(shared.m_name, "")))
			{
				attachCategory2 |= AttachCategory.Decor;
				list.Add("light source");
			}
			if (produce != null && produce.IsProduce(((Object)go).name, shared))
			{
				attachCategory2 |= AttachCategory.Produce;
			}
			if (mobDrops.TryGetValue(((Object)go).name, out var value5))
			{
				attachCategory2 |= AttachCategory.Drop;
				list.Add("dropped by " + Shorten(value5, 8));
			}
			bool qualified = false;
			if (attachCategory2 != AttachCategory.None)
			{
				if (AttachVisuals.GetModel(go).Valid)
				{
					qualified = true;
				}
				else
				{
					Excluded.Add($"{((Object)go).name}: no physical model to show ({attachCategory2})");
					attachCategory2 = AttachCategory.None;
					list.Clear();
				}
			}
			attachCategory |= attachCategory2;
			if (attachCategory == AttachCategory.None)
			{
				return null;
			}
			AttachEntry attachEntry = BuildItemEntry(go, component, attachCategory, value, recipes, qualified);
			attachEntry.Sources = string.Join("; ", list);
			return attachEntry;
		}

		private static AttachEntry BuildItemEntry(GameObject go, ItemDrop drop, AttachCategory cats, int price, Dictionary<string, Recipe> recipes, bool qualified)
		{
			SharedData shared = drop.m_itemData.m_shared;
			AttachEntry attachEntry = new AttachEntry
			{
				Prefab = ((Object)go).name,
				Object = go,
				Categories = cats,
				SharedName = shared.m_name,
				Icon = ((shared.m_icons != null && shared.m_icons.Length != 0) ? shared.m_icons[0] : null),
				IsPiece = false,
				CoinPrice = price,
				Qualified = qualified
			};
			recipes.TryGetValue(((Object)go).name, out var value);
			bool flag = qualified && (Object)(object)value != (Object)null && value.m_amount > 1;
			if ((Object)(object)value != (Object)null && !flag)
			{
				attachEntry.Recipe = value;
				attachEntry.Cost = CostKind.Recipe;
				attachEntry.Listed = true;
			}
			else if (price > 0)
			{
				attachEntry.Cost = CostKind.Coins;
			}
			else
			{
				attachEntry.Cost = CostKind.Item;
			}
			if (qualified)
			{
				attachEntry.Listed = true;
			}
			return attachEntry;
		}

		private static bool IsTraderValuable(GameObject go, SharedData shared)
		{
			if (ValuableNames.Contains(Normalize(((Object)go).name)))
			{
				return true;
			}
			return ValuableNames.Contains(Normalize(Localize(shared.m_name, "")));
		}

		private static bool IsCraftableArrowOrBolt(string name, SharedData shared, Dictionary<string, Recipe> recipes)
		{
			if (((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString() != "Ammo")
			{
				return false;
			}
			if (!recipes.ContainsKey(name))
			{
				return false;
			}
			string text = shared.m_ammoType ?? "";
			if (Contains(text, "arrow") || Contains(text, "bolt"))
			{
				return true;
			}
			if (!name.StartsWith("Arrow", StringComparison.OrdinalIgnoreCase))
			{
				return name.StartsWith("Bolt", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static bool IsFood(string name, SharedData shared)
		{
			string text = name.Replace("meadows", "", StringComparison.OrdinalIgnoreCase);
			string[] foodWords = FoodWords;
			foreach (string part in foodWords)
			{
				if (Contains(text, part))
				{
					return true;
				}
			}
			return HasFoodStats(shared);
		}

		private static bool HasFoodStats(SharedData shared)
		{
			if (shared != null && ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString() == "Consumable")
			{
				if (!(shared.m_food > 0f) && !(shared.m_foodStamina > 0f))
				{
					return shared.m_foodEitr > 0f;
				}
				return true;
			}
			return false;
		}

		private static Dictionary<string, string> CollectRawFood(ObjectDB db, out Dictionary<string, CastInfo> castItems)
		{
			Dictionary<string, string> result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			castItems = new Dictionary<string, CastInfo>(StringComparer.OrdinalIgnoreCase);
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			ZNetScene instance = ZNetScene.instance;
			if ((Object)(object)instance != (Object)null && instance.m_prefabs != null)
			{
				foreach (GameObject prefab in instance.m_prefabs)
				{
					if ((Object)(object)prefab == (Object)null)
					{
						continue;
					}
					CookingStation componentInChildren = prefab.GetComponentInChildren<CookingStation>(true);
					if ((Object)(object)componentInChildren == (Object)null || componentInChildren.m_conversion == null)
					{
						continue;
					}
					foreach (ItemConversion item in componentInChildren.m_conversion)
					{
						if (item == null)
						{
							continue;
						}
						if ((Object)(object)item.m_to != (Object)null)
						{
							hashSet.Add(((Object)((Component)item.m_to).gameObject).name);
						}
						if ((Object)(object)item.m_from == (Object)null)
						{
							continue;
						}
						string name = ((Object)((Component)item.m_from).gameObject).name;
						AttachCategory attachCategory = CastCategory(item.m_to);
						if (attachCategory != AttachCategory.None)
						{
							if (!castItems.ContainsKey(name))
							{
								castItems[name] = new CastInfo
								{
									Category = attachCategory,
									Why = "cast at " + ((Object)prefab).name
								};
							}
						}
						else
						{
							AddRawFood(result, name, "cooked at " + ((Object)prefab).name);
						}
					}
				}
			}
			int num = 0;
			if ((Object)(object)db != (Object)null && db.m_recipes != null)
			{
				foreach (Recipe recipe in db.m_recipes)
				{
					Requirement[] resources;
					if (IsPreparationRecipe(recipe))
					{
						num++;
						string name2 = ((Object)((Component)recipe.m_item).gameObject).name;
						AddRawFood(result, name2, "made at the food preparation table");
						if (recipe.m_resources != null)
						{
							resources = recipe.m_resources;
							foreach (Requirement val in resources)
							{
								if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0)
								{
									string name3 = ((Object)((Component)val.m_resItem).gameObject).name;
									if (!hashSet.Contains(name3))
									{
										AddRawFood(result, name3, "ingredient of " + name2 + " at the food preparation table");
									}
								}
							}
						}
					}
					if (!IsCraftable(recipe) || !HasFoodStats(recipe.m_item.m_itemData.m_shared))
					{
						continue;
					}
					resources = recipe.m_resources;
					foreach (Requirement val2 in resources)
					{
						if (val2 != null && !((Object)(object)val2.m_resItem == (Object)null) && val2.m_amount > 0)
						{
							string name4 = ((Object)((Component)val2.m_resItem).gameObject).name;
							if (!hashSet.Contains(name4))
							{
								AddRawFood(result, name4, "ingredient of " + ((Object)((Component)recipe.m_item).gameObject).name);
							}
						}
					}
				}
			}
			if ((Object)(object)db != (Object)null && num == 0)
			{
				Excluded.Add("food preparation table: no enabled recipe found at a crafting station whose prefab or shown name contains 'prep'");
			}
			return result;
		}

		private static AttachCategory CastCategory(ItemDrop result)
		{
			if ((Object)(object)result == (Object)null)
			{
				return AttachCategory.None;
			}
			SharedData val = ((result.m_itemData != null) ? result.m_itemData.m_shared : null);
			if (val == null)
			{
				return AttachCategory.None;
			}
			if (IsFood(((Object)((Component)result).gameObject).name, val))
			{
				return AttachCategory.None;
			}
			if (!ArmorTypes.Contains(((object)Unsafe.As<ItemType, ItemType>(ref val.m_itemType)/*cast due to .constrained prefix*/).ToString()))
			{
				return AttachCategory.Weapon;
			}
			return AttachCategory.Armor;
		}

		private static void AddRawFood(Dictionary<string, string> result, string name, string why)
		{
			if (!string.IsNullOrEmpty(name) && !result.ContainsKey(name))
			{
				result[name] = why;
			}
		}

		private static bool IsPreparationRecipe(Recipe recipe)
		{
			if ((Object)(object)recipe == (Object)null || !recipe.m_enabled || (Object)(object)recipe.m_item == (Object)null)
			{
				return false;
			}
			if (recipe.m_item.m_itemData == null || recipe.m_item.m_itemData.m_shared == null)
			{
				return false;
			}
			return IsPreparationTable(recipe.m_craftingStation);
		}

		private static bool IsPreparationTable(CraftingStation station)
		{
			if ((Object)(object)station == (Object)null)
			{
				return false;
			}
			if (!Normalize(((Object)((Component)station).gameObject).name).Contains("prep"))
			{
				return Normalize(station.m_name).Contains("prep");
			}
			return true;
		}

		private static string Normalize(string text)
		{
			if (string.IsNullOrEmpty(text))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(text.Length);
			foreach (char c in text)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}

		private static string Shorten(List<string> names, int max)
		{
			if (names.Count <= max)
			{
				return string.Join(", ", names);
			}
			return string.Join(", ", names.GetRange(0, max)) + $" +{names.Count - max} more";
		}

		private static AttachEntry MakePlantEntry(PlantInfo plant)
		{
			return new AttachEntry
			{
				Prefab = ((Object)plant.Object).name,
				Object = plant.Object,
				Categories = AttachCategory.Produce,
				SharedName = plant.SharedName,
				Icon = plant.Icon,
				IsPiece = false,
				IsPlant = true,
				Cost = CostKind.Material,
				CostItem = plant.CostItem,
				CostAmount = plant.CostAmount,
				Qualified = true,
				Listed = true,
				Sources = plant.Source
			};
		}

		private static AttachEntry MakePieceEntry(GameObject go, bool listed)
		{
			Piece component = go.GetComponent<Piece>();
			if ((Object)(object)component == (Object)null)
			{
				return null;
			}
			return new AttachEntry
			{
				Prefab = ((Object)go).name,
				Object = go,
				Categories = AttachCategory.Decor,
				SharedName = component.m_name,
				Icon = component.m_icon,
				IsPiece = true,
				Piece = component,
				Cost = CostKind.Piece,
				Listed = listed
			};
		}

		private static void AddExtras(List<AttachEntry> entries, Dictionary<string, AttachEntry> byName, Dictionary<string, int> prices, Dictionary<string, Recipe> recipes, Dictionary<string, AttachPieceInfo> pieces)
		{
			string value = ChestDecorPlugin.ExtraDecorPrefabs.Value;
			if (string.IsNullOrWhiteSpace(value))
			{
				return;
			}
			string[] array = value.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length == 0)
				{
					continue;
				}
				if (byName.TryGetValue(text, out var value2))
				{
					value2.Categories |= AttachCategory.Decor;
					continue;
				}
				if (pieces.TryGetValue(text, out var value3))
				{
					AttachEntry attachEntry = MakePieceEntry(value3.Object, value3.Listed);
					if (attachEntry != null)
					{
						entries.Add(attachEntry);
						byName[attachEntry.Prefab] = attachEntry;
						continue;
					}
				}
				GameObject val = ResolvePrefab(text);
				if ((Object)(object)val == (Object)null)
				{
					ChestDecorPlugin.Log.LogWarning((object)("Extra decor prefab '" + text + "' was not found."));
					continue;
				}
				AttachEntry attachEntry2 = null;
				ItemDrop component = val.GetComponent<ItemDrop>();
				if ((Object)(object)component != (Object)null && component.m_itemData != null && component.m_itemData.m_shared != null)
				{
					prices.TryGetValue(((Object)val).name, out var value4);
					attachEntry2 = BuildItemEntry(val, component, AttachCategory.Decor, value4, recipes, qualified: false);
				}
				else if ((Object)(object)val.GetComponent<Piece>() != (Object)null)
				{
					attachEntry2 = MakePieceEntry(val, listed: false);
				}
				if (attachEntry2 == null)
				{
					ChestDecorPlugin.Log.LogWarning((object)("Extra decor prefab '" + text + "' is neither an item nor a piece, ignored."));
					continue;
				}
				entries.Add(attachEntry2);
				byName[attachEntry2.Prefab] = attachEntry2;
			}
		}
	}
	[Flags]
	public enum AttachCategory
	{
		None = 0,
		Trophy = 1,
		Weapon = 2,
		Armor = 4,
		Trader = 8,
		Decor = 0x10,
		Ammo = 0x40,
		Food = 0x80,
		Drop = 0x100,
		Produce = 0x200
	}
	internal static class AttachCost
	{
		private const string CoinsName = "$item_coins";

		private static readonly MethodInfo HaveRecipeItemsM = Find(typeof(Player), "HaveRequirementItems", typeof(Recipe));

		private static readonly MethodInfo HavePieceM = Find(typeof(Player), "HaveRequirements", typeof(Piece));

		private static readonly MethodInfo ConsumeM = Find(typeof(Player), "ConsumeResources", typeof(Requirement[]));

		private static readonly MethodInfo NoCostM = Find(typeof(Player), "NoCostCheat", null);

		private static readonly MethodInfo RecipeKnownM = Find(typeof(Player), "IsRecipeKnown", typeof(string));

		private static readonly Dictionary<string, Recipe> Loose = new Dictionary<string, Recipe>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> Warned = new HashSet<string>();

		private const string CoinsPrefab = "Coins";

		public static void ClearCache()
		{
			foreach (Recipe value in Loose.Values)
			{
				if ((Object)(object)value != (Object)null)
				{
					Object.Destroy((Object)(object)value);
				}
			}
			Loose.Clear();
		}

		public static bool Evaluate(Player player, AttachEntry entry, out string reason)
		{
			reason = null;
			if ((Object)(object)player == (Object)null || entry == null)
			{
				reason = "Not available";
				return false;
			}
			if (IsFree(player))
			{
				return true;
			}
			return entry.Cost switch
			{
				CostKind.Piece => CheckPiece(player, entry, out reason), 
				CostKind.Recipe => CheckRecipe(player, entry, out reason), 
				_ => CheckLoose(player, entry, out reason), 
			};
		}

		private static bool CheckLoose(Player player, AttachEntry entry, out string reason)
		{
			reason = null;
			bool flag = entry.Cost == CostKind.Coins;
			bool flag2 = entry.Cost == CostKind.Material;
			Recipe val = LooseRecipe(entry);
			if ((Object)(object)val != (Object)null)
			{
				if (HaveRecipeItems(player, val, skipIgnored: false))
				{
					return true;
				}
			}
			else if (Count(player, LooseName(entry)) >= LooseAmount(entry))
			{
				return true;
			}
			reason = (flag ? "Not enough coins" : (flag2 ? "Missing materials" : "You need this item"));
			return false;
		}

		private static string LooseName(AttachEntry entry)
		{
			switch (entry.Cost)
			{
			case CostKind.Coins:
				return "$item_coins";
			case CostKind.Material:
			{
				ItemDrop val = (((Object)(object)entry.CostItem != (Object)null) ? entry.CostItem.GetComponent<ItemDrop>() : null);
				if (!((Object)(object)val != (Object)null) || val.m_itemData == null || val.m_itemData.m_shared == null)
				{
					return null;
				}
				return val.m_itemData.m_shared.m_name;
			}
			default:
				return entry.SharedName;
			}
		}

		private static int LooseAmount(AttachEntry entry)
		{
			return entry.Cost switch
			{
				CostKind.Coins => entry.CoinPrice, 
				CostKind.Material => entry.CostAmount, 
				_ => 1, 
			};
		}

		private static Recipe LooseRecipe(AttachEntry entry)
		{
			//IL_00d6: 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_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Expected O, but got Unknown
			if (Loose.TryGetValue(entry.Prefab, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			GameObject val;
			int num;
			if (entry.Cost == CostKind.Coins)
			{
				val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab("Coins") : null);
				num = entry.CoinPrice;
			}
			else if (entry.Cost == CostKind.Material)
			{
				val = entry.CostItem;
				num = entry.CostAmount;
			}
			else
			{
				val = entry.Object;
				num = 1;
			}
			ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null);
			if ((Object)(object)val2 == (Object)null || num <= 0)
			{
				return null;
			}
			Recipe val3 = ScriptableObject.CreateInstance<Recipe>();
			((Object)val3).name = "ChestDecor_" + entry.Prefab;
			((Object)val3).hideFlags = (HideFlags)61;
			val3.m_item = val2;
			val3.m_enabled = true;
			val3.m_resources = (Requirement[])(object)new Requirement[1]
			{
				new Requirement
				{
					m_resItem = val2,
					m_amount = num
				}
			};
			Loose[entry.Prefab] = val3;
			return val3;
		}

		private static bool CheckPiece(Player player, AttachEntry entry, out string reason)
		{
			reason = null;
			Piece piece = entry.Piece;
			if ((Object)(object)piece == (Object)null)
			{
				reason = "Not available";
				return false;
			}
			if (ChestDecorPlugin.RequireKnown.Value && !HavePiece(player, piece, "IsKnown"))
			{
				reason = "Not discovered yet";
				return false;
			}
			if (!HavePiece(player, piece, "CanBuild"))
			{
				reason = "Missing materials";
				return false;
			}
			return true;
		}

		private static bool CheckRecipe(Player player, AttachEntry entry, out string reason)
		{
			reason = null;
			Recipe recipe = entry.Recipe;
			if ((Object)(object)recipe == (Object)null)
			{
				reason = "Not available";
				return false;
			}
			if (ChestDecorPlugin.RequireKnown.Value && !IsRecipeKnown(player, entry.SharedName))
			{
				reason = "Recipe not known yet";
				return false;
			}
			if (!HaveRecipeItems(player, recipe))
			{
				reason = "Missing materials";
				return false;
			}
			return true;
		}

		private static bool IsRecipeKnown(Player player, string sharedName)
		{
			if (RecipeKnownM == null)
			{
				WarnOnce("Player.IsRecipeKnown was not found, so 'Require Known Recipe' treats every recipe as unknown.");
				return false;
			}
			try
			{
				return (bool)RecipeKnownM.Invoke(player, new object[1] { sharedName });
			}
			catch (Exception ex)
			{
				WarnOnce("Recipe known check failed, treating the recipe as unknown: " + ex.Message);
				return false;
			}
		}

		private static void WarnOnce(string message)
		{
			if (Warned.Add(message))
			{
				ChestDecorPlugin.Log.LogWarning((object)message);
			}
		}

		private static bool HaveRecipeItems(Player player, Recipe recipe, bool skipIgnored = true)
		{
			if (HaveRecipeItemsM != null)
			{
				Requirement[] resources = recipe.m_resources;
				if (skipIgnored)
				{
					recipe.m_resources = IgnoredIngredients.Filter(resources);
				}
				try
				{
					return (bool)HaveRecipeItemsM.Invoke(player, BuildArgs(HaveRecipeItemsM, recipe));
				}
				catch (Exception ex)
				{
					ChestDecorPlugin.Log.LogDebug((object)("HaveRequirementItems failed, counting by hand: " + ex.Message));
				}
				finally
				{
					recipe.m_resources = resources;
				}
			}
			return HaveByHand(player, recipe.m_resources, skipIgnored);
		}

		private static bool HavePiece(Player player, Piece piece, string mode)
		{
			bool flag = mode == "CanBuild";
			if (HavePieceM == null)
			{
				if (!flag)
				{
					WarnOnce("Player.HaveRequirements was not found, so 'Require Known Recipe' treats every piece as unknown.");
				}
				if (flag)
				{
					return HaveByHand(player, piece.m_resources);
				}
				return false;
			}
			CraftingStation craftingStation = piece.m_craftingStation;
			Requirement[] resources = piece.m_resources;
			piece.m_resources = IgnoredIngredients.Filter(resources);
			if (flag)
			{
				piece.m_craftingStation = null;
			}
			try
			{
				return (bool)HavePieceM.Invoke(player, BuildArgs(HavePieceM, piece, mode));
			}
			catch (Exception ex)
			{
				ChestDecorPlugin.Log.LogDebug((object)("HaveRequirements failed: " + ex.Message));
				if (!flag)
				{
					WarnOnce("Known-piece check failed, treating the piece as unknown: " + ex.Message);
				}
				return flag && HaveByHand(player, piece.m_resources);
			}
			finally
			{
				if (flag)
				{
					piece.m_craftingStation = craftingStation;
				}
				piece.m_resources = resources;
			}
		}

		private static bool HaveByHand(Player player, Requirement[] requirements, bool skipIgnored = true)
		{
			if (requirements == null)
			{
				return true;
			}
			foreach (Requirement val in requirements)
			{
				if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0 && (!skipIgnored || !IgnoredIngredients.IsIgnored(val)) && Count(player, val.m_resItem.m_itemData.m_shared.m_name) < val.m_amount)
				{
					return false;
				}
			}
			return true;
		}

		public static bool Pay(Player player, AttachEntry entry)
		{
			if ((Object)(object)player == (Object)null || entry == null)
			{
				return false;
			}
			if (IsFree(player))
			{
				return true;
			}
			try
			{
				switch (entry.Cost)
				{
				case CostKind.Piece:
					return Consume(player, IgnoredIngredients.Filter(((Object)(object)entry.Piece != (Object)null) ? entry.Piece.m_resources : null));
				case CostKind.Recipe:
					return Consume(player, IgnoredIngredients.Filter(((Object)(object)entry.Recipe != (Object)null) ? entry.Recipe.m_resources : null));
				default:
				{
					Recipe val = LooseRecipe(entry);
					if ((Object)(object)val != (Object)null)
					{
						return Consume(player, val.m_resources);
					}
					string name = LooseName(entry);
					int amount = LooseAmount(entry);
					if (string.IsNullOrEmpty(name) || amount <= 0)
					{
						return true;
					}
					return TakeAndVerify(player, new Dictionary<string, int> { { name, amount } }, delegate
					{
						((Humanoid)player).GetInventory().RemoveItem(name, amount, -1, true);
					});
				}
				}
			}
			catch (Exception ex)
			{
				ChestDecorPlugin.Log.LogWarning((object)("Paying for '" + entry.Prefab + "' failed: " + ex.Message));
				return false;
			}
		}

		private static bool Consume(Player player, Requirement[] requirements)
		{
			if (requirements == null)
			{
				return true;
			}
			Dictionary<string, int> needed = Needed(requirements);
			if (needed.Count == 0)
			{
				return true;
			}
			return TakeAndVerify(player, needed, delegate
			{
				if (ConsumeM != null)
				{
					ConsumeM.Invoke(player, BuildArgs(ConsumeM, requirements));
					return;
				}
				foreach (KeyValuePair<string, int> item in needed)
				{
					((Humanoid)player).GetInventory().RemoveItem(item.Key, item.Value, -1, true);
				}
			});
		}

		private static Dictionary<string, int> Needed(Requirement[] requirements)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			if (requirements == null)
			{
				return dictionary;
			}
			foreach (Requirement val in requirements)
			{
				if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0)
				{
					string name = val.m_resItem.m_itemData.m_shared.m_name;
					dictionary.TryGetValue(name, out var value);
					dictionary[name] = value + val.m_amount;
				}
			}
			return dictionary;
		}

		private static bool TakeAndVerify(Player player, Dictionary<string, int> needed, Action take)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (string key in needed.Keys)
			{
				dictionary[key] = Count(player, key);
			}
			take();
			bool flag = false;
			foreach (KeyValuePair<string, int> item in needed)
			{
				int num = dictionary[item.Key] - Count(player, item.Key);
				if (num < item.Value)
				{
					flag = true;
					ChestDecorPlugin.Log.LogDebug((object)$"Payment check: {item.Key} needed {item.Value}, {num} left the inventory.");
				}
			}
			if (!flag)
			{
				return true;
			}
			if (ConsumeIsPatchedByAnotherMod())
			{
				ChestDecorPlugin.Log.LogDebug((object)"Payment check: part of the cost did not come from the inventory; trusting the crafting-from-storage mod.");
				return true;
			}
			ChestDecorPlugin.Log.LogWarning((object)"The materials were not taken from the player, so nothing is attached.");
			return false;
		}

		private static bool ConsumeIsPatchedByAnotherMod()
		{
			if (ConsumeM == null)
			{
				return false;
			}
			try
			{
				Patches patchInfo = Harmony.GetPatchInfo((MethodBase)ConsumeM);
				return patchInfo != null && (patchInfo.Prefixes.Count > 0 || patchInfo.Postfixes.Count > 0 || patchInfo.Transpilers.Count > 0 || patchInfo.Finalizers.Count > 0);
			}
			catch (Exception ex)
			{
				ChestDecorPlugin.Log.LogDebug((object)("Could not read the patches on ConsumeResources: " + ex.Message));
				return true;
			}
		}

		public static List<PaidMaterial> CostOf(AttachEntry entry)
		{
			List<PaidMaterial> list = new List<PaidMaterial>();
			if (entry == null)
			{
				return list;
			}
			switch (entry.Cost)
			{
			case CostKind.Piece:
				AddRequirements(list, ((Object)(object)entry.Piece != (Object)null) ? entry.Piece.m_resources : null);
				break;
			case CostKind.Recipe:
				AddRequirements(list, ((Object)(object)entry.Recipe != (Object)null) ? entry.Recipe.m_resources : null);
				break;
			case CostKind.Coins:
				AddMaterial(list, "Coins", entry.CoinPrice);
				break;
			case CostKind.Material:
				if ((Object)(object)entry.CostItem != (Object)null)
				{
					AddMaterial(list, ((Object)entry.CostItem).name, entry.CostAmount);
				}
				break;
			default:
				AddMaterial(list, entry.Prefab, 1);
				break;
			}
			return list;
		}

		private static void AddRequirements(List<PaidMaterial> list, Requirement[] requirements)
		{
			if (requirements == null)
			{
				return;
			}
			Requirement[] array = IgnoredIngredients.Filter(requirements);
			foreach (Requirement val in array)
			{
				if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0)
				{
					AddMaterial(list, ((Object)((Component)val.m_resItem).gameObject).name, val.m_amount);
				}
			}
		}

		private static void AddMaterial(List<PaidMaterial> list, string prefab, int amount)
		{
			if (string.IsNullOrEmpty(prefab) || amount <= 0)
			{
				return;
			}
			for (int i = 0; i < list.Count; i++)
			{
				if (string.Equals(list[i].Prefab, prefab, StringComparison.OrdinalIgnoreCase))
				{
					list[i] = new PaidMaterial(list[i].Prefab, list[i].Amount + amount);
					return;
				}
			}
			list.Add(new PaidMaterial(prefab, amount));
		}

		public static List<PaidMaterial> OwedMaterials(ChestAttachment attachment)
		{
			if (attachment == null || !attachment.Paid)
			{
				return new List<PaidMaterial>();
			}
			if (attachment.Cost != null)
			{
				return new List<PaidMaterial>(attachment.Cost);
			}
			AttachEntry attachEntry = AttachCatalog.Find(attachment.Prefab);
			if (attachEntry == null)
			{
				ChestDecorPlugin.Log.LogWarning((object)("Could not refund attachment '" + attachment.Prefab + "': its cost was not stored (older save) and it is no longer in the catalog."));
				return new List<PaidMaterial>();
			}
			return CostOf(attachEntry);
		}

		public static List<PaidMaterial> Refund(Player player, Vector3 position, ChestAttachment attachment)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (attachment == null)
			{
				return new List<PaidMaterial>();
			}
			return Give(attachment.Prefab, OwedMaterials(attachment), player, position);
		}

		public static List<PaidMaterial> Give(string label, IList<PaidMaterial> owed, Player player, Vector3 position)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			List<PaidMaterial> list = HandOut(label, owed, player, position);
			if (list.Count > 0)
			{
				list = HandOut(label, list, player, position);
			}
			return list;
		}

		private static List<PaidMaterial> HandOut(string label, IList<PaidMaterial> materials, Player player, Vector3 position)
		{
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			List<PaidMaterial> list = new List<PaidMaterial>();
			if (materials == null)
			{
				return list;
			}
			foreach (PaidMaterial material in materials)
			{
				if (string.IsNullOrEmpty(material.Prefab) || material.Amount <= 0)
				{
					continue;
				}
				GameObject val = null;
				try
				{
					val = ResolveItemPrefab(material.Prefab);
				}
				catch (Exception ex)
				{
					ChestDecorPlugin.Log.LogWarning((object)("Refunding '" + label + "': looking up '" + material.Prefab + "' failed: " + ex.Message));
					list.Add(material);
					continue;
				}
				if ((Object)(object)val == (Object)null)
				{
					ChestDecorPlugin.Log.LogWarning((object)$"Refunding '{label}': the paid material '{material.Prefab}' no longer exists, {material.Amount}x could not be returned.");
					continue;
				}
				int num = SpawnDrop(label, player, position, val, material.Amount);
				if (num < material.Amount)
				{
					list.Add(new PaidMaterial(material.Prefab, material.Amount - num));
				}
			}
			return list;
		}

		private static GameObject ResolveItemPrefab(string name)
		{
			GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(name) : null);
			if ((Object)(object)val == (Object)null)
			{
				val = AttachCatalog.ResolvePrefab(name);
			}
			if (!((Object)(object)val != (Object)null) || !((Object)(object)val.GetComponent<ItemDrop>() != (Object)null))
			{
				return null;
			}
			return val;
		}

		private static int SpawnDrop(string label, Player player, Vector3 center, GameObject prefab, int amount)
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)prefab == (Object)null || amount <= 0)
			{
				return 0;
			}
			int num = 0;
			try
			{
				ItemDrop component = prefab.GetComponent<ItemDrop>();
				int num2 = ((!((Object)(object)component != (Object)null) || component.m_itemData == null || component.m_itemData.m_shared == null) ? 1 : Mathf.Max(1, component.m_itemData.m_shared.m_maxStackSize));
				int num3 = amount;
				while (num3 > 0)
				{
					int num4 = Mathf.Min(num3, num2);
					Vector3 val = ((!((Object)(object)player != (Object)null)) ? (center + Vector3.up + Random.insideUnitSphere * 0.25f) : (((Component)player).transform.position + ((Component)player).transform.forward * 0.8f + Vector3.up * 1.2f));
					GameObject obj = Object.Instantiate<GameObject>(prefab, val, Quaternion.identity);
					num3 -= num4;
					num += num4;
					ItemDrop component2 = obj.GetComponent<ItemDrop>();
					if ((Object)(object)component2 != (Object)null && num4 > 1)
					{
						component2.SetStack(num4);
					}
				}
			}
			catch (Exception ex)
			{
				ChestDecorPlugin.Log.LogWarning((object)$"Refunding '{label}': handing out '{((Object)prefab).name}' failed after {num} of {amount}: {ex.Message}");
			}
			return num;
		}

		public static string Describe(AttachEntry entry)
		{
			if (entry == null)
			{
				return "";
			}
			if (IsFree(Player.m_localPlayer))
			{
				return "Free";
			}
			switch (entry.Cost)
			{
			case CostKind.Coins:
				return entry.CoinPrice + " " + Loc("$item_coins");
			case CostKind.Item:
				return "1x " + entry.DisplayName;
			case CostKind.Material:
			{
				string text = LooseName(entry);
				return entry.CostAmount + "x " + (string.IsNullOrEmpty(text) ? "?" : Loc(text));
			}
			default:
			{
				Requirement[] requirements = entry.Requirements;
				if (requirements == null || requirements.Length == 0)
				{
					return "Free";
				}
				StringBuilder stringBuilder = new StringBuilder();
				Requirement[] array = requirements;
				foreach (Requirement val in array)
				{
					if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0)
					{
						if (stringBuilder.Length > 0)
						{
							stringBuilder.Append(", ");
						}
						stringBuilder.Append(val.m_amount).Append("x ").Append(Loc(val.m_resItem.m_itemData.m_shared.m_name));
					}
				}
				if (stringBuilder.Length <= 0)
				{
					return "Free";
				}
				return stringBuilder.ToString();
			}
			}
		}

		private static string Loc(string key)
		{
			try
			{
				return Localization.instance.Localize(key);
			}
			catch
			{
				return key;
			}
		}

		public static bool IsFree(Player player)
		{
			if (ChestDecorPlugin.FreeAttachments.Value)
			{
				return true;
			}
			if (NoCostM == null)
			{
				return false;
			}
			try
			{
				return (bool)NoCostM.Invoke(player, null);
			}
			catch
			{
				return false;
			}
		}

		private static int Count(Player player, string sharedName)
		{
			if (string.IsNullOrEmpty(sharedName))
			{
				return 0;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return 0;
			}
			return inventory.CountItems(sharedName, -1, true);
		}

		private static MethodInfo Find(Type type, string name, Type firstParam)
		{
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name != name)
				{
					continue;
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				if (firstParam == null)
				{
					if (parameters.Length == 0)
					{
						return methodInfo;
					}
				}
				else if (parameters.Length != 0 && parameters[0].ParameterType == firstParam)
				{
					return methodInfo;
				}
			}
			return null;
		}

		private static object[] BuildArgs(MethodInfo method, object first, string enumMode = "CanBuild")
		{
			ParameterInfo[] parameters = method.GetParameters();
			object[] array = new object[parameters.Length];
			array[0] = first;
			for (int i = 1; i < parameters.Length; i++)
			{
				ParameterInfo parameterInfo = parameters[i];
				Type parameterType = parameterInfo.ParameterType;
				if (parameterType.IsEnum)
				{
					array[i] = Enum.Parse(parameterType, enumMode);
				}
				else if (parameterType == typeof(bool))
				{
					array[i] = false;
				}
				else if (parameterType == typeof(int))
				{
					if (parameterInfo.Name != null && parameterInfo.Name.IndexOf("itemQuality", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						array[i] = (parameterInfo.HasDefaultValue ? parameterInfo.DefaultValue : ((object)(-1)));
					}
					else
					{
						array[i] = 1;
					}
				}
				else if (parameterInfo.HasDefaultValue)
				{
					array[i] = parameterInfo.DefaultValue;
				}
				else
				{
					array[i] = (parameterType.IsValueType ? Activator.CreateInstance(parameterType) : null);
				}
			}
			return array;
		}
	}
	public sealed class AttachEntry
	{
		public string Prefab;

		public GameObject Object;

		public AttachCategory Categories;

		public string SharedName;

		public string DisplayName;

		public string RowLabel;

		public string Sources;

		public Sprite Icon;

		public bool IsPiece;

		public Piece Piece;

		public Recipe Recipe;

		public int CoinPrice;

		public CostKind Cost;

		public GameObject CostItem;

		public int CostAmount;

		public bool IsPlant;

		public bool Listed;

		public bool Qualified;

		public Requirement[] Requirements
		{
			get
			{
				if (IsPiece)
				{
					return IgnoredIngredients.Filter(((Object)(object)Piece != (Object)null) ? Piece.m_resources : null);
				}
				return IgnoredIngredients.Filter(((Object)(object)Recipe != (Object)null) ? Recipe.m_resources : null);
			}
		}
	}
	internal static class AttachmentRemoval
	{
		private static ChestDecorator Decor => ChestDecorUI.Decor;

		internal static void Remove(int index)
		{
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Decor == (Object)null || index < 0 || index >= ChestDecorUI.SavedAtt.Count || index >= ChestDecorUI.WorkAtt.Count || !ChestDecorUI.CanChangeAttachments() || index >= ChestDecorUI.SavedAtt.Count || index >= ChestDecorUI.WorkAtt.Count)
			{
				return;
			}
			ChestAttachment chestAttachment = ChestDecorUI.SavedAtt[index];
			ChestAttachment removedWork = ChestDecorUI.WorkAtt[index];
			string prefab = chestAttachment.Prefab;
			int selectedIndex = PositionPage.SelectedIndex;
			List<ChestAttachment> list = ChestAttachment.CloneList(ChestDecorUI.SavedAtt);
			list.RemoveAt(index);
			if (!Decor.SaveAttachments(list))
			{
				ChestDecorUI.Toast("Could not remove the attachment.");
				return;
			}
			ChestDecorUI.SavedAtt = list;
			ChestDecorUI.WorkAtt.RemoveAt(index);
			Decor.ShowAttachments(ChestDecorUI.WorkAtt);
			if (index < PositionPage.SelectedIndex)
			{
				PositionPage.SelectedIndex--;
			}
			AttachEntry attachEntry = AttachCatalog.Find(prefab);
			string text = ((attachEntry != null) ? attachEntry.DisplayName : prefab);
			if (ChestDecorPlugin.RefundOnRemove.Value && chestAttachment.Paid)
			{
				Player localPlayer = Player.m_localPlayer;
				List<PaidMaterial> list2 = (((Object)(object)localPlayer != (Object)null) ? AttachCost.Refund(localPlayer, ((Component)localPlayer).transform.position, chestAttachment) : AttachCost.OwedMaterials(chestAttachment));
				if (list2.Count > 0)
				{
					PutBackWithOutstanding(index, chestAttachment, removedWork, list2, text, selectedIndex);
					return;
				}
			}
			if (ChestDecorPlugin.RefundOnRemove.Value && chestAttachment.Paid && chestAttachment.Cost == null && attachEntry == null)
			{
				ChestDecorUI.Toast("Removed " + text + ". Its cost was not recorded and the item no longer exists, so nothing could be returned.");
			}
			else
			{
				ChestDecorUI.Toast("Removed " + text);
			}
			PositionPage.Refresh();
		}

		private static void PutBackWithOutstanding(int index, ChestAttachment removed, ChestAttachment removedWork, List<PaidMaterial> outstanding, string shownName, int selectionBefore)
		{
			ChestAttachment chestAttachment = removed.Clone();
			chestAttachment.Cost = outstanding;
			ChestAttachment chestAttachment2 = (removedWork ?? removed).Clone();
			chestAttachment2.Paid = chestAttachment.Paid;
			chestAttachment2.Cost = new List<PaidMaterial>(outstanding);
			List<ChestAttachment> list = ChestAttachment.CloneList(ChestDecorUI.SavedAtt);
			list.Insert(Mathf.Min(index, list.Count), chestAttachment);
			if (Decor.SaveAttachments(list))
			{
				ChestDecorUI.SavedAtt = list;
				ChestDecorUI.WorkAtt.Insert(Mathf.Min(index, ChestDecorUI.WorkAtt.Count), chestAttachment2);
				PositionPage.SelectedIndex = selectionBefore;
				Decor.ShowAttachments(ChestDecorUI.WorkAtt);
				ChestDecorUI.Toast("Could not return all materials for " + shownName + ". It stays on the chest; remove it again to retry.");
			}
			else
			{
				ChestDecorPlugin.Log.LogWarning((object)("Refund of '" + removed.Prefab + "' was incomplete and the attachment could not be put back on the chest."));
				ChestDecorUI.Toast("Could not return all materials for " + shownName + ".");
			}
			PositionPage.Refresh();
		}
	}
	internal static class AttachPage
	{
		private const int AttachRows = 8;

		private static readonly List<ChestDecorAttachRow> _attachRows = new List<ChestDecorAttachRow>();

		private static readonly List<AttachEntry> _filtered = new List<AttachEntry>();

		private static readonly string[] _filterNames = new string[10] { "All", "Trophies", "Trader", "Produce", "Drops", "Food", "Ammo", "Weapons", "Armor", "Decor" };

		private static readonly AttachCategory[] _filterValues = new AttachCategory[10]
		{
			AttachCategory.None,
			AttachCategory.Trophy,
			AttachCategory.Trader,
			AttachCategory.Produce,
			AttachCategory.Drop,
			AttachCategory.Food,
			AttachCategory.Ammo,
			AttachCategory.Weapon,
			AttachCategory.Armor,
			AttachCategory.Decor
		};

		private static readonly Image[] _filterImages = (Image[])(object)new Image[10];

		private static AttachCategory _filter = AttachCategory.None;

		private static string _search = "";

		private static int _page;

		private static Text _pageLabel;

		private static Text _attachStatus;

		private static InputField _searchField;

		private static float _nextRefresh;

		private static ChestDecorator Decor => ChestDecorUI.Decor;

		internal static void Open()
		{
			_filter = AttachCategory.None;
			_search = "";
			_page = 0;
			_nextRefresh = 0f;
		}

		internal static void Close()
		{
			_attachRows.Clear();
			_filtered.Clear();
			_pageLabel = null;
			_attachStatus = null;
			_searchField = null;
			for (int i = 0; i < _filterImages.Length; i++)
			{
				_filterImages[i] = null;
			}
		}

		internal static void Tick()
		{
			if (ChestDecorUI.Tab == ChestDecorTab.Attach && !((Object)(object)ChestDecorUI.AttachPageRoot == (Object)null) && !(Time.unscaledTime < _nextRefresh))
			{
				_nextRefresh = Time.unscaledTime + 1.5f;
				RefreshRows();
			}
		}

		internal static void Build(Transform t)
		{
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_032a: Unknown result type (might be due to invalid IL or missing references)
			//IL_03e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0416: Unknown result type (might be due to invalid IL or missing references)
			//IL_043f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0468: Unknown result type (might be due to invalid IL or missing references)
			GUIManager instance = GUIManager.Instance;
			for (int i = 0; i < _filterNames.Length; i++)
			{
				int index = i;
				int num = i % 5;
				int num2 = i / 5;
				Button val = UiKit.MakeButton(_filterNames[i], t, 40f + (float)num * 113.25f, 92f + (float)num2 * 36f, 107f, 32f, delegate
				{
					OnFilter(index);
				}, 15);
				_filterImages[i] = ((Component)val).GetComponent<Image>();
			}
			GameObject obj = instance.CreateInputField(t, new Vector2(0f, 1f), new Vector2(0f, 1f), Vector2.zero, (ContentType)0, "Search...", 16, 330f, 34f);
			UiKit.Place(obj, 40f, 166f, 330f, 34f);
			_searchField = obj.GetComponent<InputField>();
			_searchField.characterLimit = 40;
			((UnityEvent<string>)(object)_searchField.onValueChanged).AddListener((UnityAction<string>)OnSearchChanged);
			UiKit.MakeButton("<", t, 380f, 166f, 40f, 34f, delegate
			{
				OnPage(-1);
			}, 18);
			_pageLabel = UiKit.Label("", t, 424f, 166f, 132f, 34f, 16, instance.ValheimBeige, (TextAnchor)4);
			UiKit.MakeButton(">", t, 560f, 166f, 40f, 34f, delegate
			{
				OnPage(1);
			}, 18);
			_attachRows.Clear();
			for (int num3 = 0; num3 < 8; num3++)
			{
				int index2 = num3;
				float y = 208f + (float)num3 * 46f;
				GameObject val2 = UiKit.NewUI("ChestDecorAttachRow" + num3, t, typeof(Image));
				UiKit.Place(val2, 40f, y, 560f, 42f);
				Image component = val2.GetComponent<Image>();
				((Graphic)component).color = new Color(0f, 0f, 0f, 0.28f);
				((Graphic)component).raycastTarget = false;
				GameObject obj2 = UiKit.NewUI("Icon", val2.transform, typeof(Image));
				UiKit.Place(obj2, 4f, 3f, 36f, 36f);
				Image component2 = obj2.GetComponent<Image>();
				component2.preserveAspect = true;
				((Graphic)component2).raycastTarget = false;
				Text val3 = UiKit.Label("", val2.transform, 46f, 0f, 206f, 42f, 16, instance.ValheimBeige, (TextAnchor)3);
				val3.resizeTextForBestFit = true;
				val3.resizeTextMinSize = 10;
				val3.resizeTextMaxSize = 16;
				Text val4 = UiKit.Label("", val2.transform, 256f, 0f, 196f, 42f, 13, instance.ValheimBeige, (TextAnchor)3);
				val4.resizeTextForBestFit = true;
				val4.resizeTextMinSize = 9;
				val4.resizeTextMaxSize = 13;
				Button button = UiKit.MakeButton("Attach", val2.transform, 456f, 4f, 100f, 34f, delegate
				{
					OnAttachClicked(index2);
				}, 16);
				_attachRows.Add(new ChestDecorAttachRow
				{
					Root = val2,
					Icon = component2,
					Name = val3,
					Cost = val4,
					Button = button
				});
			}
			_attachStatus = UiKit.Label("", t, 40f, 584f, 560f, 26f, 18, instance.ValheimBeige, (TextAnchor)4);
			UiKit.Label("Attaching pays the cost and saves immediately. Move items in the Position tab.", t, 40f, 614f, 560f, 22f, 14, instance.ValheimBeige, (TextAnchor)4);
			UiKit.Label("Costs use your inventory and, with crafting-from-storage mods, nearby chests.", t, 40f, 636f, 560f, 22f, 14, instance.ValheimBeige, (TextAnchor)4);
			UiKit.Label("Listed: things you can craft or build, plus the items in the filters above.", t, 40f, 658f, 560f, 22f, 14, instance.ValheimBeige, (TextAnchor)4);
		}

		private static void RebuildFilter()
		{
			_filtered.Clear();
			string text = ((_search != null) ? _search.Trim() : "");
			foreach (AttachEntry entry in AttachCatalog.Entries)
			{
				if (entry.Listed && (_filter == AttachCategory.None || (entry.Categories & _filter) != AttachCategory.None) && (text.Length <= 0 || entry.DisplayName.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0 || entry.Prefab.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0))
				{
					_filtered.Add(entry);
				}
			}
		}

		internal static void RefreshList()
		{
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ChestDecorUI.AttachPageRoot == (Object)null || (Object)(object)Decor == (Object)null)
			{
				return;
			}
			RebuildFilter();
			int num = Mathf.Max(1, Mathf.CeilToInt((float)_filtered.Count / 8f));
			_page = Mathf.Clamp(_page, 0, num - 1);
			if ((Object)(object)_pageLabel != (Object)null)
			{
				_pageLabel.text = ((_filtered.Count == 0) ? "No items" : $"{_page + 1} / {num}");
			}
			for (int i = 0; i < _filterImages.Length; i++)
			{
				if ((Object)(object)_filterImages[i] != (Object)null)
				{
					((Graphic)_filterImages[i]).color = ((_filterValues[i] == _filter) ? UiKit.RowSelected : UiKit.RowNormal);
				}
			}
			RefreshRows();
		}

		internal static void RefreshRows()
		{
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ChestDecorUI.AttachPageRoot == (Object)null || (Object)(object)Decor == (Object)null)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			bool flag = ChestDecorUI.SavedAtt.Count >= Decor.MaxAttachments;
			for (int i = 0; i < _attachRows.Count; i++)
			{
				ChestDecorAttachRow chestDecorAttachRow = _attachRows[i];
				int num = _page * 8 + i;
				if (num >= _filtered.Count)
				{
					chestDecorAttachRow.Entry = null;
					chestDecorAttachRow.Root.SetActive(false);
					continue;
				}
				AttachEntry attachEntry = (chestDecorAttachRow.Entry = _filtered[num]);
				chestDecorAttachRow.Root.SetActive(true);
				chestDecorAttachRow.Icon.sprite = attachEntry.Icon;
				((Behaviour)chestDecorAttachRow.Icon).enabled = (Object)(object)attachEntry.Icon != (Object)null;
				chestDecorAttachRow.Name.text = attachEntry.RowLabel ?? attachEntry.DisplayName;
				string reason;
				bool flag2 = AttachCost.Evaluate(localPlayer, attachEntry, out reason);
				string text = AttachCost.Describe(attachEntry);
				chestDecorAttachRow.Cost.text = ((flag2 || string.IsNullOrEmpty(reason)) ? text : (text + "\n" + reason));
				((Graphic)chestDecorAttachRow.Cost).color = (flag2 ? UiKit.GoodColor : UiKit.BadColor);
				((Selectable)chestDecorAttachRow.Button).interactable = flag2 && !flag;
			}
			if ((Object)(object)_attachStatus != (Object)null)
			{
				_attachStatus.text = (flag ? $"This chest is full ({ChestDecorUI.SavedAtt.Count} / {Decor.MaxAttachments}). Remove one in the Position tab." : $"Attached to this chest: {ChestDecorUI.SavedAtt.Count} / {Decor.MaxAttachments}");
				((Graphic)_attachStatus).color = (flag ? GUIManager.Instance.ValheimYellow : GUIManager.Instance.ValheimBeige);
			}
		}

		private static void OnFilter(int index)
		{
			if (index >= 0 && index < _filterValues.Length)
			{
				_filter = _filterValues[index];
				_page = 0;
				RefreshList();
			}
		}

		private static void OnSearchChanged(string text)
		{
			_search = text ?? "";
			_page = 0;
			RefreshList();
		}

		private static void OnPage(int delta)
		{
			_page += delta;
			RefreshList();
		}

		private static void OnAttachClicked(int rowIndex)
		{
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)Decor == (Object)null || rowIndex < 0 || rowIndex >= _attachRows.Count)
			{
				return;
			}
			AttachEntry entry = _attachRows[rowIndex].Entry;
			Player localPlayer = Player.m_localPlayer;
			if (entry == null || (Object)(object)localPlayer == (Object)null || !ChestDecorUI.CanChangeAttachments())
			{
				return;
			}
			if (ChestDecorUI.SavedAtt.Count >= Decor.MaxAttachments)
			{
				ChestDecorUI.Toast("This chest can't hold more attachments.");
				return;
			}
			if (!AttachVisuals.GetModel(entry.Object).Valid)
			{
				ChestDecorUI.Toast(entry.DisplayName + " has no model that can be shown.");
				return;
			}
			if (!AttachCost.Evaluate(localPlayer, entry, out var reason))
			{
				ChestDecorUI.Toast(string.IsNullOrEmpty(reason) ? "You can't afford that." : reason);
				RefreshRows();
				return;
			}
			bool flag = !AttachCost.IsFree(localPlayer);
			List<PaidMaterial> cost = (flag ? AttachCost.CostOf(entry) : new List<PaidMaterial>());
			if (!AttachCost.Pay(localPlayer, entry))
			{
				ChestDecorUI.Toast("Could not pay for " + entry.DisplayName + ".");
				return;
			}
			ChestAttachment chestAttachment = Decor.CreateDefault(entry.Prefab, ChestDecorUI.SavedAtt.Count);
			chestAttachment.Paid = flag;
			chestAttachment.Cost = cost;
			ChestDecorUI.SavedAtt.Add(chestAttachment);
			ChestDecorUI.WorkAtt.Add(chestAttachment.Clone());
			if (!Decor.SaveAttachments(ChestDecorUI.SavedAtt))
			{
				ChestDecorUI.SavedAtt.RemoveAt(ChestDecorUI.SavedAtt.Count - 1);
				ChestDecorUI.WorkAtt.RemoveAt(ChestDecorUI.WorkAtt.Count - 1);
				AttachCost.Refund(localPlayer, ((Component)localPlayer).transform.position, chestAttachment);
				ChestDecorUI.Toast("Could not attach to the chest.");
				RefreshRows();
			}
			else
			{
				Decor.ShowAttachments(ChestDecorUI.WorkAtt);
				PositionPage.SelectedIndex = ChestDecorUI.WorkAtt.Count - 1;
				ChestDecorUI.Toast("Attached " + entry.DisplayName);
				RefreshRows();
				_nextRefresh = Time.unscaledTime + 1.5f;
			}
		}
	}
	internal sealed class AttachPieceInfo
	{
		public GameObject Object;

		public bool Furniture;

		public bool Light;

		public bool Listed;
	}
	internal sealed class AttachVisualModel
	{
		private const int MaxMeshVertices = 60000;

		public readonly List<AttachVisualPart> Parts = new List<AttachVisualPart>();

		public Bounds Bounds;

		private Vector3[] _outline;

		private bool _hasExtent;

		private Quaternion _extentRotation;

		private float _extentScale;

		private Bounds _extent;

		public bool Valid => Parts.Count > 0;

		public Bounds ExtentAround(Quaternion rotation, float scale)
		{
			//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)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: 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_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: 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_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0219: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: 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)
			Vector3[] array = _outline ?? (_outline = BuildOutline());
			if (array.Length == 0)
			{
				return new Bounds(Vector3.zero, Vector3.zero);
			}
			if (_hasExtent && _extentScale == scale && _extentRotation.x == rotation.x && _extentRotation.y == rotation.y && _extentRotation.z == rotation.z && _extentRotation.w == rotation.w)
			{
				return _extent;
			}
			Matrix4x4 val = Matrix4x4.Rotate(rotation);
			Vector3 val2 = default(Vector3);
			((Vector3)(ref val2))..ctor(float.MaxValue, float.MaxValue, float.MaxValue);
			Vector3 val3 = default(Vector3);
			((Vector3)(ref val3))..ctor(float.MinValue, float.MinValue, float.MinValue);
			foreach (Vector3 val4 in array)
			{
				float num = (val.m00 * val4.x + val.m01 * val4.y + val.m02 * val4.z) * scale;
				float num2 = (val.m10 * val4.x + val.m11 * val4.y + val.m12 * val4.z) * scale;
				float num3 = (val.m20 * val4.x + val.m21 * val4.y + val.m22 * val4.z) * scale;
				if (num < val2.x)
				{
					val2.x = num;
				}
				if (num2 < val2.y)
				{
					val2.y = num2;
				}
				if (num3 < val2.z)
				{
					val2.z = num3;
				}
				if (num > val3.x)
				{
					val3.x = num;
				}
				if (num2 > val3.y)
				{
					val3.y = num2;
				}
				if (num3 > val3.z)
				{
					val3.z = num3;
				}
			}
			Bounds val5 = default(Bounds);
			((Bounds)(ref val5)).SetMinMax(val2, val3);
			_extentRotation = rotation;
			_extentScale = scale;
			_extent = val5;
			_hasExtent = true;
			return val5;
		}

		private Vector3[] BuildOutline()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: 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_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			List<Vector3> list = new List<Vector3>();
			HashSet<Vector3> hashSet = new HashSet<Vector3>();
			Vector3 center = ((Bounds)(ref Bounds)).center;
			foreach (AttachVisualPart part in Parts)
			{
				Mesh mesh = part.Mesh;
				if ((Object)(object)mesh == (Object)null)
				{
					continue;
				}
				Vector3[] array = null;
				try
				{
					if (mesh.isReadable && mesh.vertexCount > 0 && mesh.vertexCount <= 60000)
					{
						array = mesh.vertices;
					}
				}
				catch (Exception)
				{
					array = null;
				}
				if (array != null && array.Length != 0)
				{
					for (int i = 0; i < array.Length; i++)
					{
						Vector3 item = ((Matrix4x4)(ref part.Local)).MultiplyPoint3x4(array[i]) - center;
						if (hashSet.Add(item))
						{
							list.Add(item);
						}
					}
				}
				else
				{
					Bounds bounds = mesh.bounds;
					for (int j = 0; j < 8; j++)
					{
						Vector3 val = ((Bounds)(ref bounds)).center + Vector3.Scale(((Bounds)(ref bounds)).extents, new Vector3(((j & 1) == 0) ? (-1f) : 1f, ((j & 2) == 0) ? (-1f) : 1f, ((j & 4) == 0) ? (-1f) : 1f));
						list.Add(((Matrix4x4)(ref part.Local)).MultiplyPoint3x4(val) - center);
					}
				}
			}
			return list.ToArray();
		}
	}
	internal sealed class AttachVisualPart
	{
		public Mesh Mesh;

		public Material[] Materials;

		public Matrix4x4 Local;
	}
	internal static class AttachVisuals
	{
		private static readonly Dictionary<string, AttachVisualModel> Cache = new Dictionary<string, AttachVisualModel>();

		public static void ClearCache()
		{
			Cache.Clear();
		}

		public static AttachVisualModel GetModel(GameObject prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return new AttachVisualModel();
			}
			string name = ((Object)prefab).name;
			if (Cache.TryGetValue(name, out var value) && IsStillLoaded(value))
			{
				return value;
			}
			AttachVisualModel attachVisualModel = Create(prefab);
			Cache[name] = attachVisualModel;
			return attachVisualModel;
		}

		private static bool IsStillLoaded(AttachVisualModel model)
		{
			foreach (AttachVisualPart part in model.Parts)
			{
				if ((Object)(object)part.Mesh == (Object)null)
				{
					return false;
				}
			}
			return true;
		}

		private static AttachVisualModel Create(GameObject prefab)
		{
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			AttachVisualModel attachVisualModel = new AttachVisualModel();
			try
			{
				Transform transform = prefab.transform;
				HashSet<Renderer> hashSet = new HashSet<Renderer>();
				LODGroup componentInChildren = prefab.GetComponentInChildren<LODGroup>(true);
				Dictionary<Renderer, string> dictionary = new Dictionary<Renderer, string>();
				if ((Object)(object)componentInChildren != (Object)null)
				{
					LOD[] lODs = componentInChildren.GetLODs();
					for (int i = 1; i < lODs.Length; i++)
					{
						if (lODs[i].renderers == null)
						{
							continue;
						}
						Renderer[] renderers = lODs[i].renderers;
						foreach (Renderer val in renderers)
						{
							if (!((Object)(object)val == (Object)null))
							{
								hashSet.Add(val);
								dictionary[val] = $"LOD{i} (skipped)";
							}
						}
					}
					if (lODs.Length != 0 && lODs[0].renderers != null)
					{
						Renderer[] renderers = lODs[0].renderers;
						foreach (Renderer val2 in renderers)
						{
							if (!((Object)(object)val2 == (Object)null))
							{
								hashSet.Remove(val2);
								dictionary[val2] = "LOD0 (kept)";
							}
						}
					}
				}
				if (((!((Object)(object)prefab.GetComponentInChildren<Pickable>(true) != (Object)null) && !((Object)(object)prefab.GetComponent<Plant>() != (Object)null)) || !Collect(attachVisualModel, transform, hashSet, allowAttach: false, allowSkinned: false, activeOnly: true)) && !Collect(attachVisualModel, transform, hashSet, allowAttach: false, allowSkinned: false) && !Collect(attachVisualModel, transform, hashSet, allowAttach: true, allowSkinned: false))
				{
					Collect(attachVisualModel, transform, hashSet, allowAttach: true, allowSkinned: true);
				}
				if (attachVisualModel.Valid)
				{
					bool flag = false;
					foreach (AttachVisualPart part in attachVisualModel.Parts)
					{
						Bounds bounds = part.Mesh.bounds;
						for (int k = 0; k < 8; k++)
						{
							Vector3 val3 = ((Bounds)(ref bounds)).center + Vector3.Scale(((Bounds)(ref bounds)).extents, new Vector3(((k & 1) == 0) ? (-1f) : 1f, ((k & 2) == 0) ? (-1f) : 1f, ((k & 4) == 0) ? (-1f) : 1f));
							Vector3 val4 = ((Matrix4x4)(ref part.Local)).MultiplyPoint3x4(val3);
							if (!flag)
							{
								attachVisualModel.Bounds = new Bounds(val4, Vector3.zero);
								flag = true;
							}
							else
							{
								((Bounds)(ref attachVisualModel.Bounds)).Encapsulate(val4);
							}
						}
					}
				}
				else if (ChestDecorPlugin.VerboseModelDiagnostics != null && ChestDecorPlugin.VerboseModelDiagnostics.Value)
				{
					LogModelDiagnostics(prefab, transform, hashSet, componentInChildren, dictionary);
				}
			}
			catch (Exception ex)
			{
				ChestDecorPlugin.Log.LogWarning((object)("Could not read the model of '" + ((Object)prefab).name + "': " + ex.Message));
				if (ChestDecorPlugin.VerboseModelDiagnostics != null && ChestDecorPlugin.VerboseModelDiagnostics.Value)
				{
					ChestDecorPlugin.Log.LogWarning((object)$"[ModelDiag] {((Object)prefab).name}: exception while reading model: {ex}");
				}
				attachVisualModel.Parts.Clear();
			}
			return attachVisualModel;
		}

		private static void LogModelDiagnostics(GameObject prefab, Transform root, HashSet<Renderer> skip, LODGroup lodGroup, Dictionary<Renderer, string> lodInfo)
		{
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Invalid comparison between Unknown and I4
			Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren<Renderer>(true);
			ChestDecorPlugin.Log.LogWarning((object)string.Format("[ModelDiag] {0}: INVALID physical model; renderer count={1}, LODGroup={2}, skippedByLOD={3}", ((Object)prefab).name, componentsInChildren.Length, ((Object)(object)lodGroup != (Object)null) ? "yes" : "no", skip.Count));
			Renderer[] array = componentsInChildren;
			foreach (Renderer val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				string transformPath = GetTransformPath(((Component)val).transform, root);
				List<string> list = new List<string>();
				if (skip.Contains(val))
				{
					list.Add(lodInfo.TryGetValue(val, out var value) ? value : "LOD-skipped");
				}
				if (!val.enabled)
				{
					list.Add("renderer-disabled");
				}
				if ((int)val.shadowCastingMode == 3)
				{
					list.Add("shadows-only");
				}
				if (UnderAttach(((Component)val).transform, root))
				{
					list.Add("under-attach");
				}
				Mesh val2 = null;
				if (val is MeshRenderer)
				{
					MeshFilter component = ((Component)val).GetComponent<MeshFilter>();
					if ((Object)(object)component == (Object)null)
					{
						list.Add("MeshRenderer-without-MeshFilter");
					}
					else if ((Object)(object)component.sharedMesh == (Object)null)
					{
						list.Add("MeshFilter.sharedMesh-null");
					}
					else
					{
						val2 = component.sharedMesh;
					}
				}
				else
				{
					SkinnedMeshRenderer val3 = (SkinnedMeshRenderer)(object)((val is SkinnedMeshRenderer) ? val : null);
					if (val3 != null)
					{
						if ((Object)(object)val3.sharedMesh == (Object)null)
						{
							list.Add("SkinnedMeshRenderer.sharedMesh-null");
						}
						else
						{
							val2 = val3.sharedMesh;
						}
					}
					else
					{
						list.Add("renderer-type-not-supported-by-collector");
					}
				}
				Material[] sharedMaterials = val.sharedMaterials;
				if (sharedMaterials == null || sharedMaterials.Length == 0)
				{
					list.Add("no-materials");
				}
				string text = ((list.Count == 0) ? "WOULD-BE-ACCEPTED" : string.Join(", ", list));
				ChestDecorPlugin.Log.LogWarning((object)string.Format("[ModelDiag] {0}: {1} [{2}] enabled={3} active={4} mesh={5} materials={6} => {7}", ((Object)prefab).name, transformPath, ((object)val).GetType().Name, val.enabled, ((Component)val).gameObject.activeSelf, ((Object)(object)val2 != (Object)null) ? ((Object)val2).name : "<null>", (sharedMaterials == null) ? "<null>" : sharedMaterials.Length.ToString(), text));
			}
			ChestDecorPlugin.Log.LogWarning((object)("[ModelDiag] " + ((Object)prefab).name + ": If no renderer is marked WOULD-BE-ACCEPTED, the reasons above explain the exclusion. Skinned meshes are only accepted in the final collector pass."));
		}

		private static string GetTransformPath(Transform t, Transform root)
		{
			List<string> list = new List<string>();
			Transform val = t;
			while ((Object)(object)val != (Object)null)
			{
				list.Add(((Object)val).name);
				if ((Object)(object)val == (Object)(object)root)
				{
					break;
				}
				val = val.parent;
			}
			list.Reverse();
			return string.Join("/", list);
		}

		private static bool Collect(AttachVisualModel model, Transform root, HashSet<Renderer> skip, bool allowAttach, bool allowSkinned, bool activeOnly = false)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Invalid comparison between Unknown and I4
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			model.Parts.Clear();
			Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if (skip.Contains(val) || !val.enabled || (activeOnly && !SwitchedOn(((Component)val).transform, root)) || (int)val.shadowCastingMode == 3 || (!allowAttach && UnderAttach(((Component)val).transform, root)))
				{
					continue;
				}
				Mesh val2 = null;
				if (val is MeshRenderer)
				{
					MeshFilter component = ((Component)val).GetComponent<MeshFilter>();
					if ((Object)(object)component != (Object)null)
					{
						val2 = component.sharedMesh;
					}
				}
				else if (allowSkinned && val is SkinnedMeshRenderer)
				{
					val2 = ((SkinnedMeshRenderer)val).sharedMesh;
				}
				if (!((Object)(object)val2 == (Object)null))
				{
					Material[] sharedMaterials = val.sharedMaterials;
					if (sharedMaterials != null && sharedMaterials.Length != 0)
					{
						model.Parts.Add(new AttachVisualPart
						{
							Mesh = val2,
							Materials = sharedMaterials,
							Local = RelativeMatrix(((Component)val).transform, root)
						});
					}
				}
			}
			return model.Parts.Count > 0;
		}

		private static bool SwitchedOn(Transform t, Transform root)
		{
			Transform val = t;
			while ((Object)(object)val != (Object)null)
			{
				if (!((Component)val).gameObject.activeSelf)
				{
					return false;
				}
				if ((Object)(object)val == (Object)(object)root)
				{
					break;
				}
				val = val.parent;
			}
			return true;
		}

		private static bool UnderAttach(Transform t, Transform root)
		{
			Transform val = t;
			while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root)
			{
				if (((Object)val).name.StartsWith("attach", StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
				val = val.parent;
			}
			return false;
		}

		private static Matrix4x4 RelativeMatrix(Transform t, Transform root)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			Matrix4x4 val = Matrix4x4.identity;
			Transform val2 = t;
			while ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)root)
			{
				val = Matrix4x4.TRS(val2.localPosition, val2.localRotation, val2.localScale) * val;
				val2 = val2.parent;
			}
			return val;
		}

		private static void ApplyMatrix(Transform t, Matrix4x4 m)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: 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_0038: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unk