Decompiled source of SanitationDepartment v1.1.2
SanitationDepartment.dll
Decompiled 5 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.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 ==