Decompiled source of GTFO An Eviction Mod v1.0.1

plugins/TenantEviction.dll

Decompiled 2 weeks 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 BepInEx;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("TenantEviction")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1+d2b2c54bccfe3834593c2d7a8ca65d77ad6a2879")]
[assembly: AssemblyProduct("TenantEviction")]
[assembly: AssemblyTitle("TenantEviction")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace TenantEviction
{
	[BepInPlugin("sm3gm.tenanteviction", "Tenant Eviction", "1.0.1")]
	public class TenantEvictionPlugin : BasePlugin
	{
		internal const string VER = "1.0.1";

		internal static ManualLogSource Log;

		internal static string StorePath;

		public override void Load()
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			Log = ((BasePlugin)this).Log;
			StorePath = Path.Combine(Paths.BepInExRootPath, "tenant-eviction-store.txt");
			((BasePlugin)this).AddComponent<EvictionController>();
			Harmony.CreateAndPatchAll(typeof(PostLoadPatch), (string)null);
			ManualLogSource log = Log;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(23, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Tenant Eviction ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>("1.0.1");
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" loaded");
			}
			log.LogInfo(val);
		}
	}
	public class EvictionController : MonoBehaviour
	{
		private class PendingMoveOut
		{
			public Human Human;

			public NewAddress Address;

			public float Due;

			public int Passes;
		}

		private Player? _player;

		private static readonly List<PendingMoveOut> Pending = new List<PendingMoveOut>();

		private const int MoveOutPasses = 10;

		private const float FirstPassDelay = 5f;

		private const float PassInterval = 30f;

		public EvictionController(IntPtr ptr)
			: base(ptr)
		{
		}

		public void Update()
		{
			if ((Object)(object)_player == (Object)null)
			{
				_player = Object.FindObjectOfType<Player>();
			}
			if (!((Object)(object)_player == (Object)null))
			{
				ProcessPendingMoveOuts();
				if (Input.GetKeyDown((KeyCode)121) && (Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307)))
				{
					TryEvict();
				}
			}
		}

		private void TryEvict()
		{
			NewNode currentNode = ((Actor)_player).currentNode;
			if (currentNode == null)
			{
				L("No current node");
				return;
			}
			NewFloor floor = currentNode.floor;
			if ((Object)(object)floor == (Object)null)
			{
				L("No floor on node");
				return;
			}
			NewAddress val = FindAddressPlayerIsIn(floor);
			if ((Object)(object)val == (Object)null)
			{
				L("No address at player position");
				Notify("No apartment here to evict");
				return;
			}
			if ((Object)(object)val.residence == (Object)null)
			{
				L("Refused: not a residence (" + ((Object)val).name + ")");
				Notify(((Object)val).name + " is not an apartment, nobody to evict");
				return;
			}
			if ((Object)(object)((Human)_player).home != (Object)null && (Object)(object)((Human)_player).home == (Object)(object)val)
			{
				L("Refused: target is the player's home (" + ((Object)val).name + ")");
				Notify("You can't evict yourself from your own home");
				return;
			}
			Toolbox instance = Toolbox.Instance;
			HotelGuest val2 = ((instance != null) ? instance.GetHotelRoom((Human)(object)_player) : null);
			if (val2 != null && (Object)(object)val2.GetAddress() == (Object)(object)val)
			{
				L("Refused: target is the player's hotel room (" + ((Object)val).name + ")");
				Notify("You can't evict yourself from your hotel room");
				return;
			}
			L("Target: " + ((Object)val).name);
			int num = val.inhabitants?.Count ?? 0;
			EvictOccupants(val, _player);
			int num2 = num - (val.inhabitants?.Count ?? 0);
			GameplayController val3 = Object.FindObjectOfType<GameplayController>();
			if ((Object)(object)val3 == (Object)null)
			{
				L("GameplayController not found");
				return;
			}
			bool flag = TrySpawnSaleNote(val, val3);
			if (flag)
			{
				AppendStore(((Object)val).name);
			}
			if (num2 > 0)
			{
				Notify(flag ? $"Evicted {num2} tenant(s) from {((Object)val).name}. Now for sale." : $"Evicted {num2} tenant(s) from {((Object)val).name}. Could not list it for sale.");
			}
			else
			{
				Notify(flag ? (((Object)val).name + " is already empty and for sale") : (((Object)val).name + " is empty but could not be listed for sale"));
			}
			L("Done");
		}

		private static void Notify(string text)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				InterfaceController instance = InterfaceController.Instance;
				if (instance != null)
				{
					instance.NewGameMessage((GameMessageType)0, 0, text, (Icon)3, (AudioEvent)null, false, default(Color), -1, 0f, (RectTransform)null, (PingOnComplete)0, (Evidence)null, (List<DataKey>)null, (Sprite)null);
				}
			}
			catch (Exception ex)
			{
				L("On-screen message failed: " + ex.Message);
			}
		}

		internal static void EvictOccupants(NewAddress address, Player? player)
		{
			int num = address.inhabitants?.Count ?? 0;
			Human[] array = (Human[])(object)new Human[num];
			for (int i = 0; i < num; i++)
			{
				array[i] = address.inhabitants[i];
			}
			if (num == 0)
			{
				L("No inhabitants, already vacant");
			}
			Human[] array2 = array;
			foreach (Human val in array2)
			{
				if (!((Object)(object)val == (Object)null))
				{
					if ((Object)(object)player != (Object)null && (Object)(object)val == (Object)(object)player)
					{
						L("  Skipping player");
						continue;
					}
					L("  Evicting: " + ((Object)val).name);
					address.RemoveInhabitant(val);
					((Actor)val).isHomeless = true;
					val.home = null;
					((Actor)val).RemoveFromKeyring(address);
					ApplyMoveOut(val, address, queue: true);
				}
			}
			if (address.owners != null)
			{
				for (int num2 = address.owners.Count - 1; num2 >= 0; num2--)
				{
					Human val2 = address.owners[num2];
					if (!((Object)(object)val2 == (Object)null))
					{
						if ((Object)(object)player != (Object)null && (Object)(object)val2 == (Object)(object)player)
						{
							L("  Skipping player as owner");
						}
						else
						{
							bool flag = false;
							array2 = array;
							for (int j = 0; j < array2.Length; j++)
							{
								if ((Object)(object)array2[j] == (Object)(object)val2)
								{
									flag = true;
									break;
								}
							}
							if (flag)
							{
								L("  Removing owner: " + ((Object)val2).name);
								address.RemoveOwner(val2);
							}
						}
					}
				}
			}
			address.CalculateRoomOwnership();
		}

		internal static string ReadMode()
		{
			try
			{
				string path = Path.Combine(Paths.BepInExRootPath, "tenant-eviction-mode.txt");
				if (!File.Exists(path))
				{
					return "goals";
				}
				string text = File.ReadAllText(path).Trim().ToLowerInvariant();
				return (text.Length == 0) ? "goals" : text;
			}
			catch
			{
				return "goals";
			}
		}

		private void ProcessPendingMoveOuts()
		{
			for (int num = Pending.Count - 1; num >= 0; num--)
			{
				PendingMoveOut pendingMoveOut = Pending[num];
				if (!(Time.time < pendingMoveOut.Due))
				{
					if ((Object)(object)pendingMoveOut.Human == (Object)null)
					{
						Pending.RemoveAt(num);
					}
					else
					{
						ApplyMoveOut(pendingMoveOut.Human, pendingMoveOut.Address, queue: false);
						pendingMoveOut.Passes++;
						if (pendingMoveOut.Passes >= 10)
						{
							Pending.RemoveAt(num);
						}
						else
						{
							pendingMoveOut.Due = Time.time + 30f;
						}
					}
				}
			}
		}

		private static int RemoveGoalsAt(Human h, NewAddress address)
		{
			NewAIController ai = ((Actor)h).ai;
			List<NewAIGoal> val = ((ai != null) ? ai.goals : null);
			if (val == null)
			{
				return 0;
			}
			List<NewAIGoal> list = new List<NewAIGoal>();
			for (int i = 0; i < val.Count; i++)
			{
				NewAIGoal val2 = val[i];
				if (val2 != null && ((Object)(object)val2.gameLocation == (Object)(object)address || (Object)(object)val2.passedGameLocation == (Object)(object)address))
				{
					list.Add(val2);
				}
			}
			foreach (NewAIGoal item in list)
			{
				item.Remove();
			}
			return list.Count;
		}

		private static void ApplyMoveOut(Human h, NewAddress address, bool queue)
		{
			string text = ReadMode();
			switch (text)
			{
			case "aitick":
			{
				NewAIController ai2 = ((Actor)h).ai;
				if (ai2 != null)
				{
					ai2.AITick(true, true);
				}
				break;
			}
			case "setresidence":
			{
				h.SetResidence((ResidenceController)null, true);
				NewAIController ai3 = ((Actor)h).ai;
				if (ai3 != null)
				{
					ai3.AITick(true, true);
				}
				break;
			}
			default:
			{
				if (text != "goals")
				{
					L("  Unknown mode '" + text + "', using goals");
				}
				int num = RemoveGoalsAt(h, address);
				NewAIController ai = ((Actor)h).ai;
				if (ai != null)
				{
					ai.AITick(true, true);
				}
				if (num > 0)
				{
					L($"  Removed {num} plan(s) at {((Object)address).name} for {((Object)h).name}");
				}
				break;
			}
			}
			if (queue)
			{
				Pending.Add(new PendingMoveOut
				{
					Human = h,
					Address = address,
					Due = Time.time + 5f
				});
			}
		}

		internal static bool TrySpawnSaleNote(NewAddress address, GameplayController gc)
		{
			if (gc.forSale.Contains(address))
			{
				L("Already for sale: " + ((Object)address).name);
				return true;
			}
			ResidenceController residence = address.residence;
			if ((Object)(object)residence == (Object)null)
			{
				L("No ResidenceController");
				return false;
			}
			ResidencePreset preset = residence.preset;
			if ((SoCustomComparison)(object)preset == (SoCustomComparison)null)
			{
				L("No preset on ResidenceController");
				return false;
			}
			ResidencePreset val = Object.Instantiate<ResidencePreset>(preset);
			val.enableForSale = true;
			residence.preset = val;
			CitizenBehaviour val2 = Object.FindObjectOfType<CitizenBehaviour>();
			if ((Object)(object)val2 == (Object)null)
			{
				residence.preset = preset;
				Object.Destroy((Object)(object)val);
				L("No CitizenBehaviour found");
				return false;
			}
			L($"inhabitants before UpdateForSale: {address.inhabitants?.Count ?? (-1)}");
			L($"owners before UpdateForSale: {address.owners?.Count ?? (-1)}");
			val2.UpdateForSale();
			residence.preset = preset;
			Object.Destroy((Object)(object)val);
			bool num = gc.forSale.Contains(address);
			L(num ? ("Listed: " + ((Object)address).name) : ("UpdateForSale did not list: " + ((Object)address).name));
			return num;
		}

		internal static string CityKey()
		{
			CityData instance = CityData.Instance;
			return ((instance != null) ? instance.seed : null) ?? "";
		}

		private static void AppendStore(string addressName)
		{
			try
			{
				List<string> list = (File.Exists(TenantEvictionPlugin.StorePath) ? new List<string>(File.ReadAllLines(TenantEvictionPlugin.StorePath)) : new List<string>());
				string item = CityKey() + "|" + addressName;
				if (!list.Contains(item))
				{
					list.Add(item);
					File.WriteAllLines(TenantEvictionPlugin.StorePath, list);
				}
				L("Stored: " + addressName);
			}
			catch (Exception ex)
			{
				L("Store write failed: " + ex.Message);
			}
		}

		internal static List<string> ReadStoreForCity()
		{
			List<string> list = new List<string>();
			string text = CityKey();
			string[] array = File.ReadAllLines(TenantEvictionPlugin.StorePath);
			List<string> list2 = new List<string>();
			bool flag = false;
			string[] array2 = array;
			foreach (string text2 in array2)
			{
				if (string.IsNullOrWhiteSpace(text2))
				{
					continue;
				}
				int num = text2.IndexOf('|');
				if (num < 0)
				{
					list.Add(text2);
					list2.Add(text + "|" + text2);
					flag = true;
					continue;
				}
				list2.Add(text2);
				if (text2.Substring(0, num) == text)
				{
					list.Add(text2.Substring(num + 1));
				}
			}
			if (flag)
			{
				File.WriteAllLines(TenantEvictionPlugin.StorePath, list2);
				L("Migrated old store entries to this city");
			}
			return list;
		}

		internal static bool PlayerOwns(Player? player, NewAddress address)
		{
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Human)player).home != (Object)null && (Object)(object)((Human)player).home == (Object)(object)address)
			{
				return true;
			}
			List<NewAddress> apartmentsOwned = player.apartmentsOwned;
			if (apartmentsOwned != null)
			{
				for (int i = 0; i < apartmentsOwned.Count; i++)
				{
					if ((Object)(object)apartmentsOwned[i] == (Object)(object)address)
					{
						return true;
					}
				}
			}
			if (address.owners != null)
			{
				for (int j = 0; j < address.owners.Count; j++)
				{
					if ((Object)(object)address.owners[j] == (Object)(object)player)
					{
						return true;
					}
				}
			}
			return false;
		}

		private NewAddress? FindAddressPlayerIsIn(NewFloor floor)
		{
			if (floor.addresses == null || (Object)(object)_player == (Object)null)
			{
				return null;
			}
			NewRoom currentRoom = ((Actor)_player).currentRoom;
			if ((Object)(object)currentRoom == (Object)null)
			{
				L("Player currentRoom is null");
				return null;
			}
			for (int i = 0; i < floor.addresses.Count; i++)
			{
				NewAddress val = floor.addresses[i];
				if ((Object)(object)val != (Object)null && (Object)(object)currentRoom.gameLocation == (Object)(object)val)
				{
					return val;
				}
			}
			return null;
		}

		internal static void L(string msg)
		{
			TenantEvictionPlugin.Log.LogInfo((object)("[TenantEviction] " + msg));
		}
	}
	[HarmonyPatch(typeof(MurderController), "OnStartGame")]
	internal static class PostLoadPatch
	{
		private static void Postfix()
		{
			if (!File.Exists(TenantEvictionPlugin.StorePath))
			{
				return;
			}
			GameplayController instance = GameplayController.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				EvictionController.L("GameplayController.Instance null at hook");
				return;
			}
			CityData instance2 = CityData.Instance;
			if (((instance2 != null) ? instance2.residenceDirectory : null) == null)
			{
				EvictionController.L("residenceDirectory null at hook");
				return;
			}
			if (instance2.residenceDirectory.Count == 0)
			{
				EvictionController.L("residenceDirectory empty at hook, skipping");
				return;
			}
			List<string> list;
			try
			{
				list = EvictionController.ReadStoreForCity();
			}
			catch (Exception ex)
			{
				EvictionController.L("Store read failed: " + ex.Message);
				return;
			}
			EvictionController.L($"Post-load: checking {list.Count} stored address(es) for this city");
			foreach (string item in list)
			{
				if (string.IsNullOrWhiteSpace(item))
				{
					continue;
				}
				ResidenceController val = null;
				for (int i = 0; i < instance2.residenceDirectory.Count; i++)
				{
					ResidenceController obj = instance2.residenceDirectory[i];
					object obj2;
					if (obj == null)
					{
						obj2 = null;
					}
					else
					{
						NewAddress address = obj.address;
						obj2 = ((address != null) ? ((Object)address).name : null);
					}
					if ((string?)obj2 == item)
					{
						val = instance2.residenceDirectory[i];
						break;
					}
				}
				if ((Object)(object)val == (Object)null)
				{
					EvictionController.L("Not found in residenceDirectory: " + item);
					continue;
				}
				NewAddress address2 = val.address;
				Player val2 = Object.FindObjectOfType<Player>();
				object obj3;
				if (!((Object)(object)val2 != (Object)null))
				{
					obj3 = null;
				}
				else
				{
					Toolbox instance3 = Toolbox.Instance;
					obj3 = ((instance3 != null) ? instance3.GetHotelRoom((Human)(object)val2) : null);
				}
				HotelGuest val3 = (HotelGuest)obj3;
				if (val3 != null && (Object)(object)val3.GetAddress() == (Object)(object)address2)
				{
					EvictionController.L("Post-load: skipping player's hotel room (" + item + ")");
					continue;
				}
				List<Human> inhabitants = address2.inhabitants;
				if (inhabitants != null && inhabitants.Count > 0)
				{
					EvictionController.L("Post-load: re-evicting " + item);
					EvictionController.EvictOccupants(address2, val2);
				}
				if (EvictionController.PlayerOwns(val2, address2))
				{
					EvictionController.L("Post-load: player owns " + item + ", not listing");
				}
				else
				{
					EvictionController.TrySpawnSaleNote(address2, instance);
				}
			}
		}
	}
}