Decompiled source of SargamAutoStore v1.0.0

BepInEx/plugins/SargamAutoStore/SargamAutoStore.Core.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("SargamAutoStore.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+d0abae280298e7b16f965b8dd964ac0fb470e2be")]
[assembly: AssemblyProduct("SargamAutoStore.Core")]
[assembly: AssemblyTitle("SargamAutoStore.Core")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 SargamAutoStore.Core
{
	public readonly struct Point3
	{
		public double X { get; }

		public double Y { get; }

		public double Z { get; }

		public Point3(double x, double y, double z)
		{
			if (!IsFinite(x) || !IsFinite(y) || !IsFinite(z))
			{
				throw new ArgumentOutOfRangeException("x", "Coordinates must be finite.");
			}
			X = x;
			Y = y;
			Z = z;
		}

		public double DistanceSquared(Point3 other)
		{
			double num = X - other.X;
			double num2 = Y - other.Y;
			double num3 = Z - other.Z;
			return num * num + num2 * num2 + num3 * num3;
		}

		internal static bool IsFinite(double value)
		{
			if (!double.IsNaN(value))
			{
				return !double.IsInfinity(value);
			}
			return false;
		}
	}
	public sealed class RoundRobinRegistry<T> where T : notnull
	{
		private readonly Dictionary<T, LinkedListNode<T>> _nodes;

		private readonly LinkedList<T> _ring = new LinkedList<T>();

		private LinkedListNode<T>? _next;

		public int Count => _nodes.Count;

		public RoundRobinRegistry(IEqualityComparer<T>? comparer = null)
		{
			_nodes = new Dictionary<T, LinkedListNode<T>>(comparer);
		}

		public bool Contains(T value)
		{
			return _nodes.ContainsKey(value);
		}

		public bool Add(T value)
		{
			if (_nodes.ContainsKey(value))
			{
				return false;
			}
			LinkedListNode<T> linkedListNode = _ring.AddLast(value);
			_nodes.Add(value, linkedListNode);
			if (_next == null)
			{
				_next = linkedListNode;
			}
			return true;
		}

		public bool Remove(T value)
		{
			if (!_nodes.TryGetValue(value, out LinkedListNode<T> value2))
			{
				return false;
			}
			if (_next == value2)
			{
				_next = value2.Next ?? _ring.First;
			}
			_ring.Remove(value2);
			_nodes.Remove(value);
			if (_ring.Count == 0)
			{
				_next = null;
			}
			return true;
		}

		public bool TryNext(out T value)
		{
			if (_next == null)
			{
				value = default(T);
				return false;
			}
			LinkedListNode<T> next = _next;
			_next = next.Next ?? _ring.First;
			value = next.Value;
			return true;
		}

		public void Clear()
		{
			_nodes.Clear();
			_ring.Clear();
			_next = null;
		}
	}
	public readonly struct SignItemIdentity
	{
		public string PrefabName { get; }

		public string SharedName { get; }

		public string LocalizedName { get; }

		public bool IsFood { get; }

		public SignItemIdentity(string prefabName, string sharedName, string localizedName, bool isFood)
		{
			PrefabName = SignLabels.Normalize(prefabName);
			SharedName = SignLabels.Normalize(sharedName);
			LocalizedName = SignLabels.Normalize(localizedName);
			IsFood = isFood;
		}
	}
	public sealed class SignLabels
	{
		public const string DefaultAliases = "madeira=Wood;madeiras=Wood,FineWood,RoundLog,YggdrasilWood;comida=@food;comidas=@food;alimentos=@food;food=@food;pedra=Stone;pedras=Stone";

		private readonly Dictionary<string, string[]> _aliases = new Dictionary<string, string[]>(StringComparer.Ordinal);

		private static readonly char[] LabelSeparators = new char[6] { ',', ';', '/', '\n', '\r', '|' };

		public SignLabels(string aliases)
		{
			string[] array = (aliases ?? "").Split(new char[1] { ';' });
			foreach (string text in array)
			{
				if (string.IsNullOrWhiteSpace(text))
				{
					continue;
				}
				int num = text.IndexOf('=');
				if (num <= 0 || num != text.LastIndexOf('='))
				{
					throw new FormatException("Sign aliases use label=Prefab,Prefab;label=@food.");
				}
				string text2 = Normalize(text.Substring(0, num));
				string[] array2 = Parse(text.Substring(num + 1));
				if (text2.Length == 0 || array2.Length == 0 || text2.IndexOfAny(LabelSeparators) >= 0)
				{
					throw new FormatException("A sign alias needs one nonempty label and at least one item.");
				}
				string[] array3 = array2;
				foreach (string text3 in array3)
				{
					if (text3.StartsWith("@", StringComparison.Ordinal) && text3 != "@FOOD")
					{
						throw new FormatException("The only supported sign category is @food; other aliases must list exact item names.");
					}
				}
				if (_aliases.ContainsKey(text2))
				{
					throw new FormatException("Duplicate sign alias: " + text2);
				}
				_aliases.Add(text2, array2);
			}
		}

		public bool Matches(string[] labels, SignItemIdentity item)
		{
			foreach (string text in labels)
			{
				if (text.Length == 0)
				{
					continue;
				}
				if (Contains(item.PrefabName, text) || Contains(item.SharedName, text) || Contains(item.LocalizedName, text) || (text == "@FOOD" && item.IsFood))
				{
					return true;
				}
				if (!_aliases.TryGetValue(text, out string[] value))
				{
					continue;
				}
				string[] array = value;
				for (int j = 0; j < array.Length; j++)
				{
					if (ExactMatch(array[j], item))
					{
						return true;
					}
				}
			}
			return false;
		}

		private static bool Contains(string? name, string label)
		{
			if (name != null)
			{
				return name.IndexOf(label, StringComparison.Ordinal) >= 0;
			}
			return false;
		}

		private static bool ExactMatch(string label, SignItemIdentity item)
		{
			if (label.Length > 0)
			{
				if ((!(label == "@FOOD") || !item.IsFood) && !(label == item.PrefabName) && !(label == item.SharedName))
				{
					return label == item.LocalizedName;
				}
				return true;
			}
			return false;
		}

		public static string[] Parse(string text)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				return Array.Empty<string>();
			}
			if (text.Length > 2048)
			{
				return new string[1] { "\0OVERSIZED-SIGN" };
			}
			List<string> list = new List<string>();
			string[] array = StripRichText(text).Split(LabelSeparators, StringSplitOptions.RemoveEmptyEntries);
			for (int i = 0; i < array.Length; i++)
			{
				string text2 = Normalize(array[i]);
				if (text2.Length > 0 && text2 != "..." && text2 != "…" && !list.Contains(text2))
				{
					list.Add(text2);
				}
			}
			return list.ToArray();
		}

		public static string Normalize(string? value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "";
			}
			string text = value.Normalize(NormalizationForm.FormD);
			StringBuilder stringBuilder = new StringBuilder(text.Length);
			bool flag = false;
			string text2 = text;
			foreach (char c in text2)
			{
				UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
				if (unicodeCategory == UnicodeCategory.NonSpacingMark || unicodeCategory == UnicodeCategory.SpacingCombiningMark || unicodeCategory == UnicodeCategory.EnclosingMark)
				{
					continue;
				}
				if (char.IsWhiteSpace(c))
				{
					flag = stringBuilder.Length > 0;
					continue;
				}
				if (flag)
				{
					stringBuilder.Append(' ');
					flag = false;
				}
				stringBuilder.Append(char.ToUpperInvariant(c));
			}
			return stringBuilder.ToString().Normalize(NormalizationForm.FormC);
		}

		private static string StripRichText(string value)
		{
			if (value.IndexOf('<') < 0)
			{
				return value;
			}
			StringBuilder stringBuilder = new StringBuilder(value.Length);
			for (int i = 0; i < value.Length; i++)
			{
				if (value[i] == '<')
				{
					int num = value.IndexOf('>', i + 1);
					if (num >= 0)
					{
						i = num;
						continue;
					}
				}
				stringBuilder.Append(value[i]);
			}
			return stringBuilder.ToString();
		}
	}
	public sealed class SpatialHashGrid<T> where T : notnull
	{
		private sealed class Bucket
		{
			public readonly Cell Cell;

			public readonly List<Entry> Entries = new List<Entry>();

			public int Slot;

			public Bucket(Cell cell, int slot)
			{
				Cell = cell;
				Slot = slot;
			}
		}

		private sealed class Entry
		{
			public readonly T Value;

			public Point3 Position;

			public Cell Cell;

			public int Slot;

			public Entry(T value, Point3 position)
			{
				Value = value;
				Position = position;
			}
		}

		private readonly struct Cell : IEquatable<Cell>
		{
			public long X { get; }

			public long Y { get; }

			public long Z { get; }

			public Cell(long x, long y, long z)
			{
				X = x;
				Y = y;
				Z = z;
			}

			public bool Equals(Cell other)
			{
				if (X == other.X && Y == other.Y)
				{
					return Z == other.Z;
				}
				return false;
			}

			public override bool Equals(object? obj)
			{
				if (obj is Cell other)
				{
					return Equals(other);
				}
				return false;
			}

			public override int GetHashCode()
			{
				return (((X.GetHashCode() * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode();
			}
		}

		public const double MaximumSupportedRadius = 512.0;

		private readonly Dictionary<T, Entry> _entries;

		private readonly Dictionary<Cell, Bucket> _cells = new Dictionary<Cell, Bucket>();

		private readonly List<Bucket> _occupiedBuckets = new List<Bucket>();

		private readonly double _cellSize;

		public int Count => _entries.Count;

		public int CellCount => _cells.Count;

		public double MaxQueryRadius { get; }

		public int LastQueryCellChecks { get; private set; }

		public int LastQueryPointChecks { get; private set; }

		public bool LastQueryUsedSparseScan { get; private set; }

		public SpatialHashGrid(double cellSize = 8.0, double maxQueryRadius = 128.0, IEqualityComparer<T>? comparer = null)
		{
			if (!Point3.IsFinite(cellSize) || cellSize <= 0.0)
			{
				throw new ArgumentOutOfRangeException("cellSize");
			}
			if (!Point3.IsFinite(maxQueryRadius) || maxQueryRadius <= 0.0 || maxQueryRadius > 512.0 || maxQueryRadius > cellSize * 32.0)
			{
				throw new ArgumentOutOfRangeException("maxQueryRadius", "The maximum radius must be finite, positive, at most 512 units, and at most 32 cell widths.");
			}
			_cellSize = cellSize;
			MaxQueryRadius = maxQueryRadius;
			_entries = new Dictionary<T, Entry>(comparer);
		}

		public void Upsert(T value, Point3 position)
		{
			Cell cell = ToCell(position);
			if (_entries.TryGetValue(value, out Entry value2))
			{
				if (!value2.Cell.Equals(cell))
				{
					RemoveFromCell(value2);
					AddToCell(value2, cell);
				}
				value2.Position = position;
			}
			else
			{
				value2 = new Entry(value, position);
				AddToCell(value2, cell);
				_entries.Add(value, value2);
			}
		}

		public bool Remove(T value)
		{
			if (!_entries.TryGetValue(value, out Entry value2))
			{
				return false;
			}
			RemoveFromCell(value2);
			_entries.Remove(value);
			return true;
		}

		public void Clear()
		{
			_entries.Clear();
			_cells.Clear();
			_occupiedBuckets.Clear();
			LastQueryCellChecks = 0;
			LastQueryPointChecks = 0;
			LastQueryUsedSparseScan = false;
		}

		public void Query(Point3 center, double radius, List<T> results)
		{
			if (results == null)
			{
				throw new ArgumentNullException("results");
			}
			if (!Point3.IsFinite(radius) || radius < 0.0 || radius > MaxQueryRadius)
			{
				throw new ArgumentOutOfRangeException("radius");
			}
			Cell cell = ToCell(new Point3(center.X - radius, center.Y - radius, center.Z - radius));
			Cell cell2 = ToCell(new Point3(center.X + radius, center.Y + radius, center.Z + radius));
			results.Clear();
			LastQueryCellChecks = 0;
			LastQueryPointChecks = 0;
			double radiusSquared = radius * radius;
			long num = (cell2.X - cell.X + 1) * (cell2.Y - cell.Y + 1) * (cell2.Z - cell.Z + 1);
			LastQueryUsedSparseScan = num > _occupiedBuckets.Count;
			if (LastQueryUsedSparseScan)
			{
				for (int i = 0; i < _occupiedBuckets.Count; i++)
				{
					Bucket bucket = _occupiedBuckets[i];
					LastQueryCellChecks++;
					Cell cell3 = bucket.Cell;
					if (cell3.X >= cell.X && cell3.X <= cell2.X && cell3.Y >= cell.Y && cell3.Y <= cell2.Y && cell3.Z >= cell.Z && cell3.Z <= cell2.Z)
					{
						CheckEntries(bucket.Entries, center, radiusSquared, results);
					}
				}
				return;
			}
			for (long num2 = cell.X; num2 <= cell2.X; num2++)
			{
				for (long num3 = cell.Y; num3 <= cell2.Y; num3++)
				{
					for (long num4 = cell.Z; num4 <= cell2.Z; num4++)
					{
						LastQueryCellChecks++;
						if (_cells.TryGetValue(new Cell(num2, num3, num4), out Bucket value))
						{
							CheckEntries(value.Entries, center, radiusSquared, results);
						}
					}
				}
			}
		}

		private void CheckEntries(List<Entry> entries, Point3 center, double radiusSquared, List<T> results)
		{
			for (int i = 0; i < entries.Count; i++)
			{
				Entry entry = entries[i];
				LastQueryPointChecks++;
				if (center.DistanceSquared(entry.Position) <= radiusSquared)
				{
					results.Add(entry.Value);
				}
			}
		}

		private Cell ToCell(Point3 position)
		{
			return new Cell(Coordinate(position.X), Coordinate(position.Y), Coordinate(position.Z));
		}

		private long Coordinate(double value)
		{
			double num = Math.Floor(value / _cellSize);
			if (!Point3.IsFinite(num) || num < -4503599627370495.0 || num > 4503599627370495.0)
			{
				throw new ArgumentOutOfRangeException("value", "Position exceeds the index coordinate range.");
			}
			return (long)num;
		}

		private void AddToCell(Entry entry, Cell cell)
		{
			if (!_cells.TryGetValue(cell, out Bucket value))
			{
				value = new Bucket(cell, _occupiedBuckets.Count);
				_cells.Add(cell, value);
				_occupiedBuckets.Add(value);
			}
			entry.Cell = cell;
			entry.Slot = value.Entries.Count;
			value.Entries.Add(entry);
		}

		private void RemoveFromCell(Entry entry)
		{
			Bucket bucket = _cells[entry.Cell];
			List<Entry> entries = bucket.Entries;
			int num = entries.Count - 1;
			if (entry.Slot != num)
			{
				Entry entry2 = entries[num];
				entries[entry.Slot] = entry2;
				entry2.Slot = entry.Slot;
			}
			entries.RemoveAt(num);
			if (entries.Count == 0)
			{
				_cells.Remove(entry.Cell);
				int num2 = _occupiedBuckets.Count - 1;
				if (bucket.Slot != num2)
				{
					Bucket bucket2 = _occupiedBuckets[num2];
					_occupiedBuckets[bucket.Slot] = bucket2;
					bucket2.Slot = bucket.Slot;
				}
				_occupiedBuckets.RemoveAt(num2);
			}
		}
	}
	public readonly struct StorageCandidate<T>
	{
		public T Value { get; }

		public bool HasMatchingType { get; }

		public bool HasCapacity { get; }

		public double DistanceSquared { get; }

		public string StableId { get; }

		public bool MatchesSign { get; }

		public bool HasStackSpace { get; }

		public bool IsNewItemChest { get; }

		public StorageCandidate(T value, bool hasMatchingType, bool hasCapacity, double distanceSquared, string stableId, bool matchesSign = false, bool hasStackSpace = false, bool isNewItemChest = false)
		{
			if (!Point3.IsFinite(distanceSquared) || distanceSquared < 0.0)
			{
				throw new ArgumentOutOfRangeException("distanceSquared");
			}
			Value = value;
			HasMatchingType = hasMatchingType;
			HasCapacity = hasCapacity;
			DistanceSquared = distanceSquared;
			StableId = stableId ?? throw new ArgumentNullException("stableId");
			MatchesSign = matchesSign;
			HasStackSpace = hasStackSpace;
			IsNewItemChest = isNewItemChest;
		}
	}
	public static class CandidateSelector
	{
		private sealed class CandidateComparer<T> : IComparer<StorageCandidate<T>>
		{
			internal static readonly CandidateComparer<T> Instance = new CandidateComparer<T>();

			internal static readonly Comparison<StorageCandidate<T>> Comparison = Instance.Compare;

			public int Compare(StorageCandidate<T> x, StorageCandidate<T> y)
			{
				int num = y.HasMatchingType.CompareTo(x.HasMatchingType);
				if (num != 0)
				{
					return num;
				}
				if (x.HasMatchingType)
				{
					int num2 = y.HasStackSpace.CompareTo(x.HasStackSpace);
					if (num2 != 0)
					{
						return num2;
					}
				}
				else
				{
					int num3 = y.MatchesSign.CompareTo(x.MatchesSign);
					if (num3 != 0)
					{
						return num3;
					}
					if (!x.MatchesSign)
					{
						int num4 = y.IsNewItemChest.CompareTo(x.IsNewItemChest);
						if (num4 != 0)
						{
							return num4;
						}
					}
				}
				int num5 = x.DistanceSquared.CompareTo(y.DistanceSquared);
				if (num5 == 0)
				{
					return StringComparer.Ordinal.Compare(x.StableId, y.StableId);
				}
				return num5;
			}
		}

		public static void Rank<T>(List<StorageCandidate<T>> candidates, bool strictExistingType = false, bool force = false)
		{
			if (candidates == null)
			{
				throw new ArgumentNullException("candidates");
			}
			int num = 0;
			for (int i = 0; i < candidates.Count; i++)
			{
				StorageCandidate<T> value = candidates[i];
				if (value.HasCapacity && (!strictExistingType || force || value.HasMatchingType || value.MatchesSign || value.IsNewItemChest))
				{
					candidates[num++] = value;
				}
			}
			if (num < candidates.Count)
			{
				candidates.RemoveRange(num, candidates.Count - num);
			}
			candidates.Sort(CandidateComparer<T>.Comparison);
		}
	}
}

BepInEx/plugins/SargamAutoStore/SargamAutoStore.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SargamAutoStore.Core;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("SargamAutoStore.RuntimeTests")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("SargamAutoStore")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+d0abae280298e7b16f965b8dd964ac0fb470e2be")]
[assembly: AssemblyProduct("SargamAutoStore")]
[assembly: AssemblyTitle("SargamAutoStore")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 SargamAutoStore
{
	internal sealed class Coordinator
	{
		private sealed class DropState
		{
			internal float Registered;

			internal float RetryAt;
		}

		internal const int RoutingPolicyVersion = 3;

		private readonly Plugin _plugin;

		private readonly Settings _settings;

		private readonly Ownership _ownership;

		private readonly SignRouting _routes = new SignRouting();

		private readonly RoundRobinRegistry<ItemDrop> _drops = new RoundRobinRegistry<ItemDrop>((IEqualityComparer<ItemDrop>)null);

		private readonly Dictionary<ItemDrop, DropState> _states = new Dictionary<ItemDrop, DropState>();

		private readonly SpatialHashGrid<Container> _chests = new SpatialHashGrid<Container>(8.0, 256.0, (IEqualityComparer<Container>)null);

		private readonly HashSet<Container> _registered = new HashSet<Container>();

		private readonly Dictionary<Container, string> _stableIds = new Dictionary<Container, string>();

		private readonly List<Container> _nearby = new List<Container>();

		private readonly List<StorageCandidate<Container>> _candidates = new List<StorageCandidate<Container>>();

		private readonly Queue<ItemData> _inventoryQueue = new Queue<ItemData>();

		private readonly HashSet<ItemData> _inventoryUnvisited = new HashSet<ItemData>();

		private readonly HashSet<string> _excluded = new HashSet<string>(StringComparer.Ordinal);

		private HashSet<ItemDrop>? _forceRemaining;

		private Player? _jobPlayer;

		private float _forceUntil;

		private float _inventoryUntil;

		private float _nextInventoryAttempt;

		private float _nextDropRequest;

		private long _forceBaseline;

		private long _inventoryBaseline;

		private bool _inventoryAll;

		private bool _uncertain;

		private readonly Stopwatch _clock = new Stopwatch();

		private long _examined;

		private long _moved;

		private long _groundMoved;

		private long _inventoryMoved;

		private long _errors;

		private long _noDestination;

		private long _ownershipWaits;

		private long _blocked;

		private double _lastMs;

		private double _maxMs;

		private int _lastExamined;

		internal Coordinator(Plugin plugin, Settings settings)
		{
			_plugin = plugin;
			_settings = settings;
			_ownership = new Ownership(plugin, settings);
			Reload();
		}

		internal void Reload()
		{
			_excluded.Clear();
			string[] array = _settings.ExcludedPrefabs.Value.Split(new char[1] { ',' });
			foreach (string text in array)
			{
				if (text.Trim().Length > 0)
				{
					_excluded.Add(text.Trim());
				}
			}
			_routes.Configure(_settings.SignRange.Value, _settings.SignAliases.Value, 0.15f, _settings.SignGroups.Value);
		}

		internal static bool IsSupported(Container chest, bool requireCreator = true)
		{
			if (!Object.op_Implicit((Object)(object)chest) || chest.m_autoDestroyEmpty || Object.op_Implicit((Object)(object)chest.m_wagon) || Object.op_Implicit((Object)(object)chest.m_rootObjectOverride) || Object.op_Implicit((Object)(object)((Component)chest).GetComponent<TombStone>()) || Object.op_Implicit((Object)(object)((Component)chest).GetComponentInParent<Ship>()))
			{
				return false;
			}
			Piece component = ((Component)chest).GetComponent<Piece>();
			if (Object.op_Implicit((Object)(object)component) && (!requireCreator || component.GetCreator() != 0L))
			{
				return chest.GetInventory() != null;
			}
			return false;
		}

		internal void Register(Container chest)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = GameAccess.View.Invoke(chest);
			if (!Object.op_Implicit((Object)(object)val) || !val.IsValid() || !IsSupported(chest, requireCreator: false) || !_registered.Add(chest))
			{
				return;
			}
			try
			{
				_ownership.Register(chest);
				_chests.Upsert(chest, Point(((Component)chest).transform.position));
				_routes.Register(chest);
				_stableIds[chest] = ((object)Unsafe.As<ZDOID, ZDOID>(ref val.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString();
				(((Component)chest).gameObject.GetComponent<ContainerLifetime>() ?? ((Component)chest).gameObject.AddComponent<ContainerLifetime>()).Container = chest;
			}
			catch
			{
				_registered.Remove(chest);
				_chests.Remove(chest);
				_routes.Remove(chest);
				_stableIds.Remove(chest);
				_ownership.Remove(chest);
				throw;
			}
		}

		internal void Register(Sign sign)
		{
			_routes.Register(sign);
		}

		internal void Register(ItemDrop drop)
		{
			ZNetView val = GameAccess.DropView.Invoke(drop);
			if (Object.op_Implicit((Object)(object)val) && val.IsValid() && _drops.Add(drop))
			{
				_states[drop] = new DropState
				{
					Registered = Time.realtimeSinceStartup
				};
			}
		}

		internal void Remove(ItemDrop drop)
		{
			_drops.Remove(drop);
			_states.Remove(drop);
			_forceRemaining?.Remove(drop);
		}

		internal void Remove(Container chest)
		{
			_chests.Remove(chest);
			_routes.Remove(chest);
			_registered.Remove(chest);
			_stableIds.Remove(chest);
			_ownership.Remove(chest);
		}

		internal void Reset()
		{
			_drops.Clear();
			_states.Clear();
			_chests.Clear();
			_routes.Clear();
			_registered.Clear();
			_stableIds.Clear();
			_nearby.Clear();
			_candidates.Clear();
			_ownership.Clear();
			CancelManual();
			_uncertain = false;
		}

		internal void CancelManual()
		{
			_forceRemaining = null;
			_forceUntil = 0f;
			_inventoryQueue.Clear();
			_inventoryUnvisited.Clear();
			_inventoryUntil = 0f;
			_jobPlayer = null;
		}

		internal void ForceGround()
		{
			if (!CanRun())
			{
				_plugin.Tell("SargamAutoStore: enter a world and enable the mod to collect items.");
				return;
			}
			if (_forceRemaining != null)
			{
				_plugin.Tell("SargamAutoStore: a forced collection is already queued.");
				return;
			}
			_forceRemaining = new HashSet<ItemDrop>(_states.Keys);
			foreach (DropState value in _states.Values)
			{
				value.RetryAt = 0f;
			}
			_forceUntil = Time.realtimeSinceStartup + 5f;
			_forceBaseline = _groundMoved;
			_plugin.Tell("SargamAutoStore: collecting ground items; " + RoutingDescription() + "; item protections and chest routing still apply.");
		}

		internal void StoreInventory(bool all)
		{
			if (!CanRun())
			{
				_plugin.Tell("SargamAutoStore: enter a world and enable the mod to store inventory.");
				return;
			}
			if (_inventoryUntil > 0f)
			{
				_plugin.Tell("SargamAutoStore: inventory storage is already queued.");
				return;
			}
			_jobPlayer = Player.m_localPlayer;
			_inventoryAll = all;
			_inventoryQueue.Clear();
			_inventoryUnvisited.Clear();
			foreach (ItemData allItem in ((Humanoid)_jobPlayer).GetInventory().GetAllItems())
			{
				if (InventoryEligible(allItem))
				{
					_inventoryQueue.Enqueue(allItem);
					_inventoryUnvisited.Add(allItem);
				}
			}
			_inventoryUntil = Time.realtimeSinceStartup + 5f;
			_nextInventoryAttempt = 0f;
			_inventoryBaseline = _inventoryMoved;
			_plugin.Tell("SargamAutoStore: storing inventory; " + RoutingDescription() + "; equipped items, blocked types and protected consumables are kept.");
		}

		private bool CanRun()
		{
			if (!_uncertain && _settings.Enabled.Value && Object.op_Implicit((Object)(object)Player.m_localPlayer) && !((Character)Player.m_localPlayer).IsDead() && !((Character)Player.m_localPlayer).IsTeleporting())
			{
				return Object.op_Implicit((Object)(object)ZNetScene.instance);
			}
			return false;
		}

		private bool Excluded(ItemData item)
		{
			if (Object.op_Implicit((Object)(object)item.m_dropPrefab))
			{
				return _excluded.Contains(((Object)item.m_dropPrefab).name);
			}
			return false;
		}

		private bool InventoryEligible(ItemData item)
		{
			if (item != null && item.m_shared != null && !item.m_shared.m_questItem && item.m_stack > 0 && !item.m_equipped && !Excluded(item) && (!_settings.ProtectConsumables.Value || !ProtectionRules.IsConsumable(item)))
			{
				if (!_inventoryAll && _settings.ProtectHotbar.Value)
				{
					return item.m_gridPos.y != 0;
				}
				return true;
			}
			return false;
		}

		private static Point3 Point(Vector3 value)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			return new Point3((double)value.x, (double)value.y, (double)value.z);
		}

		internal void Tick()
		{
			if (!CanRun())
			{
				CancelManual();
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			_clock.Restart();
			int num = 0;
			if (_inventoryUntil > 0f && realtimeSinceStartup >= _nextInventoryAttempt)
			{
				try
				{
					ProcessInventory(realtimeSinceStartup);
				}
				catch (UncertainTransferException error)
				{
					HaltAfterUncertainTransfer(error);
				}
				catch (Exception ex)
				{
					_errors++;
					_plugin.Error("Inventory item failed; remaining queue continues", ex);
				}
				num++;
			}
			bool flag = _forceRemaining != null;
			if (!_uncertain && (_settings.Automatic.Value || flag))
			{
				int num2 = Math.Min(_drops.Count, _settings.ItemsPerFrame.Value - num);
				ItemDrop val = default(ItemDrop);
				for (int i = 0; i < num2; i++)
				{
					if (_uncertain)
					{
						break;
					}
					if (!(_clock.Elapsed.TotalMilliseconds < (double)_settings.FrameBudgetMs.Value))
					{
						break;
					}
					if (!_drops.TryNext(ref val))
					{
						break;
					}
					num++;
					_examined++;
					if (!_states.TryGetValue(val, out DropState value))
					{
						continue;
					}
					if (!Object.op_Implicit((Object)(object)val))
					{
						Remove(val);
					}
					else if (!(realtimeSinceStartup < value.RetryAt) && !(realtimeSinceStartup - value.Registered < _settings.DropDelay.Value))
					{
						_forceRemaining?.Remove(val);
						value.RetryAt = realtimeSinceStartup + (flag ? Math.Min(0.25f, _settings.RetrySeconds.Value) : _settings.RetrySeconds.Value);
						try
						{
							ProcessGround(val, flag, realtimeSinceStartup);
						}
						catch (UncertainTransferException error2)
						{
							HaltAfterUncertainTransfer(error2);
						}
						catch (Exception ex2)
						{
							_errors++;
							value.RetryAt = realtimeSinceStartup + 2f;
							_plugin.Error("Ground item failed; other items continue", ex2);
						}
					}
				}
			}
			if (_forceRemaining != null && realtimeSinceStartup >= _forceUntil && _forceRemaining.Count == 0)
			{
				_forceRemaining = null;
				_plugin.Tell($"SargamAutoStore: ground collection completed; {_groundMoved - _forceBaseline} units moved. {RemainingItemHint()}");
			}
			_clock.Stop();
			_lastMs = _clock.Elapsed.TotalMilliseconds;
			_maxMs = Math.Max(_maxMs, _lastMs);
			_lastExamined = num;
		}

		private void ProcessGround(ItemDrop drop, bool force, float now)
		{
			//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_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = GameAccess.DropView.Invoke(drop);
			if (!Object.op_Implicit((Object)(object)val) || !val.IsValid())
			{
				Remove(drop);
				return;
			}
			Vector3 position = ((Component)drop).transform.position;
			Vector3 val2 = position - ((Component)Player.m_localPlayer).transform.position;
			if (((Vector3)(ref val2)).sqrMagnitude > _settings.ActivityRange.Value * _settings.ActivityRange.Value)
			{
				return;
			}
			if (val.IsOwner())
			{
				GameAccess.LoadDrop(drop);
			}
			if (drop.IsPiece() || drop.InTar() || Object.op_Implicit((Object)(object)((Component)drop).GetComponent<Fish>()) || drop.m_itemData.m_shared.m_questItem || Excluded(drop.m_itemData) || !PrivateArea.CheckAccess(position, 0f, false, false))
			{
				_blocked++;
			}
			else
			{
				if (drop.m_itemData.m_stack <= 0)
				{
					return;
				}
				BuildCandidates(drop.m_itemData, position, force);
				if (_candidates.Count != 0)
				{
					int num = int.MaxValue;
					{
						foreach (StorageCandidate<Container> candidate in _candidates)
						{
							int num2 = RoutingTier(candidate);
							if (num2 > num)
							{
								break;
							}
							Container value = candidate.Value;
							if (!Accessible(value))
							{
								continue;
							}
							if (!_ownership.Ready(value, now))
							{
								_ownershipWaits++;
								num = Math.Min(num, num2);
								continue;
							}
							if (!val.IsOwner())
							{
								if (now >= _nextDropRequest)
								{
									_nextDropRequest = now + 0.1f;
									drop.RequestOwn();
								}
								_ownershipWaits++;
								break;
							}
							if (!val.IsValid() || !val.IsOwner() || !drop.CanPickup(true))
							{
								break;
							}
							ItemData itemData = drop.m_itemData;
							int num3 = InventoryTransfer.MoveFromGround(drop, value, itemData.m_stack);
							_moved += num3;
							_groundMoved += num3;
							if (num3 > 0)
							{
								_plugin.NotifyStored(value, itemData, num3, fromGround: true, candidate.MatchesSign, candidate.HasMatchingType, candidate.IsNewItemChest);
							}
							if (Object.op_Implicit((Object)(object)drop) && drop.m_itemData.m_stack > 0)
							{
								continue;
							}
							break;
						}
						return;
					}
				}
				_noDestination++;
			}
		}

		private void ProcessInventory(float now)
		{
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_jobPlayer != (Object)(object)Player.m_localPlayer || !Object.op_Implicit((Object)(object)_jobPlayer) || (now >= _inventoryUntil && _inventoryUnvisited.Count == 0) || _inventoryQueue.Count == 0)
			{
				_plugin.Tell($"SargamAutoStore: inventory storage completed; {_inventoryMoved - _inventoryBaseline} units moved; {_inventoryQueue.Count} stacks remaining. {RemainingItemHint()}");
				_inventoryQueue.Clear();
				_inventoryUnvisited.Clear();
				_inventoryUntil = 0f;
				_jobPlayer = null;
				return;
			}
			ItemData val = _inventoryQueue.Dequeue();
			_inventoryUnvisited.Remove(val);
			Inventory inventory = ((Humanoid)_jobPlayer).GetInventory();
			if (!inventory.ContainsItem(val) || !InventoryEligible(val))
			{
				return;
			}
			BuildCandidates(val, ((Component)_jobPlayer).transform.position, force: true);
			int num = int.MaxValue;
			foreach (StorageCandidate<Container> candidate in _candidates)
			{
				int num2 = RoutingTier(candidate);
				if (num2 > num)
				{
					break;
				}
				Container value = candidate.Value;
				if (!Accessible(value))
				{
					continue;
				}
				if (!_ownership.Ready(value, now))
				{
					_ownershipWaits++;
					num = Math.Min(num, num2);
					continue;
				}
				int num3 = InventoryTransfer.MoveFromInventory(inventory, val, value, val.m_stack);
				_moved += num3;
				_inventoryMoved += num3;
				if (num3 > 0)
				{
					_plugin.NotifyStored(value, val, num3, fromGround: false, candidate.MatchesSign, candidate.HasMatchingType, candidate.IsNewItemChest);
				}
				if (inventory.ContainsItem(val) && val.m_stack > 0)
				{
					continue;
				}
				return;
			}
			if (inventory.ContainsItem(val) && val.m_stack > 0)
			{
				_inventoryQueue.Enqueue(val);
			}
			_nextInventoryAttempt = now + 0.05f;
		}

		private bool Accessible(Container chest)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if (!IsSupported(chest))
			{
				return false;
			}
			ZNetView val = GameAccess.View.Invoke(chest);
			if (Object.op_Implicit((Object)(object)val) && val.IsValid() && !chest.IsInUse() && val.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 0 && GameAccess.CheckAccess(chest, Player.m_localPlayer.GetPlayerID()))
			{
				return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, false);
			}
			return false;
		}

		private static int RoutingTier(StorageCandidate<Container> candidate)
		{
			if (!candidate.HasMatchingType)
			{
				if (!candidate.MatchesSign)
				{
					if (!candidate.IsNewItemChest)
					{
						return 3;
					}
					return 2;
				}
				return 1;
			}
			return 0;
		}

		private void BuildCandidates(ItemData item, Vector3 position, bool force)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			_chests.Query(Point(position), (double)_settings.Range.Value, _nearby);
			_candidates.Clear();
			SignItemIdentity item2 = (SignItemIdentity)(_settings.UseSigns.Value ? _routes.Describe(item) : default(SignItemIdentity));
			foreach (Container item3 in _nearby)
			{
				if (!Object.op_Implicit((Object)(object)item3))
				{
					Remove(item3);
					continue;
				}
				try
				{
					if (!Accessible(item3))
					{
						continue;
					}
					GameAccess.Load(item3);
					Inventory inventory = item3.GetInventory();
					SignRoute signRoute = (_settings.UseSigns.Value ? _routes.Evaluate(item3, item2) : SignRoute.Unlabeled);
					bool flag = NewItemChests.IsMarked(item3);
					bool flag2 = signRoute == SignRoute.Match || flag || _settings.AllowUnlabelledFallback.Value;
					if (flag2 || InventoryTransfer.HasType(inventory, item))
					{
						InventoryAnalysis inventoryAnalysis = InventoryTransfer.Analyze(inventory, item);
						bool hasMatchingType = inventoryAnalysis.HasMatchingType;
						if ((hasMatchingType || flag2) && inventoryAnalysis.Capacity > 0)
						{
							List<StorageCandidate<Container>> candidates = _candidates;
							Vector3 val = ((Component)item3).transform.position - position;
							candidates.Add(new StorageCandidate<Container>(item3, hasMatchingType, true, (double)((Vector3)(ref val)).sqrMagnitude, _stableIds[item3], signRoute == SignRoute.Match, inventoryAnalysis.HasStackSpace, flag));
						}
					}
				}
				catch (Exception ex)
				{
					_errors++;
					_plugin.Error("Chest failed; other destinations continue", ex);
				}
			}
			CandidateSelector.Rank<Container>(_candidates, _settings.ExistingOnly.Value, force);
		}

		private string RemainingItemHint()
		{
			string text = (_settings.UseSigns.Value ? "a chest with the same type, matching sign text or a designated new-item chest" : (_settings.AllowUnlabelledFallback.Value ? "a chest allowed by content routing" : "a chest already containing the same type or a designated new-item chest"));
			return "For remaining items, check " + text + " within range, capacity, access, item protections and network ownership (/sas status).";
		}

		private string RoutingDescription()
		{
			if (!_settings.UseSigns.Value)
			{
				return "content routing, then designated new-item chests";
			}
			return "chest contents, then matching sign text, then designated new-item chests";
		}

		private void HaltAfterUncertainTransfer(Exception error)
		{
			_uncertain = true;
			_errors++;
			CancelManual();
			_plugin.Error("Transfer persistence is uncertain; reload the world before further storage", error);
			_plugin.Tell("SargamAutoStore: transfer persistence is uncertain. Collection suspended; check the log and reload the world.");
		}

		internal string Status()
		{
			return string.Format("SargamAutoStore {0}: health={1}; enabled={2}; auto={3}; mode={4}; drops={5}; chests={6}; moved={7}; examined={8}; noDestination={9}; protected={10}; ownershipWaits={11}; requests={12}; timeouts={13}; denied={14}; errors={15}; frameChecks={16}/{17}; last/maxMs={18:F3}/{19:F3}; forcePending={20}; inventoryQueue={21}. Counters are cumulative attempts, not unique items.", "1.0.0", _uncertain ? "UNCERTAIN_RELOAD_WORLD" : "ready", _settings.Enabled.Value, _settings.Automatic.Value, _settings.UseSigns.Value ? "hybrid" : "content", _drops.Count, _registered.Count, _moved, _examined, _noDestination, _blocked, _ownershipWaits, _ownership.Requests, _ownership.Timeouts, _ownership.Denied, _errors, _lastExamined, _settings.ItemsPerFrame.Value, _lastMs, _maxMs, _forceRemaining?.Count ?? 0, _inventoryQueue.Count);
		}
	}
	internal static class DestinationHighlight
	{
		private static class QueuedProperties
		{
			private const BindingFlags PrivateInstance = BindingFlags.Instance | BindingFlags.NonPublic;

			private static readonly FieldInfo Blocks = typeof(MaterialMan).GetField("m_blocks", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingFieldException(typeof(MaterialMan).FullName, "m_blocks");

			private static readonly Type ContainerType = typeof(MaterialMan).GetNestedType("PropertyContainer", BindingFlags.NonPublic) ?? throw new MissingMemberException(typeof(MaterialMan).FullName, "PropertyContainer");

			private static readonly FieldInfo Properties = ContainerType.GetField("m_shaderProperties", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingFieldException(ContainerType.FullName, "m_shaderProperties");

			internal static readonly Type ColorProperty = (typeof(MaterialMan).GetNestedType("ShaderProperty`1", BindingFlags.NonPublic) ?? throw new MissingMemberException(typeof(MaterialMan).FullName, "ShaderProperty<T>")).MakeGenericType(typeof(Color));

			internal static readonly MethodInfo ReadColor = ColorProperty.GetMethod("Get", BindingFlags.Instance | BindingFlags.Public) ?? throw new MissingMethodException(ColorProperty.FullName, "Get");

			internal static IDictionary? Read(MaterialMan manager, object targetId)
			{
				object obj = ((Blocks.GetValue(manager) as IDictionary) ?? throw new InvalidOperationException("MaterialMan.m_blocks is not a dictionary."))[targetId];
				if (obj == null)
				{
					return null;
				}
				return (Properties.GetValue(obj) as IDictionary) ?? throw new InvalidOperationException("MaterialMan.PropertyContainer.m_shaderProperties is not a dictionary.");
			}
		}

		private sealed class ColorReader
		{
			private object? _property;

			private Func<Color>? _read;

			internal bool Matches(object? property, Color expected)
			{
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				if (property == null)
				{
					return false;
				}
				if (_property != property)
				{
					_property = property;
					_read = (QueuedProperties.ColorProperty.IsInstanceOfType(property) ? ((Func<Color>)Delegate.CreateDelegate(typeof(Func<Color>), property, QueuedProperties.ReadColor)) : null);
				}
				if (_read != null)
				{
					return Same(_read(), expected);
				}
				return false;
			}
		}

		private sealed class Entry
		{
			internal readonly GameObject Target;

			internal readonly object TargetId;

			internal readonly MaterialMan Manager;

			internal readonly List<Renderer> Renderers;

			internal readonly ColorReader TintReader = new ColorReader();

			internal readonly ColorReader EmissionReader = new ColorReader();

			internal bool Preview;

			internal float Started;

			internal float Until;

			internal float NextUpdate;

			internal float Duration;

			internal int LastAppliedFrame;

			internal Color Tint;

			internal Color Emission;

			internal Entry(GameObject target, object targetId, MaterialMan manager, List<Renderer> renderers, bool preview, float now, float duration)
			{
				Target = target;
				TargetId = targetId;
				Manager = manager;
				Renderers = renderers;
				Preview = preview;
				Started = now;
				Duration = duration;
				Until = now + duration;
			}
		}

		internal const int MaximumActive = 64;

		private const float UpdateInterval = 0.1f;

		private static readonly List<Entry> Active = new List<Entry>(64);

		private static readonly MaterialPropertyBlock Scratch = new MaterialPropertyBlock();

		private static readonly Color ReceivedColor = new Color(0.15f, 1f, 0.35f, 1f);

		private static readonly Color PreviewColor = new Color(0.15f, 0.65f, 1f, 1f);

		private static readonly object TintKey = ShaderProps._Color;

		private static readonly object EmissionKey = ShaderProps._EmissionColor;

		internal static int ActiveCount => Active.Count;

		internal static void Pulse(Container container, float duration = 1.5f, bool preview = false)
		{
			if (!Object.op_Implicit((Object)(object)container) || !Object.op_Implicit((Object)(object)MaterialMan.instance))
			{
				return;
			}
			GameObject gameObject = ((Component)container).gameObject;
			float unscaledTime = Time.unscaledTime;
			if (float.IsNaN(duration) || float.IsInfinity(duration))
			{
				duration = 1.5f;
			}
			duration = Mathf.Clamp(duration, 0.25f, 5f);
			for (int i = 0; i < Active.Count; i++)
			{
				Entry entry = Active[i];
				if (!((Object)(object)entry.Target != (Object)(object)gameObject))
				{
					if (!OwnsQueuedColors(entry))
					{
						RemoveAt(i);
					}
					else if (!preview || entry.Preview)
					{
						entry.Preview = preview;
						entry.Started = unscaledTime;
						entry.Duration = duration;
						entry.Until = unscaledTime + duration;
					}
					return;
				}
			}
			WearNTear component = ((Component)container).GetComponent<WearNTear>();
			if (Object.op_Implicit((Object)(object)component) && ((MonoBehaviour)component).IsInvoking("ResetHighlight"))
			{
				return;
			}
			object targetId = ((Object)gameObject).GetInstanceID();
			IDictionary dictionary = QueuedProperties.Read(MaterialMan.instance, targetId);
			if (dictionary != null && (dictionary.Contains(TintKey) || dictionary.Contains(EmissionKey)))
			{
				return;
			}
			List<Renderer> list = new List<Renderer>();
			((Component)container).GetComponentsInChildren<Renderer>(true, list);
			list.RemoveAll((Renderer renderer) => !(renderer is MeshRenderer) && !(renderer is SkinnedMeshRenderer));
			if (list.Count == 0 || list.Count > 32)
			{
				return;
			}
			foreach (Renderer item in list)
			{
				if (Object.op_Implicit((Object)(object)item))
				{
					item.GetPropertyBlock(Scratch);
					if (Scratch.HasColor(ShaderProps._Color) || Scratch.HasColor(ShaderProps._EmissionColor))
					{
						return;
					}
				}
			}
			if (Active.Count == 64)
			{
				RemoveAt(0);
			}
			Entry entry2 = new Entry(gameObject, targetId, MaterialMan.instance, list, preview, unscaledTime, duration);
			Active.Add(entry2);
			Apply(entry2, unscaledTime);
		}

		internal static void Tick()
		{
			float unscaledTime = Time.unscaledTime;
			for (int num = Active.Count - 1; num >= 0; num--)
			{
				Entry entry = Active[num];
				if (!Object.op_Implicit((Object)(object)entry.Target) || !Object.op_Implicit((Object)(object)entry.Manager) || unscaledTime >= entry.Until)
				{
					RemoveAt(num);
				}
				else if (!(unscaledTime < entry.NextUpdate))
				{
					if (!OwnsQueuedColors(entry) || (Time.frameCount > entry.LastAppliedFrame + 1 && !StillOwnsColors(entry)))
					{
						RemoveAt(num);
					}
					else
					{
						Apply(entry, unscaledTime);
					}
				}
			}
		}

		internal static void Clear()
		{
			for (int num = Active.Count - 1; num >= 0; num--)
			{
				RemoveAt(num);
			}
		}

		internal static void YieldToNative(GameObject target)
		{
			for (int num = Active.Count - 1; num >= 0; num--)
			{
				if ((Object)(object)Active[num].Target == (Object)(object)target)
				{
					RemoveAt(num);
				}
			}
		}

		private static void Apply(Entry pulse, float now)
		{
			//IL_004c: 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)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: 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)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Clamp01((now - pulse.Started) / pulse.Duration);
			float num2 = (0.65f + 0.35f * Mathf.Sin(num * (float)Math.PI)) * (1f - 0.4f * num);
			pulse.Tint = (pulse.Preview ? PreviewColor : ReceivedColor);
			pulse.Emission = pulse.Tint * num2;
			pulse.Manager.SetValue<Color>(pulse.Target, ShaderProps._Color, pulse.Tint, false);
			pulse.Manager.SetValue<Color>(pulse.Target, ShaderProps._EmissionColor, pulse.Emission, false);
			pulse.NextUpdate = now + 0.1f;
			pulse.LastAppliedFrame = Time.frameCount;
		}

		private static bool StillOwnsColors(Entry pulse)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			foreach (Renderer renderer in pulse.Renderers)
			{
				if (Object.op_Implicit((Object)(object)renderer))
				{
					renderer.GetPropertyBlock(Scratch);
					if (!Same(Scratch, ShaderProps._Color, pulse.Tint) || !Same(Scratch, ShaderProps._EmissionColor, pulse.Emission))
					{
						return false;
					}
				}
			}
			return true;
		}

		private static bool OwnsQueuedColors(Entry pulse)
		{
			//IL_0027: 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)
			IDictionary dictionary = QueuedProperties.Read(pulse.Manager, pulse.TargetId);
			if (dictionary != null && pulse.TintReader.Matches(dictionary[TintKey], pulse.Tint))
			{
				return pulse.EmissionReader.Matches(dictionary[EmissionKey], pulse.Emission);
			}
			return false;
		}

		private static bool Same(MaterialPropertyBlock block, int property, Color expected)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			if (!block.HasColor(property))
			{
				return false;
			}
			return Same(block.GetColor(property), expected);
		}

		private static bool Same(Color actual, Color expected)
		{
			//IL_0000: 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_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			if (Mathf.Abs(actual.r - expected.r) < 0.001f && Mathf.Abs(actual.g - expected.g) < 0.001f && Mathf.Abs(actual.b - expected.b) < 0.001f)
			{
				return Mathf.Abs(actual.a - expected.a) < 0.001f;
			}
			return false;
		}

		private static void RemoveAt(int index)
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: 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)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			Entry entry = Active[index];
			Active.RemoveAt(index);
			if (!Object.op_Implicit((Object)(object)entry.Target) || !Object.op_Implicit((Object)(object)entry.Manager))
			{
				return;
			}
			IDictionary dictionary = QueuedProperties.Read(entry.Manager, entry.TargetId);
			bool flag = dictionary != null && entry.TintReader.Matches(dictionary[TintKey], entry.Tint);
			bool flag2 = dictionary != null && entry.EmissionReader.Matches(dictionary[EmissionKey], entry.Emission);
			foreach (Renderer renderer in entry.Renderers)
			{
				if (Object.op_Implicit((Object)(object)renderer))
				{
					renderer.GetPropertyBlock(Scratch);
					flag &= !Scratch.HasColor(ShaderProps._Color) || Same(Scratch, ShaderProps._Color, entry.Tint);
					flag2 &= !Scratch.HasColor(ShaderProps._EmissionColor) || Same(Scratch, ShaderProps._EmissionColor, entry.Emission);
				}
			}
			if (flag)
			{
				entry.Manager.ResetValue(entry.Target, ShaderProps._Color);
			}
			if (flag2)
			{
				entry.Manager.ResetValue(entry.Target, ShaderProps._EmissionColor);
			}
		}
	}
	[HarmonyPatch(typeof(WearNTear), "Highlight")]
	internal static class DestinationHighlightNativePriorityPatch
	{
		private static void Prefix(WearNTear __instance)
		{
			try
			{
				DestinationHighlight.YieldToNative(((Component)__instance).gameObject);
			}
			catch (Exception ex)
			{
				Plugin.Instance?.Error("Destination highlight cleanup", ex);
			}
		}
	}
	internal static class GameAccess
	{
		private sealed class LoadRecovery
		{
			internal bool Required;
		}

		internal static readonly FieldRef<Container, ZNetView> View = AccessTools.FieldRefAccess<Container, ZNetView>("m_nview");

		internal static readonly FieldRef<ItemDrop, ZNetView> DropView = AccessTools.FieldRefAccess<ItemDrop, ZNetView>("m_nview");

		internal static readonly Func<Container, long, bool> CheckAccess = AccessTools.MethodDelegate<Func<Container, long, bool>>(AccessTools.Method(typeof(Container), "CheckAccess", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Func<Container, bool> NativeLoad = AccessTools.MethodDelegate<Func<Container, bool>>(AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null), (object)null, true);

		private static readonly FieldRef<Container, bool> Loading = AccessTools.FieldRefAccess<Container, bool>("m_loading");

		private static readonly FieldRef<Container, uint> Revision = AccessTools.FieldRefAccess<Container, uint>("m_lastRevision");

		private static readonly FieldRef<ItemDrop, uint> DropRevision = AccessTools.FieldRefAccess<ItemDrop, uint>("m_loadedRevision");

		private static readonly ConditionalWeakTable<Container, LoadRecovery> Recoveries = new ConditionalWeakTable<Container, LoadRecovery>();

		private static readonly ConditionalWeakTable<ItemDrop, LoadRecovery> DropRecoveries = new ConditionalWeakTable<ItemDrop, LoadRecovery>();

		internal static bool Load(Container container)
		{
			LoadRecovery value = Recoveries.GetValue(container, (Container _) => new LoadRecovery());
			if (Loading.Invoke(container))
			{
				value.Required = true;
			}
			if (value.Required)
			{
				InvalidateLoad(container);
			}
			try
			{
				bool num = NativeLoad(container);
				if (num)
				{
					value.Required = false;
				}
				return num;
			}
			catch
			{
				value.Required = true;
				InvalidateLoad(container);
				throw;
			}
		}

		private static void InvalidateLoad(Container container)
		{
			Loading.Invoke(container) = false;
			Revision.Invoke(container) = View.Invoke(container).GetZDO().DataRevision ^ 1;
		}

		internal static void LoadDrop(ItemDrop drop)
		{
			ZNetView val = DropView.Invoke(drop);
			if (!Object.op_Implicit((Object)(object)drop) || !Object.op_Implicit((Object)(object)val) || !val.IsValid())
			{
				throw new InvalidOperationException("Ground item lost its network state before loading.");
			}
			LoadRecovery value = DropRecoveries.GetValue(drop, (ItemDrop _) => new LoadRecovery());
			if (value.Required)
			{
				DropRevision.Invoke(drop) = val.GetZDO().DataRevision ^ 1;
			}
			if (DropRevision.Invoke(drop) == val.GetZDO().DataRevision)
			{
				return;
			}
			ItemData itemData = drop.m_itemData.Clone();
			try
			{
				drop.Load();
				if (!Object.op_Implicit((Object)(object)val) || !val.IsValid())
				{
					throw new InvalidOperationException("Ground item lost its network state while loading.");
				}
				value.Required = false;
			}
			catch
			{
				drop.m_itemData = itemData;
				value.Required = true;
				if (Object.op_Implicit((Object)(object)val) && val.IsValid())
				{
					DropRevision.Invoke(drop) = val.GetZDO().DataRevision ^ 1;
				}
				throw;
			}
		}

		internal static void Validate()
		{
			if (View == null || DropView == null || CheckAccess == null || NativeLoad == null || Loading == null || Revision == null || DropRevision == null)
			{
				throw new MissingMemberException("Valheim inventory APIs are incompatible with SargamAutoStore.");
			}
		}
	}
	internal sealed class HudFeedback
	{
		private struct Entry
		{
			internal string Key;

			internal string Name;

			internal long Amount;
		}

		private const int MaximumNamedTypes = 3;

		private readonly List<Entry> _items = new List<Entry>(3);

		private long _otherUnits;

		private int _sources;

		private int _routes;

		private float _due;

		private float _nextMessage;

		internal void Record(string key, string name, int amount, bool ground, int route, float now)
		{
			if (amount <= 0)
			{
				return;
			}
			if (_sources == 0)
			{
				_due = Math.Max(now + 0.35f, _nextMessage);
			}
			_sources |= (ground ? 1 : 2);
			_routes |= route;
			for (int i = 0; i < _items.Count; i++)
			{
				Entry value = _items[i];
				if (string.Equals(value.Key, key, StringComparison.Ordinal))
				{
					value.Amount += amount;
					_items[i] = value;
					return;
				}
			}
			if (_items.Count < 3)
			{
				_items.Add(new Entry
				{
					Key = key,
					Name = name,
					Amount = amount
				});
			}
			else
			{
				_otherUnits += amount;
			}
		}

		internal bool TryTake(float now, Func<string, string> localize, out string message)
		{
			message = "";
			if (_sources == 0 || now < _due)
			{
				return false;
			}
			StringBuilder stringBuilder = new StringBuilder("SargamAutoStore: Stored ");
			for (int i = 0; i < _items.Count; i++)
			{
				Entry entry = _items[i];
				string text = localize(entry.Name);
				if (string.IsNullOrEmpty(text) || text[0] == '$')
				{
					text = entry.Key;
				}
				if (i > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(entry.Amount).Append(' ').Append(PlainText(text, 36));
			}
			if (_otherUnits > 0)
			{
				stringBuilder.Append(" + ").Append(_otherUnits).Append(" other units");
			}
			stringBuilder.Append((_sources == 1) ? " (ground; " : ((_sources == 2) ? " (inventory; " : " (ground/inventory; "));
			stringBuilder.Append((_routes == 1) ? "existing contents" : ((_routes == 2) ? "matching sign" : ((_routes == 4) ? "new-item chest" : ((_routes == 8) ? "fallback chest" : "multiple routes"))));
			stringBuilder.Append(").");
			message = stringBuilder.ToString();
			Clear();
			_nextMessage = now + 2f;
			return true;
		}

		internal void Clear()
		{
			_items.Clear();
			_otherUnits = 0L;
			_sources = (_routes = 0);
			_due = (_nextMessage = 0f);
		}

		internal static string PlainText(string value, int limit)
		{
			value = value ?? "";
			if (value.Length > limit)
			{
				value = value.Substring(0, limit) + "...";
			}
			return value.Replace('<', '(').Replace('>', ')').Replace('\n', ' ')
				.Replace('\r', ' ')
				.Replace('\t', ' ');
		}
	}
	internal readonly struct InventoryAnalysis
	{
		internal bool HasMatchingType { get; }

		internal bool HasStackSpace { get; }

		internal int Capacity { get; }

		internal InventoryAnalysis(bool hasMatchingType, bool hasStackSpace, int capacity)
		{
			HasMatchingType = hasMatchingType;
			HasStackSpace = hasStackSpace;
			Capacity = capacity;
		}
	}
	internal static class InventoryTransfer
	{
		private readonly struct StackChange
		{
			public readonly ItemData Item;

			public readonly int Before;

			public readonly int Amount;

			public readonly bool BeforePickedUp;

			public readonly bool IncomingPickedUp;

			public StackChange(ItemData item, int amount, bool incomingPickedUp)
			{
				Item = item;
				Before = item.m_stack;
				Amount = amount;
				BeforePickedUp = item.m_pickedUp;
				IncomingPickedUp = incomingPickedUp;
			}
		}

		private sealed class TransferPlan
		{
			private readonly List<ItemData> _items;

			private ItemData[]? _before;

			public readonly List<StackChange> Stacks = new List<StackChange>();

			public readonly List<ItemData> NewStacks = new List<ItemData>();

			public int Amount;

			public TransferPlan(List<ItemData> items)
			{
				_items = items;
			}

			public void Apply()
			{
				_before = _items.ToArray();
				if (_items.Capacity < _items.Count + NewStacks.Count)
				{
					_items.Capacity = _items.Count + NewStacks.Count;
				}
				foreach (StackChange stack in Stacks)
				{
					ItemData item = stack.Item;
					item.m_stack += stack.Amount;
					ItemData item2 = stack.Item;
					item2.m_pickedUp |= stack.IncomingPickedUp;
				}
				_items.AddRange(NewStacks);
			}

			public void Rollback()
			{
				foreach (StackChange stack in Stacks)
				{
					stack.Item.m_stack = stack.Before;
					stack.Item.m_pickedUp = stack.BeforePickedUp;
				}
				if (_before != null)
				{
					_items.Clear();
					_items.AddRange(_before);
				}
			}
		}

		private static readonly FieldRef<Container, ZNetView> ContainerView = AccessTools.FieldRefAccess<Container, ZNetView>("m_nview");

		private static readonly Action<Inventory, bool, bool> Changed = AccessTools.MethodDelegate<Action<Inventory, bool, bool>>(AccessTools.Method(typeof(Inventory), "Changed", new Type[2]
		{
			typeof(bool),
			typeof(bool)
		}, (Type[])null), (object)null, true);

		private static readonly Action<Inventory> RecalculateWeight = AccessTools.MethodDelegate<Action<Inventory>>(AccessTools.Method(typeof(Inventory), "UpdateTotalWeight", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Action<Container> SaveContainer = AccessTools.MethodDelegate<Action<Container>>(AccessTools.Method(typeof(Container), "Save", (Type[])null, (Type[])null), (object)null, true);

		private static bool _transferring;

		private static readonly HashSet<int> CapacityOccupied = new HashSet<int>();

		public static InventoryAnalysis Analyze(Inventory inventory, ItemData item)
		{
			if (inventory == null || item?.m_shared == null)
			{
				return default(InventoryAnalysis);
			}
			int width = 0;
			int cells = 0;
			bool flag = item.m_shared.m_maxStackSize >= 1 && TryGetGrid(inventory, out width, out cells);
			HashSet<int> capacityOccupied = CapacityOccupied;
			if (flag)
			{
				capacityOccupied.Clear();
			}
			bool flag2 = false;
			bool hasStackSpace = false;
			long num = 0L;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem == null)
				{
					continue;
				}
				if (flag)
				{
					MarkOccupied(allItem, width, cells, capacityOccupied);
				}
				if (!flag2 && allItem.m_stack > 0 && SameType(allItem, item))
				{
					flag2 = true;
				}
				if (allItem.m_shared != null && allItem.m_stack < allItem.m_shared.m_maxStackSize && CanStack(allItem, item))
				{
					int num2 = Math.Max(0, allItem.m_shared.m_maxStackSize - allItem.m_stack);
					if (num2 > 0)
					{
						hasStackSpace = true;
					}
					if (flag)
					{
						num += num2;
					}
				}
			}
			if (flag)
			{
				num += (long)(cells - capacityOccupied.Count) * (long)item.m_shared.m_maxStackSize;
				capacityOccupied.Clear();
			}
			return new InventoryAnalysis(flag2, hasStackSpace, (int)(flag ? Math.Min(2147483647L, num) : 0));
		}

		public static bool HasType(Inventory inventory, ItemData item)
		{
			if (inventory == null || item?.m_shared == null)
			{
				return false;
			}
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != null && allItem.m_stack > 0 && SameType(allItem, item))
				{
					return true;
				}
			}
			return false;
		}

		public static bool HasStackSpace(Inventory inventory, ItemData item)
		{
			if (inventory == null || item?.m_shared == null)
			{
				return false;
			}
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != null && CanStack(allItem, item) && allItem.m_stack < allItem.m_shared.m_maxStackSize)
				{
					return true;
				}
			}
			return false;
		}

		public static int Capacity(Inventory inventory, ItemData item)
		{
			if (inventory == null || item?.m_shared == null || item.m_shared.m_maxStackSize < 1)
			{
				return 0;
			}
			if (!TryGetGrid(inventory, out var width, out var cells))
			{
				return 0;
			}
			HashSet<int> capacityOccupied = CapacityOccupied;
			capacityOccupied.Clear();
			long num = 0L;
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem != null)
				{
					MarkOccupied(allItem, width, cells, capacityOccupied);
					if (CanStack(allItem, item))
					{
						num += Math.Max(0, allItem.m_shared.m_maxStackSize - allItem.m_stack);
					}
				}
			}
			num += (long)(cells - capacityOccupied.Count) * (long)item.m_shared.m_maxStackSize;
			int result = (int)Math.Min(2147483647L, num);
			capacityOccupied.Clear();
			return result;
		}

		public static int MoveFromInventory(Inventory source, ItemData item, Container destination, int maxAmount)
		{
			if (_transferring || source == null || item?.m_shared == null || item.m_shared.m_questItem || item.m_stack <= 0 || maxAmount <= 0)
			{
				return 0;
			}
			if (!TryGetOwnedDestination(destination, out Inventory inventory, out ZNetView view) || source == inventory)
			{
				return 0;
			}
			List<ItemData> allItems = source.GetAllItems();
			if (!allItems.Contains(item) || inventory.GetAllItems().Contains(item))
			{
				return 0;
			}
			TransferPlan transferPlan = Plan(inventory, item, maxAmount);
			if (transferPlan.Amount == 0)
			{
				return 0;
			}
			ItemData[] collection = allItems.ToArray();
			int stack = item.m_stack;
			_transferring = true;
			try
			{
				if (!Owns(view) || destination.IsInUse())
				{
					return 0;
				}
				try
				{
					transferPlan.Apply();
					item.m_stack -= transferPlan.Amount;
					if (item.m_stack == 0)
					{
						allItems.Remove(item);
					}
					Changed(source, arg2: false, arg3: false);
					RequireOwned(view);
					Changed(inventory, arg2: false, arg3: false);
					RequireOwned(view);
					SaveContainer(destination);
					RequireOwned(view);
					return transferPlan.Amount;
				}
				catch (Exception failure)
				{
					item.m_stack = stack;
					allItems.Clear();
					allItems.AddRange(collection);
					transferPlan.Rollback();
					RollbackPersistence(failure, source, destination, inventory, view, null, null);
					throw;
				}
			}
			finally
			{
				_transferring = false;
			}
		}

		public static int MoveFromGround(ItemDrop drop, Container destination, int maxAmount)
		{
			if (_transferring || !Object.op_Implicit((Object)(object)drop) || maxAmount <= 0)
			{
				return 0;
			}
			if (!TryGetOwnedDestination(destination, out Inventory inventory, out ZNetView view))
			{
				return 0;
			}
			ZNetView component = ((Component)drop).GetComponent<ZNetView>();
			if (!Owns(component))
			{
				return 0;
			}
			GameAccess.LoadDrop(drop);
			ItemData itemData = drop.m_itemData;
			if (itemData?.m_shared == null || itemData.m_shared.m_questItem || itemData.m_stack <= 0 || inventory.GetAllItems().Contains(itemData))
			{
				return 0;
			}
			TransferPlan transferPlan = Plan(inventory, itemData, maxAmount);
			if (transferPlan.Amount == 0)
			{
				return 0;
			}
			int stack = itemData.m_stack;
			_transferring = true;
			try
			{
				if (!Owns(component) || !Owns(view) || destination.IsInUse())
				{
					return 0;
				}
				try
				{
					transferPlan.Apply();
					itemData.m_stack -= transferPlan.Amount;
					ItemDrop.SaveToZDO(itemData, component.GetZDO(), -1);
					RequireOwned(component);
					RequireOwned(view);
					Changed(inventory, arg2: false, arg3: false);
					RequireOwned(component);
					RequireOwned(view);
					SaveContainer(destination);
					RequireOwned(component);
					RequireOwned(view);
				}
				catch (Exception failure)
				{
					itemData.m_stack = stack;
					transferPlan.Rollback();
					RollbackPersistence(failure, null, destination, inventory, view, itemData, component);
					throw;
				}
				if (itemData.m_stack == 0 && Owns(component))
				{
					try
					{
						component.Destroy();
					}
					catch (Exception ex)
					{
						Debug.LogWarning((object)("[SargamAutoStore] Items were stored, but empty ground-object cleanup failed: " + ex));
					}
				}
				return transferPlan.Amount;
			}
			finally
			{
				_transferring = false;
			}
		}

		private static bool TryGetOwnedDestination(Container destination, out Inventory inventory, out ZNetView view)
		{
			inventory = null;
			view = null;
			if (!Object.op_Implicit((Object)(object)destination))
			{
				return false;
			}
			view = ContainerView.Invoke(destination);
			if (!Owns(view) || destination.IsInUse())
			{
				return false;
			}
			inventory = destination.GetInventory();
			return inventory != null;
		}

		private static bool Owns(ZNetView view)
		{
			if (Object.op_Implicit((Object)(object)view) && view.IsValid())
			{
				return view.IsOwner();
			}
			return false;
		}

		private static void RequireOwned(ZNetView view)
		{
			if (!Owns(view))
			{
				throw new InvalidOperationException("Network ownership changed during an item transfer.");
			}
		}

		private static void RollbackPersistence(Exception failure, Inventory? source, Container destination, Inventory target, ZNetView targetView, ItemData? groundItem, ZNetView? sourceView)
		{
			List<Exception> list = new List<Exception> { failure };
			TryRollback(delegate
			{
				RecalculateWeight(target);
			}, list);
			if (source != null)
			{
				TryRollback(delegate
				{
					RecalculateWeight(source);
				}, list);
			}
			if (Owns(targetView))
			{
				TryRollback(delegate
				{
					SaveContainer(destination);
				}, list);
			}
			else
			{
				list.Add(new InvalidOperationException("Destination rollback could not be persisted after ownership changed."));
			}
			if (groundItem != null && (Object)(object)sourceView != (Object)null)
			{
				if (Owns(sourceView))
				{
					TryRollback(delegate
					{
						ItemDrop.SaveToZDO(groundItem, sourceView.GetZDO(), -1);
					}, list);
				}
				else
				{
					list.Add(new InvalidOperationException("Ground-item rollback could not be persisted after ownership changed."));
				}
			}
			if (list.Count > 1)
			{
				throw new UncertainTransferException(list);
			}
		}

		private static void TryRollback(Action restore, List<Exception> errors)
		{
			try
			{
				restore();
			}
			catch (Exception item)
			{
				errors.Add(item);
			}
		}

		private static bool SameType(ItemData left, ItemData right)
		{
			if (left.m_shared == null || right.m_shared == null || !string.Equals(left.m_shared.m_name, right.m_shared.m_name, StringComparison.Ordinal))
			{
				return false;
			}
			if (Object.op_Implicit((Object)(object)left.m_dropPrefab) && Object.op_Implicit((Object)(object)right.m_dropPrefab))
			{
				return string.Equals(((Object)left.m_dropPrefab).name, ((Object)right.m_dropPrefab).name, StringComparison.Ordinal);
			}
			return true;
		}

		private static bool CanStack(ItemData left, ItemData right)
		{
			//IL_0098: 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)
			if (left == right || left.m_stack <= 0 || left.m_shared == null || left.m_shared.m_maxStackSize <= 1 || ((object)left).GetType() != typeof(ItemData) || ((object)right).GetType() != typeof(ItemData) || !SameType(left, right))
			{
				return false;
			}
			if (Object.op_Implicit((Object)(object)left.m_dropPrefab) != Object.op_Implicit((Object)(object)right.m_dropPrefab) || left.m_shared.m_maxStackSize != right.m_shared.m_maxStackSize || left.m_shared.m_itemType != right.m_shared.m_itemType || left.m_quality != right.m_quality || left.m_variant != right.m_variant || left.m_worldLevel != right.m_worldLevel || left.m_crafterID != right.m_crafterID || !string.Equals(left.m_crafterName, right.m_crafterName, StringComparison.Ordinal) || !left.m_durability.Equals(right.m_durability) || left.m_cheated != right.m_cheated || left.m_equipped != right.m_equipped)
			{
				return false;
			}
			Dictionary<string, string> customData = left.m_customData;
			Dictionary<string, string> customData2 = right.m_customData;
			if (customData == customData2)
			{
				return true;
			}
			if (customData == null || customData2 == null || customData.Count != customData2.Count)
			{
				return false;
			}
			foreach (KeyValuePair<string, string> item in customData)
			{
				if (!customData2.TryGetValue(item.Key, out var value) || !string.Equals(item.Value, value, StringComparison.Ordinal))
				{
					return false;
				}
				if (customData2.Comparer == EqualityComparer<string>.Default || customData2.Comparer == StringComparer.Ordinal)
				{
					continue;
				}
				bool flag = false;
				foreach (string key in customData2.Keys)
				{
					if (string.Equals(key, item.Key, StringComparison.Ordinal))
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					return false;
				}
			}
			return true;
		}

		private static bool TryGetGrid(Inventory inventory, out int width, out int cells)
		{
			width = inventory.GetWidth();
			int height = inventory.GetHeight();
			long num = (long)width * (long)height;
			cells = (int)((num > 0 && num <= int.MaxValue) ? num : 0);
			if (width > 0 && height > 0)
			{
				return cells > 0;
			}
			return false;
		}

		private static void MarkOccupied(ItemData item, int width, int cells, HashSet<int> occupied)
		{
			int x = item.m_gridPos.x;
			int y = item.m_gridPos.y;
			if (x >= 0 && x < width && y >= 0 && y < cells / width)
			{
				occupied.Add(y * width + x);
			}
		}

		private static TransferPlan Plan(Inventory inventory, ItemData item, int maxAmount)
		{
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			TransferPlan transferPlan = new TransferPlan(inventory.GetAllItems());
			if (item.m_shared.m_maxStackSize < 1 || !TryGetGrid(inventory, out var width, out var cells))
			{
				return transferPlan;
			}
			int num = Math.Min(item.m_stack, maxAmount);
			HashSet<int> hashSet = new HashSet<int>();
			foreach (ItemData allItem in inventory.GetAllItems())
			{
				if (allItem == null)
				{
					continue;
				}
				MarkOccupied(allItem, width, cells, hashSet);
				if (num > 0 && CanStack(allItem, item))
				{
					int num2 = Math.Min(num, Math.Max(0, allItem.m_shared.m_maxStackSize - allItem.m_stack));
					if (num2 > 0)
					{
						transferPlan.Stacks.Add(new StackChange(allItem, num2, item.m_pickedUp));
						transferPlan.Amount += num2;
						num -= num2;
					}
				}
			}
			int num3 = 0;
			while (num > 0 && num3 < cells)
			{
				if (!hashSet.Contains(num3))
				{
					int num4 = Math.Min(num, item.m_shared.m_maxStackSize);
					ItemData val = item.Clone();
					val.m_stack = num4;
					val.m_gridPos = new Vector2i(num3 % width, num3 / width);
					transferPlan.NewStacks.Add(val);
					transferPlan.Amount += num4;
					num -= num4;
				}
				num3++;
			}
			return transferPlan;
		}
	}
	internal sealed class UncertainTransferException : AggregateException
	{
		public UncertainTransferException(IEnumerable<Exception> failures)
			: base("Transfer failed; in-memory changes were restored, but rollback persistence was incomplete. Stop storage until state is reloaded.", failures)
		{
		}
	}
	internal static class ItemProtection
	{
		private static readonly HashSet<string> Blocked = new HashSet<string>(StringComparer.Ordinal);

		private static string? _cached;

		internal static bool IsBlocked(ItemData item)
		{
			Plugin instance = Plugin.Instance;
			if (!Object.op_Implicit((Object)(object)instance) || (Object)(object)item?.m_dropPrefab == (Object)null)
			{
				return false;
			}
			Refresh(instance.Options.ExcludedPrefabs.Value);
			return Blocked.Contains(((Object)item.m_dropPrefab).name);
		}

		private static void Refresh(string value)
		{
			if (value == _cached)
			{
				return;
			}
			Blocked.Clear();
			string[] array = value.Split(new char[1] { ',' });
			foreach (string text in array)
			{
				if (text.Trim().Length > 0)
				{
					Blocked.Add(text.Trim());
				}
			}
			_cached = value;
		}

		internal static void Toggle(ItemData item)
		{
			if (!((Object)(object)item?.m_dropPrefab == (Object)null))
			{
				Set(((Object)item.m_dropPrefab).name, !IsBlocked(item));
			}
		}

		private static void Set(string prefab, bool blocked)
		{
			Plugin instance = Plugin.Instance;
			if (Object.op_Implicit((Object)(object)instance))
			{
				Refresh(instance.Options.ExcludedPrefabs.Value);
				HashSet<string> hashSet = new HashSet<string>(Blocked, StringComparer.Ordinal);
				if (blocked)
				{
					hashSet.Add(prefab);
				}
				else
				{
					hashSet.Remove(prefab);
				}
				List<string> list = new List<string>(hashSet);
				list.Sort(StringComparer.Ordinal);
				try
				{
					instance.Options.ExcludedPrefabs.Value = string.Join(",", list);
					((BaseUnityPlugin)instance).Config.Save();
				}
				finally
				{
					_cached = null;
					Refresh(instance.Options.ExcludedPrefabs.Value);
					instance.Runner?.Reload();
				}
				instance.Tell("SargamAutoStore: " + prefab + " " + (blocked ? "blocked from all transfers" : "removed from the type blocklist") + ". Ctrl+Alt+click toggles this block.");
			}
		}

		internal static string ApplyQuery(string query, bool blocked)
		{
			query = query.Trim();
			if (query.Length == 0)
			{
				return "Use /sas block <item name/prefab>, /sas unblock <name>, or Ctrl+Alt+click an item in your inventory.";
			}
			if (!Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				return "Enter a world to select an item.";
			}
			foreach (GameObject item in ObjectDB.instance.m_items)
			{
				if (Object.op_Implicit((Object)(object)item) && Object.op_Implicit((Object)(object)item.GetComponent<ItemDrop>()) && string.Equals(((Object)item).name, query, StringComparison.OrdinalIgnoreCase))
				{
					Set(((Object)item).name, blocked);
					return "Preference saved.";
				}
			}
			string text = null;
			foreach (GameObject item2 in ObjectDB.instance.m_items)
			{
				if (!Object.op_Implicit((Object)(object)item2))
				{
					continue;
				}
				ItemDrop component = item2.GetComponent<ItemDrop>();
				if (!Object.op_Implicit((Object)(object)component) || component.m_itemData?.m_shared == null)
				{
					continue;
				}
				string name = component.m_itemData.m_shared.m_name;
				string b = Localization.instance.Localize(name);
				if (string.Equals(query, name, StringComparison.OrdinalIgnoreCase) || string.Equals(query, b, StringComparison.OrdinalIgnoreCase))
				{
					if (text != null && text != ((Object)item2).name)
					{
						return "Ambiguous name: use the prefab ID or Ctrl+Alt+click the item.";
					}
					text = ((Object)item2).name;
				}
			}
			if (text == null)
			{
				return "Item not found. Use its exact name in your game language, the prefab ID, or Ctrl+Alt+click the item.";
			}
			Set(text, blocked);
			return "Preference saved.";
		}

		internal static string List()
		{
			Plugin instance = Plugin.Instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				return "SargamAutoStore unavailable.";
			}
			return "Blocked types: " + (string.IsNullOrWhiteSpace(instance.Options.ExcludedPrefabs.Value) ? "none" : instance.Options.ExcludedPrefabs.Value) + ". Keep food/potions in inventory: " + instance.Options.ProtectConsumables.Value + ".";
		}
	}
	[HarmonyPatch(typeof(InventoryGui), "OnSelectedItem")]
	internal static class ProtectItemClickPatch
	{
		private static readonly FieldRef<InventoryGui, GameObject> DragObject = AccessTools.FieldRefAccess<InventoryGui, GameObject>("m_dragGo");

		private static bool Prefix(InventoryGui __instance, InventoryGrid grid, ItemData item)
		{
			if ((!Input.GetKey((KeyCode)306) && !Input.GetKey((KeyCode)305)) || (!Input.GetKey((KeyCode)308) && !Input.GetKey((KeyCode)307)))
			{
				return true;
			}
			if (!Object.op_Implicit((Object)(object)Player.m_localPlayer) || grid.GetInventory() != ((Humanoid)Player.m_localPlayer).GetInventory())
			{
				return true;
			}
			if (Object.op_Implicit((Object)(object)DragObject.Invoke(__instance)))
			{
				return false;
			}
			if (item == null)
			{
				return true;
			}
			try
			{
				ItemProtection.Toggle(item);
			}
			catch (Exception ex)
			{
				Plugin.Instance?.Error("Item protection preference failed", ex);
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(ItemData), "GetTooltip", new Type[]
	{
		typeof(ItemData),
		typeof(int),
		typeof(bool),
		typeof(float),
		typeof(int),
		typeof(bool)
	})]
	internal static class ProtectedItemTooltipPatch
	{
		private static void Postfix(ItemData item, bool crafting, bool appending, ref string __result)
		{
			if (!(crafting || appending) && Object.op_Implicit((Object)(object)Plugin.Instance))
			{
				if (ItemProtection.IsBlocked(item))
				{
					__result += "\n<color=orange>SargamAutoStore: BLOCKED (all stacks of this type)</color>\nCtrl+Alt+click in inventory to unblock.";
				}
				else if (Plugin.Instance.Options.ProtectConsumables.Value && ProtectionRules.IsConsumable(item))
				{
					__result += "\n<color=orange>SargamAutoStore: food/potion kept in inventory.</color>";
				}
				else
				{
					__result += "\nSargamAutoStore: Ctrl+Alt+click in inventory to block this type.";
				}
			}
		}
	}
	internal static class NewItemChests
	{
		internal const string MarkerKey = "sargam_autostore_newitems";

		private static readonly int MarkerHash = StringExtensionMethods.GetStableHashCode("sargam_autostore_newitems");

		private const float MaximumMarkDistance = 8f;

		internal static bool IsMarked(Container chest)
		{
			if (!Object.op_Implicit((Object)(object)chest) || !Coordinator.IsSupported(chest))
			{
				return false;
			}
			ZNetView val = GameAccess.View.Invoke(chest);
			if (Object.op_Implicit((Object)(object)val) && val.IsValid())
			{
				return val.GetZDO().GetBool(MarkerHash, false);
			}
			return false;
		}

		internal static bool TryCommand(string operation, out string message)
		{
			//IL_00ec: 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_0101: Unknown result type (might be due to invalid IL or missing references)
			operation = operation?.ToLowerInvariant() ?? "";
			if (operation != "add" && operation != "remove" && operation != "list" && operation != "toggle")
			{
				message = "Use /sas newchest add, remove, toggle, or list. Aim at a chest to mark or unmark it.";
				return false;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || !Object.op_Implicit((Object)(object)ZNet.instance) || !Object.op_Implicit((Object)(object)ZNetScene.instance) || ((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting())
			{
				message = "Enter a world with an active character to configure chests for new item types.";
				return false;
			}
			if (operation == "list")
			{
				message = ListLoaded(localPlayer);
				return true;
			}
			GameObject hoverObject = ((Humanoid)localPlayer).GetHoverObject();
			Container val = (Object.op_Implicit((Object)(object)hoverObject) ? hoverObject.GetComponentInParent<Container>() : null);
			if ((Object)(object)val == (Object)null || !Object.op_Implicit((Object)(object)val) || !Coordinator.IsSupported(val))
			{
				message = "Aim at a stationary player-built chest. Tombstones, boats, carts, and world-generated chests cannot be marked.";
				return false;
			}
			Vector3 val2 = ((Component)val).transform.position - ((Component)localPlayer).transform.position;
			if (((Vector3)(ref val2)).sqrMagnitude > 64f)
			{
				message = "Move closer and aim at the chest again before marking or unmarking it.";
				return false;
			}
			ZNetView val3 = GameAccess.View.Invoke(val);
			if (!Object.op_Implicit((Object)(object)val3) || !val3.IsValid())
			{
				message = "The chest is not ready yet. Wait and try again.";
				return false;
			}
			if (!HasAccess(val, localPlayer))
			{
				message = "You do not have access to this chest; privacy and area protection are respected.";
				return false;
			}
			if (val.IsInUse() || val3.GetZDO().GetInt(ZDOVars.s_inUse, 0) != 0)
			{
				message = "Close the chest and wait until it is free before marking or unmarking it.";
				return false;
			}
			if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val3) || !val3.IsValid() || !val3.IsOwner())
			{
				message = "Open and close the chest normally to obtain ownership, then aim at it and repeat the command.";
				return false;
			}
			ZDO zDO = val3.GetZDO();
			bool flag = ((operation == "toggle") ? (!zDO.GetBool(MarkerHash, false)) : (operation == "add"));
			if (zDO.GetBool(MarkerHash, false) != flag)
			{
				zDO.Set(MarkerHash, flag);
			}
			if (zDO.GetBool(MarkerHash, false) != flag)
			{
				message = "Could not confirm the change to this chest. Try again.";
				return false;
			}
			message = (flag ? "Chest marked in this world for new item types. Other marked chests were preserved; this command did not move any items." : "Mark removed from this chest. Other marked chests were preserved; this command did not move any items.");
			return true;
		}

		private static bool HasAccess(Container chest, Player player)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			if (GameAccess.CheckAccess(chest, player.GetPlayerID()))
			{
				return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, false);
			}
			return false;
		}

		private static string ListLoaded(Player player)
		{
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			List<Container> list = new List<Container>();
			Container[] array = Object.FindObjectsByType<Container>((FindObjectsSortMode)0);
			foreach (Container val in array)
			{
				if (IsMarked(val) && HasAccess(val, player))
				{
					list.Add(val);
				}
			}
			list.Sort(delegate(Container left, Container right)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//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)
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_0031: Unknown result type (might be due to invalid IL or missing references)
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: Unknown result type (might be due to invalid IL or missing references)
				//IL_004b: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val4 = ((Component)left).transform.position - ((Component)player).transform.position;
				float sqrMagnitude = ((Vector3)(ref val4)).sqrMagnitude;
				val4 = ((Component)right).transform.position - ((Component)player).transform.position;
				return sqrMagnitude.CompareTo(((Vector3)(ref val4)).sqrMagnitude);
			});
			StringBuilder stringBuilder = new StringBuilder("Loaded, accessible marked chests in this world: ").Append(list.Count).Append('.');
			GameObject hoverObject = ((Humanoid)player).GetHoverObject();
			Container val2 = (Object.op_Implicit((Object)(object)hoverObject) ? hoverObject.GetComponentInParent<Container>() : null);
			for (int num = 0; num < list.Count; num++)
			{
				Container val3 = list[num];
				string value = Localization.instance.Localize(val3.GetHoverName());
				float num2 = Vector3.Distance(((Component)val3).transform.position, ((Component)player).transform.position);
				stringBuilder.Append('\n').Append(num + 1).Append(") ")
					.Append(value)
					.Append(" — ")
					.Append(num2.ToString("F1"))
					.Append(" m");
				if ((Object)(object)val3 == (Object)(object)val2)
				{
					stringBuilder.Append(" [targeted chest]");
				}
			}
			stringBuilder.Append("\nChests outside the loaded area are not listed; their marks remain in the world.");
			return stringBuilder.ToString();
		}
	}
	internal sealed class Ownership
	{
		private sealed class Pending
		{
			internal int Nonce;

			internal long Owner;

			internal float Sent;

			internal float RetryAfter;

			internal bool Answered;

			internal bool Granted;
		}

		private const string Request = "SargamAutoStore_Request_v1";

		private const string Reply = "SargamAutoStore_Reply_v1";

		private readonly Settings _settings;

		private readonly Plugin _plugin;

		private readonly Dictionary<Container, Pending> _pending = new Dictionary<Container, Pending>();

		private readonly Dictionary<Container, float> _lastGrant = new Dictionary<Container, float>();

		private static readonly Func<PrivateArea, Vector3, float, bool> Inside = AccessTools.MethodDelegate<Func<PrivateArea, Vector3, float, bool>>(AccessTools.Method(typeof(PrivateArea), "IsInside", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Func<PrivateArea, bool> Enabled = AccessTools.MethodDelegate<Func<PrivateArea, bool>>(AccessTools.Method(typeof(PrivateArea), "IsEnabled", (Type[])null, (Type[])null), (object)null, true);

		private static readonly Func<PrivateArea, List<KeyValuePair<long, string>>> Permitted = AccessTools.MethodDelegate<Func<PrivateArea, List<KeyValuePair<long, string>>>>(AccessTools.Method(typeof(PrivateArea), "GetPermittedPlayers", (Type[])null, (Type[])null), (object)null, true);

		private readonly HashSet<ZNetView> _views = new HashSet<ZNetView>();

		private int _sequence;

		private float _nextRequest;

		internal long Requests;

		internal long Timeouts;

		internal long Denied;

		private static List<PrivateArea> Areas => AccessTools.StaticFieldRefAccess<List<PrivateArea>>(typeof(PrivateArea), "m_allAreas");

		internal Ownership(Plugin plugin, Settings settings)
		{
			_plugin = plugin;
			_settings = settings;
		}

		internal void Register(Container chest)
		{
			ZNetView val = GameAccess.View.Invoke(chest);
			val.Unregister("SargamAutoStore_Request_v1");
			val.Unregister("SargamAutoStore_Reply_v1");
			val.Register<ZDOID, int>("SargamAutoStore_Request_v1", (Action<long, ZDOID, int>)delegate(long sender, ZDOID character, int nonce)
			{
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				OnRequest(chest, sender, character, nonce);
			});
			val.Register<int, bool>("SargamAutoStore_Reply_v1", (Action<long, int, bool>)delegate(long sender, int nonce, bool granted)
			{
				OnReply(chest, sender, nonce, granted);
			});
			_views.Add(val);
		}

		internal bool Ready(Container chest, float now)
		{
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = GameAccess.View.Invoke(chest);
			if (!Object.op_Implicit((Object)(object)val) || !val.IsValid())
			{
				return false;
			}
			if (val.IsOwner())
			{
				if (_pending.TryGetValue(chest, out Pending value) && !value.Answered && now - value.Sent < 2f)
				{
					return false;
				}
				if (_pending.Remove(chest))
				{
					_lastGrant[chest] = now;
				}
				GameAccess.Load(chest);
				return true;
			}
			if (!_settings.CooperativeOwnership.Value || !Object.op_Implicit((Object)(object)Player.m_localPlayer) || !val.HasOwner())
			{
				return false;
			}
			if (_pending.TryGetValue(chest, out Pending value2))
			{
				if (!value2.Answered && now - value2.Sent >= 2f)
				{
					value2.Answered = true;
					Timeouts++;
				}
				if (now < value2.RetryAfter)
				{
					return false;
				}
			}
			if (now < _nextRequest)
			{
				return false;
			}
			_nextRequest = now + 0.25f;
			Pending pending = new Pending
			{
				Nonce = ++_sequence,
				Owner = val.GetZDO().GetOwner(),
				Sent = now,
				RetryAfter = now + 3f
			};
			_pending[chest] = pending;
			Requests++;
			val.InvokeRPC(pending.Owner, "SargamAutoStore_Request_v1", new object[2]
			{
				((Character)Player.m_localPlayer).GetZDOID(),
				pending.Nonce
			});
			return false;
		}

		private void OnReply(Container chest, long sender, int nonce, bool granted)
		{
			if (_pending.TryGetValue(chest, out Pending value) && value.Nonce == nonce && value.Owner == sender && !(Time.realtimeSinceStartup - value.Sent > 2f))
			{
				value.Answered = true;
				value.Granted = granted;
				if (granted)
				{
					_lastGrant[chest] = Time.realtimeSinceStartup;
				}
				if (!granted)
				{
					Denied++;
				}
			}
		}

		private void OnRequest(Container chest, long sender, ZDOID character, int nonce)
		{
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: 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_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!_settings.Enabled.Value || !_settings.CooperativeOwnership.Value || !Object.op_Implicit((Object)(object)chest) || !Coordinator.IsSupported(chest))
				{
					return;
				}
				ZNetView val = GameAccess.View.Invoke(chest);
				if (!Object.op_Implicit((Object)(object)val) || !val.IsValid() || !val.IsOwner())
				{
					return;
				}
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (_lastGrant.TryGetValue(chest, out var value) && realtimeSinceStartup - value < 1f)
				{
					return;
				}
				_lastGrant[chest] = realtimeSinceStartup;
				GameObject val2 = (Object.op_Implicit((Object)(object)ZNetScene.instance) ? ZNetScene.instance.FindInstance(character) : null);
				Player val3 = (Object.op_Implicit((Object)(object)val2) ? val2.GetComponent<Player>() : null);
				ZNetView val4 = (Object.op_Implicit((Object)(object)val2) ? val2.GetComponent<ZNetView>() : null);
				int num;
				if (Object.op_Implicit((Object)(object)val3) && Object.op_Implicit((Object)(object)val4) && val4.IsValid() && val4.GetZDO().GetOwner() == sender && !((Character)val3).IsDead() && !((Character)val3).IsTeleporting())
				{
					Vector3 val5 = ((Component)val3).transform.position - ((Component)chest).transform.position;
					if (((Vector3)(ref val5)).sqrMagnitude <= 9216f && !chest.IsInUse() && val.GetZDO().GetInt(ZDOVars.s_inUse, 0) == 0 && GameAccess.CheckAccess(chest, val3.GetPlayerID()))
					{
						num = (WardAllows(((Component)chest).transform.position, val3.GetPlayerID()) ? 1 : 0);
						goto IL_01a5;
					}
				}
				num = 0;
				goto IL_01a5;
				IL_01a5:
				bool flag = (byte)num != 0;
				if (flag)
				{
					ZDOMan.instance.ForceSendZDO(sender, val.GetZDO().m_uid);
					val.GetZDO().SetOwner(sender);
				}
				val.InvokeRPC(sender, "SargamAutoStore_Reply_v1", new object[2] { nonce, flag });
			}
			catch (Exception ex)
			{
				_plugin.Error("Ownership request rejected after error", ex);
			}
		}

		private static bool WardAllows(Vector3 point, long playerId)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			foreach (PrivateArea area in Areas)
			{
				if (!Object.op_Implicit((Object)(object)area) || !Enabled(area) || !Inside(area, point, 0f))
				{
					continue;
				}
				flag = true;
				Piece component = ((Component)area).GetComponent<Piece>();
				if (Object.op_Implicit((Object)(object)component) && component.GetCreator() == playerId)
				{
					return true;
				}
				foreach (KeyValuePair<long, string> item in Permitted(area))
				{
					if (item.Key == playerId)
					{
						return true;
					}
				}
			}
			return !flag;
		}

		internal void Remove(Container chest)
		{
			_pending.Remove(chest);
			_lastGrant.Remove(chest);
			if (chest != null)
			{
				ZNetView val = GameAccess.View.Invoke(chest);
				if (val != null && _views.Remove(val) && Object.op_Implicit((Object)(object)val))
				{
					val.Unregister("SargamAutoStore_Request_v1");
					val.Unregister("SargamAutoStore_Reply_v1");
				}
			}
		}

		internal void Clear()
		{
			foreach (ZNetView view in _views)
			{
				if (Object.op_Implicit((Object)(object)view))
				{
					view.Unregister("SargamAutoStore_Request_v1");
					view.Unregister("SargamAutoStore_Reply_v1");
				}
			}
			_views.Clear();
			_pending.Clear();
			_lastGrant.Clear();
			_nextRequest = 0f;
		}
	}
	[BepInPlugin("com.sargam.valheim.autostore", "SargamAutoStore", "1.0.0")]
	[BepInIncompatibility("Azumatt.AzuAutoStore")]
	[BepInIncompatibility("aedenthorn.AutoStore")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.sargam.valheim.autostore";

		public const string Version = "1.0.0";

		internal static Plugin? Instance;

		internal Coordinator? Runner;

		internal Settings Options;

		private Harmony? _harmony;

		private RangeDebug? _rangeDebug;

		private float _lastError = -100f;

		private string _lastReceipt = "No transfers this session.";

		private bool _worldNotified;

		private readonly HudFeedback _hudFeedback = new HudFeedback();

		private Player? _feedbackPlayer;

		private ZNetScene? _feedbackScene;

		private string? _startupError;

		private void Awake()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			try
			{
				Options = new Settings(((BaseUnityPlugin)this).Config);
				GameAccess.Validate();
				Runner = new Coordinator(this, Options);
				_harmony = new Harmony("com.sargam.valheim.autostore");
				_harmony.PatchAll(typeof(Plugin).Assembly);
				new ConsoleCommand("sas", "SargamAutoStore: /sas help in normal chat lists commands and shortcuts.", (ConsoleEvent)delegate(ConsoleEventArgs args)
				{
					Command(args);
				}, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
				try
				{
					_rangeDebug = new RangeDebug(this);
					_rangeDebug.Register();
				}
				catch (Exception ex)
				{
					_rangeDebug?.Clear();
					_rangeDebug = null;
					Error("Range debug unavailable; storage continues", ex);
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)("SargamAutoStore 1.0.0 ready. auto=" + Options.Automatic.Value + "; F8 = force ground; F9 = inventory; Alt+N = toggle new-item chest; /sas help in chat."));
			}
			catch (Exception ex2)
			{
				Harmony? harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
				Runner = null;
				((BaseUnityPlugin)this).Logger.LogError((object)("Initialization failed; transfers disabled. " + ex2));
				_startupError = "SargamAutoStore error: Initialization failed; transfers disabled. " + HudFeedback.PlainText(ex2.GetBaseException().Message, 100) + " See BepInEx log.";
			}
		}

		private void Update()
		{
			//IL_0123: 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_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			if (_startupError != null && HasFeedbackWorld())
			{
				string startupError = _startupError;
				_startupError = null;
				try
				{
					if (Options == null || Options.ShowHudErrors.Value)
					{
						((Character)Player.m_localPlayer).Message((MessageType)1, startupError, 0, (Sprite)null, true);
					}
				}
				catch
				{
				}
			}
			_rangeDebug?.Tick();
			if (Runner == null || Options == null)
			{
				return;
			}
			try
			{
				TickFeedback();
				switch (RuntimeReadiness.Evaluate())
				{
				case RuntimeState.NoPlayableWorld:
					_worldNotified = false;
					Runner.CancelManual();
					ClearFeedback();
					return;
				case RuntimeState.Paused:
					return;
				}
				if (!_worldNotified)
				{
					_worldNotified = true;
					if (!Options.Automatic.Value)
					{
						Tell("Automatic collection is PAUSED. Use /sas resume in chat to resume.");
					}
				}
				if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && !Console.IsVisible() && !TextInput.IsVisible() && !Menu.IsVisible() && (!Object.op_Implicit((Object)(object)Chat.instance) || !Chat.instance.HasFocus()))
				{
					KeyboardShortcut value = Options.GroundKey.Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						Runner.ForceGround();
					}
					value = Options.InventoryKey.Value;
					if (((KeyboardShortcut)(ref value)).IsDown())
					{
						Runner.StoreInventory(all: false);
					}
					if (!InventoryGui.IsVisible())
					{
						value = Options.NewChestKey.Value;
						if (((KeyboardShortcut)(ref value)).IsDown())
						{
							NewItemChests.TryCommand("toggle", out string message);
							Tell(message);
						}
					}
				}
				Runner.Tick();
			}
			catch (Exception ex)
			{
				Error("Scheduler recovered on next frame", ex);
			}
		}

		private void Command(ConsoleEventArgs args)
		{
			try
			{
				string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "status");
				if (text == "help")
				{
					Help(args);
					return;
				}
				if (Runner == null)
				{
					args.Context.AddString("SargamAutoStore initialization failed. See BepInEx log.");
					return;
				}
				switch (text)
				{
				case "ground":
					Runner.ForceGround();
					break;
				case "inventory":
					Runner.StoreInventory(args.Length > 2 && args[2] == "all");
					break;
				case "pause":
					Options.Automatic.Value = false;
					((BaseUnityPlugin)this).Config.Save();
					Runner.CancelManual();
					Tell("SargamAutoStore: automatic collection PAUSED; use /sas resume in chat.");
					break;
				case "resume":
					Options.Automatic.Value = true;
					((BaseUnityPlugin)this).Config.Save();
					Tell("SargamAutoStore: automatic collection ACTIVE; routing: " + (Options.UseSigns.Value ? "contents, then matching sign text, then designated new-item chests" : "contents, then designated new-item chests") + "; item protections still apply.");
					break;
				case "reload":
					((BaseUnityPlugin)this).Config.Reload();
					Runner.Reload();
					break;
				case "newchest":
				{
					NewItemChests.TryCommand((args.Length == 3) ? args[2] : "", out string message);
					args.Context.AddString(message);
					((BaseUnityPlugin)this).Logger.LogInfo((object)message);
					return;
				}
				case "mode":
				{
					string text2 = ((args.Length == 3) ? args[2].ToLowerInvariant() : "");
					if (text2 != "content" && text2 != "signs" && text2 != "hybrid")
					{
						args.Context.AddString("Use /sas mode content or /sas mode hybrid in chat. Configuration was not changed.");
						return;
					}
					Options.UseSigns.Value = text2 != "content";
					((BaseUnityPlugin)this).Config.Save();
					Runner.Reload();
					Tell("SargamAutoStore: routing: " + (Options.UseSigns.Value ? "contents, then matching sign text" : "chest contents") + " ACTIVE and saved; item protections still apply.");
					break;
				}
				case "block":
				case "unblock":
				{
					List<string> list = new List<string>();
					for (int i = 2; i < args.Length; i++)
					{
						list.Add(args[i]);
					}
					args.Context.AddString(ItemProtection.ApplyQuery(string.Join(" ", list), text == "block"));
					return;
				}
				case "blocked":
					args.Context.AddString(ItemProtection.List());
					return;
				default:
					args.Context.AddString("Unknown SargamAutoStore command. Use /sas help in chat.");
					return;
				case "status":
					break;
				}
				args.Context.AddString(Runner.Status());
				args.Context.AddString(_lastReceipt);
				((BaseUnityPlugin)this).Logger.LogInfo((object)(Runner.Status() + " Last transfer: " + _lastReceipt));
			}
			catch (Exception ex)
			{
				Error("Command failed", ex);
				args.Context.AddString("SargamAutoStore command failed; see BepInEx log.");
			}
		}

		private static void Help(ConsoleEventArgs args)
		{
			string[] array;
			if (((args.Length == 3) ? args[2].ToLowerInvariant() : "") == "range")
			{
				array = new string[13]
				{
					"SargamAutoStore ranges — type these in normal chat (Enter):", "/sas_range items <meters> — ground-item radius around you, 1–256 m.", "/sas_range chests <meters> — chest radius around each source, 1–256 m.", "/sas_range signs <meters> — sign radius around a chest, 0.2–8 m.", "/sas_range status — show saved ranges and the selected origin.", "/sas_range debug on | off — show all range guides for 90 seconds or hide them.", "/sas_range origin item — aim at a ground item, then use this command.", "/sas_range origin player — show the inventory/F9 chest-search origin.", "Cyan: ground items. Yellow: chest search. Purple: sign association.", "Guides show 3D distance through walls; item protections, access and routing still apply.",
					"Only loaded items/chests participate. Range changes are saved immediately.", "Decimals accept a dot or comma. Example: /sas_range chests 52", "/sas help — storage commands and shortcuts."
				};
				foreach (string text in array)
				{
					args.Context.AddString(text);
				}
				return;
			}
			array = new string[17]
			{
				"SargamAutoStore — commands for normal chat (Enter); no console required:", "/sas help range — range commands, limits and colored guides.", "/sas ground — collect ground items (F8).", "/sas inventory [all] — store inventory (F9); all also includes the hotbar.", "/sas pause | resume — pause or enable automatic ground collection.", "/sas mode hybrid | content — enable sign routing or use contents/marked chests only.", "/sas newchest add | remove | list — designate chests for new item types.", "Alt+N: close the chest, aim at it, then toggle its new-item designation.", "/sas block <item> | unblock <item> — block/unblock an exact item name or prefab.", "/sas blocked — list blocked types and consumable protection.",
				"Ctrl+Alt+click an inventory item to toggle its type block.", "/sas_range items | chests | signs <meters> — set and visualize a range.", "/sas_range debug on | off — show/hide all range guides.", "/sas_range origin player | item — choose the yellow guide's source.", "/sas_range status — show current ranges.", "/sas status | reload — show diagnostics or reload configuration.", "Equipped/quest items, blocked types and protected consumables remain protected."
			};
			foreach (string text2 in array)
			{
				args.Context.AddString(text2);
			}
		}

		internal void NotifyStored(Container chest, ItemData item, int amount, bool fromGround, bool matchedSign, bool hasExistingType = false, bool isNewItemChest = false)
		{
			if (amount <= 0)
			{
				return;
			}
			try
			{
				ZNetView val = GameAccess.View.Invoke(chest);
				string text = ((Object.op_Implicit((Object)(object)val) && val.IsValid()) ? ((object)Unsafe.As<ZDOID, ZDOID>(ref val.GetZDO().m_uid)/*cast due to .constrained prefix*/).ToString() : "unloaded");
				string text2 = (Object.op_Implicit((Object)(object)item.m_dropPrefab) ? ((Object)item.m_dropPrefab).name : item.m_shared.m_name);
				string text3 = (hasExistingType ? "existing type" : (matchedSign ? "matching sign substring" : (isNewItemChest ? "designated new-item chest" : "legacy unrestricted fallback")));
				_lastReceipt = string.Format("{0} {1} -> chest={2}; source={3}; route={4}", amount, text2, text, fromGround ? "ground" : "inventory", text3);
				if (Options.LogTransfers.Value)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("Stored " + _lastReceipt));
				}
				if (Options.ShowHudTransfers.Value && HasFeedbackWorld())
				{
					_hudFeedback.Record(text2, item.m_shared.m_name, amount, fromGround, hasExistingType ? 1 : (matchedSign ? 2 : (isNewItemChest ? 4 : 8)), Time.realtimeSinceStartup);
				}
				if (Options.HighlightDestination.Value)
				{
					DestinationHighlight.Pulse(chest, Options.HighlightSeconds.Value);
				}
			}
			catch (Exception ex)
			{
				Error("Post-transfer feedback failed; items were already stored", ex);
			}
		}

		internal void Tell(string message)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)message);
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				((Character)Player.m_localPlayer).Message((MessageType)1, message, 0, (Sprite)null, false);
			}
		}

		private void TickFeedback()
		{
			try
			{
				string message;
				if (!Options.ShowHudTransfers.Value || !HasFeedbackWorld())
				{
					_hudFeedback.Clear();
				}
				else if (_hudFeedback.TryTake(Time.realtimeSinceStartup, LocalizeFeedbackItem, out message))
				{
					((Character)Player.m_localPlayer).Message((MessageType)1, message, 0, (Sprite)null, true);
				}
			}
			catch (Exception ex)
			{
				_hudFeedback.Clear();
				Error("Storage notification failed; items were already stored", ex);
			}
			try
			{
				if (Options.HighlightDestination.Value)
				{
					DestinationHighlight.Tick();
				}
				else
				{
					DestinationHighlight.Clear();
				}
			}
			catch (Exception ex2)
			{
				Error("Destination highlight update failed; storage continues", ex2);
				try
				{
					DestinationHighlight.Clear();
				}
				catch (Exception ex3)
				{
					Error("Destination highlight reset failed", ex3);
				}
			}
		}

		internal void ClearFeedback()
		{
			_hudFeedback.Clear();
			_feedbackPlayer = null;
			_feedbackScene = null;
			_rangeDebug?.Clear();
			try
			{
				DestinationHighlight.Clear();
			}
			catch (Exception ex)
			{
				Error("Destination highlight cleanup failed", ex);
			}
			_lastReceipt = "No transfers this session.";
		}

		internal void Error(string context, Exception ex)
		{
			if (Time.realtimeSinceStartup - _lastError < 5f)
			{
				return;
			}
			_lastError = Time.realtimeSinceStartup;
			((BaseUnityPlugin)this).Logger.LogError((object)(context + ": " + ex));
			try
			{
				if (Options != null && Options.ShowHudErrors.Value && HasFeedbackWorld())
				{
					((Character)Player.m_localPlayer).Message((MessageType)1, "SargamAutoStore error: " + HudFeedback.PlainText(context, 80) + ". " + HudFeedback.PlainText(ex.GetBaseException().Message, 100) + " See BepInEx log.", 0, (Sprite)null, true);
				}
			}
			catch
			{
			}
		}

		private bool HasFeedbackWorld()
		{
			if (_feedbackPlayer != Player.m_localPlayer || _feedbackScene != ZNetScene.instance)
			{
				_hudFeedback.Clear();
				_feedbackPlayer = Player.m_localPlayer;
				_feedbackScene = ZNetScene.instance;
			}
			if (Object.op_Implicit((Object)(object)Game.instance) && Object.op_Implicit((Object)(object)ZNet.instance) && Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				return Object.op_Implicit((Object)(object)Player.m_localPlayer);
			}
			return false;
		}

		private static string LocalizeFeedbackItem(string name)
		{
			if (Localization.instance == null)
			{
				return name;
			}
			return Localization.instance.Localize(name);
		}

		private void OnDestroy()
		{
			ClearFeedback();
			Runner?.Reset();
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			Instance = null;
		}

		private void OnGUI()
		{
			_rangeDebug?.Draw();
		}

		private void OnDisable()
		{
			_rangeDebug?.Clear();
			_hudFeedback.Clear();
		}
	}
	[HarmonyPatch(typeof(Container), "Awake")]
	internal static class ContainerAwakePatch
	{
		private static void Postfix(Container __instance)
		{
			try
			{
				Plugin.Instance?.Runner?.Register(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Instance?.Error("Container registration", ex);
			}
		}
	}
	[HarmonyPatch(typeof(Sign), "Awake")]
	internal static class SignAwakePatch
	{
		private static void Postfix(Sign __instance)
		{
			try
			{
				Plugin.Instance?.Runner?.Register(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Instance?.Error("Sign registration", ex);
			}
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "Awake")]
	internal static class DropAwakePatch
	{
		private static void Postfix(ItemDrop __instance)
		{
			try
			{
				Plugin.Instance?.Runner?.Register(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Instance?.Error("Drop registration", ex);
			}
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "OnDestroy")]
	internal static class DropDestroyPatch
	{
		private static void Prefix(ItemDrop __instance)
		{
			Plugin.Instance?.Runner?.Remove(__instance);
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "OnDestroy")]
	internal static class SceneDestroyPatch
	{
		private static void Prefix()
		{
			Plugin.Instance?.ClearFeedback();
			Plugin.Instance?.Runner?.Reset();
		}
	}
	internal sealed class ContainerLifetime : MonoBehaviour
	{
		internal Container? Container;

		private void OnDestroy()
		{
			if (Container != null)
			{
				Plugin.Instance?.Runner?.Remove(Container);
			}
		}
	}
	internal static class ProtectionRules
	{
		internal static bool IsConsumable(ItemData item)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Invalid comparison between Unknown and I4
			if (item?.m_shared != null)
			{
				if ((int)item.m_shared.m_itemType != 2 && !(item.m_shared.m_food > 0f) && !(item.m_shared.m_foodStamina > 0f))
				{
					return item.m_shared.m_foodEitr > 0f;
				}
				return true;
			}
			return false;
		}
	}
	internal sealed class RangeDebug
	{
		private const string Help = "Use /sas_range items|chests|signs <meters>, /sas_range debug on|off, /sas_range origin player|item, or /sas_range status.";

		private static readonly string[] ShaderNames = new string[3] { "Hidden/Internal-Colored", "Unlit/Color", "Sprites/Default" };

		private readonly Plugin _plugin;

		private readonly List<Material> _materials = new List<Material>();

		private Mesh? _mesh;

		private GameObject? _items;

		private GameObject? _chests;

		private GameObject? _signs;

		private Player? _player;

		private ZNetScene? _scene;

		private ItemDrop? _dropOrigin;

		private Container? _signOrigin;

		private bool _visible;

		private float _until;

		private float _nextTick;

		private string _legend = "";

		private GUIStyle? _style;

		private Vector3 _itemsCenter;

		private Vector3 _chestsCenter;

		private Vector3 _signsCenter;

		private float _itemsRadius;

		private float _chestsRadius;

		private float _signsRadius;

		internal RangeDebug(Plugin plugin)
		{
			_plugin = plugin;
		}

		internal void Register()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			new ConsoleCommand("sas_range", "Use /sas_range items|chests|signs <meters>, /sas_range debug on|off, /sas_range origin player|item, or /sas_range status. Changes show range spheres for 90 seconds.", new ConsoleEvent(Command), false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private static bool InWorld()
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				return Object.op_Implicit((Object)(object)ZNet.instance);
			}
			return false;
		}

		internal void Command(ConsoleEventArgs args)
		{
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "status");
				string text2 = ((args.Length == 3) ? args[2].ToLowerInvariant() : "");
				if (text == "debug" && text2 == "off")
				{
					Clear();
					args.Context.AddString("Range debug disabled.");
					return;
				}
				if (!Object.op_Implicit((Object)(object)_plugin) || !((Behaviour)_plugin).isActiveAndEnabled || Plugin.Instance != _plugin)
				{
					throw new InvalidOperationException("SargamAutoStore is disabled; range spheres are unavailable.");
				}
				if (_plugin.Runner == null)
				{
					throw new InvalidOperationException("SargamAutoStore has not initialized; check the log.");
				}
				if (_visible && (!InWorld() || (Object)(object)Player.m_localPlayer != (Object)(object)_player || (Object)(object)ZNetScene.instance != (Object)(object)_scene))
				{
					Clear();
				}
				if (text == "status" && args.Length <= 2)
				{
					args.Context.AddString(Status());
					return;
				}
				if (text == "debug" && args.Length == 3)
				{
					if (text2 != "on")
					{
						throw new ArgumentException("Use /sas_range debug on or off.");
					}
					Show();
					args.Context.AddString("Range spheres for 90 seconds: cyan = items; yellow = chests; purple = signs. " + Status());
					return;
				}
				if (!InWorld())
				{
					throw new InvalidOperationException("Enter a world to adjust or display ranges.");
				}
				if (text == "origin" && args.Length == 3)
				{
					if (text2 == "player")
					{
						_dropOrigin = null;
					}
					else
					{
						if (!(text2 == "item"))
						{
							throw new ArgumentException("Use /sas_range origin player or item.");
						}
						GameObject hoverObject = ((Humanoid)Player.m_localPlayer).GetHoverObject();
						ItemDrop val = (Object.o