using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using HexAutoStorage.Configuration; using HexAutoStorage.Core; using HexAutoStorage.Features; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("HexAutoStorage")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("HexAutoStorage")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("536277cd-6e46-4758-853f-ae19c2ded793")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace HexAutoStorage { [BepInPlugin("com.hex.autostorage", "HexAutoStorage", "1.1.0")] public class Plugin : BaseUnityPlugin { private const string PluginGuid = "com.hex.autostorage"; private const string PluginName = "HexAutoStorage"; private const string PluginVersion = "1.1.0"; private Harmony _harmonyInstance; internal static ManualLogSource Log; internal static Plugin Instance; private void Awake() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; StorageConfig.Bind(((BaseUnityPlugin)this).Config); Assembly executingAssembly = Assembly.GetExecutingAssembly(); _harmonyInstance = new Harmony("com.hex.autostorage"); _harmonyInstance.PatchAll(executingAssembly); Log.LogInfo((object)"HexAutoStorage v1.1.0 loaded."); } private void OnDestroy() { Log.LogInfo((object)"HexAutoStorage v1.1.0 unloaded."); Harmony harmonyInstance = _harmonyInstance; if (harmonyInstance != null) { harmonyInstance.UnpatchSelf(); } _harmonyInstance = null; Instance = null; Log = null; } } internal static class AutoStorageService { private const long InvalidCreator = 0L; internal static bool TryStore(Smelter smelter, ItemDrop producedItem, int stack) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)smelter == (Object)null || (Object)(object)producedItem == (Object)null || stack <= 0) { return false; } Piece component = ((Component)smelter).GetComponent(); if ((Object)(object)component == (Object)null) { return false; } long creator = component.GetCreator(); if (creator == 0L) { return false; } List list = FindNearbyContainers(((Component)smelter).transform.position, StorageConfig.StorageRadius.Value, creator); if (list.Count == 0) { return false; } string prefabName = ((Object)((Component)producedItem).gameObject).name; foreach (Container item in (from container in list where ContainerTagEditor.HasTag(container, prefabName) orderby Vector3.SqrMagnitude(((Component)container).transform.position - ((Component)smelter).transform.position) select container).ToList()) { if (TryAddToContainer(item, producedItem, stack)) { return true; } } foreach (Container item2 in (from container in list where !HasTags(container) && ContainsProducedItem(container, prefabName) orderby Vector3.SqrMagnitude(((Component)container).transform.position - ((Component)smelter).transform.position) select container).ToList()) { if (TryAddToContainer(item2, producedItem, stack)) { return true; } } foreach (Container item3 in (from container in list where !HasTags(container) && !ContainsProducedItem(container, prefabName) orderby Vector3.SqrMagnitude(((Component)container).transform.position - ((Component)smelter).transform.position) select container).ToList()) { if (TryAddToContainer(item3, producedItem, stack)) { return true; } } return false; } private static List FindNearbyContainers(Vector3 position, float radius, long creator) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Collider[] array = Physics.OverlapSphere(position, radius); HashSet hashSet = new HashSet(); Collider[] array2 = array; for (int i = 0; i < array2.Length; i++) { Piece componentInParent = ((Component)array2[i]).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null) && componentInParent.GetCreator() == creator && StoragePrefabRules.TryGetStorageType(((Object)((Component)componentInParent).gameObject).name.Replace("(Clone)", ""), out var _)) { Container componentInChildren = ((Component)componentInParent).GetComponentInChildren(true); if (!((Object)(object)componentInChildren == (Object)null)) { hashSet.Add(componentInChildren); } } } return hashSet.ToList(); } private static bool HasTags(Container container) { return !string.IsNullOrWhiteSpace(ContainerTagEditor.GetTags(container)); } private static bool ContainsProducedItem(Container container, string prefabName) { if ((Object)(object)container == (Object)null) { return false; } Inventory inventory = container.GetInventory(); if (inventory == null) { return false; } return inventory.GetItem(prefabName, -1, true) != null; } private static bool TryAddToContainer(Container container, ItemDrop producedItem, int stack) { if ((Object)(object)container == (Object)null || (Object)(object)producedItem == (Object)null) { return false; } Inventory inventory = container.GetInventory(); if (inventory == null) { return false; } ItemData val = producedItem.m_itemData.Clone(); val.m_stack = stack; if (!inventory.CanAddItem(val, stack)) { return false; } return inventory.AddItem(val); } } } namespace HexAutoStorage.Patches { [HarmonyPatch(typeof(Container), "GetHoverText")] [HarmonyAfter(new string[] { "shudnal.MyLittleUI" })] internal static class ContainerHoverTextPatch { [HarmonyPostfix] internal static void Postfix(Container __instance, ref string __result) { if ((Object)(object)Plugin.Instance == (Object)null || !StorageConfig.ModEnabled.Value || (Object)(object)__instance == (Object)null) { return; } Piece componentInParent = ((Component)__instance).GetComponentInParent(); if (!((Object)(object)componentInParent == (Object)null) && StoragePrefabRules.TryGetStorageType(((Object)((Component)componentInParent).gameObject).name.Replace("(Clone)", ""), out var _)) { string editTagsShortcutDisplay = ContainerTagEditor.GetEditTagsShortcutDisplay(); string text = ContainerTagEditor.GetDisplayTags(__instance); if (string.IsNullOrWhiteSpace(text)) { text = "None"; } __result = __result + "\n[" + editTagsShortcutDisplay + "] Edit Auto Storage Tags"; __result = __result + "\n[Tags: " + text + "]"; } } } [HarmonyPatch(typeof(Player), "FindHoverObject")] internal static class PlayerFindHoverObjectPatch { [HarmonyPostfix] internal static void Postfix(Player __instance, GameObject hover) { if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } ContainerTagEditor.HoveredObject = hover; if ((Object)(object)hover == (Object)null) { StorageRadiusVisualizer.HoveredSmelter = null; return; } Smelter componentInParent = hover.GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { StorageRadiusVisualizer.HoveredSmelter = null; return; } string text = ((Object)((Component)componentInParent).gameObject).name.Replace("(Clone)", ""); Plugin.Log.LogDebug((object)("FindHoverObject pathc Prefab name: " + text)); if (!StoragePrefabRules.IsSupportedSmelter(text)) { StorageRadiusVisualizer.HoveredSmelter = null; } else { StorageRadiusVisualizer.HoveredSmelter = componentInParent; } } } [HarmonyPatch(typeof(Player), "Start")] internal static class PlayerStartPatch { [HarmonyPostfix] internal static void Postfix(Player __instance) { if (!((Object)(object)Plugin.Instance == (Object)null) && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } [HarmonyPatch(typeof(Smelter), "Awake")] internal static class SmelterAwakePatch { [HarmonyPostfix] internal static void Postfix(Smelter __instance) { if (!((Object)(object)Plugin.Instance == (Object)null) && !((Object)(object)__instance == (Object)null) && StoragePrefabRules.IsSupportedSmelter(((Object)((Component)__instance).gameObject).name.Replace("(Clone)", "")) && (Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } [HarmonyPatch(typeof(Smelter), "Spawn")] internal static class SmelterSpawnPatch { [HarmonyPrefix] internal static bool Prefix(Smelter __instance, string ore, int stack) { //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Plugin.Instance == (Object)null || !StorageConfig.ModEnabled.Value) { return true; } if (!StoragePrefabRules.IsSupportedSmelter(((Object)((Component)__instance).gameObject).name.Replace("(Clone)", ""))) { return true; } ItemConversion val = null; foreach (ItemConversion item in __instance.m_conversion) { if ((Object)(object)item.m_from != (Object)null && ((Object)((Component)item.m_from).gameObject).name == ore) { val = item; break; } } if (val == null || (Object)(object)val.m_to == (Object)null) { return true; } if (!AutoStorageService.TryStore(__instance, val.m_to, stack)) { return true; } __instance.m_produceEffects.Create(((Component)__instance).transform.position, ((Component)__instance).transform.rotation, (Transform)null, 1f, -1); return false; } } [HarmonyPatch(typeof(TextInput), "setText")] internal static class TextInputSetTextPatch { [HarmonyPostfix] internal static void Postfix(string text) { if (ContainerTagEditor.IsEditing) { ContainerTagEditor.SaveTags(text); } } } [HarmonyPatch(typeof(TextInput), "Hide")] internal static class TextInputHidePatch { [HarmonyPostfix] internal static void Postfix() { if (ContainerTagEditor.IsEditing) { ContainerTagEditor.CancelEditing(); } } } } namespace HexAutoStorage.Features { internal class ContainerTagEditor : MonoBehaviour { private const string TagsKey = "HexAutoStorage_Tags"; private const int MaxTagLength = 200; private const string TagEditorTitle = "Auto Storage Tags - Example: Copper,Tin,Bronze,Iron,Silver,Blackmetal,Flametal,Coal,Flour,Eitr,Linen"; private static readonly Dictionary ValidTags = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "Copper", "Copper" }, { "Tin", "Tin" }, { "Bronze", "Bronze" }, { "Iron", "Iron" }, { "Silver", "Silver" }, { "Blackmetal", "BlackMetal" }, { "Flametal", "FlametalNew" }, { "Coal", "Coal" }, { "Flour", "BarleyFlour" }, { "Eitr", "Eitr" }, { "Linen", "LinenThread" } }; private static readonly Dictionary DisplayTags = ValidTags.ToDictionary, string, string>((KeyValuePair entry) => entry.Value, (KeyValuePair entry) => entry.Key, StringComparer.OrdinalIgnoreCase); private static readonly MethodInfo TextInputShowMethod = AccessTools.Method(typeof(TextInput), "Show", new Type[3] { typeof(string), typeof(string), typeof(int) }, (Type[])null); private static Container _editingContainer; internal static GameObject HoveredObject; internal static bool IsEditing => (Object)(object)_editingContainer != (Object)null; internal unsafe static string GetEditTagsShortcutDisplay() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = StorageConfig.EditTagsShortcut.Value; List list = ((KeyboardShortcut)(ref value)).Modifiers.Select((KeyCode modifier) => ((object)(*(KeyCode*)(&modifier))/*cast due to .constrained prefix*/).ToString()).ToList(); list.Add(((object)((KeyboardShortcut)(ref value)).MainKey/*cast due to .constrained prefix*/).ToString()); return string.Join(" + ", list); } private void Update() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Plugin.Instance == (Object)null) && StorageConfig.ModEnabled.Value && !((Object)(object)Player.m_localPlayer == (Object)null) && !IsEditing) { KeyboardShortcut value = StorageConfig.EditTagsShortcut.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { TryOpenHoveredContainer(); } } } private void TryOpenHoveredContainer() { if ((Object)(object)HoveredObject == (Object)null) { return; } Piece componentInParent = HoveredObject.GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { return; } string text = ((Object)((Component)componentInParent).gameObject).name.Replace("(Clone)", ""); if (!StoragePrefabRules.TryGetStorageType(text, out var _)) { return; } Container componentInChildren = ((Component)componentInParent).GetComponentInChildren(true); ZNetView component = ((Component)componentInParent).GetComponent(); if (!((Object)(object)componentInChildren == (Object)null) && !((Object)(object)component == (Object)null) && component.IsValid()) { if ((Object)(object)TextInput.instance == (Object)null) { Plugin.Log.LogDebug((object)"Unable to open Auto Storage tag editor because TextInput.instance is null."); return; } _editingContainer = componentInChildren; TextInputShowMethod.Invoke(TextInput.instance, new object[3] { "Auto Storage Tags - Example: Copper,Tin,Bronze,Iron,Silver,Blackmetal,Flametal,Coal,Flour,Eitr,Linen", GetDisplayTags(componentInChildren), 200 }); Plugin.Log.LogDebug((object)("Opened Auto Storage tag editor for " + text + ".")); } } private static bool TryNormalizeTags(string value, out string normalizedTags, out string invalidTag) { normalizedTags = ""; invalidTag = ""; if (string.IsNullOrWhiteSpace(value)) { return true; } List list = new List(); string[] array = value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (!string.IsNullOrWhiteSpace(text)) { if (!ValidTags.TryGetValue(text, out var value2)) { invalidTag = text; return false; } if (!list.Contains(value2, StringComparer.OrdinalIgnoreCase)) { list.Add(value2); } } } normalizedTags = string.Join(",", list); return true; } internal static string GetTags(Container container) { if (!TryGetContainerZNetView(container, out var nview)) { return ""; } return nview.GetZDO().GetString("HexAutoStorage_Tags", ""); } internal static bool HasTag(Container container, string prefabName) { if (string.IsNullOrWhiteSpace(prefabName)) { return false; } string tags = GetTags(container); if (string.IsNullOrWhiteSpace(tags)) { return false; } return tags.Split(new char[1] { ',' }).Any((string tag) => tag.Trim().Equals(prefabName, StringComparison.OrdinalIgnoreCase)); } internal static void SaveTags(string text) { if ((Object)(object)_editingContainer == (Object)null) { return; } string normalizedTags; string invalidTag; if (!TryGetContainerZNetView(_editingContainer, out var nview)) { _editingContainer = null; } else if (!TryNormalizeTags(text, out normalizedTags, out invalidTag)) { Player localPlayer = Player.m_localPlayer; if (localPlayer != null) { ((Character)localPlayer).Message((MessageType)2, "Invalid Auto Storage tag: " + invalidTag, 0, (Sprite)null); } Plugin.Log.LogDebug((object)("Invalid Auto Storage tag '" + invalidTag + "' rejected.")); _editingContainer = null; } else { Piece componentInParent = ((Component)_editingContainer).GetComponentInParent(); nview.GetZDO().Set("HexAutoStorage_Tags", normalizedTags); Plugin.Log.LogDebug((object)("Saved Auto Storage tags '" + normalizedTags + "' to " + (((componentInParent != null) ? ((Object)((Component)componentInParent).gameObject).name : null) ?? "container") + ".")); _editingContainer = null; } } internal static void CancelEditing() { _editingContainer = null; } internal static string GetDisplayTags(Container container) { string tags = GetTags(container); if (string.IsNullOrWhiteSpace(tags)) { return ""; } List list = new List(); string[] array = tags.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (DisplayTags.TryGetValue(text, out var value)) { list.Add(value); } else { list.Add(text); } } return string.Join(",", list); } private static bool TryGetContainerZNetView(Container container, out ZNetView nview) { nview = null; if ((Object)(object)container == (Object)null) { return false; } Piece componentInParent = ((Component)container).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null) { return false; } nview = ((Component)componentInParent).GetComponent(); if ((Object)(object)nview != (Object)null) { return nview.IsValid(); } return false; } } internal class StorageRadiusVisualizer : MonoBehaviour { private const int SegmentCount = 96; private const float LineWidth = 0.4f; private const float HeightOffset = 0.05f; private const float RaycastHeight = 20f; private const float RaycastDistance = 50f; private LineRenderer _lineRenderer; private Smelter _smelter; private int _terrainMask; internal static Smelter HoveredSmelter; private void Awake() { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown _smelter = ((Component)this).GetComponent(); if ((Object)(object)_smelter == (Object)null) { Plugin.Log.LogDebug((object)("StorageRadiusVisualizer attached to non-Smelter object: " + ((Object)((Component)this).gameObject).name)); Object.Destroy((Object)(object)this); return; } _terrainMask = LayerMask.GetMask(new string[1] { "terrain" }); _lineRenderer = ((Component)this).gameObject.AddComponent(); _lineRenderer.loop = true; _lineRenderer.useWorldSpace = true; _lineRenderer.positionCount = 96; _lineRenderer.startWidth = 0.4f; _lineRenderer.endWidth = 0.4f; ((Renderer)_lineRenderer).material = new Material(Shader.Find("Sprites/Default")); ((Renderer)_lineRenderer).enabled = false; } private void Update() { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: 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_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_lineRenderer == (Object)null) { return; } if ((Object)(object)Plugin.Instance == (Object)null || !StorageConfig.ModEnabled.Value || !StorageConfig.ShowStorageRadius.Value || (Object)(object)HoveredSmelter != (Object)(object)_smelter) { ((Renderer)_lineRenderer).enabled = false; return; } ((Renderer)_lineRenderer).enabled = true; float value = StorageConfig.StorageRadius.Value; RaycastHit val2 = default(RaycastHit); for (int i = 0; i < 96; i++) { float num = (float)i * (float)Math.PI * 2f / 96f; Vector3 val = ((Component)this).transform.position + new Vector3(Mathf.Cos(num) * value, 0f, Mathf.Sin(num) * value); if (Physics.Raycast(val + Vector3.up * 20f, Vector3.down, ref val2, 50f, _terrainMask)) { val.y = ((RaycastHit)(ref val2)).point.y + 0.05f; } else { val.y = ((Component)this).transform.position.y + 0.05f; } _lineRenderer.SetPosition(i, val); } } private void OnDestroy() { if (!((Object)(object)_lineRenderer == (Object)null)) { if ((Object)(object)((Renderer)_lineRenderer).material != (Object)null) { Object.Destroy((Object)(object)((Renderer)_lineRenderer).material); } Object.Destroy((Object)(object)_lineRenderer); _lineRenderer = null; } } } } namespace HexAutoStorage.Core { internal enum StorageType { Chest, Barrel, Cart } internal static class StoragePrefabRules { private static readonly HashSet SupportedSmelterPrefabs = new HashSet(StringComparer.OrdinalIgnoreCase) { "smelter", "charcoal_kiln", "blastfurnace", "windmill", "eitrrefinery", "piece_spinningwheel" }; private static readonly Dictionary SupportedPrefabs = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "piece_chest_wood", StorageType.Chest }, { "piece_chest_barrel", StorageType.Barrel }, { "piece_chest", StorageType.Chest }, { "piece_chest_blackmetal", StorageType.Chest }, { "piece_chest_private", StorageType.Chest }, { "Cart", StorageType.Cart } }; internal static bool TryGetStorageType(string prefabName, out StorageType storageType) { return SupportedPrefabs.TryGetValue(prefabName, out storageType); } internal static bool IsSupportedSmelter(string prefabName) { return SupportedSmelterPrefabs.Contains(prefabName); } } } namespace HexAutoStorage.Configuration { internal static class StorageConfig { internal static ConfigEntry ModEnabled; internal static ConfigEntry StorageRadius; internal static ConfigEntry EditTagsShortcut; internal static ConfigEntry ShowStorageRadius; internal static void Bind(ConfigFile config) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_0089: Unknown result type (might be due to invalid IL or missing references) ModEnabled = config.Bind("General", "Enabled", true, "Enable or disable HexAutoStorage."); StorageRadius = config.Bind("General", "StorageRadius", 5f, new ConfigDescription("Radius in meters that production stations search for nearby containers.", (AcceptableValueBase)(object)new AcceptableValueRange(5f, 100f), Array.Empty())); ShowStorageRadius = config.Bind("General", "ShowRadiusVisual", false, "Show storage radius outline."); EditTagsShortcut = config.Bind("Input", "EditTagsShortcut", new KeyboardShortcut((KeyCode)112, (KeyCode[])(object)new KeyCode[1] { (KeyCode)304 }), "Keyboard shortcut used to edit Auto Storage tags while looking at a container. Supported tags: Copper, Tin, Bronze, Iron, Silver, Blackmetal, Flametal, Coal, Flour, Eitr, Linen"); } } }