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<float> s_hoursRequired;
private static ConfigEntry<float> s_checkInterval;
private static readonly Dictionary<string, RestEntry> s_resting = new Dictionary<string, RestEntry>();
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<ItemContainer> s_chestCache = new List<ItemContainer>();
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<float>("General", "HoursRequired", 24f, "In-game hours an item must rest inside a legacy chest before it transmutes.");
s_checkInterval = ((BaseUnityPlugin)this).Config.Bind<float>("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<Item> list = new List<Item>();
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<string, ItemContainer> dictionary = new Dictionary<string, ItemContainer>();
foreach (ItemContainer item2 in EnumerateLegacyChests())
{
if (!dictionary.ContainsKey(((Item)item2).UID))
{
dictionary.Add(((Item)item2).UID, item2);
}
}
foreach (KeyValuePair<string, ItemContainer> item3 in dictionary)
{
ItemContainer value = item3.Value;
List<Item> list = new List<Item>();
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<string> list2 = new List<string>();
foreach (KeyValuePair<string, RestEntry> 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<ItemContainer> 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<string, RestEntry> 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<string, RestEntry> 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;
}
}
}