Decompiled source of ZombieLogistics v1.0.6
GraveyardKeeper2.ZombieLogistics.dll
Decompiled 4 days ago
The result has been truncated due to the large size, download it to view full contents!
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