Decompiled source of LocalDeposit v1.1.7

LocalDeposit.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
[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 LocalDeposit
{
	internal sealed class LedgerEntry
	{
		public int Id;

		public int ItemIndex;

		public string Name;

		public int Value;

		public Vector3 Position;

		public bool IsScrap;

		public bool SavesVariable;

		public int SaveData;
	}
	internal interface IShipWorld
	{
		object Respawn(LedgerEntry entry);

		bool IsAlive(object handle);

		void Despawn(object handle);
	}
	internal sealed class GuardOutcome
	{
		public int Restored;

		public int RestoreFailed;

		public int Redeposited;

		public int Stranded;

		public int Vanished;

		public bool Skipped;

		public readonly List<string> Warnings = new List<string>();
	}
	internal sealed class AutosaveGuard
	{
		private sealed class Temp
		{
			public LedgerEntry Entry;

			public object Handle;
		}

		private readonly IList<LedgerEntry> entries;

		private readonly IShipWorld world;

		private readonly List<Temp> temps = new List<Temp>();

		public bool Active { get; private set; }

		public AutosaveGuard(IList<LedgerEntry> entries, IShipWorld world)
		{
			this.entries = entries;
			this.world = world;
		}

		public GuardOutcome Before()
		{
			GuardOutcome guardOutcome = new GuardOutcome();
			if (Active)
			{
				guardOutcome.Skipped = true;
				return guardOutcome;
			}
			Active = true;
			foreach (LedgerEntry item in new List<LedgerEntry>(entries))
			{
				try
				{
					object handle = world.Respawn(item);
					temps.Add(new Temp
					{
						Entry = item,
						Handle = handle
					});
					guardOutcome.Restored++;
				}
				catch (Exception ex)
				{
					guardOutcome.RestoreFailed++;
					guardOutcome.Warnings.Add("could not restore " + item.Name + " for the save (" + ex.Message + "); it is NOT in this save");
				}
			}
			return guardOutcome;
		}

		public GuardOutcome After()
		{
			GuardOutcome guardOutcome = new GuardOutcome();
			if (!Active)
			{
				guardOutcome.Skipped = true;
				return guardOutcome;
			}
			foreach (Temp temp in temps)
			{
				if (!world.IsAlive(temp.Handle))
				{
					guardOutcome.Vanished++;
					continue;
				}
				try
				{
					world.Despawn(temp.Handle);
					guardOutcome.Redeposited++;
				}
				catch (Exception ex)
				{
					if (world.IsAlive(temp.Handle))
					{
						entries.Remove(temp.Entry);
						guardOutcome.Stranded++;
						guardOutcome.Warnings.Add(temp.Entry.Name + " ($" + temp.Entry.Value + ") could not be re-deposited (" + ex.Message + ") and was left on the ship");
					}
					else
					{
						guardOutcome.Vanished++;
					}
				}
			}
			temps.Clear();
			Active = false;
			return guardOutcome;
		}
	}
	internal static class Commands
	{
		private const string Usage = "USAGE\n  /deposit               deposit eligible ship scrap (blacklist applied by default; Gift Boxes never touched)\n  /deposit -b            same, but skip every item on the configured blacklist\n  /deposit --blacklist   alias of -b\n  /deposit --list        show what is currently deposited\n  /undeposit             restore everything you deposited\n";

		public static bool IsOurs(string lowerInput)
		{
			if (!MatchesWord(lowerInput, "/deposit"))
			{
				return MatchesWord(lowerInput, "/undeposit");
			}
			return true;
		}

		private static bool MatchesWord(string s, string word)
		{
			if (!s.StartsWith(word, StringComparison.Ordinal))
			{
				return false;
			}
			if (s.Length != word.Length)
			{
				return char.IsWhiteSpace(s[word.Length]);
			}
			return true;
		}

		public static string Run(string lowerInput)
		{
			try
			{
				if (HqolCheck.IsLoaded)
				{
					return "LocalDeposit is disabled because HQoL is installed.\nHQoL is more important and takes precedence, so /deposit and /undeposit do nothing while it is active.\nUse HQoL's own deposit command, or remove HQoL if you want to use LocalDeposit.\n";
				}
				string[] array = lowerInput.Split(new char[4] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
				if (array.Length == 0)
				{
					return "USAGE\n  /deposit               deposit eligible ship scrap (blacklist applied by default; Gift Boxes never touched)\n  /deposit -b            same, but skip every item on the configured blacklist\n  /deposit --blacklist   alias of -b\n  /deposit --list        show what is currently deposited\n  /undeposit             restore everything you deposited\n";
				}
				if (!HostCheck.IsHost)
				{
					return "ERROR: Only the lobby host can deposit scrap.\nClients cannot remove items from the shared ship, so this command is disabled for you.\n";
				}
				if (Ledger.Busy)
				{
					return "ERROR: A deposit/restore is already in progress. Try again in a moment.\n";
				}
				if (array[0] == "/undeposit")
				{
					if (array.Length > 1)
					{
						return "ERROR: /undeposit takes no options.\n\nUSAGE\n  /deposit               deposit eligible ship scrap (blacklist applied by default; Gift Boxes never touched)\n  /deposit -b            same, but skip every item on the configured blacklist\n  /deposit --blacklist   alias of -b\n  /deposit --list        show what is currently deposited\n  /undeposit             restore everything you deposited\n";
					}
					return Undeposit();
				}
				bool blacklist = Plugin.Cfg.PlainDepositUsesBlacklist.Value;
				bool flag = false;
				for (int i = 1; i < array.Length; i++)
				{
					switch (array[i])
					{
					case "-b":
					case "--blacklist":
						blacklist = true;
						break;
					case "-l":
					case "--list":
						flag = true;
						break;
					case "-h":
					case "--help":
					case "help":
						return "USAGE\n  /deposit               deposit eligible ship scrap (blacklist applied by default; Gift Boxes never touched)\n  /deposit -b            same, but skip every item on the configured blacklist\n  /deposit --blacklist   alias of -b\n  /deposit --list        show what is currently deposited\n  /undeposit             restore everything you deposited\n";
					default:
						return "ERROR: Unknown option '" + array[i] + "'.\n\nUSAGE\n  /deposit               deposit eligible ship scrap (blacklist applied by default; Gift Boxes never touched)\n  /deposit -b            same, but skip every item on the configured blacklist\n  /deposit --blacklist   alias of -b\n  /deposit --list        show what is currently deposited\n  /undeposit             restore everything you deposited\n";
					}
				}
				if (flag)
				{
					return List();
				}
				return Deposit(blacklist);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Command failed: " + ex));
				return "ERROR: Something went wrong; nothing was changed unexpectedly. See BepInEx log.\n";
			}
		}

		private static string Deposit(bool blacklist)
		{
			DepositReport depositReport = Ledger.Deposit(blacklist);
			if (depositReport.Refused)
			{
				return "ERROR: Cannot deposit: " + depositReport.RefusedReason + ".\n";
			}
			StringBuilder stringBuilder = new StringBuilder();
			if (depositReport.Count == 0)
			{
				stringBuilder.Append("Nothing to deposit: no eligible scrap found on the ship.\n");
			}
			else
			{
				stringBuilder.Append("DEPOSIT COMPLETE\n");
				stringBuilder.Append("Deposited ").Append(depositReport.Count).Append((depositReport.Count == 1) ? " item" : " items")
					.Append(" worth $")
					.Append(depositReport.Value)
					.Append(blacklist ? " (blacklist applied)" : "")
					.Append(".\n");
			}
			if (depositReport.Skipped.Count > 0)
			{
				stringBuilder.Append("\nSkipped:\n");
				foreach (KeyValuePair<string, int> item in depositReport.Skipped)
				{
					stringBuilder.Append("  ").Append(item.Value).Append("x ")
						.Append(item.Key)
						.Append('\n');
				}
			}
			if (depositReport.IgnoredOutsideShip > 0)
			{
				stringBuilder.Append("\n").Append(depositReport.IgnoredOutsideShip).Append(" scrap item(s) outside the ship were ignored.\n");
			}
			if (depositReport.Failed > 0)
			{
				stringBuilder.Append("\nWARNING: ").Append(depositReport.Failed).Append(" item(s) could not be removed and were left on the ship.\n");
			}
			stringBuilder.Append("\nTotal deposited: ").Append(Ledger.Count).Append(" items, $")
				.Append(Ledger.TotalValue)
				.Append(".\n");
			if (Ledger.Count > 0)
			{
				stringBuilder.Append("Deposited scrap cannot be sold until you /undeposit.\n");
			}
			return stringBuilder.ToString();
		}

		private static string Undeposit()
		{
			if (Ledger.Count == 0)
			{
				return "Nothing is deposited.\n";
			}
			RestoreReport restoreReport = Ledger.RestoreAll("/undeposit");
			StringBuilder stringBuilder = new StringBuilder();
			if (restoreReport.Problem != null && restoreReport.Restored == 0 && restoreReport.Failed == 0)
			{
				return "ERROR: Could not restore: " + restoreReport.Problem + ".\n";
			}
			stringBuilder.Append("UNDEPOSIT COMPLETE\n");
			stringBuilder.Append("Restored ").Append(restoreReport.Restored).Append((restoreReport.Restored == 1) ? " item" : " items")
				.Append(" worth $")
				.Append(restoreReport.RestoredValue)
				.Append(".\n");
			if (restoreReport.Failed > 0)
			{
				stringBuilder.Append("\nWARNING: ").Append(restoreReport.Failed).Append(" item(s) worth $")
					.Append(restoreReport.FailedValue)
					.Append(" could NOT be restored and are still stored. Run /undeposit again.\n");
			}
			stringBuilder.Append("\nStill deposited: ").Append(Ledger.Count).Append(" items, $")
				.Append(Ledger.TotalValue)
				.Append(".\n");
			return stringBuilder.ToString();
		}

		private static string List()
		{
			if (Ledger.Count == 0)
			{
				return "Nothing is deposited.\n";
			}
			SortedDictionary<string, int> sortedDictionary = new SortedDictionary<string, int>();
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (LedgerEntry entry in Ledger.Entries)
			{
				sortedDictionary.TryGetValue(entry.Name, out var value);
				sortedDictionary[entry.Name] = value + 1;
				dictionary.TryGetValue(entry.Name, out var value2);
				dictionary[entry.Name] = value2 + entry.Value;
			}
			StringBuilder stringBuilder = new StringBuilder("DEPOSITED SCRAP\n");
			foreach (KeyValuePair<string, int> item in sortedDictionary)
			{
				stringBuilder.Append("  ").Append(item.Value).Append("x ")
					.Append(item.Key)
					.Append("  $")
					.Append(dictionary[item.Key])
					.Append('\n');
			}
			stringBuilder.Append("\nTotal: ").Append(Ledger.Count).Append(" items, $")
				.Append(Ledger.TotalValue)
				.Append(".\n");
			return stringBuilder.ToString();
		}
	}
	internal static class HostCheck
	{
		public static bool IsHost
		{
			get
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton != (Object)null)
				{
					return singleton.IsHost;
				}
				return false;
			}
		}
	}
	internal static class HqolCheck
	{
		public const string Message = "LocalDeposit is disabled because HQoL is installed.\nHQoL is more important and takes precedence, so /deposit and /undeposit do nothing while it is active.\nUse HQoL's own deposit command, or remove HQoL if you want to use LocalDeposit.\n";

		public static bool IsLoaded
		{
			get
			{
				try
				{
					foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
					{
						string obj = pluginInfo.Key ?? "";
						string text = ((pluginInfo.Value != null && pluginInfo.Value.Metadata != null) ? (pluginInfo.Value.Metadata.Name ?? "") : "");
						if (obj.IndexOf("hqol", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("hqol", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							return true;
						}
					}
				}
				catch
				{
				}
				return false;
			}
		}
	}
	internal sealed class DepositReport
	{
		public int Count;

		public int Value;

		public int Failed;

		public int IgnoredOutsideShip;

		public readonly SortedDictionary<string, int> Skipped = new SortedDictionary<string, int>();

		public bool Refused;

		public string RefusedReason;
	}
	internal sealed class RestoreReport
	{
		public int Restored;

		public int RestoredValue;

		public int Failed;

		public int FailedValue;

		public string Problem;
	}
	internal static class Ledger
	{
		private enum Verdict
		{
			Eligible,
			SilentSkip,
			ReportedSkip,
			OutsideShip
		}

		private sealed class UnityShipWorld : IShipWorld
		{
			public object Respawn(LedgerEntry entry)
			{
				StartOfRound instance = StartOfRound.Instance;
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)instance == (Object)null || (Object)(object)instance.allItemsList == (Object)null || (Object)(object)instance.elevatorTransform == (Object)null || (Object)(object)singleton == (Object)null || !singleton.IsServer)
				{
					throw new InvalidOperationException("ship/network not available");
				}
				return SpawnEntry(entry, instance);
			}

			public bool IsAlive(object handle)
			{
				GrabbableObject val = (GrabbableObject)((handle is GrabbableObject) ? handle : null);
				if ((Object)(object)val != (Object)null && (Object)(object)((NetworkBehaviour)val).NetworkObject != (Object)null)
				{
					return ((NetworkBehaviour)val).NetworkObject.IsSpawned;
				}
				return false;
			}

			public void Despawn(object handle)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_000b: Expected O, but got Unknown
				RemoveFromWorld((GrabbableObject)handle);
			}
		}

		private static readonly List<LedgerEntry> entries = new List<LedgerEntry>();

		private static int nextId = 1;

		private static int totalValue;

		private static AutosaveGuard guard;

		public static bool Busy { get; private set; }

		public static int UnsavedCount { get; set; }

		public static int Count => entries.Count;

		public static int TotalValue => totalValue;

		public static IList<LedgerEntry> Entries => entries;

		private static AutosaveGuard Guard
		{
			get
			{
				if (guard == null)
				{
					guard = new AutosaveGuard(entries, new UnityShipWorld());
				}
				return guard;
			}
		}

		private static void Recount()
		{
			int num = 0;
			for (int i = 0; i < entries.Count; i++)
			{
				num += entries[i].Value;
			}
			totalValue = num;
		}

		private static Verdict Classify(GrabbableObject g, bool useBlacklist, StartOfRound sor, out string reason)
		{
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			reason = null;
			if ((Object)(object)g == (Object)null || (Object)(object)g.itemProperties == (Object)null)
			{
				return Verdict.SilentSkip;
			}
			Item itemProperties = g.itemProperties;
			if (!itemProperties.isScrap)
			{
				return Verdict.SilentSkip;
			}
			NetworkObject networkObject = ((NetworkBehaviour)g).NetworkObject;
			if ((Object)(object)networkObject == (Object)null || !networkObject.IsSpawned)
			{
				return Verdict.SilentSkip;
			}
			if (g.isHeld || g.isPocketed || g.isHeldByEnemy || g.heldByPlayerOnServer)
			{
				reason = "being held";
				return Verdict.ReportedSkip;
			}
			if (g.deactivated || g.itemUsedUp)
			{
				return Verdict.SilentSkip;
			}
			if (g.scrapValue <= 0)
			{
				reason = "no scrap value";
				return Verdict.ReportedSkip;
			}
			if (!((Object)(object)sor.shipInnerRoomBounds == (Object)null))
			{
				Bounds bounds = sor.shipInnerRoomBounds.bounds;
				if (((Bounds)(ref bounds)).Contains(((Component)g).transform.position))
				{
					if (PluginConfig.Norm(((Object)itemProperties).name).Contains("giftbox"))
					{
						reason = "Gift Box (always kept)";
						return Verdict.ReportedSkip;
					}
					Transform parent = ((Component)g).transform.parent;
					if (Plugin.Cfg.SkipItemsOnShipFurniture.Value && (Object)(object)parent != (Object)null && (Object)(object)((Component)parent).gameObject.GetComponent<AutoParentToShip>() != (Object)null)
					{
						reason = "attached to ship furniture";
						return Verdict.ReportedSkip;
					}
					if ((Object)(object)sor.allItemsList == (Object)null || sor.allItemsList.itemsList.IndexOf(itemProperties) < 0 || (Object)(object)itemProperties.spawnPrefab == (Object)null)
					{
						reason = "cannot be respawned (not in game item list / no prefab)";
						return Verdict.ReportedSkip;
					}
					if (useBlacklist)
					{
						string text = null;
						ScanNodeProperties componentInChildren = ((Component)g).GetComponentInChildren<ScanNodeProperties>();
						if ((Object)(object)componentInChildren != (Object)null)
						{
							text = componentInChildren.headerText;
						}
						if (Plugin.Cfg.IsBlacklisted(((Object)itemProperties).name, itemProperties.itemName, text))
						{
							reason = "blacklisted";
							return Verdict.ReportedSkip;
						}
					}
					return Verdict.Eligible;
				}
			}
			return Verdict.OutsideShip;
		}

		public static DepositReport Deposit(bool useBlacklist)
		{
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_0196: Unknown result type (might be due to invalid IL or missing references)
			DepositReport depositReport = new DepositReport();
			if (HqolCheck.IsLoaded)
			{
				depositReport.Refused = true;
				depositReport.RefusedReason = "HQoL is installed and takes precedence";
				return depositReport;
			}
			if (!HostCheck.IsHost)
			{
				depositReport.Refused = true;
				depositReport.RefusedReason = "only the lobby host can deposit";
				return depositReport;
			}
			if (Busy)
			{
				depositReport.Refused = true;
				depositReport.RefusedReason = "another deposit/restore is still running";
				return depositReport;
			}
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.allItemsList == (Object)null)
			{
				depositReport.Refused = true;
				depositReport.RefusedReason = "the ship is not loaded";
				return depositReport;
			}
			Busy = true;
			try
			{
				GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsSortMode)0);
				foreach (GrabbableObject val in array)
				{
					Verdict verdict;
					string reason;
					try
					{
						verdict = Classify(val, useBlacklist, instance, out reason);
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Classify failed: " + ex.Message));
						continue;
					}
					switch (verdict)
					{
					case Verdict.OutsideShip:
						depositReport.IgnoredOutsideShip++;
						continue;
					case Verdict.ReportedSkip:
					{
						string key = reason + ": " + DisplayName(val);
						depositReport.Skipped.TryGetValue(key, out var value);
						depositReport.Skipped[key] = value + 1;
						continue;
					}
					case Verdict.SilentSkip:
						continue;
					}
					LedgerEntry ledgerEntry;
					try
					{
						ledgerEntry = new LedgerEntry();
						ledgerEntry.Id = nextId++;
						ledgerEntry.Name = DisplayName(val);
						ledgerEntry.ItemIndex = instance.allItemsList.itemsList.IndexOf(val.itemProperties);
						ledgerEntry.Value = val.scrapValue;
						ledgerEntry.Position = ((Component)val).transform.position;
						ledgerEntry.IsScrap = val.itemProperties.isScrap;
						ledgerEntry.SavesVariable = val.itemProperties.saveItemVariable;
						if (ledgerEntry.SavesVariable)
						{
							ledgerEntry.SaveData = val.GetItemDataToSave();
						}
					}
					catch (Exception ex2)
					{
						Plugin.Log.LogWarning((object)("Could not capture " + DisplayName(val) + ": " + ex2.Message));
						string key2 = "state could not be read: " + DisplayName(val);
						depositReport.Skipped.TryGetValue(key2, out var value2);
						depositReport.Skipped[key2] = value2 + 1;
						continue;
					}
					entries.Add(ledgerEntry);
					try
					{
						RemoveFromWorld(val);
						depositReport.Count++;
						depositReport.Value += ledgerEntry.Value;
					}
					catch (Exception ex3)
					{
						entries.Remove(ledgerEntry);
						depositReport.Failed++;
						Plugin.Log.LogError((object)("Despawn failed for " + ledgerEntry.Name + ", entry rolled back: " + ex3));
					}
				}
			}
			finally
			{
				Recount();
				Busy = false;
			}
			Plugin.Log.LogInfo((object)("Deposit: " + depositReport.Count + " items, $" + depositReport.Value + ". Ledger now " + Count + " items, $" + TotalValue + "."));
			return depositReport;
		}

		public static RestoreReport RestoreAll(string reason)
		{
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			RestoreReport restoreReport = new RestoreReport();
			if (entries.Count == 0)
			{
				return restoreReport;
			}
			if (!HostCheck.IsHost)
			{
				restoreReport.Problem = "not the host";
				return restoreReport;
			}
			if (Busy)
			{
				restoreReport.Problem = "busy";
				return restoreReport;
			}
			StartOfRound instance = StartOfRound.Instance;
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.allItemsList == (Object)null || (Object)(object)instance.elevatorTransform == (Object)null || (Object)(object)singleton == (Object)null || !singleton.IsServer)
			{
				restoreReport.Problem = "ship/network not available - " + entries.Count + " items stay in the ledger";
				Plugin.Log.LogWarning((object)("RestoreAll(" + reason + ") skipped: " + restoreReport.Problem));
				return restoreReport;
			}
			Busy = true;
			List<NetworkObjectReference> list = new List<NetworkObjectReference>();
			List<int> list2 = new List<int>();
			try
			{
				foreach (LedgerEntry item in new List<LedgerEntry>(entries))
				{
					try
					{
						GrabbableObject val = SpawnEntry(item, instance);
						Item itemProperties = val.itemProperties;
						entries.Remove(item);
						restoreReport.Restored++;
						restoreReport.RestoredValue += item.Value;
						if (itemProperties.isScrap)
						{
							list.Add(NetworkObjectReference.op_Implicit(((NetworkBehaviour)val).NetworkObject));
							list2.Add(item.Value);
						}
					}
					catch (Exception ex)
					{
						restoreReport.Failed++;
						restoreReport.FailedValue += item.Value;
						Plugin.Log.LogError((object)("Could not restore " + item.Name + " ($" + item.Value + "): " + ex.Message + " - entry kept."));
					}
				}
			}
			finally
			{
				Recount();
				Busy = false;
			}
			if (list.Count > 0 && (Object)(object)Plugin.Instance != (Object)null)
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(SyncValuesToClients(list, list2));
			}
			Plugin.Log.LogInfo((object)("RestoreAll(" + reason + "): restored " + restoreReport.Restored + ", failed " + restoreReport.Failed + "."));
			return restoreReport;
		}

		private static IEnumerator SyncValuesToClients(List<NetworkObjectReference> refs, List<int> values)
		{
			yield return null;
			RoundManager rm = RoundManager.Instance;
			if ((Object)(object)rm == (Object)null)
			{
				yield break;
			}
			for (int i = 0; i < refs.Count; i += 100)
			{
				int count = Math.Min(100, refs.Count - i);
				try
				{
					rm.SyncScrapValuesClientRpc(refs.GetRange(i, count).ToArray(), values.GetRange(i, count).ToArray());
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Value sync failed: " + ex.Message));
					break;
				}
				yield return null;
			}
		}

		internal static GrabbableObject SpawnEntry(LedgerEntry e, StartOfRound sor)
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			if (e.ItemIndex < 0 || e.ItemIndex >= sor.allItemsList.itemsList.Count)
			{
				throw new InvalidOperationException("item index " + e.ItemIndex + " no longer valid");
			}
			Item val = sor.allItemsList.itemsList[e.ItemIndex];
			if ((Object)(object)val == (Object)null || (Object)(object)val.spawnPrefab == (Object)null)
			{
				throw new InvalidOperationException("no spawn prefab");
			}
			GameObject val2 = Object.Instantiate<GameObject>(val.spawnPrefab, e.Position, Quaternion.identity, sor.elevatorTransform);
			try
			{
				GrabbableObject component = val2.GetComponent<GrabbableObject>();
				if ((Object)(object)component == (Object)null)
				{
					throw new InvalidOperationException("prefab has no GrabbableObject");
				}
				component.fallTime = 0f;
				component.scrapPersistedThroughRounds = true;
				if ((Object)(object)component.radarIcon != (Object)null)
				{
					Object.Destroy((Object)(object)((Component)component.radarIcon).gameObject);
				}
				component.isInElevator = true;
				component.isInShipRoom = true;
				if (val.isScrap)
				{
					component.SetScrapValue(e.Value);
				}
				if (e.SavesVariable && val.saveItemVariable)
				{
					component.LoadItemSaveData(e.SaveData);
				}
				((NetworkBehaviour)component).NetworkObject.Spawn(false);
				return component;
			}
			catch
			{
				try
				{
					NetworkObject component2 = val2.GetComponent<NetworkObject>();
					if ((Object)(object)component2 != (Object)null && component2.IsSpawned)
					{
						component2.Despawn(true);
					}
					else
					{
						Object.Destroy((Object)(object)val2);
					}
				}
				catch
				{
				}
				throw;
			}
		}

		internal static void RemoveFromWorld(GrabbableObject g)
		{
			((NetworkBehaviour)g).NetworkObject.Despawn(true);
			try
			{
				((Component)g).gameObject.SetActive(false);
			}
			catch
			{
			}
		}

		public static void BeforeSave()
		{
			if (entries.Count != 0 && !Busy && HostCheck.IsHost)
			{
				Busy = true;
				Stopwatch stopwatch = Stopwatch.StartNew();
				GuardOutcome guardOutcome = Guard.Before();
				if (guardOutcome.Skipped)
				{
					Busy = false;
					return;
				}
				UnsavedCount = guardOutcome.RestoreFailed;
				Plugin.Log.LogInfo((object)("Autosave: restored " + guardOutcome.Restored + " deposited items for the save (" + stopwatch.ElapsedMilliseconds + " ms), " + guardOutcome.RestoreFailed + " failed."));
				Report(guardOutcome);
			}
		}

		public static void AfterSave()
		{
			if (Guard.Active)
			{
				Stopwatch stopwatch = Stopwatch.StartNew();
				GuardOutcome guardOutcome = Guard.After();
				Recount();
				Busy = false;
				Plugin.Log.LogInfo((object)("Autosave: re-deposited " + guardOutcome.Redeposited + " items (" + stopwatch.ElapsedMilliseconds + " ms). Ledger " + Count + " items, $" + TotalValue + "."));
				Report(guardOutcome);
			}
		}

		internal static void Warn(string text)
		{
			Plugin.Log.LogWarning((object)text);
			try
			{
				if ((Object)(object)HUDManager.Instance != (Object)null)
				{
					HUDManager.Instance.DisplayTip("LocalDeposit warning", text, true, false, "LC_LocalDepositWarn");
				}
			}
			catch
			{
			}
		}

		private static void Report(GuardOutcome o)
		{
			if (o.Warnings.Count == 0)
			{
				return;
			}
			foreach (string warning in o.Warnings)
			{
				Plugin.Log.LogWarning((object)("Autosave: " + warning));
			}
			try
			{
				if ((Object)(object)HUDManager.Instance != (Object)null)
				{
					HUDManager.Instance.DisplayTip("LocalDeposit warning", o.Warnings.Count + " deposited item problem(s) during autosave. Check the terminal / log; use /undeposit if unsure.", true, false, "LC_LocalDepositWarn");
				}
			}
			catch
			{
			}
		}

		public static void Clear(string reason)
		{
			if (entries.Count > 0)
			{
				Plugin.Log.LogWarning((object)("Ledger cleared (" + reason + "): dropping " + entries.Count + " items worth $" + totalValue + "."));
			}
			entries.Clear();
			totalValue = 0;
			Busy = false;
		}

		private static string DisplayName(GrabbableObject g)
		{
			string text = (((Object)(object)g.itemProperties != (Object)null) ? g.itemProperties.itemName : null);
			if (string.IsNullOrEmpty(text) && (Object)(object)g.itemProperties != (Object)null)
			{
				text = ((Object)g.itemProperties).name;
			}
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return "Unknown item";
		}
	}
	[HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")]
	internal static class TerminalParsePatch
	{
		[HarmonyPrefix]
		private static bool Prefix(Terminal __instance, ref TerminalNode __result)
		{
			try
			{
				string text = __instance.screenText.text;
				int textAdded = __instance.textAdded;
				if (string.IsNullOrEmpty(text) || textAdded <= 0 || textAdded > text.Length)
				{
					return true;
				}
				string lowerInput = text.Substring(text.Length - textAdded).Trim().ToLowerInvariant();
				if (!Commands.IsOurs(lowerInput))
				{
					return true;
				}
				string text2 = Commands.Run(lowerInput);
				TerminalNode val = ScriptableObject.CreateInstance<TerminalNode>();
				val.displayText = text2 + "\n";
				val.clearPreviousText = true;
				__result = val;
				return false;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Terminal patch error (falling back to vanilla): " + ex));
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "Update")]
	internal static class TerminalDisplayPatch
	{
		[HarmonyPostfix]
		private static void Postfix(Terminal __instance)
		{
			if (__instance.terminalInUse && Ledger.TotalValue > 0 && !((Object)(object)__instance.topRightText == (Object)null))
			{
				((TMP_Text)__instance.topRightText).text = ((TMP_Text)__instance.topRightText).text + "\n<color=#09DBB5>$" + Ledger.TotalValue;
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "SaveGame")]
	internal static class SaveGamePatch
	{
		[HarmonyPrefix]
		private static bool Prefix()
		{
			try
			{
				Ledger.UnsavedCount = 0;
				if (Ledger.Count == 0)
				{
					return true;
				}
				if (Plugin.Cfg.AutosaveBehaviour.Value == AutosaveMode.RestoreOnly)
				{
					RestoreReport restoreReport = Ledger.RestoreAll("before save");
					Plugin.Log.LogWarning((object)("Save: " + restoreReport.Restored + " deposited items restored and left on the ship; " + restoreReport.Failed + " failed."));
					Ledger.UnsavedCount = Ledger.Count;
				}
				else
				{
					Ledger.BeforeSave();
				}
				if (Ledger.UnsavedCount > 0 && Plugin.Cfg.SkipSaveIfItemsCannotBeRestored.Value)
				{
					Ledger.Warn(Ledger.UnsavedCount + " deposited items could not be restored, so this save was SKIPPED to protect your previous save file.");
					return false;
				}
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Save guard failed: " + ex));
				return Ledger.Count <= 0 || !Plugin.Cfg.SkipSaveIfItemsCannotBeRestored.Value;
			}
		}

		[HarmonyFinalizer]
		private static void Finalizer()
		{
			try
			{
				Ledger.AfterSave();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("AfterSave failed: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(GameNetworkManager), "Disconnect")]
	internal static class DisconnectPatch
	{
		[HarmonyPrefix]
		private static void Prefix(GameNetworkManager __instance)
		{
			if (!__instance.isDisconnecting)
			{
				Ledger.RestoreAll("leaving game");
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "ResetShip")]
	internal static class ResetShipPatch
	{
		[HarmonyPrefix]
		private static void Prefix()
		{
			Ledger.Clear("ship reset (fired / new run)");
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "Awake")]
	internal static class SessionStartPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			Ledger.Clear("new session");
			if (Plugin.Cfg.LogItemIdentifiers.Value)
			{
				ItemIdLogger.Log();
			}
		}
	}
	[HarmonyPatch(typeof(StartOfRound), "OnDestroy")]
	internal static class SessionEndPatch
	{
		[HarmonyPostfix]
		private static void Postfix()
		{
			Ledger.Clear("session ended");
		}
	}
	internal static class ItemIdLogger
	{
		public static void Log()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.allItemsList == (Object)null)
			{
				return;
			}
			Plugin.Log.LogInfo((object)"---- LocalDeposit item identifiers (asset name | display name | isScrap) ----");
			foreach (Item items in instance.allItemsList.itemsList)
			{
				if ((Object)(object)items != (Object)null)
				{
					Plugin.Log.LogInfo((object)(((Object)items).name + " | " + items.itemName + " | " + items.isScrap));
				}
			}
		}
	}
	[BepInPlugin("local.deposit.lethalcompany", "LocalDeposit", "1.1.7")]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "local.deposit.lethalcompany";

		public const string Name = "LocalDeposit";

		public const string Version = "1.1.7";

		internal static ManualLogSource Log;

		internal static Plugin Instance;

		internal static PluginConfig Cfg;

		private void Start()
		{
			if (HqolCheck.IsLoaded)
			{
				Log.LogWarning((object)"HQoL detected: LocalDeposit commands are disabled. HQoL is more important and takes precedence.");
			}
		}

		private void Awake()
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Cfg = new PluginConfig(((BaseUnityPlugin)this).Config);
			new Harmony("local.deposit.lethalcompany").PatchAll(typeof(Plugin).Assembly);
			Log.LogInfo((object)"LocalDeposit 1.1.7 loaded. Host-side only; clients need nothing installed.");
		}
	}
	internal enum AutosaveMode
	{
		RestoreSaveRedeposit,
		RestoreOnly
	}
	internal sealed class PluginConfig
	{
		public readonly ConfigEntry<AutosaveMode> AutosaveBehaviour;

		public readonly ConfigEntry<bool> SkipSaveIfItemsCannotBeRestored;

		private readonly ConfigEntry<string> blacklist;

		public readonly ConfigEntry<bool> PlainDepositUsesBlacklist;

		public readonly ConfigEntry<bool> BlacklistPartialMatch;

		public readonly ConfigEntry<bool> SkipItemsOnShipFurniture;

		public readonly ConfigEntry<bool> LogItemIdentifiers;

		private HashSet<string> blacklistSet = new HashSet<string>();

		public PluginConfig(ConfigFile cfg)
		{
			blacklist = cfg.Bind<string>("Blacklist", "Items", "ShotgunItem, KnifeItem, Shotgun, Knife, Kitchen knife, GiftBox, Gift Box", "Comma-separated items that '/deposit -b' (or '/deposit --blacklist') will NOT deposit. Each entry is matched, ignoring case, spaces, '_' and '-', against THREE identifiers of an item: (1) its internal asset name (Item.name, e.g. GiftBox), (2) its display name (Item.itemName), (3) its scan-node header text. Unknown/modded names are simply ignored. To find valid identifiers set [Debug] LogItemIdentifiers = true and read BepInEx/LogOutput.log after loading into a lobby as host: every item the game knows is printed there.");
			PlainDepositUsesBlacklist = cfg.Bind<bool>("Behaviour", "PlainDepositUsesBlacklist", true, "If true, plain '/deposit' also applies the blacklist (so it behaves like '/deposit -b'). Default true. If false, plain '/deposit' deposits every eligible scrap item except Gift Boxes, which are ALWAYS skipped because deleting one destroys its hidden contents.");
			BlacklistPartialMatch = cfg.Bind<bool>("Blacklist", "PartialMatch", true, "true: an entry also matches any item whose name CONTAINS it (so 'knife' matches 'Kitchen knife' and 'KnifeItem', 'shotgun' matches 'Shotgun'). Entries shorter than 3 letters are always exact. false: whole-name match only.");
			SkipItemsOnShipFurniture = cfg.Bind<bool>("Behaviour", "SkipItemsOnShipFurniture", true, "true: scrap sitting on/inside ship furniture (the cabinet, shelves, other placeable objects) is never deposited, matching HQoL. false: such scrap is deposited like any other. This is separate from the blacklist.");
			AutosaveBehaviour = cfg.Bind<AutosaveMode>("Behaviour", "AutosaveMode", AutosaveMode.RestoreOnly, "What happens to deposited items when the game saves (end of round, ship ready to land, leaving). RestoreSaveRedeposit: items are respawned for the save and removed again right after it, so every save contains them (this briefly spawns and despawns every deposited item; with thousands of items expect a short hitch). RestoreOnly: items are restored before the save and STAY on the ship; run /deposit again yourself. Either way the mod never writes anything to the save file itself.");
			SkipSaveIfItemsCannotBeRestored = cfg.Bind<bool>("Behaviour", "SkipSaveIfItemsCannotBeRestored", true, "If deposited items could not be put back on the ship when the game is about to save (typically during Alt+F4 / application quit, when the network is already shutting down), skip that save entirely so your previous save file stays intact. Otherwise the game would overwrite it with a ship that is missing the deposited items.");
			LogItemIdentifiers = cfg.Bind<bool>("Debug", "LogItemIdentifiers", false, "Print every item's asset name / display name when a lobby loads (host only). Use this to fill the blacklist.");
			blacklist.SettingChanged += delegate
			{
				Rebuild();
			};
			Rebuild();
		}

		public static string Norm(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length);
			foreach (char c in s)
			{
				if (!char.IsWhiteSpace(c) && c != '_' && c != '-')
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}

		private void Rebuild()
		{
			HashSet<string> hashSet = new HashSet<string>();
			string[] array = (blacklist.Value ?? "").Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text = Norm(array[i]);
				if (text.Length > 0)
				{
					hashSet.Add(text);
				}
			}
			blacklistSet = hashSet;
		}

		public bool IsBlacklisted(params string[] identifiers)
		{
			bool value = BlacklistPartialMatch.Value;
			for (int i = 0; i < identifiers.Length; i++)
			{
				string text = Norm(identifiers[i]);
				if (text.Length == 0)
				{
					continue;
				}
				if (blacklistSet.Contains(text))
				{
					return true;
				}
				if (!value)
				{
					continue;
				}
				foreach (string item in blacklistSet)
				{
					if (item.Length >= 3 && text.Contains(item))
					{
						return true;
					}
				}
			}
			return false;
		}
	}
}