Decompiled source of MonstersGordion v1.9.0

MonstersGordion.dll

Decompiled 2 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 System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using MonstersGordion.Compat;
using MonstersGordion.Patches;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

[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("MonstersGordion")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.9.0.0")]
[assembly: AssemblyInformationalVersion("1.9.0+6ebcd4f96dbc0f4617f06cdb5691906ae869c699")]
[assembly: AssemblyProduct("MonstersGordion")]
[assembly: AssemblyTitle("MonstersGordion")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.9.0.0")]
[module: UnverifiableCode]
[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 MonstersGordion
{
	internal sealed class CompanyMonsterSpawner : MonoBehaviour
	{
		private readonly List<GameObject> _aiNodes = new List<GameObject>();

		private readonly List<GameObject> _fakeTrees = new List<GameObject>();

		private readonly List<EnemyAI> _ownedEnemies = new List<EnemyAI>();

		private readonly List<GameObject> _ownedNests = new List<GameObject>();

		private readonly Dictionary<int, float> _spawnTimes = new Dictionary<int, float>();

		private string[] _originalSurfaceTags;

		private readonly List<(EnemyAI ai, string name, float time)> _recentSpawns = new List<(EnemyAI, string, float)>();

		private readonly Dictionary<string, int> _earlyDeathCounts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		private readonly HashSet<string> _disabledThisLanding = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private const int EarlyDeathsBeforeDisable = 2;

		private readonly Dictionary<int, (Vector3 pos, float time)> _lastMovement = new Dictionary<int, (Vector3, float)>();

		private const float StuckSeconds = 25f;

		private const float StuckDistance = 1.5f;

		private NavMeshSampler _sampler;

		private const float AnnounceDelay = 3f;

		private Coroutine _loop;

		private Coroutine _maintenance;

		private Coroutine _events;

		private readonly List<GameObject> _ownedHazards = new List<GameObject>();

		private bool _shuttingDown;

		private bool _hasVainShrouds;

		private readonly HashSet<string> _warnedRequirements = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly HashSet<string> HarmlessCreatures = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Manticoil", "Docile Locust Bees" };

		private const float EarlyDeathSeconds = 4f;

		private int _earlyDeathTracesLogged;

		private static readonly HashSet<string> StationaryByDesign = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Flowerman", "Spring", "Clay Surgeon", "Jester", "Girl", "Cadaver Bloom", "Feiopar" };

		private const int TreeCanopyLayer = 25;

		private static Material _trunkMaterial;

		private static Mesh _realTreeMesh;

		private static Material _realTreeMaterial;

		private static Quaternion _realTreeRotation = Quaternion.identity;

		private static Vector3 _realTreeScale = Vector3.one;

		private static bool _realTreeSearched;

		internal static CompanyMonsterSpawner Instance { get; private set; }

		internal static bool IsCompanyLevel()
		{
			return IsCompanyLevel(((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
		}

		internal static bool IsCompanyLevel(SelectableLevel level)
		{
			if ((Object)(object)level == (Object)null)
			{
				return false;
			}
			if (!(level.sceneName == "CompanyBuilding") && level.levelID != 3)
			{
				if (level.PlanetName != null)
				{
					return level.PlanetName.Contains("Gordion");
				}
				return false;
			}
			return true;
		}

		internal static void OnShipLanded()
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			StartOfRound instance = StartOfRound.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				SelectableLevel currentLevel = instance.currentLevel;
				bool isServer = ((NetworkBehaviour)instance).IsServer;
				bool flag = IsCompanyLevel();
				Plugin.Log.LogInfo((object)("Ship landed: planet='" + currentLevel?.PlanetName + "', scene='" + currentLevel?.sceneName + "', " + $"host={isServer}, companyMoon={flag}."));
				if (!isServer)
				{
					Plugin.Log.LogInfo((object)"This game session is not the host — the spawner only runs on the host's machine. Make sure the lobby host has MonstersGordion installed.");
				}
				else if (!flag)
				{
					Plugin.DebugLog("Not the Company moon — spawner stays idle.");
				}
				else if (!((Object)(object)Instance != (Object)null))
				{
					ToilHeadCompat.Scan();
					StarlancerCompat.Scan();
					BcmeCompat.Scan();
					Instance = new GameObject("MonstersGordion_Spawner").AddComponent<CompanyMonsterSpawner>();
					Instance.Begin();
				}
			}
		}

		internal static void OnShipLeaving()
		{
			if ((Object)(object)Instance != (Object)null)
			{
				Instance.Shutdown();
			}
		}

		private void Begin()
		{
			PluginConfig cfg = Plugin.Cfg;
			EnemyCatalog.Resolve();
			if (cfg.RespawnOnLoad.Value)
			{
				DespawnAllEnemies();
			}
			_sampler = new NavMeshSampler(cfg.RequireIndoorPoints.Value);
			if (_sampler.Build() == 0)
			{
				Plugin.Log.LogError((object)"No usable navmesh found inside the Company building. Make sure NavMeshInCompanyRedux is installed and working (or set RequireIndoorPoints=false to loosen the filter). Spawning is disabled for this landing.");
				return;
			}
			_hasVainShrouds = CheckVainShrouds();
			if (!_hasVainShrouds && BushWolfIsSpawnable())
			{
				int iterations = ((cfg.VainShroudIterations.Value > 0) ? cfg.VainShroudIterations.Value : 12);
				_hasVainShrouds = GrowVainShrouds(iterations);
			}
			PlaceQueuedHazards();
			CreateAINodes(cfg.AINodeCount.Value);
			CreateDeadTreesIfNeeded();
			EnableWormFloorEmergeIfNeeded();
			_loop = ((MonoBehaviour)this).StartCoroutine(SpawnLoop());
			_maintenance = ((MonoBehaviour)this).StartCoroutine(MaintenanceLoop());
			_events = ((MonoBehaviour)this).StartCoroutine(EventLoop());
			Plugin.Log.LogInfo((object)($"Company spawner active: cap={cfg.GlobalCap.Value}, " + $"interval=[{cfg.MinSpawnInterval.Value:F0}s..{cfg.MaxSpawnInterval.Value:F0}s], " + $"outsideAIMode={cfg.TreatEnemiesAsOutside.Value}, foreignEnemies={cfg.ForeignEnemies.Value}, " + "ToilHead=" + (ToilHeadCompat.Present ? $"Coil {cfg.CoilHeadTurretChance.Value}% / Manti {cfg.ManticoilTurretChance.Value}% / Masked {cfg.MaskedTurretChance.Value}%" : "absent") + ", StarlancerAIFix=" + (StarlancerCompat.Present ? "present" : "absent") + ", BCME=" + (BcmeCompat.Present ? "present" : "absent") + "."));
		}

		private void Shutdown()
		{
			if (_shuttingDown)
			{
				return;
			}
			_shuttingDown = true;
			if (_loop != null)
			{
				((MonoBehaviour)this).StopCoroutine(_loop);
				_loop = null;
			}
			if (_maintenance != null)
			{
				((MonoBehaviour)this).StopCoroutine(_maintenance);
				_maintenance = null;
			}
			if (_events != null)
			{
				((MonoBehaviour)this).StopCoroutine(_events);
				_events = null;
			}
			GordionEvents.Reset();
			try
			{
				if (Plugin.Cfg.DespawnOnShipLeave.Value)
				{
					DespawnOwnedEnemies();
				}
				DespawnOwnedNests();
				RestoreWormFloorEmerge();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Cleanup despawn failed (scene tearing down?): " + ex.Message));
			}
			foreach (GameObject aiNode in _aiNodes)
			{
				if ((Object)(object)aiNode != (Object)null)
				{
					Object.Destroy((Object)(object)aiNode);
				}
			}
			_aiNodes.Clear();
			foreach (GameObject fakeTree in _fakeTrees)
			{
				if ((Object)(object)fakeTree != (Object)null)
				{
					Object.Destroy((Object)(object)fakeTree);
				}
			}
			_fakeTrees.Clear();
			DespawnOwnedHazards();
			_ownedEnemies.Clear();
			Instance = null;
			Object.Destroy((Object)(object)((Component)this).gameObject);
			Plugin.DebugLog("Company spawner shut down.");
		}

		private void OnDestroy()
		{
			if ((Object)(object)Instance == (Object)(object)this && !_shuttingDown)
			{
				_shuttingDown = true;
				((MonoBehaviour)this).StopAllCoroutines();
				Instance = null;
			}
		}

		private IEnumerator SpawnLoop()
		{
			PluginConfig cfg = Plugin.Cfg;
			while (true)
			{
				float num = Mathf.Max(1f, cfg.MinSpawnInterval.Value);
				float num2 = Mathf.Max(num, cfg.MaxSpawnInterval.Value);
				float num3 = Random.Range(num, num2);
				Plugin.DebugLog($"Next spawn attempt in {num3:F1}s.");
				yield return (object)new WaitForSeconds(num3);
				try
				{
					TrySpawnCycle();
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Spawn cycle failed: {arg}");
				}
			}
		}

		private void TrySpawnCycle()
		{
			//IL_030d: Unknown result type (might be due to invalid IL or missing references)
			PluginConfig cfg = Plugin.Cfg;
			if ((Object)(object)RoundManager.Instance == (Object)null || (Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.shipIsLeaving)
			{
				return;
			}
			Dictionary<string, int> perType;
			int num = CountAliveEnemies(out perType);
			if (num >= cfg.GlobalCap.Value)
			{
				Plugin.DebugLog($"Global cap reached ({num}/{cfg.GlobalCap.Value}) — skipping.");
				return;
			}
			ScanForEarlyDeaths();
			List<(EnemyType, EnemySpawnSettings, int)> list = new List<(EnemyType, EnemySpawnSettings, int)>();
			foreach (EnemyType enemy in EnemyCatalog.Enemies)
			{
				EnemySpawnSettings enemySpawnSettings = Plugin.Cfg.For(enemy);
				if (enemySpawnSettings.Enabled.Value && enemySpawnSettings.SpawnWeight.Value > 0 && enemySpawnSettings.MaxSpawnCount.Value > 0 && (cfg.AllowHarmlessCreatures.Value || !IsHarmlessCreature(enemy)) && !_disabledThisLanding.Contains(enemy.enemyName))
				{
					perType.TryGetValue(enemy.enemyName, out var value);
					if (value < enemySpawnSettings.MaxSpawnCount.Value && MeetsMapRequirements(enemy))
					{
						list.Add((enemy, enemySpawnSettings, value));
					}
				}
			}
			if (list.Count == 0)
			{
				Plugin.DebugLog("No eligible enemy types (all disabled or at their max).");
				return;
			}
			List<(EnemyType, EnemySpawnSettings, int)> list2 = list.Where<(EnemyType, EnemySpawnSettings, int)>(((EnemyType type, EnemySpawnSettings s, int alive) c) => c.alive < c.s.MinSpawnCount.Value).ToList();
			List<(EnemyType, EnemySpawnSettings, int)> pool;
			string arg;
			if (list2.Count > 0)
			{
				pool = list2;
				arg = "min-deficit pool";
			}
			else
			{
				bool wantOutside = Random.Range(0, 100) < cfg.OutsideEnemyShare.Value;
				List<(EnemyType, EnemySpawnSettings, int)> list3 = list.Where<(EnemyType, EnemySpawnSettings, int)>(((EnemyType type, EnemySpawnSettings s, int alive) c) => c.type.isOutsideEnemy == wantOutside).ToList();
				if (list3.Count == 0)
				{
					list3 = list;
					arg = (wantOutside ? "outdoor pool empty → all" : "indoor pool empty → all");
				}
				else
				{
					arg = (wantOutside ? "outdoor pool" : "indoor pool");
				}
				pool = list3;
			}
			(EnemyType, EnemySpawnSettings, int) tuple = WeightedPick(pool);
			Plugin.DebugLog($"Picked '{tuple.Item1.enemyName}' (weight {tuple.Item2.SpawnWeight.Value}, " + $"alive {tuple.Item3}/{tuple.Item2.MaxSpawnCount.Value}, {arg}, " + $"total alive {num}/{cfg.GlobalCap.Value}).");
			Vector3? randomPoint = _sampler.GetRandomPoint(cfg.MinDistanceFromPlayers.Value, 12, UpperShareFor(tuple.Item1));
			if (!randomPoint.HasValue)
			{
				Plugin.Log.LogWarning((object)("Could not find a valid interior navmesh point for '" + tuple.Item1.enemyName + "' — skipping this cycle."));
			}
			else
			{
				SpawnEnemy(tuple.Item1, randomPoint.Value);
			}
		}

		private bool EnsureNestFor(EnemyType type)
		{
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)type.nestSpawnPrefab == (Object)null || !type.requireNestObjectsToSpawn)
			{
				return true;
			}
			RoundManager instance = RoundManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return true;
			}
			instance.enemyNestSpawnObjects.RemoveAll((EnemyAINestSpawnObject n) => (Object)(object)n == (Object)null);
			if (instance.enemyNestSpawnObjects.Any((EnemyAINestSpawnObject n) => (Object)(object)n != (Object)null && (Object)(object)n.enemyType == (Object)(object)type))
			{
				return true;
			}
			Vector3? randomPoint = _sampler.GetRandomPoint(Plugin.Cfg.MinDistanceFromPlayers.Value, 12, Plugin.Cfg.UpperFloorSpawnShare.Value);
			if (!randomPoint.HasValue)
			{
				WarnRequirementOnce(type, "no interior navmesh point was free for its nest object");
				return false;
			}
			GameObject val = null;
			try
			{
				val = Object.Instantiate<GameObject>(type.nestSpawnPrefab, randomPoint.Value, Quaternion.Euler(0f, Random.Range(-180f, 180f), 0f));
				EnemyAINestSpawnObject component = val.GetComponent<EnemyAINestSpawnObject>();
				NetworkObject componentInChildren = val.GetComponentInChildren<NetworkObject>();
				if ((Object)(object)component == (Object)null || (Object)(object)componentInChildren == (Object)null)
				{
					WarnRequirementOnce(type, "its nest prefab '" + ((Object)type.nestSpawnPrefab).name + "' has no " + (((Object)(object)component == (Object)null) ? "EnemyAINestSpawnObject" : "NetworkObject") + " component");
					Object.Destroy((Object)(object)val);
					return false;
				}
				componentInChildren.Spawn(true);
				instance.enemyNestSpawnObjects.Add(component);
				_ownedNests.Add(val);
				Plugin.DebugLog($"Placed a nest for '{type.enemyName}' at {randomPoint.Value:F1}.");
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not place a nest for '" + type.enemyName + "': " + ex.Message));
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
				return false;
			}
		}

		private void WarnRequirementOnce(EnemyType type, string reason)
		{
			if (_warnedRequirements.Add(type.enemyName))
			{
				Plugin.Log.LogWarning((object)("Not spawning '" + type.enemyName + "' [" + EnemyCatalog.AIClassName(type) + "]: " + reason + ". Without it the game destroys the enemy inside EnemyAI.Start()."));
			}
		}

		private void DespawnOwnedNests()
		{
			foreach (GameObject item in _ownedNests.Where((GameObject n) => (Object)(object)n != (Object)null).ToList())
			{
				try
				{
					EnemyAINestSpawnObject component = item.GetComponent<EnemyAINestSpawnObject>();
					if ((Object)(object)component != (Object)null && (Object)(object)RoundManager.Instance != (Object)null)
					{
						RoundManager.Instance.enemyNestSpawnObjects.Remove(component);
					}
					NetworkObject componentInChildren = item.GetComponentInChildren<NetworkObject>();
					if ((Object)(object)componentInChildren != (Object)null && componentInChildren.IsSpawned)
					{
						componentInChildren.Despawn(true);
					}
					else
					{
						Object.Destroy((Object)(object)item);
					}
				}
				catch (Exception ex)
				{
					Plugin.DebugLog("Nest cleanup failed: " + ex.Message);
				}
			}
			_ownedNests.Clear();
		}

		private bool GrowVainShrouds(int iterations)
		{
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				MoldSpreadManager val = Object.FindObjectOfType<MoldSpreadManager>();
				if ((Object)(object)val == (Object)null)
				{
					Plugin.Log.LogWarning((object)"Cannot grow vain shrouds: no MoldSpreadManager on this moon.");
					return false;
				}
				try
				{
					typeof(MoldSpreadManager).GetField("finishedGeneratingMold", BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(val, false);
				}
				catch (Exception ex)
				{
					Plugin.DebugLog("Could not reset finishedGeneratingMold: " + ex.Message);
				}
				FieldInfo field = typeof(MoldSpreadManager).GetField("finishedGeneratingMold", BindingFlags.Instance | BindingFlags.NonPublic);
				SelectableLevel val2 = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
				int num = Mathf.Max(1, Plugin.Cfg.VainShroudPatches.Value);
				List<string> list = new List<string>();
				for (int i = 0; i < num; i++)
				{
					try
					{
						field?.SetValue(val, false);
					}
					catch (Exception ex2)
					{
						Plugin.DebugLog("Could not reset finishedGeneratingMold: " + ex2.Message);
					}
					if ((Object)(object)val2 != (Object)null)
					{
						val2.canSpawnMold = true;
						val2.moldSpreadIterations = iterations;
						val2.moldStartPosition = -1;
					}
					Vector3? val3 = PickVainShroudOrigin();
					if (!val3.HasValue)
					{
						Plugin.DebugLog("No free interior point left for another weed patch.");
						break;
					}
					val.GenerateMold(val3.Value, iterations);
					Vector3 value = val3.Value;
					list.Add(((Vector3)(ref value)).ToString("F1"));
				}
				bool weeds = val.GetWeeds();
				string text = ((list.Count > 0) ? string.Join(", ", list) : "nowhere");
				if (weeds)
				{
					Plugin.Log.LogInfo((object)($"Vain shrouds grown on Gordion: {list.Count} patch(es) x {iterations} iterations " + "at " + text + ". Bush Wolf can now hide and will survive."));
				}
				else
				{
					Plugin.Log.LogWarning((object)($"Tried to grow vain shrouds ({list.Count} patch(es) x {iterations} iterations at " + text + ") but the game still reports none — Bush Wolf will be skipped. Try a higher [Integration] VainShroudIterations."));
				}
				return weeds;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Growing vain shrouds failed: {arg}");
				return false;
			}
		}

		private Vector3? PickVainShroudOrigin()
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			if (_sampler != null)
			{
				Vector3? result = _sampler.GetRandomPoint(Plugin.Cfg.MinDistanceFromPlayers.Value, 12, 50) ?? _sampler.GetRandomPoint(0f, 12, 50);
				if (result.HasValue)
				{
					return result;
				}
			}
			if (!((Object)(object)StartOfRound.Instance != (Object)null))
			{
				return null;
			}
			return StartOfRound.Instance.shipLandingPosition.position;
		}

		private void EnableWormFloorEmergeIfNeeded()
		{
			//IL_00bc: 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_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: 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)
			if (!Plugin.Cfg.EarthLeviathanFloorEmerge.Value)
			{
				return;
			}
			EnemyType val = ((IEnumerable<EnemyType>)EnemyCatalog.Enemies).FirstOrDefault((Func<EnemyType, bool>)((EnemyType e) => string.Equals(e.enemyName, "Earth Leviathan", StringComparison.OrdinalIgnoreCase)));
			if ((Object)(object)val == (Object)null || !Plugin.Cfg.For(val).Enabled.Value)
			{
				return;
			}
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || instance.naturalSurfaceTags == null)
			{
				return;
			}
			try
			{
				HashSet<string> hashSet = new HashSet<string>(instance.naturalSurfaceTags, StringComparer.Ordinal);
				RaycastHit val2 = default(RaycastHit);
				if (Physics.Raycast((Vector3)(((??)_sampler?.Anchor) ?? instance.shipLandingPosition.position) + Vector3.up * 3f, Vector3.down, ref val2, 12f, instance.collidersAndRoomMaskAndDefault, (QueryTriggerInteraction)1))
				{
					string tag = ((Component)((RaycastHit)(ref val2)).collider).tag;
					if (!string.IsNullOrEmpty(tag))
					{
						hashSet.Add(tag);
					}
					Plugin.DebugLog("Worm floor probe hit '" + ((Object)((RaycastHit)(ref val2)).collider).name + "' tag='" + tag + "'.");
				}
				string[] array = new string[6] { "Untagged", "Catwalk", "Metal", "Concrete", "Wood", "Tiles" };
				foreach (string item in array)
				{
					hashSet.Add(item);
				}
				_originalSurfaceTags = instance.naturalSurfaceTags;
				instance.naturalSurfaceTags = hashSet.ToArray();
				Plugin.Log.LogInfo((object)($"Earth Leviathan: extended naturalSurfaceTags to {instance.naturalSurfaceTags.Length} entries " + "so the worm can breach up through the Company floor."));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Worm floor-emerge setup failed: " + ex.Message));
				_originalSurfaceTags = null;
			}
		}

		private void RestoreWormFloorEmerge()
		{
			if (_originalSurfaceTags == null)
			{
				return;
			}
			try
			{
				if ((Object)(object)StartOfRound.Instance != (Object)null)
				{
					StartOfRound.Instance.naturalSurfaceTags = _originalSurfaceTags;
				}
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("Restoring naturalSurfaceTags failed: " + ex.Message);
			}
			_originalSurfaceTags = null;
		}

		private static bool BushWolfIsSpawnable()
		{
			EnemyType val = ((IEnumerable<EnemyType>)EnemyCatalog.Enemies).FirstOrDefault((Func<EnemyType, bool>)((EnemyType e) => string.Equals(e.enemyName, "Bush Wolf", StringComparison.OrdinalIgnoreCase)));
			if ((Object)(object)val != (Object)null)
			{
				return Plugin.Cfg.For(val).Enabled.Value;
			}
			return false;
		}

		private static bool CheckVainShrouds()
		{
			try
			{
				MoldSpreadManager val = Object.FindObjectOfType<MoldSpreadManager>();
				if ((Object)(object)val == (Object)null)
				{
					Plugin.DebugLog("No MoldSpreadManager on this moon — no vain shrouds.");
					return false;
				}
				bool weeds = val.GetWeeds();
				Plugin.Log.LogInfo((object)$"Vain shrouds present: {weeds}.");
				return weeds;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Vain shroud check failed: " + ex.Message));
				return false;
			}
		}

		private bool MeetsMapRequirements(EnemyType type)
		{
			if (string.Equals(type.enemyName, "Bush Wolf", StringComparison.OrdinalIgnoreCase) && !_hasVainShrouds)
			{
				if (_warnedRequirements.Add(type.enemyName))
				{
					Plugin.Log.LogWarning((object)("Not spawning '" + type.enemyName + "' [" + EnemyCatalog.AIClassName(type) + "]: it needs vain shrouds to hide in and none grew on this moon. Weeds are grown at level load, so set [Integration] VainShroudIterations > 0 (or keep it at 0 with Bush Wolf enabled) and fly to Gordion again — enabling it mid-round is too late."));
				}
				return false;
			}
			if (string.Equals(type.enemyName, "Cadaver Bloom", StringComparison.OrdinalIgnoreCase) && !Plugin.Cfg.CadaverBloomTraps.Value)
			{
				if (_warnedRequirements.Add(type.enemyName))
				{
					Plugin.Log.LogWarning((object)"Not spawning 'Cadaver Bloom': it needs its Growth (a dungeon) to activate. Enable [Integration] CadaverBloomTraps to plant standalone burst traps instead.");
				}
				return false;
			}
			return true;
		}

		private void ScanForEarlyDeaths()
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			for (int num = _recentSpawns.Count - 1; num >= 0; num--)
			{
				var (val, text, num2) = _recentSpawns[num];
				if (!((Object)(object)val == (Object)null) && !val.isEnemyDead)
				{
					if (realtimeSinceStartup - num2 > 4f)
					{
						_recentSpawns.RemoveAt(num);
					}
				}
				else
				{
					_recentSpawns.RemoveAt(num);
					if (!(realtimeSinceStartup - num2 > 4f))
					{
						_earlyDeathCounts.TryGetValue(text, out var value);
						value++;
						_earlyDeathCounts[text] = value;
						if (value >= 2 && _disabledThisLanding.Add(text))
						{
							Plugin.Log.LogWarning((object)($"'{text}' died within {4f:F0}s of spawning {value} times — it " + "cannot survive on Gordion (it likely needs a dungeon, weeds or other map feature this moon lacks). Disabling it until the next landing."));
						}
					}
				}
			}
		}

		private static bool IsHarmlessCreature(EnemyType type)
		{
			return HarmlessCreatures.Contains(type.enemyName);
		}

		private static int UpperShareFor(EnemyType type)
		{
			if (Plugin.Cfg.OldBirdUpperFloorOnly.Value && string.Equals(type.enemyName, "RadMech", StringComparison.OrdinalIgnoreCase))
			{
				return 100;
			}
			return Plugin.Cfg.UpperFloorSpawnShare.Value;
		}

		private static (EnemyType type, EnemySpawnSettings s, int alive) WeightedPick(List<(EnemyType type, EnemySpawnSettings s, int alive)> pool)
		{
			int num = 0;
			foreach (var item in pool)
			{
				num += item.s.SpawnWeight.Value;
			}
			int num2 = Random.Range(0, num);
			foreach (var item2 in pool)
			{
				num2 -= item2.s.SpawnWeight.Value;
				if (num2 < 0)
				{
					return item2;
				}
			}
			return pool[pool.Count - 1];
		}

		private int CountAliveEnemies(out Dictionary<string, int> perType)
		{
			perType = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			int num = 0;
			_ownedEnemies.RemoveAll((EnemyAI e) => (Object)(object)e == (Object)null || e.isEnemyDead);
			foreach (EnemyAI item in (IEnumerable<EnemyAI>)(Plugin.Cfg.CountForeignEnemies.Value ? RoundManager.Instance.SpawnedEnemies : _ownedEnemies))
			{
				if (!((Object)(object)item == (Object)null) && !item.isEnemyDead && !((Object)(object)item.enemyType == (Object)null))
				{
					num++;
					string enemyName = item.enemyType.enemyName;
					perType.TryGetValue(enemyName, out var value);
					perType[enemyName] = value + 1;
				}
			}
			return num;
		}

		internal static void NotifyEnemyKilled(EnemyAI enemy)
		{
			if (!((Object)(object)Instance == (Object)null) && !((Object)(object)enemy == (Object)null) && !((Object)(object)enemy.enemyType == (Object)null))
			{
				Instance.OnEnemyKilled(enemy);
			}
		}

		private void OnEnemyKilled(EnemyAI enemy)
		{
			string enemyName = enemy.enemyType.enemyName;
			int instanceID = ((Object)enemy).GetInstanceID();
			if (_spawnTimes.TryGetValue(instanceID, out var value))
			{
				float num = Time.realtimeSinceStartup - value;
				_spawnTimes.Remove(instanceID);
				if (num <= 4f)
				{
					if (_earlyDeathTracesLogged < 5)
					{
						_earlyDeathTracesLogged++;
						Plugin.Log.LogWarning((object)($"'{enemyName}' [{EnemyCatalog.AIClassName(enemy.enemyType)}] died only {num:F2}s " + "after we spawned it — something is culling it. Caller stack trace:\n" + new StackTrace(fNeedFileInfo: false)));
					}
					else
					{
						Plugin.Log.LogWarning((object)$"'{enemyName}' died {num:F2}s after spawn (stack trace suppressed after 5).");
					}
					return;
				}
			}
			Plugin.DebugLog("Enemy died: " + enemyName + ".");
		}

		internal void SpawnEnemy(EnemyType type, Vector3 point)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			PluginConfig cfg = Plugin.Cfg;
			try
			{
				if (!EnsureNestFor(type))
				{
					return;
				}
				Vector3 val = point + Vector3.up * cfg.SpawnYOffset.Value;
				float num = Random.Range(0f, 360f);
				NetworkObjectReference val2 = RoundManager.Instance.SpawnEnemyGameObject(val, num, -1, type);
				NetworkObject val3 = default(NetworkObject);
				if (!((NetworkObjectReference)(ref val2)).TryGet(ref val3, (NetworkManager)null) || (Object)(object)val3 == (Object)null)
				{
					Plugin.Log.LogWarning((object)("Spawn of '" + type.enemyName + "' returned no NetworkObject."));
					return;
				}
				EnemyAI val4 = ((Component)val3).GetComponent<EnemyAI>();
				if ((Object)(object)val4 == (Object)null)
				{
					val4 = ((Component)val3).GetComponentInChildren<EnemyAI>();
				}
				if ((Object)(object)val4 == (Object)null)
				{
					Plugin.Log.LogWarning((object)("Spawned '" + type.enemyName + "' has no EnemyAI component."));
					return;
				}
				_ownedEnemies.Add(val4);
				_spawnTimes[((Object)val4).GetInstanceID()] = Time.realtimeSinceStartup;
				_recentSpawns.Add((val4, type.enemyName, Time.realtimeSinceStartup));
				Plugin.DebugLog($"Spawned '{type.enemyName}' at {val}.");
				((MonoBehaviour)this).StartCoroutine(PostSpawnSetup(val4));
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to spawn '{type.enemyName}': {arg}");
			}
		}

		private IEnumerator PostSpawnSetup(EnemyAI ai)
		{
			yield return null;
			yield return null;
			if ((Object)(object)ai == (Object)null || ai.isEnemyDead)
			{
				yield break;
			}
			ApplyInteriorAI(ai);
			string text = (((Object)(object)ai.enemyType != (Object)null) ? ai.enemyType.enemyName : string.Empty);
			ToilHeadCompat.Kind kind = ToilHeadCompat.KindOf(text);
			if (ToilHeadCompat.Present && kind != ToilHeadCompat.Kind.None)
			{
				int num = Plugin.Cfg.ToilHeadTurretChance(kind);
				int num2 = Random.Range(0, 100);
				if (num2 < num)
				{
					bool flag = Random.Range(0, 100) < Plugin.Cfg.ToilHeadSlayerChance(kind);
					bool flag2 = ToilHeadCompat.TryApply(ai, flag);
					Plugin.DebugLog($"ToilHead: '{text}' rolled {num2} < {num} (slayer={flag}) — " + (flag2 ? "turret attached" : "attach FAILED, see warnings above") + ".");
				}
				else
				{
					Plugin.DebugLog($"ToilHead: '{text}' rolled {num2} >= {num} — plain spawn.");
				}
			}
		}

		private void ApplyInteriorAI(EnemyAI ai)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				bool value = Plugin.Cfg.TreatEnemiesAsOutside.Value;
				try
				{
					ai.SetEnemyOutside(value);
				}
				catch (Exception ex)
				{
					Plugin.DebugLog("SetEnemyOutside threw: " + ex.Message);
				}
				ai.isOutside = value;
				AssignNodes(ai);
				NavMeshHit val = default(NavMeshHit);
				if ((Object)(object)ai.agent != (Object)null && ((Behaviour)ai.agent).isActiveAndEnabled && NavMesh.SamplePosition(((Component)ai).transform.position, ref val, 4f, -1))
				{
					ai.agent.Warp(((NavMeshHit)(ref val)).position);
				}
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Interior AI fix-up failed for '" + ai.enemyType?.enemyName + "': " + ex2.Message));
			}
		}

		private void AssignNodes(EnemyAI ai)
		{
			GameObject[] array = _aiNodes.Where((GameObject n) => (Object)(object)n != (Object)null).ToArray();
			if (array.Length != 0)
			{
				ai.allAINodes = array;
			}
		}

		private void PlaceQueuedHazards()
		{
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			List<(GameObject, float)> list = BcmeCompat.DrainQueuedOutdoorObjects();
			if (list.Count == 0)
			{
				return;
			}
			PluginConfig cfg = Plugin.Cfg;
			float num = ((_sampler != null) ? _sampler.WalkableArea : 0f);
			if (num <= 0f)
			{
				Plugin.Log.LogWarning((object)$"{list.Count} queued outdoor object(s) dropped: no walkable area was measured.");
				return;
			}
			foreach (var (val, num2) in list)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				int num3 = Mathf.Clamp(Mathf.RoundToInt(num2 * num * cfg.HazardDensityMultiplier.Value), 0, cfg.HazardMaxPerEvent.Value);
				Plugin.Log.LogInfo((object)($"Placing '{((Object)val).name}': density {num2:0.####} over {num:F0} m^2 " + $"x{cfg.HazardDensityMultiplier.Value:0.##} = {num3}."));
				int num4 = 0;
				for (int i = 0; i < num3; i++)
				{
					Vector3? randomPoint = _sampler.GetRandomPoint(cfg.MinDistanceFromPlayers.Value, 12, cfg.UpperFloorSpawnShare.Value);
					if (randomPoint.HasValue && SpawnHazard(val, randomPoint.Value))
					{
						num4++;
					}
				}
				if (num4 < num3)
				{
					Plugin.DebugLog($"'{((Object)val).name}': placed {num4} of {num3} (no room for the rest).");
				}
			}
		}

		private bool SpawnHazard(GameObject prefab, Vector3 point)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Quaternion val = Quaternion.Euler(0f, Random.Range(0f, 360f), 0f);
				GameObject val2 = Object.Instantiate<GameObject>(prefab, point, val);
				NetworkObject component = val2.GetComponent<NetworkObject>();
				if ((Object)(object)component != (Object)null)
				{
					component.Spawn(true);
				}
				_ownedHazards.Add(val2);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Failed to place '" + ((Object)prefab).name + "': " + ex.Message));
				return false;
			}
		}

		private void DespawnOwnedHazards()
		{
			foreach (GameObject ownedHazard in _ownedHazards)
			{
				if ((Object)(object)ownedHazard == (Object)null)
				{
					continue;
				}
				try
				{
					NetworkObject component = ownedHazard.GetComponent<NetworkObject>();
					if ((Object)(object)component != (Object)null && component.IsSpawned)
					{
						component.Despawn(true);
					}
					else
					{
						Object.Destroy((Object)(object)ownedHazard);
					}
				}
				catch (Exception ex)
				{
					Plugin.DebugLog("Hazard cleanup failed: " + ex.Message);
				}
			}
			_ownedHazards.Clear();
		}

		private IEnumerator EventLoop()
		{
			PluginConfig cfg = Plugin.Cfg;
			yield return (object)new WaitForSeconds(3f);
			if (!cfg.HordeEnabled.Value)
			{
				yield break;
			}
			float num = Mathf.Max(1f, (float)cfg.HordeDelaySeconds.Value - 3f);
			Plugin.DebugLog($"Horde due in {num:F0}s.");
			yield return (object)new WaitForSeconds(num);
			while (true)
			{
				yield return RunHorde();
				int value = cfg.HordeRepeatSeconds.Value;
				if (value <= 0)
				{
					break;
				}
				yield return (object)new WaitForSeconds((float)value);
			}
		}

		private IEnumerator RunHorde()
		{
			string wanted = Plugin.Cfg.HordeEnemy.Value;
			EnemyType enemy = ((IEnumerable<EnemyType>)EnemyCatalog.Enemies).FirstOrDefault((Func<EnemyType, bool>)((EnemyType e) => string.Equals(e.enemyName, wanted, StringComparison.OrdinalIgnoreCase)));
			if ((Object)(object)enemy == (Object)null)
			{
				Plugin.Log.LogWarning((object)("Horde: '" + wanted + "' is not in the spawn pool — check the [Horde] Enemy name and that it is not barred by ExcludedEnemies. Skipping."));
				yield break;
			}
			if (_sampler == null)
			{
				Plugin.Log.LogWarning((object)"Horde: no navmesh sample available — skipping.");
				yield break;
			}
			int perSide = Mathf.Max(1, Plugin.Cfg.HordeCountPerSide.Value);
			float value = Plugin.Cfg.MinDistanceFromPlayers.Value;
			if (!_sampler.TryGetOppositeEdgePoints(perSide, value, out var sideA, out var sideB) && !_sampler.TryGetOppositeEdgePoints(perSide, 0f, out sideA, out sideB))
			{
				Plugin.Log.LogWarning((object)"Horde: could not find usable points at both edges of the map — skipping. On a heavily reshaped Company moon the walkable area can be too fragmented for this.");
				yield break;
			}
			GordionEvents.AnnounceHorde(enemy.enemyName);
			Plugin.Log.LogInfo((object)$"Horde: spawning {sideA.Count} + {sideB.Count} '{enemy.enemyName}' at opposite map edges.");
			int most = Mathf.Max(sideA.Count, sideB.Count);
			for (int i = 0; i < most; i++)
			{
				if (i < sideA.Count)
				{
					SpawnEnemy(enemy, sideA[i]);
				}
				yield return null;
				if (i < sideB.Count)
				{
					SpawnEnemy(enemy, sideB[i]);
				}
				yield return null;
			}
		}

		private IEnumerator MaintenanceLoop()
		{
			while (true)
			{
				yield return (object)new WaitForSeconds(Mathf.Max(1f, Plugin.Cfg.MaintenanceInterval.Value));
				try
				{
					MaintenanceTick();
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"Maintenance tick failed: {arg}");
				}
			}
		}

		private void MaintenanceTick()
		{
			if ((Object)(object)StartOfRound.Instance == (Object)null || StartOfRound.Instance.shipIsLeaving)
			{
				return;
			}
			ScanForEarlyDeaths();
			_ownedEnemies.RemoveAll((EnemyAI e) => (Object)(object)e == (Object)null || e.isEnemyDead);
			bool value = Plugin.Cfg.TreatEnemiesAsOutside.Value;
			GameObject[] array = _aiNodes.Where((GameObject n) => (Object)(object)n != (Object)null).ToArray();
			foreach (EnemyAI ownedEnemy in _ownedEnemies)
			{
				if (!((Object)(object)ownedEnemy == (Object)null) && !ownedEnemy.isEnemyDead && !ownedEnemy.inSpecialAnimation)
				{
					if (ownedEnemy.isOutside != value)
					{
						ownedEnemy.isOutside = value;
						Plugin.DebugLog($"Re-applied isOutside={value} to '{ownedEnemy.enemyType?.enemyName}'.");
					}
					if (array.Length != 0 && (ownedEnemy.allAINodes == null || ownedEnemy.allAINodes.Length != array.Length || (ownedEnemy.allAINodes.Length != 0 && (Object)(object)ownedEnemy.allAINodes[0] != (Object)(object)array[0])))
					{
						ownedEnemy.allAINodes = array;
						Plugin.DebugLog("Restored patrol nodes for '" + ownedEnemy.enemyType?.enemyName + "'.");
					}
					if (!RescueIfStranded(ownedEnemy))
					{
						UnstickIfIdle(ownedEnemy);
					}
				}
			}
			_lastMovement.Keys.Where((int id) => _ownedEnemies.All((EnemyAI e) => (Object)(object)e == (Object)null || ((Object)e).GetInstanceID() != id)).ToList().ForEach(delegate(int id)
			{
				_lastMovement.Remove(id);
			});
			DriveCadaverBlooms();
			EnforceForeignEnemyPolicy();
		}

		private void DriveCadaverBlooms()
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: 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_0110: 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_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			float value = Plugin.Cfg.CadaverBloomTriggerRange.Value;
			float num = value * value;
			foreach (EnemyAI ownedEnemy in _ownedEnemies)
			{
				CadaverBloomAI val = (CadaverBloomAI)(object)((ownedEnemy is CadaverBloomAI) ? ownedEnemy : null);
				if (val == null || ((EnemyAI)val).isEnemyDead || val.hasBurst)
				{
					continue;
				}
				PlayerControllerB val2 = null;
				float num2 = num;
				Vector3 position = ((Component)val).transform.position;
				PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts;
				foreach (PlayerControllerB val3 in allPlayerScripts)
				{
					if (!((Object)(object)val3 == (Object)null) && val3.isPlayerControlled && !val3.isPlayerDead)
					{
						Vector3 val4 = ((Component)val3).transform.position - position;
						float sqrMagnitude = ((Vector3)(ref val4)).sqrMagnitude;
						if (sqrMagnitude < num2)
						{
							num2 = sqrMagnitude;
							val2 = val3;
						}
					}
				}
				if (!((Object)(object)val2 == (Object)null))
				{
					try
					{
						val.BurstForth(val2, false, position, ((Component)val).transform.eulerAngles);
						Plugin.Log.LogInfo((object)$"Cadaver Bloom burst on a player within {Mathf.Sqrt(num2):F1} m at {position:F1}.");
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("Cadaver Bloom BurstForth failed: " + ex.Message));
					}
				}
			}
		}

		private void UnstickIfIdle(EnemyAI ai)
		{
			//IL_0133: 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)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: 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_0104: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)ai.agent == (Object)null || !((Behaviour)ai.agent).isActiveAndEnabled)
			{
				return;
			}
			string text = (((Object)(object)ai.enemyType != (Object)null) ? ai.enemyType.enemyName : string.Empty);
			if (StationaryByDesign.Contains(text))
			{
				return;
			}
			int instanceID = ((Object)ai).GetInstanceID();
			Vector3 position = ((Component)ai).transform.position;
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (!_lastMovement.TryGetValue(instanceID, out (Vector3, float) value))
			{
				_lastMovement[instanceID] = (position, realtimeSinceStartup);
				return;
			}
			Vector3 val = position - value.Item1;
			if (((Vector3)(ref val)).sqrMagnitude > 2.25f)
			{
				_lastMovement[instanceID] = (position, realtimeSinceStartup);
			}
			else
			{
				if (realtimeSinceStartup - value.Item2 < 25f)
				{
					return;
				}
				Vector3? randomPoint = _sampler.GetRandomPoint(Plugin.Cfg.MinDistanceFromPlayers.Value, 8, Plugin.Cfg.UpperFloorSpawnShare.Value);
				if (randomPoint.HasValue)
				{
					try
					{
						ai.SetDestinationToPosition(randomPoint.Value, false);
					}
					catch (Exception ex)
					{
						Plugin.DebugLog("Unstick SetDestination failed: " + ex.Message);
					}
					_lastMovement[instanceID] = (position, realtimeSinceStartup);
					Plugin.DebugLog($"Nudged idle '{text}' toward a new point (was stuck ~{25f:F0}s).");
				}
			}
		}

		private bool RescueIfStranded(EnemyAI ai)
		{
			//IL_0031: 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_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			if (_sampler == null || (Object)(object)ai.agent == (Object)null || !((Behaviour)ai.agent).isActiveAndEnabled)
			{
				return false;
			}
			if (_sampler.IsReachable(((Component)ai).transform.position))
			{
				return false;
			}
			Vector3? randomPoint = _sampler.GetRandomPoint(Plugin.Cfg.MinDistanceFromPlayers.Value, 8, Plugin.Cfg.UpperFloorSpawnShare.Value);
			if (!randomPoint.HasValue)
			{
				return false;
			}
			ai.agent.Warp(randomPoint.Value + Vector3.up * Plugin.Cfg.SpawnYOffset.Value);
			_lastMovement[((Object)ai).GetInstanceID()] = (randomPoint.Value, Time.realtimeSinceStartup);
			Plugin.Log.LogInfo((object)("Rescued stranded '" + ai.enemyType?.enemyName + "' — teleported back onto the interior navmesh."));
			return true;
		}

		private void EnforceForeignEnemyPolicy()
		{
			ForeignEnemyPolicy value = Plugin.Cfg.ForeignEnemies.Value;
			if (value == ForeignEnemyPolicy.Ignore)
			{
				return;
			}
			RoundManager instance = RoundManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			foreach (EnemyAI item in instance.SpawnedEnemies.Where((EnemyAI e) => (Object)(object)e != (Object)null).ToList())
			{
				if (!item.isEnemyDead && !((Object)(object)item.enemyType == (Object)null) && !_ownedEnemies.Contains(item) && !item.inSpecialAnimation && !((Object)(object)item.inSpecialAnimationWithPlayer != (Object)null))
				{
					string enemyName = item.enemyType.enemyName;
					bool flag = EnemyCatalog.IsExcluded(enemyName);
					if (!flag && value == ForeignEnemyPolicy.RemoveNotEnabled)
					{
						flag = !Plugin.Cfg.For(item.enemyType).Enabled.Value;
					}
					if (flag && TryDespawn(instance, item))
					{
						Plugin.Log.LogInfo((object)$"Removed '{enemyName}' spawned by another mod (ForeignEnemies={value}).");
					}
				}
			}
		}

		private void CreateAINodes(int count)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < count; i++)
			{
				Vector3? randomPoint = _sampler.GetRandomPoint(0f, 15, 50);
				if (randomPoint.HasValue)
				{
					_aiNodes.Add(CreateNode($"MG_AINode_{i}", randomPoint.Value, "AINode"));
					_aiNodes.Add(CreateNode($"MG_OutsideAINode_{i}", randomPoint.Value, "OutsideAINode"));
				}
			}
			Plugin.Log.LogInfo((object)$"Created {_aiNodes.Count / 2} interior AI patrol nodes (indoor + outdoor tagged).");
		}

		private GameObject CreateNode(string name, Vector3 position, string tagName)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(((Component)this).transform, false);
			val.transform.position = position;
			try
			{
				val.tag = tagName;
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("Could not tag AI node '" + tagName + "': " + ex.Message);
			}
			return val;
		}

		private void CreateDeadTreesIfNeeded()
		{
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0186: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Expected O, but got Unknown
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Expected O, but got Unknown
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			if (!Plugin.Cfg.FeioparDeadTrees.Value)
			{
				return;
			}
			EnemyType val = ((IEnumerable<EnemyType>)EnemyCatalog.Enemies).FirstOrDefault((Func<EnemyType, bool>)((EnemyType e) => string.Equals(e.enemyName, "Feiopar", StringComparison.OrdinalIgnoreCase)));
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			if (!Plugin.Cfg.For(val).Enabled.Value)
			{
				Plugin.DebugLog("Feiopar disabled — skipping dead-tree generation.");
				return;
			}
			int value = Plugin.Cfg.FeioparTreeCount.Value;
			int num = 0;
			List<Vector3> list = new List<Vector3>();
			for (int num2 = 0; num2 < value; num2++)
			{
				Vector3? val2 = PickOpenTreePoint(list);
				if (val2.HasValue)
				{
					list.Add(val2.Value);
					GameObject val3 = new GameObject($"MG_DeadTree_{num2}")
					{
						layer = 25
					};
					val3.transform.SetParent(((Component)this).transform, false);
					val3.transform.position = val2.Value;
					try
					{
						val3.tag = "Tree";
					}
					catch (Exception ex)
					{
						Plugin.DebugLog("Could not tag tree: " + ex.Message);
					}
					CapsuleCollider obj = val3.AddComponent<CapsuleCollider>();
					obj.direction = 1;
					obj.radius = 0.5f;
					obj.height = 16f;
					obj.center = new Vector3(0f, 8f, 0f);
					((Collider)obj).isTrigger = false;
					AttachTreeVisual(val3, 10f);
					_fakeTrees.Add(val3);
					GameObject val4 = new GameObject($"MG_DeadTreeCanopy_{num2}")
					{
						layer = 25
					};
					val4.transform.SetParent(((Component)this).transform, false);
					val4.transform.position = val2.Value + Vector3.up * 16f;
					SphereCollider obj2 = val4.AddComponent<SphereCollider>();
					obj2.radius = 2f;
					((Collider)obj2).isTrigger = false;
					_fakeTrees.Add(val4);
					num++;
				}
			}
			ResetPumaTreeCache(val);
			Plugin.Log.LogInfo((object)($"Feiopar: grew {num} dead trees (tagged 'Tree', layer 25, with collider + canopy) " + "for it to stalk from. Set [Integration] FeioparDeadTrees=false to disable."));
		}

		private Vector3? PickOpenTreePoint(List<Vector3> alreadyPlaced)
		{
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			int num = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.collidersAndRoomMaskAndDefault : (-5));
			Vector3? val = null;
			for (int i = 0; i < 40; i++)
			{
				Vector3? p = _sampler.GetRandomPoint(0f, 10, 0);
				if (!p.HasValue)
				{
					continue;
				}
				bool flag = alreadyPlaced.Any(delegate(Vector3 q)
				{
					//IL_0000: Unknown result type (might be due to invalid IL or missing references)
					//IL_0007: Unknown result type (might be due to invalid IL or missing references)
					//IL_000c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0011: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val3 = q - p.Value;
					return ((Vector3)(ref val3)).sqrMagnitude < 25f;
				});
				bool flag2 = Physics.CheckSphere(p.Value + Vector3.up * 1.5f, 1f, num, (QueryTriggerInteraction)1);
				bool num2 = SameLevelFloorCount(p.Value, 4f) == 8;
				bool flag3 = SameLevelFloorCount(p.Value, 7f) >= 6;
				if (num2 && !flag && !flag2)
				{
					Vector3? val2 = val;
					if (!val2.HasValue)
					{
						val = p;
					}
				}
				if (num2 && flag3 && !flag && !flag2)
				{
					return p;
				}
			}
			return val;
		}

		private static int SameLevelFloorCount(Vector3 center, float radius)
		{
			//IL_001b: 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_0033: 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_004e: 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)
			int num = 0;
			NavMeshHit val = default(NavMeshHit);
			for (int i = 0; i < 8; i++)
			{
				float num2 = (float)i * MathF.PI * 2f / 8f;
				if (NavMesh.SamplePosition(center + new Vector3(Mathf.Cos(num2), 0f, Mathf.Sin(num2)) * radius, ref val, 1.2f, -1) && Mathf.Abs(((NavMeshHit)(ref val)).position.y - center.y) <= 1.5f)
				{
					num++;
				}
			}
			return num;
		}

		private static void AttachTreeVisual(GameObject tree, float targetHeight)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Expected O, but got Unknown
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Expected O, but got Unknown
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Expected O, but got Unknown
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Expected O, but got Unknown
			try
			{
				if (!TryAttachRealTree(tree, targetHeight))
				{
					GameObject val = new GameObject("ProcTree");
					val.transform.SetParent(tree.transform, false);
					val.transform.localPosition = Vector3.zero;
					val.transform.localScale = Vector3.one * (targetHeight / 4.4f);
					Material trunkMaterial = GetTrunkMaterial();
					AddBranch(val, trunkMaterial, new Vector3(0f, 2.2f, 0f), new Vector3(0.35f, 2.2f, 0.35f), Quaternion.identity);
					AddBranch(val, trunkMaterial, new Vector3(0.35f, 3.6f, 0.1f), new Vector3(0.12f, 1.1f, 0.12f), Quaternion.Euler(0f, 0f, 55f));
					AddBranch(val, trunkMaterial, new Vector3(-0.3f, 3.9f, -0.15f), new Vector3(0.1f, 1f, 0.1f), Quaternion.Euler(0f, 120f, -50f));
					AddBranch(val, trunkMaterial, new Vector3(0.05f, 4.3f, 0.3f), new Vector3(0.09f, 0.9f, 0.09f), Quaternion.Euler(35f, 0f, 20f));
				}
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("Could not build tree visual: " + ex.Message);
			}
		}

		private static void AddBranch(GameObject tree, Material mat, Vector3 localPos, Vector3 localScale, Quaternion localRot)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: 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)
			GameObject obj = GameObject.CreatePrimitive((PrimitiveType)2);
			((Object)obj).name = "Branch";
			Collider component = obj.GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			obj.transform.SetParent(tree.transform, false);
			obj.transform.localPosition = localPos;
			obj.transform.localRotation = localRot;
			obj.transform.localScale = localScale;
			Renderer component2 = obj.GetComponent<Renderer>();
			if ((Object)(object)component2 != (Object)null && (Object)(object)mat != (Object)null)
			{
				component2.sharedMaterial = mat;
			}
		}

		private static bool TryAttachRealTree(GameObject tree, float targetHeight)
		{
			//IL_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Expected O, but got Unknown
			//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0243: Unknown result type (might be due to invalid IL or missing references)
			//IL_0247: Unknown result type (might be due to invalid IL or missing references)
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_022d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			if (!_realTreeSearched)
			{
				_realTreeSearched = true;
				try
				{
					MeshFilter[] array = Resources.FindObjectsOfTypeAll<MeshFilter>();
					foreach (MeshFilter val in array)
					{
						if ((Object)(object)val == (Object)null || (Object)(object)val.sharedMesh == (Object)null)
						{
							continue;
						}
						string text = (((Object)val.sharedMesh).name + " " + ((Object)((Component)val).gameObject).name).ToLowerInvariant();
						if (text.Contains("tree") || text.Contains("leafless") || text.Contains("pine") || text.Contains("birch") || text.Contains("trunk"))
						{
							MeshRenderer component = ((Component)val).GetComponent<MeshRenderer>();
							if (!((Object)(object)component == (Object)null) && !((Object)(object)((Renderer)component).sharedMaterial == (Object)null))
							{
								_realTreeMesh = val.sharedMesh;
								_realTreeMaterial = ((Renderer)component).sharedMaterial;
								_realTreeRotation = ((Component)val).transform.rotation;
								_realTreeScale = ((Component)val).transform.lossyScale;
								Plugin.Log.LogInfo((object)("Feiopar: using real tree mesh '" + ((Object)val.sharedMesh).name + "' for visuals."));
								break;
							}
						}
					}
					if ((Object)(object)_realTreeMesh == (Object)null)
					{
						Plugin.DebugLog("No real tree mesh loaded — using procedural dead trees.");
					}
				}
				catch (Exception ex)
				{
					Plugin.DebugLog("Real-tree search failed: " + ex.Message);
				}
			}
			if ((Object)(object)_realTreeMesh == (Object)null || (Object)(object)_realTreeMaterial == (Object)null)
			{
				return false;
			}
			GameObject val2 = new GameObject("TreeMesh");
			val2.transform.SetParent(tree.transform, false);
			val2.transform.rotation = _realTreeRotation;
			val2.transform.localScale = _realTreeScale;
			val2.AddComponent<MeshFilter>().sharedMesh = _realTreeMesh;
			MeshRenderer obj = val2.AddComponent<MeshRenderer>();
			((Renderer)obj).sharedMaterial = _realTreeMaterial;
			Bounds bounds = ((Renderer)obj).bounds;
			float y = ((Bounds)(ref bounds)).size.y;
			if (y > 0.01f)
			{
				float num = Mathf.Clamp(targetHeight / y, 0.05f, 20f);
				Transform transform = val2.transform;
				transform.localScale *= num;
			}
			bounds = ((Renderer)obj).bounds;
			float y2 = ((Bounds)(ref bounds)).min.y;
			Transform transform2 = val2.transform;
			transform2.position += Vector3.up * (tree.transform.position.y - y2);
			return true;
		}

		private static Material GetTrunkMaterial()
		{
			//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_008e: Expected O, but got Unknown
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_trunkMaterial != (Object)null)
			{
				return _trunkMaterial;
			}
			try
			{
				MeshRenderer[] array = Object.FindObjectsOfType<MeshRenderer>();
				Color val2 = default(Color);
				for (int i = 0; i < array.Length; i++)
				{
					Material sharedMaterial = ((Renderer)array[i]).sharedMaterial;
					if (!((Object)(object)sharedMaterial == (Object)null) && !((Object)(object)sharedMaterial.shader == (Object)null) && (((Object)sharedMaterial.shader).name.IndexOf("HDRP", StringComparison.OrdinalIgnoreCase) >= 0 || ((Object)sharedMaterial.shader).name.IndexOf("Lit", StringComparison.OrdinalIgnoreCase) >= 0))
					{
						Material val = new Material(sharedMaterial)
						{
							name = "MG_DeadTreeBark"
						};
						((Color)(ref val2))..ctor(0.16f, 0.11f, 0.08f);
						if (val.HasProperty("_BaseColor"))
						{
							val.SetColor("_BaseColor", val2);
						}
						if (val.HasProperty("_Color"))
						{
							val.SetColor("_Color", val2);
						}
						if (val.HasProperty("_BaseColorMap"))
						{
							val.SetTexture("_BaseColorMap", (Texture)null);
						}
						if (val.HasProperty("_MainTex"))
						{
							val.SetTexture("_MainTex", (Texture)null);
						}
						_trunkMaterial = val;
						Plugin.DebugLog("Dead-tree material cloned from shader '" + ((Object)sharedMaterial.shader).name + "'.");
						return _trunkMaterial;
					}
				}
				Plugin.DebugLog("No HDRP material found in scene for dead-tree trunks (will render default).");
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("GetTrunkMaterial failed: " + ex.Message);
			}
			return null;
		}

		private static void ResetPumaTreeCache(EnemyType feiopar)
		{
			try
			{
				Type type = ((object)(((Object)(object)feiopar.enemyPrefab != (Object)null) ? feiopar.enemyPrefab.GetComponentInChildren<EnemyAI>(true) : null))?.GetType();
				FieldInfo fieldInfo = type?.GetField("AllTreeNodes", BindingFlags.Static | BindingFlags.Public);
				if (fieldInfo != null)
				{
					fieldInfo.SetValue(null, null);
					Plugin.DebugLog("Reset " + type.Name + ".AllTreeNodes so it rebuilds from our fake trees.");
				}
				else
				{
					Plugin.DebugLog("Could not find PumaAI.AllTreeNodes to reset (field renamed?).");
				}
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("ResetPumaTreeCache failed: " + ex.Message);
			}
		}

		private void DespawnAllEnemies()
		{
			RoundManager instance = RoundManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			List<EnemyAI> list = instance.SpawnedEnemies.Where((EnemyAI e) => (Object)(object)e != (Object)null).ToList();
			int num = 0;
			foreach (EnemyAI item in list)
			{
				if (TryDespawn(instance, item))
				{
					num++;
				}
			}
			instance.SpawnedEnemies.RemoveAll((EnemyAI e) => (Object)(object)e == (Object)null);
			if (num > 0)
			{
				Plugin.Log.LogInfo((object)$"RespawnOnLoad: despawned {num} pre-existing enemies.");
			}
		}

		private void DespawnOwnedEnemies()
		{
			RoundManager instance = RoundManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			int num = 0;
			foreach (EnemyAI item in _ownedEnemies.Where((EnemyAI e) => (Object)(object)e != (Object)null).ToList())
			{
				if (TryDespawn(instance, item))
				{
					num++;
				}
			}
			if (num > 0)
			{
				Plugin.DebugLog($"Ship leaving: despawned {num} of our enemies.");
			}
		}

		private static bool TryDespawn(RoundManager rm, EnemyAI enemy)
		{
			try
			{
				NetworkObject val = enemy.thisNetworkObject;
				if ((Object)(object)val == (Object)null)
				{
					val = ((Component)enemy).GetComponent<NetworkObject>();
				}
				if ((Object)(object)val != (Object)null && val.IsSpawned)
				{
					rm.DespawnEnemyOnServer(val);
					return true;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Failed to despawn '" + enemy.enemyType?.enemyName + "': " + ex.Message));
			}
			return false;
		}
	}
	internal static class EnemyCatalog
	{
		private static readonly HashSet<string> HardExcluded = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Lasso", "Red pill" };

		private static readonly Dictionary<string, string> Notes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
		{
			["Bush Wolf"] = "BushWolfEnemy needs vain shrouds to hide in. The mod now grows them on Gordion at save load (see [Integration] VainShroudIterations), so enable Bush Wolf and reload the save / fly fresh — enabling it mid-round is too late for weed generation",
			["Cadaver Growths"] = "Intentionally unsupported: CadaverGrowthAI hard-requires a DunGen dungeon, which the Company building is not (even BrutalCompanyMinus's own Cadaver event can't bypass it). Use Cadaver Bloom traps (CadaverBloomTraps) for a working cadaver on Gordion",
			["Cadaver Bloom"] = "Enabled via [Integration] CadaverBloomTraps: planted directly as standalone corpse traps that burst and chase when a player walks close, so no Growth/dungeon is needed",
			["Feiopar"] = "PumaAI hunts from 'Tree'-tagged colliders on layer 25. With [Integration] FeioparDeadTrees the mod grows proper dead trees (tag + layer 25 collider + canopy) so it perches ~3 m up on a tree and pounces players who come near",
			["Earth Leviathan"] = "SandWormAI only emerges through natural ground, which the Company interior lacks. With [Balance] EarthLeviathanFloorEmerge the mod lets it breach up through the building floor"
		};

		private static readonly HashSet<string> ListedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static bool _whitelistMode;

		internal static readonly List<EnemyType> Enemies = new List<EnemyType>();

		internal static bool IsExcluded(string enemyName)
		{
			if (enemyName == null)
			{
				return false;
			}
			if (HardExcluded.Contains(enemyName))
			{
				return true;
			}
			if (!_whitelistMode)
			{
				return ListedNames.Contains(enemyName);
			}
			return !ListedNames.Contains(enemyName);
		}

		internal static string AIClassName(EnemyType type)
		{
			try
			{
				EnemyAI val = (((Object)(object)type.enemyPrefab != (Object)null) ? type.enemyPrefab.GetComponentInChildren<EnemyAI>(true) : null);
				return ((Object)(object)val != (Object)null) ? ((object)val).GetType().Name : "<no EnemyAI component>";
			}
			catch (Exception ex)
			{
				return "<unreadable: " + ex.GetType().Name + ">";
			}
		}

		internal static void Resolve()
		{
			Enemies.Clear();
			ListedNames.Clear();
			_whitelistMode = Plugin.Cfg.ExcludedEnemiesIsWhitelist.Value;
			foreach (string item in from s in (Plugin.Cfg.ExcludedEnemies.Value ?? string.Empty).Split(',')
				select s.Trim() into s
				where s.Length > 0
				select s)
			{
				ListedNames.Add(item);
			}
			Plugin.Log.LogInfo((object)(_whitelistMode ? $"Enemy list is a WHITELIST of {ListedNames.Count} name(s): only these may spawn." : $"Enemy list is a blacklist of {ListedNames.Count} name(s)."));
			if (_whitelistMode && ListedNames.Count == 0)
			{
				Plugin.Log.LogWarning((object)"ExcludedEnemiesIsWhitelist=true but ExcludedEnemies is empty — nothing will spawn.");
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			List<string> list = new List<string>();
			EnemyType[] array = Resources.FindObjectsOfTypeAll<EnemyType>();
			foreach (EnemyType val in array)
			{
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrWhiteSpace(val.enemyName) && hashSet.Add(val.enemyName))
				{
					if ((Object)(object)val.enemyPrefab == (Object)null)
					{
						list.Add("'" + val.enemyName + "' [no prefab] — the EnemyType asset has no enemyPrefab, so it cannot be instantiated");
					}
					else if (IsExcluded(val.enemyName))
					{
						string text = (HardExcluded.Contains(val.enemyName) ? "built-in exclusion" : (_whitelistMode ? "not on the whitelist" : "on the blacklist"));
						list.Add("'" + val.enemyName + "' [" + AIClassName(val) + "] — excluded (" + text + ")");
					}
					else
					{
						Enemies.Add(val);
					}
				}
			}
			Enemies.Sort((EnemyType a, EnemyType b) => string.CompareOrdinal(a.enemyName, b.enemyName));
			foreach (EnemyType enemy in Enemies)
			{
				Plugin.Cfg.For(enemy);
			}
			Plugin.Cfg.FinishLegacyCapMigration();
			Plugin.Log.LogInfo((object)($"Enemy catalog resolved: {Enemies.Count} spawnable types " + $"({list.Count} rejected)."));
			LogSpawnabilityReport(list);
		}

		private static void LogSpawnabilityReport(List<string> rejected)
		{
			foreach (string item in rejected)
			{
				Plugin.Log.LogInfo((object)("  rejected: " + item));
			}
			List<string> list = new List<string>();
			foreach (EnemyType enemy in Enemies)
			{
				EnemySpawnSettings enemySpawnSettings = Plugin.Cfg.For(enemy);
				string text = null;
				if (!enemySpawnSettings.Enabled.Value)
				{
					text = "Enabled = false";
				}
				else if (enemySpawnSettings.SpawnWeight.Value <= 0)
				{
					text = "SpawnWeight = 0";
				}
				else if (enemySpawnSettings.MaxSpawnCount.Value <= 0)
				{
					text = "MaxSpawnCount = 0";
				}
				Notes.TryGetValue(enemy.enemyName, out var value);
				if (text != null)
				{
					if (Plugin.Cfg.DebugMode.Value)
					{
						Plugin.DebugLog("  off: '" + enemy.enemyName + "' [" + AIClassName(enemy) + "] — " + text + ((value != null) ? ("; note: " + value) : string.Empty));
					}
					continue;
				}
				list.Add($"{enemy.enemyName} (w{enemySpawnSettings.SpawnWeight.Value}, max {enemySpawnSettings.MaxSpawnCount.Value}" + (enemy.isOutsideEnemy ? ", outdoor" : ", indoor") + ")");
				if (value != null)
				{
					Plugin.Log.LogWarning((object)("'" + enemy.enemyName + "' [" + AIClassName(enemy) + "] is enabled, note: " + value + "."));
				}
			}
			Plugin.Log.LogInfo((object)((list.Count > 0) ? ("Spawn pool: " + string.Join(", ", list)) : "Spawn pool is EMPTY — nothing is enabled with a non-zero weight and max count."));
		}
	}
	internal static class GordionEvents
	{
		private const string HordeColor = "#cc0000";

		private static readonly string[] HordeLines = new string[3] { "Something has been wearing their faces for a while now.", "They came in from both ends of the dock. All of them.", "You stayed too long." };

		internal static void Reset()
		{
		}

		internal static void RollForLanding()
		{
			Reset();
			if (!Plugin.Cfg.EnableStockEvents.Value)
			{
				return;
			}
			BcmeCompat.Scan();
			List<(string, string, int, object)> list = new List<(string, string, int, object)>();
			CollectStockCandidates(list);
			if (list.Count == 0)
			{
				BcmeCompat.PublishCurrentEvent(null);
				Plugin.Log.LogInfo((object)"No event is eligible on the Company moon this landing — see the reasons above.");
				return;
			}
			(string, string, int, object) tuple = PickWeighted(list);
			Plugin.Log.LogInfo((object)("Company moon event chosen: '" + tuple.Item2 + "' " + $"(from {list.Count} eligible candidate(s))."));
			if (!BcmeCompat.Execute(tuple.Item4, tuple.Item1))
			{
				BcmeCompat.PublishCurrentEvent(null);
			}
			else
			{
				BcmeCompat.PublishCurrentEvent(tuple.Item4);
			}
		}

		private static void CollectStockCandidates(List<(string name, string display, int weight, object mEvent)> candidates)
		{
			if (!BcmeCompat.Present)
			{
				Plugin.Log.LogInfo((object)"EnableStockEvents is on but BrutalCompanyMinus is not installed — skipping.");
				return;
			}
			if (!BcmeCompat.EventsUsable)
			{
				Plugin.Log.LogWarning((object)"EnableStockEvents is on but BCMER's event API is unavailable — skipping.");
				return;
			}
			foreach (string item in Plugin.Cfg.ResolveStockEventWhitelist())
			{
				string reason;
				object obj = BcmeCompat.TryResolve(item, out reason);
				if (obj == null)
				{
					Plugin.Log.LogInfo((object)("  stock event '" + item + "' skipped: " + reason + "."));
				}
				else
				{
					int num = Mathf.Max(1, BcmeCompat.WeightOf(item));
					candidates.Add((item, item, num, obj));
					Plugin.DebugLog($"  stock event '{item}' eligible ({BcmeCompat.TypeOf(item)}, w{num}).");
				}
			}
		}

		private static (string name, string display, int weight, object mEvent) PickWeighted(List<(string name, string display, int weight, object mEvent)> candidates)
		{
			long num = 0L;
			foreach (var candidate in candidates)
			{
				num += candidate.weight;
			}
			if (num <= 0)
			{
				return candidates[Random.Range(0, candidates.Count)];
			}
			long num2 = (long)(Random.value * (float)num);
			foreach (var candidate2 in candidates)
			{
				num2 -= candidate2.weight;
				if (num2 < 0)
				{
					return candidate2;
				}
			}
			return candidates[candidates.Count - 1];
		}

		internal static void AnnounceHorde(string enemyName)
		{
			Say(Format(string.IsNullOrWhiteSpace(enemyName) ? "Horde" : (enemyName + " Horde"), "#cc0000", HordeLines[Random.Range(0, HordeLines.Length)]), Plugin.Cfg.HordeAnnounce.Value);
		}

		private static string Format(string title, string colorHex, string description)
		{
			if (string.IsNullOrWhiteSpace(colorHex) || colorHex[0] != '#')
			{
				colorHex = "#ffffff";
			}
			string text = "<color=" + colorHex + ">" + title + "</color>";
			if (!string.IsNullOrWhiteSpace(description))
			{
				return text + ": " + description;
			}
			return text;
		}

		private static void Say(string message, bool allowed)
		{
			if (!allowed)
			{
				return;
			}
			try
			{
				if ((Object)(object)HUDManager.Instance != (Object)null)
				{
					HUDManager.Instance.AddChatMessage(message, "", -1, false);
				}
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("Event announcement failed: " + ex.Message);
			}
		}
	}
	internal sealed class LandingWatcher : MonoBehaviour
	{
		private const float PollInterval = 0.5f;

		private const float FallbackLandedAfter = 20f;

		private float _nextPoll;

		private bool _landedHandled;

		private float _fallbackTimer;

		internal static void Create()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("MonstersGordion_LandingWatcher");
			Object.DontDestroyOnLoad((Object)val);
			((Object)val).hideFlags = (HideFlags)61;
			val.AddComponent<LandingWatcher>();
		}

		private void Update()
		{
			if (Time.unscaledTime < _nextPoll)
			{
				return;
			}
			_nextPoll = Time.unscaledTime + 0.5f;
			try
			{
				Poll();
			}
			catch (Exception ex)
			{
				Plugin.DebugLog("LandingWatcher poll failed: " + ex.Message);
			}
		}

		private void Poll()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				ResetLandingState();
				return;
			}
			bool flag = instance.shipHasLanded && !instance.shipIsLeaving;
			if (!flag && !instance.shipIsLeaving && !instance.inShipPhase && IsCompanySceneLoaded())
			{
				_fallbackTimer += 0.5f;
				if (_fallbackTimer >= 20f && !_landedHandled)
				{
					Plugin.Log.LogWarning((object)"shipHasLanded was never set (another mod likely altered the landing sequence) — starting the spawner via fallback detection.");
					flag = true;
				}
			}
			else
			{
				_fallbackTimer = 0f;
			}
			if (flag && !_landedHandled)
			{
				_landedHandled = true;
				CompanyMonsterSpawner.OnShipLanded();
			}
			else if (!flag && _landedHandled && (instance.shipIsLeaving || !instance.shipHasLanded))
			{
				ResetLandingState();
				CompanyMonsterSpawner.OnShipLeaving();
			}
		}

		private void ResetLandingState()
		{
			_landedHandled = false;
			_fallbackTimer = 0f;
		}

		private static bool IsCompanySceneLoaded()
		{
			//IL_000e: 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)
			if (!CompanyMonsterSpawner.IsCompanyLevel())
			{
				return false;
			}
			Scene sceneByName = SceneManager.GetSceneByName("CompanyBuilding");
			if (((Scene)(ref sceneByName)).IsValid())
			{
				return ((Scene)(ref sceneByName)).isLoaded;
			}
			return false;
		}
	}
	internal sealed class NavMeshSampler
	{
		private sealed class Tier
		{
			public readonly List<(int a, int b, int c)> Triangles = new List<(int, int, int)>();

			public readonly List<float> CumulativeAreas = new List<float>();

			public float TotalArea;

			public void Add((int a, int b, int c) triangle, float area)
			{
				Triangles.Add(triangle);
				TotalArea += area;
				CumulativeAreas.Add(TotalArea);
			}
		}

		private const float EdgeSliceFraction = 0.2f;

		private const float EdgeSpacing = 3f;

		private readonly bool _requireIndoor;

		private readonly int _roofMask;

		private readonly NavMeshPath _pathCache = new NavMeshPath();

		private Vector3[] _vertices;

		private readonly Tier _upper = new Tier();

		private readonly Tier _lower = new Tier();

		private Vector3? _anchor;

		public Vector3? Anchor => _anchor;

		public bool HasUpperTier => _upper.Triangles.Count > 0;

		public bool HasLowerTier => _lower.Triangles.Count > 0;

		public float WalkableArea => _upper.TotalArea + _lower.TotalArea;

		public NavMeshSampler(bool requireIndoor)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			_requireIndoor = requireIndoor;
			_roofMask = BuildRoofMask();
		}

		private static int BuildRoofMask()
		{
			int num = 0;
			string[] array = new string[5] { "Room", "Colliders", "Default", "MapHazards", "Terrain" };
			for (int i = 0; i < array.Length; i++)
			{
				int num2 = LayerMask.NameToLayer(array[i]);
				if (num2 >= 0)
				{
					num |= 1 << num2;
				}
			}
			if (num != 0)
			{
				return num;
			}
			return -5;
		}

		public int Build()
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: 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_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: 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_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: 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_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_0354: Unknown result type (might be due to invalid IL or missing references)
			//IL_0368: Unknown result type (might be due to invalid IL or missing references)
			//IL_033d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0342: Unknown result type (might be due to invalid IL or missing references)
			//IL_0397: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_054d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0552: Unknown result type (might be due to invalid IL or missing references)
			_upper.Triangles.Clear();
			_upper.CumulativeAreas.Clear();
			_upper.TotalArea = 0f;
			_lower.Triangles.Clear();
			_lower.CumulativeAreas.Clear();
			_lower.TotalArea = 0f;
			_anchor = null;
			NavMeshTriangulation val = NavMesh.CalculateTriangulation();
			_vertices = val.vertices;
			List<(int, int, int, float, Vector3)> list = new List<(int, int, int, float, Vector3)>();
			float num = 0f;
			Vector3 val5;
			for (int i = 0; i + 2 < val.indices.Length; i += 3)
			{
				int num2 = val.indices[i];
				int num3 = val.indices[i + 1];
				int num4 = val.indices[i + 2];
				Vector3 val2 = _vertices[num2];
				Vector3 val3 = _vertices[num3];
				Vector3 val4 = _vertices[num4];
				val5 = Vector3.Cross(val3 - val2, val4 - val2);
				float num5 = ((Vector3)(ref val5)).magnitude * 0.5f;
				if (!(num5 < 0.05f))
				{
					Vector3 val6 = (val2 + val3 + val4) / 3f;
					if (!IsInsideShip(val6) && (!_requireIndoor || HasCeilingAbove(val6)))
					{
						list.Add((num2, num3, num4, num5, val6));
						num += num5;
					}
				}
			}
			if (list.Count == 0)
			{
				return 0;
			}
			int[] array = new int[list.Count];
			for (int j = 0; j < array.Length; j++)
			{
				array[j] = j;
			}
			Dictionary<long, int> dictionary = new Dictionary<long, int>();
			for (int k = 0; k < list.Count; k++)
			{
				int[] array2 = new int[3]
				{
					list[k].Item1,
					list[k].Item2,
					list[k].Item3
				};
				foreach (int num6 in array2)
				{
					long key = WeldKey(_vertices[num6]);
					if (dictionary.TryGetValue(key, out var value))
					{
						Union(array, k, value);
					}
					else
					{
						dictionary[key] = k;
					}
				}
			}
			Dictionary<int, float> dictionary2 = new Dictionary<int, float>();
			for (int m = 0; m < list.Count; m++)
			{
				int key2 = Find(array, m);
				dictionary2.TryGetValue(key2, out var value2);
				dictionary2[key2] = value2 + list[m].Item4;
			}
			int num7 = -1;
			float num8 = 0f;
			foreach (KeyValuePair<int, float> item in dictionary2)
			{
				if (item.Value > num8)
				{
					num8 = item.Value;
					num7 = item.Key;
				}
			}
			float num9 = 0f;
			Vector3 val7 = Vector3.zero;
			for (int n = 0; n < list.Count; n++)
			{
				if (Find(array, n) == num7 && list[n].Item4 > num9)
				{
					num9 = list[n].Item4;
					val7 = list[n].Item5;
				}
			}
			NavMeshHit val8 = default(NavMeshHit);
			if (NavMesh.SamplePosition(val7, ref val8, 5f, -1))
			{
				_anchor = ((NavMeshHit)(ref val8)).position;
			}
			float num10 = DetermineSplitY(list);
			int num11 = 0;
			NavMeshHit val9 = default(NavMeshHit);
			foreach (var item2 in list)
			{
				if (!NavMesh.SamplePosition(item2.Item5, ref val9, 3f, -1) || !IsReachable(((NavMeshHit)(ref val9)).position))
				{
					num11++;
				}
				else
				{
					((item2.Item5.y >= num10) ? _upper : _lower).Add((a: item2.Item1, b: item2.Item2, c: item2.Item3), item2.Item4);
				}
			}
			int num12 = _upper.Triangles.Count + _lower.Triangles.Count;
			ManualLogSource log = Plugin.Log;
			string[] obj = new string[8]
			{
				$"NavMeshSampler: kept {num12}/{list.Count} triangles ",
				$"({_upper.TotalArea + _lower.TotalArea:F0} of {num:F0} m², ",
				$"{dictionary2.Count} regions, {num11} unreachable). ",
				$"Tiers @ splitY={num10:F1}: upper {_upper.Triangles.Count} tris / {_upper.TotalArea:F0} m², ",
				$"lower {_lower.Triangles.Count} tris / {_lower.TotalArea:F0} m². ",
				"Anchor=",
				null,
				null
			};
			object obj2;
			if (!_anchor.HasValue)
			{
				obj2 = "NONE";
			}
			else
			{
				val5 = _anchor.Value;
				obj2 = ((Vector3)(ref val5)).ToString("F1");
			}
			obj[6] = (string)obj2;
			obj[7] = ".";
			log.LogInfo((object)string.Concat(obj));
			return num12;
		}

		private static float DetermineSplitY(List<(int a, int b, int c, float area, Vector3 centroid)> candidates)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance != (Object)null && (Object)(object)instance.shipBounds != (Object)null)
				{
					Bounds bounds = instance.shipBounds.bounds;
					return ((Bounds)(ref bounds)).min.y - 3f;
				}
			}
			catch
			{
			}
			List<float> list = new List<float>(candidates.Count);
			foreach (var candidate in candidates)
			{
				list.Add(candidate.centroid.y);
			}
			list.Sort();
			return list[list.Count / 2];
		}

		public Vector3? GetRandomPoint(float minPlayerDistance, int attempts, int upperSharePercent)
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			if (_upper.Triangles.Count == 0 && _lower.Triangles.Count == 0)
			{
				return null;
			}
			for (int i = 0; i < attempts; i++)
			{
				bool flag = Random.Range(0, 100) < upperSharePercent;
				Tier tier = (flag ? _upper : _lower);
				if (tier.Triangles.Count == 0)
				{
					tier = (flag ? _lower : _upper);
				}
				Vector3 candidate = RandomPointInTriangle(tier);
				if (TryValidate(candidate, minPlayerDistance, out var point))
				{
					return point;
				}
			}
			return null;
		}

		public bool TryGetOppositeEdgePoints(int perSide, float minPlayerDistance, out List<Vector3> sideA, out List<Vector3> sideB)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: 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_00da: 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_00ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			sideA = new List<Vector3>();
			sideB = new List<Vector3>();
			if (perSide <= 0)
			{
				return false;
			}
			List<((int, int, int), Vector3)> list = new List<((int, int, int), Vector3)>();
			float num = float.MaxValue;
			float num2 = float.MinValue;
			float num3 = float.MaxValue;
			float num4 = float.MinValue;
			Tier[] array = new Tier[2] { _upper, _lower };
			for (int i = 0; i < array.Length; i++)
			{
				foreach (var triangle in array[i].Triangles)
				{
					Vector3 val = (_vertices[triangle.a] + _vertices[triangle.b] + _vertices[triangle.c]) / 3f;
					if (val.x < num)
					{
						num = val.x;
					}
					if (val.x > num2)
					{
						num2 = val.x;
					}
					if (val.z < num3)
					{
						num3 = val.z;
					}
					if (val.z > num4)
					{
						num4 = val.z;
					}
					list.Add((triangle, val));
				}
			}
			if (list.Count < 2)
			{
				return false;
			}
			bool useX = num2 - num >= num4 - num3;
			list.Sort((((int a, int b, int c) tri, Vector3 centroid) l, ((int a, int b, int c) tri, Vector3 centroid) r) => (useX ? l.centroid.x : l.centroid.z).CompareTo(useX ? r.centroid.x : r.centroid.z));
			int num5 = Mathf.Clamp(Mathf.RoundToInt((float)list.Count * 0.2f), 1, list.Count / 2);
			FillEdge(list, 0, num5, perSide, minPlayerDistance, sideA);
			FillEdge(list, list.Count - num5, list.Count, perSide, minPlayerDistance, sideB);
			Plugin.DebugLog("Edge sampling on " + (useX ? "X" : "Z") + " axis, span " + $"{(useX ? (num2 - num) : (num4 - num3)):F0} m over {list.Count} triangles " + $"(slice {num5}): got {sideA.Count} + {sideB.Count} of {perSide} per side.");
			if (sideA.Count > 0)
			{
				return sideB.Count > 0;
			}
			return false;
		}

		private void FillEdge(List<((int a, int b, int c) tri, Vector3 centroid)> tris, int from, int to, int wanted, float minPlayerDistance, List<Vector3> into)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			int num = wanted * 12;
			for (int i = 0; i < num; i++)
			{
				if (into.Count >= wanted)
				{
					break;
				}
				(int, int, int) item = tris[Random.Range(from, to)].tri;
				if (!TryValidate(RandomPointInTriangle(item), minPlayerDistance, out var point))
				{
					continue;
				}
				bool flag = false;
				foreach (Vector3 item2 in into)
				{
					Vector3 val = item2 - point;
					if (((Vector3)(ref val)).sqrMagnitude < 9f)
					{
						flag = true;
						break;
					}
				}
				if (!flag)
				{
					into.Add(point);
				}
			}
		}

		private bool TryValidate(Vector3 candidate, float minPlayerDistance, out Vector3 point)
		{
			//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_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: 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_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			point = candidate;
			NavMeshHit val = default(NavMeshHit);
			if (!NavMesh.SamplePosition(candidate, ref val, 3f, -1))
			{
				return false;
			}
			point = ((NavMeshHit)(ref val)).position;
			if (IsInsideShip(point))
			{
				return false;
			}
			if (_requireIndoor && !HasCeilingAbove(point))
			{
				return false;
			}
			if (minPlayerDistance > 0f && IsTooCloseToAnyPlayer(point, minPlayerDistance))
			{
				return false;
			}
			if (!IsReachable(point))
			{
				Plugin.DebugLog($"Rejected unreachable point {point:F1}.");
				return false;
			}
			return true;
		}

		public bool IsReachable(Vector3 point)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Invalid comparison between Unknown and I4
			if (!_anchor.HasValue)
			{
				return true;
			}
			_pathCache.ClearCorners();
			if (NavMesh.CalculatePath(point, _anchor.Value, -1, _pathCache))
			{
				return (int)_pathCache.status == 0;
			}
			return false;
		}

		private Vector3 RandomPointInTriangle((int a, int b, int c) triangle)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: 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_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			float num = Mathf.Sqrt(Random.value);
			float value = Random.value;
			return _vertices[triangle.a] * (1f - num) + _vertices[triangle.b] * (num * (1f - value)) + _vertices[triangle.c] * (num * value);
		}

		private Vector3 RandomPointInTriangle(Tier tier)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: 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_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			float value = Random.value * tier.TotalArea;
			int index = LowerBound(tier.CumulativeAreas, value);
			(int a, int b, int c) tuple = tier.Triangles[index];
			int item = tuple.a;
			int item2 = tuple.b;
			int item3 = tuple.c;
			float num = Mathf.Sqrt(Random.value);
			float value2 = Random.value;
			return _vertices[item] * (1f - num) + _vertices[item2] * (num * (1f - value2)) + _vertices[item3] * (num * value2);
		}

		private static long WeldKey(Vector3 v)
		{
			//IL_0000: 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_0025: Unknown result type (might be due to invalid IL or missing references)
			long num = (long)Mathf.Round(v.x * 10f);
			long num2 = (long)Mathf.Round(v.y * 10f);
			long num3 = (long)Mathf.Round(v.z * 10f);
			return (num & 0x1FFFFF) | ((num2 & 0x1FFFFF) << 21) | ((num3 & 0x1FFFFF) << 42);
		}

		private static int Find(int[] parent, int i)
		{
			while (parent[i] != i)
			{
				parent[i] = parent[parent[i]];
				i = parent[i];
			}
			return i;
		}

		private static void Union(int[] parent, int a, int b)
		{
			int num = Find(parent, a);
			int num2 = Find(parent, b);
			if (num != num2)
			{
				parent[num] = num2;
			}
		}

		private static int LowerBound(List<float> sorted, float value)
		{
			int num = 0;
			int num2 = sorted.Count - 1;
			while (num < num2)
			{
				int num3 = (num + num2) / 2;
				if (sorted[num3] < value)
				{
					num = num3 + 1;
				}
				else
				{
					num2 = num3;
				}
			}
			return num;
		}

		private bool HasCeilingAbove(Vector3 point)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			return Physics.Raycast(point + Vector3.up * 0.3f, Vector3.up, 60f, _roofMask, (QueryTriggerInteraction)1);
		}

		private static bool IsInsideShip(Vector3 point)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.shipBounds == (Object)null)
			{
				return false;
			}
			Bounds bounds = instance.shipBounds.bounds;
			((Bounds)(ref bounds)).Expand(4f);
			return ((Bounds)(ref bounds)).Contains(point);
		}

		private static bool IsTooCloseToAnyPlayer(Vector3 point, float minDistance)
		{
			//IL_0048: 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_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			float num = minDistance * minDistance;
			PlayerControllerB[] allPlayerScripts = instance.allPlayerScripts;
			foreach (PlayerControllerB val in allPlayerScripts)
			{
				if (!((Object)(object)val == (Object)null) && val.isPlayerControlled && !val.isPlayerDead)
				{
					Vector3 val2 = ((Component)val).transform.position - point;
					if (((Vector3)(ref val2)).sqrMagnitude < num)
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	[BepInPlugin("Timofey.MonstersGordion", "MonstersGordion", "1.9.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		private Harmony _harmony;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal static PluginConfig Cfg { get; private set; }

		private void Awake()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			Cfg = new PluginConfig(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("Timofey.MonstersGordion");
			_harmony.PatchAll(typeof(StartOfRoundPatches));
			_harmony.PatchAll(typeof(EnemyAIPatches));
			_harmony.PatchAll(typeof(M