Decompiled source of Enemies Anywhere v1.0.0

AnyRoomEnemies.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
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(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("")]
[assembly: AssemblyCompany("Omniscye")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("AnyRoomEnemies")]
[assembly: AssemblyTitle("AnyRoomEnemies")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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 Empress.InitialSpawnDistributor
{
	[BepInPlugin("dev.empress.initialspawndistributor", "Empress Initial Spawn Distributor", "1.1.3")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public enum Distribution
		{
			BalancedRooms,
			UniformPoints
		}

		public enum SeedMode
		{
			UnityRandom,
			DeterministicLevelSeed
		}

		internal static ConfigEntry<bool> IncludeStartRoom = null;

		internal static ConfigEntry<Distribution> DistributionMode = null;

		internal static ConfigEntry<SeedMode> RandomSeedMode = null;

		internal static ConfigEntry<bool> ForceSeparateGroupRooms = null;

		internal static Random? DeterministicRng;

		internal static Plugin Instance = null;

		internal Harmony? _harmony;

		private const string AsciiBanner = "\r\n/*  ██████╗ ███╗   ███╗███╗   ██╗██╗                           */\r\n/* ██╔═══██╗████╗ ████║████╗  ██║██║                           */\r\n/* ██║   ██║██╔████╔██║██╔██╗ ██║██║                           */\r\n/* ██║   ██║██║╚██╔╝██║██║╚██╗██║██║                           */\r\n/* ╚██████╔╝██║ ╚═╝ ██║██║ ╚████║██║                           */\r\n/*  ╚═════╝ ╚═╝     ╚═╝╚═╝  ╚═══╝╚═╝                           */\r\n/*                                                             */\r\n/* ███████╗███╗   ███╗██████╗ ██████╗ ███████╗███████╗███████╗ */\r\n/* ██╔════╝████╗ ████║██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝ */\r\n/* █████╗  ██╔████╔██║██████╔╝██████╔╝█████╗  ███████╗███████╗ */\r\n/* ██╔══╝  ██║╚██╔╝██║██╔═══╝ ██╔══██╗██╔══╝  ╚════██║╚════██║ */\r\n/* ███████╗██║ ╚═╝ ██║██║     ██║  ██║███████╗███████║███████║ */\r\n/* ╚══════╝╚═╝     ╚═╝╚═╝     ╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝ */\r\n";

		internal static bool GroupContextActive = false;

		internal static HashSet<object> GroupUsedRooms = new HashSet<object>();

		public static ManualLogSource Log { get; private set; } = null;


		private void Awake()
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"\r\n/*  ██████╗ ███╗   ███╗███╗   ██╗██╗                           */\r\n/* ██╔═══██╗████╗ ████║████╗  ██║██║                           */\r\n/* ██║   ██║██╔████╔██║██╔██╗ ██║██║                           */\r\n/* ██║   ██║██║╚██╔╝██║██║╚██╗██║██║                           */\r\n/* ╚██████╔╝██║ ╚═╝ ██║██║ ╚████║██║                           */\r\n/*  ╚═════╝ ╚═╝     ╚═╝╚═╝  ╚═══╝╚═╝                           */\r\n/*                                                             */\r\n/* ███████╗███╗   ███╗██████╗ ██████╗ ███████╗███████╗███████╗ */\r\n/* ██╔════╝████╗ ████║██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝ */\r\n/* █████╗  ██╔████╔██║██████╔╝██████╔╝█████╗  ███████╗███████╗ */\r\n/* ██╔══╝  ██║╚██╔╝██║██╔═══╝ ██╔══██╗██╔══╝  ╚════██║╚════██║ */\r\n/* ███████╗██║ ╚═╝ ██║██║     ██║  ██║███████╗███████║███████║ */\r\n/* ╚══════╝╚═╝     ╚═╝╚═╝     ╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝ */\r\n");
			IncludeStartRoom = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "IncludeStartRoom", false, "");
			DistributionMode = ((BaseUnityPlugin)this).Config.Bind<Distribution>("General", "DistributionMode", Distribution.BalancedRooms, "");
			RandomSeedMode = ((BaseUnityPlugin)this).Config.Bind<SeedMode>("General", "RandomSeedMode", SeedMode.UnityRandom, "");
			ForceSeparateGroupRooms = ((BaseUnityPlugin)this).Config.Bind<bool>("Groups", "ForceSeparateGroupRooms", true, "");
			TrySetDeterministicSeed();
			_harmony = new Harmony("dev.empress.initialspawndistributor");
			_harmony.PatchAll(typeof(FirstSpawnPatch));
			_harmony.PatchAll(typeof(EnemySpawnGroupScopePatch));
			Log.LogInfo((object)"[Empress] Initial Spawn Distributor 1.1.3 loaded. Host-only install is enough.");
		}

		private void OnDestroy()
		{
			try
			{
				Harmony? harmony = _harmony;
				if (harmony != null)
				{
					harmony.UnpatchSelf();
				}
			}
			catch
			{
			}
		}

		internal static void TrySetDeterministicSeed()
		{
			if (RandomSeedMode.Value != SeedMode.DeterministicLevelSeed)
			{
				return;
			}
			try
			{
				Type type = AccessTools.TypeByName("LevelGenerator");
				if (type != null)
				{
					object obj = AccessTools.Property(type, "Instance")?.GetValue(null);
					if (obj != null)
					{
						FieldInfo fieldInfo = AccessTools.Field(type, "Seed") ?? AccessTools.Field(type, "LevelSeed");
						if (fieldInfo != null)
						{
							int num = (int)fieldInfo.GetValue(obj);
							DeterministicRng = new Random(num ^ 0x6E6D5A31);
							return;
						}
					}
				}
			}
			catch
			{
			}
			DeterministicRng = new Random(1337);
		}

		internal static Random GetRng()
		{
			if (RandomSeedMode.Value == SeedMode.DeterministicLevelSeed && DeterministicRng != null)
			{
				return DeterministicRng;
			}
			int seed = Mathf.RoundToInt(Random.value * 2.1474836E+09f);
			return new Random(seed);
		}
	}
	[HarmonyPatch]
	internal static class EnemySpawnGroupScopePatch
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(AccessTools.TypeByName("LevelGenerator"), "EnemySpawn", new Type[2]
			{
				AccessTools.TypeByName("EnemySetup"),
				typeof(Vector3)
			}, (Type[])null);
		}

		private static void Prefix()
		{
			if (Plugin.ForceSeparateGroupRooms.Value)
			{
				Plugin.GroupContextActive = true;
				Plugin.GroupUsedRooms.Clear();
			}
		}

		private static void Finalizer(Exception __exception)
		{
			Plugin.GroupContextActive = false;
			Plugin.GroupUsedRooms.Clear();
		}
	}
	[HarmonyPatch]
	internal static class FirstSpawnPatch
	{
		private static MethodBase TargetMethod()
		{
			Type type = AccessTools.TypeByName("EnemyDirector");
			return AccessTools.Method(type, "FirstSpawnPointAdd", new Type[1] { AccessTools.TypeByName("EnemyParent") }, (Type[])null);
		}

		private static bool Prefix(object __instance, object _enemyParent)
		{
			try
			{
				Type type = AccessTools.TypeByName("SemiFunc");
				if (AccessTools.Method(type, "IsMasterClientOrSingleplayer", (Type[])null, (Type[])null)?.Invoke(null, null) as bool? == false)
				{
					return true;
				}
				Type type2 = AccessTools.TypeByName("LevelPoint");
				Type type3 = AccessTools.TypeByName("RoomVolume");
				Type type4 = _enemyParent.GetType();
				IEnumerable<object> source = (IEnumerable<object>)AccessTools.Method(type, "LevelPointsGetAll", (Type[])null, (Type[])null).Invoke(null, null);
				Type type5 = __instance.GetType();
				FieldInfo fieldInfo = AccessTools.Field(type5, "enemyFirstSpawnPoints");
				IList list = (IList)(fieldInfo?.GetValue(__instance) ?? CreateGenericList(type2));
				FieldInfo truckField = AccessTools.Field(type2, "Truck");
				FieldInfo inStartRoomField = AccessTools.Field(type2, "inStartRoom");
				FieldInfo roomField = AccessTools.Field(type2, "Room");
				IEnumerable<object> source2 = source.Where(delegate(object lp)
				{
					if (truckField != null && (bool)truckField.GetValue(lp))
					{
						return false;
					}
					return (Plugin.IncludeStartRoom.Value || !(inStartRoomField != null) || !(bool)inStartRoomField.GetValue(lp)) ? true : false;
				});
				HashSet<object> usedHash = new HashSet<object>(list.Cast<object>());
				List<object> list2 = source2.Where((object lp) => !usedHash.Contains(lp)).ToList();
				if (list2.Count == 0)
				{
					list.Clear();
					fieldInfo?.SetValue(__instance, list);
					usedHash.Clear();
					list2 = source2.ToList();
					if (list2.Count == 0)
					{
						return true;
					}
				}
				Dictionary<object, List<object>> byRoom = null;
				Dictionary<object, int> usedPerRoom = null;
				if (roomField != null && type3 != null)
				{
					byRoom = new Dictionary<object, List<object>>();
					foreach (object item in list2)
					{
						object value = roomField.GetValue(item);
						if (!byRoom.TryGetValue(value, out List<object> value2))
						{
							value2 = (byRoom[value] = new List<object>());
						}
						value2.Add(item);
					}
					usedPerRoom = new Dictionary<object, int>();
					foreach (object item2 in usedHash)
					{
						object value3 = roomField.GetValue(item2);
						if (!usedPerRoom.ContainsKey(value3))
						{
							usedPerRoom[value3] = 0;
						}
						usedPerRoom[value3]++;
					}
				}
				Random rng = Plugin.GetRng();
				object obj2;
				if (Plugin.DistributionMode.Value == Plugin.Distribution.BalancedRooms && byRoom != null && byRoom.Count > 0)
				{
					List<object> source3;
					if (Plugin.GroupContextActive && Plugin.ForceSeparateGroupRooms.Value)
					{
						List<object> list4 = byRoom.Keys.Where((object r) => !Plugin.GroupUsedRooms.Contains(r)).ToList();
						source3 = ((list4.Count > 0) ? list4 : byRoom.Keys.ToList());
					}
					else
					{
						source3 = byRoom.Keys.ToList();
					}
					int value6;
					int minUse = source3.Select((object r) => (usedPerRoom != null && usedPerRoom.TryGetValue(r, out value6)) ? value6 : 0).DefaultIfEmpty(0).Min();
					int value5;
					List<object> source4 = source3.Where((object r) => ((usedPerRoom != null && usedPerRoom.TryGetValue(r, out value5)) ? value5 : 0) == minUse).ToList();
					int maxAvail = source4.Select((object r) => byRoom[r].Count).Max();
					List<object> list5 = source4.Where((object r) => byRoom[r].Count == maxAvail).ToList();
					object obj = list5[rng.Next(list5.Count)];
					List<object> list6 = byRoom[obj];
					obj2 = list6[rng.Next(list6.Count)];
					if (Plugin.GroupContextActive && Plugin.ForceSeparateGroupRooms.Value)
					{
						Plugin.GroupUsedRooms.Add(obj);
					}
				}
				else
				{
					obj2 = list2[rng.Next(list2.Count)];
					if (Plugin.GroupContextActive && Plugin.ForceSeparateGroupRooms.Value && roomField != null)
					{
						object value4 = roomField.GetValue(obj2);
						if (Plugin.GroupUsedRooms.Contains(value4))
						{
							object obj3 = list2.FirstOrDefault((object lp) => !Plugin.GroupUsedRooms.Contains(roomField.GetValue(lp)));
							if (obj3 != null)
							{
								obj2 = obj3;
							}
							Plugin.GroupUsedRooms.Add(roomField.GetValue(obj2));
						}
						else
						{
							Plugin.GroupUsedRooms.Add(value4);
						}
					}
				}
				FieldInfo fieldInfo2 = AccessTools.Field(type4, "firstSpawnPoint");
				if (fieldInfo2 == null)
				{
					return true;
				}
				fieldInfo2.SetValue(_enemyParent, obj2);
				list.Add(obj2);
				fieldInfo?.SetValue(__instance, list);
				if (list.Count >= source2.Count())
				{
					list.Clear();
					fieldInfo?.SetValue(__instance, list);
				}
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"[Empress] FirstSpawnPointAdd patch failed, falling back to vanilla: {arg}");
				return true;
			}
		}

		private static object CreateGenericList(Type t)
		{
			Type type = typeof(List<>).MakeGenericType(t);
			return Activator.CreateInstance(type);
		}
	}
}