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);
}
}
}
}
}