Decompiled source of SideStreet v0.1.0

SideStreet.dll

Decompiled 5 days ago
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<Plugin, IPluginBindings>
	{
		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<Plugin, IPluginBindings>.Log;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(20, 2, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Plugin ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("Sclamy.SideStreet");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" v");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("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<JobPreset> list = new List<JobPreset>();
					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<FurnitureLocation> __state)
		{
			__state = null;
			if (__instance != null)
			{
				bool flag = ArrestWarrants.IsArrestWarrant(__instance.preset);
				__state = JobBoards.Restrict(flag);
				if (__state == null && flag)
				{
					PluginController<Plugin, IPluginBindings>.Log.LogError((object)"No City Hall job board found - falling back to vanilla placement for this warrant");
				}
			}
		}

		[HarmonyFinalizer]
		private static void Finalizer(ref List<FurnitureLocation> __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<FurnitureLocation> __state)
		{
			EvictExistingListings();
			__state = JobBoards.Restrict(cityHall: false);
		}

		[HarmonyFinalizer]
		private static void Finalizer(ref List<FurnitureLocation> __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<SideJob> list = new List<SideJob>();
			List<SideJob> list2 = new List<SideJob>();
			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<Plugin, IPluginBindings>.Log;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val3 = new BepInExWarningLogInterpolatedStringHandler(44, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Cleared ");
					((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<int>(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<FurnitureLocation> Collect(bool cityHall)
		{
			List<FurnitureLocation> val = new List<FurnitureLocation>();
			CityData instance = CityData.Instance;
			if ((Object)(object)instance == (Object)null || instance.jobBoardsDirectory == null)
			{
				return val;
			}
			List<FurnitureLocation> 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<FurnitureLocation> Restrict(bool cityHall)
		{
			CityData instance = CityData.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			List<FurnitureLocation> jobBoardsDirectory = instance.jobBoardsDirectory;
			if (jobBoardsDirectory == null || jobBoardsDirectory.Count <= 0)
			{
				return null;
			}
			List<FurnitureLocation> 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<FurnitureLocation> 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<FurnitureLocation> 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<Plugin, IPluginBindings>.Log;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(18, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not write ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("sidestreet.json");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(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<SaveState>(File.ReadAllText(saveGameDataPath)) ?? new SaveState();
					return;
				}
			}
			catch (JsonException ex)
			{
				ManualLogSource log = PluginController<Plugin, IPluginBindings>.Log;
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(33, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not read ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("sidestreet.json");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", starting fresh: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				log.LogWarning(val);
			}
			catch (IOException ex2)
			{
				ManualLogSource log2 = PluginController<Plugin, IPluginBindings>.Log;
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(33, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Could not read ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("sidestreet.json");
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", starting fresh: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex2.Message);
				}
				log2.LogWarning(val);
			}
			State = new SaveState
			{
				LastWarrantRetirement = SessionData.Instance.gameTime
			};
		}
	}
}