Decompiled source of MeliciousFurnitureFixPlusLethalPerformanceCompat v0.1.1

plugins/MliciosFurnitureFixPlus/MliciosFurnitureFixPlus.dll

Decompiled 14 hours ago
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using BepInEx;
using BepInEx.Bootstrap;
using HarmonyLib;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: AssemblyFileVersion("0.1.1.0")]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyTitle("MliciosFurnitureFixPlus")]
[assembly: AssemblyDescription("Targeted Utility Furniture Pack ship fire exit compatibility fix")]
[assembly: AssemblyVersion("0.1.1.0")]
namespace MliciosFurnitureFixPlus;

internal static class Patches
{
	internal static void Install(Harmony harmony)
	{
		Type type = AccessTools.TypeByName("MelanieMeliciousFurniture.Plugin");
		Type type2 = ((type == null) ? null : type.Assembly.GetType("FireExitPatch"));
		Type type3 = AccessTools.TypeByName("LethalLevelLoader.DungeonLoader");
		MethodInfo methodInfo = Required(type2, "PreLevelLoad");
		MethodInfo methodInfo2 = Required(type2, "PreShipLeft");
		MethodInfo methodInfo3 = Required(type3, "GetEntranceTeleports");
		MethodInfo methodInfo4 = Required(type3, "PatchFireEscapes");
		MethodInfo methodInfo5 = Required(typeof(RoundManager), "SetExitIDs");
		MethodInfo methodInfo6 = Required(typeof(EntranceTeleport), "FindExitPoint");
		MethodInfo methodInfo7 = Required(typeof(StartOfRound), "ShipHasLeft");
		harmony.Patch((MethodBase)methodInfo, Hook("SkipOriginal"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		harmony.Patch((MethodBase)methodInfo2, Hook("SkipOriginal"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		harmony.Patch((MethodBase)methodInfo4, Hook("Prepare"), Hook("CaptureIds"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, Hook("Register"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		HarmonyMethod val = Hook("FreshExitScan");
		harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, Hook("Resolve"), val, (HarmonyMethod)null, (HarmonyMethod)null);
		harmony.Patch((MethodBase)methodInfo6, Hook("Find"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		harmony.Patch((MethodBase)Required(typeof(EntranceTeleport), "Awake"), (HarmonyMethod)null, Hook("Created"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		harmony.Patch((MethodBase)methodInfo7, Hook("Reset"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		Type type4 = AccessTools.TypeByName("Dawn.DungeonRegistrationHandler");
		if (type4 != null)
		{
			MethodInfo methodInfo8 = Required(type4, "AdjustFireExits");
			HarmonyMethod obj = Hook("PrepareDawn");
			HarmonyMethod val2 = Hook("FreshDawnScan");
			harmony.Patch((MethodBase)methodInfo8, obj, Hook("CaptureDawnIds"), val2, (HarmonyMethod)null, (HarmonyMethod)null);
		}
		harmony.Unpatch((MethodBase)Required(typeof(RoundManager), "LoadNewLevel"), methodInfo);
		harmony.Unpatch((MethodBase)methodInfo7, methodInfo2);
	}

	private static MethodInfo Required(Type type, string name)
	{
		MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, name, (Type[])null, (Type[])null));
		if (methodInfo == null)
		{
			throw new MissingMethodException((type == null) ? "missing dependency type" : type.FullName, name);
		}
		return methodInfo;
	}

	private static HarmonyMethod Hook(string name)
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Expected O, but got Unknown
		HarmonyMethod val = new HarmonyMethod(typeof(Patches), name, (Type[])null);
		val.priority = 800;
		return val;
	}

	private static bool SkipOriginal()
	{
		return false;
	}

	private static void Prepare()
	{
		Runtime.Prepare();
	}

	private static void PrepareDawn()
	{
		Runtime.Prepare();
	}

	private static void CaptureIds()
	{
		Runtime.CaptureIds();
	}

	private static void CaptureDawnIds()
	{
		if (Runtime.HasRegisteredShip)
		{
			Runtime.Log("DawnLib fire-exit generation handled on this peer.");
		}
		Runtime.CaptureIds();
	}

	private static void Register(List<EntranceTeleport> __result)
	{
		Runtime.Register(__result);
	}

	private static void Resolve()
	{
		Runtime.Resolve();
	}

	private static void Reset()
	{
		Runtime.Reset();
	}

	private static void Created(EntranceTeleport __instance)
	{
		Runtime.OnCreated(__instance);
	}

	private static bool Find(EntranceTeleport __instance, ref bool __result)
	{
		if (!Runtime.TryFind(__instance, out var found))
		{
			return true;
		}
		__result = found;
		return false;
	}

	private static EntranceTeleport[] ScanForExitIds()
	{
		if (!Runtime.HasRegisteredShip)
		{
			return Object.FindObjectsOfType<EntranceTeleport>();
		}
		return Runtime.FreshScan();
	}

	private static EntranceTeleport[] ScanForDawnIds(FindObjectsInactive inactive, FindObjectsSortMode sortMode)
	{
		return Runtime.RegisterDawn();
	}

	private static IEnumerable<CodeInstruction> FreshDawnScan(IEnumerable<CodeInstruction> instructions)
	{
		int replacements = 0;
		MethodInfo replacement = AccessTools.Method(typeof(Patches), "ScanForDawnIds", (Type[])null, (Type[])null);
		foreach (CodeInstruction instruction in instructions)
		{
			MethodInfo called = instruction.operand as MethodInfo;
			if (instruction.opcode == OpCodes.Call && called != null && called.DeclaringType == typeof(Object) && called.Name == "FindObjectsByType" && called.IsGenericMethod && called.GetGenericArguments()[0] == typeof(EntranceTeleport) && called.GetParameters().Length == 2)
			{
				instruction.operand = replacement;
				replacements++;
			}
			yield return instruction;
		}
		if (replacements != 1)
		{
			throw new NotSupportedException("Unexpected DawnLib AdjustFireExits layout: expected one entrance scan, found " + replacements + ".");
		}
	}

	private static IEnumerable<CodeInstruction> FreshExitScan(IEnumerable<CodeInstruction> instructions)
	{
		int replacements = 0;
		MethodInfo replacement = AccessTools.Method(typeof(Patches), "ScanForExitIds", (Type[])null, (Type[])null);
		foreach (CodeInstruction instruction in instructions)
		{
			MethodInfo called = instruction.operand as MethodInfo;
			if (instruction.opcode == OpCodes.Call && called != null && called.DeclaringType == typeof(Object) && called.Name == "FindObjectsOfType" && called.IsGenericMethod && called.GetGenericArguments()[0] == typeof(EntranceTeleport) && called.GetParameters().Length == 0)
			{
				instruction.operand = replacement;
				replacements++;
			}
			yield return instruction;
		}
		if (replacements != 1)
		{
			throw new NotSupportedException("Unexpected v81 SetExitIDs method: expected exactly one entrance scan, found " + replacements + ".");
		}
	}
}
[BepInDependency("imabatby.lethallevelloader", "1.7.13")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency("MelanieMelicious.furniturePack0", "2.0.5")]
[BepInPlugin("TranslationLookasideBufferModding.MliciosFurnitureFixPlus", "MliciosFurniture Fix+", "0.1.1")]
public sealed class Plugin : BaseUnityPlugin
{
	public const string Id = "TranslationLookasideBufferModding.MliciosFurnitureFixPlus";

	private Harmony harmony;

	private float nextCheck;

	private void Awake()
	{
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Expected O, but got Unknown
		if (!((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Repair the Utility Furniture Pack ship fire exit. Install on ALL players. Restart required.").Value)
		{
			return;
		}
		try
		{
			RequireVersion("MelanieMelicious.furniturePack0", "2.0.5");
			RequireVersion("imabatby.lethallevelloader", "1.7.13");
			Runtime.Log = delegate(string text)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)text);
			};
			Runtime.Warn = delegate(string text)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)text);
			};
			harmony = new Harmony("TranslationLookasideBufferModding.MliciosFurnitureFixPlus");
			Patches.Install(harmony);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"MliciosFurnitureFixPlus 0.1.1 active: LLL and DawnLib generation paths, no furniture reparenting, fresh exit-ID scan and explicit ship/interior pairing. LethalPerformance can stay enabled. Same addon required on every player.");
		}
		catch (Exception ex)
		{
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			((BaseUnityPlugin)this).Logger.LogError((object)("MliciosFurnitureFixPlus could not initialize; original behavior retained: " + ex));
			((Behaviour)this).enabled = false;
		}
	}

	private static void RequireVersion(string guid, string version)
	{
		if (!Chainloader.PluginInfos.TryGetValue(guid, out var value) || value.Metadata.Version.ToString() != version)
		{
			throw new NotSupportedException("Expected " + guid + " " + version + ".");
		}
	}

	private void Update()
	{
		if (!(TimeNow() < nextCheck))
		{
			nextCheck = TimeNow() + 0.5f;
			Runtime.UpdateInteraction();
		}
	}

	private static float TimeNow()
	{
		return Time.unscaledTime;
	}
}
internal sealed class DoorPair
{
	internal EntranceTeleport Ship;

	internal EntranceTeleport Interior;

	internal int Id;

	internal bool Active;

	internal bool Reported;
}
internal static class Runtime
{
	internal static Action<string> Log = delegate
	{
	};

	internal static Action<string> Warn = delegate
	{
	};

	internal static Func<bool> Landed = delegate
	{
		StartOfRound instance = StartOfRound.Instance;
		return (Object)(object)instance != (Object)null && instance.shipHasLanded && !instance.shipIsLeaving && !instance.inShipPhase;
	};

	internal static Func<EntranceTeleport, bool> Available = IsAvailable;

	internal static Func<int> Mode = delegate
	{
		Type type = AccessTools.TypeByName("MelanieUtilFurnitureConfig");
		FieldInfo fieldInfo = ((type == null) ? null : type.GetField("fireExit0Type", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
		object obj = ((fieldInfo == null) ? null : fieldInfo.GetValue(null));
		return (obj != null) ? Convert.ToInt32(obj.GetType().GetProperty("Value").GetValue(obj, null)) : 0;
	};

	private static readonly List<DoorPair> pairs = new List<DoorPair>();

	private static readonly HashSet<EntranceTeleport> managedInteriors = new HashSet<EntranceTeleport>();

	private static readonly FieldInfo gotExit = typeof(EntranceTeleport).GetField("gotExitPoint", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo firstCheck = typeof(EntranceTeleport).GetField("checkedForFirstTime", BindingFlags.Instance | BindingFlags.NonPublic);

	private static readonly FieldInfo noExit = typeof(EntranceTeleport).GetField("exitPointDoesntExist", BindingFlags.Instance | BindingFlags.NonPublic);

	private static bool registered;

	private static bool idsReady;

	internal static bool HasRegisteredShip => registered;

	internal static bool IsShip(EntranceTeleport door)
	{
		if ((Object)(object)door != (Object)null && door.isEntranceToBuilding)
		{
			return ((Object)((Component)door).gameObject).name == "EntranceTeleportShip0";
		}
		return false;
	}

	internal static void OnCreated(EntranceTeleport door)
	{
		if (IsShip(door))
		{
			SetInteraction(door, active: false);
		}
	}

	private static bool IsAvailable(EntranceTeleport door)
	{
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: 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_0036: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)door == (Object)null) && ((Component)door).gameObject.activeInHierarchy)
		{
			Scene scene = ((Component)door).gameObject.scene;
			if (((Scene)(ref scene)).IsValid())
			{
				Scene scene2 = ((Component)door).gameObject.scene;
				if (((Scene)(ref scene2)).isLoaded)
				{
					AutoParentToShip componentInParent = ((Component)door).GetComponentInParent<AutoParentToShip>();
					if ((Object)(object)componentInParent == (Object)null)
					{
						return false;
					}
					if (componentInParent.disableObject)
					{
						return false;
					}
					StartOfRound instance = StartOfRound.Instance;
					if ((Object)(object)instance != (Object)null && (Object)(object)instance.unlockablesList != (Object)null && componentInParent.unlockableID >= 0 && componentInParent.unlockableID < instance.unlockablesList.unlockables.Count)
					{
						return !instance.unlockablesList.unlockables[componentInParent.unlockableID].inStorage;
					}
					return true;
				}
			}
		}
		return false;
	}

	internal static EntranceTeleport[] FreshScan()
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		List<EntranceTeleport> list = new List<EntranceTeleport>();
		HashSet<int> scenes = new HashSet<int>();
		for (int i = 0; i < SceneManager.sceneCount; i++)
		{
			AddScene(SceneManager.GetSceneAt(i), list, scenes);
		}
		if ((Object)(object)StartOfRound.Instance != (Object)null)
		{
			AddScene(((Component)StartOfRound.Instance).gameObject.scene, list, scenes);
		}
		return list.OrderBy((EntranceTeleport d) => ((Object)d).GetInstanceID()).ToArray();
	}

	private static void AddScene(Scene scene, List<EntranceTeleport> result, HashSet<int> scenes)
	{
		if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded && scenes.Add(((Scene)(ref scene)).handle))
		{
			GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects();
			foreach (GameObject val in rootGameObjects)
			{
				result.AddRange(val.GetComponentsInChildren<EntranceTeleport>(false));
			}
		}
	}

	internal static void Prepare()
	{
		Reset();
		foreach (EntranceTeleport item in FreshScan().Where(IsShip).Where(Available).OrderBy<EntranceTeleport, string>(StableKey, StringComparer.Ordinal))
		{
			item.entranceId = 20;
			ClearExit(item);
			pairs.Add(new DoorPair
			{
				Ship = item,
				Id = -1
			});
			SetInteraction(item, active: false);
		}
	}

	internal static void Register(List<EntranceTeleport> entrances)
	{
		if (entrances == null)
		{
			return;
		}
		if (pairs.Count == 0)
		{
			foreach (EntranceTeleport item in FreshScan().Where(IsShip).Where(Available).OrderBy<EntranceTeleport, string>(StableKey, StringComparer.Ordinal))
			{
				pairs.Add(new DoorPair
				{
					Ship = item,
					Id = -1
				});
			}
		}
		List<EntranceTeleport> list = (from p in pairs
			where (Object)(object)p.Ship != (Object)null && Available(p.Ship)
			select p.Ship).ToList();
		entrances.RemoveAll((EntranceTeleport d) => (Object)(object)d == (Object)null || IsShip(d));
		entrances.InsertRange(0, list);
		registered = list.Count > 0;
	}

	internal static EntranceTeleport[] RegisterDawn()
	{
		List<EntranceTeleport> list = (from d in FreshScan()
			where d.isEntranceToBuilding && d.entranceId > 0 && !IsShip(d)
			select d).ToList();
		List<EntranceTeleport> list2 = (from p in pairs
			where (Object)(object)p.Ship != (Object)null && Available(p.Ship)
			select p.Ship).ToList();
		list.AddRange(list2);
		registered = list2.Count > 0;
		return list.ToArray();
	}

	internal static void CaptureIds()
	{
		foreach (DoorPair pair in pairs)
		{
			if ((Object)(object)pair.Ship != (Object)null)
			{
				pair.Id = pair.Ship.entranceId;
			}
		}
		if (registered)
		{
			Log("Registered " + pairs.Count + " ship fire exit(s) for this landing: IDs=" + string.Join(",", pairs.Select((DoorPair p) => p.Id.ToString()).ToArray()) + ". Furniture parent preserved.");
		}
	}

	internal static void Resolve()
	{
		if (!registered)
		{
			return;
		}
		idsReady = true;
		EntranceTeleport[] source = FreshScan();
		foreach (DoorPair pair in pairs)
		{
			if ((Object)(object)pair.Interior != (Object)null && (Object)(object)pair.Interior.exitScript == (Object)(object)pair.Ship)
			{
				ClearExit(pair.Interior);
			}
			pair.Interior = null;
			if ((Object)(object)pair.Ship == (Object)null)
			{
				continue;
			}
			EntranceTeleport[] array = source.Where((EntranceTeleport d) => !d.isEntranceToBuilding && d.entranceId == pair.Id).ToArray();
			bool flag = source.Count((EntranceTeleport d) => d.isEntranceToBuilding && d.entranceId == pair.Id && (!IsShip(d) || Available(d))) == 1;
			if (pair.Id <= 0 || !flag || array.Length != 1)
			{
				ClearExit(pair.Ship);
				if (!pair.Reported)
				{
					Warn("Cannot pair ship fire exit ID=" + pair.Id + ": interior candidates=" + array.Length + ", unique outside=" + flag + ". It remains blocked instead of using a moon's normal fire exit.");
				}
				pair.Reported = true;
				continue;
			}
			pair.Interior = array[0];
			managedInteriors.Add(pair.Interior);
			pair.Ship.exitScript = pair.Interior;
			pair.Interior.exitScript = pair.Ship;
			if (!pair.Reported)
			{
				Log("Paired ID=" + pair.Id + ": " + StableKey(pair.Ship) + " <-> " + StableKey(pair.Interior) + "; spawned=" + ((NetworkBehaviour)pair.Ship).IsSpawned + "/" + ((NetworkBehaviour)pair.Interior).IsSpawned + ".");
			}
			pair.Reported = true;
		}
		UpdateInteraction();
	}

	internal static bool TryFind(EntranceTeleport source, out bool found)
	{
		found = false;
		DoorPair doorPair = pairs.FirstOrDefault((DoorPair p) => (Object)(object)p.Ship == (Object)(object)source || ((Object)(object)p.Interior != (Object)null && (Object)(object)p.Interior == (Object)(object)source));
		if (doorPair == null)
		{
			if (IsShip(source) || managedInteriors.Contains(source))
			{
				ClearExit(source);
				return true;
			}
			return false;
		}
		EntranceTeleport val = (((Object)(object)source == (Object)(object)doorPair.Ship) ? doorPair.Interior : doorPair.Ship);
		int num = Mode();
		bool flag = (((Object)(object)source == (Object)(object)doorPair.Ship) ? (num != 2) : (num != 1));
		found = registered && idsReady && Landed() && flag && Available(doorPair.Ship) && (Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy && (Object)(object)val.entrancePoint != (Object)null && source.entranceId == doorPair.Id && val.entranceId == doorPair.Id;
		source.exitScript = (found ? val : null);
		return true;
	}

	internal static void Reset()
	{
		foreach (DoorPair pair in pairs)
		{
			if ((Object)(object)pair.Ship != (Object)null)
			{
				ClearExit(pair.Ship);
				SetInteraction(pair.Ship, active: false);
			}
			if ((Object)(object)pair.Interior != (Object)null && (Object)(object)pair.Interior.exitScript == (Object)(object)pair.Ship)
			{
				ClearExit(pair.Interior);
			}
		}
		pairs.Clear();
		managedInteriors.Clear();
		registered = false;
		idsReady = false;
	}

	internal static void UpdateInteraction()
	{
		foreach (DoorPair pair in pairs)
		{
			if (!((Object)(object)pair.Ship == (Object)null))
			{
				bool flag = registered && idsReady && Landed() && Available(pair.Ship) && (Object)(object)pair.Interior != (Object)null;
				if (flag != pair.Active)
				{
					SetInteraction(pair.Ship, flag);
					pair.Active = flag;
				}
			}
		}
	}

	private static void ClearExit(EntranceTeleport door)
	{
		door.exitScript = null;
		if (gotExit != null)
		{
			gotExit.SetValue(door, false);
		}
		if (firstCheck != null)
		{
			firstCheck.SetValue(door, false);
		}
		if (noExit != null)
		{
			noExit.SetValue(door, false);
		}
	}

	private static void SetInteraction(EntranceTeleport door, bool active)
	{
		((Behaviour)door).enabled = active;
		InteractTrigger component = ((Component)door).GetComponent<InteractTrigger>();
		if ((Object)(object)component != (Object)null)
		{
			((Behaviour)component).enabled = active;
		}
		BoxCollider component2 = ((Component)door).GetComponent<BoxCollider>();
		if ((Object)(object)component2 != (Object)null)
		{
			((Collider)component2).enabled = active;
		}
		if ((Object)(object)((Component)door).transform.parent != (Object)null)
		{
			Transform val = ((Component)door).transform.parent.Find("EntranceTeleportWarning");
			BoxCollider val2 = (((Object)(object)val == (Object)null) ? null : ((Component)val).GetComponent<BoxCollider>());
			if ((Object)(object)val2 != (Object)null)
			{
				((Collider)val2).enabled = !active;
			}
		}
	}

	private static string StableKey(EntranceTeleport door)
	{
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		string text = "";
		Transform val = ((Component)door).transform;
		while ((Object)(object)val != (Object)null)
		{
			text = "/" + ((Object)val).name + "[" + val.GetSiblingIndex() + "]" + text;
			val = val.parent;
		}
		NetworkObject componentInParent = ((Component)door).GetComponentInParent<NetworkObject>();
		if (!((Object)(object)componentInParent != (Object)null) || !componentInParent.IsSpawned)
		{
			Scene scene = ((Component)door).gameObject.scene;
			return ((Scene)(ref scene)).name + text;
		}
		return "net:" + componentInParent.NetworkObjectId.ToString("D20") + text;
	}
}
internal static class VersionInfo
{
	internal const string Version = "0.1.1";
}