Decompiled source of DangerGift v1.0.0

DangerGift.dll

Decompiled 3 hours 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.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("DangerGift")]
[assembly: AssemblyTitle("DangerGift")]
[assembly: AssemblyVersion("1.0.0.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> EnableLogging;

		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.");
			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();
			};
		}

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

		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();
		}
	}
	[BepInPlugin("Timofey.DangerGift", "DangerGift", "1.0.0")]
	[BepInProcess("Lethal Company.exe")]
	public class Plugin : BaseUnityPlugin
	{
		public const string GUID = "Timofey.DangerGift";

		public const string NAME = "DangerGift";

		public const string VERSION = "1.0.0";

		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.0.0 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
	{
		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;

		public static EnemyType ResolveMonster(bool insideFacility)
		{
			List<EnemyType> list = new List<EnemyType>();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			List<string> list2 = new List<string>();
			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))
				{
					list2.Add(val.enemyName);
					if (PassesListFilter(val) && PassesInsideOutside(val, insideFacility))
					{
						list.Add(val);
					}
				}
			}
			if (ConfigSettings.EnableLogging.Value && !_dumpedAllEnemies)
			{
				_dumpedAllEnemies = true;
				list2.Sort(StringComparer.OrdinalIgnoreCase);
				Plugin.Log.LogInfo((object)string.Format("[Enemies] all loaded ({0}): {1}", list2.Count, string.Join(", ", list2)));
			}
			if (list.Count == 0)
			{
				if (ConfigSettings.EnableLogging.Value)
				{
					Plugin.Log.LogInfo((object)$"[Enemies] inside={insideFacility} allowed=0 (nothing to spawn)");
				}
				return null;
			}
			EnemyType val2 = list[Rng.Next(list.Count)];
			if (ConfigSettings.EnableLogging.Value)
			{
				List<string> list3 = new List<string>(list.Count);
				foreach (EnemyType item in list)
				{
					list3.Add(item.enemyName);
				}
				Plugin.Log.LogInfo((object)($"[Enemies] inside={insideFacility} allowed={list.Count} " + "=> [" + string.Join(", ", list3) + "] chosen='" + val2.enemyName + "'"));
			}
			return val2;
		}

		private static bool PassesInsideOutside(EnemyType et, bool insideFacility)
		{
			if (ConfigSettings.IgnoreInsideOutsideRule.Value)
			{
				return true;
			}
			bool flag = et.isOutsideEnemy || et.isDaytimeEnemy;
			bool flag2 = !et.isOutsideEnemy && !et.isDaytimeEnemy;
			if (insideFacility)
			{
				if (!flag2)
				{
					return ConfigSettings.AllowOutsideSpawnInside.Value;
				}
				return true;
			}
			if (!flag)
			{
				return ConfigSettings.AllowInsideSpawnOutside.Value;
			}
			return true;
		}

		private static bool PassesListFilter(EnemyType et)
		{
			bool value = ConfigSettings.IsAllowlist.Value;
			HashSet<string> set = (value ? ConfigSettings.AllowlistSet : ConfigSettings.BlocklistSet);
			bool flag = NameMatchesSet(et, set);
			if (!value)
			{
				return !flag;
			}
			return flag;
		}

		private static bool NameMatchesSet(EnemyType et, HashSet<string> set)
		{
			if (set == null || set.Count == 0)
			{
				return false;
			}
			List<string> list = new List<string>(3);
			if (!string.IsNullOrEmpty(et.enemyName))
			{
				list.Add(ConfigSettings.Normalize(et.enemyName));
			}
			if ((Object)(object)et.enemyPrefab != (Object)null)
			{
				list.Add(ConfigSettings.Normalize(((Object)et.enemyPrefab).name));
			}
			list.Add(ConfigSettings.Normalize(((Object)et).name));
			foreach (string item in set)
			{
				foreach (string item2 in ExpandAliases(item))
				{
					if (item2.Length == 0)
					{
						continue;
					}
					foreach (string item3 in list)
					{
						if (item3.Length != 0 && (item3 == item2 || item3.Contains(item2) || item2.Contains(item3)))
						{
							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_0058: 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);
				RoundManager.Instance.SpawnEnemyGameObject(position, num, -1, enemyType);
				if (ConfigSettings.EnableLogging.Value)
				{
					Plugin.Log.LogInfo((object)$"[Spawn] Monster '{enemyType.enemyName}' at {position}");
				}
			}
		}

		private static void ScanTrapPrefabs()
		{
			if (_trapsScanned)
			{
				return;
			}
			_trapsScanned = true;
			try
			{
				List<SpawnableMapObject[]> list = new List<SpawnableMapObject[]>();
				SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
				if ((Object)(object)val != (Object)null && val.spawnableMapObjects != null)
				{
					list.Add(val.spawnableMapObjects);
				}
				SelectableLevel[] array = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.levels : null);
				if (array != null)
				{
					SelectableLevel[] array2 = array;
					foreach (SelectableLevel val2 in array2)
					{
						if ((Object)(object)val2 != (Object)null && val2.spawnableMapObjects != null)
						{
							list.Add(val2.spawnableMapObjects);
						}
					}
				}
				foreach (SpawnableMapObject[] item in list)
				{
					for (int i = 0; i < item.Length; i++)
					{
						GameObject val3 = item[i]?.prefabToSpawn;
						if (!((Object)(object)val3 == (Object)null))
						{
							if ((Object)(object)_minePrefab == (Object)null && (Object)(object)val3.GetComponentInChildren<Landmine>() != (Object)null)
							{
								_minePrefab = val3;
							}
							if ((Object)(object)_turretPrefab == (Object)null && (Object)(object)val3.GetComponentInChildren<Turret>() != (Object)null)
							{
								_turretPrefab = val3;
							}
							if ((Object)(object)_spikeTrapPrefab == (Object)null && (Object)(object)val3.GetComponentInChildren<SpikeRoofTrap>() != (Object)null)
							{
								_spikeTrapPrefab = val3;
							}
						}
					}
					if ((Object)(object)_minePrefab != (Object)null && (Object)(object)_turretPrefab != (Object)null && (Object)(object)_spikeTrapPrefab != (Object)null)
					{
						break;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Trap prefab scan failed: " + ex.Message));
			}
			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}");
			}
		}

		public static TrapTarget ResolveTrap(Vector3 near)
		{
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: 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_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			ScanTrapPrefabs();
			List<GameObject> list = new List<GameObject>();
			if (ConfigSettings.SpawnMines.Value && (Object)(object)_minePrefab != (Object)null)
			{
				list.Add(_minePrefab);
			}
			if (ConfigSettings.SpawnTurrets.Value && (Object)(object)_turretPrefab != (Object)null)
			{
				list.Add(_turretPrefab);
			}
			if (ConfigSettings.SpawnSpikeTraps.Value && (Object)(object)_spikeTrapPrefab != (Object)null)
			{
				list.Add(_spikeTrapPrefab);
			}
			if (list.Count == 0)
			{
				return null;
			}
			if (!TryGetGroundPosition(near, out var position, out var normal))
			{
				return null;
			}
			GameObject prefab = list[Rng.Next(list.Count)];
			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_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: 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)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			position = origin;
			normal = Vector3.up;
			RaycastHit val = default(RaycastHit);
			if (Physics.Raycast(origin + Vector3.up * 0.5f, Vector3.down, ref val, 12f, -1, (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;
		}
	}
}