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