using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.Json; using BepInEx; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using HarmonyLib; using Il2CppSystem.Collections.Generic; using Microsoft.CodeAnalysis; using SOD.Common; using SOD.Common.BepInEx; using SOD.Common.BepInEx.Configuration; using SOD.Common.Helpers; using SideStreet.Core; using SideStreet.Patches.BoardPatches; using SideStreet.Patches.SideJobPatches; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("SideStreet")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+c0fd6cd84d138b50117fbaadbe4984e3a2e93716")] [assembly: AssemblyProduct("Side job thematic coherence")] [assembly: AssemblyTitle("SideStreet")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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 SideStreet { public interface IPluginBindings : IPatchToggleBindings { } public interface IPatchToggleBindings { [Binding(true, "Post arrest warrants only at City Hall", "_PatchEnable.ArrestWarrantsAtCityHall")] bool PatchEnableArrestWarrantsAtCityHall { get; set; } [Binding(true, "Keep apartment listings off the City Hall board", "_PatchEnable.ApartmentListingsOffCityHallBoard")] bool PatchEnableApartmentListingsOffCityHallBoard { get; set; } [Binding(true, "Scale arrest warrant count to city population", "_PatchEnable.ArrestWarrantQuota")] bool PatchEnableArrestWarrantQuota { get; set; } } [BepInPlugin("Sclamy.SideStreet", "SideStreet", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : PluginController { public const string PLUGIN_GUID = "Sclamy.SideStreet"; public const string PLUGIN_NAME = "SideStreet"; public const string PLUGIN_VERSION = "0.1.0"; public override void Load() { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown PatchIfEnabled(typeof(ArrestWarrantsAtCityHall), base.Config.PatchEnableArrestWarrantsAtCityHall); PatchIfEnabled(typeof(ApartmentListingsOffCityHallBoard), base.Config.PatchEnableApartmentListingsOffCityHallBoard); PatchIfEnabled(typeof(ArrestWarrantQuota), base.Config.PatchEnableArrestWarrantQuota); SideStreetSaveData.Enable(); ArrestWarrantRefresh.Enable(); ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(20, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Plugin "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("Sclamy.SideStreet"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" v"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("0.1.0"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" is loaded!"); } log.LogInfo(val); } private void PatchIfEnabled(Type patchType, bool enabled) { if (enabled) { base.Harmony.CreateClassProcessor(patchType).Patch(); Type[] nestedTypes = patchType.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); foreach (Type type in nestedTypes) { base.Harmony.CreateClassProcessor(type).Patch(); } } } } public static class MyPluginInfo { public const string PLUGIN_GUID = "SideStreet"; public const string PLUGIN_NAME = "Side job thematic coherence"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace SideStreet.Patches.SideJobPatches { internal static class ArrestWarrantQuota { [HarmonyPatch(typeof(SideJobController), "JobCreationCheck")] internal static class ChooseTier { [HarmonyPrefix] private static void Prefix(SideJobController __instance) { chosenTier = null; cullTier = null; if ((Object)(object)__instance == (Object)null || __instance.jobTracking == null) { return; } int num = ArrestWarrants.CountPosted(__instance); int num2 = ArrestWarrants.DesiredTotal(); if (num > num2) { cullTier = FirstStandingTier(__instance); } else { if (num >= num2 || JobBoards.FreePostingSlots(FirstArrestPosting(__instance)) <= 0) { return; } List list = new List(); for (int i = 0; i < __instance.jobTracking.Count; i++) { JobTracking val = __instance.jobTracking[i]; if (val != null && ArrestWarrants.IsArrestWarrant(val.preset) && val.activeJobs.Count <= 0 && VanillaFrequency(val.preset) > 0) { list.Add(val.preset); } } if (list.Count > 0) { chosenTier = list[Toolbox.Instance.Rand(0, list.Count, false)]; } } } private static InteractablePreset FirstArrestPosting(SideJobController controller) { for (int i = 0; i < controller.jobTracking.Count; i++) { JobTracking val = controller.jobTracking[i]; if (val != null && ArrestWarrants.IsArrestWarrant(val.preset)) { return val.preset.jobPosting; } } return null; } private static JobPreset FirstStandingTier(SideJobController controller) { for (int i = 0; i < controller.jobTracking.Count; i++) { JobTracking val = controller.jobTracking[i]; if (val != null && ArrestWarrants.IsArrestWarrant(val.preset) && val.activeJobs.Count > 0) { return val.preset; } } return null; } private static int VanillaFrequency(JobPreset preset) { ApplyTier.readingVanilla = true; try { return preset.GetFrequencyForSocialCreditLevel(); } finally { ApplyTier.readingVanilla = false; } } } [HarmonyPatch(typeof(JobPreset), "GetFrequencyForSocialCreditLevel")] internal static class ApplyTier { internal static bool readingVanilla; [HarmonyPostfix] private static void Postfix(JobPreset __instance, ref int __result) { if (readingVanilla || !ArrestWarrants.IsArrestWarrant(__instance)) { return; } SideJobController instance = SideJobController.Instance; if ((Object)(object)instance == (Object)null || instance.jobTracking == null) { return; } int num = 0; for (int i = 0; i < instance.jobTracking.Count; i++) { JobTracking val = instance.jobTracking[i]; if (val != null && !((SoCustomComparison)(object)val.preset != (SoCustomComparison)(object)__instance)) { num = val.activeJobs.Count; break; } } if ((SoCustomComparison)(object)__instance == (SoCustomComparison)(object)chosenTier) { __result = num + 1; } else if ((SoCustomComparison)(object)__instance == (SoCustomComparison)(object)cullTier) { __result = num - 1; } else { __result = num; } } } private static JobPreset chosenTier; private static JobPreset cullTier; } [HarmonyPatch(typeof(SideJob), "PostJob")] internal static class ArrestWarrantsAtCityHall { [HarmonyPrefix] private static void Prefix(SideJob __instance, ref List __state) { __state = null; if (__instance != null) { bool flag = ArrestWarrants.IsArrestWarrant(__instance.preset); __state = JobBoards.Restrict(flag); if (__state == null && flag) { PluginController.Log.LogError((object)"No City Hall job board found - falling back to vanilla placement for this warrant"); } } } [HarmonyFinalizer] private static void Finalizer(ref List __state) { JobBoards.Restore(__state); __state = null; } } } namespace SideStreet.Patches.BoardPatches { [HarmonyPatch(typeof(CitizenBehaviour), "UpdateForSale")] internal static class ApartmentListingsOffCityHallBoard { [HarmonyPrefix] private static void Prefix(ref List __state) { EvictExistingListings(); __state = JobBoards.Restrict(cityHall: false); } [HarmonyFinalizer] private static void Finalizer(ref List __state) { JobBoards.Restore(__state); __state = null; } private static void EvictExistingListings() { GameplayController instance = GameplayController.Instance; if ((Object)(object)instance == (Object)null || instance.forSale == null) { return; } for (int i = 0; i < instance.forSale.Count; i++) { NewAddress val = instance.forSale[i]; if (!((Object)(object)val == (Object)null) && val.saleNote != null && JobBoards.IsCityHall(val.saleNote.furnitureParent)) { val.saleNote.Delete(); val.saleNote = null; } } } } } namespace SideStreet.Core { internal static class ArrestWarrantRefresh { private const float HoursBetweenRetirements = 6f; internal static void Enable() { Lib.Time.OnHourChanged += delegate { CheckForRetirement(); }; } private static void CheckForRetirement() { float gameTime = SessionData.Instance.gameTime; SaveState state = SideStreetSaveData.State; if (!(gameTime - state.LastWarrantRetirement < 6f) && RetireOneWarrant()) { state.LastWarrantRetirement = gameTime; } } private static bool RetireOneWarrant() { //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Expected O, but got Unknown SideJobController instance = SideJobController.Instance; if ((Object)(object)instance == (Object)null || instance.jobTracking == null) { return false; } List list = new List(); List list2 = new List(); for (int i = 0; i < instance.jobTracking.Count; i++) { JobTracking val = instance.jobTracking[i]; if (val == null || !ArrestWarrants.IsArrestWarrant(val.preset)) { continue; } for (int j = 0; j < val.activeJobs.Count; j++) { SideJob val2 = val.activeJobs[j]; if (val2 != null && !val2.accepted) { if (val2.post == null) { list2.Add(val2); } else if (CanRetire(val2)) { list.Add(val2); } } } } foreach (SideJob item in list2) { item.End(); } if (list2.Count > 0) { ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val3 = new BepInExWarningLogInterpolatedStringHandler(44, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Cleared "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted(list2.Count); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" warrant(s) that could not be placed"); } log.LogWarning(val3); } if (list.Count <= 0) { return false; } list[Toolbox.Instance.Rand(0, list.Count, false)].End(); return true; } private static bool CanRetire(SideJob job) { if (job.post.rPl || job.post.node == null) { return false; } Player instance = Player.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)((Actor)instance).currentGameLocation == (Object)null) { return false; } return (Object)(object)job.post.node.gameLocation != (Object)(object)((Actor)instance).currentGameLocation; } } internal static class ArrestWarrants { private const string ArrestCaseName = "arrest"; private const int TwoWarrantsBelow = 250; private const int ThreeWarrantsBelow = 400; private const int FourWarrantsBelow = 525; internal static bool IsArrestWarrant(JobPreset preset) { if ((SoCustomComparison)(object)preset == (SoCustomComparison)null) { return false; } string caseName = preset.caseName; return !string.IsNullOrEmpty(caseName) && caseName.Equals("arrest", StringComparison.OrdinalIgnoreCase); } internal static int DesiredTotal() { CityData instance = CityData.Instance; if ((Object)(object)instance == (Object)null || instance.citizenDirectory == null) { return 2; } int count = instance.citizenDirectory.Count; if (count < 250) { return 2; } if (count < 400) { return 3; } if (count < 525) { return 4; } return 5; } internal static int CountPosted(SideJobController controller) { if ((Object)(object)controller == (Object)null || controller.jobTracking == null) { return 0; } int num = 0; for (int i = 0; i < controller.jobTracking.Count; i++) { JobTracking val = controller.jobTracking[i]; if (val == null || !IsArrestWarrant(val.preset)) { continue; } for (int j = 0; j < val.activeJobs.Count; j++) { SideJob val2 = val.activeJobs[j]; if (val2 != null && val2.post != null) { num++; } } } return num; } } internal static class JobBoards { internal static bool IsCityHall(FurnitureLocation board) { if (board == null || board.anchorNode == null) { return false; } NewGameLocation gameLocation = board.anchorNode.gameLocation; if ((Object)(object)gameLocation == (Object)null || (Object)(object)gameLocation.building == (Object)null) { return false; } BuildingPreset preset = gameLocation.building.preset; RoutineControls instance = RoutineControls.Instance; if ((SoCustomComparison)(object)preset == (SoCustomComparison)null || (Object)(object)instance == (Object)null || (SoCustomComparison)(object)instance.cityHall == (SoCustomComparison)null) { return false; } return ((SoCustomComparison)preset).presetName == ((SoCustomComparison)instance.cityHall).presetName; } internal static List Collect(bool cityHall) { List val = new List(); CityData instance = CityData.Instance; if ((Object)(object)instance == (Object)null || instance.jobBoardsDirectory == null) { return val; } List jobBoardsDirectory = instance.jobBoardsDirectory; for (int i = 0; i < jobBoardsDirectory.Count; i++) { FurnitureLocation val2 = jobBoardsDirectory[i]; if (IsCityHall(val2) == cityHall) { val.Add(val2); } } return val; } internal static List Restrict(bool cityHall) { CityData instance = CityData.Instance; if ((Object)(object)instance == (Object)null) { return null; } List jobBoardsDirectory = instance.jobBoardsDirectory; if (jobBoardsDirectory == null || jobBoardsDirectory.Count <= 0) { return null; } List val = Collect(cityHall); if (val.Count <= 0) { return null; } instance.jobBoardsDirectory = val; return jobBoardsDirectory; } internal static int FreePostingSlots(InteractablePreset posting) { if ((SoCustomComparison)(object)posting == (SoCustomComparison)null || posting.subObjectClasses == null) { return 0; } int num = 0; List val = Collect(cityHall: true); for (int i = 0; i < val.Count; i++) { FurnitureLocation val2 = val[i]; object obj; if (val2 == null) { obj = null; } else { FurniturePreset furniture = val2.furniture; obj = ((furniture != null) ? furniture.subObjects : null); } if (obj == null || val2.spawnedInteractables == null) { continue; } for (int j = 0; j < val2.furniture.subObjects.Count; j++) { SubObject val3 = val2.furniture.subObjects[j]; if (val3 == null || !posting.subObjectClasses.Contains(val3.preset)) { continue; } bool flag = false; for (int k = 0; k < val2.spawnedInteractables.Count; k++) { Interactable val4 = val2.spawnedInteractables[k]; if (val4 != null && val4.subObject == val3) { flag = true; break; } } if (!flag) { num++; } } } return num; } internal static void Restore(List original) { if (original != null) { CityData instance = CityData.Instance; if ((Object)(object)instance != (Object)null) { instance.jobBoardsDirectory = original; } } } } internal class SaveState { public float LastWarrantRetirement { get; set; } } internal static class SideStreetSaveData { private const string FileName = "sidestreet.json"; internal static SaveState State { get; private set; } = new SaveState(); internal static void Enable() { Lib.SaveGame.OnAfterSave += OnSave; Lib.SaveGame.OnAfterLoad += OnLoad; Lib.SaveGame.OnAfterNewGame += delegate { State = new SaveState(); }; } private static void OnSave(object sender, SaveGameArgs args) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown string saveGameDataPath = Lib.SaveGame.GetSaveGameDataPath(args, "sidestreet.json"); try { File.WriteAllText(saveGameDataPath, JsonSerializer.Serialize(State)); } catch (IOException ex) { ManualLogSource log = PluginController.Log; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(18, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not write "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("sidestreet.json"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } } private static void OnLoad(object sender, SaveGameArgs args) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown string saveGameDataPath = Lib.SaveGame.GetSaveGameDataPath(args, "sidestreet.json"); bool flag = default(bool); try { if (File.Exists(saveGameDataPath)) { State = JsonSerializer.Deserialize(File.ReadAllText(saveGameDataPath)) ?? new SaveState(); return; } } catch (JsonException ex) { ManualLogSource log = PluginController.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(33, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not read "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("sidestreet.json"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", starting fresh: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex.Message); } log.LogWarning(val); } catch (IOException ex2) { ManualLogSource log2 = PluginController.Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(33, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not read "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted("sidestreet.json"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", starting fresh: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex2.Message); } log2.LogWarning(val); } State = new SaveState { LastWarrantRetirement = SessionData.Instance.gameTime }; } } }