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 Storehouse v0.8.8
plugins/Storehouse/Storehouse.dll
Decompiled 19 hours 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.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Rendering; 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("Storehouse")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("Storehouse")] [assembly: AssemblyTitle("Storehouse")] [assembly: AssemblyVersion("0.1.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 Finn.Storehouse { [BepInPlugin("com.joostliebregts.storehouse", "Storehouse", "0.8.8")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInProcess("valheim.exe")] [BepInProcess("valheim_server.exe")] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Id = "com.joostliebregts.storehouse"; public const string Version = "0.8.8"; internal static ManualLogSource Log; internal static string Dir; internal static readonly List<string> Failures = new List<string>(); private const string LiveSlot = "com.joostliebregts.storehouse.live"; internal static bool FromScripts; private bool started; private bool failed; private bool quitting; private float nextWaitLog; private Action onQuit; private Harmony harmony; private EventHandler<SettingChangedEventArgs> onConfig; private EventHandler<ConfigurationSynchronizationEventArgs> onSync; private float nextModelCheck; private float nextConfigCheck; private DateTime configStamp; private const string UnofficialNote = "Unofficial mod, not affiliated with or endorsed by Iron Gate or Coffee Stain."; internal static bool Live { get; private set; } internal static bool Headless => (int)SystemInfo.graphicsDeviceType == 4; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; FromScripts = typeof(Plugin).Assembly.GetName().Name != "Storehouse"; Dir = (FromScripts ? Path.Combine(Paths.BepInExRootPath, "scripts", "Storehouse") : Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location)); onQuit = delegate { quitting = true; }; Application.quitting += onQuit; if (FromScripts) { Log.LogInfo((object)"Storehouse 0.8.8: loaded from BepInEx/scripts (live reload); hooking in once Jotunn is up and an older copy has left."); } else { Begin(); } } private void Begin() { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Expected O, but got Unknown started = true; Live = true; AppDomain.CurrentDomain.SetData("com.joostliebregts.storehouse.live", this); if (FromScripts) { Guard("version check", ShowToVersionCheck); } Guard("settings", delegate { Settings.Bind(((BaseUnityPlugin)this).Config); }); Guard("icon settings", delegate { Icons.Bind(((BaseUnityPlugin)this).Config); }); if (!Object.op_Implicit((Object)(object)ObjectDB.instance)) { PrefabManager.OnVanillaPrefabsAvailable += AddPieces; } Guard("words", AddLocalization); harmony = new Harmony("com.joostliebregts.storehouse." + Guid.NewGuid().ToString("N")); Guard("patches", delegate { harmony.PatchAll(typeof(Patches)); }); Guard("load guard", delegate { harmony.PatchAll(typeof(LoadGuard)); }); Guard("skin patches", delegate { harmony.PatchAll(typeof(SkinPatches)); }); Guard("armory tidy", delegate { harmony.PatchAll(typeof(Mannequins)); }); Guard("typed slots", delegate { harmony.PatchAll(typeof(TypedSlots)); }); Guard("dev fill", DevFill.Register); Guard("skins", delegate { Skins.Load(); Settings.SkinChests.SettingChanged += delegate { if (Live) { Skins.Refresh(); } }; }); Guard("perf overlay", delegate { Settings.PerfOverlay.SettingChanged += delegate { Overlay(); }; Overlay(); }); onSync = delegate { if (Live) { Settings.ApplyToPieces(); } }; onConfig = delegate { if (Live) { Settings.ApplyToPieces(); } }; SynchronizationManager.OnConfigurationSynchronized += onSync; ((BaseUnityPlugin)this).Config.SettingChanged += onConfig; if (Object.op_Implicit((Object)(object)ObjectDB.instance)) { AddPieces(); } void Overlay() { if (Object.op_Implicit((Object)(object)this)) { PerfOverlay component = ((Component)this).GetComponent<PerfOverlay>(); if (Settings.PerfOverlay.Value && !Object.op_Implicit((Object)(object)component)) { ((Component)this).gameObject.AddComponent<PerfOverlay>(); } else if (!Settings.PerfOverlay.Value && Object.op_Implicit((Object)(object)component)) { Object.Destroy((Object)(object)component); } } } } private void ShowToVersionCheck() { PluginInfo info = ((BaseUnityPlugin)this).Info; if ((Object)(object)info.Instance != (Object)(object)this) { AccessTools.Property(typeof(PluginInfo), "Instance").SetValue(info, this); } Chainloader.PluginInfos["com.joostliebregts.storehouse"] = info; FieldInfo fieldInfo = AccessTools.Field(typeof(BepInExUtils), "Plugins"); if (fieldInfo.GetValue(null) is BaseUnityPlugin[] source) { fieldInfo.SetValue(null, source.Where(delegate(BaseUnityPlugin p) { if (Object.op_Implicit((Object)(object)p)) { PluginInfo info2 = p.Info; object obj; if (info2 == null) { obj = null; } else { BepInPlugin metadata = info2.Metadata; obj = ((metadata != null) ? metadata.GUID : null); } return (string?)obj != "com.joostliebregts.storehouse"; } return false; }).Append((BaseUnityPlugin)(object)this).ToArray()); } Log.LogInfo((object)$"Version check: this live copy counts as Storehouse {info.Metadata.Version}; a server with any 0.8 lets it in."); } private void TryBegin() { object? data = AppDomain.CurrentDomain.GetData("com.joostliebregts.storehouse.live"); Object val = (Object)((data is Object) ? data : null); PluginInfo value; bool flag = Chainloader.PluginInfos.TryGetValue("com.jotunn.jotunn", out value) && Object.op_Implicit((Object)(object)value.Instance); if ((Object.op_Implicit(val) && val != (Object)(object)this) || !flag) { if (Time.realtimeSinceStartup >= nextWaitLog) { nextWaitLog = Time.realtimeSinceStartup + 5f; Log.LogInfo((object)((!flag) ? "Waiting for Jotunn." : "Waiting for the previous Storehouse copy to leave.")); } return; } try { Begin(); } catch (Exception arg) { failed = true; Live = false; Log.LogError((object)$"Live reload: could not hook in; this copy stays idle until the next build ({arg})."); } } private void OnDestroy() { Application.quitting -= onQuit; ModelWatch.Stop(); LoadWarning component = ((Component)this).GetComponent<LoadWarning>(); if (component != null) { Object.Destroy((Object)(object)component); } if (!started) { return; } Live = false; try { Patches.RestoreGrid(); } catch (Exception ex) { Log.LogWarning((object)("Storehouse: container grid not restored (" + ex.Message + ").")); } if (AppDomain.CurrentDomain.GetData("com.joostliebregts.storehouse.live") == this) { AppDomain.CurrentDomain.SetData("com.joostliebregts.storehouse.live", null); } PrefabManager.OnVanillaPrefabsAvailable -= AddPieces; PieceManager.OnPiecesRegistered -= HammerOrder; if (onSync != null) { SynchronizationManager.OnConfigurationSynchronized -= onSync; } if (onConfig != null) { ((BaseUnityPlugin)this).Config.SettingChanged -= onConfig; } Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } if (!quitting) { try { Reload.Teardown(); } catch (Exception arg) { Log.LogError((object)$"Live reload: cleanup failed ({arg})."); } Log.LogInfo((object)"Storehouse copy unloaded (live reload)."); } } private static void Guard(string step, Action run) { try { run(); } catch (Exception arg) { Failures.Add(step); Log.LogError((object)string.Format("Storehouse {0}: start-up step '{1}' failed; the pieces stay registered, this part is off until the next start ({2}).", "0.8.8", step, arg)); } } private void Update() { if (!started) { TryBegin(); } else if (!failed) { long timestamp = Stopwatch.GetTimestamp(); Perf.Frame(); try { Tick(); } finally { Perf.Add(timestamp); } } } private void LateUpdate() { if (!started || failed) { return; } long timestamp = Stopwatch.GetTimestamp(); try { StoreContents.DrawAll(); } finally { Perf.Add(timestamp); Perf.EndFrame(); } } private void Tick() { if (Time.unscaledTime >= nextConfigCheck) { nextConfigCheck = Time.unscaledTime + 1f; DateTime dateTime = (File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath) ? File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath) : default(DateTime)); if (configStamp == default(DateTime)) { configStamp = dateTime; } else if (dateTime != configStamp) { configStamp = dateTime; ((BaseUnityPlugin)this).Config.Reload(); } } if (!(Time.time < nextModelCheck)) { nextModelCheck = Time.time + 1f; if (Settings.LiveModels.Value && ModelWatch.Changed()) { long timestamp = Stopwatch.GetTimestamp(); CribModel.CheckForChanges(); StoreModel.CheckAllForChanges(); Perf.LiveCheck(timestamp); } Icons.Tick(); } } private static void Unofficial(Dictionary<string, string> words) { string key = Kinds.All.First((Kind k) => k == Kinds.Crib || Kinds.InRelease(k)).Token + "_desc"; if (words.TryGetValue(key, out var value) && !value.EndsWith("Unofficial mod, not affiliated with or endorsed by Iron Gate or Coffee Stain.", StringComparison.Ordinal)) { words[key] = value + "\n\nUnofficial mod, not affiliated with or endorsed by Iron Gate or Coffee Stain."; } } private static void AddLocalization() { Dictionary<string, string> dictionary = new Dictionary<string, string> { ["$item_storehouse_board"] = "Board", ["$item_storehouse_board_desc"] = "A plank split and planed from a log. What furniture and storage are built from.", [Kinds.Strongbox.Token] = "Hauler's Strongbox", [Kinds.Strongbox.Token + "_desc"] = "A black metal chest built for a cart, not a doorway. Holds twice what a black metal chest does. Stands on level ground or a floor.", [Kinds.Crib.Token] = "Timber Crib", [Kinds.Crib.Token + "_desc"] = "An open crib of logs and iron-banded beams. Holds wood only: wood, fine wood, core wood, ancient bark, yggdrasil wood, ashwood, boards. Stands on level ground or a floor.", [Kinds.Bin.Token] = "Ore Bin", [Kinds.Bin.Token + "_desc"] = "A stone-lined bin with an iron rim. Holds ore, scrap, stone, coal and metal bars only. Stands on level ground or a floor." }; Unofficial(dictionary); CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); string key3 = "English"; localization.AddTranslation(ref key3, dictionary); Dictionary<string, string> dictionary2 = new Dictionary<string, string>(); Kind[] all = Kinds.All; foreach (Kind kind in all) { if (kind.Desc != null) { dictionary2[kind.Token] = kind.Shown ?? kind.Title; dictionary2[kind.Token + "_desc"] = kind.Desc; } } Unofficial(dictionary2); CustomLocalization localization2 = LocalizationManager.Instance.GetLocalization(); key3 = "English"; localization2.AddTranslation(ref key3, dictionary2); object? obj = AccessTools.Field(typeof(Localization), "m_instance")?.GetValue(null); Localization val = (Localization)((obj is Localization) ? obj : null); if (val == null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(Localization), "AddWord", new Type[2] { typeof(string), typeof(string) }, (Type[])null); if ((object)methodInfo == null) { return; } foreach (KeyValuePair<string, string> item in dictionary.Concat(dictionary2)) { methodInfo.Invoke(val, new object[2] { item.Key.TrimStart('$'), item.Value }); } } private void AddPieces() { PrefabManager.OnVanillaPrefabsAvailable -= AddPieces; Guard("board", Board.Add); if (Settings.DumpPrefabs.Value && !Object.op_Implicit((Object)(object)PrefabManager.Instance.GetPrefab(Kinds.Crib.Prefab))) { Guard("prefab dump", delegate { Inspect.Dump(PrefabManager.Instance.GetPrefab(Kinds.Crib.Base), "vanilla base of the Timber Crib"); }); } int num = 0; Kind[] all = Kinds.All; foreach (Kind kind in all) { try { AddPiece(kind); } catch (Exception arg) { Log.LogError((object)$"{kind.Title}: not added ({arg})"); } if (Object.op_Implicit((Object)(object)PrefabManager.Instance.GetPrefab(kind.Prefab))) { num++; } else { Failures.Add("piece " + kind.Title); } } PieceManager.OnPiecesRegistered += HammerOrder; Guard("hammer order", HammerOrder); Guard("armory sort", delegate { Mannequins.HookSort(harmony); }); try { Reload.AfterPieces(); } catch (Exception arg2) { Log.LogError((object)$"Live reload: the world's pieces were not all redressed ({arg2})."); } string text = ""; if (FromScripts) { text = " (ScriptEngine copy " + typeof(Plugin).Assembly.GetName().Name + ")"; } Log.LogInfo((object)(string.Format("Storehouse {0}: live, sha256 {1}, {2}/{3} pieces, {4} start-up failure(s)", "0.8.8", DllSha(), num, Kinds.All.Length, Failures.Count) + ((Failures.Count > 0) ? (": " + string.Join("; ", Failures)) : "") + text + ".")); if (!Headless && !Object.op_Implicit((Object)(object)((Component)this).GetComponent<LoadWarning>())) { ((Component)this).gameObject.AddComponent<LoadWarning>().Set(num, Kinds.All.Length); } } internal static void HammerOrder() { PieceTable pieceTable = PieceManager.Instance.GetPieceTable(PieceTables.Hammer); if (Object.op_Implicit((Object)(object)pieceTable)) { HashSet<string> ours = new HashSet<string>(Kinds.All.Select((Kind k) => k.Prefab), StringComparer.Ordinal); List<GameObject> pieces = pieceTable.m_pieces; int num = pieces.FindIndex((GameObject p) => Object.op_Implicit((Object)(object)p) && ours.Contains(((Object)p).name)); if (num < 0) { num = pieces.Count; } pieces.RemoveAll((GameObject p) => Object.op_Implicit((Object)(object)p) && ours.Contains(((Object)p).name)); Kind[] all = Kinds.All; pieces.InsertRange(Math.Min(num, pieces.Count), from k in all select PrefabManager.Instance.GetPrefab(k.Prefab) into p where Object.op_Implicit((Object)(object)p) select p); if (Object.op_Implicit((Object)(object)Player.m_localPlayer)) { AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null)?.Invoke(Player.m_localPlayer, null); } } } private string DllSha() { try { string path = (FromScripts ? Path.Combine(Paths.BepInExRootPath, "scripts", "Storehouse.dll") : ((BaseUnityPlugin)this).Info.Location); using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(path); return BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "").ToLowerInvariant() .Substring(0, 12); } catch (Exception ex) { Log.LogWarning((object)("Storehouse: could not hash its own DLL (" + ex.Message + ").")); return "unknown"; } } private void AddPiece(Kind kind) { //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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown GameObject prefab = PrefabManager.Instance.GetPrefab(kind.Prefab); if (prefab != null) { Reload.Rebuild(prefab, kind); return; } PieceConfig val = new PieceConfig { Name = kind.Token, Description = kind.Token + "_desc", PieceTable = PieceTables.Hammer, Category = "Storage", CraftingStation = (kind.Station?.Value ?? kind.DefaultStation) }; foreach (var (text, num) in Settings.ParseRecipe(kind.Recipe?.Value ?? kind.DefaultRecipe)) { val.AddRequirement(new RequirementConfig(text, num, 0, true)); } CustomPiece val2 = new CustomPiece(kind.Prefab, kind.Base, val); if ((Object)(object)val2.PiecePrefab == (Object)null) { Log.LogError((object)("Could not clone " + kind.Base + "; the " + kind.Title + " is not added.")); return; } GameObject piecePrefab = val2.PiecePrefab; Piece piece = val2.Piece; Container val3 = Configure(piecePrefab, piece, kind); if (!Object.op_Implicit((Object)(object)val3)) { Log.LogError((object)(kind.Base + " has no Container; the " + kind.Title + " is not added.")); return; } kind.Build(piecePrefab, val3); piece.m_icon = Icons.Render(piecePrefab, kind) ?? piece.m_icon; PieceManager.Instance.AddPiece(val2); Reload.RegisterLate(piecePrefab, kind); if (Settings.DumpPrefabs.Value) { Inspect.Dump(piecePrefab, kind.Title + " (built)"); } Log.LogInfo((object)$"{kind.Title}: piece added ({kind.Width}x{kind.Height})."); } internal static Container Configure(GameObject go, Piece p, Kind kind) { p.m_comfort = 0; p.m_notOnFloor = false; switch (kind.Placement) { case Placement.Ground: p.m_groundOnly = false; p.m_notOnTiltingSurface = true; break; case Placement.Wall: p.m_groundOnly = false; p.m_notOnFloor = true; p.m_notOnTiltingSurface = false; break; default: p.m_groundOnly = false; break; } Container componentInChildren = go.GetComponentInChildren<Container>(true); if (!Object.op_Implicit((Object)(object)componentInChildren)) { return null; } componentInChildren.m_name = kind.Token; componentInChildren.m_width = kind.Width; componentInChildren.m_height = kind.Height; return componentInChildren; } } internal enum Placement { Ground, Normal, Wall } internal sealed class Kind { public string Title; public string Prefab; public string Token; public string Base; public string Section; public string DefaultRecipe; public string DefaultStation; public string DefaultAccepts; public string FilterLabel; public string Shown; public HashSet<string> Never; public string NeverWhy; public string Desc; public string Slug; public string Piece; public string[] Rounds; public bool Display; public bool Mirror; public bool Heap; public bool Homes; public Action<GameObject> Dress; public int Planks; public int DefaultRows; public Placement Placement; public ConfigEntry<int> Rows; public ConfigEntry<float> ItemSize; public ConfigEntry<Color> GlassColour; public ConfigEntry<string> Model; public ConfigEntry<string> Recipe; public ConfigEntry<string> Station; public ConfigEntry<string> Accepts; public Action<GameObject, Container> Build; public Func<ItemData, bool> Rule; public Filter Filter; public int Columns; public string Spoken { get { string text = Shown ?? Title; int num = text.IndexOf(", ", StringComparison.Ordinal); if (num <= 0) { return text; } return text.Substring(num + 2) + " " + text.Substring(0, num); } } public int Width { get { if (Planks <= 0) { if (Columns <= 0) { return 8; } return Columns; } return Settings.SlotsPerPlank.Value; } } public int Height { get { if (Planks <= 0) { return Rows?.Value ?? TypedSlots.Rows(this, Width) ?? DefaultRows; } return Planks; } } } internal static class Kinds { public const int Columns = 8; public const int MinRows = 1; public static readonly Kind Strongbox = new Kind { Title = "Hauler's Strongbox", Prefab = "piece_storehouse_strongbox", Token = "$piece_storehouse_strongbox", Base = "piece_chest_blackmetal", Section = "1 - Haulers Strongbox", DefaultRows = 8, DefaultRecipe = "BlackMetal:12,Iron:8,FineWood:20", DefaultStation = "forge", Build = StrongboxModel.Build }; public static readonly Kind Crib = new Kind { Title = "Timber Crib", Prefab = "piece_storehouse_timbercrib", Token = "$piece_storehouse_timbercrib", Base = "piece_chest_wood", Section = "2 - Timber Crib", DefaultRows = 8, DefaultRecipe = "Wood:20,StorehouseBoard:10,BronzeNails:10", DefaultStation = "piece_workbench", DefaultAccepts = "Wood,FineWood,RoundLog,ElderBark,YggdrasilWood,Blackwood,Frostwood,Root,WrithanRoots,BarkaBranch,StorehouseBoard", FilterLabel = "Wood only", Build = CribModel.Build, Dress = delegate(GameObject go) { CribModel.Dress(go, quiet: true); } }; public static readonly Kind Bin = new Kind { Title = "Ore Bin", Prefab = "piece_storehouse_orebin", Token = "$piece_storehouse_orebin", Base = "piece_chest", Section = "3 - Ore Bin", DefaultRows = 6, DefaultRecipe = "Stone:30,Iron:6,Wood:10", DefaultStation = "piece_stonecutter", DefaultAccepts = "CopperOre,TinOre,IronScrap,IronOre,SilverOre,BlackMetalScrap,FlametalOre,FlametalOreNew,GoldOre,CopperScrap,BronzeScrap,Stone,Flint,Coal,Obsidian,Crystal,Grausten,BlackMarble,SulfurStone,Copper,Tin,Bronze,Iron,Silver,BlackMetal,Flametal,FlametalNew,Gold", FilterLabel = "Ore, stone and metal only", Build = Kitbash.OreBin }; private static readonly char[] notInSection = new char[8] { '=', '\n', '\t', '\\', '"', '\'', '[', ']' }; public static readonly Dictionary<string, string[]> ModelAlternates = new Dictionary<string, string[]>(StringComparer.Ordinal) { ["bombcrate"] = new string[1] { "_r2" }, ["fletcher"] = new string[4] { "_r3ds", "_r3cs", "_r3s", "_r2" }, ["ossuary"] = new string[3] { "_r5", "_r3", "_r4" }, ["weaponrack"] = new string[2] { "_r3", "_r1" }, ["weaponrackb"] = new string[2] { "_r3", "_r1" }, ["arsenal"] = new string[1] { "_r1" }, ["arsenalb"] = new string[3] { "_r4", "_r3", "_r1" }, ["utilitywall"] = new string[1] { "_r1" }, ["utilityisland"] = new string[1] { "_r1" }, ["armoryrail"] = new string[1] { "_r1" }, ["armoryrail3"] = new string[1] { "_r1" }, ["armoryisland"] = new string[1] { "_r2" }, ["armorcoffer"] = new string[2] { "_r2", "_r1" }, ["armorcabinet"] = new string[2] { "_r2", "_r1" }, ["armory"] = new string[1] { "_r1" }, ["armoryb"] = new string[2] { "_r2b", "_r1" }, ["arsenalkit_shield"] = new string[2] { "_r3", "_r4" }, ["arsenalkit_spine"] = new string[1] { "_r4" }, ["arsenalkit_long"] = new string[2] { "_r5b", "_r4" }, ["arrowtubes"] = new string[1] { "_r3" }, ["coopersrow"] = new string[2] { "_r10", "_r9" }, ["coopersrowstack"] = new string[2] { "_r3", "_r2" } }; public static readonly Kind[] Stores = new Kind[53] { Shown("Brewer's Cabinet", Store("meadrack", "Mead Rack", "A rack of shelves for bottles. Holds meads and potions only.", "Meads and potions only", ItemRules.Mead, "BarleyWine,BarleyWineBase,MeadBase*", 64, Placement.Normal, "StorehouseBoard:16,BronzeNails:10", "piece_workbench", null, null, display: false, 0, new string[2] { "_r3", "_r2" }, mirror: true)), Store("larder", "Larder Hutch", "A kitchen hutch for cooked food. Holds meals only.", "Meals only", ItemRules.Meal, "", 64, Placement.Normal, "StorehouseBoard:18,BronzeNails:12", "piece_workbench", null, null, display: true, 0, new string[5] { "_r6", "_r5", "_r4", "_r3", "_r2" }, mirror: true), Store("meatrack", "Meat Rack", "Hooks and bins for the day's catch. Holds meat and fish, raw or cooked, and the animal parts for food and meads.", "Meat and fish only", ItemRules.RawMeat, "RawMeat,DeerMeat,NeckTail,WolfMeat,LoxMeat,SerpentMeat,ChickenMeat,HareMeat,BugMeat,AsksvinMeat,VoltureMeat,BoneMawSerpentMeat,FishRaw,Bloodclot,Entrails", 64, Placement.Normal, "Wood:12,StorehouseBoard:10,BronzeNails:10", "piece_workbench"), Store("rootbin", "Produce Crates", "Crates for what the farm and the forest give. Holds vegetables, mushrooms, berries, herbs, seeds and eggs only.", "Produce only", ItemRules.Plant, "Turnip,Onion,Carrot,Barley,Flax,Thistle,Dandelion,Fiddleheadfern,*Seeds,Mushroom*,*berries,Raspberry,Cloudberry,Acorn,AncientSeed,FirCone,FirConeFrost,PineCone,FreshSeaweed", 72, Placement.Normal, "StorehouseBoard:14,BronzeNails:8", "piece_workbench", null, null, display: false, 0, new string[7] { "_r8", "_r7", "_r6", "_r5", "_r4", "_r3", "_r2" }), Store("orebin", "Ore Bin", "A timber bin for the mine's haul. Holds ore and scrap only. Stands on level ground or a floor.", "Ore and scrap only", ItemRules.Ore, "CopperOre,TinOre,IronScrap,IronOre,SilverOre,BlackMetalScrap,FlametalOre,FlametalOreNew,CopperScrap,BronzeScrap", 80, Placement.Ground, "Wood:12,StorehouseBoard:10,Stone:10,IronNails:8,Iron:4", "piece_workbench", null, "piece_storehouse_orebin"), Store("bunker", "Coal & Stone Bunker", "Three bays for loose heaps. Holds coal and stone only. Stands on level ground or a floor.", "Coal and stone only", ItemRules.StoneCoal, "Coal,Stone,Flint,Grausten,BlackMarble,Obsidian,SulfurStone,StoneRock,PungentPebbles,BFP_Clay,Ice,Crystal", 90, Placement.Ground, "Wood:16,StorehouseBoard:12,IronNails:8,Iron:4", "piece_workbench"), Store("bunkeropen", "Open Coal & Stone Bunker", "The bunker without a load-bearing top: three open bays under a leaning back wall. Holds coal and stone only; nothing stacks on it.", "Coal and stone only", ItemRules.StoneCoal, "Coal,Stone,Flint,Grausten,BlackMarble,Obsidian,SulfurStone,StoneRock,PungentPebbles,BFP_Clay,Ice,Crystal", 90, Placement.Ground, "Wood:14,StorehouseBoard:10,IronNails:6,Iron:2", "piece_workbench", null, null, display: false, 0, new string[1] { "_r1" }), Store("metalrack", "Metal Rack (tall)", "A tall timber rack for bars, iron-strapped. Holds metal bars only. Stacks.", "Metal bars only", ItemRules.Metal, "Copper,Tin,Bronze,Iron,Silver,BlackMetal,Flametal,FlametalNew", 64, Placement.Ground, "Wood:12,StorehouseBoard:12,IronNails:8,Iron:4", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3", "_r2", "_r1" }, mirror: true), Store("metalrackhalf", "Metal Rack (tall, half)", "A tall timber rack for bars, half as wide. Holds metal bars only. Stacks.", "Metal bars only", ItemRules.Metal, "Copper,Tin,Bronze,Iron,Silver,BlackMetal,Flametal,FlametalNew", 32, Placement.Ground, "Wood:8,StorehouseBoard:8,IronNails:6,Iron:2", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }, mirror: true, 4), Store("metalracklow", "Metal Rack (low)", "A low timber rack for bars. Holds metal bars only. Stacks.", "Metal bars only", ItemRules.Metal, "Copper,Tin,Bronze,Iron,Silver,BlackMetal,Flametal,FlametalNew", 32, Placement.Ground, "Wood:8,StorehouseBoard:8,IronNails:6,Iron:2", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }, mirror: true), Store("metalracklowhalf", "Metal Rack (low, half)", "A low timber rack for bars, half as wide. Holds metal bars only. Stacks.", "Metal bars only", ItemRules.Metal, "Copper,Tin,Bronze,Iron,Silver,BlackMetal,Flametal,FlametalNew", 16, Placement.Ground, "Wood:4,StorehouseBoard:4,IronNails:4,Iron:1", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }, mirror: true, 4), Store("metalpallet", "Metal Pallet", "A low pallet for stacked bars. Holds metal bars only. Stacks.", "Metal bars only", ItemRules.Metal, "Copper,Tin,Bronze,Iron,Silver,BlackMetal,Flametal,FlametalNew", 64, Placement.Ground, "Wood:16,StorehouseBoard:10,IronNails:8,Iron:4", "piece_workbench", null, null, display: false, 0, new string[4] { "_r4", "_r3", "_r2", "_r1" }, mirror: false, 0, heap: true), Store("coopersrow", "Cooper's Row", "Barrels, pots and tubs for sticky goods: tar, resin, sap, guck, ooze and powder, one kind per vessel.", "Tar, resin, sap and goo only", null, "Tar,Resin,Sap,Guck,Ooze,RoyalJelly,Honey,CharcoalResin,OozeMork,HatefulBlood,Voidplasm,BlobVial,Ectoplasm,ProustitePowder", 64, Placement.Ground, "Wood:10,StorehouseBoard:12,IronNails:6,Iron:2", "piece_workbench", null, null, display: false, 0, new string[9] { "_r11", "_r10", "_r9", "_r6", "_r5", "_r4", "_r3", "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("coopersrowstack", "Stacking Cooper's Row", "Cooper's Row in a six-post frame under a plank deck: the same vessels, room above to pour, and another one stacks on top.", "Tar, resin, sap and goo only", null, "Tar,Resin,Sap,Guck,Ooze,RoyalJelly,Honey,CharcoalResin,OozeMork,HatefulBlood,Voidplasm,BlobVial,Ectoplasm,ProustitePowder", 64, Placement.Ground, "Wood:16,StorehouseBoard:18,IronNails:10,Iron:4", "piece_workbench", null, null, display: false, 0, new string[4] { "_r4", "_r3", "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("fletcher", "Fletcher's Barrels", "Barrels for arrows, tubs for bolts and a straw crate for bombs, one kind per holder.", "Arrows, bolts and bombs only", ItemRules.ArrowOrBolt, "Arrow*,Bolt*,Bomb*,VA*Arrow*,VA*arrow*,VA*Bolt*,VA*bolt*,TurretBolt*,Catapult_*,FireworksRocket_*,Sparkler", 64, Placement.Ground, "StorehouseBoard:14,BronzeNails:10,LeatherScraps:4", "piece_workbench", null, null, display: false, 0, new string[7] { "_r3es", "_r3ds", "_r3cs", "_r3s", "_r3", "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("treasure", "Treasure Hoard", "An iron-bound coffer heaped with coins and gold. Gems and jewellery go in the Jeweller's Casket. Open the inventory for any stored kinds without a display place.", "Coins and gold only", ItemRules.CoinOrGold, "Coins,AncientCoin,Gold", 96, Placement.Ground, "StorehouseBoard:12,IronNails:8,Iron:4,DeerHide:2", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("apothecary", "Apothecary Rack", "Jars, crocks, bags and baskets on a standing rack for the small things creatures leave behind, one kind per vessel.", "Creature parts only", null, "Feathers,Needle,GreydwarfEye,GiantBloodSack,AskBladder,WolfFang,Carapace,WolfHairBundle,ElakingHairBundle,Chitin,CelestialFeather,MooseSinew,BjornPaw,WolfClaw,MorgenHeart,Bilebag,MorgenSinew,Mandible,SerpentScale,BonemawSerpentScale,FreezeGland,MoleClaws", 64, Placement.Normal, "StorehouseBoard:12,BronzeNails:8,LeatherScraps:2", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3", "_r2", "_r1" }), Store("hiderack", "Hide Rack", "Hinged frames on an iron-banded post, one hide stretched in each, the folded hides piled in the bay below.", "Hides and leather only", null, "TrollHide,WolfPelt,LoxPelt,DeerHide,LeatherScraps,AskHide,ScaleHide,BjornHide,MooseHide,SealHide,Leatherstraps", 64, Placement.Ground, "Wood:10,StorehouseBoard:12,BronzeNails:8,LeatherScraps:4", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3", "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("clothrack", "Cloth Rack", "A narrow rack for cloth and thread: linen, jute and Norn thread, one kind per bay.", "Cloth and thread only", null, "LinenThread,JuteRed,JuteBlue,NornThread,CandleWick", 32, Placement.Normal, "StorehouseBoard:10,BronzeNails:6,LinenThread:2", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }, mirror: false, 4), Store("ossuary", "Ossuary Bin", "A stone-footed bin for bones: fragments, charred and withered bones, skulls and antlers, one kind per bay.", "Bones only", null, "BoneFragments,CharredBone,WitheredBone,Charredskull,HardAntler,AsksvinCarrionSkull,BonemawSerpentTooth,AsksvinCarrionNeck,AsksvinCarrionPelvic,AsksvinCarrionRibcage,UndeadBjornRibcage,LargeBone,GhostSkull", 128, Placement.Ground, "Wood:12,StorehouseBoard:10,Stone:16,IronNails:6,Iron:2", "piece_workbench", null, null, display: false, 0, new string[5] { "_r5b", "_r5", "_r3", "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("arrowtubes", "Arrow Tubes", "A stepped stand of tubes, one per kind of arrow, each with its painted band; bundles packed tight.", "Arrows only", ItemRules.Arrow, "Arrow*,VA*Arrow*,VA*arrow*", 80, Placement.Ground, "Wood:12,StorehouseBoard:14,BronzeNails:10,LeatherScraps:4", "piece_workbench", null, null, display: false, 0, new string[4] { "_r4", "_r3", "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("bolttubs", "Bolt Crate", "A stepped crate of square cells for handheld crossbow bolts, one kind per cell. Siege ammunition needs its own fitted storage.", "Crossbow bolts only", ItemRules.Bolt, "Bolt*,VA*Bolt*,VA*bolt*", 64, Placement.Ground, "Wood:10,StorehouseBoard:12,IronNails:8", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3", "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("bombcrate", "Bomb Crate", "A compact stepped crate of straw-bedded trays, one kind of bomb per tray, a reserve tier at the back and a bucket of sand at hand.", "Bombs only", null, "Bomb*", 64, Placement.Ground, "StorehouseBoard:14,IronNails:8,Iron:2", "piece_workbench", null, null, display: false, 0, new string[2] { "_r3", "_r2" }, mirror: false, 0, heap: false, homes: true), Shown("Tall Apothecary Rack", Store("apothecarytall", "Tall Apothecary Rack", "The Apothecary Rack two metres wide and as tall as a metal rack: forty places, each vessel following what it holds.", "Creature parts only", null, "Feathers,Needle,GreydwarfEye,GiantBloodSack,AskBladder,WolfFang,Carapace,WolfHairBundle,ElakingHairBundle,Chitin,CelestialFeather,MooseSinew,BjornPaw,WolfClaw,MorgenHeart,Bilebag,MorgenSinew,Mandible,SerpentScale,BonemawSerpentScale,FreezeGland,MoleClaws", 64, Placement.Normal, "StorehouseBoard:16,BronzeNails:10,LeatherScraps:2", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" })), Store("pantry", "Pantry Dresser", "A cook's dry reserve for flour, spices, herbs and processed ingredients. Genuine uncooked dishes are optional stock; prepared meals belong in the Larder.", "Dry cooking ingredients and uncooked food only", null, "SpiceForests,SpicePlains,SpiceMountains,SpiceOceans,SpiceMistlands,SpiceDeepNorth,SpiceAshlands,BarleyFlour,OatFlour,BreadDough,FragrantBundle,FeastAshlands_Material,FeastBlackforest_Material,FeastDeepNorth_Material,FeastMeadows_Material,FeastMistlands_Material,FeastMountains_Material,FeastOceans_Material,FeastPlains_Material,FeastSwamps_Material,BakedPoteitrUncooked,FishAndBreadUncooked,HoneyGlazedChickenUncooked,KaleChipsUncooked,LoxPieUncooked,MagicallyStuffedShroomUncooked,MeatPlatterUncooked,MisthareSupremeUncooked,OvenPancakeUncooked,PiquantPieUncooked,RoastedCrustPieUncooked,VikingCupcakeUncooked", 80, Placement.Normal, "StorehouseBoard:16,BronzeNails:10", "piece_workbench", null, null, display: false, 0, new string[3] { "_r4", "_r2", "_r1" }), Store("smithdrawers", "Smith's Drawers", "A chest of timber drawers pulled half open: nails, chain, rings, springs, axe heads and the smith's odds and ends.", "Smithing parts only", null, "BronzeNails,IronNails,Chain,BarrelRings,MechanicalSpring,AxeHead1,AxeHead2,ScytheHandle,CeramicPlate,CharredCogwheel,SharpeningStone,BellFragment", 64, Placement.Normal, "StorehouseBoard:12,Iron:4,IronNails:10", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("jeweller", "Jeweller's Casket", "A casket of lined trays for gems and jewellery, one kind per tray. Coins and gold go in the Treasure Hoard; open the inventory for stored kinds without a place.", "Gems and jewellery only", ItemRules.Jewel, "Amber,Ruby,AmberPearl,GemstoneBlue,GemstoneGreen,GemstoneRed,AncientGemstone*,SilverNecklace,CrownJewel,Pot_Shard*", 64, Placement.Normal, "StorehouseBoard:8,IronNails:6,LinenThread:4", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Store("embercabinet", "Ember Cabinet", "An iron lantern cabinet with a niche for each glowing thing: wisps, cores, embers, orbs and glow worms.", "Glowing things only", ItemRules.ArcaneCore, "Wisp,GlowWorm,FaderEmber,MemorialCoal,MoltenCore,BlackCore,FrostCore,ShieldCore,OrbFrostFire,OrbThunderBlood,SurtlingCore,FrozenFuel,Thunderstone,Eitr,DragonTear", 64, Placement.Normal, "StorehouseBoard:8,Iron:6,IronNails:6", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }, mirror: false, 0, heap: false, homes: true), Shown("Trophy Hall (80, first)", Store("trophyhall", "Trophy Hall", "A free-standing wall of boards, one trophy mounted on each.", "Trophies only", (ItemData item) => (int)item.m_shared.m_itemType == 13, "Trophy*", 80, Placement.Normal, "Wood:20,StorehouseBoard:24,IronNails:12", "piece_workbench", null, null, display: true, 0, new string[3] { "_r3", "_r2", "_r1" }, mirror: true, 10)), Shown("Trophy Hall", Store("trophyhall13x5", "Trophy Hall 13x5", "A long wall of boards, one trophy mounted on each, the bosses' too.", "Trophies only", (ItemData item) => (int)item.m_shared.m_itemType == 13, "Trophy*", 65, Placement.Normal, "Wood:18,StorehouseBoard:22,IronNails:11", "piece_workbench", null, null, display: true, 0, new string[1] { "_r1" }, mirror: true, 13)), Store("trophyhall10x7", "Tall Trophy Hall", "A tall wall of boards, one trophy mounted on each, the bosses' too.", "Trophies only", (ItemData item) => (int)item.m_shared.m_itemType == 13, "Trophy*", 70, Placement.Normal, "Wood:18,StorehouseBoard:24,IronNails:12", "piece_workbench", null, null, display: true, 0, new string[1] { "_r1" }, mirror: true, 10), Store("armorcoffer", "Armor Coffer", "An open reserve coffer for armor: helmets in their trays, chest, legs and capes folded in piles. Shows every piece it holds.", "Armor and trinkets only", EquipRules.Armor, "", 64, Placement.Normal, "StorehouseBoard:16,IronNails:10,Iron:2", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3", "_r2", "_r1" }), Store("armorcabinet", "Armor Cabinet", "A tall coffer cabinet for armor: helmets in cubbies, the rest folded in piles. Shows every piece it holds.", "Armor and trinkets only", EquipRules.Armor, "", 64, Placement.Normal, "StorehouseBoard:20,IronNails:12,Iron:4", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3", "_r2", "_r1" }), Store("weaponrack", "Weapon Rack A", "A narrow rack for eight one-handed weapons at their true size. Put several side by side.", "One-handed weapons only", EquipRules.RackWeapon, "", 8, Placement.Normal, "StorehouseBoard:6,IronNails:4", "piece_workbench", null, null, display: false, 0, new string[4] { "_r4", "_r3", "_r2", "_r1" }), Store("weaponrackb", "Weapon Rack B", "The narrow weapon rack in a second build: eight one-handed weapons at their true size.", "One-handed weapons only", EquipRules.RackWeapon, "", 8, Placement.Normal, "StorehouseBoard:6,IronNails:4", "piece_workbench", null, null, display: false, 0, new string[4] { "_r4", "_r3", "_r2", "_r1" }), Store("arsenal", "Arsenal A", "Weapons and shields: one-handed and long weapons, bows, reserve shields edge-on and the best shield on show.", "Weapons, tools and shields only", EquipRules.Arsenal, "", 64, Placement.Normal, "StorehouseBoard:16,IronNails:10,Iron:4", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }), Shown("Arsenal", Store("arsenalb", "Arsenal B", "Weapons, bows, reserve shields edge-on and the best shield on show.", "Weapons, tools and shields only", EquipRules.Arsenal, "", 32, Placement.Normal, "StorehouseBoard:18,IronNails:10,Iron:4", "piece_workbench", null, null, display: false, 0, new string[5] { "_r6", "_r4", "_r3", "_r2", "_r1" })), Store("arsenalkit_spine", "Arsenal Spine", "A trestle spine with arms on both faces: swords, knives, axes and maces hung by their heads in three tiers. Shield troughs snap to its sides, the long and bow racks to its ends.", "One-handed swords, knives, axes and maces only", EquipRules.KitSpine, "", 56, Placement.Normal, "StorehouseBoard:20,IronNails:12,Iron:4", "piece_workbench", null, null, display: false, 0, new string[2] { "_r5", "_r4" }), Store("arsenalkit_shield", "Arsenal Shield Trough", "A low trough with a high back: shields lean in their own heel slots, the largest in the turned end bay. Snaps to a side of the Arsenal Spine.", "Shields only", EquipRules.KitShield, "", 10, Placement.Normal, "StorehouseBoard:12,IronNails:8", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3b", "_r3", "_r4" }, mirror: false, 5), Store("arsenalkit_long", "Arsenal Long Rack", "A straw-filled box with an upper guide: spears, atgeirs, sledges, battleaxes and greatswords standing upright. Snaps to an end of the Arsenal Spine.", "Spears and two-handed weapons only", EquipRules.KitLong, "", 12, Placement.Normal, "StorehouseBoard:12,IronNails:8", "piece_workbench", null, null, display: false, 0, new string[4] { "_r5c", "_r5b", "_r5", "_r4" }, mirror: false, 6), Store("arsenalkit_bow", "Arsenal Bow Rack", "An A-frame end cap: six bows in skinny slots and two crossbows across their pegs. Snaps to an end of the Arsenal Spine.", "Bows and crossbows only", EquipRules.KitBow, "", 8, Placement.Normal, "StorehouseBoard:8,IronNails:6", "piece_workbench", null, null, display: false, 0, new string[2] { "_r4", "_r5" }), Store("armory", "Armory A", "Armor, weapons and shields together: armor folded in the coffer, weapons racked, the best shield on show.", "Armor, weapons and shields only", EquipRules.Combined, "", 64, Placement.Normal, "StorehouseBoard:24,IronNails:14,Iron:6", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }), Shown("Armory", Store("armoryb", "Armory B", "Reachable from both sides: armor, weapons and shields together.", "Armor, weapons and shields only", EquipRules.Combined, "", 64, Placement.Normal, "StorehouseBoard:24,IronNails:14,Iron:6", "piece_workbench", null, null, display: false, 0, new string[4] { "_r2c", "_r2b", "_r2", "_r1" })), Store("armoryrail", "Armory Rail", "A rail of carved wooden mannequins, each wearing a full set of armor with its cape, weapon and shield, ready to grab.", "Armor, one-handed weapons and shields only", EquipRules.Mannequin, "", 36, Placement.Normal, "StorehouseBoard:18,IronNails:10,Iron:4", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3b", "_r3", "_r1" }, mirror: false, 6), Store("armoryisland", "Armory Island", "A two-sided island of carved wooden mannequins, each wearing a full set of armor with its cape, weapon and shield.", "Armor, one-handed weapons and shields only", EquipRules.Mannequin, "", 36, Placement.Normal, "StorehouseBoard:20,IronNails:12,Iron:4", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3", "_r2", "_r1" }, mirror: false, 6), Store("armoryrail3", "Short Armory Rail", "A short rail of three carved wooden mannequins, each wearing a full set of armor with its cape, weapon and shield.", "Armor, one-handed weapons and shields only", EquipRules.Mannequin, "", 18, Placement.Normal, "StorehouseBoard:10,IronNails:6,Iron:2", "piece_workbench", null, null, display: false, 0, new string[3] { "_r3b", "_r3", "_r1" }, mirror: false, 3), Store("armorstand", "Armor Stand", "A carved wooden mannequin on its own deck, wearing a full set of armor with its cape, weapon and shield.", "Armor, one-handed weapons and shields only", EquipRules.Mannequin, "", 6, Placement.Normal, "StorehouseBoard:5,IronNails:3,Iron:1", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }, mirror: false, 1), Store("armorstandopen", "Armor Stand, open", "A carved wooden mannequin on its own open deck, without the back rail, wearing a full set of armor with its cape, weapon and shield. Snaps to other stands and the armory rails.", "Armor, one-handed weapons and shields only", EquipRules.Mannequin, "", 6, Placement.Normal, "StorehouseBoard:5,IronNails:3,Iron:1", "piece_workbench", null, null, display: false, 0, new string[1] { "_r2" }, mirror: false, 1), Store("utilitywall", "Utility Wall", "Shelves and sockets for tools, belts and wearables, trinkets, and a saddle on the deep bay floor.", "Tools, utility items, trinkets and saddles only", EquipRules.Utility, "Saddle*", 64, Placement.Normal, "StorehouseBoard:14,IronNails:8,Iron:2", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }), Store("utilityisland", "Utility Island", "A low island table for tools, wearables, trinkets and two saddles on its top.", "Tools, utility items, trinkets and saddles only", EquipRules.Utility, "Saddle*", 64, Placement.Normal, "StorehouseBoard:12,IronNails:8,Iron:2", "piece_workbench", null, null, display: false, 0, new string[2] { "_r2", "_r1" }), Shelf(1, "StorehouseBoard:5,BronzeNails:4"), Shelf(2, "StorehouseBoard:10,BronzeNails:8"), Shelf(3, "StorehouseBoard:15,BronzeNails:12") }; public static readonly HashSet<string> Released = new HashSet<string>(StringComparer.Ordinal) { "meadrack", "larder", "meatrack", "rootbin", "orebin", "bunker", "bunkeropen", "metalrack", "metalrackhalf", "metalracklow", "metalracklowhalf", "metalpallet", "coopersrow", "coopersrowstack", "fletcher", "treasure", "apothecary", "hiderack", "clothrack", "ossuary", "emergencyshelf" }; public static readonly HashSet<string> NextRelease = new HashSet<string>(StringComparer.Ordinal) { "armoryrail", "armoryrail3", "armoryisland", "armorstand", "weaponrack", "weaponrackb", "bolttubs", "bombcrate", "arrowtubes", "apothecarytall", "arsenalb", "armoryb", "embercabinet", "pantry", "smithdrawers", "jeweller", "trophyhall13x5", "trophyhall10x7", "arsenalkit_spine", "arsenalkit_shield", "arsenalkit_long", "arsenalkit_bow", "armorstandopen", "armorcoffer", "armorcabinet" }; public static readonly (string Name, string[] Slugs)[] Groups = new(string, string[])[10] { ("Wood, ore and stone", new string[4] { "timbercrib", "orebin", "bunker", "bunkeropen" }), ("Metal", new string[5] { "metalrack", "metalrackhalf", "metalracklow", "metalracklowhalf", "metalpallet" }), ("Kitchen", new string[5] { "meadrack", "larder", "meatrack", "rootbin", "pantry" }), ("Crafting goods", new string[9] { "coopersrow", "coopersrowstack", "apothecary", "apothecarytall", "hiderack", "clothrack", "ossuary", "smithdrawers", "embercabinet" }), ("Ammunition", new string[4] { "fletcher", "arrowtubes", "bolttubs", "bombcrate" }), ("Weapons", new string[7] { "weaponrack", "weaponrackb", "arsenalb", "arsenalkit_spine", "arsenalkit_shield", "arsenalkit_long", "arsenalkit_bow" }), ("Armor", new string[8] { "armoryrail", "armoryrail3", "armoryisland", "armorstand", "armorstandopen", "armorcabinet", "armorcoffer", "armoryb" }), ("Trophies", new string[2] { "trophyhall13x5", "trophyhall10x7" }), ("Valuables", new string[2] { "treasure", "jeweller" }), ("Shelves", new string[1] { "emergencyshelf" }) }; public static readonly Kind[] All = new Kind[1] { Crib }.Concat(Stores.Where(InRelease)).OrderBy(Rank).ToArray(); private static string SectionSafe(string title) { return new string(title.Where((char c) => Array.IndexOf(notInSection, c) < 0).ToArray()); } private static Kind Store(string slug, string title, string desc, string label, Func<ItemData, bool> rule, string accepts, int units, Placement placement, string recipe, string station, string piece = null, string prefab = null, bool display = false, int planks = 0, string[] rounds = null, bool mirror = false, int columns = 0, bool heap = false, bool homes = false) { string text = ((piece == null) ? slug : (slug + "_" + piece)); if (prefab == null) { prefab = "piece_storehouse_" + text; } return new Kind { Title = title, Prefab = prefab, Token = "$" + prefab, Base = "piece_chest_wood", Section = "4 - " + SectionSafe(title), Slug = slug, Piece = piece, Desc = desc, FilterLabel = label, Rule = rule, DefaultAccepts = accepts, DefaultRows = (units + ((columns > 0) ? columns : 8) - 1) / ((columns > 0) ? columns : 8), Columns = columns, Placement = placement, DefaultRecipe = recipe, DefaultStation = station, Display = display, Planks = planks, Rounds = rounds, Mirror = mirror, Heap = heap, Homes = homes, Build = delegate(GameObject go, Container container) { StoreModel.Get(slug, piece, title).BuildPiece(go); }, Dress = delegate(GameObject go) { StoreModel.Get(slug, piece, title).BuildPiece(go, quiet: true); } }; } private static Kind Shown(string name, Kind k) { k.Shown = name; return k; } private static Kind Never(HashSet<string> refused, string why, Kind k) { k.Never = refused; k.NeverWhy = why; return k; } private static Kind Shelf(int planks, string recipe) { return Store("emergencyshelf", string.Format("Shelf ({0} plank{1})", planks, (planks > 1) ? "s" : ""), "A 2 m wall shelf with " + planks switch { 2 => "two planks", 1 => "one plank", _ => "three planks", } + " for what you grab in a hurry. Holds anything; only what fits on the planks shows. Hangs on a wall.", null, null, null, 0, Placement.Wall, recipe, "piece_workbench", planks.ToString(CultureInfo.InvariantCulture), null, display: true, planks, new string[2] { "_r5", "_r4" }); } public static bool Buildable(Kind k) { if (k != Crib) { return InRelease(k); } return true; } public static bool InRelease(Kind k) { if (k.Slug != null) { if (!Released.Contains(k.Slug)) { return NextRelease.Contains(k.Slug); } return true; } return false; } private static string GroupSlug(Kind k) { if (k != Crib) { return k.Slug; } return "timbercrib"; } public static string GroupOf(Kind k) { return Groups.FirstOrDefault(((string Name, string[] Slugs) g) => Array.IndexOf(g.Slugs, GroupSlug(k)) >= 0).Name; } private static int Rank(Kind k) { int num = 0; (string, string[])[] groups = Groups; for (int i = 0; i < groups.Length; i++) { string[] item = groups[i].Item2; for (int j = 0; j < item.Length; j++) { if (item[j] == GroupSlug(k)) { return num; } num++; } } return int.MaxValue; } public static Kind ByToken(string token) { if (token == null || token.Length < 10 || token[0] != '$') { return null; } for (int i = 0; i < All.Length; i++) { if (All[i].Token == token) { return All[i]; } } return null; } } internal static class Settings { public enum TooltipStyles { Sentence, Short, Custom } public static ConfigEntry<bool> DumpPrefabs; public static ConfigEntry<bool> LiveModels; public static ConfigEntry<bool> MatteFrames; public static ConfigEntry<bool> ShowLoadedLine; public static readonly Dictionary<string, ConfigEntry<float>> TrophyScale = new Dictionary<string, ConfigEntry<float>>(StringComparer.Ordinal); internal static readonly string[] TrophyKinds = "TrophyAbomination,TrophyAsksvin,TrophyBarka,TrophyBjorn,TrophyBjornUndead,TrophyBlob,TrophyBlob_Frost,TrophyBlob_Lava,TrophyBlob_Morkhalla,TrophyBoar,TrophyBonemass,TrophyBonemawSerpent,TrophyCharredArcher,TrophyCharredMage,TrophyCharredMelee,TrophyCultist,TrophyCultist_Hildir,TrophyDeathsquito,TrophyDeer,TrophyDeerWhite,TrophyDragonQueen,TrophyDraugr,TrophyDraugrElite,TrophyDraugrFem,TrophyDvergr,TrophyEikthyr,TrophyElaking,TrophyFader,TrophyFallenValkyrie,TrophyFenring,TrophyForestTroll,TrophyFrostTroll,TrophyGhost,TrophyGjall,TrophyGoblin,TrophyGoblinBrute,TrophyGoblinBruteBrosBrute,TrophyGoblinBruteBrosShaman,TrophyGoblinKing,TrophyGoblinShaman,TrophyGreydwarf,TrophyGreydwarfBrute,TrophyGreydwarfShaman,TrophyGrowth,TrophyHare,TrophyHatchling,TrophyJotunWarrior,TrophyJotunWitch,TrophyKvastur,TrophyLeech,TrophyLox,TrophyMole,TrophyMoose,TrophyMorgen,TrophyNeck,TrophySGolem,TrophySeal,TrophySeeker,TrophySeekerBrute,TrophySeekerQueen,TrophySerpent,TrophySkeleton,TrophySkeletonHildir,TrophySkeletonPoison,TrophySurtling,TrophyTheElder,TrophyTick,TrophyUlv,TrophyVolture,TrophyWolf,TrophyWraith,TrophyWrithan".Split(','); private static readonly Dictionary<string, float> TrophyDefaults = new Dictionary<string, float>(StringComparer.Ordinal) { ["TrophyAbomination"] = 1.25f, ["TrophyAsksvin"] = 1.1f, ["TrophyBarka"] = 1.5f, ["TrophyBjorn"] = 1.5f, ["TrophyBjornUndead"] = 1.5f, ["TrophyBlob_Frost"] = 0.9f, ["TrophyBlob_Lava"] = 0.9f, ["TrophyBlob_Morkhalla"] = 0.9f, ["TrophyBoar"] = 1.3f, ["TrophyBonemawSerpent"] = 1.2f, ["TrophyDeathsquito"] = 2f, ["TrophyDeer"] = 2.3f, ["TrophyDeerWhite"] = 2.3f, ["TrophyDragonQueen"] = 2.65f, ["TrophyDraugr"] = 1.5f, ["TrophyDraugrElite"] = 1.5f, ["TrophyDraugrFem"] = 1.5f, ["TrophyEikthyr"] = 3f, ["TrophyElaking"] = 1.4f, ["TrophyFader"] = 1.5f, ["TrophyFallenValkyrie"] = 1.2f, ["TrophyFenring"] = 1.4f, ["TrophyGjall"] = 1.7f, ["TrophyGoblinKing"] = 1.25f, ["TrophyGoblinShaman"] = 1.35f, ["TrophyGreydwarf"] = 1.15f, ["TrophyGreydwarfBrute"] = 1.15f, ["TrophyGreydwarfShaman"] = 1.65f, ["TrophyHare"] = 0.8f, ["TrophyHatchling"] = 1.7f, ["TrophyJotunWarrior"] = 1.6f, ["TrophyJotunWitch"] = 1.3f, ["TrophyKvastur"] = 1.35f, ["TrophyLeech"] = 0.95f, ["TrophyLox"] = 1.7f, ["TrophyMoose"] = 2.1f, ["TrophyMorgen"] = 0.9f, ["TrophyNeck"] = 1.1f, ["TrophySGolem"] = 1.3f, ["TrophySeal"] = 1.25f, ["TrophySeeker"] = 1.15f, ["TrophySeekerBrute"] = 1.7f, ["TrophySeekerQueen"] = 1.85f, ["TrophySerpent"] = 1.7f, ["TrophySkeleton"] = 0.8f, ["TrophySkeletonHildir"] = 0.85f, ["TrophySkeletonPoison"] = 0.9f, ["TrophySurtling"] = 0.6f, ["TrophyTheElder"] = 2.35f, ["TrophyTick"] = 0.75f, ["TrophyUlv"] = 2.6f, ["TrophyVolture"] = 1.9f, ["TrophyWolf"] = 0.94f }; private static readonly HashSet<string> GameBosses = new HashSet<string>(StringComparer.Ordinal) { "TrophyEikthyr", "TrophyTheElder", "TrophyBonemass", "TrophyDragonQueen", "TrophyGoblinKing", "TrophySeekerQueen", "TrophyFader" }; public static ConfigEntry<string> ExtraBosses; public static ConfigEntry<bool> StoreInTooltip; public static ConfigEntry<TooltipStyles> TooltipStyle; public static ConfigEntry<string> TooltipFormat; public static ConfigEntry<string> ModelFolder; public static ConfigEntry<bool> SkinChests; public static ConfigEntry<KeyboardShortcut> SkinKey; public static ConfigEntry<float> DisplaySize; public static ConfigEntry<int> SlotsPerPlank; public static ConfigEntry<bool> ShowContents; public static ConfigEntry<bool> ContentsShadows; public static ConfigEntry<bool> Instancing; public static ConfigEntry<bool> PerfLog; public static ConfigEntry<bool> PerfOverlay; public static ConfigEntry<bool> ManyKindBoards; public static ConfigEntry<bool> ContentsInShade; public static ConfigEntry<bool> PlacementCheck; public static ConfigEntry<float> MannequinTurn; public static ConfigEntry<float> ContentsDistance; public static ConfigEntry<int> MaxTrisPerStack; public static ConfigEntry<float> FarContents; internal static readonly Dictionary<string, string[]> PastAccepts = new Dictionary<string, string[]> { ["2 - Timber Crib"] = new string[2] { "Wood,FineWood,RoundLog,ElderBark,YggdrasilWood,Blackwood,Frostwood,StorehouseBoard,Root,WrithanRoots,BarkaBranch", "Wood,FineWood,RoundLog,ElderBark,YggdrasilWood,Blackwood,Frostwood" }, ["4 - Apothecary Rack"] = new string[2] { "Feathers,Needle,GreydwarfEye,GiantBloodSack,AskBladder,WolfFang,Carapace,WolfHairBundle,ElakingHairBundle,Chitin,CelestialFeather,MooseSinew,BjornPaw,WolfClaw,MorgenHeart,Bilebag,MorgenSinew,DragonTear,Mandible,SerpentScale,BonemawSerpentScale", "Feathers,Needle,GreydwarfEye,RoyalJelly,GiantBloodSack,Ectoplasm,AskBladder,WolfFang,Carapace,WolfHairBundle,ElakingHairBundle,Chitin,CelestialFeather,MooseSinew,BjornPaw,WolfClaw,MorgenHeart,Bilebag,SurtlingCore,MorgenSinew,DragonTear,Mandible,SerpentScale,BonemawSerpentScale" }, ["4 - Cloth Rack"] = new string[1] { "LinenThread,JuteRed,JuteBlue,NornThread" }, ["4 - Coal & Stone Bunker"] = new string[3] { "Coal,Stone,Flint,Grausten,BlackMarble,Obsidian,SulfurStone,StoneRock,PungentPebbles,BFP_Clay,Ice", "Coal,Stone,Flint,Grausten,BlackMarble,Obsidian,Crystal,SulfurStone,StoneRock,PungentPebbles,Thunderstone,BFP_Clay,FrozenFuel,Ice", "Coal,Stone,Flint,Grausten,BlackMarble,Obsidian,Crystal" }, ["4 - Coopers Row"] = new string[2] { "Tar,Resin,Sap,Guck,Eitr,Ooze,RoyalJelly,Honey,CharcoalResin,OozeMork,HatefulBlood,Voidplasm,BlobVial,Ectoplasm", "Tar,Resin,Sap,Guck,Eitr,Ooze" }, ["4 - Ember Cabinet"] = new string[3] { "Wisp,GlowWorm,FaderEmber,MemorialCoal,MoltenCore,BlackCore,FrostCore,ShieldCore,OrbFrostFire,OrbThunderBlood,SurtlingCore,FrozenFuel,Thunderstone,Crystal", "Wisp,GlowWorm,FaderEmber,MemorialCoal,MoltenCore,BlackCore,FrostCore,ShieldCore,OrbFrostFire,OrbThunderBlood,SurtlingCore,FrozenFuel,Thunderstone", "Wisp,GlowWorm,FaderEmber,MemorialCoal,MoltenCore,BlackCore,FrostCore,ShieldCore,OrbFrostFire,OrbThunderBlood,SurtlingCore" }, ["4 - Fletchers Barrels"] = new string[1] { "Arrow*,Bolt*,Bomb*,Dynamite" }, ["4 - Jewellers Casket"] = new string[1] { "Coins,Amber,Ruby,AmberPearl,GemstoneBlue,GemstoneGreen,GemstoneRed,AncientGemstone*,AncientCoin,SilverNecklace,CrownJewel,Pot_Shard*" }, ["4 - Mead Rack"] = new string[1] { "BarleyWine,MeadBase*" }, ["4 - Open Coal & Stone Bunker"] = new string[3] { "Coal,Stone,Flint,Grausten,BlackMarble,Obsidian,SulfurStone,StoneRock,PungentPebbles,BFP_Clay,Ice", "Coal,Stone,Flint,Grausten,BlackMarble,Obsidian,Crystal,SulfurStone,StoneRock,PungentPebbles,Thunderstone,BFP_Clay,FrozenFuel,Ice", "Coal,Stone,Flint,Grausten,BlackMarble,Obsidian,Crystal" }, ["4 - Ossuary Bin"] = new string[1] { "BoneFragments,CharredBone,WitheredBone,Charredskull,HardAntler,AsksvinCarrionSkull,BonemawSerpentTooth" }, ["4 - Produce Crates"] = new string[1] { "Turnip,Onion,Carrot,Barley,Flax,Thistle,Dandelion,Fiddleheadfern,*Seeds,Mushroom*,*berries,Raspberry,Cloudberry" }, ["4 - Stacking Coopers Row"] = new string[2] { "Tar,Resin,Sap,Guck,Eitr,Ooze,RoyalJelly,Honey,CharcoalResin,OozeMork,HatefulBlood,Voidplasm,BlobVial,Ectoplasm", "Tar,Resin,Sap,Guck,Eitr,Ooze" }, ["4 - Tall Apothecary Rack"] = new string[1] { "Feathers,Needle,GreydwarfEye,GiantBloodSack,AskBladder,WolfFang,Carapace,WolfHairBundle,ElakingHairBundle,Chitin,CelestialFeather,MooseSinew,BjornPaw,WolfClaw,MorgenHeart,Bilebag,MorgenSinew,DragonTear,Mandible,SerpentScale,BonemawSerpentScale" }, ["4 - Treasure Hoard"] = new string[1] { "Coins,Amber,Ruby,AmberPearl" } }; internal static readonly Dictionary<string, string[]> TestAccepts = new Dictionary<string, string[]> { ["4 - Treasure Hoard"] = new string[1] { "Coins,Amber,Ruby,AmberPearl,GemstoneBlue,GemstoneGreen,GemstoneRed,AncientGemstone*,AncientCoin,SilverNecklace,CrownJewel,Pot_Shard*" }, ["4 - Jewellers Casket"] = new string[1] { "Amber,Ruby,AmberPearl,GemstoneBlue,GemstoneGreen,GemstoneRed,AncientGemstone*,AncientCoin,SilverNecklace,CrownJewel,Pot_Shard*" }, ["4 - Apothecary Rack"] = new string[2] { "Feathers,Needle,GreydwarfEye,GiantBloodSack,Ectoplasm,AskBladder,WolfFang,Carapace,WolfHairBundle,ElakingHairBundle,Chitin,CelestialFeather,MooseSinew,BjornPaw,WolfClaw,MorgenHeart,Bilebag,SurtlingCore,MorgenSinew,DragonTear,Mandible,SerpentScale,BonemawSerpentScale", "Feathers,Needle,GreydwarfEye,GiantBloodSack,Ectoplasm,AskBladder,WolfFang,Carapace,WolfHairBundle,ElakingHairBundle,Chitin,CelestialFeather,MooseSinew,BjornPaw,WolfClaw,MorgenHeart,Bilebag,MorgenSinew,DragonTear,Mandible,SerpentScale,BonemawSerpentScale" }, ["4 - Tall Apothecary Rack"] = new string[2] { "Feathers,Needle,GreydwarfEye,GiantBloodSack,Ectoplasm,AskBladder,WolfFang,Carapace,WolfHairBundle,ElakingHairBundle,Chitin,CelestialFeather,MooseSinew,BjornPaw,WolfClaw,MorgenHeart,Bilebag,SurtlingCore,MorgenSinew,DragonTear,Mandible,SerpentScale,BonemawSerpentScale", "Feathers,Needle,GreydwarfEye,GiantBloodSack,Ectoplasm,AskBladder,WolfFang,Carapace,WolfHairBundle,ElakingHairBundle,Chitin,CelestialFeather,MooseSinew,BjornPaw,WolfClaw,MorgenHeart,Bilebag,MorgenSinew,DragonTear,Mandible,SerpentScale,BonemawSerpentScale" } }; internal static readonly Dictionary<string, int[]> PastRows = new Dictionary<string, int[]> { ["4 - Apothecary Rack"] = new int[1] { 4 }, ["4 - Armory Island"] = new int[1] { 8 }, ["4 - Armory Rail"] = new int[2] { 5, 8 }, ["4 - Arsenal B"] = new int[1] { 8 }, ["4 - Bomb Crate"] = new int[1] { 5 }, ["4 - Coopers Row"] = new int[1] { 4 }, ["4 - Fletchers Barrels"] = new int[1] { 4 }, ["4 - Hide Rack"] = new int[1] { 4 }, ["4 - Larder Hutch"] = new int[1] { 9 }, ["4 - Meat Rack"] = new int[1] { 10 }, ["4 - Ossuary Bin"] = new int[1] { 4 }, ["4 - Short Armory Rail"] = new int[1] { 3 }, ["4 - Treasure Hoard"] = new int[1] { 4 } }; private static readonly HashSet<string> Shrunk = new HashSet<string>(StringComparer.Ordinal) { "arsenalb" }; private static ConfigFile seen; private static ConfigFile fallback; private static int bound; private static readonly Dictionary<string, string> F1Names = new Dictionary<string, string>(StringComparer.Ordinal) { ["Performance (dev)"] = "General", ["Shelf"] = "Shelves" }; private static readonly Dictionary<string, string> LookOf = new Dictionary<string, string>(StringComparer.Ordinal) { ["coopersrowstack"] = "coopersrow", ["apothecarytall"] = "apothecary" }; public static bool IsBossTrophy(string prefab) { if (!GameBosses.Contains(prefab)) { string text = ExtraBosses?.Value; if (text != null && text.Length > 0) { return text.Split(',').Any((string x) => string.Equals(x.Trim(), prefab, StringComparison.Ordinal)); } return false; } return true; } private static ConfigEntry<string> Seen(Kind k, string key) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown if (seen == null) { seen = new ConfigFile(Path.Combine(Paths.ConfigPath, "com.joostliebregts.storehouse.defaults.cfg"), true); } return seen.Bind<string>(k.Section, key, "", "The default Storehouse last met here; it tells an edit from an old default. Leave it be."); } private static void MigrateRows(Kind k) { if (k.Rows == null) { return; } ConfigEntry<string> obj = Seen(k, "Rows"); string value = obj.Value; obj.Value = k.DefaultRows.ToString(); if (k.Rows.Value == k.DefaultRows) { return; } int value2 = k.Rows.Value; int result; bool flag = int.TryParse(value, out result); if (flag && result == k.DefaultRows) { return; } bool num; if (!flag) { if (!PastRows.TryGetValue(k.Section, out var value3)) { goto IL_015a; } num = value3.Contains(value2); } else { num = result == value2; } if (num) { if (k.DefaultRows > value2 || Shrunk.Contains(k.Slug)) { k.Rows.Value = k.DefaultRows; Plugin.Log.LogInfo((object)(string.Format("Storehouse: {0} Rows {1} -> {2}, the {3} default", k.Title, value2, k.DefaultRows, "0.8.8") + ((k.DefaultRows < value2) ? "; placed ones keep their rows until what they hold fits." : "."))); } else { Plugin.Log.LogWarning((object)string.Format("Storehouse: {0} Rows kept at {1}: the {2} default {3} is fewer, and fewer rows would hide what lies in the others.", k.Title, value2, "0.8.8", k.DefaultRows)); } return; } goto IL_015a; IL_015a: Plugin.Log.LogWarning((object)string.Format("Storehouse: {0} Rows {1} was edited, kept. The {2} default is {3}.", k.Title, value2, "0.8.8", k.DefaultRows)); } private static void MigrateAccepts(Kind k) { if (k.Accepts == null || k.DefaultAccepts == null) { return; } ConfigEntry<string> obj = Seen(k, "Accepts"); string value = obj.Value; obj.Value = k.DefaultAccepts; HashSet<string> have = Names(k.Accepts.Value); HashSet<string> want = Names(k.DefaultAccepts); if (have.SetEquals(want) || (value.Length > 0 && Names(value).SetEquals(want))) { return; } string[] value2; string[] value3; string[] array = ((value.Length <= 0) ? (PastAccepts.TryGetValue(k.Section, out value2) ? value2 : new string[0]).Concat(TestAccepts.TryGetValue(k.Section, out value3) ? value3 : new string[0]).ToArray() : new string[1] { value }); if (array.Length == 0) { array = null; } if (array != null && array.Any((string v) => Names(v).SetEquals(have))) { k.Accepts.Value = k.DefaultAccepts; Plugin.Log.LogInfo((object)("Storehouse: " + k.Title + " Accepts updated to the 0.8.8 default.")); return; } if (array != null && array.Any((string v) => Names(v).IsSubsetOf(have)) && have.IsSubsetOf(want)) { k.Accepts.Value = k.DefaultAccepts; Plugin.Log.LogInfo((object)("Storehouse: " + k.Title + " Accepts updated to the 0.8.8 default, which keeps every name added to it.")); return; } List<string> list = want.Where((string x) => !have.Contains(x)).ToList(); List<string> list2 = have.Where((string x) => !want.Contains(x)).ToList(); Plugin.Log.LogWarning((object)("Storehouse: " + k.Title + " Accepts was edited, kept as it is. The 0.8.8 default would " + ((list.Count > 0) ? ("add " + string.Join(", ", list)) : "add nothing") + ((list2.Count > 0) ? (" and remove " + string.Join(", ", list2)) : "") + ".")); static HashSet<string> Names(string v) { return new HashSet<string>(from x in (v ?? "").Split(',') select x.Trim() into x where x.Length > 0 select x, StringComparer.OrdinalIgnoreCase); } } internal static ConfigEntry<T> Safe<T>(this ConfigFile config, string section, string key, T value, ConfigDescription description = null) { //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_00ce: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown try { description = InF1(section, key, description); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Storehouse: '" + section + "' / '" + key + "' keeps its plain F1 look (" + ex.Message + ").")); } try { return config.Bind<T>(section, key, value, description); } catch (Exception ex2) { Plugin.Log.LogError((object)$"Storehouse: setting '{section}' / '{key}' could not be bound; it stays at its default {value} for this session ({ex2.Message})."); if (fallback == null) { fallback = new ConfigFile(Path.Combine(Paths.CachePath ?? Path.GetTempPath(), "Storehouse.fallback.cfg"), false) { SaveOnConfigSet = false }; } return fallback.Bind<T>(Clean(section), Clean(key), value, new ConfigDescription(((description != null) ? description.Description : null) ?? "", (AcceptableValueBase)null, Array.Empty<object>())); } static string Clean(string s) { string text = new string((s ?? "").Where((char c) => "=\n\t\\\"'[]".IndexOf(c) < 0).ToArray()).Trim(); if (text == null || text.Length <= 0) { return "Setting"; } return text; } } internal static ConfigEntry<T> Safe<T>(this ConfigFile config, string section, string key, T value, string description) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return config.Safe(section, key, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>())); } private static ConfigDescription InF1(string section, string key, ConfigDescription description) { //IL_0179: 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) //IL_021c: Expected O, but got Unknown Kind kind = Kinds.All.FirstOrDefault((Kind k) => k.Section == section); string text = Regex.Replace(section ?? "", "^\\d+ - ", ""); string value; string category = ((kind != null) ? (Kinds.GroupOf(kind) ?? "Test pieces (dev)") : null) ?? ((key == "Display item size") ? "General" : (F1Names.TryGetValue(text, out value) ? value : text)); string text2; if (kind?.Slug == "coopersrow") { bool flag = ((key == "Item size" || key == "Glass colour") ? true : false); text2 = (flag ? "Cooper's Rows (both)" : "Cooper's Row (ground)"); } else { bool flag = kind?.Slug == "apothecary"; if (flag) { bool flag2 = ((key == "Item size" || key == "Glass colour") ? true : false); flag = flag2; } text2 = (flag ? "Apothecary Racks (both)" : ((kind != null) ? (kind.Shown ?? kind.Title) : ((text == "Shelf") ? "Shelf" : null))); } string text3 = text2; object[] source = ((description != null) ? description.Tags : null) ?? new object[0]; ConfigurationManagerAttributes shown = (ConfigurationManagerAttributes)(((object)source.OfType<ConfigurationManagerAttributes>().FirstOrDefault()) ?? ((object)new ConfigurationManagerAttributes())); shown.Category = category; shown.Order = -(++bound); if (text3 != null) { shown.DispName = text3 + ": " + key; } return new ConfigDescription(((description != null) ? description.Description : null) ?? "", (description != null) ? description.AcceptableValues : null, new object[1] { shown }.Concat(source.Where((object t) => t != shown)).ToArray()); } public static void Bind(ConfigFile config) { //IL_045c: Unknown result type (might be due to invalid IL or missing references) //IL_0499: Unknown result type (might be due to invalid IL or missing references) //IL_04a3: Expected O, but got Unknown //IL_04f7: Unknown result type (might be due to invalid IL or missing references) //IL_0501: Expected O, but got Unknown //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_05ea: Unknown result type (might be due to invalid IL or missing references) //IL_05f4: Expected O, but got Unknown //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Expected O, but got Unknown //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0380: Expected O, but got Unknown //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Expected O, but got Unknown //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_03e0: Expected O, but got Unknown //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Expected O, but got Unknown BindGeneral(config); Kind[] all = Kinds.All; string[] rounds; foreach (Kind kind in all) { if (kind.Planks == 0 && !Mannequins.Is(kind) && !TypedSlots.Is(kind)) { kind.Rows = config.Safe(kind.Section, "Rows", kind.DefaultRows, new ConfigDescription("Rows of 8 slots. The chest UI scrolls past 4 rows. Applies to chests that load after the change. Lowering it hides items in the removed rows (the game keeps them, out of reach) until the rows come back: empty them first.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), new object[1] { Synced() })); } try { MigrateRows(kind); } catch (Exception arg) { Plugin.Log.LogError((object)$"Storehouse: {kind.Title} Rows not migrated, kept as it is ({arg})."); } if (kind.Slug != null && LookOf.TryGetValue(kind.Slug, out var lookSlug)) { Kind kind2 = Kinds.All.FirstOrDefault((Kind x) => x.Slug == lookSlug); if (kind2 != null && kind2.ItemSize != null) { kind.ItemSize = kind2.ItemSize; kind.GlassColour = kind2.GlassColour; goto IL_0168; } } if (kind.Planks == 0) { kind.ItemSize = config.Safe(kind.Section, "Item size", 1f, new ConfigDescription("Live: grows the item models inside this store by this factor, each about its own centre (1 = the layout's own size). Heaps may poke through walls above about 1.2. Only how it looks on your screen.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.8f, 1.6f), Array.Empty<object>())); kind.ItemSize.SettingChanged += delegate { if (Plugin.Live) { StoreModel.Touch(); } }; } goto IL_0168; IL_0168: string slug = kind.Slug; if ((slug == "coopersrow" || slug == "apothecary") ? true : false) { ConfigEntry<Color> entry = (kind.GlassColour = config.Safe<Color>(kind.Section, "Glass colour", Glass.Default, new ConfigDescription("Live: the colour of the glass (sight strips, jars), shared with the " + ((kind.Slug == "coopersrow") ? "Stacking Cooper's Row" : "Tall Apothecary Rack") + "; alpha is how opaque it is (at least 4%). Only how it looks on your screen.", (AcceptableValueBase)null, Array.Empty<object>()))); entry.SettingChanged += delegate { //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Live) { return; } foreach (Kind item in Kinds.All.Where((Kind x) => x.GlassColour == entry)) { StoreModel.Get(item.Slug, item.Piece, item.Title).Retint(entry.Value); } }; } rounds = kind.Rounds; if (rounds != null && rounds.Length > 1 && kind.Slug != null && Kinds.ModelAlternates.TryGetValue(kind.Slug, out var value)) { kind.Model = config.Safe(kind.Section, "Model", "newest", new ConfigDescription("Live (with Live models on): which model this store shows. newest = " + kind.Rounds[0].TrimStart('_') + ". Only how it looks on your screen.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "newest", kind.Rounds[0].TrimStart('_') }.Concat(value.Select((string r) => r.TrimStart('_'))).ToArray()), Array.Empty<object>())); kind.Model.SettingChanged += delegate { ModelWatch.Poke(); }; } kind.Recipe = config.Safe(kind.Section, "Recipe", kind.DefaultRecipe, new ConfigDescription("Build cost: item prefab names with amounts, e.g. BlackMetal:12,Iron:8.", (AcceptableValueBase)null, new object[1] { Synced() })); kind.Station = config.Safe(kind.Section, "Crafting station", kind.DefaultStation, new ConfigDescription("Station prefab that must be nearby: piece_workbench, forge, piece_stonecutter, blackforge, piece_artisanstation, or empty for none.", (AcceptableValueBase)null, new object[1] { Synced() })); if (kind.DefaultAccepts != null) { kind.Accepts = config.Safe(kind.Section, "Accepts", kind.DefaultAccepts, new ConfigDescription((kind.Rule == null) ? "Item prefab names this chest takes, comma separated. Anything else is refused. Items already inside always stay." : ("Takes its own kind of item by type (" + kind.FilterLabel + "), plus these item prefab names, comma separated; * stands for any letters (MeadBase* is every mead base). Anything else is refused. Items already inside always stay."), (AcceptableValueBase)null, new object[1] { Synced() })); try { MigrateAccepts(kind); } catch (Exception arg2) { Plugin.Log.LogError((object)$"Storehouse: {kind.Title} Accepts not migrated, kept as it is ({arg2})."); } kind.Filter = new Filter(kind); } } SkinChests = config.Safe("8 - Skins (dev)", "Skin chests", value: true, "Dev, this PC only: look at a vanilla chest and press the hotkey to dress it as one of the Storehouse stores, with its real contents. Only you see it; the chest itself, its size and the network stay vanilla. Off: every chest shows its own look again (choices are kept)."); SkinKey = config.Safe<KeyboardShortcut>("8 - Skins (dev)", "Hotkey", new KeyboardShortcut((KeyCode)107, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Cycles the skin of the chest you look at: none, then each store, then none again."); DisplaySize = config.Safe("8 - Skins (dev)", "Display item size", 0.7f, new ConfigDescription("Dev, live: the size of the items in the display stores (Larder Hutch, Shelf) as a fraction of the item's true in-game size (as it lies in the world when dropped). The shelf's planks have room for up to about 0.8; taller items move on to a plank where they fit or are not shown. Mass stores (crib, racks, bins, bunker) keep their own fit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 1.2f), Array.Empty<object>())); DisplaySize.SettingChanged += delegate { if (Plugin.Live) { StoreModel.Touch(); } }; SlotsPerPlank = config.Safe("4 - Shelf", "Slots per plank", 6, new ConfigDescription("Container slots per plank of the Shelf (1, 2 or 3 planks: 1, 2 or 3 rows of this many). 6 is what typically fits on a 2 m plank at Display item size 0.7 (meads about 0.2 m, meals about 0.4 m across); a stack that does not fit is not shown but stays in the chest. Applies to shelves that load after the change; lowering it hides the stacks in the removed slots until it goes back up.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), new object[1] { Synced() })); ShowLoadedLine = config.Safe("6 - Interface", "Show loaded line on the main menu", value: true, "On the main menu, top left: Storehouse <version> loaded: <n> pieces ready. Off hides that line only; the red did not load fully warning always shows. With it off, no line no longer means Storehouse did not load."); StoreInTooltip = config.Safe("6 - Interface", "Store in item tooltips", value: true, "An item's tooltip ends with the Storehouse stores that take it, e.g. Storehouse: goes in the Larder Hutch. Items no store takes get no line."); TooltipStyle = config.Safe("6 - Interface", "Tooltip style", TooltipStyles.Sentence, "Sentence: Storehouse: goes in the Utility Island or the Utility Wall. Short: Storehouse: Utility Island, Utility Wall. Custom: the Tooltip format below."); TooltipFormat = config.Safe("6 - Interface", "Tooltip format", "Storehouse: {stores}", "With Tooltip style Custom: the tooltip line, where {stores} becomes the stores that take the item, joined by commas."); ExtraBosses = config.Safe("6 - Interface", "Other boss trophies", "", "Item prefab names of other mods' boss trophies, comma separated. Like the game's seven bosses, they get a gold glow behind their icon in a store's window."); rounds = TrophyKinds; foreach (string key in rounds) { float value2; ConfigEntry<float> val = (TrophyScale[key] = config.Safe("5 - Trophy sizes", key, TrophyDefaults.TryGetValue(key, out value2) ? value2 : 1f, new ConfigDescription("Live: this trophy's size on a Trophy Hall board, times the hall's own fit (1 = as fitted). Only how it looks on your screen.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 3f), Array.Empty<object>()))); val.SettingChanged += delegate { if (Plugin.Live) { StoreModel.Touch("trophyhall"); } }; } BindDebug(config); } private static void BindGeneral(ConfigFile config) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Expected O, but got Unknown //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Expected O, but got Unknown MannequinTurn = config.Safe("7 - Performance (dev)", "Mannequin extra turn", 0f, new ConfigDescription("Dev, live: extra degrees the worn sets turn on their forms. They face the frame's open side by themselves; 0 normally.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-180f, 180f), Array.Empty<object>())); MannequinTurn.SettingChanged += delegate { if (Plugin.Live) { StoreModel.Touch(); } }; ContentsInShade = config.Safe("7 - Performance (dev)", "Contents in the frames shade", value: false, "Live: the items in a store take the shadows of its frame (a bunker's stacking top darkens its heaps). Off: they are lit by the surroundings only, so what lies inside reads (Joost, 25 Oct: the bunker's coal and stone looked black)."); ManyKindBoards = config.Safe("7 - Performance (dev)", "Boards between three or more kinds", value: true, "Live: a bay that holds three or more kinds side by side (a bunker with more kinds than bays) gets a board between each. Off: those kinds lie as heaps side by side without boards; two kinds in a bay always keep their board."); ManyKindBoards.SettingChanged += delegate { if (Plugin.Live) { StoreModel.Touch(); } }; ShowContents = config.Safe("7 - Performance (dev)", "Show contents", value: true, "Dev, live: draw the item models inside the stores. Off: every store shows its frame only, so you can compare the FPS in the same spot."); ContentsDistance = config.Safe("7 - Performance (dev)", "Contents distance", 35f, new ConfigDescription("Dev, live: a store's contents are drawn only while the camera is within this many metres of it; further away they cost nothing per frame (and a store out of range rebuilds its contents only once you come near).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 150f), Array.Empty<object>())); MaxTrisPerStack = config.Safe("7 - Performance (dev)", "Max triangles per stack", 6000, new ConfigDescription("Live: a stack shows at most this many triangles of item copies (always at least one copy). Light items (ore, arrows, bones) are not affected; heavy ones (Feast platters, trophies of big bosses) show fewer copies. 0 = no limit.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 50000), Array.Empty<object>())); MaxTrisPerStack.SettingChanged += delegate { if (Plugin.Live) { StoreModel.Touch(); } }; FarContents = config.Safe("7 - Performance (dev)", "Far contents beyond", 12f, new ConfigDescription("Live: beyond this many metres a store draws its items with the game's own lighter far models (their last LOD), where an item has one. 0 = always the full models.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>())); ContentsShadows = config.Safe("7 - Performance (dev)", "Contents cast shadows", value: false, "Dev, live: the item models in the stores cast shadows. Hundreds of small casters are expensive (one extra draw per shadow cascade); the frame keeps its own shadows either way."); Instancing = config.Safe("7 - Performance (dev)", "GPU instancing", value: true, "Dev, live: draw the copies of an item whose mesh can't be merged (not readable) with one instanced draw per mesh and material, where the item's shader supports it. Off: one draw per copy. Turn off if copies of an item show up in one spot."); PerfLog = config.Safe("7 - Performance (dev)", "Perf log", value: false, "Dev, live: every 10 s write one line to the BepInEx log: FPS (average and 1% low), stores in range and in view, item copies drawn, draws issued by Storehouse, ms per frame spent in Storehouse code, and the last contents rebuild."); PerfOverlay = config.Safe("7 - Performance (dev)", "Perf overlay", value: false, "Dev, live: show the latest perf line on screen, top left."); PlacementCheck = config.Safe("7 - Performance (dev)", "Placement self-check", value: false, "Dev, live: every answer a typed store's window and display get by replay (which slots show hints, whether a dropped item shows, where each item is drawn) is also solved from scratch the 0.8.0 way, and any difference is logged as a warning (Placement self-check: ...). Costs what 0.8.0 cost, the Spine's lag included: on only to check."); } private static void BindDebug(ConfigFile config) { LiveModels = config.Safe("9 - Debug", "Live models", value: false, "Dev: check the model files once a second and swap a changed model into the running game (piece and placed ones)."); ModelFolder = config.Safe("9 - Debug", "Model folder", "", "Dev: read the models from this folder (with a crib/ subfolder, and <store>_r3 or <store>_r2 subfolders for the dev stores) instead of the mod's own assets, e.g. the Blender export folder."); MatteFrames = config.Safe("9 - Debug", "Matte frames", value: true, "Dev, live: every store's frame without gloss, specular, metal, wetness or noise (the default since 25 Oct: straw glints and dark patches). Off: as the wood chest's material, to compare."); MatteFrames.SettingChanged += delegate { if (Plugin.Live) { Atlas.Matte(MatteFrames.Value); } }; DumpPrefabs = config.Safe("9 - Debug", "Dump prefabs", value: true, "At game start, write the hierarchy, meshes, materials, colliders and LODs of the black metal chest and the three pieces to the BepInEx log."); } private static ConfigurationManagerAttributes Synced() { //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_000d: Expected O, but got Unknown return new ConfigurationManagerAttributes { IsAdminOnly = true }; } public static List<(string item, int amount)> ParseRecipe(string s) { List<(string, int)> list = new List<(string, int)>(); string[] array = (s ?? "").Split(','); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(':'); if (array2.Length == 2 && int.TryParse(array2[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result > 0) { string text = array2[0].Trim(); if (text.Length > 0) { list.Add((text, result)); } } } return list; } public static void ApplyToPieces() { //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown Kind[] all = Kinds.All; foreach (Kind kind in all) { kind.Filter?.Invalidate(); PrefabManager instance = PrefabManager.Instance; GameObject val = ((instance != null) ? instance.GetPrefab(kind.Prefab) : null); Piece val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<Piece>() : null); if (!Object.op_Implicit((Object)(object)val2)) { continue; } List<Requirement> list = new List<Requirement>(); bool flag = true; foreach (var item3 in ParseRecipe(kind.Recipe.Value)) { string item = item3.item; int item2 = item3.amount; GameObject val3 = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.GetItemPrefab(item) : PrefabManager.Instance.GetPrefab(item)); ItemDrop val4 = (Object.op_Implicit((Object)(object)val3) ? val3.GetComponent<ItemDrop>() : null); if (!Object.op_Implicit((Object)(object)val4)) { flag = false; Plugin.Log.LogWarning((object)(kind.Title + ": recipe item '" + item + "' not found; keeping the previous recipe.")); break; } list.Add(new Requirement { m_resItem = val4, m_amount = item2, m_recover = true }); } if (flag && list.Count > 0) { val2.m_resources = list.ToArray(); } string text = kind.Station.Value?.Trim(); if (string.IsNullOrEmpty(text)) { val2.m_craftingStation = null; continue; } GameObject val5 = (Object.op_Implicit((Object)(object)ZNetScene.instance) ? ZNetScene.instance.GetPrefab(text) : PrefabManager.Instance.GetPrefab(text)); if (Object.op_Implicit((Object)(object)val5)) { CraftingStation component = val5.GetComponent<CraftingStation>(); if (component != null) { val2.m_craftingStation = component; continue; } } Plugin.Log.LogWarning((object)(kind.Title + ": crafting station '" + text + "' not found; keeping the previous one.")); } } } internal static class StorageReviewPolicy { public const string PantryUncooked = "BakedPoteitrUncooked,FishAndBreadUncooked,HoneyGlazedChickenUncooked,KaleChipsUncooked,LoxPieUncooked,MagicallyStuffedShroomUncooked,MeatPlatterUncooked,MisthareSupremeUncooked,OvenPancakeUncooked,PiquantPieUncooked,RoastedCrustPieUncooked,VikingCupcakeUncooked"; public const string PantryDefaults = "SpiceForests,SpicePlains,SpiceMountains,SpiceOceans,SpiceMistlands,SpiceDeepNorth,SpiceAshlands,BarleyFlour,OatFlour,BreadDough,FragrantBundle,FeastAshlands_Material,FeastBlackforest_Material,FeastDeepNorth_Material,FeastMeadows_Material,FeastMistlands_Material,FeastMountains_Material,FeastOceans_Material,FeastPlains_Material,FeastSwamps_Material,BakedPoteitrUncooked,FishAndBreadUncooked,HoneyGlazedChickenUncooked,KaleChipsUncooked,LoxPieUncooked,MagicallyStuffedShroomUncooked,MeatPlatterUncooked,MisthareSupremeUncooked,OvenPancakeUncooked,PiquantPieUncooked,RoastedCrustPieUncooked,VikingCupcakeUncooked"; public const string CooperLegacy = "Tar,Resin,Sap,Guck,Eitr,Ooze,RoyalJelly"; public const string CooperDefaults = "Tar,Resin,Sap,Guck,Ooze,RoyalJelly,Honey,CharcoalResin,OozeMork,HatefulBlood,Voidplasm,BlobVial,Ectoplasm,ProustitePowder"; public const string ValuablesDefaults = "Coins,Amber,Ruby,AmberPearl,GemstoneBlue,GemstoneGreen,GemstoneRed,AncientGemstone*,AncientCoin,SilverNecklace,CrownJewel,Pot_Shard*"; public const string TreasureDefaults = "Coins,AncientCoin,Gold"; public const string JewellerDefaults = "Amber,Ruby,AmberPearl,GemstoneBlue,GemstoneGreen,GemstoneRed,AncientGemstone*,SilverNecklace,CrownJewel,Pot_Shard*"; private static readonly HashSet<string> uncookedFoods = new HashSet<string>("BakedPoteitrUncooked,FishAndBreadUncooked,HoneyGlazedChickenUncooked,KaleChipsUncooked,LoxPieUncooked,MagicallyStuffedShroomUncooked,MeatPlatterUncooked,MisthareSupremeUncooked,OvenPancakeUncooked,PiquantPieUncooked,RoastedCrustPieUncooked,VikingCupcakeUncooked".Split(','), StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> valuables = new HashSet<string>(from n in "Coins,Amber,Ruby,AmberPearl,GemstoneBlue,GemstoneGreen,GemstoneRed,AncientGemstone*,AncientCoin,SilverNecklace,CrownJewel,Pot_Shard*".Split(',') where !n.Contains('*') select n, StringComparer.Ordinal); private static readonly HashSet<string> wetParts = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "entrails", "bloodclot", "giantbloodsack", "bloodbag", "freezegland" }; private static readonly string[] notTreasure = new string[4] { "uncooked", "mold", "mould", "ammo" }; public static bool NonFoodUncooked(string prefab) { if (!string.IsNullOrEmpty(prefab) && prefab.EndsWith("Uncooked", StringComparison.OrdinalIgnoreCase)) { return !uncookedFoods.Contains(prefab); } return false; } public static bool MeadProductName(string prefab) { if (!string.IsNullOrEmpty(prefab)) { if (!prefab.Equals("BarleyWine", StringComparison.OrdinalIgnoreCase)) { if (prefab.StartsWith("Mead", StringComparison.OrdinalIgnoreCase)) { return !prefab.StartsWith("MeadBase", StringComparison.OrdinalIgnoreCase); } return false; } return true; } return false; } public static bool MeadProduct(bool consumable, bool food, bool fermenter, string prefab) { if (!fermenter) { if (consumable && !food) { return MeadProductName(prefab); } return false; } return true; } public static bool CoinWellAllows(string store, bool coinHome, string prefab) { if (!(store != "treasure") && coinHome) { return prefab == "Coins"; } return true; } public static bool ValuableName(string prefab) { if (prefab != null) { if (!valuables.Contains(prefab) && !prefab.StartsWith("AncientGemstone", StringComparison.Ordinal)) { return prefab.StartsWith("Pot_Shard", StringComparison.Ordinal); } return true; } return false; } public static bool JewelName(string prefab) { if (ValuableName(prefab)) { return !CoinOrGoldName(prefab); } return false; } public static bool CoinOrGoldName(string prefab) { if (!string.IsNullOrEmpty(prefab) && (prefab.IndexOf("coin", StringComparison.OrdinalIgnoreCase) >= 0 || prefab.IndexOf("gold", StringComparison.OrdinalIgnoreCase) >= 0) && !notTreasure.Any((string w) => prefab.IndexOf(w, StringComparison.OrdinalIgnoreCase) >= 0) && !prefab.EndsWith("Key", StringComparison.OrdinalIgnoreCase)) { return !prefab.EndsWith("Ore", StringComparison.OrdinalIgnoreCase); } return false; } public static int UnrepresentedKinds(IReadOnlyList<string> types, IReadOnlyList<int> places, int processed) { HashSet<string> hashSet = new HashSet<string>(types, StringComparer.Ordinal); for (int i = 0; i < Math.Min(types.Count, Math.Min(places.Count, processed)); i++) { if (places[i] >= 0) { hashSet.Remove(types[i]); } } return hashSet.Count; } public static bool CanHangMeat(string prefab) { if (string.IsNullOrEmpty(prefab)) { return false; } if (prefab.IndexOf("egg", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } string text = (prefab.StartsWith("$item_", StringComparison.OrdinalIgnoreCase) ? prefab.Substring(6) : prefab); return !wetParts.Contains(text.Replace("_", "")); } } internal sealed class Filter { private readonly Kind kind; private readonly HashSet<string> prefabs = new HashSet<string>(StringComparer.Ordinal); private readonly HashSet<string> shared = new HashSet<string>(StringComparer.Ordinal); private readonly List<Regex> patterns = new List<Regex>(); private string resolvedFrom; private ObjectDB resolvedFor; private float nextMessage; private static readonly HashSet<string> oneKindPerBay = new HashSet<string>(StringComparer.Ordinal) { "bunker", "bunkeropen", "orebin", "jeweller" }; private string lastMessage; private static readonly (string[] Slugs, string Short, string Sentence)[] FamilyTable = new(string[], string, string)[9] { (new string[2] { "trophyhall13x5", "trophyhall10x7" }, "all Trophy Halls", "the tall and wide Trophy Halls"), (new string[2] { "apothecary", "apothecarytall" }, "all Apothecary Racks", "the short and tall Apothecary Racks"), (new string[2] { "coopersrow", "coopersrowstack" }, "all Cooper's Rows", "the single and stacking Cooper's Rows"), (new string[2] { "bunker", "bunkeropen" }, "all Coal & Stone Bunkers", "the closed and open Coal & Stone Bunkers"), (new string[4] { "metalrack", "metalrackhalf", "metalracklow", "metalracklowhalf" }, "all Metal Racks", "every Metal Rack, tall or low, wide or half"), (new string[2] { "weaponrack", "weaponrackb" }, "both Weapon Racks", "both Weapon Racks"), (new string[2] { "armorstand", "armorstandopen" }, "both Armor Stands", "the Armor Stand, with or without its back rail"), (new string[3] { "armoryrail", "armoryrail3", "armoryisland" }, "all Armory Rails", "the long and short Armory Rails and the Armory Island"), (new string[5] { "arsenalb", "arsenalkit_spine", "arsenalkit_shield", "arsenalkit_long", "arsenalkit_bow" }, "all Arsenal racks", "the Arsenal and its racks") }; public string Label => kind.FilterLabel; public bool KindPerBay { get { if (kind.Slug != null) { return oneKindPerBay.Contains(kind.Slug); } return false; } } private int Bays { get { if (!KindPerBay) { return 0; } return StoreModel.BaysOf(kind.Slug, kind.Piece); } } public Filter(Kind kind) { this.kind = kind; } private static string KindOf(ItemData it) { if (!Object.op_Implicit((Object)(object)it.m_dropPrefab)) { return it.m_shared.m_name; } return ((Object)it.m_dropPrefab).name; } public (int used, int bays) BayUse(Inventory inv) { return BayUse(inv, null, null); } private (int used, int bays) BayUse(Inventory inv, ItemData gone, string added) { int bays = Bays; if (bays <= 0 || inv == null) { return (used: 0, bays: 0); } float perBay = (float)(inv.GetWidth() * inv.GetHeight()) / (float)bays; IEnumerable<string> source = (from i in inv.GetAllItems() where i?.m_shared != null && i != gone select i).Select(KindOf); if (added != null) { source = source.Append(added); } return (used: (from n in source group n by n).Sum((IGrouping<string, string> g) => Mathf.Max(1, Mathf.CeilToInt((float)g.Count() / perBay))), bays: bays); } private static bool NewStack(Inventory inv, ItemData item, int amount, Vector2i? at, out ItemData gone) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_009e: Unknown result type (might be due to invalid IL or missing references) gone = null; amount = ((amount > 0) ? Mathf.Min(amount, item.m_stack) : item.m_stack); if (inv.ContainsItem(item) && amount >= item.m_stack) { return false; } if (at.HasValue) { Vector2i valueOrDefault = at.GetValueOrDefault(); if (valueOrDefault.x < 0 || valueOrDefault.y < 0 || valueOrDefault.x >= inv.GetWidth() || valueOrDefault.y >= inv.GetHeight()) { return false; } ItemData itemAt = inv.GetItemAt(valueOrDefault.x, valueOrDefault.y); if (itemAt == null || itemAt == item) { return itemAt == null; } if ((itemAt.m_shared.m_name != item.m_shared.m_name || (item.m_shared.m_maxQuality > 1 && itemAt.m_quality != item.m_quality) || itemAt.m_shared.m_maxStackSize == 1) && item.m_stack == amount) { gone = itemAt; return true; } return false; } if (item.m_shared.m_maxStackSize <= 1) { return true; } return (from i in inv.GetAllItems() where i != null && i != item && i.m_shared.m_name == item.m_shared.m_name && i.m_quality == item.m_quality && i.m_worldLevel == item.m_worldLevel select i).Sum((ItemData i) => Mathf.Max(0, i.m_shared.m_maxStackSize - i.m_stack)) < amount; } public bool AllowsIn(Inventory inv, ItemData item, out string why, int amount = -1, Vector2i? at = null) { why = null; if (!Allows(item)) { return false; } if (Mannequins.Is(kind)