Decompiled source of ZombieLogistics v1.0.6

GraveyardKeeper2.ZombieLogistics.dll

Decompiled 4 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using GK2.SharedStorage;
using HarmonyLib;
using LazyBearTechnology;
using Pathfinding;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("GraveyardKeeper2.ZombieLogistics")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.6.0")]
[assembly: AssemblyInformationalVersion("1.0.6+63749da357e22fa8ab0da836fbcc0603432d13a0")]
[assembly: AssemblyProduct("GraveyardKeeper2.ZombieLogistics")]
[assembly: AssemblyTitle("GraveyardKeeper2.ZombieLogistics")]
[assembly: AssemblyVersion("1.0.6.0")]
namespace GK2.ZombieLogistics;

internal enum GardenerPickupAction
{
	UseVanilla,
	ReleaseOrder
}
internal static class GardenerPickupPolicy
{
	internal static GardenerPickupAction ForResolution(NetworkBuildStatus buildStatus, GardenerSeedResolution resolution)
	{
		if (buildStatus != NetworkBuildStatus.Ready || resolution != GardenerSeedResolution.Resolved)
		{
			return GardenerPickupAction.ReleaseOrder;
		}
		return GardenerPickupAction.UseVanilla;
	}
}
internal static class GardenerSeedInventoryPolicy
{
	internal static int GetRemainingNeed(int required, int carried)
	{
		if (required <= 0)
		{
			return 0;
		}
		if (carried <= 0)
		{
			return required;
		}
		return Math.Max(0, required - carried);
	}

	internal static string SelectCarriedCandidate(IReadOnlyList<string> candidates, IReadOnlyDictionary<string, int> carriedCounts)
	{
		if (candidates == null || carriedCounts == null)
		{
			return null;
		}
		for (int i = 0; i < candidates.Count; i++)
		{
			string text = candidates[i];
			if (!string.IsNullOrEmpty(text) && carriedCounts.TryGetValue(text, out var value) && value > 0)
			{
				return text;
			}
		}
		return null;
	}
}
internal enum GardenerSeedResolution
{
	Resolved,
	Insufficient,
	Failed
}
internal static class GardenerSeedResolver
{
	internal static GardenerSeedResolution TryResolve(IReadOnlyList<string> candidates, int need, Func<string, bool> allowsItem, Func<string, int> totalCount, out string seedItemId)
	{
		seedItemId = null;
		if (candidates == null || candidates.Count == 0)
		{
			return GardenerSeedResolution.Failed;
		}
		bool flag = false;
		try
		{
			for (int i = 0; i < candidates.Count; i++)
			{
				string text = candidates[i];
				if (!string.IsNullOrEmpty(text))
				{
					flag = true;
					if (need <= 0)
					{
						seedItemId = text;
						return GardenerSeedResolution.Resolved;
					}
					if ((allowsItem == null || allowsItem(text)) && (totalCount?.Invoke(text) ?? 0) >= need)
					{
						seedItemId = text;
						return GardenerSeedResolution.Resolved;
					}
				}
			}
			return flag ? GardenerSeedResolution.Insufficient : GardenerSeedResolution.Failed;
		}
		catch (Exception)
		{
			seedItemId = null;
			return GardenerSeedResolution.Failed;
		}
	}
}
internal interface IInventorySource
{
	string ContainerId { get; }

	string ZoneId { get; }

	bool IsCurrentZone { get; }

	float Distance { get; }

	int GetCount(string itemId);

	RemoveItemsResult RemoveItems(string itemId, int count);

	RestoreItemsResult RestoreItems(List<Item> items);

	int Remove(string itemId, int count);

	void Restore(string itemId, int count);
}
internal static class InventoryRemovalAccounting
{
	internal static RemoveItemsResult Resolve(string itemId, int before, int after, List<Item> removed, Exception failure)
	{
		//IL_002e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: Expected O, but got Unknown
		RemoveItemsResult removeItemsResult = new RemoveItemsResult();
		int num = Math.Max(0, before - after);
		if (removed == null)
		{
			removeItemsResult.RemovedCount = num;
			removeItemsResult.Failure = failure;
			if (num > 0)
			{
				removeItemsResult.Items.Add(new Item(itemId, num));
			}
			return removeItemsResult;
		}
		removeItemsResult.Items.AddRange(removed);
		int num2 = 0;
		for (int i = 0; i < removed.Count; i++)
		{
			Item val = removed[i];
			if (val != null && val.Count > 0)
			{
				num2 += val.Count;
			}
		}
		removeItemsResult.RemovedCount = num2;
		if (num2 != num)
		{
			removeItemsResult.Failure = failure ?? new InvalidOperationException("Inventory removal count disagreed with the returned item list: countDelta=" + num + ", listed=" + num2 + ".");
			removeItemsResult.CountUnknown = true;
		}
		else
		{
			removeItemsResult.Failure = failure;
		}
		if (removeItemsResult.Failure != null && removeItemsResult.RemovedCount <= 0)
		{
			removeItemsResult.Items.Clear();
		}
		return removeItemsResult;
	}
}
internal static class InventoryRestore
{
	internal static RestoreItemsResult Apply(IReadOnlyDictionary<string, int> expected, Func<string, int> getCount, Action add)
	{
		RestoreItemsResult restoreItemsResult = new RestoreItemsResult();
		if (expected == null)
		{
			restoreItemsResult.Failure = new ArgumentNullException("expected");
			return restoreItemsResult;
		}
		foreach (KeyValuePair<string, int> item in expected)
		{
			if (!string.IsNullOrEmpty(item.Key) && item.Value > 0)
			{
				restoreItemsResult.ExpectedCount += item.Value;
			}
		}
		if (restoreItemsResult.ExpectedCount <= 0)
		{
			return restoreItemsResult;
		}
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		try
		{
			foreach (KeyValuePair<string, int> item2 in expected)
			{
				if (!string.IsNullOrEmpty(item2.Key) && item2.Value > 0)
				{
					dictionary[item2.Key] = getCount?.Invoke(item2.Key) ?? 0;
				}
			}
		}
		catch (Exception failure)
		{
			restoreItemsResult.Failure = failure;
			restoreItemsResult.CountUnknown = true;
			return restoreItemsResult;
		}
		try
		{
			if (add == null)
			{
				throw new InvalidOperationException("add delegate is missing");
			}
			add();
		}
		catch (Exception failure2)
		{
			restoreItemsResult.Failure = failure2;
		}
		try
		{
			foreach (KeyValuePair<string, int> item3 in expected)
			{
				if (!string.IsNullOrEmpty(item3.Key) && item3.Value > 0)
				{
					int value = item3.Value;
					if (!dictionary.TryGetValue(item3.Key, out var value2))
					{
						value2 = 0;
					}
					int num = (getCount?.Invoke(item3.Key) ?? 0) - value2;
					if (num < 0)
					{
						num = 0;
					}
					restoreItemsResult.RestoredCount += Math.Min(value, num);
				}
			}
		}
		catch (Exception ex)
		{
			restoreItemsResult.Failure = restoreItemsResult.Failure ?? ex;
			restoreItemsResult.CountUnknown = true;
		}
		return restoreItemsResult;
	}
}
internal sealed class InventoryTransferResult
{
	internal bool Complete;

	internal Exception Failure;

	internal readonly Dictionary<string, int> Added = new Dictionary<string, int>();
}
internal static class InventoryTransfer
{
	internal static InventoryTransferResult Apply(IReadOnlyDictionary<string, int> expected, Func<string, int> getCount, Action add)
	{
		InventoryTransferResult inventoryTransferResult = new InventoryTransferResult();
		if (expected == null)
		{
			inventoryTransferResult.Failure = new ArgumentNullException("expected");
			return inventoryTransferResult;
		}
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		try
		{
			foreach (KeyValuePair<string, int> item in expected)
			{
				if (item.Key != null)
				{
					dictionary[item.Key] = getCount?.Invoke(item.Key) ?? 0;
				}
			}
		}
		catch (Exception failure)
		{
			inventoryTransferResult.Failure = failure;
			return inventoryTransferResult;
		}
		try
		{
			if (add == null)
			{
				throw new InvalidOperationException("add delegate is missing");
			}
			add();
		}
		catch (Exception failure2)
		{
			inventoryTransferResult.Failure = failure2;
		}
		try
		{
			bool complete = true;
			foreach (KeyValuePair<string, int> item2 in expected)
			{
				if (item2.Key != null)
				{
					int num = Math.Max(0, item2.Value);
					int num2 = (getCount?.Invoke(item2.Key) ?? 0) - dictionary[item2.Key];
					if (num2 < 0)
					{
						num2 = 0;
					}
					if (num2 > num)
					{
						num2 = num;
					}
					inventoryTransferResult.Added[item2.Key] = num2;
					if (num2 < num)
					{
						complete = false;
					}
				}
			}
			inventoryTransferResult.Complete = complete;
		}
		catch (Exception failure3)
		{
			inventoryTransferResult.Complete = false;
			if (inventoryTransferResult.Failure == null)
			{
				inventoryTransferResult.Failure = failure3;
			}
		}
		return inventoryTransferResult;
	}
}
internal sealed class LogRateLimiter
{
	private readonly int intervalMilliseconds;

	private readonly int capacity;

	private readonly Dictionary<string, int> nextAllowedTick = new Dictionary<string, int>(StringComparer.Ordinal);

	private readonly Queue<string> order = new Queue<string>();

	internal LogRateLimiter(int intervalMilliseconds, int capacity)
	{
		this.intervalMilliseconds = Math.Max(0, intervalMilliseconds);
		this.capacity = Math.Max(1, capacity);
	}

	internal bool ShouldLog(string key, int nowTick)
	{
		if (string.IsNullOrEmpty(key))
		{
			return true;
		}
		if (nextAllowedTick.TryGetValue(key, out var value) && nowTick - value < 0)
		{
			return false;
		}
		if (!nextAllowedTick.ContainsKey(key))
		{
			order.Enqueue(key);
			while (order.Count > capacity)
			{
				string text = order.Dequeue();
				if (text != key)
				{
					nextAllowedTick.Remove(text);
				}
			}
		}
		nextAllowedTick[key] = nowTick + intervalMilliseconds;
		return true;
	}
}
internal enum NetworkBuildStatus
{
	Ready,
	Unavailable,
	BlockedByExclusion
}
internal static class NetworkBuildDecision
{
	internal static NetworkBuildStatus Classify(bool snapshotAvailable, bool hasLocalCandidates, bool currentZoneEligible, IInventorySource anchor)
	{
		if (!snapshotAvailable)
		{
			return NetworkBuildStatus.Unavailable;
		}
		if (!currentZoneEligible && hasLocalCandidates)
		{
			return NetworkBuildStatus.BlockedByExclusion;
		}
		if (anchor != null)
		{
			return NetworkBuildStatus.Ready;
		}
		if (!hasLocalCandidates)
		{
			return NetworkBuildStatus.Unavailable;
		}
		return NetworkBuildStatus.BlockedByExclusion;
	}
}
internal enum PorterExecutionStatus
{
	Ready,
	Unavailable,
	PartialFailure,
	Skipped
}
internal static class PorterTransportPolicy
{
	internal static bool IsPoolInternalDestination(bool usesCustomTargets, int destinationCount, int destinationsInsidePool, bool targetZoneInsidePool, bool hasVanillaSideEffect)
	{
		if (hasVanillaSideEffect)
		{
			return false;
		}
		if (usesCustomTargets)
		{
			if (destinationCount > 0)
			{
				return destinationsInsidePool == destinationCount;
			}
			return false;
		}
		return targetZoneInsidePool;
	}

	internal static bool ShouldSkipTransport(bool skipEnabled, bool poolInternalDestination)
	{
		return skipEnabled && poolInternalDestination;
	}
}
internal sealed class RemoveItemsResult
{
	internal List<Item> Items = new List<Item>();

	internal int RemovedCount;

	internal Exception Failure;

	internal bool CountUnknown;
}
internal sealed class RestoreItemsResult
{
	internal List<Item> Items = new List<Item>();

	internal int ExpectedCount;

	internal int RestoredCount;

	internal Exception Failure;

	internal bool CountUnknown;
}
internal sealed class RollbackResult
{
	internal bool Complete;

	internal Exception Failure;

	internal bool Unknown;

	internal readonly List<WithdrawalLine> Unrestored = new List<WithdrawalLine>();
}
internal sealed class WithdrawalRollbackException : Exception
{
	internal readonly RollbackResult Rollback;

	internal WithdrawalRollbackException(Exception original, RollbackResult rollback)
		: base("Withdrawal rollback did not complete; original error: " + ((original == null) ? "<none>" : (original.GetType().Name + ": " + original.Message)), original)
	{
		Rollback = rollback ?? new RollbackResult();
	}
}
internal sealed class WithdrawalMutationException : Exception
{
	internal WithdrawalMutationException(Exception original)
		: base("Withdrawal mutated inventory and the mutation was rolled back.", original)
	{
	}
}
internal sealed class WithdrawalMutationUnknownException : Exception
{
	internal WithdrawalMutationUnknownException(string message, Exception inner)
		: base(message, inner)
	{
	}
}
internal static class SettingsParser
{
	internal static SettingsData Parse(string roles, string containers)
	{
		SettingsData settingsData = new SettingsData();
		foreach (string item in Split(roles))
		{
			if (TryParseRole(item, out var role))
			{
				settingsData.DisabledZombieTypes.Add(role);
			}
		}
		foreach (string item2 in Split(containers))
		{
			settingsData.ExcludedContainerIds.Add(item2);
		}
		return settingsData;
	}

	private static IEnumerable<string> Split(string value)
	{
		if (string.IsNullOrWhiteSpace(value))
		{
			yield break;
		}
		string[] array = value.Split(',', ';', '|');
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].Trim();
			if (text.Length > 0)
			{
				yield return text;
			}
		}
	}

	private static bool TryParseRole(string value, out ZombieRole role)
	{
		switch (value.ToLowerInvariant())
		{
		case "caretaker":
			role = ZombieRole.Caretaker;
			return true;
		case "gardener":
			role = ZombieRole.Gardener;
			return true;
		case "porter":
			role = ZombieRole.Porter;
			return true;
		case "conveyor_transporter":
			role = ZombieRole.ConveyorTransporter;
			return true;
		default:
			role = ZombieRole.Caretaker;
			return false;
		}
	}
}
internal sealed class StorageNetwork
{
	private sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class
	{
		internal static readonly ReferenceEqualityComparer<T> Instance = new ReferenceEqualityComparer<T>();

		public bool Equals(T x, T y)
		{
			return x == y;
		}

		public int GetHashCode(T obj)
		{
			return RuntimeHelpers.GetHashCode(obj);
		}
	}

	private readonly List<IInventorySource> local;

	private readonly List<IInventorySource> remote;

	private readonly HashSet<string> excluded;

	internal List<IInventorySource> PoolSources => local;

	internal List<IInventorySource> OtherZoneSources => remote;

	internal StorageNetwork(List<IInventorySource> local, List<IInventorySource> remote, HashSet<string> excluded)
	{
		this.local = local ?? new List<IInventorySource>();
		this.remote = remote ?? new List<IInventorySource>();
		this.excluded = excluded ?? new HashSet<string>();
	}

	internal IInventorySource SelectAnchor(float x, float z, HashSet<string> excluded)
	{
		IInventorySource result = null;
		float num = float.MaxValue;
		for (int i = 0; i < local.Count; i++)
		{
			IInventorySource inventorySource = local[i];
			if (inventorySource != null && !IsExcluded(inventorySource, excluded) && inventorySource.Distance < num)
			{
				num = inventorySource.Distance;
				result = inventorySource;
			}
		}
		return result;
	}

	internal int GetTotal(string itemId)
	{
		int total = 0;
		HashSet<IInventorySource> seen = new HashSet<IInventorySource>(ReferenceEqualityComparer<IInventorySource>.Instance);
		AccumulateTotal(local, itemId, seen, ref total);
		AccumulateTotal(remote, itemId, seen, ref total);
		return total;
	}

	internal bool CanSupply(string itemId, int count)
	{
		if (count > 0)
		{
			return GetTotal(itemId) >= count;
		}
		return true;
	}

	internal WithdrawalResult TryWithdraw(string itemId, int count, IInventorySource anchor, Func<IInventorySource, bool> allowsItem)
	{
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_013a: Expected O, but got Unknown
		WithdrawalResult withdrawalResult = new WithdrawalResult();
		if (count <= 0)
		{
			withdrawalResult.Success = true;
			return withdrawalResult;
		}
		if (!CanSupply(itemId, count))
		{
			withdrawalResult.Success = false;
			return withdrawalResult;
		}
		List<IInventorySource> list = BuildOrder(anchor);
		int num = count;
		bool flag = false;
		try
		{
			for (int i = 0; i < list.Count; i++)
			{
				if (num <= 0)
				{
					break;
				}
				IInventorySource inventorySource = list[i];
				if (inventorySource == null || excluded.Contains(inventorySource.ContainerId) || (allowsItem != null && !allowsItem(inventorySource)))
				{
					continue;
				}
				int num2 = Math.Min(num, inventorySource.GetCount(itemId));
				if (num2 <= 0)
				{
					continue;
				}
				RemoveItemsResult removeItemsResult = inventorySource.RemoveItems(itemId, num2);
				if (removeItemsResult == null)
				{
					throw new InvalidOperationException("Inventory source returned a null RemoveItemsResult.");
				}
				if (removeItemsResult.CountUnknown)
				{
					flag = true;
					throw new WithdrawalMutationUnknownException("Inventory source may have mutated while its count was unreadable.", removeItemsResult.Failure);
				}
				int removedCount = removeItemsResult.RemovedCount;
				if (removedCount <= 0)
				{
					if (removeItemsResult.Failure != null)
					{
						throw removeItemsResult.Failure;
					}
					continue;
				}
				flag = true;
				WithdrawalLine withdrawalLine = new WithdrawalLine
				{
					Source = inventorySource,
					ItemId = itemId,
					Count = removedCount,
					Items = CopyItems(removeItemsResult.Items)
				};
				if (withdrawalLine.Items.Count == 0)
				{
					withdrawalLine.Items.Add(new Item(itemId, removedCount));
				}
				withdrawalResult.Lines.Add(withdrawalLine);
				if (removeItemsResult.Failure != null)
				{
					throw removeItemsResult.Failure;
				}
				if (removedCount > num2)
				{
					RollbackResult rollbackResult = RollbackPartial(withdrawalResult, EmptyTransferredCounts());
					withdrawalResult.Success = false;
					withdrawalResult.Lines.Clear();
					if (!rollbackResult.Complete)
					{
						throw new WithdrawalRollbackException(new InvalidOperationException("Source removed more items than requested and rollback did not complete."), rollbackResult);
					}
					throw new WithdrawalMutationException(new InvalidOperationException("Source removed more items than requested."));
				}
				num -= removedCount;
			}
		}
		catch (Exception ex)
		{
			RollbackResult rollbackResult2 = RollbackPartial(withdrawalResult, EmptyTransferredCounts());
			withdrawalResult.Success = false;
			if (!rollbackResult2.Complete)
			{
				throw new WithdrawalRollbackException(ex, rollbackResult2);
			}
			withdrawalResult.Lines.Clear();
			if (ex is WithdrawalMutationException)
			{
				throw;
			}
			if (flag)
			{
				throw new WithdrawalMutationException(ex);
			}
			throw;
		}
		if (num == 0)
		{
			withdrawalResult.Success = true;
			return withdrawalResult;
		}
		RollbackResult rollbackResult3 = RollbackPartial(withdrawalResult, EmptyTransferredCounts());
		withdrawalResult.Success = false;
		withdrawalResult.Lines.Clear();
		if (!rollbackResult3.Complete)
		{
			throw new WithdrawalRollbackException(new InvalidOperationException("Withdrawal ended short after partial removal."), rollbackResult3);
		}
		if (flag)
		{
			throw new WithdrawalMutationException(new InvalidOperationException("Withdrawal ended short after partial removal."));
		}
		return withdrawalResult;
	}

	internal RollbackResult Rollback(WithdrawalResult result)
	{
		return RollbackPartial(result, EmptyTransferredCounts());
	}

	internal RollbackResult RollbackPartial(WithdrawalResult result, IReadOnlyDictionary<string, int> transferredCounts)
	{
		RollbackResult rollbackResult = new RollbackResult();
		if (result == null)
		{
			rollbackResult.Complete = true;
			return rollbackResult;
		}
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		if (transferredCounts != null)
		{
			foreach (KeyValuePair<string, int> transferredCount in transferredCounts)
			{
				dictionary[transferredCount.Key] = Math.Max(0, transferredCount.Value);
			}
		}
		List<WithdrawalLine> list = new List<WithdrawalLine>();
		for (int num = result.Lines.Count - 1; num >= 0; num--)
		{
			WithdrawalLine withdrawalLine = result.Lines[num];
			if (withdrawalLine != null && withdrawalLine.Source != null)
			{
				int num2 = 0;
				if (dictionary.TryGetValue(withdrawalLine.ItemId, out var value) && value > 0)
				{
					num2 = Math.Min(withdrawalLine.Count, value);
					dictionary[withdrawalLine.ItemId] = value - num2;
				}
				int num3 = withdrawalLine.Count - num2;
				if (num3 > 0)
				{
					Exception failure;
					bool unknown;
					WithdrawalLine withdrawalLine2 = RestoreLine(new WithdrawalLine
					{
						Source = withdrawalLine.Source,
						ItemId = withdrawalLine.ItemId,
						Count = num3,
						Items = TakeTailItems(withdrawalLine.Items, withdrawalLine.ItemId, num3)
					}, out failure, out unknown);
					if (unknown)
					{
						rollbackResult.Unknown = true;
					}
					if (failure != null && rollbackResult.Failure == null)
					{
						rollbackResult.Failure = failure;
					}
					if (withdrawalLine2 != null)
					{
						list.Add(withdrawalLine2);
						rollbackResult.Unrestored.Add(withdrawalLine2);
					}
				}
			}
		}
		result.Lines.Clear();
		result.Lines.AddRange(list);
		result.Success = false;
		rollbackResult.Complete = !rollbackResult.Unknown && rollbackResult.Unrestored.Count == 0;
		return rollbackResult;
	}

	internal List<IInventorySource> GetSources()
	{
		List<IInventorySource> list = new List<IInventorySource>();
		for (int i = 0; i < local.Count; i++)
		{
			IInventorySource inventorySource = local[i];
			if (inventorySource != null && !excluded.Contains(inventorySource.ContainerId))
			{
				list.Add(inventorySource);
			}
		}
		for (int j = 0; j < remote.Count; j++)
		{
			IInventorySource inventorySource2 = remote[j];
			if (inventorySource2 != null && !excluded.Contains(inventorySource2.ContainerId))
			{
				list.Add(inventorySource2);
			}
		}
		return list;
	}

	private static WithdrawalLine RestoreLine(WithdrawalLine line, out Exception failure, out bool unknown)
	{
		failure = null;
		unknown = false;
		int num = 0;
		try
		{
			RestoreItemsResult restoreItemsResult = line.Source.RestoreItems(line.Items);
			if (restoreItemsResult == null)
			{
				unknown = true;
			}
			else
			{
				num = restoreItemsResult.RestoredCount;
				failure = restoreItemsResult.Failure;
				unknown = restoreItemsResult.CountUnknown;
			}
		}
		catch (Exception ex)
		{
			failure = ex;
			unknown = true;
		}
		if (!unknown && num >= line.Count)
		{
			return null;
		}
		if (unknown)
		{
			return line;
		}
		int count = line.Count - Math.Max(0, num);
		return new WithdrawalLine
		{
			Source = line.Source,
			ItemId = line.ItemId,
			Count = count,
			Items = TakeTailItems(line.Items, line.ItemId, count)
		};
	}

	private static List<Item> TakeTailItems(IReadOnlyList<Item> items, string itemId, int count)
	{
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Expected O, but got Unknown
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Expected O, but got Unknown
		List<Item> list = new List<Item>();
		int num = count;
		if (items != null)
		{
			int num2 = items.Count - 1;
			while (num2 >= 0 && num > 0)
			{
				Item val = items[num2];
				if (val != null && val.Count > 0)
				{
					if (val.Count <= num)
					{
						list.Insert(0, val);
						num -= val.Count;
					}
					else
					{
						list.Insert(0, new Item(((ObjectLinkedToDefinition<ItemDef>)(object)val).id, num));
						num = 0;
					}
				}
				num2--;
			}
		}
		if (num > 0)
		{
			list.Insert(0, new Item(itemId, num));
		}
		return list;
	}

	private static int SumItemCounts(IReadOnlyList<Item> items)
	{
		int num = 0;
		if (items == null)
		{
			return num;
		}
		for (int i = 0; i < items.Count; i++)
		{
			Item val = items[i];
			if (val != null && val.Count > 0)
			{
				num += val.Count;
			}
		}
		return num;
	}

	private static List<Item> CopyItems(IReadOnlyList<Item> items)
	{
		List<Item> list = new List<Item>();
		if (items == null)
		{
			return list;
		}
		for (int i = 0; i < items.Count; i++)
		{
			Item val = items[i];
			if (val != null && val.Count > 0)
			{
				list.Add(val);
			}
		}
		return list;
	}

	private static Dictionary<string, int> EmptyTransferredCounts()
	{
		return new Dictionary<string, int>();
	}

	private List<IInventorySource> BuildOrder(IInventorySource anchor)
	{
		List<IInventorySource> list = new List<IInventorySource>();
		HashSet<IInventorySource> hashSet = new HashSet<IInventorySource>(ReferenceEqualityComparer<IInventorySource>.Instance);
		if (anchor != null && !excluded.Contains(anchor.ContainerId))
		{
			list.Add(anchor);
			hashSet.Add(anchor);
		}
		for (int i = 0; i < local.Count; i++)
		{
			IInventorySource inventorySource = local[i];
			if (inventorySource != null && !excluded.Contains(inventorySource.ContainerId) && !hashSet.Contains(inventorySource))
			{
				list.Add(inventorySource);
				hashSet.Add(inventorySource);
			}
		}
		for (int j = 0; j < remote.Count; j++)
		{
			IInventorySource inventorySource2 = remote[j];
			if (inventorySource2 != null && !excluded.Contains(inventorySource2.ContainerId) && !hashSet.Contains(inventorySource2))
			{
				list.Add(inventorySource2);
				hashSet.Add(inventorySource2);
			}
		}
		return list;
	}

	private void AccumulateTotal(List<IInventorySource> sources, string itemId, HashSet<IInventorySource> seen, ref int total)
	{
		for (int i = 0; i < sources.Count; i++)
		{
			IInventorySource inventorySource = sources[i];
			if (inventorySource != null && !excluded.Contains(inventorySource.ContainerId) && seen.Add(inventorySource))
			{
				total += inventorySource.GetCount(itemId);
			}
		}
	}

	private bool IsExcluded(IInventorySource item, HashSet<string> extraExcluded)
	{
		if (item == null)
		{
			return true;
		}
		if (excluded.Contains(item.ContainerId))
		{
			return true;
		}
		return extraExcluded?.Contains(item.ContainerId) ?? false;
	}
}
internal sealed class WithdrawalLine
{
	internal IInventorySource Source;

	internal string ItemId;

	internal int Count;

	internal List<Item> Items = new List<Item>();
}
internal sealed class WithdrawalResult
{
	internal bool Success;

	internal readonly List<WithdrawalLine> Lines = new List<WithdrawalLine>();
}
internal static class ZombieFailureGuard
{
	private static readonly object Sync = new object();

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

	internal static void Mark(string zombieKey, ZombieRole role)
	{
		if (string.IsNullOrEmpty(zombieKey))
		{
			return;
		}
		lock (Sync)
		{
			Marked.Add(role.ToString() + ":" + zombieKey);
		}
	}

	internal static bool IsMarked(string zombieKey, ZombieRole role)
	{
		if (string.IsNullOrEmpty(zombieKey))
		{
			return false;
		}
		lock (Sync)
		{
			return Marked.Contains(role.ToString() + ":" + zombieKey);
		}
	}

	internal static void ClearAll()
	{
		lock (Sync)
		{
			Marked.Clear();
		}
	}
}
internal sealed class SettingsData
{
	internal readonly HashSet<ZombieRole> DisabledZombieTypes = new HashSet<ZombieRole>();

	internal readonly HashSet<string> ExcludedContainerIds = new HashSet<string>();
}
internal enum ZombieRole
{
	Caretaker,
	Gardener,
	Porter,
	ConveyorTransporter
}
internal static class ZombieScope
{
	private sealed class Frame
	{
		internal readonly ZombieRole Role;

		internal readonly string Key;

		internal Frame(ZombieRole role, string key)
		{
			Role = role;
			Key = key;
		}
	}

	[ThreadStatic]
	private static Stack<Frame> frames;

	internal static string CurrentKey
	{
		get
		{
			if (frames == null || frames.Count <= 0)
			{
				return null;
			}
			return frames.Peek().Key;
		}
	}

	internal static void Enter(ZombieRole role)
	{
		Enter(role, null);
	}

	internal static void Enter(ZombieRole role, string key)
	{
		if (frames == null)
		{
			frames = new Stack<Frame>();
		}
		frames.Push(new Frame(role, key));
	}

	internal static void Exit()
	{
		if (frames != null && frames.Count != 0)
		{
			frames.Pop();
		}
	}

	internal static bool Is(ZombieRole role)
	{
		if (frames != null && frames.Count > 0)
		{
			return frames.Peek().Role == role;
		}
		return false;
	}
}
internal sealed class GameInventorySource : IInventorySource
{
	private readonly SharedContainer container;

	internal WgoData WgoData => container.WgoData;

	internal SGuid UniqueId => container.WgoData.UniqueId;

	public string ContainerId => ((ObjectLinkedToDefinition<WGODef>)(object)container.WgoData).id;

	public string ZoneId => ((ObjectLinkedToDefinition<WorldZoneDef>)(object)container.Zone).id;

	public bool IsCurrentZone => container.IsCurrentZone;

	public Inventory Inventory => container.Inventory;

	public float Distance { get; }

	internal GameInventorySource(SharedContainer container, float x, float z)
	{
		//IL_0010: 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_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		this.container = container;
		Distance = Vector2.Distance(new Vector2(x, z), new Vector2(container.WgoData.Position.x, container.WgoData.Position.z));
	}

	internal bool AllowsItem(Item item)
	{
		if (item == null || ((ObjectLinkedToDefinition<ItemDef>)(object)item).Definition == null || container.WgoData == null)
		{
			return false;
		}
		WGODef definition = ((ObjectLinkedToDefinition<WGODef>)(object)container.WgoData).Definition;
		if (definition == null || definition.inventoryWhiteList == null || definition.inventoryBlackList == null)
		{
			return false;
		}
		if (((ItemFilter)definition.inventoryWhiteList).Contains(((ObjectLinkedToDefinition<ItemDef>)(object)item).Definition))
		{
			return !((ItemFilter)definition.inventoryBlackList).Contains(((ObjectLinkedToDefinition<ItemDef>)(object)item).Definition);
		}
		return false;
	}

	public int GetCount(string itemId)
	{
		return container.Inventory.Data.GetTotalCountInInventory(itemId, (Item)null, false);
	}

	public RemoveItemsResult RemoveItems(string itemId, int count)
	{
		RemoveItemsResult removeItemsResult = new RemoveItemsResult();
		if (count <= 0)
		{
			return removeItemsResult;
		}
		int count2;
		try
		{
			count2 = GetCount(itemId);
		}
		catch (Exception failure)
		{
			removeItemsResult.Failure = failure;
			removeItemsResult.CountUnknown = true;
			return removeItemsResult;
		}
		List<Item> removed = null;
		try
		{
			removed = container.Inventory.RemoveItemById(itemId, count, (Item)null, (Item)null, false);
		}
		catch (Exception failure2)
		{
			removeItemsResult.Failure = failure2;
		}
		int count3;
		try
		{
			count3 = GetCount(itemId);
		}
		catch (Exception ex)
		{
			removeItemsResult.Failure = removeItemsResult.Failure ?? ex;
			removeItemsResult.CountUnknown = true;
			return removeItemsResult;
		}
		RemoveItemsResult removeItemsResult2 = InventoryRemovalAccounting.Resolve(itemId, count2, count3, removed, removeItemsResult.Failure);
		removeItemsResult.RemovedCount = removeItemsResult2.RemovedCount;
		removeItemsResult.Items = removeItemsResult2.Items;
		removeItemsResult.Failure = removeItemsResult2.Failure;
		removeItemsResult.CountUnknown = removeItemsResult2.CountUnknown;
		return removeItemsResult;
	}

	public RestoreItemsResult RestoreItems(List<Item> items)
	{
		RestoreItemsResult restoreItemsResult = new RestoreItemsResult();
		if (items == null || items.Count == 0)
		{
			return restoreItemsResult;
		}
		Dictionary<string, int> dictionary = BuildExpectedCounts(items);
		if (dictionary.Count == 0)
		{
			return restoreItemsResult;
		}
		try
		{
			return InventoryRestore.Apply(dictionary, (string id) => GetCount(id), delegate
			{
				List<Item> list = default(List<Item>);
				for (int i = 0; i < items.Count; i++)
				{
					Item val = items[i];
					if (val != null && val.Count > 0)
					{
						container.Inventory.TryAddItemToInventory(val, ref list, (Item)null, false);
					}
				}
			});
		}
		catch (Exception failure)
		{
			restoreItemsResult.Failure = failure;
			return restoreItemsResult;
		}
	}

	public int Remove(string itemId, int count)
	{
		return RemoveItems(itemId, count).RemovedCount;
	}

	public void Restore(string itemId, int count)
	{
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Expected O, but got Unknown
		if (count > 0)
		{
			RestoreItems(new List<Item>
			{
				new Item(itemId, count)
			});
		}
	}

	private static Dictionary<string, int> BuildExpectedCounts(List<Item> items)
	{
		Dictionary<string, int> dictionary = new Dictionary<string, int>();
		if (items == null)
		{
			return dictionary;
		}
		for (int i = 0; i < items.Count; i++)
		{
			Item val = items[i];
			if (val != null && !string.IsNullOrEmpty(((ObjectLinkedToDefinition<ItemDef>)(object)val).id) && val.Count > 0)
			{
				dictionary.TryGetValue(((ObjectLinkedToDefinition<ItemDef>)(object)val).id, out var value);
				dictionary[((ObjectLinkedToDefinition<ItemDef>)(object)val).id] = value + val.Count;
			}
		}
		return dictionary;
	}
}
internal sealed class PatchGroup
{
	internal readonly string Name;

	internal readonly List<Type> Types;

	internal PatchGroup(string name, params Type[] types)
	{
		Name = name;
		Types = new List<Type>(types ?? new Type[0]);
	}
}
internal static class PatchRegistry
{
	internal static readonly List<Type> All;

	internal static readonly List<PatchGroup> Groups;

	static PatchRegistry()
	{
		All = new List<Type>();
		Groups = new List<PatchGroup>();
		All.Add(typeof(Patch_CaretakerTryGetNewOrder));
		All.Add(typeof(Patch_CanDeliveryOrder));
		All.Add(typeof(Patch_CaretakerAnchor));
		All.Add(typeof(Patch_CaretakerPickup));
		All.Add(typeof(Patch_GardenerTryGetNewOrder));
		All.Add(typeof(Patch_CanPlantOrder));
		All.Add(typeof(Patch_GardenerPickup));
		All.Add(typeof(Patch_PorterCheckDeliveryStart));
		All.Add(typeof(Patch_PorterSourceCount));
		All.Add(typeof(Patch_PorterFill));
		Groups.Add(new PatchGroup("Caretaker", typeof(Patch_CaretakerTryGetNewOrder), typeof(Patch_CanDeliveryOrder), typeof(Patch_CaretakerAnchor), typeof(Patch_CaretakerPickup)));
		Groups.Add(new PatchGroup("Gardener", typeof(Patch_GardenerTryGetNewOrder), typeof(Patch_CanPlantOrder), typeof(Patch_GardenerPickup)));
		Groups.Add(new PatchGroup("Porter", typeof(Patch_PorterCheckDeliveryStart), typeof(Patch_PorterSourceCount), typeof(Patch_PorterFill)));
	}
}
internal static class SharedStorageApiCaller
{
	internal static bool IsCurrentZoneEligible(WorldZoneData currentZone)
	{
		return SharedStorageApi.IsCurrentZoneEligible(currentZone);
	}

	internal static bool TryBuildSources(WorldZoneData currentZone, float x, float z, out List<IInventorySource> local, out List<IInventorySource> remote)
	{
		local = null;
		remote = null;
		StorageSnapshot val = default(StorageSnapshot);
		if (!SharedStorageApi.TryGetSnapshot(currentZone, ref val) || val == null)
		{
			return false;
		}
		List<IInventorySource> list = new List<IInventorySource>();
		List<IInventorySource> list2 = new List<IInventorySource>();
		IReadOnlyList<SharedContainer> localContainers = val.LocalContainers;
		for (int i = 0; i < localContainers.Count; i++)
		{
			SharedContainer val2 = localContainers[i];
			if (val2 != null)
			{
				list.Add(new GameInventorySource(val2, x, z));
			}
		}
		IReadOnlyList<SharedContainer> otherZoneContainers = val.OtherZoneContainers;
		for (int j = 0; j < otherZoneContainers.Count; j++)
		{
			SharedContainer val3 = otherZoneContainers[j];
			if (val3 != null)
			{
				list2.Add(new GameInventorySource(val3, x, z));
			}
		}
		local = list;
		remote = list2;
		return true;
	}
}
internal static class SharedStorageApiShape
{
	internal static bool Validate(Type apiType, Type snapshotType, Type containerType, Type worldZoneType, Type wgoDataType, Type inventoryType, out string failureReason)
	{
		failureReason = null;
		if (apiType == null || snapshotType == null || containerType == null || worldZoneType == null || wgoDataType == null || inventoryType == null)
		{
			failureReason = "one or more required API types are missing";
			return false;
		}
		FieldInfo field = apiType.GetField("ApiVersion", BindingFlags.Static | BindingFlags.Public);
		if (field == null || field.FieldType != typeof(int) || !field.IsLiteral)
		{
			failureReason = "ApiVersion must be a public const int";
			return false;
		}
		object rawConstantValue = field.GetRawConstantValue();
		if (!(rawConstantValue is int) || (int)rawConstantValue != 1)
		{
			failureReason = "ApiVersion must be 1";
			return false;
		}
		if (!IsReadableProperty(apiType.GetProperty("IsOperational", BindingFlags.Static | BindingFlags.Public), typeof(bool)))
		{
			failureReason = "IsOperational must be a public static bool property";
			return false;
		}
		MethodInfo method = apiType.GetMethod("TryGetSnapshot", BindingFlags.Static | BindingFlags.Public, null, new Type[2]
		{
			worldZoneType,
			snapshotType.MakeByRefType()
		}, null);
		if (method == null || !method.IsStatic || !method.IsPublic)
		{
			failureReason = "TryGetSnapshot has the wrong signature";
			return false;
		}
		if (method.ReturnType != typeof(bool))
		{
			failureReason = "TryGetSnapshot must return bool";
			return false;
		}
		ParameterInfo[] parameters = method.GetParameters();
		if (parameters.Length != 2 || parameters[0].ParameterType != worldZoneType || parameters[1].ParameterType != snapshotType.MakeByRefType() || !parameters[1].IsOut)
		{
			failureReason = "TryGetSnapshot must accept (WorldZoneData, out StorageSnapshot)";
			return false;
		}
		MethodInfo method2 = apiType.GetMethod("IsCurrentZoneEligible", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { worldZoneType }, null);
		if (method2 == null || !method2.IsStatic || !method2.IsPublic || method2.ReturnType != typeof(bool))
		{
			failureReason = "IsCurrentZoneEligible must be public static bool(WorldZoneData)";
			return false;
		}
		Type expectedType = typeof(IReadOnlyList<>).MakeGenericType(containerType);
		if (!HasPublicGetter(snapshotType, "LocalContainers", expectedType))
		{
			failureReason = "StorageSnapshot.LocalContainers has the wrong type";
			return false;
		}
		if (!HasPublicGetter(snapshotType, "OtherZoneContainers", expectedType))
		{
			failureReason = "StorageSnapshot.OtherZoneContainers has the wrong type";
			return false;
		}
		if (!HasPublicGetter(containerType, "WgoData", wgoDataType))
		{
			failureReason = "SharedContainer.WgoData has the wrong type";
			return false;
		}
		if (!HasPublicGetter(containerType, "Inventory", inventoryType))
		{
			failureReason = "SharedContainer.Inventory has the wrong type";
			return false;
		}
		if (!HasPublicGetter(containerType, "Zone", worldZoneType))
		{
			failureReason = "SharedContainer.Zone has the wrong type";
			return false;
		}
		if (!HasPublicGetter(containerType, "IsCurrentZone", typeof(bool)))
		{
			failureReason = "SharedContainer.IsCurrentZone has the wrong type";
			return false;
		}
		return true;
	}

	private static bool IsReadableProperty(PropertyInfo property, Type expectedType)
	{
		if (property != null && property.PropertyType == expectedType)
		{
			return property.GetGetMethod(nonPublic: false) != null;
		}
		return false;
	}

	private static bool HasPublicGetter(Type type, string propertyName, Type expectedType)
	{
		return IsReadableProperty(type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public), expectedType);
	}
}
internal static class SharedStorageBridge
{
	private const string SharedStoragePluginGuid = "gk2.sharedstorage";

	private const string SharedStorageAssemblyName = "GraveyardKeeper2.SharedStorage";

	private const string ApiTypeName = "GK2.SharedStorage.SharedStorageApi";

	private const string SnapshotTypeName = "GK2.SharedStorage.StorageSnapshot";

	private const string ContainerTypeName = "GK2.SharedStorage.SharedContainer";

	private const int WarningIntervalMilliseconds = 30000;

	private static bool ready;

	private static readonly LogRateLimiter WarningRateLimiter = new LogRateLimiter(30000, 256);

	private static readonly LogRateLimiter ErrorRateLimiter = new LogRateLimiter(30000, 256);

	private static readonly LogRateLimiter VerboseRateLimiter = new LogRateLimiter(60000, 256);

	internal static bool TryInitialize()
	{
		if (ready)
		{
			return true;
		}
		try
		{
			if (!Chainloader.PluginInfos.ContainsKey("gk2.sharedstorage"))
			{
				WarnRateLimited("SharedStorage plugin is not loaded; cross-zone logistics patches will not be installed.");
				return false;
			}
			if (!TryResolveTypes(out var apiType, out var snapshotType, out var containerType))
			{
				WarnRateLimited("SharedStorage API types are unavailable (old SharedStorage build?); cross-zone logistics patches will not be installed.");
				return false;
			}
			if (!SharedStorageApiShape.Validate(apiType, snapshotType, containerType, typeof(WorldZoneData), typeof(WgoData), typeof(Inventory), out var failureReason))
			{
				WarnRateLimited("SharedStorage API shape is incompatible: " + failureReason + "; cross-zone logistics patches will not be installed.");
				return false;
			}
			object value = apiType.GetProperty("IsOperational", BindingFlags.Static | BindingFlags.Public).GetValue(null, null);
			if (!(value is bool) || !(bool)value)
			{
				WarnRateLimited("SharedStorage API is not operational; cross-zone logistics patches will not be installed.");
				return false;
			}
			ready = true;
			if (ZombieLogisticsPlugin.Log != null)
			{
				ZombieLogisticsPlugin.Log.LogInfo((object)"SharedStorage API 1 detected; cross-zone logistics bridge is ready.");
			}
			return true;
		}
		catch (Exception ex)
		{
			WarnRateLimited("SharedStorage bridge initialization failed: " + ex.GetType().Name + ": " + ex.Message);
			return false;
		}
	}

	internal static NetworkBuildStatus TryBuildNetwork(WorldZoneData currentZone, float x, float z, HashSet<string> excluded, out StorageNetwork network, out IInventorySource anchor)
	{
		network = null;
		anchor = null;
		if (!ready)
		{
			WarnRateLimited("SharedStorage bridge is not ready; using vanilla behavior.");
			return NetworkBuildStatus.Unavailable;
		}
		try
		{
			bool flag = HasLocalMultiInventoryCandidates(currentZone);
			if (!SharedStorageApiCaller.TryBuildSources(currentZone, x, z, out var local, out var remote))
			{
				if (flag && !SharedStorageApiCaller.IsCurrentZoneEligible(currentZone))
				{
					WarnRateLimited("SharedStorage current zone is excluded; fail-closed to avoid vanilla bypass.");
					return NetworkBuildStatus.BlockedByExclusion;
				}
				WarnRateLimited("SharedStorage snapshot is unavailable; using vanilla behavior.");
				return NetworkBuildStatus.Unavailable;
			}
			StorageNetwork storageNetwork = new StorageNetwork(local, remote, excluded);
			IInventorySource inventorySource = storageNetwork.SelectAnchor(x, z, excluded);
			bool currentZoneEligible = SharedStorageApiCaller.IsCurrentZoneEligible(currentZone);
			NetworkBuildStatus networkBuildStatus = NetworkBuildDecision.Classify(snapshotAvailable: true, flag, currentZoneEligible, inventorySource);
			LogVerbose("SharedStorage snapshot: localSources=" + local.Count + ", remoteSources=" + remote.Count + ", hasLocalCandidates=" + flag + ", anchor=" + ((inventorySource == null) ? "<none>" : inventorySource.ContainerId) + ", status=" + networkBuildStatus.ToString() + ".");
			switch (networkBuildStatus)
			{
			case NetworkBuildStatus.BlockedByExclusion:
				WarnRateLimited("SharedStorage snapshot has local candidates but no allowed anchor; fail-closed to avoid vanilla bypass.");
				return NetworkBuildStatus.BlockedByExclusion;
			case NetworkBuildStatus.Unavailable:
				LogVerbose("SharedStorage snapshot has no local candidate; using vanilla behavior.");
				return NetworkBuildStatus.Unavailable;
			default:
				network = storageNetwork;
				anchor = inventorySource;
				return NetworkBuildStatus.Ready;
			}
		}
		catch (Exception ex)
		{
			network = null;
			anchor = null;
			WarnRateLimited("SharedStorage snapshot bridge failed: " + ex.GetType().Name + ": " + ex.Message);
			return NetworkBuildStatus.Unavailable;
		}
	}

	private static bool HasLocalMultiInventoryCandidates(WorldZoneData currentZone)
	{
		if (currentZone == null)
		{
			return false;
		}
		List<WgoData> multiInventoryWgoDatas = currentZone.MultiInventoryWgoDatas;
		if (multiInventoryWgoDatas != null)
		{
			return multiInventoryWgoDatas.Count > 0;
		}
		return false;
	}

	internal static void LogVerbose(string message)
	{
		if (ZombieLogisticsPlugin.Settings != null && ZombieLogisticsPlugin.Settings.VerboseLogging.Value && ZombieLogisticsPlugin.Log != null)
		{
			ZombieLogisticsPlugin.Log.LogInfo((object)message);
		}
	}

	internal static void WarnRateLimited(string message)
	{
		LogRateLimited(message, isError: false);
	}

	internal static void LogVerboseRateLimited(string key, string message)
	{
		if (ZombieLogisticsPlugin.Settings != null && ZombieLogisticsPlugin.Settings.VerboseLogging.Value && VerboseRateLimiter.ShouldLog(key, Environment.TickCount) && ZombieLogisticsPlugin.Log != null)
		{
			ZombieLogisticsPlugin.Log.LogInfo((object)message);
		}
	}

	internal static void ErrorRateLimited(string message)
	{
		LogRateLimited(message, isError: true);
	}

	private static void LogRateLimited(string message, bool isError)
	{
		int tickCount = Environment.TickCount;
		if ((isError ? ErrorRateLimiter : WarningRateLimiter).ShouldLog(message, tickCount) && ZombieLogisticsPlugin.Log != null)
		{
			if (isError)
			{
				ZombieLogisticsPlugin.Log.LogError((object)message);
			}
			else
			{
				ZombieLogisticsPlugin.Log.LogWarning((object)message);
			}
		}
	}

	private static bool TryResolveTypes(out Type apiType, out Type snapshotType, out Type containerType)
	{
		apiType = null;
		snapshotType = null;
		containerType = null;
		if (Chainloader.PluginInfos.TryGetValue("gk2.sharedstorage", out var value) && value != null && (Object)(object)value.Instance != (Object)null && TryGetTypesFromAssembly(((object)value.Instance).GetType().Assembly, out apiType, out snapshotType, out containerType))
		{
			return true;
		}
		string text = ", GraveyardKeeper2.SharedStorage";
		apiType = Type.GetType("GK2.SharedStorage.SharedStorageApi" + text, throwOnError: false);
		snapshotType = Type.GetType("GK2.SharedStorage.StorageSnapshot" + text, throwOnError: false);
		containerType = Type.GetType("GK2.SharedStorage.SharedContainer" + text, throwOnError: false);
		return AreSameAssembly(apiType, snapshotType, containerType);
	}

	private static bool TryGetTypesFromAssembly(Assembly assembly, out Type apiType, out Type snapshotType, out Type containerType)
	{
		apiType = null;
		snapshotType = null;
		containerType = null;
		if (assembly == null)
		{
			return false;
		}
		apiType = assembly.GetType("GK2.SharedStorage.SharedStorageApi", throwOnError: false);
		snapshotType = assembly.GetType("GK2.SharedStorage.StorageSnapshot", throwOnError: false);
		containerType = assembly.GetType("GK2.SharedStorage.SharedContainer", throwOnError: false);
		return AreSameAssembly(apiType, snapshotType, containerType);
	}

	private static bool AreSameAssembly(Type apiType, Type snapshotType, Type containerType)
	{
		if (apiType != null && snapshotType != null && containerType != null && apiType.Assembly == snapshotType.Assembly)
		{
			return snapshotType.Assembly == containerType.Assembly;
		}
		return false;
	}
}
internal static class ZombieFailureGuardAccess
{
	internal static bool ShouldSkip(ZombieWgoData zombie, ZombieRole role)
	{
		if (!IsMarked(zombie, role))
		{
			return false;
		}
		LogHardStopped(role);
		return true;
	}

	internal static bool ShouldSkipCurrentScope(ZombieRole role)
	{
		if (!ZombieFailureGuard.IsMarked(ZombieScope.CurrentKey, role))
		{
			return false;
		}
		LogHardStopped(role);
		return true;
	}

	internal static void Mark(ZombieWgoData zombie, ZombieRole role)
	{
		try
		{
			ZombieFailureGuard.Mark(GetKey(zombie), role);
		}
		catch
		{
		}
	}

	internal static string GetKey(ZombieWgoData zombie)
	{
		try
		{
			if (zombie == null || ((WgoData)zombie).UniqueId == (SGuid)null)
			{
				return null;
			}
			return ((object)((WgoData)zombie).UniqueId).ToString();
		}
		catch
		{
			return null;
		}
	}

	private static bool IsMarked(ZombieWgoData zombie, ZombieRole role)
	{
		try
		{
			return ZombieFailureGuard.IsMarked(GetKey(zombie), role);
		}
		catch
		{
			return false;
		}
	}

	private static void LogHardStopped(ZombieRole role)
	{
		SharedStorageBridge.ErrorRateLimited("Zombie " + role.ToString() + " is hard-stopped after an unrecoverable failure; reload the plugin or rebuild the zombie.");
	}
}
[BepInPlugin("gk2.zombielogistics", "Zombie Cross-Zone Logistics", "1.0.6")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class ZombieLogisticsPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "gk2.zombielogistics";

	public const string PluginName = "Zombie Cross-Zone Logistics";

	public const string PluginVersion = "1.0.6";

	internal static ManualLogSource Log;

	internal static ZombieLogisticsSettings Settings;

	private readonly List<Harmony> groupHarmonies = new List<Harmony>();

	private void Awake()
	{
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Settings = new ZombieLogisticsSettings(((BaseUnityPlugin)this).Config);
		if (!Settings.Enabled.Value)
		{
			Log.LogInfo((object)"Zombie Cross-Zone Logistics disabled by config.");
			return;
		}
		if (!SharedStorageBridge.TryInitialize())
		{
			Log.LogWarning((object)"Zombie Cross-Zone Logistics disabled: SharedStorage API is unavailable. Vanilla behavior remains active.");
			return;
		}
		for (int i = 0; i < PatchRegistry.Groups.Count; i++)
		{
			PatchGroup patchGroup = PatchRegistry.Groups[i];
			Harmony val = new Harmony("gk2.zombielogistics." + patchGroup.Name.ToLowerInvariant());
			try
			{
				for (int j = 0; j < patchGroup.Types.Count; j++)
				{
					val.CreateClassProcessor(patchGroup.Types[j]).Patch();
				}
				groupHarmonies.Add(val);
				Log.LogInfo((object)("Zombie Cross-Zone Logistics group '" + patchGroup.Name + "' active (" + patchGroup.Types.Count + " patch class(es))."));
			}
			catch (Exception ex)
			{
				try
				{
					val.UnpatchSelf();
				}
				catch (Exception ex2)
				{
					Log.LogError((object)("Group '" + patchGroup.Name + "' rollback failed. " + ex2));
				}
				Log.LogError((object)("Group '" + patchGroup.Name + "' disabled after patch failure; vanilla behavior is used for this role. " + ex));
			}
		}
		Log.LogInfo((object)("Zombie Cross-Zone Logistics 1.0.6 loaded. Active groups=" + groupHarmonies.Count + "/" + PatchRegistry.Groups.Count + "."));
	}

	private void OnDestroy()
	{
		for (int i = 0; i < groupHarmonies.Count; i++)
		{
			try
			{
				groupHarmonies[i].UnpatchSelf();
			}
			catch (Exception ex)
			{
				if (Log != null)
				{
					Log.LogWarning((object)("Harmony cleanup failed: " + ex));
				}
			}
		}
		groupHarmonies.Clear();
		ZombieFailureGuard.ClearAll();
	}
}
internal sealed class ZombieLogisticsSettings
{
	internal readonly ConfigEntry<bool> Enabled;

	internal readonly ConfigEntry<string> DisabledZombieTypesRaw;

	internal readonly ConfigEntry<string> ExcludedContainerIdsRaw;

	internal readonly ConfigEntry<bool> VerboseLogging;

	internal readonly ConfigEntry<bool> PorterSkipPoolInternalTransport;

	internal HashSet<ZombieRole> DisabledZombieTypes { get; private set; }

	internal HashSet<string> ExcludedContainerIds { get; private set; }

	internal ZombieLogisticsSettings(ConfigFile config)
	{
		Enabled = config.Bind<bool>("General", "Enabled", true, "Master switch. Changing this value at runtime requires a restart.");
		DisabledZombieTypesRaw = config.Bind<string>("General", "DisabledZombieTypes", "", "Comma separated role blacklist. Takes effect immediately.");
		ExcludedContainerIdsRaw = config.Bind<string>("General", "ExcludedContainerIds", "", "Comma separated WgoData id blacklist. Takes effect immediately.");
		VerboseLogging = config.Bind<bool>("Advanced", "VerboseLogging", false, "Verbose log output; takes effect immediately.");
		PorterSkipPoolInternalTransport = config.Bind<bool>("Advanced", "PorterSkipPoolInternalTransport", true, "true: when every delivery target of a porter station belongs to the shared storage pool, the porter stays at its station instead of hauling goods inside that pool (such a trip empties the pool for the whole walk without giving you anything new). false: restore the old behaviour. Takes effect immediately.");
		RefreshParsedSets();
		DisabledZombieTypesRaw.SettingChanged += OnParsedSettingChanged;
		ExcludedContainerIdsRaw.SettingChanged += OnParsedSettingChanged;
		Enabled.SettingChanged += OnEnabledChanged;
	}

	internal bool IsRoleDisabled(ZombieRole role)
	{
		return DisabledZombieTypes.Contains(role);
	}

	internal void LogVerbose(string message)
	{
		if (VerboseLogging.Value && ZombieLogisticsPlugin.Log != null)
		{
			ZombieLogisticsPlugin.Log.LogInfo((object)message);
		}
	}

	private void OnParsedSettingChanged(object sender, EventArgs e)
	{
		RefreshParsedSets();
		LogVerbose("Configuration reloaded: disabledRoles=" + DisabledZombieTypes.Count + ", excludedContainers=" + ExcludedContainerIds.Count + ".");
	}

	private void OnEnabledChanged(object sender, EventArgs e)
	{
		if (ZombieLogisticsPlugin.Log != null)
		{
			ZombieLogisticsPlugin.Log.LogWarning((object)"Enabled changed in config; this setting requires a restart.");
		}
	}

	private void RefreshParsedSets()
	{
		SettingsData settingsData = SettingsParser.Parse(DisabledZombieTypesRaw.Value, ExcludedContainerIdsRaw.Value);
		DisabledZombieTypes = settingsData.DisabledZombieTypes;
		ExcludedContainerIds = settingsData.ExcludedContainerIds;
	}
}
internal static class ZombieWgoDataAccess
{
	private static readonly FieldInfo CaretakerOrder = AccessTools.Field(typeof(ZombieWgoData), "caretakerExecutingOrder");

	private static readonly FieldInfo CaretakerTarget = AccessTools.Field(typeof(ZombieWgoData), "caretakerCurrentTargetUniqueId");

	private static readonly FieldInfo CaretakerTimer = AccessTools.Field(typeof(ZombieWgoData), "caretakerPickingUpFromInventoryTime");

	private static readonly FieldInfo GardenerOrder = AccessTools.Field(typeof(ZombieWgoData), "gardenerExecutingOrder");

	private static readonly FieldInfo GardenerTarget = AccessTools.Field(typeof(ZombieWgoData), "gardenerCurrentTargetUniqueId");

	private static readonly FieldInfo PorterInventory = AccessTools.Field(typeof(ZombieWgoData), "porterInventory");

	private static readonly MethodInfo CaretakerMove = AccessTools.Method(typeof(ZombieWgoData), "CaretakerTryMoveToCurrentTarget", (Type[])null, (Type[])null);

	private static readonly MethodInfo CaretakerMoveToZombieMethod = AccessTools.Method(typeof(ZombieWgoData), "CaretakerTryMoveToZombie", (Type[])null, (Type[])null);

	private static readonly MethodInfo CaretakerStop = AccessTools.Method(typeof(ZombieWgoData), "CaretakerTryStopOrderExecution", (Type[])null, (Type[])null);

	private static readonly MethodInfo CaretakerGetNext = AccessTools.Method(typeof(ZombieWgoData), "CaretakerTryGetNewOrderOrMoveToStation", (Type[])null, (Type[])null);

	private static readonly MethodInfo GardenerMove = AccessTools.Method(typeof(ZombieWgoData), "GardenerTryMoveToCurrentTarget", (Type[])null, (Type[])null);

	private static readonly MethodInfo GardenerMoveToStation = AccessTools.Method(typeof(ZombieWgoData), "GardenerTryMoveToStation", (Type[])null, (Type[])null);

	private static readonly MethodInfo GardenerStopOrder = AccessTools.Method(typeof(ZombieWgoData), "GardenerTryStopOrderExecution", (Type[])null, (Type[])null);

	private static readonly MethodInfo PorterTake = AccessTools.Method(typeof(ZombieWgoData), "PorterTryTakeItemFromInventory", new Type[2]
	{
		typeof(Inventory),
		typeof(NeedItemData)
	}, (Type[])null);

	private static readonly MethodInfo PorterFill = AccessTools.Method(typeof(ZombieWgoData), "PorterTryFillEmptySlotsFromInventory", new Type[3]
	{
		typeof(Inventory),
		typeof(NeedItemData),
		typeof(int)
	}, (Type[])null);

	private static readonly MethodInfo SetGameRes = AccessTools.Method(typeof(WgoData), "SetGameRes", new Type[2]
	{
		typeof(string),
		typeof(int)
	}, (Type[])null);

	private static readonly MethodInfo SetTrigger = AccessTools.Method(typeof(WgoData), "SetTriggerToAnimator", new Type[1] { typeof(string) }, (Type[])null);

	private static readonly MethodInfo SetLayerWeight = AccessTools.Method(typeof(WgoData), "SetLayerWeightToAnimator", new Type[2]
	{
		typeof(int),
		typeof(float)
	}, (Type[])null);

	internal static bool TrySetCaretakerTarget(ZombieWgoData zombie, SGuid targetId)
	{
		try
		{
			CaretakerTarget.SetValue(zombie, targetId);
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TrySetCaretakerTarget", exception);
			return false;
		}
	}

	internal static bool TryGetCaretakerTarget(ZombieWgoData zombie, out SGuid target)
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Expected O, but got Unknown
		target = SGuid.Empty;
		try
		{
			target = (SGuid)CaretakerTarget.GetValue(zombie);
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TryGetCaretakerTarget", exception);
			return false;
		}
	}

	internal static DeliveryOrder GetCaretakerOrder(ZombieWgoData zombie)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		try
		{
			SGuid val = (SGuid)CaretakerOrder.GetValue(zombie);
			OrderBase obj = ((WgoData)zombie).WorldZoneData.FindOrder(val);
			return (DeliveryOrder)(object)((obj is DeliveryOrder) ? obj : null);
		}
		catch (Exception exception)
		{
			LogAdapterException("GetCaretakerOrder", exception);
			return null;
		}
	}

	internal static bool TryGetCaretakerPortableCount(ZombieWgoData zombie, out int count)
	{
		count = 0;
		try
		{
			Item caretakerPortableItem = zombie.CaretakerPortableItem;
			count = ((caretakerPortableItem != null && !caretakerPortableItem.IsEmpty) ? caretakerPortableItem.Count : 0);
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TryGetCaretakerPortableCount", exception);
			return false;
		}
	}

	internal static bool TryCaretakerTimerReached(ZombieWgoData zombie, float deltaTime, out bool reached)
	{
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Expected O, but got Unknown
		reached = false;
		try
		{
			float num = (float)CaretakerTimer.GetValue(zombie) + deltaTime;
			LazyExpression val = new LazyExpression(ConstDef.Get("zombie_caretaker_picking_up_time").StringValue);
			if (num < val.EvaluateFloat((WgoData)(object)zombie))
			{
				CaretakerTimer.SetValue(zombie, num);
				return true;
			}
			CaretakerTimer.SetValue(zombie, 0f);
			reached = true;
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TryCaretakerTimerReached", exception);
			return false;
		}
	}

	internal static bool TrySetCaretakerPortable(ZombieWgoData zombie, Item item)
	{
		try
		{
			zombie.CaretakerPortableItem = item;
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TrySetCaretakerPortable", exception);
			return false;
		}
	}

	internal static bool TrySetCaretakerPortable(ZombieWgoData zombie, string itemId, int count)
	{
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Expected O, but got Unknown
		try
		{
			return TrySetCaretakerPortable(zombie, new Item(itemId, count));
		}
		catch (Exception exception)
		{
			LogAdapterException("TrySetCaretakerPortable", exception);
			return false;
		}
	}

	internal static bool TryCaretakerMoveToTarget(ZombieWgoData zombie)
	{
		try
		{
			CaretakerMove.Invoke(zombie, null);
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TryCaretakerMoveToTarget", exception);
			return false;
		}
	}

	internal static bool TryCaretakerMoveToZombie(ZombieWgoData zombie)
	{
		try
		{
			zombie.CaretakerState = (ZombieCaretakerState)4;
			CaretakerMoveToZombieMethod.Invoke(zombie, null);
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TryCaretakerMoveToZombie", exception);
			return false;
		}
	}

	internal static bool CaretakerFail(ZombieWgoData zombie, bool forceGuard = false)
	{
		if (forceGuard)
		{
			ZombieFailureGuardAccess.Mark(zombie, ZombieRole.Caretaker);
		}
		bool flag = false;
		try
		{
			if (zombie == null)
			{
				return false;
			}
			CaretakerStop.Invoke(zombie, null);
			zombie.CaretakerState = (ZombieCaretakerState)8;
			CaretakerGetNext.Invoke(zombie, null);
			flag = true;
		}
		catch (Exception exception)
		{
			LogAdapterException("CaretakerFail", exception);
		}
		if (!flag || forceGuard)
		{
			ZombieFailureGuardAccess.Mark(zombie, ZombieRole.Caretaker);
		}
		return flag;
	}

	internal static OrderBase GetGardenerOrder(ZombieWgoData zombie)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Expected O, but got Unknown
		try
		{
			SGuid val = (SGuid)GardenerOrder.GetValue(zombie);
			return ((WgoData)zombie).WorldZoneData.FindOrder(val);
		}
		catch (Exception exception)
		{
			LogAdapterException("GetGardenerOrder", exception);
			return null;
		}
	}

	internal static InventoryTransferResult TryAddGardenerItemsReconciled(ZombieWgoData zombie, List<Item> planned)
	{
		InventoryTransferResult inventoryTransferResult = new InventoryTransferResult();
		if (zombie == null || zombie.WorkerInventory == null || planned == null)
		{
			inventoryTransferResult.Failure = new InvalidOperationException("Gardener worker inventory is unavailable.");
			return inventoryTransferResult;
		}
		try
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			for (int i = 0; i < planned.Count; i++)
			{
				Item val = planned[i];
				if (val != null)
				{
					dictionary.TryGetValue(((ObjectLinkedToDefinition<ItemDef>)(object)val).id, out var value);
					dictionary[((ObjectLinkedToDefinition<ItemDef>)(object)val).id] = value + val.Count;
				}
			}
			inventoryTransferResult = InventoryTransfer.Apply(dictionary, (string id) => zombie.WorkerInventory.Data.GetTotalCountInInventory(id, (Item)null, false), delegate
			{
				if (!zombie.WorkerInventory.CanAddItemsToInventory(planned))
				{
					throw new InvalidOperationException("Gardener worker inventory cannot hold all seeds.");
				}
				zombie.WorkerInventory.AddItemsToInventory(planned);
			});
		}
		catch (Exception failure)
		{
			inventoryTransferResult = new InventoryTransferResult();
			inventoryTransferResult.Failure = failure;
		}
		return inventoryTransferResult;
	}

	internal static bool TryGardenerMoveToTarget(ZombieWgoData zombie)
	{
		try
		{
			OrderBase gardenerOrder = GetGardenerOrder(zombie);
			if (gardenerOrder == null)
			{
				return false;
			}
			zombie.GardenerState = (ZombieGardenerState)3;
			GardenerTarget.SetValue(zombie, gardenerOrder.TargetWgoUniqueId);
			GardenerMove.Invoke(zombie, null);
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TryGardenerMoveToTarget", exception);
			return false;
		}
	}

	internal static bool GardenerFail(ZombieWgoData zombie, bool forceGuard = false)
	{
		if (forceGuard)
		{
			ZombieFailureGuardAccess.Mark(zombie, ZombieRole.Gardener);
		}
		bool flag = false;
		try
		{
			if (zombie == null)
			{
				return false;
			}
			zombie.GardenerState = (ZombieGardenerState)1;
			GardenerMoveToStation.Invoke(zombie, null);
			flag = true;
		}
		catch (Exception exception)
		{
			LogAdapterException("GardenerFail", exception);
		}
		if (!flag || forceGuard)
		{
			ZombieFailureGuardAccess.Mark(zombie, ZombieRole.Gardener);
		}
		return flag;
	}

	internal static bool TryReleaseGardenerOrder(ZombieWgoData zombie)
	{
		if (zombie == null)
		{
			return false;
		}
		try
		{
			GardenerStopOrder.Invoke(zombie, null);
			zombie.GardenerState = (ZombieGardenerState)1;
			GardenerMoveToStation.Invoke(zombie, null);
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TryReleaseGardenerOrder", exception);
			return false;
		}
	}

	internal static bool TryGetGardenerCarriedSeed(ZombieWgoData zombie, List<string> candidates, out string seedItemId, out int count)
	{
		seedItemId = null;
		count = 0;
		try
		{
			if (zombie == null || zombie.WorkerInventory == null || candidates == null || candidates.Count == 0)
			{
				return false;
			}
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			for (int i = 0; i < candidates.Count; i++)
			{
				string text = candidates[i];
				if (!string.IsNullOrEmpty(text) && !dictionary.ContainsKey(text))
				{
					dictionary[text] = zombie.WorkerInventory.Data.GetTotalCountInInventory(text, (Item)null, false);
				}
			}
			seedItemId = GardenerSeedInventoryPolicy.SelectCarriedCandidate(candidates, dictionary);
			if (seedItemId == null)
			{
				return false;
			}
			count = dictionary[seedItemId];
			return true;
		}
		catch (Exception exception)
		{
			LogAdapterException("TryGetGardenerCarriedSeed", exception);
			seedItemId = null;
			count = 0;
			return false;
		}
	}

	internal static GardenerSeedResolution TryResolveGardenerSeed(StorageNetwork network, PlantOrder plantOrder, int need, out string seedItemId, string requiredCandidate = null)
	{
		seedItemId = null;
		try
		{
			if (network == null || plantOrder == null || ((OrderBase)plantOrder).Item == null)
			{
				return GardenerSeedResolution.Failed;
			}
			if (!TryBuildGardenerCandidates(plantOrder, out var candidates) || candidates.Count == 0)
			{
				return GardenerSeedResolution.Failed;
			}
			if (!string.IsNullOrEmpty(requiredCandidate))
			{
				bool flag = false;
				for (int i = 0; i < candidates.Count; i++)
				{
					if (string.Equals(candidates[i], requiredCandidate, StringComparison.Ordinal))
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					return GardenerSeedResolution.Failed;
				}
				candidates = new List<string> { requiredCandidate };
			}
			List<IInventorySource> sources = network.GetSources();
			return GardenerSeedResolver.TryResolve(candidates, need, (string candidate) => HasAllowedGardenerSource(sources, candidate, need), (string candidate) => GetAllowedGardenerTotal(sources, candidate, need), out seedItemId);
		}
		catch (Exception exception)
		{
			LogAdapterException("TryResolveGardenerSeed", exception);
			return GardenerSeedResolution.Failed;
		}
	}

	internal static bool TryGetGardenerCandidates(PlantOrder plantOrder, out List<string> candidates)
	{
		return TryBuildGardenerCandidates(plantOrder, out candidates);
	}

	internal static bool TryGetFirstGardenerCandidate(PlantOrder plantOrder, out string candidate)
	{
		candidate = null;
		try
		{
			if (!TryBuildGardenerCandidates(plantOrder, out var candidates) || candidates.Count == 0)
			{
				return false;
			}
			for (int i = 0; i < candidates.Count; i++)
			{
				if (!string.IsNullOrEmpty(candidates[i]))
				{
					candidate = candidates[i];
					return true;
				}
			}
			return false;
		}
		catch (Exception exception)
		{
			LogAdapterException("TryGetFirstGardenerCandidate", exception);
			return false;
		}
	}

	private static bool TryBuildGardenerCandidates(PlantOrder plantOrder, out List<string> candidates)
	{
		candidates = new List<string>();
		if (!plantOrder.isStarGroupItem)
		{
			candidates.Add(((ObjectLinkedToDefinition<ItemDef>)(object)((OrderBase)plantOrder).Item).id);
			return true;
		}
		if ((Object)(object)GameBalance.Me == (Object)null || !GameBalance.Me.starGroupItemsCache.TryGetValue(((ObjectLinkedToDefinition<ItemDef>)(object)((OrderBase)plantOrder).Item).id, out var value) || value == null)
		{
			return false;
		}
		for (int i = 0; i < value.Count; i++)
		{
			ItemDef val = value[i];
			if (val != null && !string.IsNullOrEmpty(((BalanceBaseObject)val).id))
			{
				candidates.Add(((BalanceBaseObject)val).id);
			}
		}
		return candidates.Count > 0;
	}

	private static bool HasAllowedGardenerSource(List<IInventorySource> sources, string itemId, int need)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Expected O, but got Unknown
		if (need <= 0)
		{
			return true;
		}
		Item item = new Item(itemId, need);
		for (int i = 0; i < sources.Count; i++)
		{
			if (sources[i] is GameInventorySource gameInventorySource && gameInventorySource.AllowsItem(item))
			{
				return true;
			}
		}
		return false;
	}

	private static int GetAllowedGardenerTotal(List<IInventorySource> sources, string itemId, int need)
	{
		//IL_0004: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Expected O, but got Unknown
		int num = 0;
		Item item = new Item(itemId, need);
		for (int i = 0; i < sources.Count; i++)
		{
			if (sources[i] is GameInventorySource gameInventorySource && gameInventorySource.AllowsItem(item))
			{
				num += gameInventorySource.GetCount(itemId);
			}
		}
		return num;
	}

	internal static bool PorterFillFromGlobal(ZombieWgoData zombie, PorterStationDef stationDef, out PorterExecutionStatus status)
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Expected O, but got Unknown
		status = PorterExecutionStatus.Unavailable;
		bool flag = false;
		try
		{
			StorageNetwork network;
			IInventorySource anchor;
			switch (SharedStorageBridge.TryBuildNetwork(((WgoData)zombie).WorldZoneData, ((WgoData)zombie).Position.x, ((WgoData)zombie).Position.z, ZombieLogisticsPlugin.Settings.ExcludedContainerIds, out network, out anchor))
			{
			case NetworkBuildStatus.BlockedByExclusion:
				status = PorterExecutionStatus.PartialFailure;
				return false;
			default:
				status = PorterExecutionStatus.Unavailable;
				return false;
			case NetworkBuildStatus.Ready:
			{
				Inventory val = (Inventory)PorterInventory.GetValue(zombie);
				if (val == null)
				{
					return false;
				}
				if (ShouldSkipPoolInternalPorterTrip(stationDef, network))
				{
					status = PorterExecutionStatus.Skipped;
					return false;
				}
				List<IInventorySource> sources = network.GetSources();
				for (int i = 0; i < sources.Count; i++)
				{
					if (!(sources[i] is GameInventorySource gameInventorySource))
					{
						continue;
					}
					for (int j = 0; j < stationDef.items.Count; j++)
					{
						NeedItemData val2 = stationDef.items[j];
						if (zombie.AttachedWgoData.GetGameResInt(val2.id) == 1)
						{
							flag = true;
							PorterTake.Invoke(zombie, new object[2] { gameInventorySource.Inventory, val2 });
						}
					}
				}
				for (int k = 0; k < stationDef.items.Count; k++)
				{
					NeedItemData val3 = stationDef.items[k];
					if (!IsPorterEnabledRegularItem(zombie, val3) || GetPorterSourceItemCount(sources, val3.id) <= 0)
					{
						continue;
					}
					int porterRemainingRegularItemsWithStock = GetPorterRemainingRegularItemsWithStock(zombie, stationDef, sources, k);
					int num = val.Data.InventorySize - val.Data.InventoryFillSize;
					if (porterRemainingRegularItemsWithStock <= 0 || num <= 0)
					{
						break;
					}
					int num2 = Mathf.Max(1, num / porterRemainingRegularItemsWithStock);
					for (int l = 0; l < sources.Count; l++)
					{
						if (num2 <= 0)
						{
							break;
						}
						if (sources[l] is GameInventorySource gameInventorySource2)
						{
							int inventoryFillSize = val.Data.InventoryFillSize;
							flag = true;
							PorterFill.Invoke(zombie, new object[3] { gameInventorySource2.Inventory, val3, num2 });
							num2 -= val.Data.InventoryFillSize - inventoryFillSize;
						}
					}
				}
				status = PorterExecutionStatus.Ready;
				return val.Data.InventoryFillSize > 0;
			}
			}
		}
		catch (Exception exception)
		{
			status = ((!flag) ? PorterExecutionStatus.Unavailable : PorterExecutionStatus.PartialFailure);
			LogAdapterException("PorterFillFromGlobal", exception);
			return false;
		}
	}

	internal static bool PorterCheckDeliveryStartGlobal(ZombieWgoData zombie, out PorterExecutionStatus status)
	{
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		status = PorterExecutionStatus.Unavailable;
		bool flag = false;
		try
		{
			if (((WgoData)zombie).GetGameResInt("is_staying_at_porter_station") != 1)
			{
				return false;
			}
			PorterStationDef data = ((GameBalanceBase)GameBalance.Me).GetData<PorterStationDef>(((ObjectLinkedToDefinition<WGODef>)(object)zombie.AttachedWgoData).id);
			if (data == null)
			{
				return false;
			}
			if (!PorterFillFromGlobal(zombie, data, out status))
			{
				return false;
			}
			flag = true;
			GDPointData gDPointDataById = MainGame.WorldData.gdPointsData.GetGDPointDataById(((ObjectLinkedToDefinition<WorldZoneDef>)(object)((WgoData)zombie).WorldZoneData).Definition.porterStartPoint);
			GDPointData gDPointDataById2 = MainGame.WorldData.gdPointsData.GetGDPointDataById(((ObjectLinkedToDefinition<WorldZoneDef>)(object)((WgoData)zombie).WorldZoneData).Definition.porterEndPoint);
			if (gDPointDataById == null || gDPointDataById2 == null)
			{
				status = PorterExecutionStatus.PartialFailure;
				return false;
			}
			((WgoData)zombie).Position = gDPointDataById.Position;
			if ((int)((WgoData)zombie).MovementComponent.StartPath(gDPointDataById2.Position, ((WgoData)zombie).WorldId, ((WgoData)zombie).WorldId, (MovementType)3, 1.5f, "", (Action)null, (Seeker)null, (DestinationType)0) != 0)
			{
				status = PorterExecutionStatus.PartialFailure;
				return false;
			}
			SetGameRes.Invoke(zombie, new object[2] { "is_staying_at_porter_station", 0 });
			SetGameRes.Invoke(zombie, new object[2] { "is_moving_to_target_world_zone", 1 });
			SetTrigger.Invoke(zombie.AttachedWgoData, new object[1] { "path_anim" });
			SetLayerWeight.Invoke(zombie, new object[2] { 4, 1f });
			((WgoData)zombie).IsInteractable = true;
			return true;
		}
		catch (Exception exception)
		{
			status = ((!flag) ? PorterExecutionStatus.Unavailable : PorterExecutionStatus.PartialFailure);
			LogAdapterException("PorterCheckDeliveryStartGlobal", exception);
			return false;
		}
	}

	private static bool IsPorterEnabledRegularItem(ZombieWgoData zombie, NeedItemData item)
	{
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Invalid comparison between Unknown and I4
		if (item == null)
		{
			return false;
		}
		if (zombie.AttachedWgoData.GetGameResInt(item.id) != 1)
		{
			return false;
		}
		if (item.ItemDef == null)
		{
			return false;
		}
		return (int)item.ItemDef.itemSize != 2;
	}

	private static int GetPorterRemainingRegularItemsWithStock(ZombieWgoData zombie, PorterStationDef stationDef, List<IInventorySource> sources, int startIndex)
	{
		int num = 0;
		for (int i = startIndex; i < stationDef.items.Count; i++)
		{
			NeedItemData val = stationDef.items[i];
			if (IsPorterEnabledRegularItem(zombie, val) && GetPorterSourceItemCount(sources, val.id) > 0)
			{
				num++;
			}
		}
		return num;
	}

	private static int GetPorterSourceItemCount(List<IInventorySource> sources, string itemId)
	{
		int num = 0;
		for (int i = 0; i < sources.Count; i++)
		{
			IInventorySource inventorySource = sources[i];
			if (inventorySource != null)
			{
				num += inventorySource.GetCount(itemId);
			}
		}
		return num;
	}

	private static bool ShouldSkipPoolInternalPorterTrip(PorterStationDef stationDef, StorageNetwork network)
	{
		bool skipEnabled = ZombieLogisticsPlugin.Settings != null && ZombieLogisticsPlugin.Settings.PorterSkipPoolInternalTransport.Value;
		bool poolInternalDestination = PorterDestinationIsInsidePool(stationDef, network);
		if (!PorterTransportPolicy.ShouldSkipTransport(skipEnabled, poolInternalDestination))
		{
			return false;
		}
		string text = ((stationDef == null) ? "" : ((BalanceBaseObject)stationDef).id);
		SharedStorageBridge.LogVerboseRateLimited("porter-pool-internal:" + text, "Porter trip of station '" + text + "' skipped: every delivery target already belongs to the shared storage pool.");
		return true;
	}

	private static bool PorterDestinationIsInsidePool(PorterStationDef stationDef, StorageNetwork network)
	{
		if (stationDef == null || network == null)
		{
			return false;
		}
		WorldData worldData = MainGame.WorldData;
		if (worldData == null)
		{
			return false;
		}
		List<string> customTargets = stationDef.customTargets;
		if (customTargets != null && customTargets.Count > 0)
		{
			int num = 0;
			int num2 = 0;
			bool hasVanillaSideEffect = false;
			for (int i = 0; i < customTargets.Count; i++)
			{
				WgoData wgoData = worldData.GetWgoData(customTargets[i]);
				if (wgoData == null)
				{
					continue;
				}
				if (HasVanillaDeliverySideEffect(wgoData))
				{
					hasVanillaSideEffect = true;
				}
				if (wgoData.Inventory != null)
				{
					num++;
					if (PoolContainsInventory(network, wgoData.Inventory))
					{
						num2++;
					}
				}
			}
			return PorterTransportPolicy.IsPoolInternalDestination(usesCustomTargets: true, num, num2, targetZoneInsidePool: false, hasVanillaSideEffect);
		}
		WorldZoneData worldZoneDataById = worldData.GetWorldZoneDataById(stationDef.targetWorldZone);
		bool targetZoneInsidePool = worldZoneDataById != null && worldZoneDataById.IsActive && SharedStorageApiCaller.IsCurrentZoneEligible(worldZoneDataById);
		return PorterTransportPolicy.IsPoolInternalDestination(usesCustomTargets: false, 0, 0, targetZoneInsidePool, hasVanillaSideEffect: false);
	}

	private static bool HasVanillaDeliverySideEffect(WgoData target)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Invalid comparison between Unknown and I4
		WGODef definition = ((ObjectLinkedToDefinition<WGODef>)(object)target).Definition;
		if (definition != null && (int)definition.interactionType == 31)
		{
			return true;
		}
		return ((ObjectLinkedToDefinition<WGODef>)(object)target).id == "crates_small";
	}

	private static bool PoolContainsInventory(StorageNetwork network, Inventory inventory)
	{
		if (!SourceListContainsInventory(network.PoolSources, inventory))
		{
			return SourceListContainsInventory(network.OtherZoneSources, inventory);
		}
		return true;
	}

	private static bool SourceListContainsInventory(List<IInventorySource> sources, Inventory inventory)
	{
		if (sources == null)
		{
			return false;
		}
		for (int i = 0; i < sources.Count; i++)
		{
			if (sources[i] is GameInventorySource gameInventorySource && gameInventorySource.Inventory == inventory)
			{
				return true;
			}
		}
		return false;
	}

	private static void LogAdapterException(string method, Exception exception)
	{
		SharedStorageBridge.ErrorRateLimited("Zombie logistics adapter " + method + " failed: " + exception.GetType().Name + ": " + exception.Message);
	}
}
[HarmonyPatch(typeof(ZombieWgoData), "CaretakerTryMoveToNearestInventoryWithRequiredItemCountToPickUp")]
internal static class Patch_CaretakerAnchor
{
	[HarmonyPrefix]
	private static bool Prefix(ZombieWgoData __instance)
	{
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		bool flag = false;
		try
		{
			if (ZombieLogisticsPlugin.Settings == null || ZombieLogisticsPlugin.Settings.IsRoleDisabled(ZombieRole.Caretaker))
			{
				return true;
			}
			if (__instance == null)
			{
				return true;
			}
			if (ZombieFailureGuardAccess.ShouldSkip(__instance, ZombieRole.Caretaker))
			{
				return false;
			}
			StorageNetwork network;
			IInventorySource anchor;
			switch (SharedStorageBridge.TryBuildNetwork(((WgoData)__instance).WorldZoneData, ((WgoData)__instance).Position.x, ((WgoData)__instance).Position.z, ZombieLogisticsPlugin.Settings.ExcludedContainerIds, out network, out anchor))
			{
			case NetworkBuildStatus.BlockedByExclusion:
				if (!ZombieWgoDataAccess.CaretakerFail(__instance))
				{
					SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
				}
				return false;
			default:
				SharedStorageBridge.WarnRateLimited("Caretaker anchor could not build the all-zone network; using vanilla behavior.");
				return true;
			case NetworkBuildStatus.Ready:
			{
				if (!(anchor is GameInventorySource gameInventorySource))
				{
					SharedStorageBridge.WarnRateLimited("Caretaker anchor was not a shared container; using vanilla behavior.");
					return true;
				}
				if (!ZombieWgoDataAccess.TryGetCaretakerTarget(__instance, out var target))
				{
					return true;
				}
				if (!ZombieWgoDataAccess.TrySetCaretakerTarget(__instance, gameInventorySource.UniqueId))
				{
					SharedStorageBridge.WarnRateLimited("Caretaker anchor target write failed; using vanilla behavior.");
					return true;
				}
				flag = true;
				if (!ZombieWgoDataAccess.TryCaretakerMoveToTarget(__instance))
				{
					if (!ZombieWgoDataAccess.TrySetCaretakerTarget(__instance, target))
					{
						SharedStorageBridge.ErrorRateLimited("Caretaker anchor movement failed and previous target restore failed; fail-closed.");
						if (!ZombieWgoDataAccess.CaretakerFail(__instance, forceGuard: true))
						{
							SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
						}
						return false;
					}
					return true;
				}
				return false;
			}
			}
		}
		catch (Exception ex)
		{
			if (!flag)
			{
				SharedStorageBridge.WarnRateLimited("Caretaker anchor setup failed before mutation; using vanilla behavior: " + ex.GetType().Name + ": " + ex.Message);
				return true;
			}
			SharedStorageBridge.ErrorRateLimited("Caretaker anchor failed after target mutation; fail-closed: " + ex.GetType().Name + ": " + ex.Message);
			if (!ZombieWgoDataAccess.CaretakerFail(__instance, forceGuard: true))
			{
				SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
			}
			return false;
		}
	}
}
[HarmonyPatch(typeof(ZombieWgoData), "CaretakerTryPickUpFromInventory")]
internal static class Patch_CaretakerPickup
{
	[HarmonyPrefix]
	private static bool Prefix(ZombieWgoData __instance, float deltaTime)
	{
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
		bool forceGuard = false;
		try
		{
			if (ZombieLogisticsPlugin.Settings == null || ZombieLogisticsPlugin.Settings.IsRoleDisabled(ZombieRole.Caretaker))
			{
				return true;
			}
			if (ZombieFailureGuardAccess.ShouldSkip(__instance, ZombieRole.Caretaker))
			{
				return false;
			}
			if (!ZombieWgoDataAccess.TryCaretakerTimerReached(__instance, deltaTime, out var reached))
			{
				SharedStorageBridge.WarnRateLimited("Caretaker pickup timer adapter failed; using vanilla behavior.");
				return true;
			}
			if (!reached)
			{
				return false;
			}
			DeliveryOrder order = ZombieWgoDataAccess.GetCaretakerOrder(__instance);
			if (order == null)
			{
				return true;
			}
			if (!ZombieWgoDataAccess.TryGetCaretakerPortableCount(__instance, out var count))
			{
				SharedStorageBridge.WarnRateLimited("Caretaker portable count could not be read; using vanilla behavior.");
				return true;
			}
			int count2 = ((OrderBase)order).Item.Count - count;
			StorageNetwork network;
			IInventorySource anchor;
			switch (SharedStorageBridge.TryBuildNetwork(((WgoData)__instance).WorldZoneData, ((WgoData)__instance).Position.x, ((WgoData)__instance).Position.z, ZombieLogisticsPlugin.Settings.ExcludedContainerIds, out network, out anchor))
			{
			case NetworkBuildStatus.BlockedByExclusion:
				if (!ZombieWgoDataAccess.CaretakerFail(__instance))
				{
					SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
				}
				return false;
			default:
				return true;
			case NetworkBuildStatus.Ready:
			{
				WithdrawalResult withdrawalResult;
				try
				{
					withdrawalResult = network.TryWithdraw(((ObjectLinkedToDefinition<ItemDef>)(object)((OrderBase)order).Item).id, count2, anchor, (IInventorySource source) => source is GameInventorySource gameInventorySource && gameInventorySource.AllowsItem(((OrderBase)order).Item));
				}
				catch (WithdrawalMutationUnknownException ex)
				{
					SharedStorageBridge.ErrorRateLimited("Caretaker withdrawal mutation is unknown; fail-closed: " + ex.GetType().Name + ": " + ex.Message);
					if (!ZombieWgoDataAccess.CaretakerFail(__instance, forceGuard: true))
					{
						SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
					}
					return false;
				}
				catch (WithdrawalRollbackException ex2)
				{
					SharedStorageBridge.ErrorRateLimited("Caretaker withdrawal rollback is incomplete; fail-closed: " + ex2.GetType().Name + ": " + ex2.Message);
					if (!ZombieWgoDataAccess.CaretakerFail(__instance, forceGuard: true))
					{
						SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
					}
					return false;
				}
				catch (WithdrawalMutationException ex3)
				{
					SharedStorageBridge.ErrorRateLimited("Caretaker withdrawal mutated inventory and rolled back; fail-closed: " + ex3.GetType().Name + ": " + ex3.Message);
					if (!ZombieWgoDataAccess.CaretakerFail(__instance, forceGuard: true))
					{
						SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
					}
					return false;
				}
				catch (Exception ex4)
				{
					SharedStorageBridge.ErrorRateLimited("Caretaker withdrawal threw before a confirmed mutation: " + ex4.GetType().Name + ": " + ex4.Message);
					if (!ZombieWgoDataAccess.CaretakerFail(__instance))
					{
						SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
					}
					return false;
				}
				if (withdrawalResult == null || !withdrawalResult.Success)
				{
					SharedStorageBridge.ErrorRateLimited("Caretaker withdrawal failed without a partial deduction; item='" + ((ObjectLinkedToDefinition<ItemDef>)(object)((OrderBase)order).Item).id + "', required=" + count2 + ".");
					if (!ZombieWgoDataAccess.CaretakerFail(__instance))
					{
						SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
					}
					return false;
				}
				forceGuard = true;
				Item item = BuildCaretakerPortable(withdrawalResult, ((ObjectLinkedToDefinition<ItemDef>)(object)((OrderBase)order).Item).id, ((OrderBase)order).Item.Count);
				if (!ZombieWgoDataAccess.TrySetCaretakerPortable(__instance, item))
				{
					bool flag = false;
					try
					{
						RollbackResult rollbackResult = network.Rollback(withdrawalResult);
						flag = !rollbackResult.Complete;
						if (flag)
						{
							SharedStorageBridge.ErrorRateLimited("Caretaker rollback is incomplete; unrestored=" + rollbackResult.Unrestored.Count + ", failure=" + ((rollbackResult.Failure == null) ? "<none>" : (rollbackResult.Failure.GetType().Name + ": " + rollbackResult.Failure.Message)));
						}
					}
					catch (Exception ex5)
					{
						flag = true;
						SharedStorageBridge.ErrorRateLimited("Caretaker portable state write failed and inventory rollback threw: " + ex5.GetType().Name + ": " + ex5.Message);
					}
					SharedStorageBridge.ErrorRateLimited(flag ? "Caretaker portable state write failed and inventory rollback did not complete." : "Caretaker portable state write failed after withdrawal; inventory was rolled back.");
					if (!ZombieWgoDataAccess.CaretakerFail(__instance, flag))
					{
						SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
					}
					return false;
				}
				if (!ZombieWgoDataAccess.TryCaretakerMoveToZombie(__instance))
				{
					SharedStorageBridge.ErrorRateLimited("Caretaker delivery move failed after portable state was written; inventory rollback skipped.");
					if (!ZombieWgoDataAccess.CaretakerFail(__instance, forceGuard: true))
					{
						SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
					}
					return false;
				}
				return false;
			}
			}
		}
		catch (Exception ex6)
		{
			SharedStorageBridge.ErrorRateLimited("Caretaker pickup adapter failed: " + ex6.GetType().Name + ": " + ex6.Message);
			if (!ZombieWgoDataAccess.CaretakerFail(__instance, forceGuard))
			{
				SharedStorageBridge.ErrorRateLimited("Caretaker fail path failed.");
			}
			return false;
		}
	}

	private static Item BuildCaretakerPortable(WithdrawalResult withdrawal, string itemId, int count)
	{
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Expected O, but got Unknown
		List<Item> list = new List<Item>();
		if (withdrawal != null && withdrawal.Lines != null)
		{
			for (int i = 0; i < withdrawal.Lines.Count; i++)
			{
				WithdrawalLine withdrawalLine = withdrawal.Lines[i];
				if (withdrawalLine == null || withdrawalLine.Items == null)
				{
					continue;
				}
				for (int j = 0; j < withdrawalLine.Items.Count; j++)
				{
					Item val = withdrawalLine.Items[j];
					if (val != null && val.Count > 0)
					{
						list.Add(val);
					}
				}
			}
		}
		if (list.Count != 1 || !(((ObjectLinkedToDefinition<ItemDef>)(object)list[0]).id == itemId) || list[0].Count != count)
		{
			return new Item(itemId, count);
		}
		return list[0];
	}
}
[HarmonyPatch(typeof(ZombieWgoData), "CaretakerTryGetNewOrder")]
internal static class Patch_CaretakerTryGetNewOrder
{
	[HarmonyPrefix]
	private static bool Prefix(ZombieWgoData __instance, out bool __state)
	{
		__state = false;
		if (ZombieLogisticsPlugin.Settings == null || ZombieLogisticsPlugin.Settings.IsRoleDisabled(ZombieRole.Caretaker))
		{
			return true;
		}
		if (ZombieFailureGuardAccess.ShouldSkip(__instance, ZombieRole.Caretaker))
		{
			return false;
		}
		ZombieScope.Enter(ZombieRole.Caretaker, ZombieFailureGuardAccess.GetKey(__instance));
		__state = true;
		return true;
	}

	[HarmonyFinalizer]
	private static void Finalizer(bool __state)
	{
		if (__state)
		{
			ZombieScope.Exit();
		}
	}
}
[HarmonyPatch(typeof(WorldZoneData), "CanDeliveryOrderBeTakenOnExecution")]
internal static class Patch_CanDeliveryOrder
{
	[HarmonyPrefix]
	private static bool Prefix(WorldZoneData __instance, DeliveryOrder deliveryOrder, int currentCount, ref bool __result)
	{
		if (ZombieLogisticsPlugin.Settings == null || ZombieLogisticsPlugin.Settings.IsRoleDisabled(ZombieRole.Caretaker))
		{
			return true;
		}
		if (ZombieFailureGuardAccess.ShouldSkipCurrentScope(ZombieRole.Caretaker))
		{
			__result = false;
			return false;
		}
		if (!ZombieScope.Is(ZombieRole.Caretaker))
		{
			return true;
		}
		try
		{
			if (__instance == null || deliveryOrder == null || ((OrderBase)deliveryOrder).Item == null)
			{
				SharedStorageBridge.WarnRateLimited("Caretaker delivery check could not read the delivery order; using vanilla behavior.");
				return true;
			}
			int num = ((OrderBase)deliveryOrder).Item.Count - currentCount;
			if (num <= 0)
			{
				__result = true;
				return false;
			}
			StorageNetwork network;
			IInventorySource anchor;
			switch (SharedStorageBridge.TryBuildNetwork(__instance, __instance.pos.x, __instance.pos.z, ZombieLogisticsPlugin.Settings.ExcludedContainerIds, out network, out anchor))
			{
			case NetworkBuildStatus.BlockedByExclusion:
				__result = false;
				return false;
			default:
				SharedStorageBridge.WarnRateLimited("Caretaker delivery check could not build the all-zone network; using vanilla behavior.");
				return true;
			case NetworkBuildStatus.Ready:
			{
				int num2 = 0;
				List<IInventorySource> sources = network.GetSources();
				for (int i = 0; i < sources.Count; i++)
				{
					if (sources[i] is GameInventorySource gameInventorySource && gameInventorySource.AllowsItem(((OrderBase)deliveryOrder).Item))
					{
						num2 += gameInventorySource.GetCount(((ObjectLinkedToDefinition<ItemDef>)(object)((OrderBase)deliveryOrder).Item).id);
					}
				}
				__result = num2 >= num;
				return false;
			}
			}
		}
		catch (Exception ex)
		{
			SharedStorageBridge.WarnRateLimited("Caretaker delivery check failed: " + ex.GetType().Name + ": " + ex.Message);
			return true;
		}
	}
}
[HarmonyPatch(typeof(ZombieWgoData), "GardenerTryTakeSeedsFromMultiInventory")]
internal static class Patch_GardenerPickup
{
	[HarmonyPrefix]
	private static bool Prefix(ZombieWgoData __instance)
	{
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0277: Unknown result type (might be due to invalid IL or missing references)
		//IL_0281: Expected O, but got Unknown
		if (ZombieLogisticsPlugin.Settings == null || ZombieLogisticsPlugin.Settings.IsRoleDisabled(ZombieRole.Gardener))
		{
			return true;
		}
		if (ZombieFailureGuardAccess.ShouldSkip(__instance, ZombieRole.Gardener))
		{
			return false;
		}
		StorageNetwork network = null;
		WithdrawalResult withdrawalResult = null;
		InventoryTransferResult inventoryTransferResult = null;
		bool flag = false;
		bool flag2 = false;
		bool flag3 = false;
		try
		{
			OrderBase gardenerOrder = ZombieWgoDataAccess.GetGardenerOrder(__instance);
			if (gardenerOrder == null)
			{
				return true;
			}
			PlantOrder val = (PlantOrder)(object)((gardenerOrder is PlantOrder) ? gardenerOrder : null);
			if (val == null)
			{
				return true;
			}
			WgoData wgoData = MainGame.WorldData.GetWgoData(gardenerOrder.TargetWgoUniqueId);
			if (wgoData == null || wgoData.CraftComponent.IsStarted)
			{
				return true;
			}
			flag3 = true;
			IInventorySource anchor;
			NetworkBuildStatus networkBuildStatus = SharedStorageBridge.TryBuildNetwork(((WgoData)__instance).WorldZoneData, ((WgoData)__instance).Position.x, ((WgoData)__instance).Position.z, ZombieLogisticsPlugin.Settings.ExcludedContainerIds, out network, out anchor);
			if (networkBuildStatus != NetworkBuildStatus.Ready)
			{
				SharedStorageBridge.WarnRateLimited("Gardener pickup network is not ready; releasing the stale order.");
				return ReleaseStaleOrder(__instance);
			}
			if (!ZombieWgoDataAccess.TryGetGardenerCandidates(val, out var candidates))
			{
				SharedStorageBridge.WarnRateLimited("Gardener seed candidates are unavailable; releasing the stale order.");
				return ReleaseStaleOrder(__instance);
			}
			string seedItemId;
			int count;
			bool flag4 = ZombieWgoDataAccess.TryGetGardenerCarriedSeed(__instance, candidates, out seedItemId, out count);
			int need = GardenerSeedInventoryPolicy.GetRemainingNeed(gardenerOrder.Item.Count, flag4 ? count : 0);
			if (need <= 0)
			{
				if (!ZombieWgoDataAccess.TryGardenerMoveToTarget(__instance))
				{
					SharedStorageBridge.ErrorRateLimited("Gardener move failed while using carried seeds.");
					if (!ZombieWgoDataAccess.GardenerFail(__instance, forceGuard: true))
					{
						SharedStorageBridge.ErrorRateLimited("Gardener fail path failed.");
					}
				}
				return false;
			}
			string seedItemId2;
			GardenerSeedResolution resolution = ZombieWgoDataAccess.TryResolveGardenerSeed(network, val, need, out seedItemId2, flag4 ? seedItemId : null);
			if (GardenerPickupPolicy.ForResolution(networkBuildStatus, resolution) == GardenerPickupAction.ReleaseOrder)
			{
				string text = DescribeSeedStock(network.GetSources(), candidates);
				SharedStorageBridge.WarnRateLimited("Gardener pickup cannot resolve seed stock; order=" + ((object)((OrderBase)val).UniqueId)?.ToString() + ", target=" + ((object)((OrderBase)val).TargetWgoUniqueId)?.ToString() + ", item=" + ((ObjectLinkedToDefinition<ItemDef>)(object)((OrderBase)val).Item).id + ", need=" + need + ", candidates=[" + string.Join(",", candidates ?? new List<string>()) + "], stock={" + text + "}.");
				return ReleaseStaleOrder(__instance);
			}
			List<Item> list = new List<Item>
			{
				new Item(seedItemId2, need)
			};
			if (__instance.WorkerInventory == null || !__instance.WorkerInventory.CanAddItemsToInventory(list))
			{
				SharedStorageBridge.WarnRateLimited("Gardener worker inventory cannot hold the order; releasing the stale order.");
				return ReleaseStaleOrder(__instance);
			}
			try
			{
				withdrawalResult = network.TryWithdraw(seedItemId2, need, anchor, (IInventorySource source) => source is GameInventorySource gameInventorySource && gameInventorySource.AllowsItem(new Item(seedItemId2, need)));
			}
			catch (WithdrawalMutationUnknownException ex)
			{
				SharedStorageBridge.ErrorRateLimited("Gardener withdrawal mutation is unknown; fail-closed. " + ex.GetType().Name + ": " + ex.Message);
				if (!ZombieWgoDataAccess.GardenerFail(__instance, forceGuard: true))
				{
					SharedStorageBridge.ErrorRateLimited("Gardener fail path failed.");
				}
				return false;
			}
			catch (WithdrawalRollbackException ex2)
			{
				SharedStorageBridge.ErrorRateLimited("Gardener withdrawal rollback is incomplete; fail-closed. " + ex2.GetType().Name + ": " + ex2.Message);
				if (!ZombieWgoDataAccess.GardenerFail(__instance, forceGuard: true))
				{
					SharedStorageBridge.ErrorRateLimited("Gardener fail path failed.");
				}
				return false;
			}
			catch (WithdrawalMutationException ex3)
			{
				SharedStorageBridge.ErrorRateLimited("Gardener withdrawal mutated inventory and rolled back; releasing the stale order. " + ex3.GetType().Name + ": " + ex3.Message);
				return ReleaseStaleOrder(__instance);
			}
			catch (Exception ex4)
			{
				SharedStorageBridge.ErrorRateLimited("Gardener withdrawal threw before a confirmed mutation; releasing the stale order. " + ex4.GetType().Name + ": " + ex4.Message);
				return ReleaseStaleOrder(__instance);
			}
			if (withdrawalResult == null || !withdrawalResult.Success)
			{
				SharedStorageBridge.ErrorRateLimited("Gardener withdrawal failed without a partial deduction; seed='" + seedItemId2 + "', required=" + need + ". Releasing the stale order.");
				return ReleaseStaleOrder(__instance);
			}
			flag = true;
			List<Item> list2 = CollectWithdrawalItems(withdrawalResult);
			if (list2.Count == 0)
			{
				throw new InvalidOperationException("Gardener withdrawal produced no item instances.");
			}
			inventoryTransferResult = ZombieWgoDataAccess.TryAddGardenerItemsReconciled(__instance, list2);
			if (inventoryTransferResult == null)
			{
				throw new InvalidOperationException("Gardener transfer reconciliation returned null.");
			}
			if (inventoryTransferResult.Complete)
			{
				flag2 = true;
				flag = false;
				if (inventoryTransferResult.Failure != null)
				{
					SharedStorageBridge.WarnRateLimited("Gardener worker inventory added all seeds but reported: " + inventoryTransferResult.Failure.GetType().Name + ": " + inventoryTransferResult.Failure.Message);
				}
				if (!ZombieWgoDataAccess.TryGardenerMoveToTarget(__instance))
				{
					SharedStorageBridge.ErrorRateLimited("Gardener move failed after worker inventory received seeds; source rollback skipped.");
					if (!ZombieWgoDataAccess.GardenerFail(__instance, forceGuard: true))
					{
						SharedStorageBridge.ErrorRateLimited("Gardener fail path failed.");
					}
					return false;
				}
				return false;
			}
			RollbackGardenerPartial(network, withdrawalResult, inventoryTransferResult.Added);
			flag = false;
			SharedStorageBridge.ErrorRateLimited("Gardener worker inventory did not receive all seeds; partial rollback applied. Added=" + FormatAdded(inventoryTransferResult.Added) + ".");
			if (!ZombieWgoDataAccess.GardenerFail(__instance, forceGuard: true))
			{
				SharedStorageBridge.ErrorRateLimited("Gardener fail path failed.");
			}
			return false;
		}
		catch (Exception ex5)
		{
			if (flag2)
			{
				SharedStorageBridge.ErrorRateLimited("Gardener failed after worker inventory received seeds; source rollback skipped: " + ex5.GetType().Name + ": " + ex5.Message);
				if (!ZombieWgoDataAccess.GardenerFail(__instance, forceGuard: true))
				{
					SharedStorageBridge.ErrorRateLimited("Gardener fail path failed.");
				}
				return false;
			}
			if (flag)
			{
				RollbackGardenerPartial(network, withdrawalResult, inventoryTransferResult?.Added);
				SharedStorageBridge.ErrorRateLimited("Gardener failed after withdrawal; partial rollback attempted: " + ex5.GetType().Name + ": " + ex5.Message);
				if (!ZombieWgoDataAccess.GardenerFail(__instance, forceGuard: true))
				{
					SharedStorageBridge.ErrorRateLimited("Gardener fail path failed.");
				}
				return false;
			}
			if (flag3)
			{
				SharedStorageBridge.WarnRateLimited("Gardener pickup setup failed before mutation; releasing the stale order: " + ex5.GetType().Name + ": " + ex5.Message);
				return ReleaseStaleOrder(__instance);
			}
			SharedStorageBridge.WarnRateLimited("Gardener pickup setup failed; using vanilla behavior: " + ex5.GetType().Name + ": " + ex5.Message);
			return true;
		}
	}

	private static string DescribeSeedStock(List<IInventorySource> sources, List<string> candidates)
	{
		if (candidates == null || candidates.Count == 0)
		{
			return "no-candidates";
		}
		List<string> list = new List<string>();
		for (int i = 0; i < candidates.Count; i++)
		{
			string text = candidates[i];
			int num = 0;
			List<string> list2 = new List<string>();
			for (int j = 0; j < sources.Count; j++)
			{
				if (sources[j] is GameInventorySource gameInventorySource)
				{
					int count = gameInventorySource.GetCount(text);
					num += count;
					if (count > 0)
					{
						list2.Add(gameInventorySource.ZoneId + "/" + gameInventorySource.ContainerId + "=" + count);
					}
				}
			}
			list.Add(text + ":total=" + num + ((list2.Count > 0) ? (" [" + string.Join(",", list2) + "]") : " [empty]"));
		}
		return string.Join("; ", list);
	}

	private static bool Rel