Decompiled source of BulletCleanup v0.1.0

Mods/BulletCleanup.dll

Decompiled 4 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BoneLib;
using BoneLib.BoneMenu;
using BulletCleanup;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Data;
using Il2CppSLZ.Marrow.Pool;
using Il2CppSLZ.Marrow.Warehouse;
using Il2CppSystem.Collections.Generic;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(TrackingProbe), "BulletCleanup", "0.1.0", "Geozek (with Codex)", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0")]
[assembly: AssemblyVersion("0.0.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 BulletCleanup
{
	public sealed class TrackingProbe : MelonMod
	{
		private readonly Stopwatch clock = new Stopwatch();

		private MelonPreferences_Category prefs;

		private MelonPreferences_Entry<bool> enabled;

		private MelonPreferences_Entry<bool> magazines;

		private MelonPreferences_Entry<int> lifetime;

		private MelonPreferences_Entry<int> limit;

		private long nextLog;

		private long frames;

		private bool menu;

		private bool ready;

		private bool fault;

		private float nextMenu;

		private float nextScan;

		private ITracker tracker;

		private int magazineProbeShots;

		private bool grabSubscribed;

		private readonly Queue<GameObject> grabbed = new Queue<GameObject>();

		public override void OnInitializeMelon()
		{
			clock.Start();
			prefs = MelonPreferences.CreateCategory("BrassCleanup");
			enabled = prefs.CreateEntry<bool>("Enabled", true, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			magazines = prefs.CreateEntry<bool>("DeleteDroppedMagazines", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			lifetime = prefs.CreateEntry<int>("CasingLifetimeSeconds", 5, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			limit = prefs.CreateEntry<int>("MaximumCasings", 100, (string)null, (string)null, false, false, (ValueValidator)null, (string)null);
			Hooking.OnPreFireGun += OnFire;
			Hooking.OnLevelLoaded += Loaded;
			Hooking.OnLevelLoading += Loading;
			((MelonBase)this).LoggerInstance.Msg("LOOSE ITEM CLEANUP: casings, unfired rounds and optional dropped magazines share timer and cap. Cleanup follows the saved enabled setting; magazines also require their own toggle.");
		}

		private void Loaded(LevelInfo info)
		{
			ready = true;
			if (!grabSubscribed)
			{
				Hooking.OnGrabObject += OnGrab;
				grabSubscribed = true;
			}
			((MelonBase)this).LoggerInstance.Msg("Level-loaded callback received.");
		}

		private void Loading(LevelInfo info)
		{
			StopGrabDiscovery();
			MagazineTypeProbe.Reset();
			ready = false;
			tracker = null;
			fault = false;
			magazineProbeShots = 0;
			((MelonBase)this).LoggerInstance.Msg("Tracking cleared for level change.");
		}

		private void StopGrabDiscovery()
		{
			if (grabSubscribed)
			{
				Hooking.OnGrabObject -= OnGrab;
				grabSubscribed = false;
			}
			grabbed.Clear();
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void OnGrab(GameObject obj, Hand hand)
		{
			if (ready && !fault && enabled.Value && grabbed.Count < 32)
			{
				grabbed.Enqueue(obj);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void ProcessGrabs()
		{
			int num = 4;
			while (grabbed.Count > 0 && num-- > 0)
			{
				GameObject val = grabbed.Dequeue();
				if (Object.op_Implicit((Object)(object)val))
				{
					if (tracker == null)
					{
						((MelonBase)this).LoggerInstance.Msg("Creating cleanup worker after grab.");
						tracker = CreateTracker();
					}
					tracker.LearnObject(val, magazines.Value);
				}
			}
		}

		private void OnFire(Gun gun)
		{
			if (!ready || !enabled.Value || fault)
			{
				return;
			}
			try
			{
				if (tracker == null)
				{
					((MelonBase)this).LoggerInstance.Msg("Creating casing worker after gunfire.");
					tracker = CreateTracker();
					((MelonBase)this).LoggerInstance.Msg("Casing worker created.");
				}
				tracker.LearnGun(gun, magazines.Value);
				if (magazines.Value && magazineProbeShots < 3)
				{
					magazineProbeShots++;
					((MelonBase)this).LoggerInstance.Msg("Magazine type probe BEGIN shot=" + magazineProbeShots);
					string text = MagazineTypeProbe.Read(gun);
					((MelonBase)this).LoggerInstance.Msg("Magazine type probe END barcode=" + (text ?? "<no default magazine>"));
				}
			}
			catch (Exception e)
			{
				Fail(e);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private ITracker CreateTracker()
		{
			return new CasingTracker(delegate(string s)
			{
				((MelonBase)this).LoggerInstance.Msg(s);
			});
		}

		private void Fail(Exception e)
		{
			fault = true;
			((MelonBase)this).LoggerInstance.Error("Tracking stopped after exception: " + e);
		}

		private void Save()
		{
			prefs.SaveToFile(false);
		}

		private void Menu()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: 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_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			((MelonBase)this).LoggerInstance.Msg("BoneMenu setup begin.");
			Page obj = Page.Root.CreatePage("BulletCleanup", new Color(0.85f, 0.65f, 0.25f), 8, true);
			obj.CreateBool("Cleanup enabled", Color.white, enabled.Value, (Action<bool>)delegate(bool v)
			{
				enabled.Value = v;
				if (!v)
				{
					tracker = null;
					grabbed.Clear();
				}
				Save();
			});
			obj.CreateInt("Loose item lifetime (seconds)", Color.white, Math.Clamp(lifetime.Value, 1, 300), 1, 1, 300, (Action<int>)delegate(int v)
			{
				lifetime.Value = v;
				Save();
			});
			obj.CreateInt("Maximum loose items", Color.white, Math.Clamp(limit.Value, 0, 500), 10, 0, 500, (Action<int>)delegate(int v)
			{
				limit.Value = v;
				Save();
			});
			obj.CreateBool("Delete dropped magazines", Color.white, magazines.Value, (Action<bool>)delegate(bool v)
			{
				magazines.Value = v;
				Save();
				((MelonBase)this).LoggerInstance.Msg("Magazine cleanup toggle saved.");
			});
			obj.CreateFunction("Clear tracked loose items now", Color.white, (Action)delegate
			{
				if (!enabled.Value)
				{
					((MelonBase)this).LoggerInstance.Msg("Enable cleanup before clearing loose items.");
					return;
				}
				try
				{
					if (ready && !fault && tracker != null)
					{
						tracker.Tick(delete: true, all: true, 0, 60, magazines.Value);
					}
				}
				catch (Exception e)
				{
					Fail(e);
				}
			});
			obj.CreateFunction("Scan magazines (read only)", Color.yellow, (Action)delegate
			{
				if (!ready || fault || !enabled.Value || !magazines.Value)
				{
					((MelonBase)this).LoggerInstance.Msg("Enable cleanup and magazine detection in a loaded level first.");
					return;
				}
				try
				{
					((MelonBase)this).LoggerInstance.Msg("Magazine scan BEGIN");
					MagazineTypeProbe.Scan(delegate(string s)
					{
						((MelonBase)this).LoggerInstance.Msg(s);
					});
					((MelonBase)this).LoggerInstance.Msg("Magazine scan END");
				}
				catch (Exception e)
				{
					Fail(e);
				}
			});
			obj.CreateFunction("Log cleanup status", Color.white, (Action)delegate
			{
				((MelonBase)this).LoggerInstance.Msg($"ready={ready}, fault={fault}, cleanup={enabled.Value}; " + (tracker?.Status ?? "waiting for gunfire or a grabbed gun/magazine"));
			});
			((MelonBase)this).LoggerInstance.Msg("BoneMenu setup complete.");
		}

		public override void OnUpdate()
		{
			frames++;
			if (clock.ElapsedMilliseconds >= nextLog)
			{
				nextLog = clock.ElapsedMilliseconds + 10000;
				((MelonBase)this).LoggerInstance.Msg($"Tracking probe heartbeat: updates={frames}, ready={ready}, worker={tracker != null}, cleanup={enabled.Value}; " + (tracker?.Status ?? "waiting for gunfire or a grabbed gun/magazine"));
			}
			try
			{
				if (!menu && Time.unscaledTime >= nextMenu)
				{
					nextMenu = Time.unscaledTime + 2f;
					if (Page.Root != null)
					{
						Menu();
						menu = true;
					}
				}
				if (ready && !fault && enabled.Value && !(Time.unscaledTime < nextScan))
				{
					nextScan = Time.unscaledTime + 0.25f;
					ProcessGrabs();
					if (tracker != null)
					{
						tracker.Tick(delete: true, all: false, Math.Clamp(limit.Value, 0, 500), Math.Clamp(lifetime.Value, 1, 300), magazines.Value);
					}
				}
			}
			catch (Exception e)
			{
				Fail(e);
			}
		}

		public override void OnDeinitializeMelon()
		{
			StopGrabDiscovery();
			Hooking.OnPreFireGun -= OnFire;
			Hooking.OnLevelLoaded -= Loaded;
			Hooking.OnLevelLoading -= Loading;
			tracker = null;
		}
	}
	internal static class MagazineTypeProbe
	{
		private static readonly Action<string> Silent = delegate
		{
		};

		private static Spawnable lastSpawn;

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static bool IsLoose(Poolee pool)
		{
			return Classify(pool, Silent) == 1;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static Spawnable GetSpawn(Gun gun)
		{
			if (!Object.op_Implicit((Object)(object)gun))
			{
				return null;
			}
			MagazineData defaultMagazine = gun.defaultMagazine;
			if (!Object.op_Implicit((Object)(object)defaultMagazine))
			{
				return null;
			}
			return defaultMagazine.spawnable;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static void Reset()
		{
			lastSpawn = null;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static string Read(Gun gun)
		{
			if (!Object.op_Implicit((Object)(object)gun))
			{
				return null;
			}
			MagazineData defaultMagazine = gun.defaultMagazine;
			Spawnable obj = (lastSpawn = (Object.op_Implicit((Object)(object)defaultMagazine) ? defaultMagazine.spawnable : null));
			if (obj == null)
			{
				return null;
			}
			SpawnableCrateReference crateRef = obj.crateRef;
			if (crateRef == null)
			{
				return null;
			}
			Barcode barcode = ((ScannableReference)crateRef).Barcode;
			if (barcode == null)
			{
				return null;
			}
			return barcode.ID;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static void Scan(Action<string> log)
		{
			Spawnable obj = lastSpawn;
			object obj2;
			if (obj == null)
			{
				obj2 = null;
			}
			else
			{
				SpawnableCrateReference crateRef = obj.crateRef;
				obj2 = ((crateRef != null) ? ((ScannableReference)crateRef).Barcode : null);
			}
			Barcode val = (Barcode)obj2;
			if (val == (Barcode)null)
			{
				log("No magazine type learned; fire a magazine-fed gun first.");
				return;
			}
			log("Magazine pool lookup BEGIN: " + val.ID);
			if (!AssetSpawner._hasInstance)
			{
				log("No spawner.");
				return;
			}
			AssetSpawner instance = AssetSpawner._instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				return;
			}
			Dictionary<Barcode, Pool> barcodeToPool = instance._barcodeToPool;
			Pool val2 = default(Pool);
			if (barcodeToPool == null || !barcodeToPool.TryGetValue(val, ref val2) || val2 == null)
			{
				log("Magazine pool not found.");
				return;
			}
			List<Poolee> spawned = val2._spawned;
			int num = spawned?.Count ?? 0;
			log("Magazine pool lookup END: spawned=" + num);
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			for (int i = 0; i < Math.Min(num, 64); i++)
			{
				Poolee val3 = spawned[i];
				if (!Object.op_Implicit((Object)(object)val3) || !((Component)val3).gameObject.activeInHierarchy)
				{
					num5++;
					continue;
				}
				log("Magazine eligibility BEGIN ID=" + val3.ID);
				int num6 = Classify(val3, log);
				switch (num6)
				{
				case 1:
					num2++;
					break;
				case 0:
					num3++;
					break;
				default:
					num4++;
					break;
				}
				log("Magazine eligibility END ID=" + val3.ID + " state=" + num6);
			}
			log($"Magazine scan: loose={num2}, protected={num3}, missing={num4}, inactive={num5}, examined={Math.Min(num, 64)}/{num}. No magazines deleted.");
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static int Classify(Poolee pool, Action<string> log)
		{
			log("Magazine component lookup BEGIN");
			Magazine componentInChildren = ((Component)pool).GetComponentInChildren<Magazine>();
			log("Magazine component lookup END");
			if (!Object.op_Implicit((Object)(object)componentInChildren) || (Object)(object)((Component)componentInChildren).GetComponentInParent<Poolee>() != (Object)(object)pool)
			{
				return -1;
			}
			log("Magazine insertion/parent checks BEGIN");
			if (componentInChildren.isMagazineInserted || Object.op_Implicit((Object)(object)((Component)componentInChildren).GetComponentInParent<Gun>()) || Object.op_Implicit((Object)(object)((Component)componentInChildren).GetComponentInParent<RigManager>()))
			{
				return 0;
			}
			log("Magazine host checks BEGIN");
			InteractableHost interactableHost = componentInChildren.interactableHost;
			bool num = Object.op_Implicit((Object)(object)interactableHost) && interactableHost.HandCount() == 0 && !interactableHost.IsAttached && !interactableHost.IsPulling;
			log("Magazine host checks END");
			return num ? 1 : 0;
		}
	}
	internal interface ITracker
	{
		string Status { get; }

		void LearnGun(Gun gun, bool includeMagazines);

		void LearnObject(GameObject obj, bool includeMagazines);

		void Tick(bool delete, bool all, int maximum, int lifetime, bool includeMagazines);
	}
	internal sealed class CasingTracker : ITracker
	{
		private sealed class Entry
		{
			internal Poolee Pool;

			internal ulong ID;

			internal float Born;
		}

		private readonly Action<string> log;

		private readonly Dictionary<string, Barcode> cases = new Dictionary<string, Barcode>();

		private readonly HashSet<string> rounds = new HashSet<string>();

		private readonly List<Gun> knownGuns = new List<Gun>();

		private readonly HashSet<string> magazineTypes = new HashSet<string>();

		private bool allowMagazines;

		private readonly Dictionary<ulong, Entry> active = new Dictionary<ulong, Entry>();

		private readonly List<Entry> ordered = new List<Entry>();

		private int removed;

		private int scanned;

		private int passes;

		public string Status => $"tracked={active.Count}, removed={removed}, scanned={scanned}, ammo types={cases.Count}, unfired types={rounds.Count}, magazine types={magazineTypes.Count}, passes={passes}";

		internal CasingTracker(Action<string> log)
		{
			this.log = log;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public void LearnGun(Gun gun, bool includeMagazines)
		{
			if (Object.op_Implicit((Object)(object)gun))
			{
				if (!knownGuns.Contains(gun) && knownGuns.Count < 128)
				{
					knownGuns.Add(gun);
				}
				Learn(gun.chamberedCartridge);
				Learn(gun.defaultCartridge);
				if (includeMagazines)
				{
					LearnMagazine(gun);
				}
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void LearnMagazine(Gun gun)
		{
			Spawnable spawn = MagazineTypeProbe.GetSpawn(gun);
			object obj;
			if (spawn == null)
			{
				obj = null;
			}
			else
			{
				SpawnableCrateReference crateRef = spawn.crateRef;
				if (crateRef == null)
				{
					obj = null;
				}
				else
				{
					Barcode barcode = ((ScannableReference)crateRef).Barcode;
					obj = ((barcode != null) ? barcode.ID : null);
				}
			}
			string text = (string)obj;
			if (!string.IsNullOrEmpty(text) && cases.Count < 128 && !rounds.Contains(text) && (!cases.ContainsKey(text) || magazineTypes.Contains(text)))
			{
				magazineTypes.Add(text);
				if (!cases.ContainsKey(text))
				{
					cases.Add(text, ((ScannableReference)spawn.crateRef).Barcode);
					log("Tracking magazine pool: " + text);
				}
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public void LearnObject(GameObject obj, bool includeMagazines)
		{
			if (!Object.op_Implicit((Object)(object)obj))
			{
				return;
			}
			Gun val = obj.GetComponentInParent<Gun>();
			if (!Object.op_Implicit((Object)(object)val))
			{
				val = obj.GetComponentInChildren<Gun>();
			}
			if (Object.op_Implicit((Object)(object)val))
			{
				LearnGun(val, includeMagazines);
			}
			if (!includeMagazines)
			{
				return;
			}
			Magazine val2 = obj.GetComponentInParent<Magazine>();
			if (!Object.op_Implicit((Object)(object)val2))
			{
				val2 = obj.GetComponentInChildren<Magazine>();
			}
			if (!Object.op_Implicit((Object)(object)val2))
			{
				return;
			}
			Poolee componentInParent = ((Component)val2).GetComponentInParent<Poolee>();
			if (!Object.op_Implicit((Object)(object)componentInParent))
			{
				return;
			}
			SpawnableCrate spawnableCrate = componentInParent.SpawnableCrate;
			Barcode val3 = (Object.op_Implicit((Object)(object)spawnableCrate) ? ((Scannable)spawnableCrate).Barcode : null);
			string text = ((val3 != null) ? val3.ID : null);
			if (!string.IsNullOrEmpty(text) && !rounds.Contains(text) && cases.Count < 128 && (!cases.ContainsKey(text) || magazineTypes.Contains(text)))
			{
				magazineTypes.Add(text);
				if (!cases.ContainsKey(text))
				{
					cases.Add(text, val3);
					log("Tracking grabbed magazine pool: " + text);
				}
			}
		}

		private void Learn(CartridgeData data)
		{
			if (!Object.op_Implicit((Object)(object)data))
			{
				return;
			}
			Spawnable cartridgeSpawnable = data.cartridgeSpawnable;
			object obj;
			if (cartridgeSpawnable == null)
			{
				obj = null;
			}
			else
			{
				SpawnableCrateReference crateRef = cartridgeSpawnable.crateRef;
				if (crateRef == null)
				{
					obj = null;
				}
				else
				{
					Barcode barcode = ((ScannableReference)crateRef).Barcode;
					obj = ((barcode != null) ? barcode.ID : null);
				}
			}
			string text = (string)obj;
			Spawnable cartridgeCaseSpawnable = data.cartridgeCaseSpawnable;
			object obj2;
			if (cartridgeCaseSpawnable == null)
			{
				obj2 = null;
			}
			else
			{
				SpawnableCrateReference crateRef2 = cartridgeCaseSpawnable.crateRef;
				if (crateRef2 == null)
				{
					obj2 = null;
				}
				else
				{
					Barcode barcode2 = ((ScannableReference)crateRef2).Barcode;
					obj2 = ((barcode2 != null) ? barcode2.ID : null);
				}
			}
			string text2 = (string)obj2;
			if (!string.IsNullOrEmpty(text) && cases.Count < 128)
			{
				rounds.Add(text);
				if (!cases.ContainsKey(text))
				{
					cases.Add(text, ((ScannableReference)data.cartridgeSpawnable.crateRef).Barcode);
					log("Tracking unfired round type: " + text);
				}
			}
			if (!string.IsNullOrEmpty(text2) && cases.Count < 128 && !cases.ContainsKey(text2))
			{
				cases.Add(text2, ((ScannableReference)cartridgeCaseSpawnable.crateRef).Barcode);
				log("Tracking spent casing type: " + text2);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private bool LooseRound(Poolee pool)
		{
			if (Object.op_Implicit((Object)(object)((Component)pool).GetComponentInParent<Gun>()) || Object.op_Implicit((Object)(object)((Component)pool).GetComponentInParent<RigManager>()))
			{
				return false;
			}
			FirearmCartridge componentInChildren = ((Component)pool).GetComponentInChildren<FirearmCartridge>();
			if (!Object.op_Implicit((Object)(object)componentInChildren) || (Object)(object)((Component)componentInChildren).GetComponentInParent<Poolee>() != (Object)(object)pool)
			{
				return false;
			}
			for (int num = knownGuns.Count - 1; num >= 0; num--)
			{
				Gun val = knownGuns[num];
				if (!Object.op_Implicit((Object)(object)val))
				{
					knownGuns.RemoveAt(num);
				}
				else if ((Object)(object)val._chamberedCartridgeArt == (Object)(object)componentInChildren || (Object)(object)val._nextCartridgeArt == (Object)(object)componentInChildren)
				{
					return false;
				}
			}
			foreach (InteractableHost componentsInChild in ((Component)pool).GetComponentsInChildren<InteractableHost>())
			{
				if (Object.op_Implicit((Object)(object)componentsInChild) && (componentsInChild.HandCount() > 0 || componentsInChild.IsAttached || componentsInChild.IsPulling))
				{
					return false;
				}
			}
			InteractableHost componentInParent = ((Component)pool).GetComponentInParent<InteractableHost>();
			if (Object.op_Implicit((Object)(object)componentInParent) && (componentInParent.HandCount() > 0 || componentInParent.IsAttached || componentInParent.IsPulling))
			{
				return false;
			}
			return true;
		}

		private bool Valid(Entry e)
		{
			Poolee pool = e.Pool;
			if (!Object.op_Implicit((Object)(object)pool) || pool.ID != e.ID || !((Component)pool).gameObject.activeInHierarchy)
			{
				return false;
			}
			SpawnableCrate spawnableCrate = pool.SpawnableCrate;
			object obj;
			if (!Object.op_Implicit((Object)(object)spawnableCrate))
			{
				obj = null;
			}
			else
			{
				Barcode barcode = ((Scannable)spawnableCrate).Barcode;
				obj = ((barcode != null) ? barcode.ID : null);
			}
			string text = (string)obj;
			if (text != null && cases.ContainsKey(text) && !Object.op_Implicit((Object)(object)((Component)pool).GetComponentInParent<Gun>()) && (!rounds.Contains(text) || LooseRound(pool)))
			{
				if (magazineTypes.Contains(text))
				{
					if (allowMagazines)
					{
						return MagazineTypeProbe.IsLoose(pool);
					}
					return false;
				}
				return true;
			}
			return false;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void Discover()
		{
			if (cases.Count == 0 || !AssetSpawner._hasInstance)
			{
				return;
			}
			AssetSpawner instance = AssetSpawner._instance;
			if (!Object.op_Implicit((Object)(object)instance))
			{
				return;
			}
			Dictionary<Barcode, Pool> barcodeToPool = instance._barcodeToPool;
			if (barcodeToPool == null)
			{
				return;
			}
			Pool val = default(Pool);
			foreach (KeyValuePair<string, Barcode> @case in cases)
			{
				if (magazineTypes.Contains(@case.Key) && !allowMagazines)
				{
					continue;
				}
				Barcode value = @case.Value;
				if (value == (Barcode)null || !barcodeToPool.TryGetValue(value, ref val) || val == null)
				{
					continue;
				}
				List<Poolee> spawned = val._spawned;
				if (spawned == null)
				{
					continue;
				}
				for (int i = 0; i < spawned.Count; i++)
				{
					Poolee val2 = spawned[i];
					scanned++;
					if (!Object.op_Implicit((Object)(object)val2) || !((Component)val2).gameObject.activeInHierarchy)
					{
						continue;
					}
					ulong iD = val2.ID;
					if (active.ContainsKey(iD) || Object.op_Implicit((Object)(object)((Component)val2).GetComponentInParent<Gun>()))
					{
						continue;
					}
					if (magazineTypes.Contains(@case.Key))
					{
						if (!MagazineTypeProbe.IsLoose(val2))
						{
							continue;
						}
					}
					else if (!Object.op_Implicit((Object)(object)((Component)val2).GetComponentInChildren<FirearmCartridge>()))
					{
						continue;
					}
					if (!rounds.Contains(@case.Key) || LooseRound(val2))
					{
						active.Add(iD, new Entry
						{
							Pool = val2,
							ID = iD,
							Born = Time.time
						});
					}
				}
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public void Tick(bool delete, bool all, int maximum, int lifetime, bool includeMagazines)
		{
			allowMagazines = includeMagazines;
			bool flag = passes < 3;
			if (flag)
			{
				log("Casing scan begin.");
			}
			ordered.Clear();
			foreach (Entry value in active.Values)
			{
				ordered.Add(value);
			}
			foreach (Entry item in ordered)
			{
				if (!Valid(item))
				{
					active.Remove(item.ID);
				}
			}
			Discover();
			passes++;
			if (flag)
			{
				log("Casing scan complete: " + Status);
			}
			if (!delete)
			{
				ordered.Clear();
			}
			else
			{
				DeleteEligible(all, maximum, lifetime);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void DeleteEligible(bool all, int maximum, int lifetime)
		{
			ordered.Clear();
			foreach (Entry value in active.Values)
			{
				ordered.Add(value);
			}
			ordered.Sort(delegate(Entry a, Entry b)
			{
				int num2 = a.Born.CompareTo(b.Born);
				return (num2 == 0) ? a.ID.CompareTo(b.ID) : num2;
			});
			int num = Math.Max(0, ordered.Count - maximum);
			float time = Time.time;
			foreach (Entry item in ordered)
			{
				bool flag = num > 0;
				if (flag)
				{
					num--;
				}
				if (!all && !flag && time - item.Born < (float)lifetime)
				{
					continue;
				}
				if (Valid(item))
				{
					if (removed < 8 || removed % 100 == 0)
					{
						log("Despawning loose item ID=" + item.ID);
					}
					item.Pool.Despawn();
					removed++;
				}
				active.Remove(item.ID);
			}
			ordered.Clear();
		}
	}
}