using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using UnityEngine; using UnityEngine.SceneManagement; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace AncestralRest; [BepInPlugin("com.spencer4792.ancestralrest", "AncestralRest", "1.0.0")] public class AncestralRestPlugin : BaseUnityPlugin { private class RestEntry { public double EnterTime; public string ChestUID; public float MissingSince; } public const string GUID = "com.spencer4792.ancestralrest"; public const string NAME = "AncestralRest"; public const string VERSION = "1.0.0"; internal static ManualLogSource Log; private static ConfigEntry s_hoursRequired; private static ConfigEntry s_checkInterval; private static readonly Dictionary s_resting = new Dictionary(); private float m_nextCheck; private static readonly FieldInfo s_itemPrefabsField = typeof(ResourcesPrefabManager).GetField("ITEM_PREFABS", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private static readonly List s_chestCache = new List(); private static string s_cacheScene = ""; private static float s_cacheExpiry; private static string LedgerPath => Path.Combine(Paths.ConfigPath, "AncestralRest-ledger.txt"); private void Awake() { Log = ((BaseUnityPlugin)this).Logger; s_hoursRequired = ((BaseUnityPlugin)this).Config.Bind("General", "HoursRequired", 24f, "In-game hours an item must rest inside a legacy chest before it transmutes."); s_checkInterval = ((BaseUnityPlugin)this).Config.Bind("General", "CheckIntervalSeconds", 2f, "How often (real seconds) chest contents are checked."); LoadLedger(); Log.LogMessage((object)("AncestralRest 1.0.0 ready. Rest duration: " + s_hoursRequired.Value + " game-hours.")); } private void Update() { if (KeyDown((KeyCode)292)) { if (InputHelper.GetKey((KeyCode)306) || InputHelper.GetKey((KeyCode)305)) { EvacuateChests(); } else { DumpLegacyState(); } } else if (!(Time.unscaledTime < m_nextCheck)) { m_nextCheck = Time.unscaledTime + Mathf.Max(0.5f, s_checkInterval.Value); try { Sweep(); } catch (Exception ex) { Log.LogError((object)("Sweep failed: " + ex)); } } } private void EvacuateChests() { try { Character val = ((!((Object)(object)CharacterManager.Instance != (Object)null)) ? null : CharacterManager.Instance.GetFirstLocalCharacter()); if ((Object)(object)val == (Object)null) { return; } int num = 0; foreach (ItemContainer item in EnumerateLegacyChests()) { List list = new List(); foreach (Item containedItem in item.GetContainedItems()) { if ((Object)(object)containedItem != (Object)null) { list.Add(containedItem); } } foreach (Item item2 in list) { s_resting.Remove(item2.UID); val.Inventory.TakeItem(item2.UID); Log.LogMessage((object)("Evacuated '" + item2.Name + "' from '" + ((Item)item).Name + "' to your bag.")); num++; } } SaveLedger(); Log.LogMessage((object)("Evacuation done: " + num + " item(s).")); } catch (Exception ex) { Log.LogError((object)("EvacuateChests: " + ex)); } } private static bool KeyDown(KeyCode k) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return InputHelper.GetKeyDown(k); } private static bool IsEffectivelyInChest(Item it, ItemContainer chest) { //IL_0044: 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 ((Object)(object)it == (Object)null || (Object)(object)chest == (Object)null) { return false; } if (object.ReferenceEquals(it.ParentContainer, chest)) { return true; } if ((Object)(object)((EffectSynchronizer)it).OwnerCharacter == (Object)null && Vector3.Distance(((Component)it).transform.position, ((Component)chest).transform.position) < 3f) { return true; } return false; } private void Sweep() { if ((Object)(object)ItemManager.Instance == (Object)null) { return; } Character val = ((!((Object)(object)CharacterManager.Instance != (Object)null)) ? null : CharacterManager.Instance.GetFirstLocalCharacter()); if ((Object)(object)val == (Object)null) { return; } double gameTime = EnvironmentConditions.GameTime; bool flag = false; Dictionary dictionary = new Dictionary(); foreach (ItemContainer item2 in EnumerateLegacyChests()) { if (!dictionary.ContainsKey(((Item)item2).UID)) { dictionary.Add(((Item)item2).UID, item2); } } foreach (KeyValuePair item3 in dictionary) { ItemContainer value = item3.Value; List list = new List(); foreach (Item containedItem in value.GetContainedItems()) { if ((Object)(object)containedItem != (Object)null) { list.Add(containedItem); } } foreach (Item item4 in list) { if (item4.LegacyItemID > 0 && !s_resting.ContainsKey(item4.UID)) { s_resting[item4.UID] = new RestEntry { EnterTime = gameTime, ChestUID = ((Item)value).UID }; flag = true; Log.LogMessage((object)("'" + item4.Name + "' begins its ancestral rest (" + s_hoursRequired.Value + " game-hours until transmutation).")); } } } List list2 = new List(); foreach (KeyValuePair item5 in s_resting) { dictionary.TryGetValue(item5.Value.ChestUID, out var value2); if ((Object)(object)value2 == (Object)null) { continue; } Item item = ItemManager.Instance.GetItem(item5.Key); if (!((Object)(object)item != (Object)null) || !IsEffectivelyInChest(item, value2)) { if (item5.Value.MissingSince == 0f) { item5.Value.MissingSince = Time.unscaledTime; } else if (Time.unscaledTime - item5.Value.MissingSince > 60f) { list2.Add(item5.Key); } } else { item5.Value.MissingSince = 0f; if (gameTime - item5.Value.EnterTime >= (double)s_hoursRequired.Value && Transmute(value2, item)) { list2.Add(item5.Key); flag = true; } } } foreach (string item6 in list2) { s_resting.Remove(item6); flag = true; } if (flag) { SaveLedger(); } } private IEnumerable EnumerateLegacyChests() { int fromManager = 0; IEnumerable raw = ((!((Object)(object)ItemManager.Instance != (Object)null)) ? null : ItemManager.Instance.WorldLegacyChests); if (raw != null) { IEnumerator enumerator = raw.GetEnumerator(); try { while (enumerator.MoveNext()) { object o = enumerator.Current; ItemContainer c = (ItemContainer)((o is ItemContainer) ? o : null); if ((Object)(object)c == (Object)null) { Type type = o?.GetType(); if (type != null && type.IsGenericType) { PropertyInfo property = type.GetProperty("Value"); if (property != null) { object? value = property.GetValue(o, null); c = (ItemContainer)((value is ItemContainer) ? value : null); } } } if ((Object)(object)c != (Object)null && ((Component)c).gameObject.activeInHierarchy) { fromManager++; yield return c; } } } finally { IDisposable disposable2; IDisposable disposable = (disposable2 = enumerator as IDisposable); if (disposable2 != null) { disposable.Dispose(); } } } if (fromManager > 0) { yield break; } Scene activeScene = SceneManager.GetActiveScene(); string scene = ((Scene)(ref activeScene)).name; if (scene != s_cacheScene || Time.unscaledTime > s_cacheExpiry || s_chestCache.Contains(null)) { s_chestCache.Clear(); if (Object.FindObjectsOfType(typeof(ItemContainer)) is ItemContainer[] array) { ItemContainer[] array2 = array; foreach (ItemContainer val in array2) { if ((Object)(object)val != (Object)null && ((Item)val).IsLegacyChest) { s_chestCache.Add(val); } } } s_cacheScene = scene; s_cacheExpiry = Time.unscaledTime + 20f; } foreach (ItemContainer c2 in s_chestCache) { if ((Object)(object)c2 != (Object)null && ((Component)c2).gameObject.activeInHierarchy) { yield return c2; } } } private bool Transmute(ItemContainer container, Item oldItem) { int legacyItemID = oldItem.LegacyItemID; Item val = ItemManager.Instance.GenerateItemNetwork(legacyItemID); if ((Object)(object)val == (Object)null) { Log.LogWarning((object)("Could not generate legacy item " + legacyItemID + " for '" + oldItem.Name + "' — leaving it untouched.")); return false; } Equipment val2 = (Equipment)(object)((oldItem is Equipment) ? oldItem : null); Equipment val3 = (Equipment)(object)((val is Equipment) ? val : null); if ((Object)(object)val2 != (Object)null && (Object)(object)val3 != (Object)null && ((Item)val2).IsEnchanted) { try { foreach (int activeEnchantmentID in val2.ActiveEnchantmentIDs) { val3.AddEnchantment(activeEnchantmentID, false); } } catch (Exception ex) { Log.LogWarning((object)("Enchant transfer failed for '" + oldItem.Name + "': " + ex.Message)); } } string name = oldItem.Name; ItemManager.Instance.DestroyItem(oldItem.UID); container.TryMoveItemToContainer(val); Log.LogMessage((object)("'" + name + "' has transcended: it is now '" + val.Name + "'.")); return true; } private void DumpLegacyState() { try { double num = ((!((Object)(object)EnvironmentConditions.Instance != (Object)null)) ? (-1.0) : EnvironmentConditions.GameTime); Log.LogMessage((object)("=== AncestralRest dump (v1.0.0) GameTime=" + num.ToString("F2") + " ===")); IDictionary dictionary = ((!(s_itemPrefabsField != null)) ? null : (s_itemPrefabsField.GetValue(null) as IDictionary)); if (dictionary != null) { int num2 = 0; foreach (DictionaryEntry item in dictionary) { object? value = item.Value; Item val = (Item)((value is Item) ? value : null); if (!((Object)(object)val == (Object)null) && val.LegacyItemID > 0) { num2++; Item val2 = ((ResourcesPrefabManager.Instance == null) ? null : ResourcesPrefabManager.Instance.GetItemPrefab(val.LegacyItemID)); Log.LogMessage((object)("PAIR " + val.ItemID + " '" + val.Name + "' -> " + val.LegacyItemID + " '" + ((!((Object)(object)val2 != (Object)null)) ? "?" : val2.Name) + "'")); } } Log.LogMessage((object)(num2 + " legacy pair(s) in loaded prefabs.")); } else { Log.LogMessage((object)"ITEM_PREFABS not accessible."); } IEnumerable enumerable = ((!((Object)(object)ItemManager.Instance != (Object)null)) ? null : ItemManager.Instance.WorldLegacyChests); int num3 = 0; string text = ""; if (enumerable != null) { foreach (object item2 in enumerable) { num3++; if (num3 <= 3 && item2 != null) { text = text + item2.GetType().Name + " "; } } } Log.LogMessage((object)("WorldLegacyChests raw: " + ((enumerable != null) ? enumerable.GetType().Name : "null") + " count=" + num3 + ((text.Length <= 0) ? "" : (" elemTypes=" + text)))); int num4 = 0; foreach (ItemContainer item3 in EnumerateLegacyChests()) { num4++; Log.LogMessage((object)("CHEST '" + ((Item)item3).Name + "' UID=" + ((Item)item3).UID + " items=" + item3.ItemCount + " IsLegacyChest=" + ((Item)item3).IsLegacyChest)); foreach (Item containedItem in item3.GetContainedItems()) { if ((Object)(object)containedItem != (Object)null) { Log.LogMessage((object)(" contains '" + containedItem.Name + "' LegacyItemID=" + containedItem.LegacyItemID + " UID=" + containedItem.UID)); } } } Log.LogMessage((object)(num4 + " legacy chest(s) usable in loaded scene.")); foreach (KeyValuePair item4 in s_resting) { double num5 = num - item4.Value.EnterTime; string text2 = "unresolved"; Item val3 = ((!((Object)(object)ItemManager.Instance != (Object)null)) ? null : ItemManager.Instance.GetItem(item4.Key)); text2 = ((!((Object)(object)val3 == (Object)null)) ? ("'" + val3.Name + "' owner=" + ((!((Object)(object)((EffectSynchronizer)val3).OwnerCharacter != (Object)null)) ? "none" : ((EffectSynchronizer)val3).OwnerCharacter.Name) + " parentContainer=" + ((!((Object)(object)val3.ParentContainer != (Object)null)) ? "none" : ((Item)val3.ParentContainer).UID.ToString())) : "ITEM NOT FOUND by UID"); Log.LogMessage((object)("RESTING item=" + item4.Key + " chest=" + item4.Value.ChestUID + " elapsed=" + num5.ToString("F2") + "h / " + s_hoursRequired.Value + "h [" + text2 + "]")); } Log.LogMessage((object)"=== end dump ==="); } catch (Exception ex) { Log.LogError((object)("DumpLegacyState: " + ex)); } } private static void LoadLedger() { try { if (!File.Exists(LedgerPath)) { return; } string[] array = File.ReadAllLines(LedgerPath); foreach (string text in array) { string[] array2 = text.Split(new char[1] { '\t' }); if (array2.Length >= 3 && double.TryParse(array2[1], out var result)) { s_resting[array2[0]] = new RestEntry { EnterTime = result, ChestUID = array2[2] }; } } Log.LogInfo((object)("Ledger loaded: " + s_resting.Count + " resting item(s).")); } catch (Exception ex) { Log.LogWarning((object)("Ledger load failed: " + ex.Message)); } } private static void SaveLedger() { try { StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair item in s_resting) { stringBuilder.Append(item.Key).Append('\t').Append(item.Value.EnterTime) .Append('\t') .Append(item.Value.ChestUID) .Append('\n'); } File.WriteAllText(LedgerPath, stringBuilder.ToString()); } catch (Exception ex) { Log.LogWarning((object)("Ledger save failed: " + ex.Message)); } } } internal static class InputHelper { private static readonly Type s_inputType = Type.GetType("UnityEngine.Input, UnityEngine.InputLegacyModule"); private static readonly MethodInfo s_getKeyDown = ((!(s_inputType != null)) ? null : s_inputType.GetMethod("GetKeyDown", new Type[1] { typeof(KeyCode) })); private static readonly MethodInfo s_getKey = ((!(s_inputType != null)) ? null : s_inputType.GetMethod("GetKey", new Type[1] { typeof(KeyCode) })); public static bool GetKeyDown(KeyCode k) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (s_getKeyDown == null) { return false; } try { return (bool)s_getKeyDown.Invoke(null, new object[1] { k }); } catch { return false; } } public static bool GetKey(KeyCode k) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (s_getKey == null) { return false; } try { return (bool)s_getKey.Invoke(null, new object[1] { k }); } catch { return false; } } }