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 VanillaPilePlus v1.0.1
BepInEx/plugins/VanillaPilePlus/VanillaPilePlus.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; [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("VanillaPilePlus")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+627c93de35fe4ea8fc7db780adf6d890daf2a24a")] [assembly: AssemblyProduct("VanillaPilePlus")] [assembly: AssemblyTitle("VanillaPilePlus")] [assembly: AssemblyVersion("1.0.1.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace VanillaPilePlus { internal static class CraftFromContainersIntegration { private static Func<IEnumerable<string>, List<Container>> _getSources; internal static void Install(Harmony harmony) { //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Expected O, but got Unknown //IL_01c0: Expected O, but got Unknown if (!Chainloader.PluginInfos.TryGetValue("local.craftfromcontainers", out var value)) { return; } List<Tuple<MethodBase, MethodInfo>> installed = new List<Tuple<MethodBase, MethodInfo>>(); try { Type index = ((object)value.Instance).GetType().Assembly.GetType("CraftFromContainers.ContainerIndex", throwOnError: true); MethodInfo methodInfo = Require("GetSourcesFor", typeof(List<Container>), new Type[1] { typeof(IEnumerable<string>) }); MethodInfo target = Require("GetNearby", typeof(List<Container>), Array.Empty<Type>()); MethodInfo target2 = Require("Count", typeof(int), new Type[3] { typeof(string), typeof(int), typeof(bool) }); MethodInfo target3 = Require("Find", typeof(ItemData), new Type[2] { typeof(string), typeof(int) }); MethodInfo methodInfo2 = Require("Prepare", typeof(void), Array.Empty<Type>()); _getSources = (Func<IEnumerable<string>, List<Container>>)Delegate.CreateDelegate(typeof(Func<IEnumerable<string>, List<Container>>), methodInfo); Postfix(methodInfo, "SourcesPostfix"); Postfix(target, "SourcesPostfix"); Postfix(target2, "CountPostfix"); Postfix(target3, "FindPostfix"); MethodInfo methodInfo3 = Handler("PreparePrefix"); MethodInfo methodInfo4 = Handler("PrepareFinalizer"); harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(methodInfo3) { priority = 800 }, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(methodInfo4) { priority = 0 }, (HarmonyMethod)null); installed.Add(Tuple.Create((MethodBase)methodInfo2, methodInfo3)); installed.Add(Tuple.Create((MethodBase)methodInfo2, methodInfo4)); Plugin.Log.LogInfo((object)("CraftFromContainers " + value.Metadata.Version?.ToString() + ": pile-building source protection available.")); MethodInfo Require(string name, Type result, params Type[] arguments) { MethodInfo method = index.GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, arguments, null); if (method == null || method.ReturnType != result) { throw new MissingMethodException(index.FullName, name); } return method; } } catch (Exception ex) { foreach (Tuple<MethodBase, MethodInfo> item in installed) { harmony.Unpatch(item.Item1, item.Item2); } _getSources = null; Plugin.Log.LogWarning((object)("CraftFromContainers pile-building protection unavailable: " + ex.Message)); } static MethodInfo Handler(string name) { return typeof(CraftFromContainersIntegration).GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } void Postfix(MethodInfo methodInfo6, string name) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown MethodInfo methodInfo5 = Handler(name); harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(methodInfo5) { priority = 0 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); installed.Add(Tuple.Create((MethodBase)methodInfo6, methodInfo5)); } } private static void PreparePrefix(out PileResourceScope __state) { __state = (PileResourceScope.BlockPileAccess ? new PileResourceScope(PileResourceScope.ExcludePiles, blockAccess: false) : null); } private static void PrepareFinalizer(PileResourceScope __state) { __state?.Dispose(); } private static List<Container> WithoutPiles(List<Container> sources) { if (sources == null) { return new List<Container>(); } List<Container> list = new List<Container>(sources.Count); foreach (Container source in sources) { if (!PileInventoryRegistry.IsPileContainer(source)) { list.Add(source); } } return list; } private static void SourcesPostfix(ref List<Container> __result) { if (PileResourceScope.ExcludePiles) { __result = WithoutPiles(__result); } } private static void CountPostfix(string __0, int __1, bool __2, ref int __result) { if (!PileResourceScope.ExcludePiles) { return; } int num = 0; foreach (Container item in WithoutPiles(_getSources(new string[1] { __0 }))) { Inventory val = ((item != null) ? item.GetInventory() : null); if (val != null) { num = checked(num + val.CountItems(__0, __1, __2)); } } __result = num; } private static void FindPostfix(string __0, int __1, ref ItemData __result) { if (!PileResourceScope.ExcludePiles) { return; } __result = null; foreach (Container item in WithoutPiles(_getSources(new string[1] { __0 }))) { object obj; if (item == null) { obj = null; } else { Inventory inventory = item.GetInventory(); obj = ((inventory != null) ? inventory.GetItem(__0, __1, false) : null); } ItemData val = (ItemData)obj; if (val != null) { __result = val; break; } } } } internal static class PileBuildAccess { internal static PileResourceScope Begin(Piece piece) { bool flag = (Object)(object)piece != (Object)null && PileCatalog.Find(Utils.GetPrefabName(((Object)piece).name)) != null; return new PileResourceScope(flag && Plugin.ExcludePilesFromPileBuilding.Value, flag && Plugin.BlockPileAccessWhenBuildingPiles.Value && PartialPilePlacement.ConsumptionHook); } internal static bool IsUnavailable(Container container) { if (PileResourceScope.BlockPileAccess) { return PileInventoryRegistry.IsPileContainer(container); } return false; } } internal sealed class PartialPlacementTransaction { internal bool Claimed { get; private set; } internal bool Paid { get; private set; } internal bool Committed { get; private set; } internal Exception? Failure { get; private set; } internal bool Execute(Action debit, Func<bool> wasDebited, Action publish, Func<bool> wasPublished, Action discard, Action refund) { if (Claimed) { return false; } Claimed = true; try { debit(); Paid = wasDebited(); if (!Paid) { throw new InvalidOperationException("Selected stack was not debited."); } publish(); Committed = wasPublished(); if (!Committed) { throw new InvalidOperationException("Partial pile was not committed."); } return true; } catch (Exception ex) { Exception ex2 = (Failure = ex); Paid |= wasDebited(); Committed = wasPublished(); if (Committed) { return true; } try { discard(); if (Paid) { refund(); Paid = false; } } catch (Exception ex4) { Failure = new AggregateException(ex2, ex4); } return false; } } } internal static class PartialPileRules { internal static bool Supports(string? resource) { return Limit(resource) > 0; } internal static int Limit(string? resource) { int num = 0; PileDefinition[] all = PileCatalog.All; foreach (PileDefinition pileDefinition in all) { if (pileDefinition.ItemPrefab == resource) { num = Math.Max(num, pileDefinition.VanillaCost); } } return num; } internal static PileDefinition? Select(string? resource, int amount) { if (amount <= 0) { return null; } PileDefinition pileDefinition = null; PileDefinition[] all = PileCatalog.All; foreach (PileDefinition pileDefinition2 in all) { if (ValidStack(pileDefinition2, resource, amount) && (pileDefinition == null || pileDefinition2.VanillaCost < pileDefinition.VanillaCost)) { pileDefinition = pileDefinition2; } } return pileDefinition; } internal static bool ValidStack(PileDefinition definition, string? resource, int amount) { if (definition.ItemPrefab == resource && amount > 0) { return amount <= definition.VanillaCost; } return false; } internal static bool Unchanged(PileDefinition definition, int selected, int current, bool sameRecord) { if (sameRecord && ValidStack(definition, definition.ItemPrefab, selected)) { return selected == current; } return false; } } internal sealed class PileDefinition { internal string Prefab { get; } internal string ItemPrefab { get; } internal int VanillaCost { get; } internal bool Heap { get; } internal PileDefinition(string prefab, string item, int cost, bool heap = false) { Prefab = prefab; ItemPrefab = item; VanillaCost = cost; Heap = heap; } } internal static class PileCatalog { internal static readonly PileDefinition[] All = new PileDefinition[24] { new PileDefinition("wood_stack", "Wood", 50), new PileDefinition("wood_core_stack", "RoundLog", 50), new PileDefinition("wood_fine_stack", "FineWood", 50), new PileDefinition("wood_yggdrasil_stack", "YggdrasilWood", 50), new PileDefinition("blackwood_stack", "Blackwood", 50), new PileDefinition("wood_frost_stack", "Frostwood", 50), new PileDefinition("stone_pile", "Stone", 50), new PileDefinition("flint_pile", "Flint", 50), new PileDefinition("coal_pile", "Coal", 50), new PileDefinition("blackmarble_pile", "BlackMarble", 50), new PileDefinition("grausten_pile", "Grausten", 50), new PileDefinition("obsidian_pile", "Obsidian", 50), new PileDefinition("bone_stack", "BoneFragments", 50), new PileDefinition("skull_pile", "Charredskull", 50), new PileDefinition("bar_copper_stack", "Copper", 30), new PileDefinition("bar_tin_stack", "Tin", 30), new PileDefinition("bar_bronze_stack", "Bronze", 30), new PileDefinition("bar_iron_stack", "Iron", 30), new PileDefinition("bar_silver_stack", "Silver", 30), new PileDefinition("bar_blackmetal_stack", "BlackMetal", 30), new PileDefinition("bar_flametal_stack", "FlametalNew", 30), new PileDefinition("bar_gold_stack", "Gold", 30), new PileDefinition("treasure_stack", "Coins", 99), new PileDefinition("treasure_pile", "Coins", 999, heap: true) }; private static readonly Dictionary<string, PileDefinition> ByName = All.ToDictionary<PileDefinition, string>((PileDefinition d) => d.Prefab, StringComparer.Ordinal); internal static PileDefinition? Find(string prefab) { if (prefab == null || !ByName.TryGetValue(prefab, out PileDefinition value)) { return null; } return value; } } internal sealed class PileResourceScope : IDisposable { [ThreadStatic] private static PileResourceScope? _current; private readonly PileResourceScope? _previous; private readonly bool _exclude; private readonly bool _blockAccess; private bool _disposed; internal static bool ExcludePiles => _current?._exclude ?? false; internal static bool BlockPileAccess => _current?._blockAccess ?? false; internal PileResourceScope(bool exclude) : this(exclude, blockAccess: false) { } internal PileResourceScope(bool exclude, bool blockAccess) { _previous = _current; _exclude = exclude; _blockAccess = blockAccess; _current = this; } public void Dispose() { _disposed = true; while (_current != null && _current._disposed) { _current = _current._previous; } } } internal static class PileRules { internal const string PiecePrefab = "wood_stack"; internal const string ItemPrefab = "Wood"; internal const int Schema = 1; internal const int MaxVisualCopies = 12; internal static bool IsWood(string prefab) { return string.Equals(prefab, "Wood", StringComparison.Ordinal); } internal static bool CanInsert(string prefab, int stored, int amount, int capacity) { return CanInsert("Wood", prefab, stored, amount, capacity); } internal static bool CanInsert(string expected, string prefab, int stored, int amount, int capacity) { if (!string.IsNullOrEmpty(expected) && string.Equals(expected, prefab, StringComparison.Ordinal) && capacity > 0 && stored >= 0 && stored <= capacity && amount > 0) { return amount <= capacity - stored; } return false; } internal static int VisualCount(int stored, int capacity, int maximum = 12) { return NativeVisualCount(stored, capacity, Math.Min(capacity, maximum)); } internal static int NativeVisualCount(int stored, int capacity, int units) { if (stored <= 0 || capacity <= 0 || units <= 0) { return 0; } return (int)Math.Min(units, ((long)stored * (long)units + capacity - 1) / capacity); } internal static double HeapScale(int stored, int capacity) { if (capacity > 0 && stored > 0) { return Math.Pow(Math.Min(1.0, (double)stored / (double)capacity), 1.0 / 3.0); } return 0.0; } internal static int StockChunk(int amount, int nativeMaximum) { if (amount < 0 || nativeMaximum <= 0) { throw new ArgumentOutOfRangeException(); } return Math.Min(amount, nativeMaximum); } internal static bool CanDelete(bool ready, bool owner, bool loading, bool destroying, bool faulted, int stored) { if (ready && owner && !loading && !destroying && !faulted) { return stored == 0; } return false; } internal static int RollbackReserve(int piles, int vanillaCost) { return checked(piles * vanillaCost); } internal static int SerializedRecords(byte[] bytes) { if (bytes == null || bytes.Length < 6) { throw new InvalidDataException("Missing/truncated pile payload."); } int num = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24); if (num != 110) { throw new InvalidDataException("Unsupported inventory format " + num + "; original payload retained."); } int num2 = bytes[4] | (bytes[5] << 8); if (num2 > 1) { throw new InvalidDataException("Serialized pile has more than one slot."); } return num2; } } [BepInPlugin("dellarion.valheim.vanillapileplus", "VanillaPilePlus", "1.0.1")] [BepInDependency("com.jotunn.jotunn", "2.30.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "dellarion.valheim.vanillapileplus"; public const string ModName = "VanillaPilePlus"; public const string Version = "1.0.1"; internal static ManualLogSource Log; internal static ConfigEntry<bool> EnablePileStorage; internal static ConfigEntry<bool> ExcludePilesFromPileBuilding; internal static ConfigEntry<bool> BlockPileAccessWhenBuildingPiles; internal static ConfigEntry<bool> DebugLogging; private Harmony _harmony; private void Awake() { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; EnablePileStorage = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnablePileStorage", true, "Enable storage for newly built supported vanilla piles and inventory pile placement. Existing managed piles remain protected when disabled. Restart after changing."); DebugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "DebugLogging", false, "Enable detailed diagnostic logging."); ExcludePilesFromPileBuilding = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "ExcludePilesFromPileBuilding", true, "Exclude pile storage from crafting-mod resource sources when building another pile. Player inventory and ordinary containers remain available. Supported: friendly_neighbor CraftFromContainers."); BlockPileAccessWhenBuildingPiles = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "BlockPileAccessWhenBuildingPiles", true, "Report managed piles as busy and inaccessible only during pile-building resource queries and payment. Does not change real busy/access state. Supports mods that check Container.IsInUse or CheckAccess; caches and direct item edits may bypass it."); _harmony = new Harmony("dellarion.valheim.vanillapileplus"); _harmony.PatchAll(); if (BlockPileAccessWhenBuildingPiles.Value && !PartialPilePlacement.ConsumptionHook) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Pile-building busy/access protection disabled: native placement payment hook is unavailable."); } CraftFromContainersIntegration.Install(_harmony); PileCommands.Register(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("VanillaPilePlus 1.0.1 loaded; " + PileCatalog.All.Length + " vanilla pile types (1x1). Install on every peer. Uses Valheim's normal ZDO/container ownership protocol.")); } private void OnDestroy() { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin stopping. Saved managed-pile data is retained."); } private void Update() { PartialPilePlacement.Tick(); } internal static void Trace(string message) { if (DebugLogging != null && DebugLogging.Value) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)message); } } } internal static void Error(string message, Exception error = null) { ManualLogSource log = Log; if (log != null) { log.LogError((object)(message + ((error == null) ? "" : (": " + error)))); } } } internal static class PileCommands { private sealed class Plan { internal ZDO Zdo; internal PileDefinition Definition; internal Inventory Contents; internal int Cost; } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__1_0; public static ConsoleEvent <>9__1_1; public static Func<KeyValuePair<string, int>, string> <>9__3_0; internal void <Register>b__1_0(ConsoleEventArgs args) { Convert(args); } internal void <Register>b__1_1(ConsoleEventArgs args) { Status(args.Context); } internal string <Convert>b__3_0(KeyValuePair<string, int> p) { return p.Key; } } internal static void Register() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown object obj = <>c.<>9__1_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { Convert(args); }; <>c.<>9__1_0 = val; obj = (object)val; } new ConsoleCommand("ConvertManagedPilesBackToVanilla", "Preview offline rollback of ALL managed piles. Append confirm to reserve native recipe resources, drop contents, and restore piles.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__1_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { Status(args.Context); }; <>c.<>9__1_1 = val2; obj2 = (object)val2; } new ConsoleCommand("vpp_status", "Report loaded Pile Storage objects and resources.", (ConsoleEvent)obj2, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } private static void Status(Terminal terminal) { int num = 0; PileStorage[] array = Object.FindObjectsByType<PileStorage>((FindObjectsSortMode)0); foreach (PileStorage pileStorage in array) { if (pileStorage.Managed) { num++; string text; try { text = PileData.Validate(PileData.ReadInventory(pileStorage.View.GetZDO()), pileStorage.Capacity, pileStorage.Resource).ToString(); } catch (Exception ex) { text = "PRESERVED ERROR: " + ex.Message; } terminal.AddString(pileStorage.Definition.Prefab + " " + pileStorage.Id + ": " + pileStorage.Resource + "=" + text + "/" + pileStorage.Capacity + ", ready=" + pileStorage.Ready + ", owner=" + pileStorage.View.IsOwner()); } } terminal.AddString("Loaded managed piles: " + num + ". Old unmarked piles remain vanilla."); } private unsafe static void Convert(ConsoleEventArgs args) { //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0442: Unknown result type (might be due to invalid IL or missing references) //IL_0525: Unknown result type (might be due to invalid IL or missing references) //IL_052a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || ZNet.instance.GetPeers().Count != 0 || (Object)(object)Player.m_localPlayer == (Object)null || ZDOMan.instance == null || (Object)(object)ObjectDB.instance == (Object)null) { args.Context.AddString("Rollback is available only to the local host with no connected peers. Load a local copy for uninstall migration."); return; } try { Dictionary<ZDOID, ZDO> obj = Traverse.Create((object)ZDOMan.instance).Field<Dictionary<ZDOID, ZDO>>("m_objectsByID").Value ?? throw new InvalidOperationException("Cannot inspect the complete world; no changes made."); List<Plan> list = new List<Plan>(); Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal); Dictionary<string, int> dictionary2 = new Dictionary<string, int>(StringComparer.Ordinal); ZDO[] array = obj.Values.Where(PileData.IsManaged).ToArray(); Inventory inventory; int num2; checked { foreach (ZDO zdo in array) { PileDefinition pileDefinition = PileData.Definition(zdo); GameObject prefab = ZNetScene.instance.GetPrefab(pileDefinition.Prefab); int num = PileData.BuildCost((prefab != null) ? prefab.GetComponent<Piece>() : null); if (num <= 0) { throw new InvalidOperationException("Cannot determine refundable recipe: " + pileDefinition.Prefab); } Inventory val = PileData.ReadInventory(zdo); list.Add(new Plan { Zdo = zdo, Definition = pileDefinition, Cost = num, Contents = val }); dictionary.TryGetValue(pileDefinition.ItemPrefab, out var value); dictionary[pileDefinition.ItemPrefab] = value + num; dictionary2.TryGetValue(pileDefinition.ItemPrefab, out var value2); dictionary2[pileDefinition.ItemPrefab] = value2 + PileTransfers.Count(val, pileDefinition.ItemPrefab); } args.Context.AddString("Managed piles in entire world (including unloaded zones): " + list.Count); foreach (KeyValuePair<string, int> item in dictionary.OrderBy((KeyValuePair<string, int> p) => p.Key)) { args.Context.AddString(item.Key + ": drop " + dictionary2[item.Key] + ", reserve " + item.Value + " from player inventory to restore refundable vanilla piles."); } if (args.Args.Length != 2 || !args.Args[1].Equals("confirm", StringComparison.OrdinalIgnoreCase)) { args.Context.AddString("No changes made. Close containers, then run ConvertManagedPilesBackToVanilla confirm."); return; } PileStorage[] array2 = Object.FindObjectsByType<PileStorage>((FindObjectsSortMode)0); foreach (PileStorage pileStorage in array2) { if (pileStorage.Managed && (Object)(object)pileStorage.Container != (Object)null && pileStorage.Container.IsInUse()) { throw new InvalidOperationException("Close every pile inventory before conversion."); } } inventory = ((Humanoid)Player.m_localPlayer).GetInventory(); foreach (KeyValuePair<string, int> item2 in dictionary) { if (PileTransfers.Count(inventory, item2.Key) < item2.Value) { throw new InvalidOperationException("Need " + item2.Value + " " + item2.Key + ". No changes made."); } } num2 = 0; } foreach (Plan plan in list) { SpendReserve(inventory, plan.Definition.ItemPrefab, plan.Cost); bool dropped = false; try { plan.Zdo.SetOwner(ZDOMan.GetSessionID()); PileTransfers.DropContents(plan.Contents, plan.Zdo.GetPosition(), delegate { dropped = true; plan.Zdo.Set(PileData.ItemsKey, PileData.Serialize(plan.Contents)); }); GameObject val2 = ZNetScene.instance.FindInstance(plan.Zdo.m_uid); if ((Object)(object)val2 != (Object)null) { val2.GetComponent<PileStorage>()?.DetachContainer(); } plan.Zdo.RemoveByteArray(PileData.ItemsKey); plan.Zdo.RemoveInt(PileData.CapacityKey); plan.Zdo.RemoveBool(StringExtensionMethods.GetStableHashCode("addedDefaultItems")); plan.Zdo.RemoveInt(StringExtensionMethods.GetStableHashCode("inUse")); plan.Zdo.RemoveInt(PileData.VersionKey); num2++; string[] obj2 = new string[8] { "Rollback ", plan.Definition.Prefab, " ", null, null, null, null, null }; ZDOID uid = plan.Zdo.m_uid; obj2[3] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj2[4] = ": contents dropped; reserved "; obj2[5] = plan.Cost.ToString(); obj2[6] = " "; obj2[7] = plan.Definition.ItemPrefab; Plugin.Trace(string.Concat(obj2)); } catch { if (!dropped) { RefundReserve(inventory, plan.Definition.ItemPrefab, plan.Cost); } throw; } } args.Context.AddString("Converted " + num2 + " piles. Contents are at each pile. Save and exit before removing the mod."); } catch (Exception ex) { Plugin.Error("Rollback stopped; inspect log before retrying", ex); args.Context.AddString("Rollback stopped: " + ex.Message); } } private static void SpendReserve(Inventory inventory, string resource, int amount) { ItemData[] array = inventory.GetAllItems().ToArray(); foreach (ItemData val in array) { if (!(PileData.ItemName(val) != resource)) { int num = Math.Min(amount, val.m_stack); if (!inventory.RemoveItem(val, num)) { throw new InvalidOperationException("Cannot reserve " + resource); } amount -= num; if (amount == 0) { return; } } } throw new InvalidOperationException("Player resource changed during rollback: " + resource); } private static void RefundReserve(Inventory inventory, string resource, int amount) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) ItemData val = PileData.NativeTemplate(resource).Clone(); val.m_dropPrefab = ObjectDB.instance.GetItemPrefab(resource); while (amount > 0) { int num = PileRules.StockChunk(amount, val.m_shared.m_maxStackSize); int num2 = PileTransfers.Count(inventory, resource); inventory.AddItem(PileData.VanillaCopy(val, num)); int num3 = Math.Max(0, Math.Min(num, PileTransfers.Count(inventory, resource) - num2)); if (num3 < num) { ItemDrop.DropItem(PileData.VanillaCopy(val, num - num3), 0, ((Component)Player.m_localPlayer).transform.position + Vector3.up, Quaternion.identity); } amount -= num; } } } internal static class InventoryAdmission { internal static bool Allows(Inventory inventory, MethodBase method, object[] args) { //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) if (!PileStorage.Inventories.TryGetValue(inventory, out var value)) { return true; } if (value.Hydrating || ((Object)(object)value.Container != (Object)null && PileStorage.Loading.Invoke(value.Container))) { return true; } if (!value.CanMutate) { return false; } ItemData val = null; string text = null; int num = -1; ParameterInfo[] parameters = method.GetParameters(); for (int i = 0; i < args.Length; i++) { string name = parameters[i].Name; object obj = args[i]; ItemData val2 = (ItemData)((obj is ItemData) ? obj : null); if (val2 != null) { val = val2; text = PileData.ItemName(val2); continue; } object obj2 = args[i]; GameObject val3 = (GameObject)((obj2 is GameObject) ? obj2 : null); if (val3 != null) { text = Utils.GetPrefabName(((Object)val3).name); } else if (args[i] is string text2 && i == 0) { text = text2; } else if (args[i] is int num2) { if (name == "amount" || name == "stack") { num = num2; } if ((name == "x" || name == "y") && num2 != 0) { return false; } } else if (args[i] is Vector2i val4 && (val4.x != 0 || val4.y != 0)) { return false; } } if (num <= 0) { num = val?.m_stack ?? 1; } try { int stored = PileData.Validate(inventory, value.Capacity, value.Resource); bool flag = PileRules.CanInsert(value.Resource, text, stored, num, value.Capacity); if (!flag) { Plugin.Trace("Rejected insert into " + value.Id + ": " + text + " x" + num + ", stored=" + stored + "/" + value.Capacity); } return flag; } catch (Exception ex) { value.Fail("Admission check failed", ex); return false; } } internal static bool AllowsMove(Inventory destination, Inventory source, ItemData item, int amount) { if (PileStorage.Inventories.TryGetValue(source, out var value) && !value.CanMutate) { return false; } if (!PileStorage.Inventories.TryGetValue(destination, out var value2)) { return true; } if (source == destination) { return true; } if (value2.CanMutate) { return PileRules.CanInsert(value2.Resource, PileData.ItemName(item), PileData.Validate(destination, value2.Capacity, value2.Resource), amount, value2.Capacity); } return false; } } [HarmonyPatch] internal static class BooleanAdmissionPatch { private static IEnumerable<MethodBase> TargetMethods() { return from m in AccessTools.GetDeclaredMethods(typeof(Inventory)) where (m.Name == "AddItem" || m.Name == "CanAddItem") && m.ReturnType == typeof(bool) select m; } [HarmonyPrefix] private static bool Prefix(Inventory __instance, MethodBase __originalMethod, object[] __args, ref bool __result) { if (InventoryAdmission.Allows(__instance, __originalMethod, __args)) { return true; } __result = false; return false; } } [HarmonyPatch] internal static class ItemAdmissionPatch { private static IEnumerable<MethodBase> TargetMethods() { return from m in AccessTools.GetDeclaredMethods(typeof(Inventory)) where m.Name == "AddItem" && m.ReturnType == typeof(ItemData) select m; } [HarmonyPrefix] private static bool Prefix(Inventory __instance, MethodBase __originalMethod, object[] __args, ref ItemData __result) { if (InventoryAdmission.Allows(__instance, __originalMethod, __args)) { return true; } __result = null; return false; } } [HarmonyPatch(typeof(InventoryGrid), "DropItem")] internal static class GridDropPatch { [HarmonyPrefix] private static bool Prefix(Inventory ___m_inventory, Inventory __0, ItemData __1, int __2, Vector2i __3, ref bool __result) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) bool flag = PileStorage.Inventories.ContainsKey(___m_inventory); bool flag2 = PileStorage.Inventories.ContainsKey(__0); if (!flag && !flag2) { return true; } if (__1 == null || !InventoryAdmission.AllowsMove(___m_inventory, __0, __1, __2)) { __result = false; return false; } ItemData itemAt = ___m_inventory.GetItemAt(__3.x, __3.y); if (itemAt != __1 && itemAt != null && !itemAt.IsSameType(__1)) { __result = false; return false; } if (flag && (__3.x != 0 || __3.y != 0)) { __result = false; return false; } return true; } } [HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[] { typeof(Inventory), typeof(ItemData) })] internal static class MoveWholePatch { [HarmonyPrefix] private static bool Prefix(Inventory __instance, Inventory __0, ItemData __1) { if (__1 == null || !InventoryAdmission.AllowsMove(__instance, __0, __1, __1.m_stack)) { return false; } bool complete; return !PileTransfers.TryWithdraw(__instance, __0, __1, __1.m_stack, out complete); } } [HarmonyPatch(typeof(Inventory), "MoveItemToThis", new Type[] { typeof(Inventory), typeof(ItemData), typeof(int), typeof(int), typeof(int) })] internal static class MoveAmountPatch { [HarmonyPrefix] private static bool Prefix(Inventory __instance, Inventory __0, ItemData __1, int __2, int __3, int __4, ref bool __result) { if (__1 != null && InventoryAdmission.AllowsMove(__instance, __0, __1, __2)) { if (PileTransfers.TryWithdraw(__instance, __0, __1, __2, out var complete, __3, __4)) { __result = complete; return false; } return true; } __result = false; return false; } } [HarmonyPatch(typeof(Inventory), "StackAll")] internal static class StackAllPatch { [HarmonyPrefix] private static bool Prefix(Inventory __instance, ref int __result) { if (!PileStorage.Inventories.ContainsKey(__instance)) { return true; } __result = 0; return false; } } [HarmonyPatch(typeof(InventoryGui), "OnSelectedItem")] internal static class PartialPileInventoryShortcutPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(InventoryGui __instance, InventoryGrid __0, ItemData __1, Modifier __3, ItemData ___m_dragItem) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if ((int)__3 != 0 || ___m_dragItem != null || !PartialPilePlacement.IsSupported(__1) || !ZInput.GetMouseButton(0) || (!ZInput.GetKey((KeyCode)308, true) && !ZInput.GetKey((KeyCode)307, true)) || (Object)(object)Player.m_localPlayer == (Object)null || __0.GetInventory() != ((Humanoid)Player.m_localPlayer).GetInventory()) { return true; } PartialPilePlacement.Arm(__instance, __0, __1); return false; } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Piece), typeof(RequirementMode) })] internal static class PartialPileRequirementsPatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Expected O, but got Unknown //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); FieldInfo field = typeof(Requirement).GetField("m_amount"); PartialPilePlacement.RequirementsHook = list.Count((CodeInstruction i) => CodeInstructionExtensions.LoadsField(i, field, false)) == 2; if (!PartialPilePlacement.RequirementsHook) { Plugin.Error("Partial piles disabled: requirement API changed."); return list; } List<CodeInstruction> list2 = new List<CodeInstruction>(); foreach (CodeInstruction item in list) { if (!CodeInstructionExtensions.LoadsField(item, field, false)) { list2.Add(item); continue; } list2.Add(CodeInstructionExtensions.MoveBlocksFrom(CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Ldarg_0, (object)null), item), item)); list2.Add(new CodeInstruction(OpCodes.Ldarg_1, (object)null)); list2.Add(new CodeInstruction(OpCodes.Call, (object)typeof(PartialPilePlacement).GetMethod("RequirementAmount", BindingFlags.Static | BindingFlags.NonPublic))); } return list2; } [HarmonyPostfix] private static void Postfix(Player __instance, Piece __0, ref bool __result) { __result &= PartialPilePlacement.ExtraRequirements(__instance, __0); } } [HarmonyPatch(typeof(Requirement), "GetAmount")] internal static class PartialPileRequirementDisplayPatch { [HarmonyPostfix] private static void Postfix(Requirement __instance, ref int __result) { __result = PartialPilePlacement.DisplayAmount(__instance, __result); } } [HarmonyPatch(typeof(Player), "UpdatePlacement")] internal static class PartialPilePlacementFramePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(Player __instance, out PartialPilePlacement.Frame __state) { __state = PartialPilePlacement.BeginFrame(__instance); } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, PartialPilePlacement.Frame __state) { PartialPilePlacement.EndFrame(__state); return __exception; } [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); MethodInfo target = typeof(Player).GetMethod("ConsumeResources"); PartialPilePlacement.ConsumptionHook = list.Count((CodeInstruction i) => CodeInstructionExtensions.Calls(i, target)) == 1; int num = list.FindIndex((CodeInstruction i) => CodeInstructionExtensions.Calls(i, target)); FieldInfo field = typeof(Piece).GetField("m_resources"); int num2 = num - 5; PartialPilePlacement.ConsumptionHook &= num2 >= 0 && CodeInstructionExtensions.LoadsField(list[num2 + 1], field, false) && (list[num2].opcode == OpCodes.Ldloc || list[num2].opcode == OpCodes.Ldloc_S || list[num2].opcode == OpCodes.Ldloc_0 || list[num2].opcode == OpCodes.Ldloc_1 || list[num2].opcode == OpCodes.Ldloc_2 || list[num2].opcode == OpCodes.Ldloc_3) && list.Skip(num2 + 2).Take(3).All((CodeInstruction i) => i.opcode.Name.StartsWith("ldc.i4", StringComparison.Ordinal)); if (!PartialPilePlacement.ConsumptionHook) { Plugin.Error("Partial piles disabled: placement consumption API changed."); return list; } CodeInstruction item = CodeInstructionExtensions.MoveBlocksFrom(CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(list[num2].opcode, list[num2].operand), list[num]), list[num]); list[num].opcode = OpCodes.Call; list[num].operand = typeof(PartialPilePlacement).GetMethod("Consume", BindingFlags.Static | BindingFlags.NonPublic); list.Insert(num, item); return list; } } [HarmonyPatch(typeof(Player), "TryPlacePiece")] internal static class PartialPileTryPlacePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(Player __instance, Piece __0, ref bool __result) { if (PartialPilePlacement.BeforeTryPlace(__instance, __0)) { return true; } __result = false; return false; } [HarmonyFinalizer] private static Exception Finalizer(Player __instance, Piece __0, ref bool __result, Exception __exception) { return PartialPilePlacement.FinishTryPlace(__instance, __0, ref __result, __exception); } } [HarmonyPatch(typeof(Player), "PlacePiece")] internal static class PartialPileSpawnPatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown List<CodeInstruction> list = instructions.ToList(); MethodInfo instantiate = typeof(Object).GetMethods().Single((MethodInfo m) => m.Name == "Instantiate" && m.IsGenericMethodDefinition && m.GetParameters().Length == 3 && m.GetParameters()[1].ParameterType == typeof(Vector3) && m.GetParameters()[2].ParameterType == typeof(Quaternion)).MakeGenericMethod(typeof(GameObject)); PartialPilePlacement.SpawnHook = list.Count((CodeInstruction i) => CodeInstructionExtensions.Calls(i, instantiate)) == 1; if (!PartialPilePlacement.SpawnHook) { Plugin.Error("Partial piles disabled: native spawn API changed."); return list; } List<CodeInstruction> list2 = new List<CodeInstruction>(); foreach (CodeInstruction item in list) { if (!CodeInstructionExtensions.Calls(item, instantiate)) { list2.Add(item); continue; } list2.Add(CodeInstructionExtensions.MoveBlocksFrom(CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Ldarg_0, (object)null), item), item)); list2.Add(new CodeInstruction(OpCodes.Call, (object)typeof(PartialPilePlacement).GetMethod("Instantiate", BindingFlags.Static | BindingFlags.NonPublic))); } return list2; } } [HarmonyPatch(typeof(Player), "UpdatePlacementGhost")] internal static class PartialPileGhostPatch { [HarmonyPostfix] private static void Postfix(Player __instance, GameObject ___m_placementGhost) { PartialPilePlacement.RefreshGhost(__instance, ___m_placementGhost); } } [HarmonyPatch] internal static class PileBuildResourceScopePatch { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(Player), "HaveRequirements", new Type[2] { typeof(Piece), typeof(RequirementMode) }, (Type[])null); yield return AccessTools.Method(typeof(Player), "TryPlacePiece", new Type[1] { typeof(Piece) }, (Type[])null); yield return AccessTools.Method(typeof(Hud), "SetupPieceInfo", new Type[1] { typeof(Piece) }, (Type[])null); } [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(Piece __0, out PileResourceScope __state) { __state = PileBuildAccess.Begin(__0); } [HarmonyFinalizer] [HarmonyPriority(0)] private static void Finalizer(PileResourceScope __state) { __state?.Dispose(); } } [HarmonyPatch(typeof(Container), "IsInUse")] internal static class PileBuildBusyPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Container __instance, ref bool __result) { if (PileBuildAccess.IsUnavailable(__instance)) { __result = true; } } } [HarmonyPatch(typeof(Container), "CheckAccess", new Type[] { typeof(long) })] internal static class PileBuildAccessPatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix(Container __instance, ref bool __result) { if (PileBuildAccess.IsUnavailable(__instance)) { __result = false; } } } [HarmonyPatch(typeof(Container), "Load")] internal static class PileBuildLoadScopePatch { [HarmonyPrefix] [HarmonyPriority(800)] private static void Prefix(out PileResourceScope __state) { __state = (PileResourceScope.BlockPileAccess ? new PileResourceScope(PileResourceScope.ExcludePiles, blockAccess: false) : null); } [HarmonyFinalizer] [HarmonyPriority(0)] private static void Finalizer(PileResourceScope __state) { __state?.Dispose(); } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(int), typeof(ItemData), typeof(bool) })] internal static class RestoreExtendedStackPatch { [HarmonyPrefix] [HarmonyPriority(800)] internal static bool Prefix(Inventory __instance, int __0, ItemData __1, ref bool __result) { PileHydration pileHydration = PileHydration.Find(__instance); if (pileHydration == null) { return true; } ItemData val = PileData.NativeTemplate(pileHydration.Definition.ItemPrefab); if (__0 != StringExtensionMethods.GetStableHashCode(pileHydration.Definition.ItemPrefab) || __1 == null || __1.m_stack <= 0 || __1.m_stack > pileHydration.Capacity || __1.m_gridPos.x != 0 || __1.m_gridPos.y != 0 || __instance.GetAllItems().Count != 0) { throw new InvalidDataException("Invalid extended pile stack; original payload retained."); } if (pileHydration.Capacity <= val.m_shared.m_maxStackSize) { return true; } __1.m_dropPrefab = ObjectDB.instance.GetItemPrefab(pileHydration.Definition.ItemPrefab); __1.m_shared = val.m_shared; PileData.NormalizeStoredItem(__1, pileHydration.Capacity); __instance.GetAllItems().Add(__1); __result = true; return false; } } [HarmonyPatch(typeof(Inventory), "AddItem", new Type[] { typeof(int), typeof(int), typeof(float), typeof(Vector2i), typeof(bool), typeof(int), typeof(int), typeof(long), typeof(string), typeof(Dictionary<string, string>), typeof(int), typeof(bool), typeof(bool), typeof(bool) })] internal static class RestoreExtendedLegacyStackPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(Inventory __instance, int __0, int __1, float __2, Vector2i __3, bool __4, int __5, int __6, long __7, string __8, Dictionary<string, string> __9, int __10, bool __11, bool __12, ref bool __result) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) PileHydration pileHydration = PileHydration.Find(__instance); if (pileHydration == null) { return true; } ItemData val = PileData.NativeTemplate(pileHydration.Definition.ItemPrefab).Clone(); val.m_stack = __1; val.m_durability = __2; val.m_gridPos = __3; val.m_equipped = __4; val.m_quality = __5; val.m_variant = __6; val.m_crafterID = __7; val.m_crafterName = __8; val.m_customData = __9 ?? new Dictionary<string, string>(); val.m_worldLevel = __10; val.m_pickedUp = __11; val.m_cheated = __12; return RestoreExtendedStackPatch.Prefix(__instance, __0, val, ref __result); } } [HarmonyPatch(typeof(Inventory), "MoveAll")] internal static class MoveExtendedPileAllPatch { [HarmonyPrefix] private static bool Prefix(Inventory __instance, Inventory __0) { if (!PileTransfers.NeedsStockSplit(__0) || PileStorage.Inventories.ContainsKey(__instance)) { return true; } ItemData[] array = __0.GetAllItems().ToArray(); foreach (ItemData val in array) { PileTransfers.TryWithdraw(__instance, __0, val, val.m_stack, out var _); } return false; } } [HarmonyPatch(typeof(Container), "OnDestroyed")] internal static class DropExtendedPilePatch { [HarmonyPrefix] private static bool Prefix(Container __instance) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (!PileData.IsManaged(((Component)__instance).gameObject) || !PileTransfers.NeedsStockSplit(__instance.GetInventory())) { return true; } PileStorage component = ((Component)__instance).GetComponent<PileStorage>(); if ((Object)(object)component != (Object)null && component.Ready && !component.Faulted && component.View.IsOwner()) { PileTransfers.DropContents(__instance.GetInventory(), ((Component)__instance).transform.position); } return false; } } [HarmonyPatch(typeof(Piece), "Awake")] internal static class PieceAwakePatch { [HarmonyPostfix] private static void Postfix(Piece __instance) { PileStorage.Attach(__instance); } } [HarmonyPatch(typeof(Piece), "OnPlaced")] internal static class PlacementPatch { [HarmonyPostfix] private static void Postfix(Piece __instance) { PileStorage.Attach(__instance)?.OnPlaced(); } } [HarmonyPatch(typeof(Container), "Awake")] internal static class ContainerAwakePatch { [HarmonyPrefix] private static void Prefix(Container __instance) { //IL_003d: 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_004d: Expected O, but got Unknown if (PileData.IsManaged(((Component)__instance).gameObject)) { __instance.m_width = 1; __instance.m_height = 1; __instance.m_name = ((Component)__instance).GetComponent<Piece>().m_name; __instance.m_autoDestroyEmpty = false; __instance.m_checkGuardStone = true; __instance.m_privacy = (PrivacySetting)2; __instance.m_defaultItems = new DropTable(); __instance.m_destroyedLootPrefab = null; ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.GetPrefab("piece_chest_wood") : null); Container val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<Container>() : null); if ((Object)(object)val2 != (Object)null) { __instance.m_bkg = val2.m_bkg; } } } [HarmonyPostfix] private static void Postfix(Container __instance) { if (PileData.IsManaged(((Component)__instance).gameObject)) { ((Component)__instance).GetComponent<PileStorage>()?.Bind(__instance); } } } [HarmonyPatch(typeof(Container), "AddDefaultItems")] internal static class NoDefaultItemsPatch { [HarmonyPrefix] private static bool Prefix(Container __instance) { return !PileData.IsManaged(((Component)__instance).gameObject); } } [HarmonyPatch(typeof(Container), "Save")] internal static class SavePatch { [HarmonyPrefix] private static bool Prefix(Container __instance) { if (PileData.IsManaged(((Component)__instance).gameObject)) { return ((Component)__instance).GetComponent<PileStorage>()?.ValidateForSave() ?? false; } return true; } [HarmonyPostfix] private static void Postfix(Container __instance) { ((Component)__instance).GetComponent<PileStorage>()?.ObserveChange(); } } [HarmonyPatch(typeof(Container), "Load")] internal static class LoadPatch { [HarmonyPrefix] private static bool Prefix(Container __instance, ref bool __result, out PileHydration __state) { __state = null; if (!PileData.IsManaged(((Component)__instance).gameObject)) { return true; } PileStorage component = ((Component)__instance).GetComponent<PileStorage>(); if ((Object)(object)component != (Object)null && !component.Faulted && (__instance.IsInUse() || component.ValidateForLoad())) { __state = PileHydration.Begin(__instance.GetInventory(), component.Definition, component.Capacity); return true; } __result = false; return false; } [HarmonyPostfix] private static void Postfix(Container __instance, PileHydration __state) { __state?.Dispose(); ((Component)__instance).GetComponent<PileStorage>()?.ObserveChange(); } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception, PileHydration __state) { __state?.Dispose(); return __exception; } } [HarmonyPatch(typeof(Container), "Interact")] internal static class InteractPatch { [HarmonyPrefix] private static bool Prefix(Container __instance, ref bool __result) { if (!PileData.IsManaged(((Component)__instance).gameObject)) { return true; } PileStorage component = ((Component)__instance).GetComponent<PileStorage>(); if (component != null && component.EnsureReady()) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Container), "RPC_OpenResponse")] internal static class OpenPatch { [HarmonyPrefix] private static bool Prefix(Container __instance, bool __1) { if (!PileData.IsManaged(((Component)__instance).gameObject)) { return true; } if (!__1) { Plugin.Trace("Network open denied (busy/access check): " + ((Component)__instance).GetComponent<PileStorage>()?.Id); return true; } PileStorage component = ((Component)__instance).GetComponent<PileStorage>(); if ((Object)(object)component == (Object)null || !component.EnsureReady() || !component.ValidateForLoad()) { return false; } AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null).Invoke(__instance, null); Plugin.Trace("Open " + component.Id + ": " + component.Count() + " " + component.Resource + "."); return true; } } [HarmonyPatch(typeof(Container), "RPC_RequestOpen")] internal static class RequestOpenPatch { [HarmonyPrefix] private static bool Prefix(Container __instance, long __0) { if (!PileData.IsManaged(((Component)__instance).gameObject)) { return true; } PileStorage component = ((Component)__instance).GetComponent<PileStorage>(); if ((Object)(object)component == (Object)null || !component.View.IsOwner()) { return true; } if (component.EnsureReady() && component.ValidateForLoad()) { return true; } Plugin.Error("Network open refused for invalid managed data: " + component.Id); component.View.InvokeRPC(__0, "RPC_OpenResponse", new object[1] { false }); return false; } } [HarmonyPatch(typeof(Container), "SetInUse")] internal static class UsePatch { [HarmonyPostfix] private static void Postfix(Container __instance, bool __0, bool ___m_inUse) { if (!__0 && !___m_inUse && PileData.IsManaged(((Component)__instance).gameObject)) { Plugin.Trace("Closed " + ((Component)__instance).GetComponent<PileStorage>()?.Id); } } } [HarmonyPatch(typeof(Container), "CanBeRemoved")] internal static class ContainerRemovePatch { [HarmonyPrefix] private static bool Prefix(Container __instance, ref bool __result) { if (!PileData.IsManaged(((Component)__instance).gameObject)) { return true; } PileStorage component = ((Component)__instance).GetComponent<PileStorage>(); __result = (Object)(object)component != (Object)null && component.EnsureReady() && !__instance.IsInUse(); return false; } } [HarmonyPatch(typeof(Piece), "DropResources")] internal static class NoBuildRefundPatch { [HarmonyPrefix] private static bool Prefix(Piece __instance) { if (!PileData.IsManaged(((Component)__instance).gameObject)) { return true; } Plugin.Trace("Suppressed vanilla full-cost refund: " + ((Component)__instance).GetComponent<PileStorage>()?.Id); return false; } } [HarmonyPatch(typeof(WearNTear), "Destroy")] internal static class DestructionPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool Prefix(WearNTear __instance) { if (!PileData.IsManaged(((Component)__instance).gameObject)) { return true; } PileStorage pileStorage = PileStorage.Attach(((Component)__instance).GetComponent<Piece>()); if ((Object)(object)pileStorage != (Object)null) { return pileStorage.PrepareDestruction(); } return false; } } internal static class PartialPilePlacement { internal sealed class Request { internal Player Player; internal Inventory Inventory; internal ItemData Source; internal ItemData Hammer; internal ItemData Previous; internal Piece Piece; internal Requirement Requirement; internal PileDefinition Definition; internal int Amount; internal int ArmedFrame; internal GameObject Ghost; internal IPileDisplay Display; internal int Capacity => Definition.VanillaCost; internal string Resource => Definition.ItemPrefab; } internal sealed class Frame { internal Player Player; internal Request Request; internal PartialPlacementTransaction Transaction; internal GameObject Spawn; internal bool Stop; } private sealed class PlacementFailed : Exception { internal PlacementFailed(Exception inner) : base("Partial pile placement failed.", inner) { } } private static class Native { internal static readonly FieldRef<Humanoid, ItemData> RightItem = AccessTools.FieldRefAccess<Humanoid, ItemData>("m_rightItem"); internal static readonly FieldRef<Player, PlacementStatus> PlacementStatus = AccessTools.FieldRefAccess<Player, PlacementStatus>("m_placementStatus"); internal static readonly MethodInfo SetGhostValid = AccessTools.Method(typeof(Player), "SetPlacementGhostValid", (Type[])null, (Type[])null); } internal static bool RequirementsHook; internal static bool SpawnHook; internal static bool ConsumptionHook; private static Request _request; private static Frame _frame; internal static bool Available { get { if (RequirementsHook && SpawnHook) { return ConsumptionHook; } return false; } } internal static bool IsSupported(ItemData item) { if (item != null) { return PartialPileRules.Supports(PileData.ItemName(item)); } return false; } private static bool Matches(Player player, Piece piece) { if (_request != null && (Object)(object)_request.Player == (Object)(object)player && (Object)(object)piece == (Object)(object)_request.Piece) { return (Object)(object)player == (Object)(object)Player.m_localPlayer; } return false; } private static bool SourceUnchanged(Request request) { if ((Object)(object)request.Player != (Object)null && ((Humanoid)request.Player).GetInventory() == request.Inventory && PartialPileRules.Unchanged(request.Definition, request.Amount, request.Source.m_stack, request.Inventory.ContainsItem(request.Source))) { return PileData.ItemName(request.Source) == request.Resource; } return false; } private static bool RecipeMatches(Request request) { if ((Object)(object)request.Piece != (Object)null && request.Piece.m_resources != null && request.Piece.m_resources.Length == 1 && request.Piece.m_resources[0] == request.Requirement) { return PileData.BuildCost(request.Piece) == request.Capacity; } return false; } private static bool HammerUsable(Request request) { if (request.Inventory.ContainsItem(request.Hammer) && Native.RightItem.Invoke((Humanoid)(object)request.Player) == request.Hammer) { return IsHammer(request.Hammer); } return false; } private static bool IsHammer(ItemData item) { if (PileData.ItemName(item) == "Hammer" && (Object)(object)item.m_shared.m_buildPieces != (Object)null) { if (item.m_shared.m_useDurability) { return item.m_durability > 0f; } return true; } return false; } private static bool NearWorkbench(Player player) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) ZNetScene instance = ZNetScene.instance; GameObject val = ((instance != null) ? instance.GetPrefab("piece_workbench") : null); CraftingStation val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<CraftingStation>() : null); if ((Object)(object)val2 != (Object)null) { return (Object)(object)CraftingStation.HaveBuildStationInRange(val2.m_name, ((Component)player).transform.position) != (Object)null; } return false; } private static void Message(Player player, string text) { if (player != null) { ((Character)player).Message((MessageType)2, text, 0, (Sprite)null, false); } } internal static void Arm(InventoryGui gui, InventoryGrid grid, ItemData source) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting() || grid.GetInventory() != ((Humanoid)localPlayer).GetInventory()) { return; } if (!Available || !Plugin.EnablePileStorage.Value) { Message(localPlayer, "Установка неполной стопки недоступна."); return; } string text = PileData.ItemName(source); PileDefinition pileDefinition = PartialPileRules.Select(text, source.m_stack); if (pileDefinition == null) { Message(localPlayer, "Стак не помещается в pile. Разделите его: максимум " + PartialPileRules.Limit(text) + " шт."); return; } ZNetScene instance = ZNetScene.instance; object obj; if (instance == null) { obj = null; } else { GameObject prefab = instance.GetPrefab(pileDefinition.Prefab); obj = ((prefab != null) ? prefab.GetComponent<Piece>() : null); } Piece val = (Piece)obj; if ((Object)(object)val == (Object)null || !val.m_enabled || PileData.BuildCost(val) != pileDefinition.VanillaCost || val.m_resources.Length != 1 || (Object)(object)((Component)val).GetComponent<Container>() != (Object)null) { Message(localPlayer, "Рецепт выбранного pile несовместим с этим режимом."); return; } if (!NearWorkbench(localPlayer)) { Message(localPlayer, "Нужен верстак поблизости."); return; } Inventory inventory = ((Humanoid)localPlayer).GetInventory(); ItemData val2 = Native.RightItem.Invoke((Humanoid)(object)localPlayer); ItemData val3 = (ItemData)(IsHammer(val2) ? ((object)val2) : ((object)((IEnumerable<ItemData>)inventory.GetAllItems()).FirstOrDefault((Func<ItemData, bool>)IsHammer))); if (val3 == null) { Message(localPlayer, "Нужен исправный молот в инвентаре."); return; } Cancel(stow: false); Request request = new Request { Player = localPlayer, Inventory = inventory, Source = source, Hammer = val3, Previous = val2, Piece = val, Definition = pileDefinition, Requirement = val.m_resources[0], Amount = source.m_stack, ArmedFrame = Time.frameCount }; if ((Native.RightItem.Invoke((Humanoid)(object)localPlayer) != val3 && !((Humanoid)localPlayer).EquipItem(val3, false)) || Native.RightItem.Invoke((Humanoid)(object)localPlayer) != val3 || !localPlayer.SetSelectedPiece(val)) { Message(localPlayer, "Не удалось выбрать этот pile молотом."); return; } _request = request; gui.Hide(); Hud.HidePieceSelection(); ZInput.IgnoreMouseInputForFrames(2); Message(localPlayer, Localization.instance.Localize(source.m_shared.m_name) + ": " + request.Amount + " шт. ЛКМ: поставить, ПКМ / Esc: отмена."); Plugin.Trace("Armed partial " + text + "=" + request.Amount + ", " + pileDefinition.Prefab + "; inventory unchanged."); } internal static int RequirementAmount(Requirement requirement, Player player, Piece piece) { if (!Matches(player, piece) || requirement != _request.Requirement) { return requirement.m_amount; } return _request.Amount; } internal static int DisplayAmount(Requirement requirement, int original) { if (_request == null || requirement != _request.Requirement) { return original; } return _request.Amount; } internal static bool ExtraRequirements(Player player, Piece piece) { if (Matches(player, piece)) { if (SourceUnchanged(_request) && RecipeMatches(_request) && HammerUsable(_request)) { return NearWorkbench(player); } return false; } return true; } internal static Frame BeginFrame(Player player) { if (_request == null || (Object)(object)_request.Player != (Object)(object)player) { return null; } if (!Plugin.EnablePileStorage.Value || !Available || ((Character)player).IsDead() || ((Character)player).IsTeleporting() || !SourceUnchanged(_request) || !RecipeMatches(_request) || !HammerUsable(_request)) { Message(player, "Установка отменена: выбранный стак или молот изменился."); Cancel(stow: true); return null; } if (ZInput.GetMouseButtonDown(1) || ZInput.GetKeyDown((KeyCode)27, true)) { Cancel(stow: true); return null; } if ((Object)(object)player.GetSelectedPiece() != (Object)(object)_request.Piece) { Cancel(stow: false); return null; } _frame = new Frame { Player = player, Request = _request }; return _frame; } internal static bool BeforeTryPlace(Player player, Piece piece) { if ((Object)(object)_frame?.Player == (Object)(object)player && _frame.Transaction != null) { return false; } if (_request == null || (Object)(object)_request.Player != (Object)(object)player) { return true; } if (!Matches(player, piece)) { Cancel(stow: false); return true; } if (Time.frameCount <= _request.ArmedFrame + 1) { return false; } if (_frame == null || (Object)(object)_frame.Player != (Object)(object)player || _frame.Transaction != null) { return false; } if (!ExtraRequirements(player, piece)) { Message(player, "Нужны выбранный стак, исправный молот и верстак поблизости."); return false; } if (!player.HaveRequirements(piece, (RequirementMode)0)) { return false; } _frame.Transaction = new PartialPlacementTransaction(); return true; } internal static GameObject Instantiate(GameObject prefab, Vector3 position, Quaternion rotation, Player builder) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Expected O, but got Unknown Frame frame = _frame; if (frame?.Transaction == null || (Object)(object)frame.Player != (Object)(object)builder) { return Object.Instantiate<GameObject>(prefab, position, rotation); } Request request = frame.Request; if (_request != request || (Object)(object)prefab != (Object)(object)((Component)request.Piece).gameObject) { throw new PlacementFailed(new InvalidOperationException("Partial placement was cancelled before spawning.")); } if (frame.Transaction.Claimed) { throw new PlacementFailed(new InvalidOperationException("Placement ticket already used.")); } if (!ExtraRequirements(request.Player, request.Piece)) { throw new PlacementFailed(new InvalidOperationException("Placement requirements changed.")); } ItemData contents = request.Source.Clone(); contents.m_stack = request.Amount; contents.m_gridPos = new Vector2i(0, 0); Inventory val = new Inventory(request.Piece.m_name, (Sprite)null, 1, 1); val.GetAllItems().Add(contents); PileData.Validate(val, request.Capacity, request.Resource); byte[] payload = PileData.Serialize(val); if (!frame.Transaction.Execute(delegate { if (!SourceUnchanged(request) || !request.Inventory.RemoveItem(request.Source, request.Amount)) { throw new InvalidOperationException("Selected " + request.Resource + " stack changed before payment."); } }, () => !request.Inventory.ContainsItem(request.Source), delegate { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) frame.Spawn = Object.Instantiate<GameObject>(prefab, position, rotation); ZNetView component = frame.Spawn.GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner() || !PileData.IsPile(frame.Spawn)) { throw new InvalidOperationException("Partial pile has no valid local network owner."); } ZDO zDO = component.GetZDO(); zDO.Set(PileData.ItemsKey, payload); zDO.Set(PileData.CapacityKey, request.Capacity, false); zDO.Set(PileData.VersionKey, 1, false); }, published, delegate { if (!((Object)(object)frame.Spawn == (Object)null)) { ZNetView component = frame.Spawn.GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid() || !component.IsOwner()) { throw new InvalidOperationException("Cannot discard uncommitted pile; refund blocked."); } component.Destroy(); frame.Spawn = null; } }, delegate { Refund(request, contents); })) { throw new PlacementFailed(frame.Transaction.Failure); } if (frame.Transaction.Failure != null) { Plugin.Error("Partial pile committed despite callback error; no refund", frame.Transaction.Failure); } frame.Spawn.GetComponent<PileStorage>()?.EnsureReady(); Plugin.Trace("Placed partial " + request.Resource + "=" + request.Amount + "/" + request.Capacity + ", " + request.Definition.Prefab + "; exact selected stack paid once."); return frame.Spawn; bool published() { if ((Object)(object)frame.Spawn != (Object)null && PileData.IsManaged(frame.Spawn) && frame.Spawn.GetComponent<ZNetView>().GetZDO().GetInt(PileData.CapacityKey, 0) == request.Capacity) { return frame.Spawn.GetComponent<ZNetView>().GetZDO().GetByteArray(PileData.ItemsKey, (byte[])null)?.SequenceEqual(payload) ?? false; } return false; } } internal static void Consume(Player player, Requirement[] requirements, int quality, int itemQuality, int multiplier, Piece piece) { if ((Object)(object)_frame?.Player == (Object)(object)player) { PartialPlacementTransaction transaction = _frame.Transaction; if (transaction != null && transaction.Committed && requirements == _frame.Request.Piece.m_resources) { return; } } using (PileBuildAccess.Begin(piece)) { player.ConsumeResources(requirements, quality, itemQuality, multiplier); } } private static void Refund(Request request, ItemData contents) { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: 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) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) int num = request.Amount; int maxStackSize = PileData.NativeTemplate(request.Resource).m_shared.m_maxStackSize; while (num > 0) { int num2 = PileRules.StockChunk(num, maxStackSize); int num3 = PileTransfers.Count(request.Inventory, request.Resource); Exception innerException = null; try { request.Inventory.AddItem(PileData.VanillaCopy(contents, num2)); } catch (Exception ex) { innerException = ex; } int num4 = Math.Max(0, Math.Min(num2, PileTransfers.Count(request.Inventory, request.Resource) - num3)); if (num4 < num2 && (Object)(object)ItemDrop.DropItem(PileData.VanillaCopy(contents, num2 - num4), 0, ((Component)request.Player).transform.position + Vector3.up, Quaternion.identity) == (Object)null) { throw new InvalidOperationException("Cannot refund partial " + request.Resource + " transfer.", innerException); } num -= num2; } } internal static Exception FinishTryPlace(Player player, Piece piece, ref bool result, Exception exception) { if ((Object)(object)_frame?.Player != (Object)(object)player || _frame.Request.Piece != piece) { return exception; } if ((exception == null) & result) { PartialPlacementTransaction transaction = _frame.Transaction; if (transaction == null || !transaction.Committed) { result = false; _frame.Stop = true; Plugin.Error("Partial pile stopped: native placement returned without a committed transfer."); } } PartialPlacementTransaction transaction2 = _frame.Transaction; if (transaction2 == null || !transaction2.Claimed) { _frame.Transaction = null; } if (exception is PlacementFailed) { result = false; _frame.Stop = true; Plugin.Error("Partial pile placement stopped", exception.InnerException ?? exception); Message(player, "Не удалось установить pile. Подробности в логе."); return null; } return exception; } internal static void EndFrame(Frame frame) { if (frame == null || _frame != frame) { return; } _frame = null; if (!frame.Stop) { PartialPlacementTransaction transaction = frame.Transaction; if (transaction == null || !transaction.Claimed) { return; } } Cancel(stow: true); } internal static void RefreshGhost(Player player, GameObject ghost) { if (_request == null || (Object)(object)_request.Player != (Object)(object)player || (Object)(object)ghost == (Object)null || (Object)(object)player.GetSelectedPiece() != (Object)(object)_request.Piece) { return; } try { if ((Object)(object)_request.Ghost != (Object)(object)ghost) { _request.Display?.Dispose(); _request.Ghost = ghost; Request request = _request; IPileDisplay display; if (!(_request.Definition.Prefab == "wood_stack")) { IPileDisplay pileDisplay = new NativePileDisplay(ghost.transform, _request.Definition); display = pileDisplay; } else { IPileDisplay pileDisplay = new WoodPileDisplay(ghost.transform); display = pileDisplay; } request.Display = display; } _request.Display.Refresh(_request.Amount, _request.Capacity); if (!ExtraRequirements(player, _request.Piece)) { Native.PlacementStatus.Invoke(player) = (PlacementStatus)1; Native.SetGhostValid.Invoke(player, new object[1] { false }); } } catch (Exception error) { Plugin.Error("Partial pile preview failed; placement cancelled", error); Cancel(stow: true); } } internal static void Tick() { if (_request != null && _frame == null && ((Object)(object)_request.Player == (Object)null || (Object)(object)_request.Player != (Object)(object)Player.m_localPlayer)) { Cancel(stow: false); } } private static void Cancel(bool stow) { Request request = _request; _request = null; if (request == null) { return; } request.Display?.Dispose(); if (stow && (Object)(object)request.Player != (Object)null && Native.RightItem.Invoke((Humanoid)(object)request.Player) == request.Hammer) { ((Humanoid)request.Player).UnequipItem(request.Hammer, false); if (request.Previous != null && request.Previous != request.Hammer && request.Inventory.ContainsItem(request.Previous)) { ((Humanoid)request.Player).EquipItem(request.Previous, false); } } Plugin.Trace("Partial pile placement closed; no reusable placement ticket remains."); } } internal static class PileData { internal static readonly int VersionKey = StringExtensionMethods.GetStableHashCode("VanillaPilePlus_Version"); internal static readonly int CapacityKey = StringExtensionMethods.GetStableHashCode("VanillaPilePlus_Capacity"); internal static readonly int ItemsKey = StringExtensionMethods.GetStableHashCode("items"); private static readonly Dictionary<int, PileDefinition> ByHash = PileCatalog.All.ToDictionary((PileDefinition d) => StringExtensionMethods.GetStableHashCode(d.Prefab)); private static readonly MethodInfo CopyShared = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic); internal static PileDefinition Definition(ZDO zdo) { if (zdo == null || !ByHash.TryGetValue(zdo.GetPrefab(), out var value)) { return null; } return value; } internal static bool IsPile(GameObject go) { if ((Object)(object)go == (Object)null) { return false; } PileDefinition pileDefinition = PileCatalog.Find(Utils.GetPrefabName(((Object)go).name)); if (pileDefinition == null) { return false; } ZNetView component = go.GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null && component.IsValid()) { return component.GetZDO().GetPrefab() == StringExtensionMethods.GetStableHashCode(pileDefinition.Prefab); } return false; } internal static bool IsManaged(ZDO zdo) { if (Definition(zdo) != null) { return zdo.GetInt(VersionKey, 0) > 0; } return false; } internal static bool IsManaged(GameObject go) { if (IsPile(go)) { return IsManaged(go.GetComponent<ZNetView>().GetZDO()); } return false; } internal static string ItemName(ItemData item) { if (!((Object)(object)item?.m_dropPrefab != (Object)null)) { return null; } return ((Object)item.m_dropPrefab).name; } internal static int BuildCost(Piece piece) { int num = 0; if ((Object)(object)piece == (Object)null || piece.m_resources == null) { return 0; } PileDefinition pileDefinition = PileCatalog.Find(Utils.GetPrefabName(((Object)piece).name)); if (pileDefinition == null) { return 0; } Requirement[] resources = piece.m_resources; foreach (Requirement val in resources) { if (val != null && !((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0) { if (Utils.GetPrefabName(((Object)val.m_resItem).name) != pileDefinition.ItemPrefab) { return 0; } num = checked(num + val.m_amount); } } return num; } internal static Inventory ReadInventory(ZDO zdo, Sprite background = null) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown if (zdo.GetInt(VersionKey, 0) != 1) { throw new InvalidOperationException("Unsupported pile schema; preserving original ZDO."); } byte[] byteArray = zdo.GetByteArray(ItemsKey, (byte[])null); if (byteArray == null || byteArray.Length == 0) { throw new InvalidOperationException("Managed pile has no inventory payload; it must not be refilled or deleted."); } int num = PileRules.SerializedRecords(byteArray); PileDefinition pileDefinition = Definition(zdo) ?? throw new InvalidOperationException("Unknown pile prefab."); int capacity = zdo.GetInt(CapacityKey, 0); Inventory val = new Inventory(pileDefinition.Prefab, background, 1, 1); using (PileHydration.Begin(val, pileDefinition, capacity)) { val.Load(new ZPackage(byteArray)); } if (val.GetAllItems().Count != num) { throw new InvalidOperationException("Inventory loader skipped an item; original payload retained."); } Validate(val, capacity, pileDefinition.ItemPrefab); return val; } internal static int Validate(Inventory inventory, int capacity, string expected = "Wood") { if (inventory == null || capacity <= 0) { throw new InvalidOperationException("Invalid pile inventory/capacity."); } List<ItemData> allItems = inventory.GetAllItems(); if (allItems.Count == 0) { return 0; } if (allItems.Count != 1) { throw new InvalidOperationException("Pile contains more than one item record."); } ItemData val = allItems[0]; if (ItemName(val) != expected || val.m_stack <= 0 || val.m_stack > capacity || val.m_gridPos.x != 0 || val.m_gridPos.y != 0) { throw new InvalidOperationException("Pile contains a foreign item, invalid slot or out-of-range stack."); } NormalizeStoredItem(val, capacity); return val.m_stack; } internal static ItemData NativeTemplate(string prefab) { GameObject val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetItemPrefab(prefab) : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null); if (!((Object)(object)val2 != (Object)null)) { throw new InvalidOperationException("Missing item prefab: " + prefab); } return val2.m_itemData; } internal static void NormalizeStoredItem(ItemData item, int capacity) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown if (item.m_shared.m_maxStackSize < capacity) { item.m_shared = (SharedData)CopyShared.Invoke(item.m_shared, null); item.m_shared.m_maxStackSize = capacity; } } internal static ItemData VanillaCopy(ItemData item, int amount) { ItemData obj = item.Clone(); obj.m_shared = NativeTemplate(ItemName(item)).m_shared; obj.m_stack = amount; return obj; } internal static byte[] Serialize(Inventory inventory) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); inventory.Save(val); return val.GetArray(); } } internal sealed class PileHydration : IDisposable { private static readonly Dictionary<Inventory, PileHydration> Active = new Dictionary<Inventory, PileHydration>(); private readonly Inventory _inventory; private readonly PileHydration _previous; private bool _disposed; internal PileDefinition Definition { get; } internal int Capacity { get; } private PileHydration(Inventory inventory, PileDefinition definition, int capacity) { _inventory = inventory; Definition = definition; Capacity = capacity; Active.TryGetValue(inventory, out _previous); Active[inventory] = this; } internal static PileHydration Begin(Inventory inventory, PileDefinition definition, int capacity) { return new PileHydration(inventory, definition, capacity); } internal static PileHydration Find(Inventory inventory) { if (!Active.TryGetValue(inventory, out var value)) { return null; } return value; } public void Dispose() { if (!_disposed) { _disposed = true; if (_previous == null) { Active.Remove(_inventory); } else { Active[_inventory] = _previous; } } } } internal static class PileInventoryRegistry { internal static readonly Dictionary<Inventory, PileStorage> Entries = new Dictionary<Inventory, PileStorage>(); internal static bool IsPileContainer(Container container) { Inventory val = ((container != null) ? container.GetInventory() : null); if (val != null) { return Entries.ContainsKey(val); } return false; } } internal sealed class PileStorage : MonoBehaviour, IPlaced { internal static readonly Dictionary<Inventory, PileStorage> Inventories = PileInventoryRegistry.Entries; internal static readonly FieldRef<Container, bool> Loading = AccessTools.FieldRefAccess<Container, bool>("m_loading"); private static readonly FieldRef<WearNTear, Action> Destroyed = AccessTools.FieldRefAccess<WearNTear, Action>("m_onDestroyed"); private static readonly MethodInfo LoadMethod = AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null); private static readonly MethodInfo DestroyMethod = AccessTools.Method(typeof(Container), "OnDestroyed", (Type[])null, (Type[])null); private static readonly string[] ContainerRpcs = new string[7] { "RPC_RequestOpen", "RPC_OpenResponse", "RPC_RequestStack", "RPC_StackResponse", "RPC_RequestTakeAll", "RPC_TakeAllResponse", "RPC_Discovered" }; private IPileDisplay _display; private Inventory _registeredInventory; private byte[] _validatedPayload; private int _validatedCapacity; private float _nextCheck; private float _legacyAfter; private bool _loggedLegacy; private bool _visualFailed; private int _lastCount = -1; internal Container Container { get; private set; } internal PileDefinition Definition { get; private set; } internal string Resource => Definition.ItemPrefab; internal ZNetView View { get; private set; } internal bool Ready { get; private set; } internal bool Hydrating { get; private set; } internal bool Faulted { get; private set; } internal bool Destroying { get; private set; } internal bool Managed { get { if ((Object)(object)View != (Object)null && View.IsValid()) { return PileData.IsManaged(View.GetZDO()); } return false; } } internal int Capacity { get { if (!Managed) { return 0; } return View.GetZDO().GetInt(PileData.CapacityKey, 0); } } internal string Id { get { if (!((Object)(object)View != (Object)null) || !View.IsValid()) { return ((Object)this).name; } return ((object)Unsafe.As<ZDOID, ZDOID>(ref View.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString(); } } internal bool CanMutate { get { if (Ready && !Faulted && !Hydrating && !Destroying && (Object)(object)View != (Object)null) { return View.IsOwner(); } return false; } } internal static PileStorage Attach(Piece piece) { if ((Object)(object)piece == (Object)null || PileCatalog.Find(Utils.GetPrefabName(((Object)piece).name)) == null || (Object)(object)((Component)piece).GetComponent<ZNetView>() == (Object)null) { return null; } return ((Component)piece).GetComponent<PileStorage>() ?? ((Component)piece).gameObject.AddComponent<PileStorage>(); } private void Awake() { View = ((Component)this).GetComponent<ZNetView>(); Definition = PileCatalog.Find(Utils.GetPrefabName(((Object)this).name)); _legacyAfter = Time.unscaledTime + 2f; } public void OnPlaced() { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown if (!Plugin.EnablePileStorage.Value || !PileData.IsPile(((Component)this).gameObject) || Managed) { return; } if (!View.IsOwner()) { Plugin.Error("Placement was not on the ZDO owner: " + Id + "; pile left vanilla."); return; } try { if ((Object)(object)((Component)this).GetComponent<Container>() != (Object)null) { throw new InvalidOperationException("Another mod already attached a Container."); } Piece component = ((Component)this).GetComponent<Piece>(); int num = PileData.BuildCost(component); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(Resource); ItemData val = itemPrefab.GetComponent<ItemDrop>().m_itemData.Clone(); if (num <= 0) { throw new InvalidOperationException("Invalid single-resource pile recipe: " + Definition.Prefab); } val.m_dropPrefab = itemPrefab; val.m_stack = num; val.m_gridPos = new Vector2i(0, 0); PileData.NormalizeStoredItem(val, num); Inventory val2 = new Inventory(component.m_name, (Sprite)null, 1, 1); val2.GetAllItems().Add(val); byte[] array = PileData.Serialize(val2); ZDO zDO = View.GetZDO(); zDO.Set(PileData.ItemsKey, array); zDO.Set(PileData.CapacityKey, num, false); zDO.Set(PileData.VersionKey, 1, false); Plugin.Trace("New " + Definition.Prefab + " " + Id + ": " + Resource + "=" + num + ", inventory seeded once."); EnsureReady(); } catch (Exception ex) { Fail("Placement initialization failed; original refundable pile retained", ex); } } internal bool EnsureReady() { if (Ready) { return !Faulted; } if (!Managed || Faulted || Hydrating || (Object)(object)ObjectDB.instance == (Object)null) { return false; } try { if (!ValidateForLoad()) { return false; } Container component = ((Component)this).GetComponent<Container>(); if ((Object)(object)component != (Object)null && (Object)(object)Container != (Object)(object)component) { throw new InvalidOperationException("Conflicting Container from another mod; refusing to overwrite it."); } Hydrating = true; Container = component ?? ((Component)this).gameObject.AddComponent<Container>(); Bind(Container); LoadMethod.Invoke(Container, null); _lastCount = Count(); Ready = true; Plugin.Trace("Container ready " + Id + ": 1x1, " + Resource + "=" + _lastCount + "/" + Capacity + ", owner=" + View.GetZDO().GetOwner()); return true; } catch (Exception ex) { Fail("Could not load managed pile; data retained", ex); return false; } finally { Hydrating = false; } } internal void Bind(Container container) { Container = container; _registeredInventory = container.GetInventory(); Inventories[_registeredInventory] = this; } internal bool ValidateForLoad() { if (Faulted) { return false; } try { ZDO zDO = View.GetZDO(); byte[] byteArray = zDO.GetByteArray(PileData.ItemsKey, (byte[])null); if (byteArray != null && byteArray == _validatedPayload && Capacity == _validatedCapacity && zDO.GetInt(PileData.VersionKey, 0) == 1) { return true; } PileData.ReadInventory(zDO); _validatedPayload = byteArray; _validatedCapacity = Capacity; return true; } catch (Exception ex) { Fail("Invalid saved inventory; load/save/delete blocked", ex); return false; } } internal bool ValidateForSave() { if (!Ready || Hydrating || Faulted || (Object)(object)Container == (Object)null || Loading.Invoke(Container) || !View.IsOwner()) { return false; } try { Count(); return true; } catch (Exception ex) { Fail("Invalid live inventory; save/delete blocked", ex); return false; } } internal void ObserveChange() { if (!Ready || Hydrating || Faulted || Loading.Invoke(Container)) { return; } try { int num = Count(); if (num != _lastCount) { Plugin.Trace(Resource + " changed " + Id + ": " + _lastCount + " -> " + num); } _lastCount = num; _nextCheck = 0f; } catch (Exception ex) { Fail("Inventory change rejected", ex); } } private void Update() { if (Time.unscaledTime < _nextCheck || (Object)(object)View == (Object)null || !View.IsValid()) { return; } _nextCheck = Time.unscaledTime + 0.25f; if (!Managed) { if (Ready || (Object)(object)Container != (Object)null) { DetachContainer(); } if (!_loggedLegacy && Time.unscaledTime >= _legacyAfter) { Plugin.Trace("Legacy " + Definition.Prefab + " " + Id + ": no mod data; left vanilla, no resources granted."); _loggedLegacy = true; } } else { if (!EnsureReady() || Destroying) { return; } try { if (View.IsOwner()) { LoadMethod.Invoke(Container, null); } if (Faulted) { return; } int stored = Count(); if (PileRules.CanDelete(Ready, View.IsOwner(), Loading.Invoke(Container), Destroying, Faulted, stored)) { Destroying = true; CloseLocalWindow(); Plugin.Trace("Empty pile " + Id + ": ZNetView.Destroy, no resource refund."); View.Destroy(); } else { if (Application.isBatchMode || _visualFailed) { return; } try { if (_display == null) { IPileDisplay display; if (!(Definition.Prefab == "wood_stack")) { IPileDisplay pileDisplay = new NativePileDisplay(((Component)this).transform, Definition); display = pileDisplay; } else { IPileDisplay pileDisplay = new WoodPileDisplay(((Component)this).transform); display = pileDisplay; } _display = display; } _display.Refresh(stored, Capacity); return; } catch (Exception error) { _display?.Dispose(); _display = null; _visualFailed = true; Plugin.Error("Visual failed for " + Id + "; vanilla rendering and storage retained", error); return; } } } catch (Exception ex) { Fail("Pile update failed; original data retained", ex); } } } internal bool PrepareDestruction() { if (!EnsureReady() || !View.IsOwner()) { return false; } if (!ValidateForSave()) { return false; } Destroying = true; CloseLocalWindow(); Plugin.Trace("Dismantle/destruction " + Id + ": drop remaining " + _lastCount + " " + Resource + " through Container.OnDestroyed; vanilla build refund suppressed."); return true; } private void CloseLocalWindow() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance != (Object)null && (Object)(object)Traverse.Create((object)instance).Field<Container>("m_currentContainer").Value == (Object)(object)Container) { AccessTools.Method(typeof(InventoryGui), "CloseContainer", (Type[])null, (Type[])null).Invoke(instance, null); } } internal int Count() { return PileData.Validate(Container.GetInventory(), Capacity, Resource); } internal void DetachContainer() { CloseLocalWindow(); _display?.Dispose(); _display = null; if ((Object)(object)Container != (Object)null) { if (_registeredInventory != null) { Inventories.Remove(_registeredInventory); } _registeredInventory = null; ((MonoBehaviour)Container).CancelInvoke(); WearNTear component = ((Component)this).GetComponent<WearNTear>(); if ((Object)(object)component != (Object)null) { ref Action reference = ref Destroyed.Invoke(component); reference = (Action)Delegate.Remove(reference, (Action)Delegate.CreateDelegate(typeof(Action), Container, DestroyMethod)); } string[] containerRpcs = ContainerRpcs; foreach (string text in containerRpcs) { View.Unregister(text); } ((Behaviour)Container).enabled = false; Object.Destroy((Object)(object)Container); Container = null; } Ready = false; } internal void Fail(string message, Exception ex) { Faulted = true; _display?.Dispose(); _display = null; Plugin.Error(message + " [" + Id + "]", ex); } private void OnDestroy() { if (_registeredInventory != null) { Inventories.Remove(_registeredInventory); } _display?.Dispose(); } } internal static class PileTransfers { private static readonly MethodInfo AddAt = AccessTools.Method(typeof(Inventory), "AddItem", new Type[5] { typeof(ItemData), typeof(int), typeof(int), typeof(int), typeof(bool) }, (Type[])null); internal static bool NeedsStockSplit(Inventory source) { if (source != null && PileStorage.Inventories.TryGetValue(source, out var value)) { return value.Capacity > PileData.NativeTemplate(value.Resource).m_shared.m_maxStackSize; } return false; } internal static bool TryWithdraw(Inventory destination, Inventory source, ItemData item, int amount, out bool complete, int x = -1, int y = -1) { complete = false; if (!NeedsStockSplit(source) || PileStorage.Inventories.ContainsKey(destination)) { return false; } PileStorage pileStorage = PileStorage.Inventories[source]; if (!pileStorage.CanMutate || item == null || PileData.ItemName(item) != pileStorage.Resource) { return true; } int num = Math.Min(amount, item.m_stack); int num2 = num; int maxStackSize = PileData.NativeTemplate(pileStorage.Resource).m_shared.m_maxStackSize; while (num2 > 0) { int num3 = PileRules.StockChunk(num2, maxStackSize); if (x >= 0 && y >= 0) { ItemData itemAt = destination.GetItemAt(x, y); if (itemAt != null && !itemAt.IsSameType(item)) { break; } num3 = Math.Min(num3, maxStackSize - (itemAt?.m_stack ?? 0)); if (num3 <= 0) { break; } } int num4 = Count(destination, pileStorage.Resource); ItemData val = PileData.VanillaCopy(item, num3); if (x >= 0 && y >= 0) { AddAt.Invoke(destination, new object[5] { val, num3, x, y, false }); } else { destination.AddItem(val); } int num5 = Math.Min(num3, Math.Max(0, Count(destination, pileStorage.Resource) - num4)); if (num5 == 0) { break; } if (!source.RemoveItem(item, num5)) { throw new InvalidOperationException("Pile source changed during stock transfer."); } if (!source.ContainsItem(item)) { item.m_stack = 0; } num2 -= num5; if (num5 < num3 || (x >= 0 && y >= 0)) { break; } } complete = num > 0 && num2 == 0; return true; } internal static int Count(Inventory inventory, string resource) { return (from i in inventory.GetAllItems() where PileData.ItemName(i) == resource select i).Sum((ItemData i) => i.m_stack); } internal static void DropContents(Inventory inventory, Vector3 position, Action progress = null) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) ItemData[] array = inventory.GetAllItems().ToArray(); foreach (ItemData val in array) { int maxStackSize = PileData.NativeTemplate(PileData.ItemName(val)).m_shared.m_maxStackSize; int num = val.m_stack; while (num > 0) { int num2 = PileRules.StockChunk(num, maxStackSize); if ((Object)(object)ItemDrop.DropItem(PileData.VanillaCopy(val, num2), 0, position + Vector3.up * 0.7f, Quaternion.identity) == (Object)null) { throw new InvalidOperationException("DropItem failed; remaining resources retained."); } inventory.RemoveItem(val, num2); num -= num2; progress?.Invoke(); } } } } internal interface IPileDisplay : IDisposable { void Refresh(int stored, int capacity); } internal sealed class NativeLogMesh { private static readonly Dictionary<Mesh, NativeLogMesh> Cache = new Dictionary<Mesh, NativeLogMesh>(); private readonly Mesh _original; private readonly NativeLogTopology.Log[] _logs; private readonly Mesh[] _variants = (Mesh[])(object)new Mesh[12]; private int _references; private NativeLogMesh(Mesh original) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) if (!original.isReadable) { throw new InvalidOperationException("Vanilla wood_stack mesh is not readable; original rendering retained."); } Vector3[] vertices = original.vertices; float[] array = new float[vertices.Length * 3]; for (int i = 0; i < ve