Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SargamAutoStore v1.0.0
BepInEx/plugins/SargamAutoStore/SargamAutoStore.Core.dll
Decompiled a day agousing 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
The result has been truncated due to the large size, download it to view full contents!
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