Decompiled source of DangerGift v1.1.1

DangerGift.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AI;

[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("DangerGift")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.1+666578fe4e51c3460b8db761cc27436eb313b0fd")]
[assembly: AssemblyProduct("DangerGift")]
[assembly: AssemblyTitle("DangerGift")]
[assembly: AssemblyVersion("1.1.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 DangerGift
{
	internal static class ConfigSettings
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<float> SpawnChance;

		public static ConfigEntry<bool> PreventOpeningOnShip;

		public static ConfigEntry<bool> SpawnMonsters;

		public static ConfigEntry<bool> SpawnTraps;

		public static ConfigEntry<float> MonstersSpawnChance;

		public static ConfigEntry<float> TrapsSpawnChance;

		public static ConfigEntry<float> SpawnDelay;

		public static ConfigEntry<string> EnemyBlocklist;

		public static ConfigEntry<bool> IsAllowlist;

		public static ConfigEntry<string> EnemyAllowlist;

		public static ConfigEntry<bool> AllowOutsideSpawnInside;

		public static ConfigEntry<bool> AllowInsideSpawnOutside;

		public static ConfigEntry<bool> IgnoreInsideOutsideRule;

		public static ConfigEntry<bool> SpawnMines;

		public static ConfigEntry<bool> SpawnTurrets;

		public static ConfigEntry<bool> SpawnSpikeTraps;

		public static ConfigEntry<string> MoonsToIgnore;

		public static ConfigEntry<bool> BypassMonstersGordion;

		public static ConfigEntry<bool> EnableLogging;

		public static int Generation { get; private set; }

		public static HashSet<string> BlocklistSet { get; private set; }

		public static HashSet<string> AllowlistSet { get; private set; }

		public static HashSet<string> IgnoredMoonsSet { get; private set; }

		public static void Init(ConfigFile cfg)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Expected O, but got Unknown
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Expected O, but got Unknown
			Enabled = cfg.Bind<bool>("General", "Enabled", true, "Enable or disable the whole mod.");
			SpawnChance = cfg.Bind<float>("General", "SpawnChance", 0.15f, new ConfigDescription("Base chance (0.0 - 1.0) that opening a Present spawns a monster/trap instead of loot.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			PreventOpeningOnShip = cfg.Bind<bool>("General", "PreventOpeningOnShip", false, "If true, presents can ONLY be opened while landed on a moon and standing outside the ship (on the surface or in the facility). Opening is blocked aboard the ship and while in orbit / after take-off (not on a moon). The present is not consumed — it can still be opened later in a valid spot.");
			SpawnMonsters = cfg.Bind<bool>("Spawning", "SpawnMonsters", true, "Allow presents to spawn monsters.");
			SpawnTraps = cfg.Bind<bool>("Spawning", "SpawnTraps", true, "Allow presents to spawn traps.");
			MonstersSpawnChance = cfg.Bind<float>("Spawning", "MonstersSpawnChance", -1f, new ConfigDescription("Separate chance for monsters. Set to -1 to just use the general SpawnChance. If either this or TrapsSpawnChance is >= 0, monsters and traps are rolled independently.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>()));
			TrapsSpawnChance = cfg.Bind<float>("Spawning", "TrapsSpawnChance", -1f, new ConfigDescription("Separate chance for traps. Set to -1 to just use the general SpawnChance.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>()));
			SpawnDelay = cfg.Bind<float>("Spawning", "SpawnDelay", 0.7f, new ConfigDescription("Delay, in seconds, between opening the present and the entity actually appearing. Gives players a brief window to react.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 5f), Array.Empty<object>()));
			EnemyBlocklist = cfg.Bind<string>("Monsters", "EnemyBlocklist", "", "Comma-separated internal enemy names that must NEVER spawn (used when IsAllowlist = false). Example: \"Flowerman,SandSpider\". Enable logging to print the exact internal names.");
			IsAllowlist = cfg.Bind<bool>("Monsters", "IsAllowlist", true, "If true, ONLY monsters in EnemyAllowlist can spawn. If false, all monsters EXCEPT those in EnemyBlocklist can spawn.");
			EnemyAllowlist = cfg.Bind<string>("Monsters", "EnemyAllowlist", "Manticoil,RoamingLocust,RedLocust,HoardingBug,GunkFish,Slime,TulipSnake,Maneater", "Comma-separated internal enemy names allowed when IsAllowlist = true. Names are matched leniently (case/space insensitive) against the game's enemy names.");
			AllowOutsideSpawnInside = cfg.Bind<bool>("Monsters", "AllowOutsideSpawnInside", false, "If true, outdoor monsters may also spawn from presents opened inside the facility. Off by default because outdoor AI can misbehave on the indoor navmesh.");
			AllowInsideSpawnOutside = cfg.Bind<bool>("Monsters", "AllowInsideSpawnOutside", false, "If true, indoor monsters may also spawn from presents opened outside.");
			IgnoreInsideOutsideRule = cfg.Bind<bool>("Monsters", "IgnoreInsideOutsideRule", false, "If true, the inside/outside rule is ignored entirely: BOTH facility (indoor) and outdoor monsters can spawn no matter where the present is opened. Overrides AllowOutsideSpawnInside / AllowInsideSpawnOutside.");
			SpawnMines = cfg.Bind<bool>("Traps", "SpawnMines", true, "Allow landmines to be spawned from presents.");
			SpawnTurrets = cfg.Bind<bool>("Traps", "SpawnTurrets", false, "Allow turrets to be spawned from presents.");
			SpawnSpikeTraps = cfg.Bind<bool>("Traps", "SpawnSpikeTraps", false, "Allow spike roof traps to be spawned. EXPERIMENTAL: these are designed to hang from ceilings, so a floor-spawned one may look/behave oddly.");
			MoonsToIgnore = cfg.Bind<string>("Moons", "MoonsToIgnore", "", "Comma-separated moon names on which the mod is DISABLED. Matched against the planet name, so \"Titan\" matches \"8 Titan\". Leave empty to run on every moon.");
			BypassMonstersGordion = cfg.Bind<bool>("Compatibility", "BypassMonstersGordion", true, "If true (and the MonstersGordion mod is installed), monsters spawned from presents on the Company moon are registered with MonstersGordion so its 'foreign enemy' cleanup doesn't despawn them. As a bonus it also gains MonstersGordion's Company AI fixes, so the monster actually functions there. No effect without MonstersGordion.");
			EnableLogging = cfg.Bind<bool>("Debug", "EnableLogging", false, "Verbose logging to the BepInEx console (also prints every available enemy/trap name).");
			RebuildCaches();
			EnemyBlocklist.SettingChanged += delegate
			{
				RebuildCaches();
			};
			EnemyAllowlist.SettingChanged += delegate
			{
				RebuildCaches();
			};
			MoonsToIgnore.SettingChanged += delegate
			{
				RebuildCaches();
			};
			IsAllowlist.SettingChanged += delegate
			{
				Generation++;
			};
			AllowOutsideSpawnInside.SettingChanged += delegate
			{
				Generation++;
			};
			AllowInsideSpawnOutside.SettingChanged += delegate
			{
				Generation++;
			};
			IgnoreInsideOutsideRule.SettingChanged += delegate
			{
				Generation++;
			};
		}

		private static void RebuildCaches()
		{
			BlocklistSet = ParseList(EnemyBlocklist.Value);
			AllowlistSet = ParseList(EnemyAllowlist.Value);
			IgnoredMoonsSet = ParseList(MoonsToIgnore.Value);
			Generation++;
		}

		private static HashSet<string> ParseList(string raw)
		{
			HashSet<string> hashSet = new HashSet<string>();
			if (string.IsNullOrWhiteSpace(raw))
			{
				return hashSet;
			}
			string[] array = raw.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text = Normalize(array[i]);
				if (text.Length > 0)
				{
					hashSet.Add(text);
				}
			}
			return hashSet;
		}

		public static string Normalize(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return string.Empty;
			}
			StringBuilder stringBuilder = new StringBuilder(s.Length);
			foreach (char c in s)
			{
				if (char.IsLetterOrDigit(c))
				{
					stringBuilder.Append(char.ToLowerInvariant(c));
				}
			}
			return stringBuilder.ToString();
		}
	}
	[HarmonyPatch(typeof(GiftBoxItem))]
	internal static class GiftBoxPatches
	{
		private static readonly FieldInfo ExecStageField = typeof(NetworkBehaviour).GetField("__rpc_exec_stage", BindingFlags.Instance | BindingFlags.NonPublic);

		[HarmonyPatch("ItemActivate", new Type[]
		{
			typeof(bool),
			typeof(bool)
		})]
		[HarmonyPrefix]
		private static bool ItemActivatePrefix(GiftBoxItem __instance, bool buttonDown)
		{
			try
			{
				if (!ConfigSettings.Enabled.Value)
				{
					return true;
				}
				if (!ConfigSettings.PreventOpeningOnShip.Value)
				{
					return true;
				}
				if (CanOpenHere(__instance))
				{
					return true;
				}
				if (buttonDown)
				{
					ShowCannotOpenTip();
				}
				return false;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"ItemActivate prefix error: {arg}");
				return true;
			}
		}

		[HarmonyPatch("OpenGiftBoxServerRpc")]
		[HarmonyPrefix]
		private static bool OpenGiftBoxPrefix(GiftBoxItem __instance)
		{
			try
			{
				if (!ConfigSettings.Enabled.Value)
				{
					return true;
				}
				if (IsIgnoredMoon())
				{
					return true;
				}
				if (!IsServerExecutionStage((NetworkBehaviour)(object)__instance))
				{
					return true;
				}
				if (ConfigSettings.PreventOpeningOnShip.Value && !CanOpenHere(__instance))
				{
					return true;
				}
				return !PresentReplacementManager.TryReplace(__instance);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"OpenGiftBox prefix error: {arg}");
				return true;
			}
		}

		private static bool CanOpenHere(GiftBoxItem box)
		{
			if (ShipHasLanded())
			{
				return !IsHolderInShip(box);
			}
			return false;
		}

		private static bool IsHolderInShip(GiftBoxItem box)
		{
			if ((Object)(object)((GrabbableObject)box).playerHeldBy != (Object)null)
			{
				return ((GrabbableObject)box).playerHeldBy.isInHangarShipRoom;
			}
			return false;
		}

		private static bool ShipHasLanded()
		{
			if ((Object)(object)StartOfRound.Instance != (Object)null)
			{
				return StartOfRound.Instance.shipHasLanded;
			}
			return false;
		}

		private static void ShowCannotOpenTip()
		{
			try
			{
				if ((Object)(object)HUDManager.Instance != (Object)null)
				{
					HUDManager.Instance.DisplayTip("Present", "Presents can only be opened out on the moon.", false, false, "LC_Tip1");
				}
			}
			catch
			{
			}
		}

		private static bool IsServerExecutionStage(NetworkBehaviour nb)
		{
			if (ExecStageField == null)
			{
				return nb.IsServer;
			}
			try
			{
				object value = ExecStageField.GetValue(nb);
				string text = value.ToString();
				if (text == "Execute" || text == "Server")
				{
					return true;
				}
				return Convert.ToInt32(value) == 1;
			}
			catch
			{
				return nb.IsServer;
			}
		}

		internal static void SetRpcExecStageToSend(NetworkBehaviour nb)
		{
			if (ExecStageField == null)
			{
				return;
			}
			try
			{
				ExecStageField.SetValue(nb, Enum.ToObject(ExecStageField.FieldType, 0));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not reset RPC exec stage: " + ex.Message));
			}
		}

		private static bool IsIgnoredMoon()
		{
			HashSet<string> ignoredMoonsSet = ConfigSettings.IgnoredMoonsSet;
			if (ignoredMoonsSet == null || ignoredMoonsSet.Count == 0)
			{
				return false;
			}
			SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			string text = ConfigSettings.Normalize(val.PlanetName);
			foreach (string item in ignoredMoonsSet)
			{
				if (text.Contains(item))
				{
					return true;
				}
			}
			return false;
		}
	}
	[HarmonyPatch(typeof(StartOfRound))]
	internal static class RoundResetPatch
	{
		[HarmonyPatch("StartGame")]
		[HarmonyPostfix]
		private static void OnStartGame()
		{
			PresentReplacementManager.Reset();
			MonstersGordionCompat.Reset();
			SpawnHelper.Invalidate();
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || ((NetworkBehaviour)instance).IsServer)
			{
				SpawnHelper.Prewarm();
			}
		}
	}
	internal static class MonstersGordionCompat
	{
		private static bool _resolved;

		private static Type _spawnerType;

		private static FieldInfo _ownedField;

		private static Object _spawner;

		private static bool _spawnerSearched;

		internal static void Reset()
		{
			_spawner = null;
			_spawnerSearched = false;
		}

		private static void EnsureResolved()
		{
			if (_resolved)
			{
				return;
			}
			_resolved = true;
			try
			{
				_spawnerType = AccessTools.TypeByName("MonstersGordion.CompanyMonsterSpawner");
				if (_spawnerType != null)
				{
					_ownedField = AccessTools.Field(_spawnerType, "_ownedEnemies");
				}
			}
			catch
			{
			}
		}

		internal static void Adopt(NetworkObjectReference spawnedRef)
		{
			if (!ConfigSettings.BypassMonstersGordion.Value)
			{
				return;
			}
			EnsureResolved();
			if (_spawnerType == null || _ownedField == null)
			{
				return;
			}
			try
			{
				if (!_spawnerSearched)
				{
					_spawnerSearched = true;
					_spawner = Object.FindObjectOfType(_spawnerType);
				}
				Object spawner = _spawner;
				NetworkObject val = default(NetworkObject);
				if (spawner == (Object)null || !((NetworkObjectReference)(ref spawnedRef)).TryGet(ref val, (NetworkManager)null) || (Object)(object)val == (Object)null)
				{
					return;
				}
				EnemyAI componentInChildren = ((Component)val).GetComponentInChildren<EnemyAI>(true);
				if (!((Object)(object)componentInChildren == (Object)null) && _ownedField.GetValue(spawner) is IList list && !list.Contains(componentInChildren))
				{
					list.Add(componentInChildren);
					if (ConfigSettings.EnableLogging.Value)
					{
						Plugin.Log.LogInfo((object)("[Gordion] Adopted '" + componentInChildren.enemyType?.enemyName + "' into MonstersGordion so it isn't despawned."));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Gordion] MonstersGordion bypass failed: " + ex.Message));
			}
		}
	}
	[BepInPlugin("Timofey.DangerGift", "DangerGift", "1.1.1")]
	[BepInProcess("Lethal Company.exe")]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "Timofey.DangerGift";

		public const string NAME = "DangerGift";

		public const string VERSION = "1.1.1";

		private readonly Harmony _harmony = new Harmony("Timofey.DangerGift");

		public static Plugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ConfigSettings.Init(((BaseUnityPlugin)this).Config);
			_harmony.PatchAll(Assembly.GetExecutingAssembly());
			Log.LogInfo((object)"DangerGift v1.1.1 loaded. Some presents bite back.");
		}
	}
	internal static class PresentReplacementManager
	{
		private class SpawnJob
		{
			public bool IsMonster;

			public EnemyType Enemy;

			public SpawnHelper.TrapTarget Trap;

			public Vector3 Position;
		}

		private static readonly Dictionary<ulong, bool> _decisions = new Dictionary<ulong, bool>();

		private static readonly Random Rng = new Random();

		public static bool TryReplace(GiftBoxItem box)
		{
			ulong networkObjectId = ((NetworkBehaviour)box).NetworkObjectId;
			if (_decisions.TryGetValue(networkObjectId, out var value))
			{
				return value;
			}
			bool flag;
			try
			{
				flag = Decide(box);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Present replacement failed, falling back to normal loot: {arg}");
				flag = false;
			}
			_decisions[networkObjectId] = flag;
			return flag;
		}

		private static bool Decide(GiftBoxItem box)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			bool isInFactory = ((GrabbableObject)box).isInFactory;
			Vector3 position = ((Component)box).transform.position;
			RollCategories(out var monster, out var trap);
			if (!monster && !trap)
			{
				return false;
			}
			List<SpawnJob> list = new List<SpawnJob>(2);
			if (monster && ConfigSettings.SpawnMonsters.Value)
			{
				EnemyType val = SpawnHelper.ResolveMonster(isInFactory);
				if ((Object)(object)val != (Object)null && SpawnHelper.TryGetMonsterPosition(position, out var position2))
				{
					list.Add(new SpawnJob
					{
						IsMonster = true,
						Enemy = val,
						Position = position2
					});
				}
			}
			if (trap && ConfigSettings.SpawnTraps.Value)
			{
				SpawnHelper.TrapTarget trapTarget = SpawnHelper.ResolveTrap(position);
				if (trapTarget != null)
				{
					list.Add(new SpawnJob
					{
						IsMonster = false,
						Trap = trapTarget,
						Position = trapTarget.Position
					});
				}
			}
			if (list.Count == 0)
			{
				return false;
			}
			SpawnJob job = list[Rng.Next(list.Count)];
			OpenBoxWithoutLoot(box);
			if ((Object)(object)Plugin.Instance != (Object)null)
			{
				((MonoBehaviour)Plugin.Instance).StartCoroutine(SpawnAfterDelay(job));
			}
			else
			{
				ExecuteJob(job);
			}
			return true;
		}

		private static void OpenBoxWithoutLoot(GiftBoxItem box)
		{
			try
			{
				GiftBoxPatches.SetRpcExecStageToSend((NetworkBehaviour)(object)box);
				box.OpenGiftBoxNoPresentClientRpc();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to open present cleanly: {arg}");
			}
		}

		private static void RollCategories(out bool monster, out bool trap)
		{
			monster = false;
			trap = false;
			bool flag = ConfigSettings.MonstersSpawnChance.Value >= 0f;
			bool flag2 = ConfigSettings.TrapsSpawnChance.Value >= 0f;
			float value = ConfigSettings.SpawnChance.Value;
			if (!flag && !flag2)
			{
				if (Rng.NextDouble() >= (double)value)
				{
					return;
				}
				bool value2 = ConfigSettings.SpawnMonsters.Value;
				bool value3 = ConfigSettings.SpawnTraps.Value;
				if (value2 && value3)
				{
					if (Rng.NextDouble() < 0.5)
					{
						monster = true;
					}
					else
					{
						trap = true;
					}
				}
				else if (value2)
				{
					monster = true;
				}
				else if (value3)
				{
					trap = true;
				}
			}
			else
			{
				float num = ((!ConfigSettings.SpawnMonsters.Value) ? 0f : (flag ? ConfigSettings.MonstersSpawnChance.Value : value));
				float num2 = ((!ConfigSettings.SpawnTraps.Value) ? 0f : (flag2 ? ConfigSettings.TrapsSpawnChance.Value : value));
				monster = Rng.NextDouble() < (double)num;
				trap = Rng.NextDouble() < (double)num2;
			}
		}

		private static IEnumerator SpawnAfterDelay(SpawnJob job)
		{
			yield return (object)new WaitForSeconds(Mathf.Max(0f, ConfigSettings.SpawnDelay.Value));
			ExecuteJob(job);
		}

		private static void ExecuteJob(SpawnJob job)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (job.IsMonster)
				{
					SpawnHelper.SpawnMonster(job.Enemy, job.Position);
				}
				else
				{
					SpawnHelper.SpawnTrap(job.Trap);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to spawn present entity: {arg}");
			}
		}

		public static void Reset()
		{
			_decisions.Clear();
		}
	}
	internal static class SpawnHelper
	{
		private sealed class MonsterEntry
		{
			public EnemyType Type;

			public string[] Candidates;

			public bool InsideCapable;

			public bool OutsideCapable;
		}

		public class TrapTarget
		{
			public GameObject Prefab;

			public Vector3 Position;

			public Quaternion Rotation;
		}

		private static readonly Random Rng = new Random();

		private static GameObject _minePrefab;

		private static GameObject _turretPrefab;

		private static GameObject _spikeTrapPrefab;

		private static bool _trapsScanned;

		private static bool _dumpedAllEnemies;

		private static List<MonsterEntry> _catalog;

		private static EnemyType[] _insidePool;

		private static EnemyType[] _outsidePool;

		private static string[] _matchFragments;

		private static int _poolGeneration = -1;

		private static readonly GameObject[] _trapChoices = (GameObject[])(object)new GameObject[3];

		public static void Invalidate()
		{
			_catalog = null;
			_insidePool = null;
			_outsidePool = null;
			_matchFragments = null;
			_poolGeneration = -1;
			_minePrefab = null;
			_turretPrefab = null;
			_spikeTrapPrefab = null;
			_trapsScanned = false;
		}

		public static void Prewarm()
		{
			try
			{
				BuildCatalog();
				RebuildPoolsIfStale();
				ScanTrapPrefabs();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Prewarm failed (will build lazily): " + ex.Message));
			}
		}

		public static EnemyType ResolveMonster(bool insideFacility)
		{
			RebuildPoolsIfStale();
			EnemyType[] array = (insideFacility ? _insidePool : _outsidePool);
			if (array == null || array.Length == 0)
			{
				if (ConfigSettings.EnableLogging.Value)
				{
					Plugin.Log.LogInfo((object)$"[Enemies] inside={insideFacility} allowed=0 (nothing to spawn)");
				}
				return null;
			}
			EnemyType val = array[Rng.Next(array.Length)];
			if (ConfigSettings.EnableLogging.Value)
			{
				Plugin.Log.LogInfo((object)$"[Enemies] inside={insideFacility} allowed={array.Length} chosen='{val.enemyName}'");
			}
			return val;
		}

		private static void BuildCatalog()
		{
			if (_catalog != null)
			{
				return;
			}
			List<MonsterEntry> list = new List<MonsterEntry>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			bool value = ConfigSettings.EnableLogging.Value;
			List<string> list2 = (value ? new List<string>() : null);
			EnemyType[] array = Resources.FindObjectsOfTypeAll<EnemyType>();
			foreach (EnemyType val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.enemyPrefab == (Object)null) && !string.IsNullOrWhiteSpace(val.enemyName) && hashSet.Add(val.enemyName))
				{
					if (value)
					{
						list2.Add(val.enemyName);
					}
					list.Add(new MonsterEntry
					{
						Type = val,
						Candidates = new string[3]
						{
							ConfigSettings.Normalize(val.enemyName),
							ConfigSettings.Normalize(((Object)val.enemyPrefab).name),
							ConfigSettings.Normalize(((Object)val).name)
						},
						InsideCapable = (!val.isOutsideEnemy && !val.isDaytimeEnemy),
						OutsideCapable = (val.isOutsideEnemy || val.isDaytimeEnemy)
					});
				}
			}
			if (list.Count != 0)
			{
				_catalog = list;
				if (value && !_dumpedAllEnemies)
				{
					_dumpedAllEnemies = true;
					list2.Sort(StringComparer.OrdinalIgnoreCase);
					Plugin.Log.LogInfo((object)string.Format("[Enemies] all loaded ({0}): {1}", list2.Count, string.Join(", ", list2)));
				}
			}
		}

		private static void RebuildPoolsIfStale()
		{
			if (_catalog == null)
			{
				BuildCatalog();
			}
			if (_catalog == null)
			{
				return;
			}
			int generation = ConfigSettings.Generation;
			if (_insidePool != null && _outsidePool != null && _poolGeneration == generation)
			{
				return;
			}
			_matchFragments = BuildMatchFragments();
			List<EnemyType> list = new List<EnemyType>();
			List<EnemyType> list2 = new List<EnemyType>();
			bool value = ConfigSettings.IgnoreInsideOutsideRule.Value;
			bool value2 = ConfigSettings.AllowOutsideSpawnInside.Value;
			bool value3 = ConfigSettings.AllowInsideSpawnOutside.Value;
			foreach (MonsterEntry item in _catalog)
			{
				if (PassesListFilter(item))
				{
					if (value || item.InsideCapable || value2)
					{
						list.Add(item.Type);
					}
					if (value || item.OutsideCapable || value3)
					{
						list2.Add(item.Type);
					}
				}
			}
			_insidePool = list.ToArray();
			_outsidePool = list2.ToArray();
			_poolGeneration = generation;
			if (ConfigSettings.EnableLogging.Value)
			{
				Plugin.Log.LogInfo((object)($"[Enemies] pools rebuilt (gen {generation}): catalog={_catalog.Count}, " + $"inside={_insidePool.Length}, outside={_outsidePool.Length}"));
			}
		}

		private static string[] BuildMatchFragments()
		{
			HashSet<string> hashSet = (ConfigSettings.IsAllowlist.Value ? ConfigSettings.AllowlistSet : ConfigSettings.BlocklistSet);
			if (hashSet == null || hashSet.Count == 0)
			{
				return new string[0];
			}
			HashSet<string> hashSet2 = new HashSet<string>();
			foreach (string item in hashSet)
			{
				foreach (string item2 in ExpandAliases(item))
				{
					if (!string.IsNullOrEmpty(item2))
					{
						hashSet2.Add(item2);
					}
				}
			}
			string[] array = new string[hashSet2.Count];
			hashSet2.CopyTo(array);
			return array;
		}

		private static bool PassesListFilter(MonsterEntry entry)
		{
			bool flag = NameMatches(entry);
			if (!ConfigSettings.IsAllowlist.Value)
			{
				return !flag;
			}
			return flag;
		}

		private static bool NameMatches(MonsterEntry entry)
		{
			string[] matchFragments = _matchFragments;
			if (matchFragments == null || matchFragments.Length == 0)
			{
				return false;
			}
			foreach (string text in matchFragments)
			{
				for (int j = 0; j < entry.Candidates.Length; j++)
				{
					string text2 = entry.Candidates[j];
					if (text2.Length != 0 && (text2 == text || text2.Contains(text) || text.Contains(text2)))
					{
						return true;
					}
				}
			}
			return false;
		}

		private static IEnumerable<string> ExpandAliases(string normalized)
		{
			yield return normalized;
			if (normalized == null)
			{
				yield break;
			}
			switch (normalized.Length)
			{
			case 9:
				switch (normalized[0])
				{
				default:
					yield break;
				case 'm':
					if (normalized == "manticoil")
					{
						yield return "doublewing";
					}
					yield break;
				case 'r':
					break;
				}
				if (!(normalized == "redlocust"))
				{
					break;
				}
				goto IL_01cd;
			case 8:
				switch (normalized[0])
				{
				case 'g':
					if (normalized == "gunkfish")
					{
						yield return "gutfish";
						yield return "fish";
					}
					break;
				case 'm':
					if (normalized == "maneater")
					{
						yield return "cavedweller";
						yield return "caveddweller";
					}
					break;
				}
				break;
			case 13:
				if (!(normalized == "roaminglocust"))
				{
					break;
				}
				goto IL_01cd;
			case 11:
				if (normalized == "hoardingbug")
				{
					yield return "hoarderbug";
					yield return "hoarding";
				}
				break;
			case 12:
				if (normalized == "kidnapperfox")
				{
					yield return "bushwolf";
					yield return "fox";
				}
				break;
			case 5:
				if (normalized == "slime")
				{
					yield return "hygrodere";
					yield return "blob";
				}
				break;
			case 10:
				{
					if (normalized == "tulipsnake")
					{
						yield return "flowersnake";
						yield return "snake";
					}
					break;
				}
				IL_01cd:
				yield return "redlocustbees";
				yield return "docilelocust";
				yield return "locust";
				break;
			}
		}

		public static void SpawnMonster(EnemyType enemyType, Vector3 position)
		{
			//IL_0032: 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)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)RoundManager.Instance == (Object)null) && !((Object)(object)enemyType == (Object)null))
			{
				float num = (float)(Rng.NextDouble() * 360.0);
				NetworkObjectReference spawnedRef = RoundManager.Instance.SpawnEnemyGameObject(position, num, -1, enemyType);
				if (ConfigSettings.EnableLogging.Value)
				{
					Plugin.Log.LogInfo((object)$"[Spawn] Monster '{enemyType.enemyName}' at {position}");
				}
				MonstersGordionCompat.Adopt(spawnedRef);
			}
		}

		private static void ScanTrapPrefabs()
		{
			if (_trapsScanned)
			{
				return;
			}
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					return;
				}
				List<SpawnableMapObject[]> list = new List<SpawnableMapObject[]>();
				if ((Object)(object)instance.currentLevel != (Object)null && instance.currentLevel.spawnableMapObjects != null)
				{
					list.Add(instance.currentLevel.spawnableMapObjects);
				}
				if (instance.levels != null)
				{
					SelectableLevel[] levels = instance.levels;
					foreach (SelectableLevel val in levels)
					{
						if ((Object)(object)val != (Object)null && val.spawnableMapObjects != null)
						{
							list.Add(val.spawnableMapObjects);
						}
					}
				}
				if (list.Count == 0)
				{
					return;
				}
				foreach (SpawnableMapObject[] item in list)
				{
					for (int i = 0; i < item.Length; i++)
					{
						GameObject val2 = item[i]?.prefabToSpawn;
						if (!((Object)(object)val2 == (Object)null))
						{
							if ((Object)(object)_minePrefab == (Object)null && (Object)(object)val2.GetComponentInChildren<Landmine>() != (Object)null)
							{
								_minePrefab = val2;
							}
							if ((Object)(object)_turretPrefab == (Object)null && (Object)(object)val2.GetComponentInChildren<Turret>() != (Object)null)
							{
								_turretPrefab = val2;
							}
							if ((Object)(object)_spikeTrapPrefab == (Object)null && (Object)(object)val2.GetComponentInChildren<SpikeRoofTrap>() != (Object)null)
							{
								_spikeTrapPrefab = val2;
							}
						}
					}
					if ((Object)(object)_minePrefab != (Object)null && (Object)(object)_turretPrefab != (Object)null && (Object)(object)_spikeTrapPrefab != (Object)null)
					{
						break;
					}
				}
				_trapsScanned = true;
				if (ConfigSettings.EnableLogging.Value)
				{
					Plugin.Log.LogInfo((object)$"[Traps] mine={(Object)(object)_minePrefab != (Object)null} turret={(Object)(object)_turretPrefab != (Object)null} spike={(Object)(object)_spikeTrapPrefab != (Object)null}");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Trap prefab scan failed: " + ex.Message));
			}
		}

		public static TrapTarget ResolveTrap(Vector3 near)
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			ScanTrapPrefabs();
			GameObject[] trapChoices = _trapChoices;
			int num = 0;
			if (ConfigSettings.SpawnMines.Value && (Object)(object)_minePrefab != (Object)null)
			{
				trapChoices[num++] = _minePrefab;
			}
			if (ConfigSettings.SpawnTurrets.Value && (Object)(object)_turretPrefab != (Object)null)
			{
				trapChoices[num++] = _turretPrefab;
			}
			if (ConfigSettings.SpawnSpikeTraps.Value && (Object)(object)_spikeTrapPrefab != (Object)null)
			{
				trapChoices[num++] = _spikeTrapPrefab;
			}
			if (num == 0)
			{
				return null;
			}
			if (!TryGetGroundPosition(near, out var position, out var normal))
			{
				return null;
			}
			GameObject prefab = trapChoices[Rng.Next(num)];
			Quaternion rotation = Quaternion.FromToRotation(Vector3.up, normal) * Quaternion.Euler(0f, (float)(Rng.NextDouble() * 360.0), 0f);
			return new TrapTarget
			{
				Prefab = prefab,
				Position = position,
				Rotation = rotation
			};
		}

		public static void SpawnTrap(TrapTarget target)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			if (target == null || (Object)(object)target.Prefab == (Object)null)
			{
				return;
			}
			GameObject val = Object.Instantiate<GameObject>(target.Prefab, target.Position, target.Rotation);
			NetworkObject component = val.GetComponent<NetworkObject>();
			if ((Object)(object)component == (Object)null)
			{
				Plugin.Log.LogWarning((object)("Trap prefab '" + ((Object)target.Prefab).name + "' has no NetworkObject; aborting spawn."));
				Object.Destroy((Object)(object)val);
				return;
			}
			component.Spawn(true);
			if (ConfigSettings.EnableLogging.Value)
			{
				Plugin.Log.LogInfo((object)$"[Spawn] Trap '{((Object)target.Prefab).name}' at {target.Position}");
			}
		}

		public static bool TryGetGroundPosition(Vector3 origin, out Vector3 position, out Vector3 normal)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			position = origin;
			normal = Vector3.up;
			int num = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.collidersAndRoomMaskAndDefault : (-1));
			RaycastHit val = default(RaycastHit);
			if (Physics.Raycast(origin + Vector3.up * 0.5f, Vector3.down, ref val, 12f, num, (QueryTriggerInteraction)1))
			{
				position = ((RaycastHit)(ref val)).point;
				normal = ((RaycastHit)(ref val)).normal;
			}
			NavMeshHit val2 = default(NavMeshHit);
			if (NavMesh.SamplePosition(position, ref val2, 3f, -1))
			{
				position = ((NavMeshHit)(ref val2)).position + Vector3.up * 0.05f;
				return true;
			}
			return false;
		}

		public static bool TryGetMonsterPosition(Vector3 origin, out Vector3 position)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (TryGetGroundPosition(origin, out position, out var _))
			{
				return true;
			}
			position = origin;
			return true;
		}
	}
}