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 DvergrReclaimer v0.2.0
plugins/DvergrReclaimer.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using DvergrReclaimer.Engine; using DvergrReclaimer.Ledger; using DvergrReclaimer.Logistics; using DvergrReclaimer.Machine; using DvergrReclaimer.Model; using DvergrReclaimer.Pieces; using HarmonyLib; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Recycle_N_Reclaim; using Recycle_N_Reclaim.GamePatches.Recycling; using TMPro; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("DvergrReclaimer.SmokeTest")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: AssemblyCompany("DvergrReclaimer")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.0.0")] [assembly: AssemblyInformationalVersion("0.2.0+598d426f5130a7e9a88599bd6b2de118722c3fff")] [assembly: AssemblyProduct("DvergrReclaimer")] [assembly: AssemblyTitle("DvergrReclaimer")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.2.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DvergrReclaimer { [HarmonyPatch(typeof(Game), "Start")] internal static class Network { private static void Postfix() { LedgerSync.Register(); ContainerTransfer.Register(); } } [BepInPlugin("greatcoltini.mods.dvergrreclaimer", "Dvergr Reclaimer", "0.2.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class DvergrReclaimerPlugin : BaseUnityPlugin { public const string ModGuid = "greatcoltini.mods.dvergrreclaimer"; public const string ModName = "Dvergr Reclaimer"; public const string ModVersion = "0.2.0"; internal static ManualLogSource Log; private readonly Harmony _harmony = new Harmony("greatcoltini.mods.dvergrreclaimer"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; ReclaimerConfig.Bind(((BaseUnityPlugin)this).Config); if (!RnrEngine.Probe()) { Log.LogError((object)("Recycle N' Reclaim is missing or incompatible, so reclaiming is disabled: " + RnrEngine.Problem)); } if (EpicLootSacrifice.Probe()) { Log.LogInfo((object)"Epic Loot found: items without a recipe can be sacrificed for its enchanting materials"); } else if (Chainloader.PluginInfos.ContainsKey("randyknapp.mods.epicloot")) { Log.LogWarning((object)("Epic Loot is installed but its sacrifice API changed, so the Reclaimer won't sacrifice: " + EpicLootSacrifice.Problem)); } ReclaimerPiece.Register(); _harmony.PatchAll(typeof(DvergrReclaimerPlugin).Assembly); } private void OnDestroy() { _harmony.UnpatchSelf(); } } internal enum NoRoomBehavior { DropInFront, Wait } internal enum RecipeGateMode { Everyone, Off } internal static class ReclaimerConfig { public static ConfigEntry<float> SecondsPerItem; public static ConfigEntry<int> ItemsPerTick; public static ConfigEntry<string> FuelItem; public static ConfigEntry<int> FuelPerItem; public static ConfigEntry<int> MaxFuel; public static ConfigEntry<bool> PullFuelFromChests; public static ConfigEntry<float> FuelRadius; public static ConfigEntry<float> OutputRadius; public static ConfigEntry<NoRoomBehavior> WhenNoRoom; public static ConfigEntry<RecipeGateMode> RecipeGate; public static ConfigEntry<float> StationRadius; public static ConfigEntry<bool> HideReclaimAllButton; public static ConfigEntry<bool> SacrificeWithoutRecipe; public static ConfigEntry<bool> SacrificeNeedsTable; public static void Bind(ConfigFile config) { SecondsPerItem = Bind(config, "Processing", "Seconds to reclaim an item", 30f, "Time to reclaim or sacrifice one item (or stack). A Smelter takes 30 seconds per bar."); ItemsPerTick = Bind(config, "Processing", "Items per catch-up tick", 4, "Most items finished in one second while catching up on time the area spent unloaded."); FuelItem = Bind(config, "Fuel", "Fuel item", "Coal", "Prefab name of the fuel. Fuel in the crate is used first."); FuelPerItem = Bind(config, "Fuel", "Fuel per item", 1, "Fuel used per item reclaimed. 0 disables fuel."); MaxFuel = Bind(config, "Fuel", "Max fuel", 40, "Fuel the Reclaimer can hold."); PullFuelFromChests = Bind(config, "Fuel", "Pull fuel from chests", value: true, "Take fuel from nearby chests when an item is waiting and fuel has run out."); FuelRadius = Bind(config, "Fuel", "Fuel radius", 8f, "How far away (metres) chests can supply fuel."); OutputRadius = Bind(config, "Output", "Output radius", 10f, "How far away (metres) chests can receive reclaimed materials."); WhenNoRoom = Bind(config, "Output", "When no chest has room", NoRoomBehavior.DropInFront, "DropInFront: results no nearby chest can take drop out of the front of the Reclaimer, like a Smelter's bars. Wait: they stay inside and it pauses when full, until a chest has room."); RecipeGate = Bind(config, "Recipes", "Recipe gate", RecipeGateMode.Everyone, "Everyone: only recipes someone on the world has discovered. Off: any recipe."); StationRadius = Bind(config, "Recipes", "Station radius", 20f, "With Recycle N' Reclaim's exact-station rule on, the matching crafting station must be this close (metres)."); HideReclaimAllButton = Bind(config, "Interface", "Hide Reclaim all on the crate", value: true, "Hide Recycle N' Reclaim's container button on the Reclaimer's crate, which would bypass fuel and the recipe gate."); SacrificeWithoutRecipe = Bind(config, "Epic Loot", "Sacrifice items without a recipe", value: true, "With Epic Loot installed, items that have no crafting recipe but that Epic Loot's Enchanting Table accepts as a sacrifice (trophies, boss items) are sacrificed for its products, such as a Runestone for the Elder's trophy."); SacrificeNeedsTable = Bind(config, "Epic Loot", "Sacrifice needs an Enchanting Table", value: true, "Sacrificing needs an Enchanting Table with its Sacrifice feature unlocked within the station radius. The table's Sacrifice level gives the same chance of double products as sacrificing at the table."); } private static ConfigEntry<T> Bind<T>(ConfigFile config, string section, string key, T value, string description) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = true }; return config.Bind<T>(section, key, value, new ConfigDescription(description, (AcceptableValueBase)null, new object[1] { val })); } } } namespace DvergrReclaimer.UI { [HarmonyPatch(typeof(InventoryGui), "Update")] internal static class ReclaimerPanel { internal const int Columns = 8; internal const int FuelColumn = 0; internal const int ProcessingColumn = 2; internal const int FirstYieldColumn = 4; private const float Gap = 8f; private const float HeaderHeight = 20f; private const float StatusMargin = 24f; private static readonly Inventory Display = new Inventory("DvergrReclaimerPanel", (Sprite)null, 8, 1); private static RectTransform s_panel; private static InventoryGrid s_grid; private static TMP_Text s_status; private static TMP_Text s_processingHeader; private static TMP_Text s_yieldHeader; private static bool s_broken; internal static RectTransform Panel => s_panel; internal static Inventory Contents => Display; internal static string StatusText { get { if (!((Object)(object)s_status != (Object)null)) { return null; } return s_status.text; } } internal static string SlotText(int column) { if (!((Object)(object)s_grid != (Object)null)) { return null; } return s_grid.m_elements[column].m_amount.text; } private static void Postfix(InventoryGui __instance) { if (s_broken) { return; } try { object obj; if (!InventoryGui.IsVisible() || !__instance.IsContainerOpen()) { obj = null; } else { Container currentContainer = __instance.m_currentContainer; obj = ((currentContainer != null) ? ((Component)currentContainer).GetComponent<ReclaimerMachine>() : null); } ReclaimerMachine reclaimerMachine = (ReclaimerMachine)obj; if ((Object)(object)reclaimerMachine == (Object)null) { if ((Object)(object)s_panel != (Object)null) { ((Component)s_panel).gameObject.SetActive(false); } return; } if ((Object)(object)s_panel == (Object)null) { Create(__instance); } ((Component)s_panel).gameObject.SetActive(true); ReclaimerMachine.PanelState view = reclaimerMachine.PanelView(Display); s_status.text = view.Status; s_processingHeader.text = view.ProcessingHeader; s_yieldHeader.text = view.YieldHeader; s_grid.UpdateInventory(Display, (Player)null, (ItemData)null); ShowAmounts(view); PlaceBelow(__instance.m_container); } catch (Exception arg) { s_broken = true; DvergrReclaimerPlugin.Log.LogError((object)$"Reclaimer panel disabled: {arg}"); if ((Object)(object)s_panel != (Object)null) { ((Component)s_panel).gameObject.SetActive(false); } } } private static void ShowAmounts(ReclaimerMachine.PanelState view) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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) foreach (InventoryElement element in s_grid.m_elements) { ItemData itemAt = Display.GetItemAt(element.Position.x, element.Position.y); if (itemAt != null) { ((Behaviour)element.m_amount).enabled = true; ((Behaviour)element.m_quality).enabled = false; if (element.Position.x == 0) { element.m_amount.text = view.FuelAmount; } else if (element.Position.x == 2) { element.m_amount.text = view.Progress; } else { element.m_amount.text = $"×{itemAt.m_stack}"; } } } } private static void Create(InventoryGui gui) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_01ab: Unknown result type (might be due to invalid IL or missing references) RectTransform container = gui.m_container; s_panel = (RectTransform)Object.Instantiate<GameObject>(((Component)container).gameObject, ((Transform)container).parent).transform; ((Object)s_panel).name = "DvergrReclaimerPanel"; s_grid = ((Component)s_panel).GetComponentsInChildren<InventoryGrid>(true).First(); s_grid.m_onSelected = delegate { }; s_grid.m_onRightClick = delegate { }; s_grid.m_onReleased = delegate { }; s_grid.m_onEnter = delegate { }; s_grid.CanDropDragOntoItem = (ItemData item) => false; s_grid.OnMoveToUpperInventoryGrid = delegate { }; s_grid.OnMoveToLowerInventoryGrid = delegate { }; s_grid.OnSetTouchSelection = delegate { }; foreach (Transform item in (Transform)s_grid.m_gridRoot) { Object.Destroy((Object)(object)((Component)item).gameObject); } s_status = ((Component)FindCopy((Transform)(object)container, (Transform)(object)s_panel, gui.m_containerName.transform)).GetComponent<TMP_Text>(); s_status.enableAutoSizing = true; s_status.fontSizeMax = s_status.fontSize; s_status.fontSizeMin = Mathf.Min(12f, s_status.fontSize); s_status.textWrappingMode = (TextWrappingModes)0; s_status.overflowMode = (TextOverflowModes)1; Object.Destroy((Object)(object)((Component)FindCopy((Transform)(object)container, (Transform)(object)s_panel, gui.m_containerWeight.transform.parent)).gameObject); foreach (Button item2 in from b in ((Component)s_panel).GetComponentsInChildren<Button>(true) where !((Component)b).transform.IsChildOf(((Component)s_grid).transform) select b) { Object.Destroy((Object)(object)((Component)item2).gameObject); } foreach (TMP_Text item3 in from t in ((Component)s_panel).GetComponentsInChildren<TMP_Text>(true) where (Object)(object)t != (Object)(object)s_status && !t.transform.IsChildOf(((Component)s_grid).transform) select t) { ((Component)item3).gameObject.SetActive(false); } Scrollbar[] componentsInChildren = ((Component)s_panel).GetComponentsInChildren<Scrollbar>(true); for (int num = 0; num < componentsInChildren.Length; num++) { ((Component)componentsInChildren[num]).gameObject.SetActive(false); } s_grid.UpdateInventory(Display, (Player)null, (ItemData)null); FitHeightToOneRow(); StretchAcrossTop(s_status.rectTransform); Header(0, "Fuel"); s_processingHeader = Header(2, "Reclaiming"); s_yieldHeader = Header(4, "Produces", alignLeft: true); } private static Transform FindCopy(Transform sourceRoot, Transform copyRoot, Transform descendant) { string text = PathFrom(sourceRoot, descendant); return copyRoot.Find(text) ?? throw new InvalidOperationException("Copied panel has no " + text); } private static string PathFrom(Transform root, Transform target) { string text = ((Object)target).name; Transform parent = target.parent; while ((Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)root) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } private static TMP_Text Header(int column, string text, bool alignLeft = false) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012f: 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_013e: Unknown result type (might be due to invalid IL or missing references) TMP_Text component = Object.Instantiate<GameObject>(((Component)s_status).gameObject, (Transform)(object)s_panel).GetComponent<TMP_Text>(); ((Object)component).name = "DvergrReclaimerHeader"; ((Component)component).gameObject.SetActive(true); component.text = text; component.enableAutoSizing = false; component.fontSize = Mathf.Max(12f, s_status.fontSizeMax * 0.6f); component.textWrappingMode = (TextWrappingModes)0; component.overflowMode = (TextOverflowModes)0; component.alignment = (TextAlignmentOptions)(alignLeft ? 257 : 258); ((Graphic)component).raycastTarget = false; RectTransform val = (RectTransform)((Component)s_grid.m_elements[column]).transform; Vector3[] array = (Vector3[])(object)new Vector3[4]; val.GetWorldCorners(array); RectTransform rectTransform = component.rectTransform; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 0.5f); rectTransform.anchorMax = val2; rectTransform.anchorMin = val2; rectTransform.pivot = new Vector2(alignLeft ? 0f : 0.5f, 1f); Rect rect = val.rect; rectTransform.sizeDelta = new Vector2(((Rect)(ref rect)).width * (alignLeft ? 4f : 2f), 20f); ((Transform)rectTransform).position = (alignLeft ? array[0] : ((array[0] + array[3]) / 2f)); return component; } private static void FitHeightToOneRow() { //IL_0033: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: 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) Canvas.ForceUpdateCanvases(); Vector3[] array = (Vector3[])(object)new Vector3[4]; Vector3[] array2 = (Vector3[])(object)new Vector3[4]; s_panel.GetWorldCorners(array); ((RectTransform)((Component)s_grid.m_elements[0]).transform).GetWorldCorners(array2); float y = ((Transform)s_panel).lossyScale.y; float num = (array[1].y - array2[0].y) / y + 20f + s_grid.m_elementSpace * 0.25f; Vector3 val = array[1]; s_panel.pivot = new Vector2(s_panel.pivot.x, 1f); s_panel.SetSizeWithCurrentAnchors((Axis)1, num); s_panel.GetWorldCorners(array); RectTransform obj = s_panel; ((Transform)obj).position = ((Transform)obj).position + (val - array[1]); } private static void StretchAcrossTop(RectTransform rect) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) ((Transform)rect).SetParent((Transform)(object)s_panel, true); Vector3[] array = (Vector3[])(object)new Vector3[4]; Vector3[] array2 = (Vector3[])(object)new Vector3[4]; rect.GetWorldCorners(array); s_panel.GetWorldCorners(array2); float y = ((Transform)s_panel).lossyScale.y; float num = (array2[1].y - array[1].y) / y; float num2 = (array[1].y - array[0].y) / y; rect.anchorMin = new Vector2(0f, 1f); rect.anchorMax = new Vector2(1f, 1f); rect.pivot = new Vector2(0.5f, 1f); rect.sizeDelta = new Vector2(-48f, num2); rect.anchoredPosition = new Vector2(0f, 0f - num); } private static void PlaceBelow(RectTransform container) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = (Vector3[])(object)new Vector3[4]; Vector3[] array2 = (Vector3[])(object)new Vector3[4]; container.GetWorldCorners(array); s_panel.GetWorldCorners(array2); float num = 8f * ((Transform)s_panel).lossyScale.y; Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(array[0].x, array[0].y - num, array2[1].z); RectTransform obj = s_panel; ((Transform)obj).position = ((Transform)obj).position + (val - array2[1]); } } } namespace DvergrReclaimer.Pieces { internal static class ReclaimerPiece { public const string PrefabName = "piece_dvergr_reclaimer"; public const int CrateWidth = 8; public const int CrateHeight = 12; private const string NameToken = "piece_dvergr_reclaimer"; private const string DescriptionToken = "piece_dvergr_reclaimer_description"; internal static void Register() { CustomLocalization localization = LocalizationManager.Instance.GetLocalization(); string text = "English"; localization.AddTranslation(ref text, new Dictionary<string, string> { ["piece_dvergr_reclaimer"] = "Dvergr Reclaimer", ["piece_dvergr_reclaimer_description"] = "An iron furnace that breaks down the gear in its crate into reclaimed materials." }); PrefabManager.OnVanillaPrefabsAvailable += Create; } private static void Create() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Expected O, but got Unknown PrefabManager.OnVanillaPrefabsAvailable -= Create; try { GameObject val = BuildPrefab(); PieceConfig val2 = new PieceConfig(); val2.Name = "$piece_dvergr_reclaimer"; val2.Description = "$piece_dvergr_reclaimer_description"; val2.PieceTable = PieceTables.Hammer; val2.Category = PieceCategories.Crafting; val2.Usage = new string[1] { PieceUsages.Crafting }; val2.CraftingStation = CraftingStations.Forge; val2.Icon = LoadSprite("icon_piece.png"); PieceConfig val3 = val2; val3.AddRequirement("Iron", 10, true); val3.AddRequirement("Stone", 10, true); val3.AddRequirement("SurtlingCore", 2, true); PieceManager.Instance.AddPiece(new CustomPiece(val, false, val3)); DvergrReclaimerPlugin.Log.LogInfo((object)"Registered piece_dvergr_reclaimer"); } catch (Exception arg) { DvergrReclaimerPlugin.Log.LogError((object)string.Format("Could not create {0}: {1}", "piece_dvergr_reclaimer", arg)); } } private static GameObject BuildPrefab() { //IL_00dc: Unknown result type (might be due to invalid IL or missing references) GameObject prefab = PrefabManager.Instance.GetPrefab("piece_chest"); GameObject prefab2 = PrefabManager.Instance.GetPrefab("forge"); Material template = RuntimeModel.FindVanillaPieceMaterial((Func<string, GameObject>)PrefabManager.Instance.GetPrefab, new string[1] { "piece_chest" }) ?? throw new InvalidOperationException("No vanilla Custom/Piece material to copy"); GameObject val = PrefabManager.Instance.CreateEmptyPrefab("piece_dvergr_reclaimer", true); Object.DestroyImmediate((Object)(object)val.GetComponent<Collider>()); Object.DestroyImmediate((Object)(object)val.GetComponent<MeshRenderer>()); Object.DestroyImmediate((Object)(object)val.GetComponent<MeshFilter>()); RuntimeModel.Read(EmbeddedAsset("dvergr_reclaimer.dvmodel")).BuildInto(val, template, EmbeddedAsset); val.AddComponent<Piece>().m_placeEffect = prefab2.GetComponent<Piece>().m_placeEffect; WearNTear component = prefab2.GetComponent<WearNTear>(); WearNTear obj = val.AddComponent<WearNTear>(); obj.m_materialType = (MaterialType)2; obj.m_health = 1500f; obj.m_hitEffect = component.m_hitEffect; obj.m_destroyedEffect = component.m_destroyedEffect; obj.m_switchEffect = component.m_switchEffect; Container component2 = prefab.GetComponent<Container>(); Container obj2 = val.AddComponent<Container>(); obj2.m_name = "$piece_dvergr_reclaimer"; obj2.m_width = 8; obj2.m_height = 12; obj2.m_bkg = component2.m_bkg; obj2.m_openEffects = component2.m_openEffects; obj2.m_closeEffects = component2.m_closeEffects; obj2.m_checkGuardStone = true; AddWorkingEffects(val); val.AddComponent<ReclaimerEffects>(); val.AddComponent<ReclaimerMachine>(); return val; } private static void AddWorkingEffects(GameObject prefab) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006e: 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_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Expected O, but got Unknown //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02d5: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) //IL_030f: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) GameObject prefab2 = PrefabManager.Instance.GetPrefab("smelter"); Transform source = ((prefab2 != null) ? prefab2.transform.Find("_enabled") : null) ?? throw new InvalidOperationException("The vanilla Smelter has no _enabled effects"); Bounds bounds = default(Bounds); bool flag = true; MeshFilter[] componentsInChildren = prefab.GetComponentsInChildren<MeshFilter>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Bounds bounds2 = componentsInChildren[i].sharedMesh.bounds; if (flag) { bounds = bounds2; flag = false; } else { ((Bounds)(ref bounds)).Encapsulate(bounds2); } } Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).min.y + ((Bounds)(ref bounds)).size.y * 0.4f, ((Bounds)(ref bounds)).min.z); List<Vector3> list = (from v in prefab.GetComponentsInChildren<MeshFilter>(true).SelectMany((MeshFilter f) => f.sharedMesh.vertices) where v.y > ((Bounds)(ref bounds)).max.y - ((Bounds)(ref bounds)).size.y * 0.03f select v).ToList(); Vector3 val2 = ((list.Count > 0) ? new Vector3(list.Average((Vector3 v) => v.x), ((Bounds)(ref bounds)).max.y, list.Average((Vector3 v) => v.z)) : new Vector3(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).max.y, ((Bounds)(ref bounds)).center.z)); GameObject val3 = new GameObject("feed_point"); val3.transform.SetParent(prefab.transform, false); val3.transform.localPosition = val; GameObject val4 = new GameObject("_working"); val4.transform.SetParent(prefab.transform, false); val4.SetActive(false); Light component = ((Component)CopyEffect(source, "Point light", val4.transform, val + new Vector3(0f, 0.2f, -0.3f))).GetComponent<Light>(); component.intensity = 3f; component.range = 3f; CopyEffect(source, "flames", val4.transform, val + new Vector3(0f, -0.15f, -0.05f)).localScale = Vector3.one * 0.5f; CopyEffect(source, "flare (1)", val4.transform, val + new Vector3(0f, 0f, -0.05f)); CopyEffect(source, "sfx_fire_loop", val4.transform, val); CopyEffect(source, "smoke (1)", val4.transform, val2); CopyEffect(source, "SmokeSpawner", val4.transform, val2 + Vector3.up * 0.3f); } private static Transform CopyEffect(Transform source, string name, Transform parent, Vector3 position) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) Transform transform = Object.Instantiate<GameObject>(((Component)(source.Find(name) ?? throw new InvalidOperationException("The vanilla Smelter has no _enabled/" + name))).gameObject, parent, false).transform; ((Object)transform).name = name; transform.localPosition = position; return transform; } private static Sprite LoadSprite(string name) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); RuntimeModel.LoadImage(val, EmbeddedAsset(name), arg3: false); return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f)); } internal static byte[] EmbeddedAsset(string name) { using Stream stream = typeof(ReclaimerPiece).Assembly.GetManifestResourceStream(name) ?? throw new FileNotFoundException("Embedded asset not found", name); using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); return memoryStream.ToArray(); } } } namespace DvergrReclaimer.Model { internal sealed class RuntimeModel { internal sealed class Part { public string Name; public int Lod; public Vector3[] Positions; public Vector3[] Normals; public Vector2[] Uvs; public readonly List<(int Material, int[] Indices)> Submeshes = new List<(int, int[])>(); } internal sealed class ModelMaterial { public string Name; public Color Color; public Color Emission; public float EmissionStrength; public float Metallic; public float Roughness; public readonly Dictionary<string, string> Textures = new Dictionary<string, string>(); } private const int PieceLayer = 10; private static readonly float[] LodHeights = new float[4] { 0.4f, 0.15f, 0.03f, 0.01f }; public readonly List<Part> Parts = new List<Part>(); public readonly List<ModelMaterial> Materials = new List<ModelMaterial>(); public readonly List<(string Name, Vector3 Center, Vector3 Size)> Boxes = new List<(string, Vector3, Vector3)>(); public readonly List<(string Name, Vector3 Position)> Points = new List<(string, Vector3)>(); internal static readonly Func<Texture2D, byte[], bool, bool> LoadImage = (Func<Texture2D, byte[], bool, bool>)Delegate.CreateDelegate(typeof(Func<Texture2D, byte[], bool, bool>), typeof(ImageConversion).GetMethod("LoadImage", new Type[3] { typeof(Texture2D), typeof(byte[]), typeof(bool) })); public static RuntimeModel Read(byte[] data) { //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) using BinaryReader binaryReader = new BinaryReader(new MemoryStream(data)); if (Encoding.ASCII.GetString(binaryReader.ReadBytes(4)) != "DVMD") { throw new InvalidDataException("Not a .dvmodel file"); } int num = binaryReader.ReadInt32(); if (num != 1) { throw new InvalidDataException($"Unsupported .dvmodel version {num}"); } RuntimeModel runtimeModel = new RuntimeModel(); int i = 0; for (int num2 = binaryReader.ReadInt32(); i < num2; i++) { Part part = new Part { Name = ReadString(binaryReader), Lod = binaryReader.ReadInt32() }; int count = binaryReader.ReadInt32(); part.Positions = ReadArray(binaryReader, count, ReadVector3); part.Normals = ReadArray(binaryReader, count, ReadVector3); part.Uvs = ReadArray(binaryReader, count, (Func<BinaryReader, Vector2>)((BinaryReader r) => new Vector2(r.ReadSingle(), r.ReadSingle()))); int num3 = 0; for (int num4 = binaryReader.ReadInt32(); num3 < num4; num3++) { int item = binaryReader.ReadInt32(); part.Submeshes.Add((item, ReadArray(binaryReader, binaryReader.ReadInt32(), (BinaryReader r) => r.ReadInt32()))); } runtimeModel.Parts.Add(part); } int num5 = 0; for (int num6 = binaryReader.ReadInt32(); num5 < num6; num5++) { ModelMaterial modelMaterial = new ModelMaterial { Name = ReadString(binaryReader), Color = new Color(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()), Emission = new Color(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()), EmissionStrength = binaryReader.ReadSingle(), Metallic = binaryReader.ReadSingle(), Roughness = binaryReader.ReadSingle() }; int num7 = 0; for (int num8 = binaryReader.ReadInt32(); num7 < num8; num7++) { modelMaterial.Textures[ReadString(binaryReader)] = ReadString(binaryReader); } runtimeModel.Materials.Add(modelMaterial); } int num9 = 0; for (int num10 = binaryReader.ReadInt32(); num9 < num10; num9++) { runtimeModel.Boxes.Add((ReadString(binaryReader), ReadVector3(binaryReader), ReadVector3(binaryReader))); } int num11 = 0; for (int num12 = binaryReader.ReadInt32(); num11 < num12; num11++) { runtimeModel.Points.Add((ReadString(binaryReader), ReadVector3(binaryReader))); } return runtimeModel; } public GameObject Build(string name, Material template, Func<string, byte[]> loadFile) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown GameObject val = new GameObject(name); val.SetActive(false); BuildInto(val, template, loadFile); return val; } public void BuildInto(GameObject root, Material template, Func<string, byte[]> loadFile) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Expected O, but got Unknown //IL_02d2: Unknown result type (might be due to invalid IL or missing references) root.layer = 10; Material[] materials = Materials.Select((ModelMaterial m) => CreateMaterial(m, template, loadFile)).ToArray(); SortedDictionary<int, List<Renderer>> sortedDictionary = new SortedDictionary<int, List<Renderer>>(); foreach (Part part in Parts) { GameObject val = new GameObject(part.Name) { layer = 10 }; val.transform.SetParent(root.transform, false); Mesh val2 = new Mesh { name = part.Name, indexFormat = (IndexFormat)(part.Positions.Length > 65535) }; val2.SetVertices(part.Positions); val2.SetNormals(part.Normals); val2.SetUVs(0, part.Uvs); val2.subMeshCount = part.Submeshes.Count; for (int num = 0; num < part.Submeshes.Count; num++) { val2.SetTriangles(part.Submeshes[num].Indices, num); } val2.RecalculateBounds(); val2.RecalculateTangents(); val.AddComponent<MeshFilter>().sharedMesh = val2; MeshRenderer val3 = val.AddComponent<MeshRenderer>(); ((Renderer)val3).sharedMaterials = part.Submeshes.Select(((int Material, int[] Indices) s) => materials[s.Material]).ToArray(); if (!sortedDictionary.TryGetValue(part.Lod, out var value)) { value = (sortedDictionary[part.Lod] = new List<Renderer>()); } value.Add((Renderer)(object)val3); } if (sortedDictionary.Count > 1) { LOD[] lODs = ((IEnumerable<List<Renderer>>)sortedDictionary.Values).Select((Func<List<Renderer>, int, LOD>)((List<Renderer> renderers, int i) => new LOD(LodHeights[Math.Min(i, LodHeights.Length - 1)], renderers.ToArray()))).ToArray(); LODGroup obj = root.AddComponent<LODGroup>(); obj.SetLODs(lODs); obj.RecalculateBounds(); } foreach (var box in Boxes) { GameObject val4 = new GameObject(box.Name) { layer = 10 }; val4.transform.SetParent(root.transform, false); BoxCollider obj2 = val4.AddComponent<BoxCollider>(); obj2.center = box.Center; obj2.size = box.Size; } foreach (var point in Points) { GameObject val5 = new GameObject(point.Name) { layer = 10 }; val5.transform.SetParent(root.transform, false); val5.transform.localPosition = point.Position; if (point.Name.StartsWith("snappoint", StringComparison.Ordinal)) { val5.tag = "snappoint"; } } } public static Material FindVanillaPieceMaterial(Func<string, GameObject> getPrefab, params string[] prefabNames) { foreach (string arg in prefabNames) { GameObject val = getPrefab(arg); if (!((Object)(object)val == (Object)null)) { Material val2 = val.GetComponentsInChildren<Renderer>(true).SelectMany((Renderer r) => r.sharedMaterials).FirstOrDefault((Func<Material, bool>)((Material m) => (Object)(object)m != (Object)null && (Object)(object)m.shader != (Object)null && ((Object)m.shader).name == "Custom/Piece")); if ((Object)(object)val2 != (Object)null) { return val2; } } } return null; } internal static Texture2D LoadTexture(byte[] data, bool linear) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown Texture2D val = new Texture2D(2, 2, (TextureFormat)4, true, linear); if (!LoadImage(val, data, arg3: false)) { throw new InvalidDataException("Unreadable texture"); } ((Texture)val).filterMode = (FilterMode)0; if (((Texture)val).width % 4 == 0 && ((Texture)val).height % 4 == 0) { val.Compress(true); } val.Apply(false, true); return val; } private static Material CreateMaterial(ModelMaterial source, Material template, Func<string, byte[]> loadFile) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Invalid comparison between Unknown and I4 //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Invalid comparison between Unknown and I4 //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) Material val = new Material(template) { name = source.Name }; Shader shader = val.shader; for (int i = 0; i < shader.GetPropertyCount(); i++) { if ((int)shader.GetPropertyType(i) == 4) { val.SetTexture(shader.GetPropertyNameId(i), (Texture)null); } } bool linearSpace = (int)QualitySettings.activeColorSpace == 1; val.SetColor("_Color", Authored(source.Color)); if (source.Textures.TryGetValue("baseColor", out var value)) { val.SetTexture("_MainTex", (Texture)(object)LoadTexture(loadFile(value), linear: false)); } if (source.Textures.TryGetValue("normal", out var value2)) { val.SetTexture("_BumpMap", (Texture)(object)LoadTexture(loadFile(value2), linear: true)); val.EnableKeyword("_NORMALMAP"); } else { val.DisableKeyword("_NORMALMAP"); } if (source.Textures.TryGetValue("metallicGloss", out var value3)) { val.SetTexture("_MetallicTex", (Texture)(object)LoadTexture(loadFile(value3), linear: true)); val.EnableKeyword("_METALLICGLOSSMAP"); } else { val.DisableKeyword("_METALLICGLOSSMAP"); val.SetFloat("_Metallic", source.Metallic); val.SetFloat("_Glossiness", 1f - source.Roughness); } if (source.EmissionStrength > 0f) { string value4; Texture2D val2 = (source.Textures.TryGetValue("emission", out value4) ? LoadTexture(loadFile(value4), linear: false) : Texture2D.whiteTexture); val.SetTexture("_EmissionMap", (Texture)(object)val2); val.SetColor("_EmissionColor", Authored(source.Emission * source.EmissionStrength)); } else { val.SetColor("_EmissionColor", Color.black); } return val; Color Authored(Color linear) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (!linearSpace) { return linear; } return ((Color)(ref linear)).gamma; } } private static string ReadString(BinaryReader reader) { return Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadInt32())); } private static Vector3 ReadVector3(BinaryReader reader) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); } private static T[] ReadArray<T>(BinaryReader reader, int count, Func<BinaryReader, T> read) { T[] array = new T[count]; for (int i = 0; i < count; i++) { array[i] = read(reader); } return array; } } } namespace DvergrReclaimer.Machine { internal enum ReclaimerEffect { ItemAdded, FuelAdded, Produced } internal sealed class ReclaimerEffects : MonoBehaviour { internal const string WorkingObjectName = "_working"; internal const string FeedPointName = "feed_point"; private static readonly Dictionary<GameObject, Vector3> BakedOffsets = new Dictionary<GameObject, Vector3>(); private static Smelter s_smelter; private ZNetView _nview; private GameObject _working; private Transform _outputPoint; private Transform _feedPoint; internal readonly int[] Played = new int[3]; internal bool WorkingShown { get { if ((Object)(object)_working != (Object)null) { return _working.activeSelf; } return false; } } private void Awake() { _nview = ((Component)this).GetComponent<ZNetView>(); Transform obj = ((Component)this).transform.Find("_working"); _working = ((obj != null) ? ((Component)obj).gameObject : null); _outputPoint = ((Component)this).transform.Find("output_point") ?? ((Component)this).transform; _feedPoint = ((Component)this).transform.Find("feed_point") ?? ((Component)this).transform; if ((Object)(object)_nview != (Object)null && _nview.GetZDO() != null) { ((MonoBehaviour)this).InvokeRepeating("UpdateWorking", 0.2f, 0.5f); } } private void UpdateWorking() { if (!((Object)(object)_working == (Object)null) && !((Object)(object)_nview == (Object)null) && _nview.IsValid()) { bool flag = _nview.GetZDO().GetInt("dvergrreclaimer_state", 0) == 1; if (_working.activeSelf != flag) { _working.SetActive(flag); } } } internal void Play(ReclaimerEffect effect) { Played[(int)effect]++; if ((Object)(object)s_smelter == (Object)null) { ZNetScene instance = ZNetScene.instance; object obj; if (instance == null) { obj = null; } else { GameObject prefab = instance.GetPrefab("smelter"); obj = ((prefab != null) ? prefab.GetComponent<Smelter>() : null); } s_smelter = (Smelter)obj; if ((Object)(object)s_smelter == (Object)null) { return; } } switch (effect) { case ReclaimerEffect.ItemAdded: Create(s_smelter.m_oreAddedEffects, _feedPoint); break; case ReclaimerEffect.FuelAdded: Create(s_smelter.m_fuelAddedEffects, _feedPoint); break; case ReclaimerEffect.Produced: Create(s_smelter.m_produceEffects, _outputPoint); break; } } private void Create(EffectList effects, Transform at) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) EffectData[] effectPrefabs = effects.m_effectPrefabs; foreach (EffectData val in effectPrefabs) { if (val.m_enabled && !((Object)(object)val.m_prefab == (Object)null)) { Vector3 val2 = BakedOffset(val.m_prefab); Object.Instantiate<GameObject>(val.m_prefab, at.position - ((Component)this).transform.rotation * val2, ((Component)this).transform.rotation); } } } private static Vector3 BakedOffset(GameObject prefab) { //IL_0055: 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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (!BakedOffsets.TryGetValue(prefab, out var value)) { value = (((Object)(object)prefab.GetComponent<ParticleSystem>() == (Object)null && prefab.transform.childCount > 0) ? prefab.transform.GetChild(0).localPosition : Vector3.zero); BakedOffsets[prefab] = value; } return value; } } internal enum MachineState { Idle, Working, Blocked } internal sealed class ReclaimerMachine : MonoBehaviour { internal struct PanelState { public string Status; public string YieldHeader; public string ProcessingHeader; public string FuelAmount; public string Progress; } [HarmonyPatch(typeof(Container), "GetHoverText")] private static class ContainerHoverPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "shudnal.MyLittleUI" })] private static void Postfix(Container __instance, ref string __result) { ReclaimerMachine component = ((Component)__instance).GetComponent<ReclaimerMachine>(); if ((Object)(object)component != (Object)null) { __result += component.HoverText(); } } } internal const string FuelKey = "dvergrreclaimer_fuel"; internal const string BankKey = "dvergrreclaimer_bank"; internal const string LastTimeKey = "dvergrreclaimer_lastTime"; internal const string ProcessingKey = "dvergrreclaimer_processing"; internal const string ProcessingNameKey = "dvergrreclaimer_processingName"; internal const string OutputKey = "dvergrreclaimer_output"; internal const string OutputStacksKey = "dvergrreclaimer_outputStacks"; internal const string StateKey = "dvergrreclaimer_state"; internal const string StatusKey = "dvergrreclaimer_status"; private const int OutputWidth = 8; private const int OutputHeight = 4; private const float MaxBankSeconds = 3600f; private const float SkipSeconds = 10f; private const float TransferTimeoutSeconds = 10f; private static HashSet<string> s_allRecipes; private static HashSet<string> s_allMaterials; private readonly Dictionary<string, float> _skipUntil = new Dictionary<string, float>(); private readonly Dictionary<string, string> _skipReasons = new Dictionary<string, string>(); private ZNetView _nview; private Container _crate; private Piece _piece; private Transform _outputPoint; private ReclaimerEffects _effects; private float _transferDeadline; private string _previewKey; private float _previewTime; private List<ItemData> _preview = new List<ItemData>(); private bool TransferPending => Time.time < _transferDeadline; private void Awake() { _nview = ((Component)this).GetComponent<ZNetView>(); _crate = ((Component)this).GetComponent<Container>(); _piece = ((Component)this).GetComponent<Piece>(); _outputPoint = ((Component)this).transform.Find("output_point") ?? ((Component)this).transform; _effects = ((Component)this).GetComponent<ReclaimerEffects>(); if ((Object)(object)_nview != (Object)null && _nview.GetZDO() != null) { ((MonoBehaviour)this).InvokeRepeating("Tick", 1f, 1f); WearNTear component = ((Component)this).GetComponent<WearNTear>(); if ((Object)(object)component != (Object)null) { component.m_onDestroyed = (Action)Delegate.Combine(component.m_onDestroyed, new Action(OnDestroyed)); } } } private void OnDestroyed() { if (!((Object)(object)_nview == (Object)null) && _nview.IsValid() && _nview.IsOwner()) { ZDO zDO = _nview.GetZDO(); Drop(LoadInventory(zDO, "dvergrreclaimer_processing", 1, 1).GetAllItems()); Drop(LoadInventory(zDO, "dvergrreclaimer_output", 8, 4).GetAllItems()); int num = Mathf.FloorToInt(zDO.GetFloat("dvergrreclaimer_fuel", 0f)); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(ReclaimerConfig.FuelItem.Value); if (num > 0 && (Object)(object)itemPrefab != (Object)null) { ItemData val = itemPrefab.GetComponent<ItemDrop>().m_itemData.Clone(); val.m_dropPrefab = itemPrefab; val.m_stack = num; Drop(SplitStacks(val)); } } } private static IEnumerable<ItemData> SplitStacks(ItemData item) { int maxStack = Mathf.Max(1, item.m_shared.m_maxStackSize); int remaining = item.m_stack; while (remaining > 0) { ItemData val = item.Clone(); val.m_stack = Mathf.Min(remaining, maxStack); remaining -= val.m_stack; yield return val; } } private void Tick() { if ((Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner()) { return; } try { RunTick(); } catch (Exception arg) { DvergrReclaimerPlugin.Log.LogError((object)$"Reclaimer tick failed: {arg}"); } } private void RunTick() { ZDO zDO = _nview.GetZDO(); long ticks = ZNet.instance.GetTime().Ticks; float num = (float)Math.Max(0.0, (double)(ticks - zDO.GetLong("dvergrreclaimer_lastTime", ticks)) / 10000000.0); zDO.Set("dvergrreclaimer_lastTime", ticks); if (!RnrEngine.Available) { Report(zDO, MachineState.Blocked, "Recycle N' Reclaim is missing or incompatible"); return; } float num2 = zDO.GetFloat("dvergrreclaimer_fuel", 0f); float num3 = zDO.GetFloat("dvergrreclaimer_bank", 0f); int num4 = Mathf.Max(0, ReclaimerConfig.FuelPerItem.Value); Inventory val = LoadInventory(zDO, "dvergrreclaimer_output", 8, 4); Inventory val2 = LoadInventory(zDO, "dvergrreclaimer_processing", 1, 1); ItemData val3 = val2.GetAllItems().FirstOrDefault(); bool flag = DeliverToOwnedChests(val); flag |= DropUndeliverable(val); bool flag2 = false; bool flag3 = false; ReclaimContext context = BuildContext(); MachineState state = MachineState.Idle; string status = null; if (val3 == null) { val3 = TakeNextItem(context, out status); flag2 = val3 != null; } if (val3 == null) { num3 = 0f; state = ((status != null) ? MachineState.Blocked : MachineState.Idle); } else { num3 = Mathf.Min(num3 + num, 3600f); int num5 = 0; while (val3 != null && num5 < ReclaimerConfig.ItemsPerTick.Value) { if (num2 < (float)num4) { float num6 = TakeFuelFromCrate(num2); num2 += num6; if (num6 > 0f) { _effects?.Play(ReclaimerEffect.FuelAdded); } } if (num2 < (float)num4) { state = MachineState.Blocked; status = "Needs " + FuelDisplayName(); break; } ReclaimResult reclaimResult = Processing.Analyze(val3, context); if (!reclaimResult.Ok) { state = MachineState.Blocked; status = CantReclaim(val3, reclaimResult); ReturnToCrate(val3); val3 = null; flag2 = true; break; } if (ReclaimerConfig.WhenNoRoom.Value == NoRoomBehavior.Wait && !Fits(val, reclaimResult.Yields)) { state = MachineState.Blocked; status = "Output full: no room in nearby chests"; break; } if (num3 < ReclaimerConfig.SecondsPerItem.Value) { state = MachineState.Working; break; } ReclaimResult reclaimResult2 = Processing.Process(val3, context); if (!reclaimResult2.Ok) { state = MachineState.Blocked; status = CantReclaim(val3, reclaimResult2); ReturnToCrate(val3); val3 = null; flag2 = true; break; } Drop(ContainerTransfer.AddWhatFits(val, reclaimResult2.Yields)); num2 -= (float)num4; num3 -= ReclaimerConfig.SecondsPerItem.Value; val3 = null; flag2 = (flag = (flag3 = true)); num5++; DeliverToOwnedChests(val); DropUndeliverable(val); val3 = TakeNextItem(context, out status); state = ((val3 != null) ? MachineState.Working : ((status != null) ? MachineState.Blocked : MachineState.Idle)); } } zDO.Set("dvergrreclaimer_fuel", num2); zDO.Set("dvergrreclaimer_bank", (val3 == null) ? 0f : num3); if (flag2) { val2.RemoveAll(); if (val3 != null) { val2.AddItem(val3); } SaveInventory(zDO, "dvergrreclaimer_processing", val2); zDO.Set("dvergrreclaimer_processingName", val3?.m_shared.m_name ?? ""); } if (flag) { SaveInventory(zDO, "dvergrreclaimer_output", val); zDO.Set("dvergrreclaimer_outputStacks", val.NrOfItems()); } Report(zDO, state, status); if (flag3) { _effects?.Play(ReclaimerEffect.Produced); } if (val3 != null && num2 < (float)num4) { RequestFuel(num2); } if (val.NrOfItems() > 0) { RequestDeposit(val); } } private ItemData TakeNextItem(ReclaimContext context, out string status) { status = null; string text = FuelName(); Inventory inventory = _crate.GetInventory(); foreach (ItemData item in (from i in inventory.GetAllItems() orderby i.m_gridPos.y, i.m_gridPos.x select i).ToList()) { if (item.m_shared.m_name == text || IsBeingDragged(item)) { continue; } string key = item.m_shared.m_name + "#" + item.m_quality; if (_skipUntil.TryGetValue(key, out var value) && Time.time < value) { status = status ?? _skipReasons[key]; continue; } ReclaimResult reclaimResult = Processing.Analyze(item, context); if (!reclaimResult.Ok) { _skipUntil[key] = Time.time + 10f; _skipReasons[key] = CantReclaim(item, reclaimResult); status = status ?? _skipReasons[key]; continue; } inventory.RemoveItem(item); _effects?.Play(ReclaimerEffect.ItemAdded); return item; } return null; } private static bool IsBeingDragged(ItemData item) { if ((Object)(object)InventoryGui.instance != (Object)null) { return InventoryGui.instance.m_dragItem == item; } return false; } private float TakeFuelFromCrate(float fuel) { Inventory inventory = _crate.GetInventory(); ItemData val = InventoryGui.instance?.m_dragItem; if (val != null && val.m_shared.m_name == FuelName() && inventory.ContainsItem(val)) { return 0f; } int num = Mathf.Min(inventory.CountItems(FuelName(), -1, true), Mathf.FloorToInt((float)ReclaimerConfig.MaxFuel.Value - fuel)); if (num <= 0) { return 0f; } inventory.RemoveItem(FuelName(), num, -1, true); return num; } private bool DeliverToOwnedChests(Inventory output) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (output.NrOfItems() == 0) { return false; } List<Container> list = (from c in ChestFinder.Nearby(((Component)this).transform.position, ReclaimerConfig.OutputRadius.Value, _piece.GetCreator()) where c.m_nview.IsOwner() select c).ToList(); if (list.Count == 0) { return false; } bool flag = false; foreach (ItemData item in output.GetAllItems().ToList()) { output.RemoveItem(item); ItemData val = item; foreach (Container item2 in Rank(list, item)) { List<ItemData> list2 = ContainerTransfer.AddWhatFits(item2.GetInventory(), (IEnumerable<ItemData>)(object)new ItemData[1] { val }); flag |= list2.Count == 0 || list2[0].m_stack < val.m_stack; val = list2.FirstOrDefault(); if (val == null) { break; } } if (val != null) { output.AddItem(val); } } return flag; } private bool DropUndeliverable(Inventory output) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (ReclaimerConfig.WhenNoRoom.Value != NoRoomBehavior.DropInFront || output.NrOfItems() == 0) { return false; } List<Container> source = (from c in ChestFinder.Nearby(((Component)this).transform.position, ReclaimerConfig.OutputRadius.Value, _piece.GetCreator()) where !c.m_nview.IsOwner() select c).ToList(); bool result = false; foreach (ItemData stack in output.GetAllItems().ToList()) { if (!source.Any((Container c) => c.GetInventory().CanAddItem(stack, 1))) { output.RemoveItem(stack); Drop((IEnumerable<ItemData>)(object)new ItemData[1] { stack }); result = true; } } return result; } private void RequestDeposit(Inventory output) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (TransferPending) { return; } ItemData stack = output.GetAllItems().First(); Container val = Rank(from c in ChestFinder.Nearby(((Component)this).transform.position, ReclaimerConfig.OutputRadius.Value, _piece.GetCreator()) where !c.m_nview.IsOwner() select c, stack).FirstOrDefault((Func<Container, bool>)((Container c) => c.GetInventory().CanAddItem(stack, 1))); if (!((Object)(object)val == (Object)null)) { output.RemoveItem(stack); ZDO zDO = _nview.GetZDO(); SaveInventory(zDO, "dvergrreclaimer_output", output); zDO.Set("dvergrreclaimer_outputStacks", output.NrOfItems()); _transferDeadline = Time.time + 10f; ContainerTransfer.Deposit(val, new List<ItemData> { stack }, delegate(List<ItemData> leftovers) { _transferDeadline = 0f; ReturnToOutput(leftovers); }); } } private void RequestFuel(float fuel) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.FloorToInt((float)ReclaimerConfig.MaxFuel.Value - fuel); if (!ReclaimerConfig.PullFuelFromChests.Value || TransferPending || num <= 0) { return; } string fuelName = FuelName(); Container val = ChestFinder.Nearby(((Component)this).transform.position, ReclaimerConfig.FuelRadius.Value, _piece.GetCreator()).FirstOrDefault((Func<Container, bool>)((Container c) => c.GetInventory().CountItems(fuelName, -1, true) > 0)); if (!((Object)(object)val == (Object)null)) { _transferDeadline = Time.time + 10f; ContainerTransfer.Withdraw(val, fuelName, num, delegate(List<ItemData> items) { _transferDeadline = 0f; AddFuel(items); }); } } private void AddFuel(List<ItemData> items) { if (items.Count == 0) { return; } if ((Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner()) { Drop(items); return; } ZDO zDO = _nview.GetZDO(); float num = zDO.GetFloat("dvergrreclaimer_fuel", 0f); int num2 = Mathf.FloorToInt((float)ReclaimerConfig.MaxFuel.Value - num); int num3 = items.Sum((ItemData i) => i.m_stack); zDO.Set("dvergrreclaimer_fuel", num + (float)Mathf.Min(num2, num3)); _effects?.Play(ReclaimerEffect.FuelAdded); if (num3 > num2) { ItemData val = items[0].Clone(); val.m_stack = num3 - num2; ReturnToCrate(val); } } private void ReturnToOutput(List<ItemData> items) { if (items.Count != 0) { if ((Object)(object)_nview == (Object)null || !_nview.IsValid() || !_nview.IsOwner()) { Drop(items); return; } ZDO zDO = _nview.GetZDO(); Inventory val = LoadInventory(zDO, "dvergrreclaimer_output", 8, 4); Drop(ContainerTransfer.AddWhatFits(val, items)); SaveInventory(zDO, "dvergrreclaimer_output", val); zDO.Set("dvergrreclaimer_outputStacks", val.NrOfItems()); } } private void ReturnToCrate(ItemData item) { Drop(ContainerTransfer.AddWhatFits(_crate.GetInventory(), (IEnumerable<ItemData>)(object)new ItemData[1] { item })); } private void Drop(IEnumerable<ItemData> items) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) foreach (ItemData item in items) { ItemDrop.DropItem(item, item.m_stack, _outputPoint.position + Vector3.up * 0.3f, Quaternion.identity); } } private static IEnumerable<Container> Rank(IEnumerable<Container> chests, ItemData item) { return chests.OrderByDescending((Container c) => c.GetInventory().ContainsItemByName(item.m_shared.m_name)); } private static bool Fits(Inventory output, IEnumerable<ItemData> yields) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown Inventory val = new Inventory("DvergrReclaimerFitCheck", (Sprite)null, output.GetWidth(), output.GetHeight()); ZPackage val2 = new ZPackage(); output.Save(val2); val2.SetPos(0); val.Load(val2); return ContainerTransfer.AddWhatFits(val, yields.Select((ItemData y) => y.Clone())).Count == 0; } private ReclaimContext BuildContext() { //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) if (ReclaimerConfig.RecipeGate.Value == RecipeGateMode.Off) { if (s_allRecipes == null) { s_allRecipes = new HashSet<string>(from r in ObjectDB.instance.m_recipes select r?.m_item?.m_itemData?.m_shared?.m_name into n where n != null select n); s_allMaterials = new HashSet<string>(from i in ObjectDB.instance.m_items select (i == null) ? null : i.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_name into n where n != null select n); } return new ReclaimContext { KnownRecipes = s_allRecipes, KnownMaterials = s_allMaterials, Position = ((Component)this).transform.position, StationRadius = ReclaimerConfig.StationRadius.Value, SkipLedgerGate = true }; } return new ReclaimContext { KnownRecipes = RecipeLedger.Current.Recipes, KnownMaterials = RecipeLedger.Current.Materials, Position = ((Component)this).transform.position, StationRadius = ReclaimerConfig.StationRadius.Value }; } private static string CantReclaim(ItemData item, ReclaimResult result) { string text = Localization.instance.Localize(item.m_shared.m_name); if (result.Sacrifice) { return "Can't sacrifice " + text + ": " + (result.Reasons.FirstOrDefault() ?? result.Status.ToString()); } return result.Status switch { ReclaimStatus.RecipeUnknown => "Can't reclaim " + text + ": nobody on this world knows its recipe", ReclaimStatus.NoRecipe => "Can't reclaim " + text + ": it has no recipe", _ => "Can't reclaim " + text + ": " + (result.Reasons.FirstOrDefault() ?? result.Status.ToString()), }; } private static string FuelName() { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(ReclaimerConfig.FuelItem.Value); return ((itemPrefab == null) ? null : itemPrefab.GetComponent<ItemDrop>()?.m_itemData.m_shared.m_name) ?? "$item_coal"; } private static string FuelDisplayName() { return Localization.instance.Localize(FuelName()); } private static Inventory LoadInventory(ZDO zdo, string key, int width, int height) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Inventory val = new Inventory("DvergrReclaimer", (Sprite)null, width, height); byte[] byteArray = zdo.GetByteArray(key, (byte[])null); if (byteArray != null) { val.Load(new ZPackage(byteArray)); } return val; } private static void SaveInventory(ZDO zdo, string key, 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); zdo.Set(key, val.GetArray()); } private static void Report(ZDO zdo, MachineState state, string status) { zdo.Set("dvergrreclaimer_state", (int)state); zdo.Set("dvergrreclaimer_status", status ?? ""); } internal PanelState PanelView(Inventory display) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) display.RemoveAll(); ZDO zDO = _nview.GetZDO(); int num = Mathf.FloorToInt(zDO.GetFloat("dvergrreclaimer_fuel", 0f)); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(ReclaimerConfig.FuelItem.Value); if (num > 0 && (Object)(object)itemPrefab != (Object)null) { ItemData val = itemPrefab.GetComponent<ItemDrop>().m_itemData.Clone(); val.m_dropPrefab = itemPrefab; val.m_stack = num; display.AddItem(val, new Vector2i(0, 0)); } ItemData val2 = LoadInventory(zDO, "dvergrreclaimer_processing", 1, 1).GetAllItems().FirstOrDefault(); if (val2 != null) { display.AddItem(val2, new Vector2i(2, 0)); } List<ItemData> list = Preview(val2); int num2 = 4; for (int i = 0; i < list.Count && i < num2; i++) { display.AddItem(list[i].Clone(), new Vector2i(4 + i, 0)); } string text = zDO.GetString("dvergrreclaimer_status", ""); string text2 = ((zDO.GetInt("dvergrreclaimer_state", 0) == 2 && text.Length > 0) ? ("<color=#ff9a76>" + text + "</color>") : ((val2 == null) ? "Put gear in the crate to reclaim it" : (Processing.Verb(val2) + " " + Localization.instance.Localize(val2.m_shared.m_name)))); int num3 = zDO.GetInt("dvergrreclaimer_outputStacks", 0); if (num3 > 0) { text2 += string.Format(" · {0} stack{1} waiting to unload", num3, (num3 == 1) ? "" : "s"); } float num4 = ((val2 == null) ? 0f : Mathf.Clamp01(zDO.GetFloat("dvergrreclaimer_bank", 0f) / Mathf.Max(0.01f, ReclaimerConfig.SecondsPerItem.Value))); return new PanelState { Status = text2, YieldHeader = ((list.Count > num2) ? $"Produces (+{list.Count - num2} more)" : "Produces"), FuelAmount = $"{num}/{ReclaimerConfig.MaxFuel.Value}", Progress = $"{Mathf.FloorToInt(num4 * 100f)}%", ProcessingHeader = ((val2 != null) ? Processing.Verb(val2) : "Reclaiming") }; } private List<ItemData> Preview(ItemData processing) { if (processing == null || !RnrEngine.Available) { _previewKey = null; return new List<ItemData>(); } string text = $"{processing.m_shared.m_name}#{processing.m_quality}#{processing.m_stack}"; if (text != _previewKey || Time.time - _previewTime > 2f) { ReclaimResult reclaimResult = Processing.Analyze(processing, BuildContext()); _preview = (reclaimResult.Ok ? reclaimResult.Yields : new List<ItemData>()); _previewKey = text; _previewTime = Time.time; } return _preview; } internal string HoverText() { ZNetView nview = _nview; ZDO val = ((nview != null) ? nview.GetZDO() : null); if (val == null) { return ""; } StringBuilder stringBuilder = new StringBuilder(); if (ReclaimerConfig.FuelPerItem.Value > 0) { stringBuilder.Append(string.Format("\n{0}: {1}/{2}", FuelDisplayName(), Mathf.FloorToInt(val.GetFloat("dvergrreclaimer_fuel", 0f)), ReclaimerConfig.MaxFuel.Value)); } string fuelName = FuelName(); stringBuilder.Append($"\nQueued: {_crate.GetInventory().GetAllItems().Count((ItemData i) => i.m_shared.m_name != fuelName)}"); string text = val.GetString("dvergrreclaimer_processingName", ""); if (!string.IsNullOrEmpty(text)) { ItemData val2 = LoadInventory(val, "dvergrreclaimer_processing", 1, 1).GetAllItems().FirstOrDefault(); string arg = ((val2 != null) ? Processing.Verb(val2) : "Reclaiming"); float num = Mathf.Clamp01(val.GetFloat("dvergrreclaimer_bank", 0f) / Mathf.Max(0.01f, ReclaimerConfig.SecondsPerItem.Value)); stringBuilder.Append($"\n{arg}: {Localization.instance.Localize(text)} ({num:P0})"); } int num2 = val.GetInt("dvergrreclaimer_outputStacks", 0); if (num2 > 0) { stringBuilder.Append(string.Format("\nWaiting to unload: {0} stack{1}", num2, (num2 == 1) ? "" : "s")); } string text2 = val.GetString("dvergrreclaimer_status", ""); if (val.GetInt("dvergrreclaimer_state", 0) == 2 && text2.Length > 0) { stringBuilder.Append("\n<color=#ff9a76>" + text2 + "</color>"); } return stringBuilder.ToString(); } } } namespace DvergrReclaimer.Logistics { internal static class ChestFinder { [HarmonyPatch(typeof(Container), "Awake")] private static class ContainerAwakePatch { private static void Postfix(Container __instance) { if ((Object)(object)__instance.m_nview != (Object)null && __instance.m_nview.GetZDO() != null) { Loaded.Add(__instance); } } } private static readonly HashSet<Container> Loaded = new HashSet<Container>(); public static IEnumerable<Container> Nearby(Vector3 position, float radius, long creator) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Loaded.RemoveWhere((Container c) => (Object)(object)c == (Object)null); return from c in Loaded where Vector3.Distance(((Component)c).transform.position, position) <= radius && Usable(c, creator) orderby Vector3.Distance(((Component)c).transform.position, position) select c; } private static bool Usable(Container chest, long creator) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) ZNetView nview = chest.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid() || chest.m_lastRevision == uint.MaxValue) { return false; } if (nview.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 1) { return false; } if ((Object)(object)((Component)chest).GetComponent<ReclaimerMachine>() != (Object)null || (Object)(object)((Component)chest).GetComponent<TombStone>() != (Object)null || (Object)(object)((Component)chest).GetComponentInParent<Ship>() != (Object)null || (Object)(object)((Component)chest).GetComponentInParent<Vagon>() != (Object)null) { return false; } Piece component = ((Component)chest).GetComponent<Piece>(); if ((Object)(object)component != (Object)null && component.IsPlacedByPlayer() && chest.CheckAccess(creator)) { return WardAllows(((Component)chest).transform.position, creator); } return false; } private static bool WardAllows(Vector3 point, long creator) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) foreach (PrivateArea allArea in PrivateArea.m_allAreas) { if ((Object)(object)allArea != (Object)null && allArea.IsEnabled() && allArea.IsInside(point, 0f) && allArea.m_piece.GetCreator() != creator && !allArea.IsPermitted(creator)) { return false; } } return true; } } internal static class ContainerTransfer { [HarmonyPatch(typeof(Container), "Awake")] private static class ContainerAwakePatch { private static void Postfix(Container __instance) { ZNetView nview = __instance.m_nview; if (!((Object)(object)nview == (Object)null) && nview.GetZDO() != null) { nview.Register<long, ZPackage>("DvergrReclaimer_Deposit", (Action<long, long, ZPackage>)delegate(long sender, long id, ZPackage pkg) { OnDeposit(__instance, sender, id, pkg); }); nview.Register<long, string, int>("DvergrReclaimer_Withdraw", (Action<long, long, string, int>)delegate(long sender, long id, string name, int count) { OnWithdraw(__instance, sender, id, name, count); }); } } } private const string DepositRpc = "DvergrReclaimer_Deposit"; private const string WithdrawRpc = "DvergrReclaimer_Withdraw"; private const string ResultRpc = "DvergrReclaimer_TransferResult"; private const int PackWidth = 8; private static readonly Dictionary<long, Action<List<ItemData>>> Pending = new Dictionary<long, Action<List<ItemData>>>(); private static long _lastRequestId; public static int PendingCount => Pending.Count; internal static void Register() { Pending.Clear(); ZRoutedRpc.instance.Register<long, ZPackage>("DvergrReclaimer_TransferResult", (Action<long, long, ZPackage>)OnResult); } public static void Deposit(Container chest, List<ItemData> items, Action<List<ItemData>> onLeftovers, bool forceRpc = false) { ZNetView nview = chest.m_nview; if (!forceRpc && nview.IsOwner()) { onLeftovers(AddWhatFits(chest.GetInventory(), items)); return; } long num = ++_lastRequestId; Pending[num] = onLeftovers; nview.InvokeRPC("DvergrReclaimer_Deposit", new object[2] { num, Pack(items) }); } public static void Withdraw(Container chest, string itemName, int count, Action<List<ItemData>> onWithdrawn, bool forceRpc = false) { ZNetView nview = chest.m_nview; if (!forceRpc && nview.IsOwner()) { onWithdrawn(RemoveUpTo(chest.GetInventory(), itemName, count)); return; } long num = ++_lastRequestId; Pending[num] = onWithdrawn; nview.InvokeRPC("DvergrReclaimer_Withdraw", new object[3] { num, itemName, count }); } internal static List<ItemData> AddWhatFits(Inventory inventory, IEnumerable<ItemData> items) { List<ItemData> list = new List<ItemData>(); foreach (ItemData item in items) { SharedData shared = item.m_shared; if (shared.m_maxStackSize <= 1) { if (!inventory.HaveEmptySlot() || !inventory.AddItem(item)) { list.Add(item); } continue; } int num = Math.Min(item.m_stack, inventory.FindFreeStackSpace(shared.m_name, (float)item.m_worldLevel) + inventory.GetEmptySlots() * shared.m_maxStackSize); int num2 = num; while (num2 > 0) { ItemData val = item.Clone(); val.m_stack = Math.Min(num2, shared.m_maxStackSize); num2 -= val.m_stack; inventory.AddItem(val); } if (num < item.m_stack) { ItemData val2 = item.Clone(); val2.m_stack = item.m_stack - num; list.Add(val2); } } return list; } internal static List<ItemData> RemoveUpTo(Inventory inventory, string itemName, int count) { List<ItemData> list = new List<ItemData>(); foreach (ItemData item in (from i in inventory.GetAllItems() where i.m_shared.m_name == itemName select i).ToList()) { if (count <= 0) { break; } if (item.m_stack <= count) { inventory.RemoveItem(item); list.Add(item); count -= item.m_stack; } else { ItemData val = item.Clone(); val.m_stack = count; inventory.RemoveItem(item, count); list.Add(val); count = 0; } } return list; } internal static ZPackage Pack(IEnumerable<ItemData> items) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Expected O, but got Unknown //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected O, but got Unknown List<ItemData> list = new List<ItemData>(); foreach (ItemData item in items) { int val = Math.Max(1, item.m_shared.m_maxStackSize); int num = item.m_stack; while (num > 0) { ItemData val2 = item.Clone(); val2.m_stack = Math.Min(num, val); num -= val2.m_stack; list.Add(val2); } } int num2 = Math.Max(1, (list.Count + 8 - 1) / 8); Inventory val3 = new Inventory("DvergrReclaimerTransfer", (Sprite)null, 8, num2); for (int i = 0; i < list.Count; i++) { list[i].m_gridPos = new Vector2i(i % 8, i / 8); val3.m_inventory.Add(list[i]); } ZPackage val4 = new ZPackage(); val3.Save(val4); ZPackage val5 = new ZPackage(); val5.Write(8); val5.Write(num2); val5.Write(val4); return val5; } internal static List<ItemData> Unpack(ZPackage pkg) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) int num = pkg.ReadInt(); int num2 = pkg.ReadInt(); Inventory val = new Inventory("DvergrReclaimerTransfer", (Sprite)null, num, num2); val.Load(pkg.ReadPackage()); return val.GetAllItems().ToList(); } private static void OnDeposit(Container chest, long sender, long requestId, ZPackage pkg) { List<ItemData> list = Unpack(pkg); List<ItemData> items = (chest.m_nview.IsOwner() ? AddWhatFits(chest.GetInventory(), list) : list); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "DvergrReclaimer_TransferResult", new object[2] { requestId, Pack(items) }); } private static void OnWithdraw(Container chest, long sender, long requestId, string itemName, int count) { List<ItemData> items = (chest.m_nview.IsOwner() ? RemoveUpTo(chest.GetInventory(), itemName, count) : new List<ItemData>()); ZRoutedRpc.instance.InvokeRoutedRPC(sender, "DvergrReclaimer_TransferResult", new object[2] { requestId, Pack(items) }); } private static void OnResult(long sender, long requestId, ZPackage pkg) { if (Pending.TryGetValue(requestId, out var value)) { Pending.Remove(requestId); value(Unpack(pkg)); } } } } namespace DvergrReclaimer.Ledger { internal static class LedgerSync { [HarmonyPatch(typeof(Player), "OnSpawned")] private static class PlayerOnSpawnedPatch { private static void Postfix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { ReportFull(__instance); } } } [HarmonyPatch(typeof(Player), "AddKnownRecipe")] private static class PlayerAddKnownRecipePatch { private static void Prefix(Player __instance, Recipe recipe, out bool __state) { __state = __instance.IsRecipeKnown(recipe.m_item.m_itemData.m_shared.m_name); } private static void Postfix(Player __instance, Recipe recipe, bool __state) { string name = recipe.m_item.m_itemData.m_shared.m_name; if (!__state && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && __instance.IsRecipeKnown(name)) { ReportNew(__instance, name, null); } } } [HarmonyPatch(typeof(Player), "AddKnownItem")] private static class PlayerAddKnownItemPatch { private static void Prefix(Player __instance, ItemData item, out bool __state) { __state = __instance.IsKnownMaterial(item.m_shared.m_name); } private static void Postfix(Player __instance, ItemData item, bool __state) { string name = item.m_shared.m_name; if (!__state && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && __instance.IsKnownMaterial(name)) { ReportNew(__instance, null, name); } } } private const string ReportRpc = "DvergrReclaimer_LedgerReport"; private const string SyncRpc = "DvergrReclaimer_LedgerSync"; private static string _savePath; private static bool _reportedFull; public static int FullSyncsReceived { get; private set; } public static int DeltaSyncsReceived { get; private set; } public static string SavePath => _savePath; internal static void Register() { ZRoutedRpc.instance.Register<ZPackage>("DvergrReclaimer_LedgerReport", (Action<long, ZPackage>)OnReport); ZRoutedRpc.instance.Register<ZPackage>("DvergrReclaimer_LedgerSync", (Action<long, ZPackage>)OnSync); _reportedFull = false; FullSyncsReceived = 0; DeltaSyncsReceived = 0; if (ZNet.instance.IsServer()) { World world = ZNet.World; _savePath = Path.Combine(Paths.ConfigPath, "greatcoltini.DvergrReclaimer", $"{Sanitize(world.m_name)}_{world.m_uid}.ledger"); RecipeLedger.Replace(RecipeLedger.Load(_savePath)); RecipeLedger current = RecipeLedger.Current; DvergrReclaimerPlugin.Log.LogInfo((object)$"Loaded recipe ledger: {current.Recipes.Count} recipes from {current.Contributors.Count} players ({_savePath})"); } else { _savePath = null; RecipeLedger.Replace(new RecipeLedger()); } } internal static void ReportFull(Player player) { SendReport(player.GetPlayerID(), player.m_knownRecipes, player.m_knownMaterial, full: true); _reportedFull = true; } internal static void ReportNew(Player player, string recipe, string material) { if (_reportedFull) { string[] recipes = ((recipe == null) ? new string[0] : new string[1] { recipe }); string[] materials = ((material == null) ? new string[0] : new string[1] { material }); SendReport(player.GetPlayerID(), recipes, materials, full: false); } } internal static void SendReport(long playerId, ICollection<string> recipes, ICollection<string> materials, bool full) { //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(); val.Write(playerId); val.Write(full); RecipeLedger.WriteSet(val, recipes); RecipeLedger.WriteSet(val, materials); ZRoutedRpc.instance.InvokeRoutedRPC("DvergrReclaimer_LedgerReport", new object[1] { val }); } private static void OnReport(long sender, ZPackage pkg) { if (ZNet.instance.IsServer()) { long num = pkg.ReadLong(); bool flag = pkg.ReadBool(); List<string> list = new List<string>(); List<string> list2 = new List<string>(); RecipeLedger.ReadSet(pkg, list); RecipeLedger.ReadSet(pkg, list2); HashSet<string> hashSet = new HashSet<string>(from r in ObjectDB.instance.m_recipes select r?.m_item?.m_itemData?.m_shared?.m_name into n where n != null select n); HashSet<string> hashSet2 = new HashSet<string>(from i in ObjectDB.instance.m_items select (i == null) ? null : i.GetComponent<ItemDrop>()?.m_itemData?.m_shared?.m_name into n where n != null select n); RecipeLedger recipeLedger = RecipeLedger.Current.Merge(num, list.Where(hashSet.Contains), list2.Where(hashSet2.Contains)); if (!recipeLedger.IsEmpty) { RecipeLedger.Current.Save(_savePath); ZRoutedRpc.instance.InvokeRoutedRPC(0L, "DvergrReclaimer_LedgerSync", new object[1] { BuildSync(recipeLedger, full: false) }); DvergrReclaimerPlugin.Log.LogInfo((object)$"Recipe ledger grew by {recipeLedger.Recipes.Count} recipes and {recipeLedger.Materials.Count} materials from player {num}"); } if (flag) { ZRoutedRpc.instance.InvokeRoutedRPC(sender, "DvergrReclaimer_LedgerSync", new object[1] { BuildSync(RecipeLedger.Current, full: true) }); } } } private static ZPackage BuildSync(RecipeLedger ledger, bool full) { //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(); val.Write(full); ledger.Write(val); return val; } private static void OnSync(long sender, ZPackage pkg) { if (!ZNet.instance.IsServer()) { bool num = pkg.ReadBool(); RecipeLedger recipeLedger = RecipeLedger.Read(pkg); if (num) { RecipeLedger.Replace(recipeLedger); FullSyncsReceived++; } else { RecipeLedger.Current.UnionWith(recipeLedger); DeltaSyncsReceived++; } } } private static string Sanitize(string name) { char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { name = name.Replace(oldChar, '_'); } return name; } } internal sealed class RecipeLedger { public readonly HashSet<string> Recipes = new HashSet<string>(); public readonly HashSet<string> Materials = new HashSet<string>(); public readonly HashSet<long> Contributors = new HashSet<long>(); public static RecipeLedger Current { get; private set; } = new RecipeLedger(); public bool IsEmpty { get { if (Recipes.Count == 0 && Materials.Count == 0) { return Contributors.Count == 0; } return false; } } internal static void Replace(RecipeLedger ledger) { Current = ledger ?? new RecipeLedger(); } public RecipeLedger Merge(long playerId, IEnumerable<string> recipes, IEnumerable<string> materials) { RecipeLedger recipeLedger = new RecipeLedger(); if (Contributors.Add(playerId)) { recipeLedger.Contributors.Add(playerId); } foreach (string recipe in recipes) { if (Recipes.Add(recipe)) { recipeLedger.Recipes.Add(recipe); } } foreach (string material in materials) { if (Materials.Add(material)) { recipeLedger.Materials.Add(material); } } return recipeLedger; } public void UnionWith(RecipeLedger other) { Recipes.UnionWith(other.Recipes); Materials.UnionWith(other.Materials); Contributors.UnionWith(other.Contributors); } public void Write(ZPackage pkg) { pkg.Write(Contributors.Count); foreach (long contributor in Contributors) { pkg.Write(contributor); } WriteSet(pkg, Recipes); WriteSet(pkg, Materials); } public static RecipeLedger Read(ZPackage pkg) { RecipeLedger recipeLedger = new RecipeLedger(); int num = pkg.ReadInt(); for (int i = 0; i < num; i++) { recipeLedger.Contributors.Add(pkg.ReadLong()); } ReadSet(pkg, recipeLedger.Recipes); ReadSet(pkg, recipeLedger.Materials); return recipeLedger; } internal static void WriteSet(ZPackage pkg, ICollection<string> values) { pkg.Write(values.Count); foreach (string value in values) { pkg.Write(value); } } internal static void ReadSet(ZPackage pkg, ICollection<string> into) { int num = pkg.ReadInt(); for (int i = 0; i < num; i++) { into.Add(pkg.ReadString()); } } public void Save(string path) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# Dvergr Reclaimer recipe ledger: p = player ID, r = recipe, m = material"); foreach (long item in Contributors.OrderBy((long id) => id)) { stringBuilder.Append("p\t").AppendLine(item.ToString()); } foreach (string item2 in Recipes.OrderBy<string, string>((string r) => r, StringComparer.Ordinal)) { stringBuilder.Append("r\t").AppendLine(item2); } foreach (string item3 in Materials.OrderBy<string, string>((string m) => m, StringComparer.Ordinal)) { stringBuilder.Append("m\t").AppendLine(item3); } Directory.CreateDirectory(Path.GetDirectoryName(path)); string text = path + ".tmp"; File.WriteAllText(text, stringBuilder.ToString(), Encoding.UTF8); if (File.Exists(path)) { File.Delete(path); } File.Move(text, path); } public static RecipeLedger Load(string path) { RecipeLedger recipeLedger = new RecipeLedger(); if (!File.Exists(path)) { return recipeLedger; } string[] array = File.ReadAllLines(path, Encoding.UTF8); foreach (string text in array) { if (text.Length < 3 || text[1] != '\t') { continue; } string text2 = text.Substring(2); switch (text[0]) { case 'p': { if (long.TryParse(text2, out var result)) { recipeLedger.Contributors.Add(result); } break; } case 'r': recipeLedger.Recipes.Add(text2); break; case 'm': recipeLedger.Materials.Add(text2); break; } } return recipeLedger; } } } namespace DvergrReclaimer.Engine { internal static class EpicLootSacrifice { internal const string EpicLootGuid = "randyknapp.mods.epicloot"; private static MethodInfo s_getProducts; private static FieldInfo s_productItem; private static FieldInfo s_productAmount; private static Type s_tableType; private static MethodInfo s_isFeatureUnlocked; private static MethodInfo s_getFeatureValue; private static object s_sacrificeFeature; public static bool Available { get; private set; } public static string Problem { get; private set; } = "not probed yet"; public static bool Probe() { Available = false; if (!Chainloader.PluginInfos.ContainsKey("randyknapp.mods.epicloot")) { Problem = "Epic Loot is not installed"; return false; } try { Type type = AccessTools.TypeByName("EpicLoot.Crafting.EnchantCostsHelper"); s_getProducts = ((type == null) ? null : AccessTools.Method(type, "GetSacrificeProducts", new Type[1] { typeof(ItemData) }, (Type[])null)); Type type2 = s_getProducts?.ReturnType.GetGenericArguments().FirstOrDefault(); s_productItem = ((type2 == null) ? null : AccessTools.Field(type2, "Item")); s_productAmount = ((type2 == null) ? null : AccessTools.Field(type2, "Amount")); if (s_getProducts == null || !s_getProducts.IsStatic || s_productItem?.FieldType != typeof(string) || s_productAmount?.FieldType != typeof(int)) { Problem = "EnchantCostsHelper.GetSacrificeProducts(ItemData) changed"; return false; } s_tableType = AccessTools.TypeByName("EpicLoot_UnityLib.EnchantingTable"); Type type3 = AccessTools.TypeByName("EpicLoot_UnityLib.EnchantingFeature"); s_isFeatureUnlocked = ((s_tableType == null || type3 == null) ? null : AccessTools.Method(s_tableType, "IsFeatureUnlocked", new Type[1] { type3 }, (Type[])null)); s_getFeatureValue = ((s_tableType == null || type3 == null) ? null : AccessTools.Method(s_tableType, "GetFeatureCurrentValue", new Type[1] { type3 }, (Type[])null)); if (s_isFeatureUnlocked == null || s_getFeatureValue?.ReturnType != typeof(Tuple<float, float>) || !Enum.IsDefined(type3, "Sacrifice")) { Problem = "EnchantingTable's feature API changed"; return false; } s_sacrificeFeature = Enum.Parse(type3, "Sacrifice"); Problem = null; Available = true; return true; } catch (Exception ex) { Problem = ex.Message; return false; } } public static bool Handles(ItemData item) { if (Available && ReclaimerConfig.SacrificeWithoutRecipe.Value && item != null && (Object)(object)ObjectDB.instance.GetRecipe(item) == (Object)null) { return Products(item) != null; } return false; } public static ReclaimResult Run(ItemData item, ReclaimContext context, bool commit) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) ReclaimResult reclaimResult = new ReclaimResult { Sacrifice = true }; List<ItemData> list = Products(item); if (list == null) { reclaimResult.Status = ReclaimStatus.NoRecipe; reclaimResult.Reasons.Add("Epic Loot doesn't accept it as a sacrifice"); return reclaimResult; } float num = 0f; if (ReclaimerConfig.SacrificeNeedsTable.Value) { Component val = FindTable(context.Position, context.StationRadius); if ((Object)(object)val == (Object)null) { reclaimResult.Status = ReclaimStatus.Blocked; reclaimResult.Reasons.Add($"it needs an Enchanting Table within {context.StationRadius:0} m"); return reclaimResult; } num = BonusChance(val); } bool flag = commit && Random.value < num; foreach (ItemData item2 in list) { item2.m_stack *= item.m_stack * ((!flag) ? 1 : 2); reclaimResult.Yields.Add(item2); } reclaimResult.Doubled = flag; reclaimResult.Status = ReclaimStatus.Ok; return reclaimResult; } private static List<ItemData> Products(ItemData item) { if (!(s_getProducts.Invoke(null, new object[1] { item }) is IList { Count: not 0 } list)) { return null; } List<ItemData> list2 = new List<ItemData>(); foreach (object item2 in list) { GameObject itemPrefab = ObjectDB.instance.GetItemPrefab((string)s_productItem.GetValue(item2)); int num = (int)s_productAmount.GetValue(item2); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); if (!((Object)(object)val == (Object)null) && num > 0) { ItemData val2 = val.m_itemData.Clone(); val2.m_dropPrefab = itemPrefab; val2.m_stack = num; list2.Add(val2); } } if (list2.Count <= 0) { return null; } return list2; } private static Component FindTable(Vector3 position, float radius) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) List<Piece> list = new List<Piece>(); Piece.GetAllPiecesInRadius(position, radius, list); return (from p in list select ((Component)p).GetComponent(s_tableType) into t where (Object)(object)t != (Object)null && (bool)s_isFeatureUnlocked.Invoke(t, new object[1] { s_sacrificeFeature }) orderby Vector3.Distance(t.transform.position, position) select t).FirstOrDefault(); } private static float BonusChance(Component table) { Tuple<float, float> tuple = (Tuple<float, float>)s_getFeatureValue.Invoke(table, new object[1] { s_sacrificeFeature }); if (!float.IsNaN(tuple.Item1)) { return Mathf.Clamp01(tuple.Item1 / 100f); } return 0f; } } internal readonly struct PlayerContextScope : IDisposable { private static readonly FieldInfo KnownRecipesField = AccessTools.Field(typeof(Player), "m_knownRecipes"); private static readonly FieldInfo KnownMaterialField = AccessTools.Field(typeof(Player), "m_knownMaterial"); private static readonly FieldInfo CurrentStationField = AccessTools.Field(typeof(Player), "m_currentStation"); private readonly Player _player; private readonly object _recipes; private readonly object _materials; private readonly object _station; public PlayerContextScope(Player player, HashSet<string> recipes, HashSet<string> materials, CraftingStation station) { _player = player; _recipes = KnownRecipesField.GetValue(player); _materials = KnownMaterialField.GetValue(player); _station = CurrentStationField.GetValue(player); KnownRecipesField.SetValue(player, recipes); KnownMaterialField.SetValue(player, materials); CurrentStationField.SetValue(player, station); } public void Dispose() { KnownRecipesField.SetValue(_player, _recipes); KnownMaterialField.SetValue(_player, _materials); CurrentStationField.SetValue(_player, _station); } internal static bool Probe(out string problem) { if (KnownRecipesField?.FieldType != typeof(HashSet<string>)) { problem = "Player.m_knownRecipes was not found"; return false; } if (KnownMaterialField?.FieldType != typeof(HashSet<string>)) { problem = "Player.m_knownMaterial was not found"; return false; } if (CurrentStationField?.FieldType != typeof(CraftingStation)) { problem = "Player.m_currentStation was not found"; return false; } problem = null; return true; } } internal static class Processing { private static string s_verbItem; private static string s_verb; private static float s_verbTime = float.MinValue; public static ReclaimResult Analyze(ItemData item, ReclaimContext context) { if (!EpicLootSacrifice.Handles(item)) { return RnrEngine.Analyze(item, context); } return EpicLootSacrifice.Run(item, context, commit: false); } public static ReclaimResult Process(ItemData item, ReclaimContext context) { if (!EpicLootSacrifice.Handles(item)) { return RnrEngine.Reclaim(item, context); } return EpicLootSacrifice.Run(item, context, commit: true); } public static string Verb(ItemData item) { if (item.m_shared.m_name != s_verbItem || Time.time - s_verbTime > 1f) { s_verb = (EpicLootSacrifice.Handles(item) ? "Sacrificing" : "Reclaiming"); s_verbItem = item.m_shared.m_name; s_verbTime = Time.time; } return s_verb; } } [HarmonyPatch(typeof(InventoryGui), "Update")] internal static class ReclaimAllButton { private stat