Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CupboardWipeProtection v1.2.0
CupboardWipeProtection.dll
Decompiled 2 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("CupboardWipeProtection")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0")] [assembly: AssemblyProduct("CupboardWipeProtection")] [assembly: AssemblyTitle("CupboardWipeProtection")] [assembly: AssemblyVersion("1.2.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace CupboardWipeProtection { [BepInPlugin("cupboardwipeprotection.plugin", "Cupboard Wipe Protection", "1.2.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "cupboardwipeprotection.plugin"; public const string PluginName = "Cupboard Wipe Protection"; public const string PluginVersion = "1.2.0"; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<bool> EnableTotalValueLimit; internal static ConfigEntry<int> MaximumProtectedTotalScrapValue; internal static ConfigEntry<bool> EnablePerItemValueLimit; internal static ConfigEntry<int> MaximumProtectedItemScrapValue; internal static ConfigEntry<float> SurfaceTolerance; internal static ConfigEntry<float> CabinetBoundsPadding; internal static ConfigEntry<bool> VerboseLogging; private Harmony _harmony; private void Awake() { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Expected O, but got Unknown //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Keep items stored in the ship cupboard when the entire crew is lost."); EnableTotalValueLimit = ((BaseUnityPlugin)this).Config.Bind<bool>("Value limits", "Enable total value limit", false, "Limit the combined value of all scrap protected by the cupboard."); MaximumProtectedTotalScrapValue = ((BaseUnityPlugin)this).Config.Bind<int>("Value limits", "Maximum total protected scrap value", 200, new ConfigDescription("Maximum combined scrap value protected after a wipe when the total value limit is enabled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000000), Array.Empty<object>())); EnablePerItemValueLimit = ((BaseUnityPlugin)this).Config.Bind<bool>("Value limits", "Enable per-item value limit", true, "Remove individual scrap items worth more than the configured per-item limit during a full-crew wipe."); MaximumProtectedItemScrapValue = ((BaseUnityPlugin)this).Config.Bind<int>("Value limits", "Maximum protected value per scrap item", 200, new ConfigDescription("Maximum value of a single scrap item that the cupboard can protect when the per-item limit is enabled.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 1000000), Array.Empty<object>())); SurfaceTolerance = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "Shelf tolerance", 0.14f, "Maximum distance in metres between an item and a cupboard shelf surface."); CabinetBoundsPadding = ((BaseUnityPlugin)this).Config.Bind<float>("Detection", "Cabinet bounds padding", 0.08f, "Extra margin in metres around the cupboard model used by fallback detection."); VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "Verbose logging", false, "Log every item classified as inside or outside the cupboard during a wipe."); _harmony = new Harmony("cupboardwipeprotection.plugin"); _harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Cupboard Wipe Protection 1.2.0 loaded."); } private void OnDestroy() { ProtectionState.Clear(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal static class ProtectionState { private sealed class ItemGroup { internal readonly List<GrabbableObject> Items; internal readonly int ScrapValue; internal readonly int MaximumIndividualScrapValue; internal readonly string Label; internal readonly ulong NetworkObjectId; private ItemGroup(List<GrabbableObject> items, int scrapValue, int maximumIndividualScrapValue, string label, ulong networkObjectId) { Items = items; ScrapValue = scrapValue; MaximumIndividualScrapValue = maximumIndividualScrapValue; Label = label; NetworkObjectId = networkObjectId; } internal static ItemGroup FromRoot(GrabbableObject root) { List<GrabbableObject> list = new List<GrabbableObject>(); CollectGroupItems(root, list, new HashSet<GrabbableObject>()); int scrapValue = ((IEnumerable<GrabbableObject>)list).Sum((Func<GrabbableObject, int>)ItemScrapValue); int maximumIndividualScrapValue = ((list.Count != 0) ? ((IEnumerable<GrabbableObject>)list).Max((Func<GrabbableObject, int>)ItemScrapValue) : 0); ulong networkObjectId = (((Object)(object)((NetworkBehaviour)root).NetworkObject != (Object)null) ? ((NetworkBehaviour)root).NetworkObjectId : ulong.MaxValue); return new ItemGroup(list, scrapValue, maximumIndividualScrapValue, ItemName(root), networkObjectId); } private static void CollectGroupItems(GrabbableObject item, List<GrabbableObject> items, HashSet<GrabbableObject> visited) { if ((Object)(object)item == (Object)null || !visited.Add(item)) { return; } items.Add(item); BeltBagItem val = (BeltBagItem)(object)((item is BeltBagItem) ? item : null); if (val != null && val.objectsInBag != null) { GrabbableObject[] array = val.objectsInBag.ToArray(); for (int i = 0; i < array.Length; i++) { CollectGroupItems(array[i], items, visited); } } } private static int ItemScrapValue(GrabbableObject item) { if (!((Object)(object)item?.itemProperties != (Object)null) || !item.itemProperties.isScrap) { return 0; } return Math.Max(0, item.scrapValue); } } private static readonly HashSet<NetworkObject> ProtectedObjects = new HashSet<NetworkObject>(); private static bool _active; internal static bool IsProtected(NetworkObject networkObject) { if (_active && (Object)(object)networkObject != (Object)null) { return ProtectedObjects.Contains(networkObject); } return false; } internal static void Begin() { Clear(); if (!Plugin.Enabled.Value) { return; } StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || !((NetworkBehaviour)instance).IsServer || !instance.allPlayersDead) { return; } CabinetDetector cabinetDetector = CabinetDetector.Create(instance); if (cabinetDetector == null) { Plugin.Log.LogWarning((object)"A full-crew wipe was detected, but the ship cupboard could not be located. No items were protected."); return; } GrabbableObject[] array = Object.FindObjectsByType<GrabbableObject>((FindObjectsInactive)1, (FindObjectsSortMode)0); HashSet<GrabbableObject> hashSet = FindContainedItems(array); List<ItemGroup> list = new List<ItemGroup>(); GrabbableObject[] array2 = array; foreach (GrabbableObject val in array2) { if (!((Object)(object)val == (Object)null) && !val.deactivated && !val.isHeld && !val.isHeldByEnemy && !hashSet.Contains(val)) { bool flag = cabinetDetector.Contains(val); if (Plugin.VerboseLogging.Value) { Plugin.Log.LogInfo((object)("Cupboard check: " + ItemName(val) + " => " + (flag ? "inside" : "outside"))); } if (flag) { list.Add(ItemGroup.FromRoot(val)); } } } if (list.Count == 0) { Plugin.Log.LogInfo((object)"Full-crew wipe: the cupboard was empty, so no items were protected."); Clear(); return; } bool perItemLimitEnabled = Plugin.EnablePerItemValueLimit.Value; int perItemLimit = Plugin.MaximumProtectedItemScrapValue.Value; List<ItemGroup> list2 = list.Where((ItemGroup group) => !perItemLimitEnabled || group.MaximumIndividualScrapValue <= perItemLimit).ToList(); List<ItemGroup> list3 = list.Where((ItemGroup group) => perItemLimitEnabled && group.MaximumIndividualScrapValue > perItemLimit).ToList(); bool value = Plugin.EnableTotalValueLimit.Value; int value2 = Plugin.MaximumProtectedTotalScrapValue.Value; List<ItemGroup> list4 = (value ? SelectGroupsWithinValueLimit(list2, value2) : list2.ToList()); int num = 0; foreach (ItemGroup item in list4) { foreach (GrabbableObject item2 in item.Items) { num += (ProtectSingle(item2) ? 1 : 0); } } int num2 = list.Sum((ItemGroup group) => group.ScrapValue); int num3 = list4.Sum((ItemGroup group) => group.ScrapValue); int num4 = list2.Count - list4.Count; int num5 = list3.Count + num4; if (Plugin.VerboseLogging.Value && num5 > 0) { foreach (ItemGroup item3 in list3) { Plugin.Log.LogInfo((object)$"Per-item limit: removing {item3.Label} group because it contains scrap worth ${item3.MaximumIndividualScrapValue}, above the ${perItemLimit} limit."); } HashSet<ItemGroup> selectedSet = new HashSet<ItemGroup>(list4); foreach (ItemGroup item4 in list2.Where((ItemGroup group) => !selectedSet.Contains(group))) { Plugin.Log.LogInfo((object)$"Total value limit: removing {item4.Label} group worth ${item4.ScrapValue}."); } } if (num == 0) { Plugin.Log.LogInfo((object)$"Full-crew wipe: cupboard value was ${num2}, but no item group passed the enabled value limits."); Clear(); return; } _active = true; string text = (perItemLimitEnabled ? $"${perItemLimit}" : "off"); string text2 = (value ? $"${value2}" : "off"); Plugin.Log.LogInfo((object)$"Full-crew wipe: protected {num} cupboard item object(s) worth ${num3} of ${num2}. Per-item limit: {text}; total limit: {text2}. {num5} over-limit group(s) will be removed normally."); } private static HashSet<GrabbableObject> FindContainedItems(IEnumerable<GrabbableObject> items) { HashSet<GrabbableObject> hashSet = new HashSet<GrabbableObject>(); foreach (BeltBagItem item in items.OfType<BeltBagItem>()) { AddContainedItems(item, hashSet); } return hashSet; } private static void AddContainedItems(BeltBagItem beltBag, HashSet<GrabbableObject> contained) { if (beltBag.objectsInBag == null) { return; } GrabbableObject[] array = beltBag.objectsInBag.ToArray(); foreach (GrabbableObject val in array) { if (!((Object)(object)val == (Object)null) && contained.Add(val)) { BeltBagItem val2 = (BeltBagItem)(object)((val is BeltBagItem) ? val : null); if (val2 != null) { AddContainedItems(val2, contained); } } } } private static List<ItemGroup> SelectGroupsWithinValueLimit(List<ItemGroup> groups, int configuredLimit) { List<ItemGroup> list = groups.Where((ItemGroup group) => group.ScrapValue == 0).ToList(); List<ItemGroup> list2 = (from @group in groups where @group.ScrapValue > 0 orderby @group.NetworkObjectId select @group).ThenBy<ItemGroup, string>((ItemGroup group) => group.Label, StringComparer.Ordinal).ToList(); int val = list2.Sum((ItemGroup group) => group.ScrapValue); int num = Math.Min(configuredLimit, val); if (num <= 0) { return list; } bool[] array = new bool[num + 1]; int[] array2 = Enumerable.Repeat(-1, num + 1).ToArray(); int[] array3 = Enumerable.Repeat(-1, num + 1).ToArray(); array[0] = true; for (int num2 = 0; num2 < list2.Count; num2++) { int scrapValue = list2[num2].ScrapValue; if (scrapValue > num) { continue; } for (int num3 = num - scrapValue; num3 >= 0; num3--) { int num4 = num3 + scrapValue; if (array[num3] && !array[num4]) { array[num4] = true; array2[num4] = num3; array3[num4] = num2; } } } int num5 = num; while (num5 > 0 && !array[num5]) { num5--; } while (num5 > 0) { int num6 = array3[num5]; if (num6 < 0) { break; } list.Add(list2[num6]); num5 = array2[num5]; } return list; } private static bool ProtectSingle(GrabbableObject item) { NetworkObject networkObject = ((NetworkBehaviour)item).NetworkObject; if ((Object)(object)networkObject == (Object)null || !networkObject.IsSpawned) { Plugin.Log.LogWarning((object)("Could not protect " + ItemName(item) + " because it has no spawned NetworkObject.")); return false; } item.isInShipRoom = true; item.isInElevator = true; item.isInFactory = false; item.scrapPersistedThroughRounds = true; return ProtectedObjects.Add(networkObject); } internal static void Clear() { _active = false; ProtectedObjects.Clear(); } private static string ItemName(GrabbableObject item) { object obj; if (!((Object)(object)item?.itemProperties != (Object)null)) { obj = ((item != null) ? ((Object)item).name : null); if (obj == null) { return "unknown item"; } } else { obj = item.itemProperties.itemName; } return (string)obj; } } internal sealed class CabinetDetector { private static readonly string[] CabinetNameHints = new string[4] { "closet", "cupboard", "cabinet", "storage" }; private readonly Transform _root; private readonly PlaceableObjectsSurface[] _surfaces; private readonly Bounds? _cabinetBounds; private CabinetDetector(Transform root, PlaceableObjectsSurface[] surfaces, Bounds? cabinetBounds) { _root = root; _surfaces = surfaces; _cabinetBounds = cabinetBounds; } internal static CabinetDetector Create(StartOfRound round) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)round.closetLeftDoor == (Object)null || (Object)(object)round.closetRightDoor == (Object)null) { return null; } Transform transform = ((Component)round.closetLeftDoor).transform; Transform transform2 = ((Component)round.closetRightDoor).transform; Transform root = FindCabinetRoot(transform, transform2); Vector3 doorMidpoint = (transform.position + transform2.position) * 0.5f; Bounds? cabinetBounds = TryGetCabinetBounds(root, transform, transform2); if (!cabinetBounds.HasValue) { root = null; } PlaceableObjectsSurface[] source = Object.FindObjectsByType<PlaceableObjectsSurface>((FindObjectsInactive)1, (FindObjectsSortMode)0); PlaceableObjectsSurface[] array = source.Where((PlaceableObjectsSurface surface) => IsCupboardSurface(surface, root)).ToArray(); if (array.Length == 0) { array = (from surface in source where (Object)(object)surface != (Object)null && (Object)(object)surface.placeableBounds != (Object)null where SurfaceDistanceSquared(surface, doorMidpoint) <= 2.25f orderby SurfaceDistanceSquared(surface, doorMidpoint) select surface).Take(4).ToArray(); } if (array.Length == 0 && !cabinetBounds.HasValue) { return null; } if (Plugin.VerboseLogging.Value) { string arg = (((Object)(object)root != (Object)null) ? HierarchyPath(root) : "unresolved"); Plugin.Log.LogInfo((object)string.Format("Cupboard detector: root={0}, shelf surfaces={1}, bounds={2}", arg, array.Length, cabinetBounds.HasValue ? ((object)cabinetBounds.Value/*cast due to .constrained prefix*/).ToString() : "none")); } return new CabinetDetector(root, array, cabinetBounds); } internal bool Contains(GrabbableObject item) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)item.parentObject != (Object)null && IsWithinRoot(item.parentObject, _root)) { return true; } Transform parent = ((Component)item).transform.parent; if ((Object)(object)parent != (Object)null && IsWithinRoot(parent, _root)) { return true; } PlaceableObjectsSurface[] surfaces = _surfaces; foreach (PlaceableObjectsSurface surface in surfaces) { if (IsOnSurface(item, surface)) { return true; } } if (!_cabinetBounds.HasValue) { return false; } Bounds value = _cabinetBounds.Value; ((Bounds)(ref value)).Expand(Mathf.Max(0f, Plugin.CabinetBoundsPadding.Value) * 2f); if (((Bounds)(ref value)).Contains(((Component)item).transform.position)) { return true; } Renderer mainObjectRenderer = (Renderer)(object)item.mainObjectRenderer; if ((Object)(object)mainObjectRenderer != (Object)null) { Bounds bounds = mainObjectRenderer.bounds; return ((Bounds)(ref value)).Contains(((Bounds)(ref bounds)).center); } return false; } private static bool IsOnSurface(GrabbableObject item, PlaceableObjectsSurface surface) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)surface == (Object)null || (Object)(object)surface.placeableBounds == (Object)null) { return false; } float num = (((Object)(object)item.itemProperties != (Object)null) ? item.itemProperties.verticalOffset : 0f); Vector3 val = ((Component)item).transform.position - ((Component)surface.placeableBounds).transform.up * num; Vector3 val2 = surface.placeableBounds.ClosestPoint(val); float num2 = Mathf.Max(0.01f, Plugin.SurfaceTolerance.Value); Vector3 val3 = val2 - val; return ((Vector3)(ref val3)).sqrMagnitude <= num2 * num2; } private static bool IsCupboardSurface(PlaceableObjectsSurface surface, Transform root) { if ((Object)(object)surface == (Object)null || (Object)(object)surface.placeableBounds == (Object)null || (Object)(object)root == (Object)null) { return false; } if (IsWithinRoot(((Component)surface).transform, root) || IsWithinRoot(((Component)surface.placeableBounds).transform, root)) { return true; } if ((Object)(object)surface.parentTo != (Object)null) { return IsWithinRoot(((Component)surface.parentTo).transform, root); } return false; } private static float SurfaceDistanceSquared(PlaceableObjectsSurface surface, Vector3 point) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) Vector3 val = surface.placeableBounds.ClosestPoint(point) - point; return ((Vector3)(ref val)).sqrMagnitude; } private static Transform FindCabinetRoot(Transform leftDoor, Transform rightDoor) { HashSet<Transform> hashSet = new HashSet<Transform>(); Transform val = rightDoor; while ((Object)(object)val != (Object)null) { hashSet.Add(val); val = val.parent; } Transform val2 = null; while ((Object)(object)leftDoor != (Object)null) { if (hashSet.Contains(leftDoor)) { if (val2 == null) { val2 = leftDoor; } if (CabinetNameHints.Any((string hint) => ((Object)leftDoor).name.IndexOf(hint, StringComparison.OrdinalIgnoreCase) >= 0)) { return leftDoor; } } leftDoor = leftDoor.parent; } return val2; } private static Bounds? TryGetCabinetBounds(Transform root, Transform leftDoor, Transform rightDoor) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)root == (Object)null) { return null; } Renderer[] componentsInChildren = ((Component)root).GetComponentsInChildren<Renderer>(true); Collider[] componentsInChildren2 = ((Component)root).GetComponentsInChildren<Collider>(true); bool flag = false; Bounds value = default(Bounds); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { if (!((Object)(object)val == (Object)null) && !IsDoorPart(((Component)val).transform, root, leftDoor, rightDoor)) { if (!flag) { value = val.bounds; flag = true; } else { ((Bounds)(ref value)).Encapsulate(val.bounds); } } } if (!flag) { Collider[] array2 = componentsInChildren2; foreach (Collider val2 in array2) { if (!((Object)(object)val2 == (Object)null) && !IsDoorPart(((Component)val2).transform, root, leftDoor, rightDoor)) { if (!flag) { value = val2.bounds; flag = true; } else { ((Bounds)(ref value)).Encapsulate(val2.bounds); } } } } if (!flag) { return null; } Vector3 size = ((Bounds)(ref value)).size; if (size.x > 7f || size.y > 7f || size.z > 7f) { Plugin.Log.LogWarning((object)$"Rejected unusually large cupboard bounds ({size}); shelf detection will be used instead."); return null; } return value; } private static bool IsDoorPart(Transform candidate, Transform root, Transform leftDoor, Transform rightDoor) { bool num = (Object)(object)leftDoor != (Object)(object)root && IsWithinRoot(candidate, leftDoor); bool flag = (Object)(object)rightDoor != (Object)(object)root && IsWithinRoot(candidate, rightDoor); return num || flag; } private static bool IsWithinRoot(Transform candidate, Transform root) { if ((Object)(object)candidate != (Object)null && (Object)(object)root != (Object)null) { if (!((Object)(object)candidate == (Object)(object)root)) { return candidate.IsChildOf(root); } return true; } return false; } private static string HierarchyPath(Transform transform) { List<string> list = new List<string>(); Transform val = transform; while ((Object)(object)val != (Object)null) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } } [HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")] internal static class RoundCleanupPatch { [HarmonyPrefix] private static void Prefix() { ProtectionState.Begin(); } [HarmonyFinalizer] private static Exception Finalizer(Exception __exception) { ProtectionState.Clear(); return __exception; } } [HarmonyPatch(typeof(NetworkObject), "Despawn", new Type[] { typeof(bool) })] internal static class NetworkObjectDespawnPatch { [HarmonyPrefix] private static bool Prefix(NetworkObject __instance) { return !ProtectionState.IsProtected(__instance); } } }