Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ChestDecor v1.0.0
ChestDecor.dll
Decompiled 5 days ago
The result has been truncated due to the large size, download it to view full contents!
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