Decompiled source of SanitationDepartment v1.1.2

SanitationDepartment.dll

Decompiled 5 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Microsoft.CodeAnalysis;
using ProtectorateShared;
using SOD.Common;
using SOD.Common.Helpers;
using SOD.Common.Helpers.DialogObjects;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("SanitationDepartment")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.2.0")]
[assembly: AssemblyInformationalVersion("1.1.2+6bbc7710946fc89f05f5ea7793f431eeb7433293")]
[assembly: AssemblyProduct("SanitationDepartment")]
[assembly: AssemblyTitle("SanitationDepartment")]
[assembly: AssemblyVersion("1.1.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ProtectorateShared
{
	internal static class CredentialsCard
	{
		private const string RootName = "ProtectorateCredentials";

		private const string HeaderName = "00_Header";

		public static void Refresh(string sectionKey, string title, Func<List<string>> lines)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Invalid comparison between Unknown and I4
			MainMenuController instance = MainMenuController.Instance;
			RectTransform val = ((instance != null) ? instance.mainMenuContainer : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			bool num = instance.mainMenuActive && (Object)(object)SessionData.Instance != (Object)null && SessionData.Instance.startedGame && instance.currentComponent != null && (int)instance.currentComponent.component == 1;
			Transform val2 = ((Transform)val).Find("ProtectorateCredentials");
			if (!num)
			{
				if ((Object)(object)val2 != (Object)null)
				{
					((Component)val2).gameObject.SetActive(false);
				}
				return;
			}
			if (val2 == null)
			{
				val2 = CreateRoot((Transform)(object)val, instance);
			}
			Transform val3 = val2.Find(sectionKey) ?? ((TMP_Text)CreateText(val2, sectionKey, instance, 17f)).transform;
			List<string> list = lines();
			TextMeshProUGUI component = ((Component)val3).GetComponent<TextMeshProUGUI>();
			if (list.Count == 0)
			{
				((Component)val3).gameObject.SetActive(false);
			}
			else
			{
				((TMP_Text)component).text = "<b><color=#E8B04B>" + title + "</color></b>\n" + string.Join("\n", list);
				((Component)val3).gameObject.SetActive(true);
			}
			bool active = false;
			List<string> list2 = new List<string>();
			for (int i = 0; i < val2.childCount; i++)
			{
				list2.Add(((Object)val2.GetChild(i)).name);
			}
			list2.Sort(StringComparer.Ordinal);
			for (int j = 0; j < list2.Count; j++)
			{
				Transform val4 = val2.Find(list2[j]);
				val4.SetSiblingIndex(j);
				if (list2[j] != "00_Header" && ((Component)val4).gameObject.activeSelf)
				{
					active = true;
				}
			}
			val2.SetAsLastSibling();
			((Component)val2).gameObject.SetActive(active);
		}

		private static Transform CreateRoot(Transform container, MainMenuController menu)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected O, but got Unknown
			//IL_00d6: 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_011e: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("ProtectorateCredentials");
			RectTransform obj = val.AddComponent<RectTransform>();
			((Transform)obj).SetParent(container, false);
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(1f, 0f);
			obj.pivot = val2;
			Vector2 anchorMin = (obj.anchorMax = val2);
			obj.anchorMin = anchorMin;
			obj.anchoredPosition = new Vector2(-48f, 48f);
			Image obj2 = val.AddComponent<Image>();
			((Graphic)obj2).color = new Color(0.08f, 0.07f, 0.11f, 0.82f);
			((Graphic)obj2).raycastTarget = false;
			VerticalLayoutGroup obj3 = val.AddComponent<VerticalLayoutGroup>();
			((LayoutGroup)obj3).padding = new RectOffset
			{
				left = 20,
				right = 20,
				top = 14,
				bottom = 16
			};
			((HorizontalOrVerticalLayoutGroup)obj3).spacing = 10f;
			((HorizontalOrVerticalLayoutGroup)obj3).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj3).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandWidth = true;
			((HorizontalOrVerticalLayoutGroup)obj3).childForceExpandHeight = false;
			ContentSizeFitter obj4 = val.AddComponent<ContentSizeFitter>();
			obj4.horizontalFit = (FitMode)2;
			obj4.verticalFit = (FitMode)2;
			TextMeshProUGUI obj5 = CreateText(val.transform, "00_Header", menu, 20f);
			((TMP_Text)obj5).text = "<b>CREDENTIALS</b>";
			((Graphic)obj5).color = new Color(0.95f, 0.93f, 0.88f, 1f);
			return val.transform;
		}

		private static TextMeshProUGUI CreateText(Transform parent, string name, MainMenuController menu, float size)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			((Transform)val.AddComponent<RectTransform>()).SetParent(parent, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			if ((Object)(object)menu.buildText != (Object)null)
			{
				((TMP_Text)val2).font = ((TMP_Text)menu.buildText).font;
			}
			((TMP_Text)val2).fontSize = size;
			((Graphic)val2).color = new Color(0.86f, 0.84f, 0.8f, 1f);
			((Graphic)val2).raycastTarget = false;
			((TMP_Text)val2).enableWordWrapping = false;
			((TMP_Text)val2).richText = true;
			val.AddComponent<LayoutElement>().minWidth = 300f;
			return val2;
		}

		public static string Hours(float hours)
		{
			hours = Mathf.Max(0f, hours);
			int num = Mathf.FloorToInt(hours);
			int value = Mathf.FloorToInt((hours - (float)num) * 60f);
			if (num > 0)
			{
				return $"{num}h {value:00}m";
			}
			return $"{value}m";
		}
	}
}
namespace SanitationDepartment
{
	[HarmonyPatch(typeof(FirstPersonItemController), "PickUpItem")]
	internal static class BagPickupPatch
	{
		private static bool Prefix(Interactable pickUpThis, ref bool __result)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!State.Licensed || (SoCustomComparison)(object)((pickUpThis != null) ? pickUpThis.preset : null) == (SoCustomComparison)null)
				{
					return true;
				}
				if ((int)Plugin.KeepKey.Value != 0 && Input.GetKey(Plugin.KeepKey.Value))
				{
					return true;
				}
				if ((Object)(object)Player.Instance != (Object)null && (Object)(object)pickUpThis.belongsTo == (Object)(object)Player.Instance)
				{
					return true;
				}
				if (!Trash.IsTrash(pickUpThis) && (!Trash.IsJunk(pickUpThis) || 2 == 0))
				{
					return true;
				}
				if (State.BagCount >= State.BagCapacity)
				{
					Bag.Message($"Bag's full ({State.BagCount}/{State.BagCapacity}). Empty it at a bin.");
					return true;
				}
				pickUpThis.SafeDelete(false);
				State.BagCount++;
				Bag.Message($"Bagged ({State.BagCount}/{State.BagCapacity})");
				__result = true;
				return false;
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)ex);
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(InteractableController), "DropThis")]
	internal static class TossTrackPatch
	{
		private static void Prefix(InteractableController __instance)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!State.Licensed)
				{
					return;
				}
				Interactable interactable = __instance.interactable;
				Player instance = Player.Instance;
				if (interactable == null || (Object)(object)instance == (Object)null)
				{
					return;
				}
				if (!__instance.isCarriedByPlayer)
				{
					Vector3 val = ((Component)__instance).transform.position - ((Component)instance).transform.position;
					if (((Vector3)(ref val)).sqrMagnitude > 25f)
					{
						return;
					}
				}
				if (Trash.IsTrash(interactable) || Trash.IsJunk(interactable))
				{
					Bag.Track(interactable);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)ex);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class BagUpdatePatch
	{
		private static void Postfix()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Invalid comparison between Unknown and I4
			try
			{
				if (State.Licensed && !((Object)(object)SessionData.Instance == (Object)null) && SessionData.Instance.play)
				{
					if (Input.GetKeyDown(Plugin.ActionKey.Value) && (int)Cursor.lockState == 1)
					{
						Bag.Action();
					}
					Bag.CheckTossed();
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)ex);
			}
		}
	}
	internal static class Bag
	{
		private const float TossWindow = 4f;

		private static readonly List<(Interactable item, float until, bool bulky)> tossed = new List<(Interactable, float, bool)>();

		private static float nextTossCheck;

		private static string lastMessage;

		private static float lastMessageAt;

		public static void Action()
		{
			InteractionController instance = InteractionController.Instance;
			InteractableController val = ((instance != null) ? instance.carryingObject : null);
			if ((Object)(object)val != (Object)null)
			{
				BreakDown(val);
				return;
			}
			InteractionController instance2 = InteractionController.Instance;
			object obj;
			if (instance2 == null)
			{
				obj = null;
			}
			else
			{
				InteractableController currentLookingAtInteractable = instance2.currentLookingAtInteractable;
				obj = ((currentLookingAtInteractable != null) ? currentLookingAtInteractable.interactable : null);
			}
			Interactable val2 = (Interactable)obj;
			if (val2 != null && Trash.IsSource(val2))
			{
				CollectFrom(val2);
			}
			else if (val2 != null && Trash.IsBin(val2))
			{
				if (State.BagCount <= 0)
				{
					Message("Your bag is empty.");
					return;
				}
				int bagCount = State.BagCount;
				State.BagCount = 0;
				Trash.Payout(bagCount, bagCount * Trash.Pay);
				Message($"Emptied the bag: {bagCount} item{((bagCount == 1) ? "" : "s")}");
			}
		}

		private static void CollectFrom(Interactable source)
		{
			float num = Trash.SourceRefillLeft(source);
			if (num > 0f)
			{
				Message($"Already emptied. Check back in about {Mathf.CeilToInt(num)} hours.");
				return;
			}
			int num2 = State.BagCapacity - State.BagCount;
			if (num2 <= 0)
			{
				Message($"Bag is full ({State.BagCount}/{State.BagCapacity}). Empty it at a bin or dumpster.");
			}
			else
			{
				int num3 = Mathf.Min(num2, Random.Range(1, 3));
				State.BagCount += num3;
				State.SourceEmptied[source.id] = SessionData.Instance.gameTime;
				Message($"Collected {num3} from the dustbin ({State.BagCount}/{State.BagCapacity})");
			}
		}

		private static void BreakDown(InteractableController carried)
		{
			Interactable interactable = carried.interactable;
			if (interactable != null && Trash.IsJunk(interactable) && Trash.IsBulky(interactable))
			{
				int value = Plugin.BreakdownPieces.Value;
				if (State.BagCount + value > State.BagCapacity)
				{
					Message($"Not enough room in the bag to break that down ({State.BagCount}/{State.BagCapacity}).");
				}
				else
				{
					carried.DropThis(false);
					interactable.SafeDelete(false);
					State.BagCount += value;
					Message($"Broke it down: +{value} ({State.BagCount}/{State.BagCapacity})");
				}
			}
		}

		public static void Track(Interactable item)
		{
			tossed.RemoveAll(((Interactable item, float until, bool bulky) t) => t.item == null || t.item.id == item.id);
			tossed.Add((item, Time.unscaledTime + 4f, Trash.IsBulky(item)));
		}

		public static void CheckTossed()
		{
			if (tossed.Count == 0 || Time.unscaledTime < nextTossCheck)
			{
				return;
			}
			nextTossCheck = Time.unscaledTime + 0.1f;
			for (int num = tossed.Count - 1; num >= 0; num--)
			{
				(Interactable item, float until, bool bulky) tuple = tossed[num];
				Interactable item = tuple.item;
				float item2 = tuple.until;
				bool item3 = tuple.bulky;
				GameObject val = ((item != null) ? item.spawnedObject : null);
				if ((Object)(object)val == (Object)null || Time.unscaledTime > item2)
				{
					tossed.RemoveAt(num);
				}
				else if (TouchingBin(val))
				{
					tossed.RemoveAt(num);
					int amount = (item3 ? Plugin.BulkyPay.Value : Trash.Pay);
					item.SafeDelete(false);
					Trash.Payout(1, amount);
					Message(item3 ? "Into the dumpster." : "In the bin.");
				}
			}
		}

		private static bool TouchingBin(GameObject obj)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			foreach (Collider item in (Il2CppArrayBase<Collider>)(object)Physics.OverlapSphere(obj.transform.position, 0.6f, -1, (QueryTriggerInteraction)2))
			{
				if (!((Object)(object)item == (Object)null) && !((Component)item).transform.IsChildOf(obj.transform))
				{
					InteractableController componentInParent = ((Component)item).GetComponentInParent<InteractableController>();
					if ((Object)(object)componentInParent != (Object)null && Trash.IsBin(componentInParent.interactable))
					{
						return true;
					}
					if (((Component)item).gameObject.CompareTag("Garbage"))
					{
						return true;
					}
				}
			}
			return false;
		}

		public static void Message(string text)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			if (!(text == lastMessage) || !(Time.unscaledTime - lastMessageAt < 2f))
			{
				lastMessage = text;
				lastMessageAt = Time.unscaledTime;
				InterfaceController instance = InterfaceController.Instance;
				if (instance != null)
				{
					instance.NewGameMessage((GameMessageType)0, 0, text, (Icon)36, (AudioEvent)null, false, default(Color), -1, 0f, (RectTransform)null, (PingOnComplete)0, (Evidence)null, (List<DataKey>)null, (Sprite)null);
				}
				Plugin.Logger.LogInfo((object)text);
			}
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class CleaningPatch
	{
		private static void Postfix()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: 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_0061: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!((Object)(object)SessionData.Instance == (Object)null) && SessionData.Instance.play)
				{
					bool flag = (int)Cursor.lockState == 1;
					if (flag && (int)Plugin.SceneDebugKey.Value != 0 && Input.GetKeyDown(Plugin.SceneDebugKey.Value))
					{
						Scenes.SpawnScene(announce: true);
					}
					if (flag && (int)Plugin.SceneStatusKey.Value != 0 && Input.GetKeyDown(Plugin.SceneStatusKey.Value))
					{
						Scenes.LogStatus();
					}
					Cleaning.KeepBagsShaped();
					Scene scene = Scenes.At(((Actor)Player.Instance).currentGameLocation);
					if (scene == null || scene.State != SceneState.Released)
					{
						Cleaning.Left();
					}
					else
					{
						Cleaning.InScene(scene, flag);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)ex);
			}
		}
	}
	internal static class Cleaning
	{
		private static int enteredScene = -1;

		private static float nextScrub;

		private static float nextCheck;

		private static float nextReport;

		private static int scrubbedSinceReport;

		private static readonly Dictionary<int, int> poolProgress = new Dictionary<int, int>();

		private static InteractablePreset bagPreset;

		private static float nextShapeCheck;

		private static float Radius
		{
			get
			{
				if (!State.Mop)
				{
					return Plugin.RagRadius.Value;
				}
				return Plugin.MopRadius.Value;
			}
		}

		public static void Left()
		{
			enteredScene = -1;
		}

		public static void KeepBagsShaped()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			if (Time.unscaledTime < nextShapeCheck)
			{
				return;
			}
			nextShapeCheck = Time.unscaledTime + 1f;
			foreach (Scene item in Scenes.All)
			{
				Interactable val = Scenes.FindBag(item);
				GameObject val2 = ((val != null) ? val.spawnedObject : null);
				if ((Object)(object)val2 != (Object)null)
				{
					Vector3 val3 = val2.transform.localScale - Vector3.one;
					if (((Vector3)(ref val3)).sqrMagnitude < 0.0001f)
					{
						BodyBagShape.Apply(val);
					}
				}
			}
		}

		public static void InScene(Scene scene, bool focused)
		{
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			NewAddress address = scene.Address;
			if (enteredScene != scene.AddressId)
			{
				enteredScene = scene.AddressId;
				int count = Pools(address).Count;
				List<string> list = new List<string>();
				if (!scene.Bagged)
				{
					list.Add($"bag the body ({Plugin.ActionKey.Value})");
				}
				if (count > 0)
				{
					list.Add("mop up the blood pool");
				}
				list.Add($"scrub {CountSpots(address)} spots of blood (hold {Plugin.ActionKey.Value} and look at them)");
				Bag.Message("Cleanup job: " + string.Join(", ", list) + ".");
			}
			KeyCode value = Plugin.ActionKey.Value;
			if (focused && Input.GetKeyDown(value) && TryBag(scene))
			{
				return;
			}
			if (focused && Input.GetKey(value))
			{
				InteractionController instance = InteractionController.Instance;
				if ((Object)(object)((instance != null) ? instance.carryingObject : null) == (Object)null && Time.unscaledTime >= nextScrub)
				{
					nextScrub = Time.unscaledTime + (State.Mop ? 0.06f : 0.1f);
					Scrub(scene, address);
				}
			}
			if (Time.unscaledTime >= nextCheck)
			{
				nextCheck = Time.unscaledTime + 0.5f;
				if (scene.Bagged && Pools(address).Count == 0 && CountSpots(address) <= Allowed(scene))
				{
					Scenes.Complete(scene);
				}
			}
		}

		private static int Allowed(Scene s)
		{
			return Mathf.FloorToInt((float)s.InitialSpots * (1f - Mathf.Clamp01(Plugin.CleanThreshold.Value)));
		}

		public static int CountSpots(NewAddress addr)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Invalid comparison between Unknown and I4
			int num = 0;
			foreach (SpatterSimulation item in Spatter(addr))
			{
				Enumerator<DecalSpawnData> enumerator2 = item.decalsSpawned.GetEnumerator();
				while (enumerator2.MoveNext())
				{
					DecalSpawnData current = enumerator2.Current;
					if (current != null && (int)current.parentID != 1)
					{
						num++;
					}
				}
			}
			return num;
		}

		private static IEnumerable<SpatterSimulation> Spatter(NewAddress addr)
		{
			Enumerator<NewRoom> enumerator = ((NewGameLocation)addr).rooms.GetEnumerator();
			while (enumerator.MoveNext())
			{
				NewRoom current = enumerator.Current;
				if (((current != null) ? current.spatter : null) == null)
				{
					continue;
				}
				foreach (SpatterSimulation item in current.spatter.ToArray())
				{
					if (((item != null) ? item.decalsSpawned : null) != null)
					{
						yield return item;
					}
				}
			}
		}

		public static List<Interactable> Pools(NewAddress addr)
		{
			List<Interactable> list = new List<Interactable>();
			InteractablePreset bloodPool = PrefabControls.Instance.bloodPool;
			Enumerator<NewRoom> enumerator = ((NewGameLocation)addr).rooms.GetEnumerator();
			while (enumerator.MoveNext())
			{
				NewRoom current = enumerator.Current;
				if (((current != null) ? current.nodes : null) == null)
				{
					continue;
				}
				Enumerator<NewNode> enumerator2 = current.nodes.GetEnumerator();
				while (enumerator2.MoveNext())
				{
					NewNode current2 = enumerator2.Current;
					if (((current2 != null) ? current2.interactables : null) == null)
					{
						continue;
					}
					Enumerator<Interactable> enumerator3 = current2.interactables.GetEnumerator();
					while (enumerator3.MoveNext())
					{
						Interactable current3 = enumerator3.Current;
						if (current3 != null && (SoCustomComparison)(object)current3.preset == (SoCustomComparison)(object)bloodPool)
						{
							list.Add(current3);
						}
					}
				}
			}
			return list;
		}

		public static bool IsPool(Interactable item)
		{
			if ((SoCustomComparison)(object)((item != null) ? item.preset : null) != (SoCustomComparison)null)
			{
				return (SoCustomComparison)(object)item.preset == (SoCustomComparison)(object)PrefabControls.Instance.bloodPool;
			}
			return false;
		}

		private static void Scrub(Scene scene, NewAddress addr)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: Invalid comparison between Unknown and I4
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			//IL_022b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			CameraController instance = CameraController.Instance;
			Camera val = ((instance != null) ? instance.cam : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Vector3 position = ((Component)val).transform.position;
			Vector3 forward = ((Component)val).transform.forward;
			float radius = Radius;
			RaycastHit val2 = default(RaycastHit);
			float num = (Physics.Raycast(position, forward, ref val2, 3f, -1, (QueryTriggerInteraction)1) ? (((RaycastHit)(ref val2)).distance + radius) : 2.5f);
			foreach (Interactable item in Pools(addr))
			{
				if (DistanceFromSight(((Object)(object)item.spawnedObject != (Object)null) ? item.spawnedObject.transform.position : item.wPos, position, forward, num + 0.5f) > (radius + 0.5f) * (radius + 0.5f))
				{
					continue;
				}
				poolProgress.TryGetValue(item.id, out var value);
				value++;
				int num2 = (State.Mop ? Plugin.PoolScrubsMop.Value : Plugin.PoolScrubsRag.Value);
				if (value < num2)
				{
					poolProgress[item.id] = value;
					if (value % 5 == 1)
					{
						Bag.Message($"Mopping up the blood pool... {Mathf.RoundToInt(100f * (float)value / (float)num2)}%");
					}
				}
				else
				{
					poolProgress.Remove(item.id);
					StopBleeding(scene.Victim);
					item.SafeDelete(true);
					scene.Scrubbed += Plugin.PoolSpots.Value;
					Bag.Message("Blood pool mopped up.");
				}
				return;
			}
			List<(SpatterSimulation, DecalSpawnData, float)> list = new List<(SpatterSimulation, DecalSpawnData, float)>();
			foreach (SpatterSimulation item2 in Spatter(addr))
			{
				Enumerator<DecalSpawnData> enumerator3 = item2.decalsSpawned.GetEnumerator();
				while (enumerator3.MoveNext())
				{
					DecalSpawnData current3 = enumerator3.Current;
					if (current3 != null && (int)current3.parentID != 1)
					{
						float num3 = DistanceFromSight(((Object)(object)current3.spawnedProjector != (Object)null) ? ((Component)current3.spawnedProjector).transform.position : current3.worldPos, position, forward, num);
						if (num3 <= radius * radius)
						{
							list.Add((item2, current3, num3));
						}
					}
				}
			}
			if (list.Count == 0)
			{
				return;
			}
			int num4 = ((!State.Mop) ? 1 : 3);
			foreach (var (sim, ds, _) in list.OrderBy<(SpatterSimulation, DecalSpawnData, float), float>(((SpatterSimulation sim, DecalSpawnData ds, float d) n) => n.d).Take(num4))
			{
				RemoveDecal(sim, ds);
			}
			scene.Scrubbed += Math.Min(num4, list.Count);
			scrubbedSinceReport += Math.Min(num4, list.Count);
			if (Time.unscaledTime >= nextReport)
			{
				nextReport = Time.unscaledTime + 3f;
				int num5 = Math.Max(0, CountSpots(addr) - Allowed(scene));
				Bag.Message((num5 > 0) ? $"Scrubbing... {num5} spots to go" : "That's the worst of the blood gone.");
			}
		}

		private static float DistanceFromSight(Vector3 pos, Vector3 origin, Vector3 dir, float reach)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: 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_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = pos - origin;
			float num = Vector3.Dot(val, dir);
			if (num < 0f || num > reach)
			{
				return float.PositiveInfinity;
			}
			Vector3 val2 = val - dir * num;
			return ((Vector3)(ref val2)).sqrMagnitude;
		}

		private static void RemoveDecal(SpatterSimulation sim, DecalSpawnData ds)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)ds.parentID == 2)
			{
				Interactable i = ds.i;
				if (((i != null) ? i.spawnedDecals : null) != null)
				{
					ds.i.spawnedDecals.Remove(ds);
				}
			}
			if ((Object)(object)ds.spawnedProjector != (Object)null)
			{
				DecalSpawnData.RecycleDecalProjector(ds.spawnedProjector);
				ds.spawnedProjector = null;
			}
			List<DecalSpawnData> decalsSpawned = sim.decalsSpawned;
			for (int j = 0; j < decalsSpawned.Count; j++)
			{
				DecalSpawnData obj = decalsSpawned[j];
				if (!(((obj != null) ? new IntPtr?(((Il2CppObjectBase)obj).Pointer) : ((IntPtr?)null)) != ((Il2CppObjectBase)ds).Pointer))
				{
					decalsSpawned.RemoveAt(j);
					break;
				}
			}
			if (decalsSpawned.Count == 0)
			{
				sim.Remove();
			}
		}

		private static void StopBleeding(Human victim)
		{
			if (((victim != null) ? victim.currentWounds : null) == null)
			{
				return;
			}
			Enumerator<Wound> enumerator = victim.currentWounds.GetEnumerator();
			while (enumerator.MoveNext())
			{
				Wound current = enumerator.Current;
				if (current != null)
				{
					current.bloodPoolAmount = 0f;
				}
			}
		}

		private static bool TryBag(Scene scene)
		{
			//IL_00bb: 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_00c0: 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_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			if (scene.Bagged)
			{
				return false;
			}
			InteractionController instance = InteractionController.Instance;
			object obj;
			if (instance == null)
			{
				obj = null;
			}
			else
			{
				InteractableController currentLookingAtInteractable = instance.currentLookingAtInteractable;
				obj = ((currentLookingAtInteractable != null) ? currentLookingAtInteractable.interactable : null);
			}
			Interactable val = (Interactable)obj;
			Human victim = scene.Victim;
			if ((Object)(object)((val != null) ? val.isActor : null) == (Object)null || (Object)(object)victim == (Object)null || ((Il2CppObjectBase)val.isActor).Pointer != ((Il2CppObjectBase)victim).Pointer)
			{
				return false;
			}
			if (bagPreset == null)
			{
				bagPreset = FindBagPreset();
			}
			if ((SoCustomComparison)(object)bagPreset == (SoCustomComparison)null)
			{
				Bag.Message("No body bag available (couldn't find the bag item).");
				return false;
			}
			CitizenOutfitController outfitController = victim.outfitController;
			Transform val2 = ((outfitController != null) ? outfitController.GetBodyAnchor((CharacterAnchor)1) : null);
			Vector3 val3 = (((Object)(object)val2 != (Object)null) ? val2.position : ((Component)victim).transform.position);
			RaycastHit val4 = default(RaycastHit);
			if (Physics.Raycast(val3 + Vector3.up * 0.3f, Vector3.down, ref val4, 3f, Toolbox.Instance.playerMovementLayerMask, (QueryTriggerInteraction)1))
			{
				val3 = ((RaycastHit)(ref val4)).point;
			}
			float num = (((Object)(object)val2 != (Object)null) ? val2.eulerAngles.y : ((Component)victim).transform.eulerAngles.y);
			StopBleeding(victim);
			victim.RemoveFromWorld(true);
			Interactable val5 = InteractableCreator.Instance.CreateWorldInteractable(bagPreset, (Human)null, (Human)null, (Human)null, val3 + Vector3.up * 0.15f, new Vector3(0f, num, 0f), (List<Passed>)null, (Object)null, "");
			if (val5 == null)
			{
				Plugin.Logger.LogError((object)"Body bag creation failed");
				return false;
			}
			scene.BagId = val5.id;
			BodyBagShape.Apply(val5);
			Bag.Message("Body bagged. The coroner will collect it when the job's done.");
			return true;
		}

		private static InteractablePreset FindBagPreset()
		{
			List<InteractablePreset> source = (from o in (IEnumerable<Object>)Resources.FindObjectsOfTypeAll(Il2CppType.Of<InteractablePreset>())
				select ((Il2CppObjectBase)o).TryCast<InteractablePreset>() into p
				where (SoCustomComparison)(object)p != (SoCustomComparison)null
				select p).ToList();
			InteractablePreset val = ((IEnumerable<InteractablePreset>)source).FirstOrDefault((Func<InteractablePreset, bool>)((InteractablePreset p) => ((Object)p).name.Equals(Plugin.BodyBagItem.Value, StringComparison.OrdinalIgnoreCase))) ?? ((IEnumerable<InteractablePreset>)source).FirstOrDefault((Func<InteractablePreset, bool>)((InteractablePreset p) => ((Object)p).name.IndexOf("binbag", StringComparison.OrdinalIgnoreCase) >= 0));
			Plugin.Logger.LogInfo((object)("Body bag item: " + (((val != null) ? ((Object)val).name : null) ?? "none") + " (bag-like items: " + string.Join(", ", from p in source
				where ((Object)p).name.IndexOf("bag", StringComparison.OrdinalIgnoreCase) >= 0
				select ((Object)p).name) + ")"));
			return val;
		}
	}
	[HarmonyPatch(typeof(RigidbodyDragObject), "Update")]
	internal static class LegalDragPatch
	{
		internal static bool Allowed;

		private static void Prefix(RigidbodyDragObject __instance)
		{
			Player instance = Player.Instance;
			Scene scene = Scenes.At((instance != null) ? ((Actor)instance).currentGameLocation : null);
			int allowed;
			if (scene != null && scene.State == SceneState.Released)
			{
				NewAIController ai = __instance.ai;
				if ((Object)(object)((ai != null) ? ai.human : null) != (Object)null)
				{
					IntPtr pointer = ((Il2CppObjectBase)__instance.ai.human).Pointer;
					Human victim = scene.Victim;
					IntPtr? obj = ((victim != null) ? new IntPtr?(((Il2CppObjectBase)victim).Pointer) : ((IntPtr?)null));
					allowed = ((pointer == obj) ? 1 : 0);
					goto IL_008a;
				}
			}
			allowed = 0;
			goto IL_008a;
			IL_008a:
			Allowed = (byte)allowed != 0;
		}

		private static void Postfix()
		{
			Allowed = false;
		}
	}
	[HarmonyPatch(typeof(InteractionController), "SetIllegalActionActive")]
	internal static class LegalDragIllegalPatch
	{
		private static bool Prefix(bool val)
		{
			if (val)
			{
				return !LegalDragPatch.Allowed;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Interactable), "OnSpawn")]
	internal static class BodyBagShape
	{
		private static void Postfix(Interactable __instance)
		{
			try
			{
				if (Scenes.IsBodyBag(__instance))
				{
					Apply(__instance);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)ex);
			}
		}

		public static void Apply(Interactable bag)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0104: 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_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: 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_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = ((bag != null) ? bag.spawnedObject : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Transform transform = val.transform;
			Quaternion rotation = transform.rotation;
			transform.rotation = Quaternion.identity;
			transform.localScale = Vector3.one;
			Il2CppArrayBase<Renderer> componentsInChildren = val.GetComponentsInChildren<Renderer>();
			if (componentsInChildren.Length == 0)
			{
				transform.rotation = rotation;
				return;
			}
			Bounds bounds = componentsInChildren[0].bounds;
			foreach (Renderer item in componentsInChildren)
			{
				((Bounds)(ref bounds)).Encapsulate(item.bounds);
			}
			Vector3 size = ((Bounds)(ref bounds)).size;
			transform.localScale = new Vector3(0.55f / Mathf.Max(size.x, 0.01f), 0.3f / Mathf.Max(size.y, 0.01f), 1.8f / Mathf.Max(size.z, 0.01f));
			transform.rotation = rotation;
			bounds = componentsInChildren[0].bounds;
			foreach (Renderer item2 in componentsInChildren)
			{
				((Bounds)(ref bounds)).Encapsulate(item2.bounds);
			}
			RaycastHit val2 = default(RaycastHit);
			if (Physics.Raycast(((Bounds)(ref bounds)).center, Vector3.down, ref val2, 3f, Toolbox.Instance.playerMovementLayerMask, (QueryTriggerInteraction)1))
			{
				transform.position += Vector3.up * (((RaycastHit)(ref val2)).point.y + 0.01f - ((Bounds)(ref bounds)).min.y);
			}
		}
	}
	[HarmonyPatch(typeof(Interactable), "GetName")]
	internal static class BodyBagNamePatch
	{
		private static void Postfix(Interactable __instance, ref string __result)
		{
			if (Scenes.All.Count > 0 && Scenes.IsBodyBag(__instance))
			{
				__result = "Body bag";
			}
		}
	}
	[HarmonyPatch]
	internal static class Credentials
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(MainMenuController), "SetMenuComponent", new Type[] { typeof(Component) })]
		private static void OnMenuComponent()
		{
			Refresh();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(MainMenuController), "EnableMainMenu")]
		private static void OnMenu()
		{
			Refresh();
		}

		private static void Refresh()
		{
			try
			{
				CredentialsCard.Refresh("20_Sanitation", "Sanitation", Sanitation);
				CredentialsCard.Refresh("30_CrimeScenes", "Crime Scene Cleanup", CrimeScenes);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Credentials card: " + ex));
			}
		}

		private static List<string> Sanitation()
		{
			List<string> list = new List<string>();
			if (!State.Licensed)
			{
				return list;
			}
			list.Add("Sanitation License");
			list.Add($"Trash bag: {State.BagCount}/{State.BagCapacity}");
			if (State.Picker)
			{
				list.Add("Litter picker");
			}
			if (State.Supervisor)
			{
				list.Add("Route Supervisor");
			}
			else if (State.Binned >= Plugin.SupervisorItems.Value)
			{
				list.Add("Route Supervisor promotion waiting at City Hall");
			}
			else
			{
				list.Add($"Items binned: {State.Binned}/{Plugin.SupervisorItems.Value} toward Route Supervisor");
			}
			if (State.FullTime)
			{
				list.Add("On sanitation full-time (no new murders)");
			}
			return list;
		}

		private static List<string> CrimeScenes()
		{
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			if (!State.Certified)
			{
				return list;
			}
			list.Add("Certified for crime scene cleanup");
			if (State.Mop)
			{
				list.Add("Mop and bucket");
			}
			list.Add($"Scenes cleaned: {State.ScenesCleaned}");
			float gameTime = SessionData.Instance.gameTime;
			if (Plugin.ScenesOnlyFullTime.Value && !State.FullTime)
			{
				list.Add("New scenes are posted while you're on full-time");
			}
			foreach (Scene item in Scenes.All)
			{
				NewAddress address = item.Address;
				if ((Object)(object)address == (Object)null)
				{
					continue;
				}
				switch (item.State)
				{
				case SceneState.Investigating:
				{
					Dictionary<NewGameLocation, EnforcerCall> enforcerCalls = GameplayController.Instance.enforcerCalls;
					EnforcerCall val = (enforcerCalls.ContainsKey((NewGameLocation)(object)address) ? enforcerCalls[(NewGameLocation)(object)address] : null);
					if (val == null || val.arrivalTime <= 0f)
					{
						list.Add(item.Name + ": Enforcers on their way");
						break;
					}
					float num = val.arrivalTime + GameplayControls.Instance.crimeSceneSearchLength + Plugin.InvestigationHours.Value;
					list.Add(item.Name + ": Enforcers investigating, released in about " + CredentialsCard.Hours(num - gameTime));
					break;
				}
				case SceneState.Released:
				{
					string text = "Open job: " + item.Name;
					if (item.Bagged)
					{
						text += ", body bagged";
					}
					Dictionary<NewAddress, Vector2> guestPasses = GameplayController.Instance.guestPasses;
					if (guestPasses.ContainsKey(address) && guestPasses[address].x > gameTime)
					{
						text = text + " (access " + CredentialsCard.Hours(guestPasses[address].x - gameTime) + ")";
					}
					list.Add(text);
					break;
				}
				case SceneState.Done:
					list.Add(item.Name + ": clean, waiting for the coroner");
					break;
				}
			}
			return list;
		}
	}
	internal static class Dialogs
	{
		private sealed class Logic : IDialogLogic
		{
			private readonly Func<bool> available;

			private readonly Action onSuccess;

			public Logic(Func<bool> available, Action onSuccess)
			{
				this.available = available;
				this.onSuccess = onSuccess;
			}

			public bool IsDialogShown(DialogPreset preset, Citizen saysTo, SideJob jobRef)
			{
				if (available())
				{
					return IsCityHallDesk(saysTo);
				}
				return false;
			}

			public ForceSuccess ShouldDialogSucceedOverride(DialogController instance, DialogOption dialog, Citizen saysTo, NewNode where, Actor saidBy)
			{
				return (ForceSuccess)1;
			}

			public void OnDialogExecute(DialogController instance, Citizen saysTo, Interactable saysToInteractable, NewNode where, Actor saidBy, bool success, NewRoom roomRef, SideJob jobRef)
			{
				if (success)
				{
					onSuccess();
				}
			}
		}

		public static void Register()
		{
			Lib.Dialogs.Builder("Sanitation_License").SetText("I'd like a sanitation license.").SetDialogLogic((IDialogLogic)(object)new Logic(() => !State.Licensed, delegate
			{
				//IL_003b: Unknown result type (might be due to invalid IL or missing references)
				State.Licensed = true;
				State.Notify($"Sanitation License acquired. Trash and junk you pick up go in your bag ({State.BagCapacity} items); empty it at any bin with {Plugin.ActionKey.Value}.", (Icon)36);
			}))
				.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
				{
					p.cost = Plugin.LicenseCost.Value;
					p.ranking = 4;
				})
				.AddResponse("You're licensed, and here's your bag. Every piece of trash or junk you bin earns you a little. Welcome aboard.", true, true)
				.CreateAndRegister();
			Lib.Dialogs.Builder("Sanitation_Picker").SetText("I'd like a litter picker.").SetDialogLogic((IDialogLogic)(object)new Logic(() => State.Licensed && !State.Picker, delegate
			{
				State.Picker = true;
				State.Notify("Litter picker: grab trash and junk from a few metres away.", (Icon)36);
			}))
				.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
				{
					p.cost = Plugin.PickerCost.Value;
					p.ranking = 4;
				})
				.AddResponse("Saves your back. Mind you don't poke anyone with it.", true, true)
				.CreateAndRegister();
			Lib.Dialogs.Builder("Sanitation_FullTime").SetText("I want to transfer to sanitation full-time.").SetDialogLogic((IDialogLogic)(object)new Logic(() => State.Licensed && !State.FullTime && FullTime.Allowed, delegate
			{
				State.FullTime = true;
				State.Notify("You're on sanitation full-time. The Enforcers will handle the killers.", (Icon)36);
			}))
				.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
				{
					p.ranking = 5;
				})
				.AddResponse("Done. The Enforcers can chase the killers; you keep the streets clean.", true, true)
				.CreateAndRegister();
			Lib.Dialogs.Builder("Sanitation_Return").SetText("I'm going back to detective work.").SetDialogLogic((IDialogLogic)(object)new Logic(() => State.FullTime, delegate
			{
				State.FullTime = false;
				State.Notify("Back on detective work. The city's killers are your problem again.", (Icon)0);
			}))
				.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
				{
					p.ranking = 5;
				})
				.AddResponse("Your old desk's still there. Your license stays valid if you want the odd shift.", true, true)
				.CreateAndRegister();
			Lib.Dialogs.Builder("Sanitation_Certification").SetText("I'd like to be certified for crime scene cleanup.").SetDialogLogic((IDialogLogic)(object)new Logic(() => State.Licensed && !State.Certified && FullTime.Allowed, delegate
			{
				State.Certified = true;
				State.Notify(Plugin.ScenesOnlyFullTime.Value ? "Crime scene cleanup certified. While you're on sanitation full-time, the Enforcers will release scenes to you." : "Crime scene cleanup certified. The Enforcers will release scenes to you.", (Icon)22);
			}))
				.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
				{
					p.cost = Plugin.CertificationCost.Value;
					p.ranking = 4;
				})
				.AddResponse("Not everyone has the stomach for it. When the Enforcers are done with a scene, it's yours: bag the body, scrub the blood, and the coroner does the rest.", true, true)
				.CreateAndRegister();
			Lib.Dialogs.Builder("Sanitation_Mop").SetText("I'd like a mop and bucket.").SetDialogLogic((IDialogLogic)(object)new Logic(() => State.Certified && !State.Mop, delegate
			{
				State.Mop = true;
				State.Notify("Mop and bucket: you scrub a wider area, faster.", (Icon)36);
			}))
				.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
				{
					p.cost = Plugin.MopCost.Value;
					p.ranking = 4;
				})
				.AddResponse("Heavy-duty. Rinse it between jobs.", true, true)
				.CreateAndRegister();
			Lib.Dialogs.Builder("Sanitation_Supervisor").SetText("I'm here about the Route Supervisor position.").SetDialogLogic((IDialogLogic)(object)new Logic(() => State.Licensed && !State.Supervisor && State.Binned >= Plugin.SupervisorItems.Value, delegate
			{
				//IL_0029: Unknown result type (might be due to invalid IL or missing references)
				State.Supervisor = true;
				State.GlowOn = true;
				State.Notify($"Route Supervisor. Nearby trash now stands out (toggle with {Plugin.GlowToggleKey.Value}), and your bag holds {State.BagCapacity}.", (Icon)13);
			}))
				.ModifyDialogOptions((Action<DialogPreset>)delegate(DialogPreset p)
				{
					p.cost = Plugin.SupervisorCost.Value;
					p.ranking = 4;
				})
				.AddResponse("Your numbers speak for themselves. You'll learn to spot the mess before anyone else does.", true, true)
				.CreateAndRegister();
		}

		internal static bool IsCityHallDesk(Citizen citizen)
		{
			if ((Object)(object)citizen == (Object)null || !((Actor)citizen).isAtWork)
			{
				return false;
			}
			Occupation job = ((Human)citizen).job;
			object obj;
			if (job == null)
			{
				obj = null;
			}
			else
			{
				Company employer = job.employer;
				if (employer == null)
				{
					obj = null;
				}
				else
				{
					NewGameLocation placeOfBusiness = employer.placeOfBusiness;
					obj = ((placeOfBusiness != null) ? placeOfBusiness.building : null);
				}
			}
			NewBuilding val = (NewBuilding)obj;
			if ((SoCustomComparison)(object)((val != null) ? val.preset : null) == (SoCustomComparison)null || ((SoCustomComparison)val.preset).presetName != "CityHall")
			{
				return false;
			}
			string value = Plugin.DeskJobs.Value;
			if (string.IsNullOrWhiteSpace(value))
			{
				return true;
			}
			OccupationPreset preset = job.preset;
			string jobName = ((preset != null) ? ((Object)preset).name : null);
			if (jobName != null)
			{
				return value.Split(',').Any((string j) => j.Trim().Equals(jobName, StringComparison.OrdinalIgnoreCase));
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(MurderController), "Tick")]
	internal static class QuietCityPatch
	{
		private static void Prefix(MurderController __instance)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (State.FullTime && FullTime.Allowed)
			{
				Murder currentMurder = __instance.GetCurrentMurder();
				if ((currentMurder == null || !FullTime.InProgress(currentMurder.state)) && __instance.pauseBetweenMurders < 1f)
				{
					__instance.pauseBetweenMurders = 1f;
				}
			}
		}
	}
	internal static class FullTime
	{
		public static bool Allowed
		{
			get
			{
				if ((Object)(object)Game.Instance != (Object)null)
				{
					return Game.Instance.sandboxMode;
				}
				return false;
			}
		}

		public static bool InProgress(MurderState state)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Invalid comparison between Unknown and I4
			if (state - 1 <= 6)
			{
				return true;
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "Update")]
	internal static class GlowPatch
	{
		private const int OutlineLayer = 30;

		private static readonly Color GlowColour = new Color(0.62f, 0.71f, 0.54f, 0.6f);

		private static readonly Dictionary<int, List<GameObject>> glowing = new Dictionary<int, List<GameObject>>();

		private static float nextScan;

		private static void Postfix(Player __instance)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: 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)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Invalid comparison between Unknown and I4
			try
			{
				KeyCode value = Plugin.GlowToggleKey.Value;
				if (State.Supervisor && (int)value != 0 && Input.GetKeyDown(value) && (int)Cursor.lockState == 1)
				{
					State.GlowOn = !State.GlowOn;
					State.Notify(State.GlowOn ? "Trash glow on" : "Trash glow off", (Icon)36);
				}
				if (!State.Supervisor || !State.GlowOn || (Object)(object)SessionData.Instance == (Object)null || !SessionData.Instance.play)
				{
					if (glowing.Count > 0)
					{
						ClearAll();
					}
				}
				else if (!(Time.unscaledTime < nextScan))
				{
					nextScan = Time.unscaledTime + 0.3f;
					Scan(__instance);
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Glow: " + ex));
				ClearAll();
			}
		}

		private static void Scan(Player player)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			CameraController instance = CameraController.Instance;
			Camera val = ((instance != null) ? instance.cam : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Vector3 position = ((Component)val).transform.position;
			float value = Plugin.GlowRange.Value;
			HashSet<int> hashSet = new HashSet<int>();
			foreach (Collider item in (Il2CppArrayBase<Collider>)(object)Physics.OverlapSphere(position, value, -1, (QueryTriggerInteraction)1))
			{
				InteractableController obj = (((Object)(object)item != (Object)null) ? ((Component)item).GetComponentInParent<InteractableController>() : null);
				Interactable val2 = ((obj != null) ? obj.interactable : null);
				if (val2 == null || hashSet.Contains(val2.id))
				{
					continue;
				}
				GameObject spawnedObject = val2.spawnedObject;
				if (!((Object)(object)spawnedObject == (Object)null) && !((Object)(object)val2.inInventory != (Object)null) && (Trash.IsTrash(val2) || Trash.IsJunk(val2) || (Trash.IsSource(val2) && Trash.SourceRefillLeft(val2) <= 0f)) && Visible(position, spawnedObject))
				{
					hashSet.Add(val2.id);
					if (!glowing.ContainsKey(val2.id))
					{
						glowing[val2.id] = AddGlow(spawnedObject);
					}
				}
			}
			List<int> list = new List<int>();
			foreach (int key in glowing.Keys)
			{
				if (!hashSet.Contains(key))
				{
					list.Add(key);
				}
			}
			foreach (int item2 in list)
			{
				Remove(item2);
			}
		}

		private static bool Visible(Vector3 eye, GameObject obj)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: 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)
			Vector3 val = obj.transform.position + Vector3.up * 0.05f - eye;
			float magnitude = ((Vector3)(ref val)).magnitude;
			if (magnitude < 0.01f)
			{
				return true;
			}
			RaycastHit val2 = default(RaycastHit);
			if (!Physics.Raycast(eye, val / magnitude, ref val2, magnitude - 0.05f, Toolbox.Instance.playerMovementLayerMask, (QueryTriggerInteraction)1))
			{
				return true;
			}
			if ((Object)(object)((RaycastHit)(ref val2)).collider != (Object)null)
			{
				return ((Component)((RaycastHit)(ref val2)).collider).transform.IsChildOf(obj.transform);
			}
			return false;
		}

		private static List<GameObject> AddGlow(GameObject obj)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Expected O, but got Unknown
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			List<GameObject> list = new List<GameObject>();
			foreach (MeshRenderer componentsInChild in obj.GetComponentsInChildren<MeshRenderer>())
			{
				MeshFilter component = ((Component)componentsInChild).GetComponent<MeshFilter>();
				if (!((Object)(object)component == (Object)null) && !((Object)(object)component.sharedMesh == (Object)null) && ((Component)componentsInChild).gameObject.layer != 30)
				{
					GameObject val = new GameObject("SanitationGlow");
					val.layer = 30;
					val.transform.SetParent(((Component)componentsInChild).transform, false);
					val.AddComponent<MeshFilter>().sharedMesh = component.sharedMesh;
					MeshRenderer obj2 = val.AddComponent<MeshRenderer>();
					((Renderer)obj2).sharedMaterials = ((Renderer)componentsInChild).sharedMaterials;
					MaterialPropertyBlock val2 = new MaterialPropertyBlock();
					val2.SetColor("_SelectionColor", GlowColour);
					((Renderer)obj2).SetPropertyBlock(val2);
					list.Add(val);
				}
			}
			return list;
		}

		private static void Remove(int id)
		{
			if (!glowing.TryGetValue(id, out var value))
			{
				return;
			}
			foreach (GameObject item in value)
			{
				if ((Object)(object)item != (Object)null)
				{
					Object.Destroy((Object)(object)item);
				}
			}
			glowing.Remove(id);
		}

		private static void ClearAll()
		{
			foreach (int item in new List<int>(glowing.Keys))
			{
				Remove(item);
			}
		}
	}
	[BepInPlugin("sodmods.sanitationdepartment", "Sanitation Department", "1.1.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BasePlugin
	{
		public const string Guid = "sodmods.sanitationdepartment";

		internal static ManualLogSource Logger;

		internal static ConfigEntry<int> LicenseCost;

		internal static ConfigEntry<int> PayPerItem;

		internal static ConfigEntry<int> SupervisorCost;

		internal static ConfigEntry<int> SupervisorItems;

		internal static ConfigEntry<float> GlowRange;

		internal static ConfigEntry<KeyCode> GlowToggleKey;

		internal static ConfigEntry<bool> TagWithStreetCleanerDisk;

		internal static ConfigEntry<string> DeskJobs;

		internal static ConfigEntry<KeyCode> ActionKey;

		internal static ConfigEntry<string> NotJunk;

		internal static ConfigEntry<string> TrashSources;

		internal static ConfigEntry<float> SourceRefillHours;

		internal static ConfigEntry<KeyCode> KeepKey;

		internal static ConfigEntry<bool> LogItems;

		internal static ConfigEntry<int> BulkyPay;

		internal static ConfigEntry<int> BreakdownPieces;

		internal static ConfigEntry<int> BagSize;

		internal static ConfigEntry<int> SupervisorBagSize;

		internal static ConfigEntry<int> PickerCost;

		internal static ConfigEntry<float> PickerReach;

		internal static ConfigEntry<int> CertificationCost;

		internal static ConfigEntry<int> MopCost;

		internal static ConfigEntry<int> SceneFee;

		internal static ConfigEntry<int> PayPerSpot;

		internal static ConfigEntry<int> PoolSpots;

		internal static ConfigEntry<float> CleanThreshold;

		internal static ConfigEntry<float> SceneIntervalMin;

		internal static ConfigEntry<float> SceneIntervalMax;

		internal static ConfigEntry<int> MaxScenes;

		internal static ConfigEntry<bool> ScenesOnlyFullTime;

		internal static ConfigEntry<float> DiscoveryHours;

		internal static ConfigEntry<float> InvestigationHours;

		internal static ConfigEntry<float> AccessHours;

		internal static ConfigEntry<float> RagRadius;

		internal static ConfigEntry<float> MopRadius;

		internal static ConfigEntry<int> PoolScrubsRag;

		internal static ConfigEntry<int> PoolScrubsMop;

		internal static ConfigEntry<string> BodyBagItem;

		internal static ConfigEntry<KeyCode> SceneDebugKey;

		internal static ConfigEntry<KeyCode> SceneStatusKey;

		public override void Load()
		{
			//IL_05d7: Unknown result type (might be due to invalid IL or missing references)
			Logger = ((BasePlugin)this).Log;
			LicenseCost = ((BasePlugin)this).Config.Bind<int>("License", "Cost", 150, "Price of a Sanitation License, in crows.");
			PayPerItem = ((BasePlugin)this).Config.Bind<int>("License", "PayPerItem", 2, "Crows paid per item of trash binned while licensed (the Street Cleaner sync disk pays more if installed; the higher value is used).");
			SupervisorItems = ((BasePlugin)this).Config.Bind<int>("RouteSupervisor", "ItemsRequired", 100, "Items you must bin before City Hall offers the Route Supervisor certificate.");
			SupervisorCost = ((BasePlugin)this).Config.Bind<int>("RouteSupervisor", "Cost", 500, "Price of the Route Supervisor certificate, in crows.");
			GlowRange = ((BasePlugin)this).Config.Bind<float>("RouteSupervisor", "GlowRange", 8f, "How far away (metres) trash is highlighted.");
			GlowToggleKey = ((BasePlugin)this).Config.Bind<KeyCode>("RouteSupervisor", "GlowToggleKey", (KeyCode)103, "Key to switch the trash glow on and off.");
			TagWithStreetCleanerDisk = ((BasePlugin)this).Config.Bind<bool>("Tag", "ShowWithStreetCleanerDisk", true, "Also show the Trash tag to players with the game's Street Cleaner sync disk perk, even without the license.");
			ActionKey = ((BasePlugin)this).Config.Bind<KeyCode>("Controls", "ActionKey", (KeyCode)98, "Look at a bin and press to empty your bag; press while carrying bulky junk to break it down into the bag. At a crime scene: press at the body to bag it, hold while looking at blood to scrub.");
			KeepKey = ((BasePlugin)this).Config.Bind<KeyCode>("Controls", "KeepKey", (KeyCode)304, "Hold while picking up trash or junk to put it in an inventory slot instead of the bag (to keep it).");
			LogItems = ((BasePlugin)this).Config.Bind<bool>("Debug", "LogItems", false, "Log how items you look at are classified (for tuning and bug reports).");
			NotJunk = ((BasePlugin)this).Config.Bind<string>("Items", "NotJunk", "TrafficCone,Barrel,PlasticStool,SmallCrate", "Comma-separated item types that are never junk (street furniture etc.).");
			TrashSources = ((BasePlugin)this).Config.Bind<string>("Items", "TrashSources", "Dustbin", "Comma-separated item types you collect trash FROM (look at one and press the action key) instead of binning them.");
			SourceRefillHours = ((BasePlugin)this).Config.Bind<float>("Items", "SourceRefillHours", 12f, "In-game hours before an emptied dustbin has trash to collect again (1 in-game hour is about 6.5 real minutes).");
			BulkyPay = ((BasePlugin)this).Config.Bind<int>("License", "BulkyPay", 5, "Crows for tossing a bulky junk item (e.g. a cardboard box) into a bin or dumpster whole.");
			BreakdownPieces = ((BasePlugin)this).Config.Bind<int>("Bag", "BreakdownPieces", 3, "How many bag items a broken-down bulky item becomes.");
			BagSize = ((BasePlugin)this).Config.Bind<int>("Bag", "Capacity", 15, "Items your bag holds.");
			SupervisorBagSize = ((BasePlugin)this).Config.Bind<int>("Bag", "SupervisorCapacity", 30, "Items your bag holds once you're a Route Supervisor.");
			PickerCost = ((BasePlugin)this).Config.Bind<int>("LitterPicker", "Cost", 75, "Price of a litter picker at City Hall.");
			PickerReach = ((BasePlugin)this).Config.Bind<float>("LitterPicker", "Reach", 4f, "How far (metres) you can grab trash and junk with the litter picker.");
			CertificationCost = ((BasePlugin)this).Config.Bind<int>("CrimeScenes", "CertificationCost", 250, "Price of crime scene cleanup certification at City Hall (requires the Sanitation License).");
			MopCost = ((BasePlugin)this).Config.Bind<int>("CrimeScenes", "MopCost", 100, "Price of a mop and bucket at City Hall (scrubs a wider area, faster).");
			SceneFee = ((BasePlugin)this).Config.Bind<int>("CrimeScenes", "SceneFee", 150, "Crows paid for finishing a crime scene.");
			PayPerSpot = ((BasePlugin)this).Config.Bind<int>("CrimeScenes", "PayPerSpot", 1, "Crows per spot of blood scrubbed, paid when the scene is finished.");
			PoolSpots = ((BasePlugin)this).Config.Bind<int>("CrimeScenes", "PoolSpots", 20, "How many spots a mopped-up blood pool counts as, for pay.");
			CleanThreshold = ((BasePlugin)this).Config.Bind<float>("CrimeScenes", "CleanThreshold", 0.9f, "Share of the blood that must be scrubbed for the scene to count as clean (0-1).");
			SceneIntervalMin = ((BasePlugin)this).Config.Bind<float>("CrimeScenes", "IntervalMinHours", 10f, "Fewest in-game hours between new scenes.");
			SceneIntervalMax = ((BasePlugin)this).Config.Bind<float>("CrimeScenes", "IntervalMaxHours", 18f, "Most in-game hours between new scenes.");
			MaxScenes = ((BasePlugin)this).Config.Bind<int>("CrimeScenes", "MaxOpenScenes", 1, "Scenes that can be open at once.");
			ScenesOnlyFullTime = ((BasePlugin)this).Config.Bind<bool>("CrimeScenes", "OnlyWhenFullTime", true, "Only post scenes while you're on sanitation full-time (when off, murders give the Enforcers enough to do).");
			DiscoveryHours = ((BasePlugin)this).Config.Bind<float>("CrimeScenes", "DiscoveryHours", 0.5f, "Most in-game hours before a neighbour reports a death (they may notice sooner).");
			InvestigationHours = ((BasePlugin)this).Config.Bind<float>("CrimeScenes", "InvestigationHours", 2f, "In-game hours the Enforcers hold a scene, after their search, before releasing it for cleaning.");
			AccessHours = ((BasePlugin)this).Config.Bind<float>("CrimeScenes", "AccessHours", 24f, "How long a released scene lets you in without trespassing.");
			RagRadius = ((BasePlugin)this).Config.Bind<float>("CrimeScenes", "RagRadius", 0.45f, "Scrubbing reach around the point you look at, without the mop (metres).");
			MopRadius = ((BasePlugin)this).Config.Bind<float>("CrimeScenes", "MopRadius", 0.9f, "Scrubbing reach with the mop and bucket (metres).");
			PoolScrubsRag = ((BasePlugin)this).Config.Bind<int>("CrimeScenes", "PoolScrubsRag", 25, "Scrubbing ticks to mop up a blood pool without the mop.");
			PoolScrubsMop = ((BasePlugin)this).Config.Bind<int>("CrimeScenes", "PoolScrubsMop", 10, "Scrubbing ticks to mop up a blood pool with the mop.");
			BodyBagItem = ((BasePlugin)this).Config.Bind<string>("CrimeScenes", "BodyBagItem", "BinBag", "Item the body bag is made from (stretched to size).");
			SceneDebugKey = ((BasePlugin)this).Config.Bind<KeyCode>("Debug", "SceneKey", (KeyCode)0, "Testing: create a crime scene somewhere in the city right now.");
			SceneStatusKey = ((BasePlugin)this).Config.Bind<KeyCode>("Debug", "SceneStatusKey", (KeyCode)0, "Testing: write every crime scene's status to the log.");
			DeskJobs = ((BasePlugin)this).Config.Bind<string>("CityHall", "DeskJobs", "", "Comma-separated job names at City Hall who handle sanitation paperwork. Empty = any City Hall employee at work.");
			Lib.SaveGame.OnAfterLoad += delegate(object? _, SaveGameArgs args)
			{
				//IL_0017: Unknown result type (might be due to invalid IL or missing references)
				//IL_001d: Expected O, but got Unknown
				State.Load(args);
				Scenes.Load(args);
				ManualLogSource log = ((BasePlugin)this).Log;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(51, 4, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Save loaded: license=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(State.Licensed);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" fullTime=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(State.FullTime);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" binned=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(State.Binned);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" supervisor=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(State.Supervisor);
				}
				log.LogInfo(val);
			};
			Lib.SaveGame.OnAfterSave += delegate(object? _, SaveGameArgs args)
			{
				State.Save(args);
				Scenes.Save(args);
			};
			Lib.SaveGame.OnBeforeNewGame += delegate
			{
				State.Reset();
				Scenes.All.Clear();
				Scenes.NextSceneAt = -1f;
			};
			Dialogs.Register();
			new Harmony("sodmods.sanitationdepartment").PatchAll(typeof(Plugin).Assembly);
			((BasePlugin)this).Log.LogInfo((object)"Sanitation Department loaded");
		}
	}
	internal static class State
	{
		private const string FileName = "sanitation.txt";

		internal static bool Licensed;

		internal static bool FullTime;

		internal static int Binned;

		internal static bool Supervisor;

		internal static bool GlowOn = true;

		internal static int BagCount;

		internal static readonly Dictionary<int, float> SourceEmptied = new Dictionary<int, float>();

		internal static bool Picker;

		internal static bool Certified;

		internal static bool Mop;

		internal static int ScenesCleaned;

		internal static int BagCapacity
		{
			get
			{
				if (!Supervisor)
				{
					return Plugin.BagSize.Value;
				}
				return Plugin.SupervisorBagSize.Value;
			}
		}

		internal static void AddBinned(int items)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			if (items > 0)
			{
				int value = Plugin.SupervisorItems.Value;
				int binned = Binned;
				Binned += items;
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(16, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Binned ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(items);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (total ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(Binned);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")");
				}
				logger.LogInfo(val);
				if (!Supervisor && binned < value && Binned >= value)
				{
					Notify($"{value} items binned. City Hall has a promotion for you: Route Supervisor.", (Icon)13);
				}
			}
		}

		internal static void Reset()
		{
			Licensed = (FullTime = (Supervisor = (Picker = (Certified = (Mop = false)))));
			Binned = (BagCount = (ScenesCleaned = 0));
			SourceEmptied.Clear();
			GlowOn = true;
		}

		internal static void Load(SaveGameArgs args)
		{
			Reset();
			try
			{
				string saveGameDataPath = Lib.SaveGame.GetSaveGameDataPath(args, "sanitation.txt");
				if (!File.Exists(saveGameDataPath))
				{
					return;
				}
				string[] array = File.ReadAllLines(saveGameDataPath);
				for (int i = 0; i < array.Length; i++)
				{
					string[] array2 = array[i].Split('=', 2);
					if (array2.Length != 2)
					{
						continue;
					}
					string text = array2[1].Trim();
					switch (array2[0].Trim())
					{
					case "licensed":
						Licensed = text == "true";
						break;
					case "fulltime":
						FullTime = text == "true";
						break;
					case "binned":
						int.TryParse(text, out Binned);
						break;
					case "supervisor":
						Supervisor = text == "true";
						break;
					case "glow":
						GlowOn = text != "false";
						break;
					case "bag":
						int.TryParse(text, out BagCount);
						break;
					case "picker":
						Picker = text == "true";
						break;
					case "certified":
						Certified = text == "true";
						break;
					case "mop":
						Mop = text == "true";
						break;
					case "scenes":
						int.TryParse(text, out ScenesCleaned);
						break;
					case "dustbins":
					{
						string[] array3 = text.Split(';', StringSplitOptions.RemoveEmptyEntries);
						for (int j = 0; j < array3.Length; j++)
						{
							string[] array4 = array3[j].Split(':');
							if (array4.Length == 2 && int.TryParse(array4[0], out var result) && float.TryParse(array4[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2))
							{
								SourceEmptied[result] = result2;
							}
						}
						break;
					}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Failed to load sanitation state: " + ex));
			}
		}

		internal static void Save(SaveGameArgs args)
		{
			try
			{
				File.WriteAllLines(Lib.SaveGame.GetSaveGameDataPath(args, "sanitation.txt"), new string[11]
				{
					"licensed=" + (Licensed ? "true" : "false"),
					"fulltime=" + (FullTime ? "true" : "false"),
					$"binned={Binned}",
					"supervisor=" + (Supervisor ? "true" : "false"),
					"glow=" + (GlowOn ? "true" : "false"),
					$"bag={BagCount}",
					"picker=" + (Picker ? "true" : "false"),
					"certified=" + (Certified ? "true" : "false"),
					"mop=" + (Mop ? "true" : "false"),
					$"scenes={ScenesCleaned}",
					"dustbins=" + string.Join(";", SourceEmptied.Select((KeyValuePair<int, float> kv) => $"{kv.Key}:{kv.Value.ToString(CultureInfo.InvariantCulture)}"))
				});
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Failed to save sanitation state: " + ex));
			}
		}

		internal static void Notify(string message, Icon icon = (Icon)36)
		{
			//IL_0007: 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)
			Lib.GameMessage.Broadcast(message, (GameMessageType)0, icon, (Color?)InterfaceControls.Instance.messageGreen, 0f);
			Plugin.Logger.LogInfo((object)message);
		}
	}
	internal enum SceneState
	{
		Undiscovered,
		Investigating,
		Released,
		Done
	}
	internal class Scene
	{
		public int AddressId;

		public int VictimId;

		public SceneState State;

		public float DiedAt;

		public int InitialSpots;

		public int Scrubbed;

		public int BagId;

		public bool Bagged => BagId != 0;

		public NewAddress Address
		{
			get
			{
				NewAddress result = default(NewAddress);
				if (!CityData.Instance.addressDictionary.TryGetValue(AddressId, ref result))
				{
					return null;
				}
				return result;
			}
		}

		public Human Victim
		{
			get
			{
				Human result = default(Human);
				if (!CityData.Instance.GetHuman(VictimId, ref result, true))
				{
					return null;
				}
				return result;
			}
		}

		public string Name
		{
			get
			{
				NewAddress address = Address;
				return ((address != null) ? ((Object)address).name : null) ?? $"address {AddressId}";
			}
		}
	}
	internal static class Scenes
	{
		private const string FileName = "cleanup.txt";

		internal static readonly List<Scene> All = new List<Scene>();

		internal static float NextSceneAt = -1f;

		private static MurderWeaponPreset weapon;

		private static bool Generating
		{
			get
			{
				if (State.Certified && FullTime.Allowed)
				{
					if (!State.FullTime)
					{
						return !Plugin.ScenesOnlyFullTime.Value;
					}
					return true;
				}
				return false;
			}
		}

		internal static Scene At(NewGameLocation loc)
		{
			int? obj;
			if (loc == null)
			{
				obj = null;
			}
			else
			{
				NewAddress thisAsAddress = loc.thisAsAddress;
				obj = ((thisAsAddress != null) ? new int?(thisAsAddress.id) : ((int?)null));
			}
			int id = obj ?? (-1);
			if (id >= 0)
			{
				return All.Find((Scene s) => s.AddressId == id);
			}
			return null;
		}

		internal static Interactable FindBag(Scene s)
		{
			if (!s.Bagged)
			{
				return null;
			}
			Interactable val = default(Interactable);
			if (CityData.Instance.savableInteractableDictionary.TryGetValue(s.BagId, ref val) && val != null)
			{
				return val;
			}
			Enumerator<Interactable> enumerator = CityData.Instance.interactableDirectory.GetEnumerator();
			while (enumerator.MoveNext())
			{
				Interactable current = enumerator.Current;
				if (current != null && current.id == s.BagId)
				{
					return current;
				}
			}
			return null;
		}

		internal static bool IsBodyBag(Interactable item)
		{
			if (item != null)
			{
				return All.Any((Scene s) => s.Bagged && s.BagId == item.id);
			}
			return false;
		}

		private static void ScheduleNext()
		{
			NextSceneAt = SessionData.Instance.gameTime + Random.Range(Plugin.SceneIntervalMin.Value, Math.Max(Plugin.SceneIntervalMin.Value, Plugin.SceneIntervalMax.Value));
		}

		private static void GeneratorTick()
		{
			if (!Generating)
			{
				NextSceneAt = -1f;
			}
			else if (NextSceneAt < 0f)
			{
				ScheduleNext();
			}
			else if (!(SessionData.Instance.gameTime < NextSceneAt))
			{
				if (All.Count((Scene s) => s.State != SceneState.Done) < Plugin.MaxScenes.Value)
				{
					SpawnScene(announce: false);
				}
				ScheduleNext();
			}
		}

		public static void SpawnScene(bool announce)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			Citizen val = PickVictim();
			if ((Object)(object)val == (Object)null)
			{
				if (announce)
				{
					State.Notify("[Debug] No suitable scene right now.", (Icon)36);
				}
				return;
			}
			NewAddress home = ((Human)val).home;
			Kill(val);
			All.Add(new Scene
			{
				AddressId = home.id,
				VictimId = ((Human)val).humanID,
				State = SceneState.Undiscovered,
				DiedAt = SessionData.Instance.gameTime
			});
			ManualLogSource logger = Plugin.Logger;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(19, 2, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Scene created: ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(((Human)val).GetCitizenName());
				((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" at ");
				((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(((Object)home).name);
			}
			logger.LogInfo(val2);
			if (announce)
			{
				State.Notify("[Debug] Scene created at " + ((Object)home).name + ".", (Icon)36);
			}
		}

		private static Citizen PickVictim()
		{
			Player player = Player.Instance;
			MurderController murders = MurderController.Instance;
			List<Citizen> list = ((IEnumerable<Citizen>)CityData.Instance.citizenDirectory.ToArray()).Where((Citizen c) => (Object)(object)c != (Object)null && !((Actor)c).isDead && !((Human)c).removedFromWorld && !((Actor)c).isPlayer && (Object)(object)((Actor)c).ai != (Object)null && !((Actor)c).isEnforcer && (Object)(object)((Human)c).home != (Object)null && (Object)(object)((Human)c).home.residence != (Object)null && ((Human)c).home.company == null && (Object)(object)((Actor)c).currentGameLocation == (Object)(object)((Human)c).home && (Object)(object)((NewGameLocation)((Human)c).home).building != (Object)(object)((Actor)player).currentBuilding && (Object)(object)((Human)c).home != (Object)(object)((Human)player).home && At((NewGameLocation)(object)((Human)c).home) == null && !GameplayController.Instance.enforcerCalls.ContainsKey((NewGameLocation)(object)((Human)c).home) && ((IEnumerable<Actor>)((NewGameLocation)((Human)c).home).currentOccupants.ToArray()).All((Actor o) => (Object)(object)o == (Object)(object)c || o.isDead) && !((IEnumerable<Murder>)murders.activeMurders.ToArray()).Any((Murder m) => (Object)(object)m.victim == (Object)(object)c || (Object)(object)m.murderer == (Object)(object)c) && (Object)(object)murders.currentMurderer != (Object)(object)c).ToList();
			if (list.Count != 0)
			{
				return list[Random.Range(0, list.Count)];
			}
			return null;
		}

		private static void Kill(Citizen victim)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Expected O, but got Unknown
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			((Actor)victim).SetHealth(0f);
			((Actor)victim).ai.SetKO(true, default(Vector3), default(Vector3), false, 0f, true, 1f);
			Death val = new Death(IL2CPP.il2cpp_object_new(Il2CppClassPointerStore<Death>.NativeClassPtr))
			{
				isDead = true,
				murder = -1,
				victim = ((Human)victim).humanID,
				killer = ((Human)victim).humanID,
				location = Vector3Int.op_Implicit(((Actor)victim).currentNodeCoord),
				time = SessionData.Instance.gameTime
			};
			val.timeOfDeathRange = Toolbox.Instance.CreateTimeRange(val.time, GameplayControls.Instance.timeOfDeathAccuracy, false, true, 15);
			if ((Object)(object)((Actor)victim).currentRoom != (Object)null)
			{
				((Actor)victim).currentRoom.containsDead = true;
			}
			((Human)victim).death = val;
			((Human)victim).Murder((Human)(object)victim, false, (Murder)null, (Interactable)null, 0f);
			Bloody(victim);
		}

		private static void Bloody(Citizen victim)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: 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_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: 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_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_013e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0148: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (weapon == null)
				{
					weapon = FindWeapon();
				}
				Vector3 val = ((Component)victim).transform.position + Vector3.up * 1.1f;
				MurderWeaponPreset obj = weapon;
				if ((SoCustomComparison)(object)((obj != null) ? obj.entryWound : null) != (SoCustomComparison)null)
				{
					victim.CreateWoundClosestToPoint(val, Vector3.up, weapon.entryWound, weapon);
					victim.CreateWoundClosestToPoint(val + Vector3.up * 0.15f, Vector3.up, weapon.entryWound, weapon);
				}
				MurderWeaponPreset obj2 = weapon;
				SpatterPatternPreset val2 = ((obj2 != null) ? obj2.forwardSpatter : null) ?? GameplayControls.Instance.bleedingSpatter;
				MurderWeaponPreset obj3 = weapon;
				SpatterPatternPreset val3 = ((obj3 != null) ? obj3.backSpatter : null) ?? GameplayControls.Instance.bleedingSpatter;
				Vector3 val4 = default(Vector3);
				((Vector3)(ref val4))..ctor(0f, 1.1f, 0f);
				Vector3[] array = (Vector3[])(object)new Vector3[4]
				{
					Vector3.forward,
					Vector3.back,
					Vector3.left + Vector3.down,
					Vector3.down
				};
				foreach (Vector3 val5 in array)
				{
					new SpatterSimulation((Human)(object)victim, val4, val5, val2, (EraseMode)0, 1f, false);
				}
				new SpatterSimulation((Human)(object)victim, val4, Vector3.back, val3, (EraseMode)0, 1f, false);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Blood failed: " + ex));
			}
		}

		private static MurderWeaponPreset FindWeapon()
		{
			List<MurderWeaponPreset> source = (from o in (IEnumerable<Object>)Resources.FindObjectsOfTypeAll(Il2CppType.Of<MurderWeaponPreset>())
				select ((Il2CppObjectBase)o).TryCast<MurderWeaponPreset>() into w
				where (SoCustomComparison)(object)w != (SoCustomComparison)null && (SoCustomComparison)(object)w.entryWound != (SoCustomComparison)null
				select w).ToList();
			return ((IEnumerable<MurderWeaponPreset>)source).FirstOrDefault((Func<MurderWeaponPreset, bool>)((MurderWeaponPreset w) => ((Object)w).name.IndexOf("knife", StringComparison.OrdinalIgnoreCase) >= 0)) ?? source.FirstOrDefault();
		}

		public static void Tick()
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Invalid comparison between Unknown and I4
			GeneratorTick();
			float gameTime = SessionData.Instance.gameTime;
			bool flag = default(bool);
			foreach (Scene item in All.ToList())
			{
				NewAddress address = item.Address;
				Human victim = item.Victim;
				if ((Object)(object)address == (Object)null || (Object)(object)victim == (Object)null || (victim.removedFromWorld && !item.Bagged && item.State != SceneState.Done))
				{
					ManualLogSource logger = Plugin.Logger;
					BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(40, 1, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Dropping scene at ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(item.Name);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": address or body gone");
					}
					logger.LogWarning(val);
					All.Remove(item);
					continue;
				}
				switch (item.State)
				{
				case SceneState.Undiscovered:
					if (victim.death != null && victim.death.reported)
					{
						Investigating(item, address);
					}
					else if (gameTime >= item.DiedAt + Plugin.DiscoveryHours.Value)
					{
						Report(item, address, victim);
					}
					break;
				case SceneState.Investigating:
				{
					Dictionary<NewGameLocation, EnforcerCall> enforcerCalls = GameplayController.Instance.enforcerCalls;
					if (!enforcerCalls.ContainsKey((NewGameLocation)(object)address))
					{
						Release(item);
						break;
					}
					EnforcerCall val2 = enforcerCalls[(NewGameLocation)(object)address];
					float num = val2.arrivalTime + GameplayControls.Instance.crimeSceneSearchLength + Plugin.InvestigationHours.Value;
					if ((int)val2.state == 2 && val2.arrivalTime > 0f && gameTime > num)
					{
						val2.isCrimeScene = true;
						val2.state = (EnforcerCallState)3;
					}
					break;
				}
				case SceneState.Done:
					if (!GameplayController.Instance.crimeSceneCleanups.Contains((NewGameLocation)(object)address))
					{
						Collected(item);
					}
					break;
				}
			}
		}

		private static void Report(Scene s, NewAddress addr, Human victim)
		{
			Citizen val = (from h in (IEnumerable<Citizen>)CityData.Instance.citizenDirectory.ToArray()
				where (Object)(object)h != (Object)null && (Object)(object)h != (Object)(object)victim && !((Actor)h).isDead && (Object)(object)((Actor)h).ai != (Object)null && !((Actor)h).isEnforcer
				orderby (!((Object)(object)((Actor)h).currentBuilding == (Object)(object)((NewGameLocation)addr).building)) ? 1 : 0
				select h).FirstOrDefault();
			GameplayController.Instance.CallEnforcers((NewGameLocation)(object)addr, false, false, 0f);
			if ((Object)(object)val != (Object)null)
			{
				victim.death.SetReported((Human)(object)val, (ReportType)1);
			}
			Investigating(s, addr);
		}

		private static void Investigating(Scene s, NewAddress addr)
		{
			s.State = SceneState.Investigating;
			if (State.Certified)
			{
				State.Notify("Enforcers responding to a death at " + ((Object)addr).name + ". Cleanup crew on standby.", (Icon)22);
			}
		}

		public static void Release(Scene s)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Expected O, but got Unknown
			s.State = SceneState.Released;
			NewAddress address = s.Address;
			if (!((Object)(object)address == (Object)null))
			{
				s.InitialSpots = Cleaning.CountSpots(address);
				GameplayController.Instance.AddGuestPass(address, Plugin.AccessHours.Value);
				string text = "Cleanup job: " + ((Object)address).name + " has been released by the Enforcers. Bag the body and scrub the blood.";
				InterfaceController.Instance.NewGameMessage((GameMessageType)0, 0, text, (Icon)22, AudioControls.Instance.enforcerScannerMsg, false, default(Color), -1, 0f, (RectTransform)null, (PingOnComplete)0, (Evidence)null, (List<DataKey>)null, (Sprite)null);
				ManualLogSource logger = Plugin.Logger;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(9, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(s.InitialSpots);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" spots)");
				}
				logger.LogInfo(val);
			}
		}

		public static void Complete(Scene s)
		{
			NewAddress address = s.Address;
			s.State = SceneState.Done;
			int num = Plugin.SceneFee.Value + s.Scrubbed * Plugin.PayPerSpot.Value;
			if (num > 0)
			{
				GameplayController.Instance.AddMoney(num, true, "sanitation");
			}
			State.ScenesCleaned++;
			((NewGameLocation)address).loggedAsCrimeScene = SessionData.Instance.gameTime - GameplayControls.Instance.crimeSceneCleanupDelay - 0.01f;
			if (!GameplayController.Instance.crimeSceneCleanups.Contains((NewGameLocation)(object)address))
			{
				GameplayController.Instance.crimeSceneCleanups.Add((NewGameLocation)(object)address);
			}
			State.Notify($"{((Object)address).name} is clean. {CityControls.Instance.cityCurrency}{num} paid. The coroner collects the body bag once you've left.", (Icon)13);
		}

		private static void Collected(Scene s)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Expected O, but got Unknown
			Interactable obj = FindBag(s);
			if (obj != null)
			{
				obj.SafeDelete(true);
			}
			All.Remove(s);
			ManualLogSource logger = Plugin.Logger;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(19, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Scene at ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(s.Name);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" collected");
			}
			logger.LogInfo(val);
		}

		internal static void Load(SaveGameArgs args)
		{
			All.Clear();
			NextSceneAt = -1f;
			try
			{
				string saveGameDataPath = Lib.SaveGame.GetSaveGameDataPath(args, "cleanup.txt");
				if (!File.Exists(saveGameDataPath))
				{
					return;
				}
				string[] array = File.ReadAllLines(saveGameDataPath);
				foreach (string text in array)
				{
					if (text.StartsWith("next="))
					{
						string text2 = text;
						float.TryParse(text2.Substring(5, text2.Length - 5), NumberStyles.Float, CultureInfo.InvariantCulture, out NextSceneAt);
						continue;
					}
					string[] array2 = text.Split(',');
					if (array2.Length >= 7 && int.TryParse(array2[0], out var result) && int.TryParse(array2[1], out var result2) && Enum.TryParse<SceneState>(array2[2], out var result3) && float.TryParse(array2[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4))
					{
						int.TryParse(array2[4], out var result5);
						int.TryParse(array2[5], out var result6);
						int.TryParse(array2[6], out var result7);
						All.Add(new Scene
						{
							AddressId = result,
							VictimId = result2,
							State = result3,
							DiedAt = result4,
							InitialSpots = result5,
							Scrubbed = result6,
							BagId = result7
						});
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Failed to load cleanup scenes: " + ex));
			}
		}

		internal static void Save(SaveGameArgs args)
		{
			try
			{
				List<string> list = new List<string> { "next=" + NextSceneAt.ToString(CultureInfo.InvariantCulture) };
				list.AddRange(All.Select((Scene s) => $"{s.AddressId},{s.VictimId},{s.State},{s.DiedAt.ToString(CultureInfo.InvariantCulture)},{s.InitialSpots},{s.Scrubbed},{s.BagId}"));
				File.WriteAllLines(Lib.SaveGame.GetSaveGameDataPath(args, "cleanup.txt"), list);
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)("Failed to save cleanup scenes: " + ex));
			}
		}

		internal static void LogStatus()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Expected O, but got Unknown
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			float gameTime = SessionData.Instance.gameTime;
			ManualLogSource logger = Plugin.Logger;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(58, 4, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("--- ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(All.Count);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" scene(s), game time ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(gameTime, "0.00");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", next scene at ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(NextSceneAt, "0.00");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", generating=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(Generating);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ---");
			}
			logger.LogInfo(val);
			foreach (Scene item in All)
			{
				NewAddress address = item.Address;
				Dictionary<NewGameLocation, EnforcerCall> enforcerCalls = GameplayController.Instance.enforcerCalls;
				EnforcerCall val2 = (((Object)(object)address != (Object)null && enforcerCalls.ContainsKey((NewGameLocation)(object)address)) ? enforcerCalls[(NewGameLocation)(object)address] : null);
				ManualLogSource logger2 = Plugin.Logger;
				val = new BepInExInfoLogInterpolatedStringHandler(78, 11, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(item.Name);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SceneState>(item.State);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", victim ");
					BepInExInfoLogInterpolatedStringHandler obj = val;
					Human victim = item.Victim;
					((BepInExLogInterpolatedStringHandler)obj).AppendFormatted<string>((victim != null) ? victim.GetCitizenName() : null);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", died ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(gameTime - item.DiedAt, "0.00");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("h ago, ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("call=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>((val2 == null) ? "none" : $"{val2.state} arrived {gameTime - val2.arrivalTime:0.00}h ago guard={val2.guard}");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("spots ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(((Object)(object)address != (Object)null) ? Cleaning.CountSpots(address) : (-1));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("/");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(item.InitialSpots);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", pools ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(((Object)(object)address != (Object)null) ? Cleaning.Pools(address).Count : (-1));
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", ");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("scrubbed ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(item.Scrubbed);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", bag ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(item.BagId);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", inGameQueue=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(GameplayController.Instance.crimeSceneCleanups.Contains((NewGameLocation)(object)address));
				}
				logger2.LogInfo(val);
			}
		}
	}
	[HarmonyPatch(typeof(CitizenBehaviour), "GameWorldCheck")]
	internal static class WorldTickPatch
	{
		private static void Prefix()
		{
			Hold();
		}

		private static void Postfix()
		{
			try
			{
				Hold();
				Scenes.Tick();
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogError((object)ex);
			}
		}

		private static void Hold()
		{
			try
			{
				GameplayController instance = GameplayController.Instance;
				List<NewGameLocation> val = ((instance != null) ? instance.crimeSceneCleanups : null);
				if (val ==