Decompiled source of LethalEcology v0.1.0

LethalEcology.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
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 System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;

[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("LethalEcology")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0")]
[assembly: AssemblyProduct("LethalEcology")]
[assembly: AssemblyTitle("LethalEcology")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.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 LethalEcology
{
	public static class CultivationVisuals
	{
		private static GameObject _root;

		private static string _levelKey = "";

		public static void Refresh(SelectableLevel level)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			//IL_0082: 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_0110: 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_0103: 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_01a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)level == (Object)null)
			{
				return;
			}
			MoonState orCreate = EcologyRuntime.GetOrCreate(level);
			if (orCreate == null)
			{
				return;
			}
			string text = EcologyRuntime.LevelKey(level);
			if ((Object)(object)_root != (Object)null && _levelKey != text)
			{
				Object.Destroy((Object)(object)_root);
				_root = null;
			}
			if ((Object)(object)_root == (Object)null)
			{
				_root = new GameObject("LethalEcology_Cultivation");
				_levelKey = text;
			}
			foreach (Transform item in _root.transform)
			{
				Object.Destroy((Object)(object)((Component)item).gameObject);
			}
			StartOfRound instance = StartOfRound.Instance;
			Vector3 val = (((Object)(object)instance != (Object)null && (Object)(object)instance.shipLandingPosition != (Object)null) ? instance.shipLandingPosition.position : (((Object)(object)GameNetworkManager.Instance != (Object)null && (Object)(object)GameNetworkManager.Instance.localPlayerController != (Object)null) ? ((Component)GameNetworkManager.Instance.localPlayerController).transform.position : Vector3.zero));
			int num = 0;
			foreach (CropState item2 in orCreate.Crops.Values.OrderBy((CropState x) => x.Name))
			{
				for (int num2 = 0; num2 < Mathf.Clamp(item2.Amount, 1, 4); num2++)
				{
					float num3 = (float)num * 47f * (MathF.PI / 180f);
					float num4 = 7f + (float)(num % 3) * 2.2f;
					Vector3 position = val + new Vector3(Mathf.Cos(num3) * num4, 0.05f, Mathf.Sin(num3) * num4);
					CreateCrop(item2.Name, position, _root.transform, num);
					num++;
				}
			}
		}

		public static void Plant(SelectableLevel level, CropDef def)
		{
			Refresh(level);
		}

		public static void Clear()
		{
			if ((Object)(object)_root != (Object)null)
			{
				Object.Destroy((Object)(object)_root);
			}
			_root = null;
			_levelKey = "";
		}

		private static void CreateCrop(string name, Vector3 position, Transform parent, int index)
		{
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: 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_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: 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_025e: Unknown result type (might be due to invalid IL or missing references)
			string text = EcoCatalog.Normalize(name);
			Color val = default(Color);
			((Color)(ref val))..ctor(0.4f, 0.9f, 0.5f);
			float num = 0.6f;
			if (text == EcoCatalog.Normalize("Nectar Bloom"))
			{
				((Color)(ref val))..ctor(1f, 0.55f, 0.2f);
				num = 0.8f;
			}
			else if (text == EcoCatalog.Normalize("Saproot"))
			{
				((Color)(ref val))..ctor(0.35f, 0.7f, 0.3f);
				num = 1.8f;
			}
			else if (text == EcoCatalog.Normalize("Glowcap"))
			{
				((Color)(ref val))..ctor(0.2f, 0.95f, 0.9f);
				num = 0.5f;
			}
			else if (text == EcoCatalog.Normalize("Ropevine"))
			{
				((Color)(ref val))..ctor(0.25f, 0.65f, 0.25f);
				num = 2f;
			}
			else if (text == EcoCatalog.Normalize("Ironroot"))
			{
				((Color)(ref val))..ctor(0.45f, 0.4f, 0.35f);
				num = 0.9f;
			}
			else if (text == EcoCatalog.Normalize("Carrion Bloom"))
			{
				((Color)(ref val))..ctor(0.8f, 0.2f, 0.55f);
				num = 0.7f;
			}
			GameObject obj = GameObject.CreatePrimitive((PrimitiveType)2);
			((Object)obj).name = "Crop_" + text + "_" + index;
			obj.transform.SetParent(parent, false);
			obj.transform.position = position + Vector3.up * (num * 0.5f);
			obj.transform.localScale = new Vector3(0.1f, num * 0.5f, 0.1f);
			Paint(obj, val * 0.6f);
			GameObject obj2 = GameObject.CreatePrimitive((PrimitiveType)((text == EcoCatalog.Normalize("Ironroot")) ? 3 : 0));
			obj2.transform.SetParent(parent, false);
			obj2.transform.position = position + Vector3.up * (num + 0.15f);
			obj2.transform.localScale = ((text == EcoCatalog.Normalize("Ropevine")) ? new Vector3(0.18f, 0.5f, 0.18f) : new Vector3(0.55f, 0.4f, 0.55f));
			Paint(obj2, val);
		}

		private static void Paint(GameObject obj, Color color)
		{
			//IL_0044: 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)
			Collider component = obj.GetComponent<Collider>();
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			Renderer component2 = obj.GetComponent<Renderer>();
			if (!((Object)(object)component2 == (Object)null))
			{
				Shader val = Shader.Find("Standard");
				Material val2 = (Material)((!((Object)(object)val != (Object)null)) ? ((object)component2.material) : ((object)new Material(val)));
				val2.color = color;
				component2.material = val2;
			}
		}
	}
	public static class EcoData
	{
		private static bool _loaded;

		public static SpeciesDataFile Species { get; private set; } = new SpeciesDataFile();

		public static CropDataFile Crops { get; private set; } = new CropDataFile();

		public static MoonDataFile Moons { get; private set; } = new MoonDataFile();

		public static MechanicsDataFile Mechanics { get; private set; } = new MechanicsDataFile();

		public static void Load()
		{
			if (!_loaded)
			{
				_loaded = true;
				try
				{
					Species = ReadEmbedded<SpeciesDataFile>("LethalEcology.Data.species.json");
					Crops = ReadEmbedded<CropDataFile>("LethalEcology.Data.crops.json");
					Moons = ReadEmbedded<MoonDataFile>("LethalEcology.Data.moons.json");
					Mechanics = ReadEmbedded<MechanicsDataFile>("LethalEcology.Data.mechanics.json");
				}
				catch (Exception ex)
				{
					Debug.LogError((object)("[LethalEcology] Failed to load embedded data: " + ex));
				}
				SpeciesDataFile speciesDataFile = LoadOverride<SpeciesDataFile>("species.json");
				if (speciesDataFile != null)
				{
					Species = speciesDataFile;
				}
				CropDataFile cropDataFile = LoadOverride<CropDataFile>("crops.json");
				if (cropDataFile != null)
				{
					Crops = cropDataFile;
				}
				MoonDataFile moonDataFile = LoadOverride<MoonDataFile>("moons.json");
				if (moonDataFile != null)
				{
					Moons = moonDataFile;
				}
				MechanicsDataFile mechanicsDataFile = LoadOverride<MechanicsDataFile>("mechanics.json");
				if (mechanicsDataFile != null)
				{
					Mechanics = mechanicsDataFile;
				}
				if (Species.Species == null)
				{
					Species.Species = new List<SpeciesDef>();
				}
				if (Crops.Crops == null)
				{
					Crops.Crops = new List<CropDef>();
				}
				if (Moons.Moons == null)
				{
					Moons.Moons = new List<MoonDef>();
				}
				if (Mechanics.Mechanics == null)
				{
					Mechanics.Mechanics = new List<MechanicDef>();
				}
			}
		}

		private static T ReadEmbedded<T>(string name) where T : new()
		{
			using Stream stream = typeof(EcoData).Assembly.GetManifestResourceStream(name);
			if (stream == null)
			{
				Debug.LogWarning((object)("[LethalEcology] Missing embedded resource " + name));
				return new T();
			}
			using StreamReader streamReader = new StreamReader(stream);
			T val = JsonConvert.DeserializeObject<T>(streamReader.ReadToEnd());
			return (val != null) ? val : new T();
		}

		private static T LoadOverride<T>(string fileName) where T : class
		{
			try
			{
				string text = Path.Combine(Path.Combine(Paths.PluginPath, "LethalEcology", "Data"), fileName);
				if (!File.Exists(text))
				{
					return null;
				}
				T val = JsonConvert.DeserializeObject<T>(File.ReadAllText(text));
				if (val != null)
				{
					Debug.Log((object)("[LethalEcology] Loaded override data sheet " + text));
				}
				return val;
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[LethalEcology] Could not load override " + fileName + ": " + ex.Message));
				return null;
			}
		}
	}
	public static class EcoCatalog
	{
		public static readonly Dictionary<string, SpeciesRuntime> Runtime = new Dictionary<string, SpeciesRuntime>();

		public static readonly Dictionary<string, SpeciesDef> Authored = new Dictionary<string, SpeciesDef>();

		private static bool _loaded;

		public static bool RuntimeBuilt { get; private set; }

		public static void Load()
		{
			if (_loaded)
			{
				return;
			}
			_loaded = true;
			EcoData.Load();
			Authored.Clear();
			foreach (SpeciesDef item in EcoData.Species.Species)
			{
				if (item != null && !string.IsNullOrWhiteSpace(item.Name))
				{
					Authored[Normalize(item.Name)] = item;
				}
			}
		}

		public static string Normalize(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return "";
			}
			return Regex.Replace(value.ToLowerInvariant(), "[^a-z0-9]+", "");
		}

		public static void EnsureRuntime()
		{
			if (RuntimeBuilt)
			{
				return;
			}
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || instance.levels == null || instance.levels.Length == 0)
			{
				return;
			}
			RuntimeBuilt = true;
			SelectableLevel[] levels = instance.levels;
			foreach (SelectableLevel val in levels)
			{
				if (!((Object)(object)val == (Object)null))
				{
					AddList(val.Enemies, outside: false, inside: true, daytime: false);
					AddList(val.OutsideEnemies, outside: true, inside: false, daytime: false);
					AddList(val.DaytimeEnemies, outside: false, inside: false, daytime: true);
				}
			}
		}

		private static void AddList(List<SpawnableEnemyWithRarity> list, bool outside, bool inside, bool daytime)
		{
			if (list == null)
			{
				return;
			}
			foreach (SpawnableEnemyWithRarity item in list)
			{
				if (item != null && !((Object)(object)item.enemyType == (Object)null))
				{
					AddRuntime(item.enemyType, outside, inside, daytime);
				}
			}
		}

		public static SpeciesRuntime AddRuntime(EnemyType type, bool outside, bool inside, bool daytime)
		{
			string text = Normalize(type.enemyName);
			if (string.IsNullOrEmpty(text))
			{
				text = Normalize(((Object)type).name);
			}
			if (Runtime.TryGetValue(text, out var value))
			{
				value.Outside |= outside;
				value.Inside |= inside;
				value.Daytime |= daytime;
				return value;
			}
			SpeciesDef def = FindAuthored(type.enemyName) ?? MakeFallback(type, outside, inside, daytime);
			SpeciesRuntime speciesRuntime = new SpeciesRuntime
			{
				Key = text,
				Name = (string.IsNullOrWhiteSpace(type.enemyName) ? ((Object)type).name : type.enemyName),
				Def = def,
				Type = type,
				Outside = (outside || type.isOutsideEnemy),
				Inside = (inside || (!type.isOutsideEnemy && !type.isDaytimeEnemy)),
				Daytime = (daytime || type.isDaytimeEnemy),
				OriginalMaxCount = Mathf.Max(1, type.MaxCount),
				OriginalSpawningDisabled = type.spawningDisabled,
				Power = Mathf.Max(0f, type.PowerLevel)
			};
			Runtime[text] = speciesRuntime;
			return speciesRuntime;
		}

		public static SpeciesDef FindAuthored(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return null;
			}
			Load();
			string text = Normalize(value);
			if (Authored.TryGetValue(text, out var value2))
			{
				return value2;
			}
			foreach (SpeciesDef item in EcoData.Species.Species)
			{
				if (item == null)
				{
					continue;
				}
				foreach (string item2 in item.Aliases ?? new List<string>())
				{
					if (Normalize(item2) == text)
					{
						return item;
					}
				}
			}
			SpeciesDef result = null;
			int num = 0;
			foreach (SpeciesDef item3 in EcoData.Species.Species)
			{
				if (item3 == null)
				{
					continue;
				}
				List<string> list = new List<string>();
				list.Add(item3.Name);
				list.AddRange(item3.Aliases ?? new List<string>());
				foreach (string item4 in list)
				{
					string text2 = Normalize(item4);
					if (text2.Length >= 4)
					{
						int num2 = 0;
						if (text2.StartsWith(text) || text.StartsWith(text2))
						{
							num2 = Mathf.Min(text2.Length, text.Length) + 2;
						}
						else if (text2.Contains(text) || text.Contains(text2))
						{
							num2 = Mathf.Min(text2.Length, text.Length);
						}
						if (num2 > num)
						{
							num = num2;
							result = item3;
						}
					}
				}
			}
			if (num < 4)
			{
				return null;
			}
			return result;
		}

		private static SpeciesDef MakeFallback(EnemyType type, bool outside, bool inside, bool daytime)
		{
			string text = (string.IsNullOrWhiteSpace(type.enemyName) ? ((Object)type).name : type.enemyName);
			string text2 = Normalize(text);
			float powerLevel = type.PowerLevel;
			string tier = ((powerLevel >= 8f) ? "Apex" : ((powerLevel >= 3f) ? "Secondary" : ((powerLevel >= 1f) ? "Primary" : "Producer")));
			if (text2.Contains("coil") || text2.Contains("jester") || text2.Contains("ghost") || text2.Contains("barber"))
			{
				tier = "Anomaly";
			}
			string text3 = (outside ? "Outside" : (daytime ? "Daytime" : "Indoor"));
			bool flag = !text2.Contains("leviathan") && !text2.Contains("coil") && !text2.Contains("jester") && !text2.Contains("ghost") && !text2.Contains("barber") && type.canBeStunned && powerLevel <= 8f;
			SpeciesDef speciesDef = new SpeciesDef
			{
				Name = text,
				Tier = tier,
				Habitat = text3,
				Role = "Field-classified " + text3.ToLowerInvariant() + " species",
				Diet = "Unclassified. Recorded from the installed game assembly.",
				Capturable = flag,
				PenCost = (flag ? Mathf.Clamp(Mathf.CeilToInt(powerLevel) + 1, 1, 4) : 0),
				Power = powerLevel,
				Notes = "This species is present in the installed build but has no hand-authored sheet. Its profile is generated from live enemy data."
			};
			speciesDef.Traits.Add("Runtime-discovered species");
			if (outside)
			{
				speciesDef.Traits.Add("Outside spawn pool");
			}
			if (inside)
			{
				speciesDef.Traits.Add("Indoor spawn pool");
			}
			if (daytime)
			{
				speciesDef.Traits.Add("Daytime spawn pool");
			}
			speciesDef.Controls.Add("Use the population and budget tools");
			speciesDef.Controls.Add("Capture only if the terminal says it is capturable");
			speciesDef.Canon.Add("Generated profile from the installed build");
			speciesDef.Mod.Add("Fallback food-web role; add a data-sheet entry to refine it.");
			return speciesDef;
		}

		public static List<SpeciesRuntime> SpeciesForLevel(SelectableLevel level)
		{
			EnsureRuntime();
			List<SpeciesRuntime> result = new List<SpeciesRuntime>();
			HashSet<string> seen = new HashSet<string>();
			if ((Object)(object)level == (Object)null)
			{
				return result;
			}
			AddLevelList(level.Enemies, result, seen);
			AddLevelList(level.OutsideEnemies, result, seen);
			AddLevelList(level.DaytimeEnemies, result, seen);
			return result;
		}

		private static void AddLevelList(List<SpawnableEnemyWithRarity> list, List<SpeciesRuntime> result, HashSet<string> seen)
		{
			if (list == null)
			{
				return;
			}
			foreach (SpawnableEnemyWithRarity item in list)
			{
				if (item != null && !((Object)(object)item.enemyType == (Object)null))
				{
					string text = Normalize(item.enemyType.enemyName);
					if (Runtime.TryGetValue(text, out var value) && seen.Add(text))
					{
						result.Add(value);
					}
				}
			}
		}

		public static bool HasSpecies(SelectableLevel level, string speciesName)
		{
			string key = Normalize(speciesName);
			return SpeciesForLevel(level).Any((SpeciesRuntime x) => x.Key == key);
		}

		public static List<SpeciesRuntime> AllRuntimeOrdered()
		{
			EnsureRuntime();
			return (from x in Runtime.Values
				orderby TierOrder(x.Def.Tier), x.Name
				select x).ToList();
		}

		public static int TierOrder(string tier)
		{
			return (tier ?? "").ToLowerInvariant() switch
			{
				"producer" => 0, 
				"primary" => 1, 
				"secondary" => 2, 
				"apex" => 3, 
				"parasite" => 4, 
				"anomaly" => 5, 
				"scavenger" => 6, 
				_ => 7, 
			};
		}

		public static List<SpeciesRuntime> CurrentMoonSpecies()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null)
			{
				return new List<SpeciesRuntime>();
			}
			return SpeciesForLevel(instance.currentLevel);
		}

		public static SpeciesRuntime GetRuntime(string keyOrName)
		{
			EnsureRuntime();
			string key = Normalize(keyOrName);
			if (Runtime.TryGetValue(key, out var value))
			{
				return value;
			}
			foreach (SpeciesRuntime value2 in Runtime.Values)
			{
				if (Normalize(value2.Name) == key)
				{
					return value2;
				}
				if ((value2.Def.Aliases ?? new List<string>()).Any((string a) => Normalize(a) == key))
				{
					return value2;
				}
			}
			return null;
		}

		public static MoonDef FindMoon(SelectableLevel level)
		{
			if ((Object)(object)level == (Object)null)
			{
				return null;
			}
			return FindMoon(level.PlanetName) ?? FindMoon("level " + level.levelID);
		}

		public static MoonDef FindMoon(string value)
		{
			Load();
			if (string.IsNullOrWhiteSpace(value))
			{
				return null;
			}
			string text = Normalize(value);
			foreach (MoonDef moon in EcoData.Moons.Moons)
			{
				if (moon != null && Normalize(moon.Name) == text)
				{
					return moon;
				}
			}
			foreach (MoonDef moon2 in EcoData.Moons.Moons)
			{
				if (moon2 == null)
				{
					continue;
				}
				foreach (string item in moon2.Aliases ?? new List<string>())
				{
					if (Normalize(item) == text)
					{
						return moon2;
					}
				}
			}
			MoonDef result = null;
			int num = 0;
			foreach (MoonDef moon3 in EcoData.Moons.Moons)
			{
				if (moon3 == null)
				{
					continue;
				}
				List<string> list = new List<string>();
				list.Add(moon3.Name);
				list.AddRange(moon3.Aliases ?? new List<string>());
				foreach (string item2 in list)
				{
					string text2 = Normalize(item2);
					if (text2.Length >= 3)
					{
						int num2 = 0;
						if (text2.StartsWith(text) || text.StartsWith(text2))
						{
							num2 = Mathf.Min(text2.Length, text.Length) + 2;
						}
						else if (text2.Contains(text) || text.Contains(text2))
						{
							num2 = Mathf.Min(text2.Length, text.Length);
						}
						if (num2 > num)
						{
							num = num2;
							result = moon3;
						}
					}
				}
			}
			if (num < 3)
			{
				return null;
			}
			return result;
		}

		public static MoonDef MoonForLevelOrFallback(SelectableLevel level)
		{
			MoonDef moonDef = FindMoon(level);
			if (moonDef != null)
			{
				return moonDef;
			}
			if ((Object)(object)level == (Object)null)
			{
				return new MoonDef();
			}
			return new MoonDef
			{
				Name = (string.IsNullOrWhiteSpace(level.PlanetName) ? ("Level " + level.levelID) : level.PlanetName),
				Identity = "Runtime-discovered moon",
				Biome = "Unclassified",
				StartingFood = 55f,
				StartingStability = 2,
				Notes = "No hand-authored moon identity exists for this level. The mod uses its actual enemy pool and vanilla power bases.",
				Project = "Stabilize the food web and record what the moon supports."
			};
		}

		public static CropDef FindCrop(string value)
		{
			Load();
			if (string.IsNullOrWhiteSpace(value))
			{
				return null;
			}
			string key = Normalize(value);
			foreach (CropDef crop in EcoData.Crops.Crops)
			{
				if (crop == null)
				{
					continue;
				}
				if (Normalize(crop.Name) == key)
				{
					return crop;
				}
				foreach (string item in crop.Aliases ?? new List<string>())
				{
					if (Normalize(item) == key)
					{
						return crop;
					}
				}
			}
			return EcoData.Crops.Crops.FirstOrDefault((CropDef c) => c != null && (Normalize(c.Name).Contains(key) || key.Contains(Normalize(c.Name))));
		}

		public static MechanicDef FindMechanic(string value)
		{
			Load();
			if (string.IsNullOrWhiteSpace(value))
			{
				return null;
			}
			string key = Normalize(value);
			foreach (MechanicDef mechanic in EcoData.Mechanics.Mechanics)
			{
				if (mechanic == null)
				{
					continue;
				}
				if (Normalize(mechanic.Name) == key)
				{
					return mechanic;
				}
				foreach (string item in mechanic.Aliases ?? new List<string>())
				{
					if (Normalize(item) == key)
					{
						return mechanic;
					}
				}
			}
			return EcoData.Mechanics.Mechanics.FirstOrDefault((MechanicDef c) => c != null && (Normalize(c.Name).Contains(key) || key.Contains(Normalize(c.Name))));
		}

		public static List<SelectableLevel> AllLevels()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || instance.levels == null)
			{
				return new List<SelectableLevel>();
			}
			return (from x in instance.levels
				where (Object)(object)x != (Object)null
				orderby x.levelID
				select x).ToList();
		}

		public static List<CropDef> CropsForMoon(SelectableLevel level)
		{
			List<string> list = MoonForLevelOrFallback(level).Crops ?? new List<string>();
			if (list.Count == 0)
			{
				return EcoData.Crops.Crops;
			}
			List<CropDef> list2 = new List<CropDef>();
			foreach (string item in list)
			{
				CropDef cropDef = FindCrop(item);
				if (cropDef != null && !list2.Contains(cropDef))
				{
					list2.Add(cropDef);
				}
			}
			return list2;
		}

		public static bool TierIs(string tier, string expected)
		{
			return string.Equals(tier ?? "", expected, StringComparison.OrdinalIgnoreCase);
		}
	}
	public static class EcologyRuntime
	{
		private static readonly Dictionary<int, LevelBase> LevelBases = new Dictionary<int, LevelBase>();

		private static readonly Dictionary<SpawnableEnemyWithRarity, int> RarityBases = new Dictionary<SpawnableEnemyWithRarity, int>();

		private static readonly HashSet<SpawnableEnemyWithRarity> IntroducedItems = new HashSet<SpawnableEnemyWithRarity>();

		private static readonly HashSet<int> CountedDeaths = new HashSet<int>();

		public static EcologySave Data { get; private set; }

		public static bool Loaded { get; private set; }

		public static void Initialize()
		{
			EcoCatalog.Load();
			EnsureLoaded();
		}

		public static void EnsureLoaded()
		{
			string text = EcologyStore.ResolveSaveName();
			if (!Loaded || EcologyStore.CurrentSaveName != text)
			{
				Data = EcologyStore.Load(text);
				Loaded = true;
				EcoCatalog.EnsureRuntime();
			}
			if (Data == null)
			{
				Data = new EcologySave();
			}
			if (Data.Moons == null)
			{
				Data.Moons = new Dictionary<string, MoonState>();
			}
			if (Data.Hold == null)
			{
				Data.Hold = new HoldState();
			}
			if (Data.Hold.Specimens == null)
			{
				Data.Hold.Specimens = new List<SpecimenState>();
			}
			if (Plugin.HoldPens != null)
			{
				Data.Hold.Capacity = Mathf.Clamp(Plugin.HoldPens.Value, 1, 8);
			}
		}

		public static void Save()
		{
			EnsureLoaded();
			EcologyStore.Save(Data);
		}

		public static void ResetAll()
		{
			string text = EcologyStore.CurrentSaveName;
			if (string.IsNullOrWhiteSpace(text))
			{
				text = EcologyStore.ResolveSaveName();
			}
			Data = new EcologySave
			{
				SaveName = text
			};
			Loaded = true;
			CountedDeaths.Clear();
			SpecimenHold.Clear();
			CultivationVisuals.Clear();
			TerritoryAnchors.Clear();
			EcologyStore.Save(Data);
		}

		public static string LevelKey(SelectableLevel level)
		{
			if ((Object)(object)level == (Object)null)
			{
				return "unknown";
			}
			return level.levelID + "|" + EcoCatalog.Normalize(level.PlanetName);
		}

		public static MoonState GetOrCreate(SelectableLevel level)
		{
			EnsureLoaded();
			if ((Object)(object)level == (Object)null)
			{
				return null;
			}
			string key = LevelKey(level);
			if (!Data.Moons.TryGetValue(key, out var value))
			{
				value = new MoonState
				{
					LevelId = level.levelID,
					LevelName = level.PlanetName
				};
				Data.Moons[key] = value;
			}
			if (value.Species == null)
			{
				value.Species = new Dictionary<string, SpeciesState>();
			}
			if (value.Territories == null)
			{
				value.Territories = new Dictionary<string, TerritoryState>();
			}
			if (value.Crops == null)
			{
				value.Crops = new Dictionary<string, CropState>();
			}
			if (value.Log == null)
			{
				value.Log = new List<string>();
			}
			if (!value.Initialized)
			{
				EcoCatalog.EnsureRuntime();
				List<SpeciesRuntime> list = EcoCatalog.SpeciesForLevel(level);
				if (list.Count > 0 || EcoCatalog.Runtime.Count > 0)
				{
					InitializeMoon(value, level, list);
				}
			}
			return value;
		}

		public static MoonState CurrentMoon()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null)
			{
				return null;
			}
			return GetOrCreate(instance.currentLevel);
		}

		private static void InitializeMoon(MoonState state, SelectableLevel level, List<SpeciesRuntime> pool)
		{
			MoonDef moonDef = EcoCatalog.MoonForLevelOrFallback(level);
			state.LevelName = level.PlanetName;
			state.LevelId = level.levelID;
			state.Food = Mathf.Clamp(moonDef.StartingFood, 0f, 100f);
			state.Stability = Mathf.Clamp(moonDef.StartingStability, 0, 3);
			state.Seed = Math.Abs((level.PlanetName ?? "moon").GetHashCode()) + level.levelID * 7919;
			state.Cycle = 0;
			state.Visits = 0;
			state.Species.Clear();
			state.Territories.Clear();
			state.Crops.Clear();
			state.Log.Clear();
			foreach (SpeciesRuntime item in pool)
			{
				state.Species[item.Key] = new SpeciesState
				{
					Key = item.Key,
					Name = item.Name,
					Population = InitialPopulation(item),
					Present = true,
					Introduced = false,
					Health = 100f
				};
			}
			bool flag = pool.Any((SpeciesRuntime x) => x.Key == EcoCatalog.Normalize("Old Bird"));
			bool flag2 = pool.Any((SpeciesRuntime x) => x.Key == EcoCatalog.Normalize("Earth Leviathan"));
			int num = moonDef.Relays;
			int num2 = moonDef.Burrows;
			if (flag && num <= 0)
			{
				num = 1;
			}
			if (flag2 && num2 <= 0)
			{
				num2 = 1;
			}
			for (int num3 = 0; num3 < num; num3++)
			{
				string text = "relay" + (num3 + 1);
				state.Territories[text] = new TerritoryState
				{
					Id = text,
					Type = "Relay",
					ResidentKey = (flag ? EcoCatalog.Normalize("Old Bird") : ""),
					ResidentName = (flag ? "Old Bird" : ""),
					Occupied = flag,
					PowerCost = ((Plugin.ApexPower != null) ? Plugin.ApexPower.Value : 12f),
					ReclaimIn = 0
				};
			}
			for (int num4 = 0; num4 < num2; num4++)
			{
				string text2 = "burrow" + (num4 + 1);
				state.Territories[text2] = new TerritoryState
				{
					Id = text2,
					Type = "Burrow",
					ResidentKey = (flag2 ? EcoCatalog.Normalize("Earth Leviathan") : ""),
					ResidentName = (flag2 ? "Earth Leviathan" : ""),
					Occupied = flag2,
					PowerCost = ((Plugin.ApexPower != null) ? Plugin.ApexPower.Value : 12f),
					ReclaimIn = 0
				};
			}
			state.Initialized = true;
			AddLog(state, "Ecology survey initialized.");
			Save();
		}

		private static float InitialPopulation(SpeciesRuntime rt)
		{
			float num = (rt.Def.Tier ?? "").ToLowerInvariant() switch
			{
				"apex" => 34f, 
				"secondary" => 42f, 
				"parasite" => 24f, 
				"anomaly" => 18f, 
				"producer" => 58f, 
				_ => 48f, 
			};
			if (rt.Key == EcoCatalog.Normalize("Old Bird") || rt.Key == EcoCatalog.Normalize("Earth Leviathan"))
			{
				num = 40f;
			}
			if (rt.Daytime)
			{
				num += 5f;
			}
			return Mathf.Clamp(num, 8f, 80f);
		}

		public static void OnArriveAtLevel()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || !((NetworkBehaviour)instance).IsServer || (Object)(object)instance.currentLevel == (Object)null)
			{
				return;
			}
			EnsureLoaded();
			MoonState orCreate = GetOrCreate(instance.currentLevel);
			if (orCreate != null)
			{
				orCreate.Visits++;
				orCreate.Cycle++;
				if (orCreate.Visits > 1)
				{
					EcoSim.Advance(orCreate, instance.currentLevel);
					AddLog(orCreate, "Cycle " + orCreate.Cycle + ": field survey updated.");
				}
				else
				{
					AddLog(orCreate, "First survey of " + orCreate.LevelName + ".");
				}
				ApplyToLevel(instance.currentLevel);
				Save();
			}
		}

		public static void OnLevelChanged()
		{
			StartOfRound instance = StartOfRound.Instance;
			if (!((Object)(object)instance == (Object)null) && ((NetworkBehaviour)instance).IsServer && !((Object)(object)instance.currentLevel == (Object)null))
			{
				EnsureLoaded();
				if (GetOrCreate(instance.currentLevel) != null)
				{
					ApplyToLevel(instance.currentLevel);
					Save();
				}
			}
		}

		public static LevelBase GetLevelBase(SelectableLevel level)
		{
			if ((Object)(object)level == (Object)null)
			{
				return new LevelBase();
			}
			int instanceID = ((Object)level).GetInstanceID();
			if (!LevelBases.TryGetValue(instanceID, out var value))
			{
				value = new LevelBase
				{
					MaxInside = level.maxEnemyPowerCount,
					MaxOutside = level.maxOutsideEnemyPowerCount,
					MaxDaytime = level.maxDaytimeEnemyPowerCount
				};
				LevelBases[instanceID] = value;
			}
			return value;
		}

		public static BudgetResult ComputeBudget(SelectableLevel level, MoonState state)
		{
			LevelBase levelBase = GetLevelBase(level);
			if (state == null)
			{
				state = new MoonState();
			}
			BudgetResult budgetResult = new BudgetResult
			{
				OutsideBase = levelBase.MaxOutside,
				InsideBase = levelBase.MaxInside,
				DaytimeBase = levelBase.MaxDaytime,
				Food = Mathf.RoundToInt(state.Food),
				Stability = state.Stability
			};
			budgetResult.FoodModifier = Mathf.Clamp(Mathf.FloorToInt((state.Food - 50f) / 10f), -5, 10);
			budgetResult.StabilityModifier = StabilityModifier(state.Stability);
			if (state.Territories != null)
			{
				budgetResult.ApexPower = Mathf.RoundToInt(state.Territories.Values.Where((TerritoryState t) => !t.Destroyed && t.Occupied).Sum((TerritoryState t) => Mathf.Max(0f, t.PowerCost)));
			}
			budgetResult.OutsideBudget = Mathf.Max(0, budgetResult.OutsideBase + budgetResult.FoodModifier + budgetResult.StabilityModifier - budgetResult.ApexPower);
			budgetResult.InsideBudget = Mathf.Max(0, budgetResult.InsideBase + budgetResult.FoodModifier + budgetResult.StabilityModifier);
			budgetResult.DaytimeBudget = Mathf.Max(0, budgetResult.DaytimeBase + budgetResult.FoodModifier + budgetResult.StabilityModifier);
			return budgetResult;
		}

		public static BudgetResult CurrentBudget()
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null)
			{
				return new BudgetResult
				{
					Food = 55,
					Stability = 2
				};
			}
			return ComputeBudget(instance.currentLevel, GetOrCreate(instance.currentLevel));
		}

		public static BudgetResult BudgetFor(SelectableLevel level)
		{
			if ((Object)(object)level == (Object)null)
			{
				return new BudgetResult
				{
					Food = 55,
					Stability = 2
				};
			}
			return ComputeBudget(level, GetOrCreate(level));
		}

		public static int StabilityModifier(int stability)
		{
			return stability switch
			{
				0 => -4, 
				1 => -2, 
				2 => 0, 
				_ => 2, 
			};
		}

		public static string StabilityName(int stability)
		{
			return stability switch
			{
				0 => "Collapsing", 
				1 => "Stressed", 
				2 => "Stable", 
				_ => "Thriving", 
			};
		}

		public static int SpawnCap(MoonState state, SpeciesRuntime rt)
		{
			if (state == null || rt == null)
			{
				return 0;
			}
			if (!state.Species.TryGetValue(rt.Key, out var value))
			{
				return 0;
			}
			if (!value.Present || value.Population < 10f)
			{
				return 0;
			}
			string territoryType = TerritoryType(rt.Key);
			if (territoryType != null)
			{
				int num = state.Territories.Values.Count((TerritoryState t) => !t.Destroyed && t.Occupied && t.Type == territoryType && string.Equals(t.ResidentKey, rt.Key, StringComparison.OrdinalIgnoreCase));
				return Mathf.Max(0, num);
			}
			return Mathf.Clamp(Mathf.CeilToInt(value.Population / 20f), 0, Mathf.Max(1, rt.OriginalMaxCount));
		}

		public static string TerritoryType(string speciesKey)
		{
			if (speciesKey == EcoCatalog.Normalize("Old Bird"))
			{
				return "Relay";
			}
			if (speciesKey == EcoCatalog.Normalize("Earth Leviathan"))
			{
				return "Burrow";
			}
			return null;
		}

		public static void ApplyToLevel(SelectableLevel level)
		{
			if ((Object)(object)level == (Object)null)
			{
				return;
			}
			EcoCatalog.EnsureRuntime();
			MoonState orCreate = GetOrCreate(level);
			if (orCreate == null)
			{
				return;
			}
			EnsureStateForPool(level, orCreate);
			EnsureIntroducedPools(level, orCreate);
			LevelBase levelBase = GetLevelBase(level);
			BudgetResult budgetResult = ComputeBudget(level, orCreate);
			if (Plugin.Profile == null || Plugin.Profile.Value != EcoProfile.FullEcology)
			{
				level.maxEnemyPowerCount = levelBase.MaxInside;
				level.maxOutsideEnemyPowerCount = levelBase.MaxOutside;
				level.maxDaytimeEnemyPowerCount = levelBase.MaxDaytime;
				RestoreCanonLists(level);
				return;
			}
			level.maxEnemyPowerCount = budgetResult.InsideBudget;
			level.maxOutsideEnemyPowerCount = budgetResult.OutsideBudget;
			level.maxDaytimeEnemyPowerCount = budgetResult.DaytimeBudget;
			ApplyList(level.Enemies, orCreate);
			ApplyList(level.OutsideEnemies, orCreate);
			ApplyList(level.DaytimeEnemies, orCreate);
			ApplyTypeFields(level, orCreate);
			RoundManager instance = RoundManager.Instance;
			if ((Object)(object)instance != (Object)null && ((NetworkBehaviour)instance).IsServer)
			{
				instance.currentMaxInsidePower = budgetResult.InsideBudget;
				instance.currentMaxOutsidePower = budgetResult.OutsideBudget;
			}
		}

		private static void EnsureStateForPool(SelectableLevel level, MoonState state)
		{
			foreach (SpeciesRuntime item in EcoCatalog.SpeciesForLevel(level))
			{
				if (!state.Species.ContainsKey(item.Key))
				{
					state.Species[item.Key] = new SpeciesState
					{
						Key = item.Key,
						Name = item.Name,
						Population = InitialPopulation(item),
						Present = true
					};
				}
			}
		}

		private static void EnsureIntroducedPools(SelectableLevel level, MoonState state)
		{
			foreach (SpeciesState value in state.Species.Values)
			{
				if (value.Introduced && value.Present && !(value.Population <= 0f))
				{
					SpeciesRuntime runtime = EcoCatalog.GetRuntime(value.Key);
					if (runtime != null && !IsInLevel(level, value.Key))
					{
						AddToPool(level, runtime);
					}
				}
			}
		}

		private static bool IsInLevel(SelectableLevel level, string key)
		{
			return EcoCatalog.SpeciesForLevel(level).Any((SpeciesRuntime x) => x.Key == key);
		}

		private static void AddToPool(SelectableLevel level, SpeciesRuntime rt)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			SpawnableEnemyWithRarity item = new SpawnableEnemyWithRarity(rt.Type, 1);
			if (rt.Outside)
			{
				if (level.OutsideEnemies == null)
				{
					level.OutsideEnemies = new List<SpawnableEnemyWithRarity>();
				}
				level.OutsideEnemies.Add(item);
			}
			else if (rt.Daytime)
			{
				if (level.DaytimeEnemies == null)
				{
					level.DaytimeEnemies = new List<SpawnableEnemyWithRarity>();
				}
				level.DaytimeEnemies.Add(item);
			}
			else
			{
				if (level.Enemies == null)
				{
					level.Enemies = new List<SpawnableEnemyWithRarity>();
				}
				level.Enemies.Add(item);
			}
			IntroducedItems.Add(item);
		}

		private static void ApplyList(List<SpawnableEnemyWithRarity> list, MoonState state)
		{
			if (list == null)
			{
				return;
			}
			foreach (SpawnableEnemyWithRarity item in list)
			{
				if (item == null || (Object)(object)item.enemyType == (Object)null)
				{
					continue;
				}
				string text = EcoCatalog.Normalize(item.enemyType.enemyName);
				SpeciesRuntime runtime = EcoCatalog.GetRuntime(text);
				if (runtime != null)
				{
					if (!state.Species.TryGetValue(text, out var value))
					{
						value = new SpeciesState
						{
							Key = text,
							Name = runtime.Name,
							Population = 20f,
							Present = false
						};
						state.Species[text] = value;
					}
					if (!RarityBases.ContainsKey(item))
					{
						RarityBases[item] = item.rarity;
					}
					int num = (value.LastCap = SpawnCap(state, runtime));
					bool flag = value.Present && value.Population > 0f && num > 0;
					item.rarity = (flag ? RarityFor(value, runtime, num) : 0);
				}
			}
		}

		private static void ApplyTypeFields(SelectableLevel level, MoonState state)
		{
			HashSet<EnemyType> hashSet = new HashSet<EnemyType>();
			foreach (SpeciesRuntime item in EcoCatalog.SpeciesForLevel(level))
			{
				if (!((Object)(object)item.Type == (Object)null) && hashSet.Add(item.Type))
				{
					if (!state.Species.TryGetValue(item.Key, out var value))
					{
						value = new SpeciesState
						{
							Key = item.Key,
							Name = item.Name,
							Population = 0f,
							Present = false
						};
						state.Species[item.Key] = value;
					}
					int num = SpawnCap(state, item);
					if (!value.Present || !(value.Population > 0f) || num <= 0)
					{
						item.Type.MaxCount = 0;
						item.Type.spawningDisabled = true;
					}
					else
					{
						int maxCount = ((TerritoryType(item.Key) != null) ? Mathf.Clamp(num, 1, 3) : Mathf.Clamp(num, 1, Mathf.Max(1, item.OriginalMaxCount)));
						item.Type.MaxCount = maxCount;
						item.Type.spawningDisabled = false;
					}
				}
			}
		}

		private static int RarityFor(SpeciesState species, SpeciesRuntime rt, int cap)
		{
			if (cap <= 0)
			{
				return 0;
			}
			if (TerritoryType(rt.Key) != null)
			{
				return 200;
			}
			return Mathf.Clamp(Mathf.RoundToInt(species.Population * 1.2f), 1, 240);
		}

		private static void RestoreCanonLists(SelectableLevel level)
		{
			RemoveIntroduced(level.Enemies);
			RemoveIntroduced(level.OutsideEnemies);
			RemoveIntroduced(level.DaytimeEnemies);
			RestoreList(level.Enemies);
			RestoreList(level.OutsideEnemies);
			RestoreList(level.DaytimeEnemies);
		}

		private static void RemoveIntroduced(List<SpawnableEnemyWithRarity> list)
		{
			list?.RemoveAll((SpawnableEnemyWithRarity item) => item != null && IntroducedItems.Contains(item));
		}

		private static void RestoreList(List<SpawnableEnemyWithRarity> list)
		{
			if (list == null)
			{
				return;
			}
			foreach (SpawnableEnemyWithRarity item in list)
			{
				if (item == null)
				{
					continue;
				}
				if (!RarityBases.ContainsKey(item))
				{
					RarityBases[item] = item.rarity;
				}
				item.rarity = RarityBases[item];
				if ((Object)(object)item.enemyType != (Object)null)
				{
					SpeciesRuntime runtime = EcoCatalog.GetRuntime(item.enemyType.enemyName);
					if (runtime != null)
					{
						item.enemyType.MaxCount = runtime.OriginalMaxCount;
						item.enemyType.spawningDisabled = runtime.OriginalSpawningDisabled;
					}
				}
			}
		}

		public static void RefreshRuntimeCaps()
		{
			StartOfRound instance = StartOfRound.Instance;
			if (!((Object)(object)instance == (Object)null) && ((NetworkBehaviour)instance).IsServer && !((Object)(object)instance.currentLevel == (Object)null) && Plugin.Profile != null && Plugin.Profile.Value == EcoProfile.FullEcology)
			{
				BudgetResult budgetResult = BudgetFor(instance.currentLevel);
				RoundManager instance2 = RoundManager.Instance;
				if ((Object)(object)instance2 != (Object)null)
				{
					instance2.currentMaxInsidePower = budgetResult.InsideBudget;
					instance2.currentMaxOutsidePower = budgetResult.OutsideBudget;
				}
			}
		}

		public static SpeciesState GetSpeciesState(MoonState state, string keyOrName)
		{
			if (state == null)
			{
				return null;
			}
			string text = EcoCatalog.Normalize(keyOrName);
			if (state.Species.TryGetValue(text, out var value))
			{
				return value;
			}
			foreach (SpeciesState value2 in state.Species.Values)
			{
				if (EcoCatalog.Normalize(value2.Name) == text)
				{
					return value2;
				}
				if (EcoCatalog.Normalize(value2.Key) == text)
				{
					return value2;
				}
			}
			return null;
		}

		public static void AddLog(MoonState state, string text)
		{
			if (state != null)
			{
				if (state.Log == null)
				{
					state.Log = new List<string>();
				}
				state.Log.Add("[" + DateTime.Now.ToString("HH:mm") + "] " + text);
				while (state.Log.Count > 24)
				{
					state.Log.RemoveAt(0);
				}
			}
		}

		public static void OnEnemyKilled(EnemyAI enemy)
		{
			if ((Object)(object)enemy == (Object)null || (Object)(object)enemy.enemyType == (Object)null || !((NetworkBehaviour)enemy).IsServer)
			{
				return;
			}
			int instanceID = ((Object)enemy).GetInstanceID();
			if (!CountedDeaths.Add(instanceID))
			{
				return;
			}
			EnsureLoaded();
			SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			MoonState orCreate = GetOrCreate(val);
			SpeciesRuntime rt = EcoCatalog.GetRuntime(enemy.enemyType.enemyName);
			if (rt == null || !orCreate.Species.TryGetValue(rt.Key, out var value))
			{
				return;
			}
			string territoryType = TerritoryType(rt.Key);
			if (territoryType != null)
			{
				TerritoryState territoryState = orCreate.Territories.Values.FirstOrDefault((TerritoryState t) => !t.Destroyed && t.Occupied && t.Type == territoryType && string.Equals(t.ResidentKey, rt.Key, StringComparison.OrdinalIgnoreCase));
				if (territoryState != null)
				{
					territoryState.Occupied = false;
					territoryState.ResidentKey = "";
					territoryState.ResidentName = "";
					territoryState.ReclaimIn = 3;
				}
			}
			float num = Mathf.Clamp(100f / (float)Mathf.Max(1, rt.OriginalMaxCount), 6f, 25f);
			value.Population = Mathf.Max(0f, value.Population - num);
			value.Killed++;
			AddLog(orCreate, rt.Name + " killed. Population -" + Mathf.RoundToInt(num) + ".");
			Save();
		}

		public static bool TryCapture(EnemyAI enemy, out string message)
		{
			message = "";
			if ((Object)(object)enemy == (Object)null || (Object)(object)enemy.enemyType == (Object)null)
			{
				message = "No creature in range.";
				return false;
			}
			EnsureLoaded();
			SpeciesRuntime rt = EcoCatalog.GetRuntime(enemy.enemyType.enemyName);
			if (rt == null)
			{
				message = "Unknown species.";
				return false;
			}
			if (!rt.Def.Capturable || rt.Def.PenCost <= 0)
			{
				message = rt.Name + " cannot be captured alive.";
				return false;
			}
			HoldState hold = Data.Hold;
			hold.Capacity = ((Plugin.HoldPens != null) ? Mathf.Clamp(Plugin.HoldPens.Value, 1, 8) : 4);
			int num = hold.Specimens.Sum((SpecimenState s) => s.PenCost);
			if (num + rt.Def.PenCost > hold.Capacity)
			{
				message = "Not enough pen space. " + rt.Name + " needs " + rt.Def.PenCost + " pen(s); " + (hold.Capacity - num) + " free.";
				return false;
			}
			SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			MoonState moonState = (((Object)(object)val != (Object)null) ? GetOrCreate(val) : null);
			if (moonState != null && moonState.Species.TryGetValue(rt.Key, out var value))
			{
				string territoryType = TerritoryType(rt.Key);
				if (territoryType != null)
				{
					TerritoryState territoryState = moonState.Territories.Values.FirstOrDefault((TerritoryState t) => !t.Destroyed && t.Occupied && t.Type == territoryType && string.Equals(t.ResidentKey, rt.Key, StringComparison.OrdinalIgnoreCase));
					if (territoryState != null)
					{
						territoryState.Occupied = false;
						territoryState.ResidentKey = "";
						territoryState.ResidentName = "";
						territoryState.ReclaimIn = 3;
					}
				}
				value.Population = Mathf.Max(0f, value.Population - Mathf.Clamp(100f / (float)Mathf.Max(1, rt.OriginalMaxCount), 5f, 20f));
				value.Captured++;
			}
			SpecimenState specimenState = new SpecimenState
			{
				Id = "spec_" + Guid.NewGuid().ToString("N").Substring(0, 8),
				Key = rt.Key,
				Name = rt.Name,
				PenCost = rt.Def.PenCost,
				Health = 100f,
				Stress = 12f,
				CapturedCycle = (moonState?.Cycle ?? Data.Cycle),
				SourceMoon = (((Object)(object)val != (Object)null) ? val.PlanetName : "unknown"),
				Juvenile = (rt.Key == EcoCatalog.Normalize("Giant Sapsucker") && Random.value < 0.35f),
				Growth = 0f,
				Hive = (rt.Key == EcoCatalog.Normalize("Circuit Bee")),
				Pen = NextPen(hold, rt.Def.PenCost)
			};
			hold.Specimens.Add(specimenState);
			if (moonState != null)
			{
				AddLog(moonState, rt.Name + " captured and stored in pen " + specimenState.Pen + ".");
			}
			Save();
			message = rt.Name + " captured. Pen " + specimenState.Pen + ".";
			return true;
		}

		private static int NextPen(HoldState hold, int cost)
		{
			HashSet<int> hashSet = new HashSet<int>();
			foreach (SpecimenState specimen in hold.Specimens)
			{
				for (int i = 0; i < Mathf.Max(1, specimen.PenCost); i++)
				{
					hashSet.Add(specimen.Pen + i);
				}
			}
			cost = Mathf.Max(1, cost);
			for (int j = 1; j + cost - 1 <= hold.Capacity; j++)
			{
				bool flag = true;
				for (int k = 0; k < cost; k++)
				{
					if (hashSet.Contains(j + k))
					{
						flag = false;
						break;
					}
				}
				if (flag)
				{
					return j;
				}
			}
			return Mathf.Max(1, hold.Capacity);
		}

		public static bool Release(int pen, out string message)
		{
			message = "";
			EnsureLoaded();
			HoldState hold = Data.Hold;
			SpecimenState specimenState = hold.Specimens.FirstOrDefault((SpecimenState s) => s.Pen == pen);
			if (specimenState == null)
			{
				specimenState = hold.Specimens.FirstOrDefault((SpecimenState s) => s.Id == pen.ToString());
			}
			if (specimenState == null)
			{
				message = "No specimen in pen " + pen + ".";
				return false;
			}
			SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			if ((Object)(object)val == (Object)null)
			{
				message = "You must be on a moon to release a specimen.";
				return false;
			}
			SpeciesRuntime runtime = EcoCatalog.GetRuntime(specimenState.Key);
			if (runtime == null || (Object)(object)runtime.Type == (Object)null)
			{
				message = "The species record is missing; release aborted.";
				return false;
			}
			MoonState orCreate = GetOrCreate(val);
			bool flag = IsInLevel(val, specimenState.Key);
			if (!orCreate.Species.TryGetValue(specimenState.Key, out var value))
			{
				value = new SpeciesState
				{
					Key = specimenState.Key,
					Name = specimenState.Name,
					Population = 0f,
					Present = true,
					Introduced = !flag
				};
				orCreate.Species[specimenState.Key] = value;
			}
			string territoryType = TerritoryType(specimenState.Key);
			TerritoryState territoryState = null;
			if (territoryType != null)
			{
				territoryState = orCreate.Territories.Values.FirstOrDefault((TerritoryState t) => !t.Destroyed && !t.Occupied && t.Type == territoryType);
				if (territoryState == null)
				{
					message = "No empty " + ((territoryType == "Relay") ? "Relay Spire" : "Burrow") + " is available on " + val.PlanetName + ". Build or clear one first.";
					return false;
				}
			}
			if (!flag)
			{
				AddToPool(val, runtime);
			}
			bool flag2 = orCreate.Food < 25f && Random.value < 0.55f;
			value.Present = true;
			if (!flag2)
			{
				value.Population = Mathf.Clamp(value.Population + ((territoryState != null) ? 35f : 18f), 0f, 100f);
				if (!flag)
				{
					value.Introduced = true;
				}
				if (territoryState != null)
				{
					territoryState.Occupied = true;
					territoryState.ResidentKey = specimenState.Key;
					territoryState.ResidentName = specimenState.Name;
					territoryState.ReclaimIn = 0;
				}
			}
			else
			{
				value.Population = Mathf.Max(0f, value.Population - 5f);
				if (territoryState != null)
				{
					territoryState.Occupied = false;
					territoryState.ResidentKey = "";
					territoryState.ResidentName = "";
					territoryState.ReclaimIn = 0;
				}
			}
			hold.Specimens.Remove(specimenState);
			AddLog(orCreate, specimenState.Name + (flag2 ? " release failed; the habitat rejected it." : (" released from pen " + pen + ".")));
			ApplyToLevel(val);
			Save();
			message = (flag2 ? (specimenState.Name + " released, but the habitat rejected it. Food is too low.") : (specimenState.Name + " released into " + val.PlanetName + "."));
			return true;
		}
	}
	public static class EcoSim
	{
		public static void Advance(MoonState state, SelectableLevel level)
		{
			if (state == null)
			{
				return;
			}
			Random random = new Random(state.Seed + state.Cycle * 7919 + state.Visits * 104729);
			EcoCatalog.MoonForLevelOrFallback(level);
			float num = 0f;
			float num2 = 0f;
			float num3 = 0f;
			bool pollinated = HasPollinator(state);
			foreach (CropState item in state.Crops.Values.ToList())
			{
				if (item.Amount <= 0)
				{
					state.Crops.Remove(item.Key);
					continue;
				}
				CropDef cropDef = EcoCatalog.FindCrop(item.Name);
				if (cropDef == null)
				{
					continue;
				}
				if (EcoCatalog.Normalize(cropDef.Name) == EcoCatalog.Normalize("Nectar Bloom"))
				{
					item.Pollinated = pollinated;
				}
				float num4 = ((state.Stability >= 2) ? 2f : (-2f));
				if (item.Pollinated)
				{
					num4 += 1f;
				}
				if (HasSpecies(state, "Spore Lizard", 10f) && EcoCatalog.Normalize(cropDef.Name) == EcoCatalog.Normalize("Glowcap"))
				{
					num4 += 2f;
				}
				item.Health = Mathf.Clamp(item.Health + num4, 0f, 100f);
				float num5 = 0f;
				if (EcoCatalog.Normalize(cropDef.Name) == EcoCatalog.Normalize("Carrion Bloom"))
				{
					num5 += 3.5f;
				}
				if (EcoCatalog.Normalize(cropDef.Name) == EcoCatalog.Normalize("Nectar Bloom"))
				{
					num5 += 0.4f;
				}
				if (HasSpecies(state, "Vain Shroud", 10f))
				{
					num5 += 1.5f;
				}
				if (HasSpecies(state, "Cadaver Bloom", 10f))
				{
					num5 += 1f;
				}
				if (EcoCatalog.Normalize(cropDef.Name) == EcoCatalog.Normalize("Glowcap"))
				{
					num5 -= 2f;
				}
				if (HasSpecies(state, "Spore Lizard", 10f))
				{
					num5 -= 1f;
				}
				if (state.Stability <= 1)
				{
					num5 += 1f;
				}
				item.Blight = Mathf.Clamp(item.Blight + num5 + (float)(random.NextDouble() * 0.8 - 0.4), 0f, 100f);
				if (item.Blight > 60f)
				{
					item.Health = Mathf.Max(0f, item.Health - 4f);
				}
				if (item.Blight > 85f)
				{
					item.Amount = 0;
				}
				if (item.Amount <= 0)
				{
					state.Crops.Remove(item.Key);
					continue;
				}
				if (state.Cycle > item.PlantedCycle && item.Amount < 3 && item.Health >= 70f && item.Blight < 40f && random.NextDouble() < 0.35)
				{
					item.Amount++;
					EcologyRuntime.AddLog(state, item.Name + " spread to patch " + item.Amount + ".");
				}
				float num6 = cropDef.FoodPerCycle * (float)item.Amount * (item.Pollinated ? 1f : 0.5f) * (item.Health / 100f);
				num2 += num6;
				num3 += cropDef.StabilityPerCycle * (item.Health / 100f);
				num += item.Blight;
			}
			float num7 = 0f;
			float num8 = 2.6f;
			foreach (SpeciesState value2 in state.Species.Values)
			{
				if (value2.Present && !(value2.Population <= 0f))
				{
					string tier = TierOf(state, value2.Key);
					if (EcoCatalog.TierIs(tier, "Producer"))
					{
						num8 += value2.Population * 0.026f;
					}
					else if (EcoCatalog.TierIs(tier, "Apex"))
					{
						num7 += value2.Population * 0.004f;
					}
					else if (!EcoCatalog.TierIs(tier, "Anomaly"))
					{
						num7 += value2.Population * 0.009f;
					}
				}
			}
			state.Food = Mathf.Clamp(state.Food + num8 + num2 - num7 + (float)(random.NextDouble() * 1.6 - 0.8), 0f, 100f);
			foreach (SpeciesState item2 in state.Species.Values.ToList())
			{
				SpeciesRuntime rt = EcoCatalog.GetRuntime(item2.Key);
				if (rt == null)
				{
					continue;
				}
				string text = (rt.Def.Tier ?? "").ToLowerInvariant();
				float num9 = AveragePopulation(state, rt.Def.Preys);
				float num10 = AveragePopulation(state, rt.Def.Predators);
				float num11 = AverageHostPopulation(state, rt.Def.Hosts);
				float num12 = (state.Food - 48f) / 18f;
				float num13 = 0f;
				num13 = text switch
				{
					"producer" => num12 * 0.9f + 0.2f, 
					"primary" => num12 * 0.8f - num10 / 60f + 0.1f, 
					"secondary" => (num9 - 40f) / 22f - num10 / 70f, 
					"apex" => (num9 - 45f) / 24f + 0.05f, 
					"parasite" => (num11 - 35f) / 24f, 
					"scavenger" => num12 * 0.5f, 
					"anomaly" => 0f, 
					_ => num12 * 0.4f, 
				};
				if (rt.Outside && state.Food < 20f)
				{
					num13 -= 2f;
				}
				if (rt.Daytime && state.Food > 65f)
				{
					num13 += 0.5f;
				}
				num13 += (float)(random.NextDouble() * 2.4 - 1.2);
				string territoryType = EcologyRuntime.TerritoryType(rt.Key);
				if (territoryType != null)
				{
					int num14 = state.Territories.Values.Count((TerritoryState t) => !t.Destroyed && t.Occupied && t.Type == territoryType && string.Equals(t.ResidentKey, rt.Key, StringComparison.OrdinalIgnoreCase));
					if (num14 <= 0)
					{
						item2.Population = Mathf.Max(0f, item2.Population - 4f);
					}
					else
					{
						item2.Population = Mathf.Clamp((float)num14 * 32f + (float)random.NextDouble() * 8f, 20f, 100f);
					}
				}
				else
				{
					item2.Population = Mathf.Clamp(item2.Population + num13, 0f, 100f);
				}
				if (item2.Population < 1f)
				{
					item2.Population = 0f;
					if (state.Food > 35f && !EcoCatalog.TierIs(text, "anomaly") && random.NextDouble() < 0.07)
					{
						item2.Population = 5f;
						EcologyRuntime.AddLog(state, item2.Name + " immigrated back into the moon.");
					}
				}
				item2.Health = Mathf.Clamp(100f - Mathf.Abs(state.Food - 55f) * 0.4f, 10f, 100f);
			}
			foreach (TerritoryState territory in state.Territories.Values)
			{
				if (territory.Destroyed)
				{
					continue;
				}
				if (territory.Occupied)
				{
					SpeciesState speciesState = state.Species.Values.FirstOrDefault((SpeciesState s) => s.Key == territory.ResidentKey);
					if (speciesState == null || speciesState.Population <= 0f)
					{
						territory.Occupied = false;
						territory.ResidentKey = "";
						territory.ResidentName = "";
						territory.ReclaimIn = 3;
					}
				}
				else if (territory.ReclaimIn > 0)
				{
					territory.ReclaimIn--;
				}
				else if (state.Food >= 30f && state.Stability >= 1 && random.NextDouble() < 0.45)
				{
					string text2 = ((territory.Type == "Relay") ? "Old Bird" : "Earth Leviathan");
					string text3 = EcoCatalog.Normalize(text2);
					if (state.Species.TryGetValue(text3, out var value) && value.Present)
					{
						territory.Occupied = true;
						territory.ResidentKey = text3;
						territory.ResidentName = text2;
						EcologyRuntime.AddLog(state, text2 + " reclaimed " + TerritoryLabel(territory) + ".");
					}
				}
			}
			int num15 = state.Species.Values.Count((SpeciesState s) => s.Present && s.Population >= 10f);
			float num16 = state.Species.Values.Where((SpeciesState s) => s.Present && s.Population >= 10f).Sum(delegate(SpeciesState s)
			{
				SpeciesRuntime runtime = EcoCatalog.GetRuntime(s.Key);
				return (runtime == null || !EcoCatalog.TierIs(runtime.Def.Tier, "Apex")) ? 0f : Mathf.Clamp(runtime.Power, 1f, 12f);
			});
			float num17 = ((state.Crops.Count > 0) ? (num / (float)state.Crops.Count) : 0f);
			float num18 = state.Food * 0.55f + (float)num15 * 6f + num3 * 2f - num16 * 1.2f;
			if (num17 > 55f)
			{
				num18 -= 12f;
			}
			if (state.Food < 20f)
			{
				num18 = Mathf.Min(num18, 24f);
			}
			else if (state.Food < 40f)
			{
				num18 = Mathf.Min(num18, 49f);
			}
			int num19 = ((!(num18 < 30f)) ? ((num18 < 50f) ? 1 : ((!(num18 < 78f)) ? 3 : 2)) : 0);
			if (num19 != state.Stability)
			{
				EcologyRuntime.AddLog(state, "Stability changed from " + EcologyRuntime.StabilityName(state.Stability) + " to " + EcologyRuntime.StabilityName(num19) + ".");
				state.Stability = num19;
			}
			UpdateHold(state, random);
			if (state.Log.Count > 24)
			{
				state.Log.RemoveRange(0, state.Log.Count - 24);
			}
		}

		private static void UpdateHold(MoonState state, Random rng)
		{
			HoldState hold = EcologyRuntime.Data.Hold;
			if (hold == null || hold.Specimens == null)
			{
				return;
			}
			bool flag = hold.Specimens.Sum((SpecimenState s) => s.PenCost) > hold.Capacity;
			foreach (SpecimenState specimen in hold.Specimens)
			{
				float num = (flag ? 4f : (-1.5f));
				if (state.Food < 30f)
				{
					num += 2f;
				}
				specimen.Stress = Mathf.Clamp(specimen.Stress + num, 0f, 100f);
				if (specimen.Stress > 80f)
				{
					specimen.Health = Mathf.Max(0f, specimen.Health - 4f);
				}
				else
				{
					specimen.Health = Mathf.Min(100f, specimen.Health + 1f);
				}
				if (specimen.Juvenile)
				{
					SpeciesRuntime runtime = EcoCatalog.GetRuntime(specimen.Key);
					bool num2 = runtime != null && EcoCatalog.Normalize(runtime.Name) == EcoCatalog.Normalize("Giant Sapsucker");
					bool flag2 = state.Crops.Values.Any((CropState c) => EcoCatalog.Normalize(c.Name) == EcoCatalog.Normalize("Saproot"));
					if (!num2 || flag2 || state.Food >= 30f)
					{
						specimen.Growth = Mathf.Clamp(specimen.Growth + 10f, 0f, 100f);
					}
					if (specimen.Growth >= 100f)
					{
						specimen.Juvenile = false;
						specimen.Name = "Giant Sapsucker";
						specimen.PenCost = 3;
						EcologyRuntime.AddLog(state, "A juvenile sapsucker matured in the Specimen Hold.");
					}
				}
			}
			hold.Specimens.RemoveAll((SpecimenState s) => s.Health <= 0f);
		}

		private static bool HasPollinator(MoonState state)
		{
			if (!HasSpecies(state, "Circuit Bee", 10f) && !HasSpecies(state, "Roaming Locust", 10f))
			{
				return EcologyRuntime.Data.Hold.Specimens.Any((SpecimenState s) => s.Hive);
			}
			return true;
		}

		private static bool HasSpecies(MoonState state, string name, float minimum)
		{
			if (state == null)
			{
				return false;
			}
			string key = EcoCatalog.Normalize(name);
			if (state.Species.TryGetValue(key, out var value) && value.Present)
			{
				return value.Population >= minimum;
			}
			return false;
		}

		private static string TierOf(MoonState state, string key)
		{
			SpeciesRuntime runtime = EcoCatalog.GetRuntime(key);
			if (runtime == null)
			{
				return "Primary";
			}
			return runtime.Def.Tier;
		}

		private static float AveragePopulation(MoonState state, List<string> names)
		{
			if (names == null || names.Count == 0)
			{
				return state.Food * 0.55f;
			}
			float num = 0f;
			int num2 = 0;
			foreach (string name in names)
			{
				string key = EcoCatalog.Normalize(name);
				if (state.Species.TryGetValue(key, out var value) && value.Present)
				{
					num += value.Population;
					num2++;
				}
			}
			if (num2 != 0)
			{
				return num / (float)num2;
			}
			return state.Food * 0.5f;
		}

		private static float AverageHostPopulation(MoonState state, List<string> hosts)
		{
			if (hosts == null || hosts.Count == 0)
			{
				return 45f;
			}
			float num = 0f;
			int num2 = 0;
			foreach (string host in hosts)
			{
				string text = EcoCatalog.Normalize(host);
				SpeciesState value;
				if (text == EcoCatalog.Normalize("Players"))
				{
					num += 45f;
					num2++;
				}
				else if (state.Species.TryGetValue(text, out value))
				{
					num += value.Population;
					num2++;
				}
			}
			if (num2 != 0)
			{
				return num / (float)num2;
			}
			return 40f;
		}

		public static string TerritoryLabel(TerritoryState territory)
		{
			if (territory == null)
			{
				return "territory";
			}
			if (territory.Type == "Relay")
			{
				string text = territory.Id.Replace("relay", "");
				return "Relay Spire " + text;
			}
			string text2 = territory.Id.Replace("burrow", "");
			return "Burrow " + text2;
		}
	}
	public static class EcologyStore
	{
		private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings
		{
			Formatting = (Formatting)1,
			NullValueHandling = (NullValueHandling)0
		};

		public static string CurrentSaveName { get; private set; } = "";

		public static string CurrentPath { get; private set; } = "";

		public static string ResolveSaveName()
		{
			try
			{
				GameNetworkManager instance = GameNetworkManager.Instance;
				if ((Object)(object)instance != (Object)null && !string.IsNullOrWhiteSpace(instance.currentSaveFileName))
				{
					return instance.currentSaveFileName;
				}
			}
			catch
			{
			}
			return "LCSaveFile1";
		}

		public static EcologySave Load(string saveName = null)
		{
			if (string.IsNullOrWhiteSpace(saveName))
			{
				saveName = ResolveSaveName();
			}
			saveName = SafeName(saveName);
			string text = Path.Combine(Paths.ConfigPath, "LethalEcology", "saves");
			Directory.CreateDirectory(text);
			string text2 = Path.Combine(text, saveName + ".json");
			CurrentSaveName = saveName;
			CurrentPath = text2;
			if (!File.Exists(text2))
			{
				EcologySave obj = new EcologySave
				{
					SaveName = saveName
				};
				Save(obj);
				return obj;
			}
			try
			{
				EcologySave ecologySave = JsonConvert.DeserializeObject<EcologySave>(File.ReadAllText(text2), JsonSettings);
				if (ecologySave == null)
				{
					ecologySave = new EcologySave
					{
						SaveName = saveName
					};
				}
				ecologySave.SaveName = saveName;
				if (ecologySave.Moons == null)
				{
					ecologySave.Moons = new Dictionary<string, MoonState>();
				}
				if (ecologySave.Hold == null)
				{
					ecologySave.Hold = new HoldState();
				}
				if (ecologySave.Hold.Specimens == null)
				{
					ecologySave.Hold.Specimens = new List<SpecimenState>();
				}
				return ecologySave;
			}
			catch (Exception ex)
			{
				Debug.LogError((object)("[LethalEcology] Could not read ecology save, starting a fresh sidecar: " + ex));
				string destFileName = text2 + ".broken-" + DateTime.UtcNow.ToString("yyyyMMddHHmmss");
				try
				{
					File.Copy(text2, destFileName, overwrite: true);
				}
				catch
				{
				}
				EcologySave obj3 = new EcologySave
				{
					SaveName = saveName
				};
				Save(obj3);
				return obj3;
			}
		}

		public static void Save(EcologySave save)
		{
			if (save == null)
			{
				return;
			}
			if (string.IsNullOrWhiteSpace(CurrentPath))
			{
				CurrentSaveName = SafeName(ResolveSaveName());
				string text = Path.Combine(Paths.ConfigPath, "LethalEcology", "saves");
				Directory.CreateDirectory(text);
				CurrentPath = Path.Combine(text, CurrentSaveName + ".json");
			}
			try
			{
				save.UpdatedUtc = DateTime.UtcNow.ToString("o");
				string contents = JsonConvert.SerializeObject((object)save, JsonSettings);
				string text2 = CurrentPath + ".tmp";
				File.WriteAllText(text2, contents);
				if (File.Exists(CurrentPath))
				{
					File.Delete(CurrentPath);
				}
				File.Move(text2, CurrentPath);
			}
			catch (Exception ex)
			{
				Debug.LogError((object)("[LethalEcology] Could not write ecology save: " + ex));
			}
		}

		public static bool Reset()
		{
			try
			{
				if (File.Exists(CurrentPath))
				{
					File.Delete(CurrentPath);
				}
				return true;
			}
			catch (Exception ex)
			{
				Debug.LogError((object)("[LethalEcology] Could not delete ecology save: " + ex));
				return false;
			}
		}

		private static string SafeName(string value)
		{
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			foreach (char oldChar in invalidFileNameChars)
			{
				value = value.Replace(oldChar, '_');
			}
			if (!string.IsNullOrWhiteSpace(value))
			{
				return value;
			}
			return "LCSaveFile1";
		}
	}
	public static class EcologyText
	{
		private const int Width = 62;

		public static string Help()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "LETHAL ECOLOGY // COMMAND INDEX");
			stringBuilder.AppendLine("All commands are host-authoritative.");
			stringBuilder.AppendLine("Use ecoindex for every topic, ecofind for search.");
			stringBuilder.AppendLine();
			Command(stringBuilder, "ecohelp", "this command list");
			Command(stringBuilder, "ecoindex", "every mechanic, species, crop, and moon");
			Command(stringBuilder, "ecostatus", "current moon dashboard");
			Command(stringBuilder, "ecomoons", "all moons and their ecology");
			Command(stringBuilder, "ecomoon <moon>", "moon identity, budget, crops, apex");
			Command(stringBuilder, "ecospecies [name]", "species index or full species sheet");
			Command(stringBuilder, "ecoweb", "current moon food web");
			Command(stringBuilder, "ecobudget", "exact power budget math");
			Command(stringBuilder, "ecocensus", "populations, spawn caps, power");
			Command(stringBuilder, "ecoterritory", "Relay Spires and Burrows");
			Command(stringBuilder, "ecocultivation", "crop list and current crops");
			Command(stringBuilder, "ecocrop <name>", "crop requirements and steps");
			Command(stringBuilder, "ecoplant <name>", "buy and plant a crop on this moon");
			Command(stringBuilder, "ecocare <name>", "spend credits to treat blight");
			Command(stringBuilder, "ecoitems", "every mod tool, charge, and kit");
			Command(stringBuilder, "ecodestroy <anchor>", "buy a demolition charge and destroy an anchor");
			Command(stringBuilder, "ecohold", "Specimen Hold inventory");
			Command(stringBuilder, "ecohold enter", "enter the containment deck");
			Command(stringBuilder, "ecohold exit", "return to the ship");
			Command(stringBuilder, "ecorelease <pen>", "release a held specimen");
			Command(stringBuilder, "ecocapture", "capture instructions and keybind");
			Command(stringBuilder, "ecogear", "capture gear and pen costs");
			Command(stringBuilder, "ecomechanic <name>", "full mechanic explanation");
			Command(stringBuilder, "ecomanual", "quick-start manual");
			Command(stringBuilder, "ecocompat", "profiles, multiplayer, and data sheets");
			Command(stringBuilder, "ecolog [n]", "recent field log");
			Command(stringBuilder, "ecosave", "force a sidecar save");
			Command(stringBuilder, "ecoreset confirm", "wipe ecology data for this save");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Species, crops, and moons also answer to aliases.");
			stringBuilder.AppendLine("Example: ecospecies forest keeper");
			return stringBuilder.ToString();
		}

		public static string Index()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "LETHAL ECOLOGY // COMPLETE INDEX");
			stringBuilder.AppendLine("COMMANDS");
			stringBuilder.AppendLine("  ecohelp, ecoindex, ecofind <text>, ecostatus");
			stringBuilder.AppendLine("  ecomoons, ecomoon <moon>, ecospecies [name], ecoweb");
			stringBuilder.AppendLine("  ecobudget, ecocensus, ecoterritory, ecolog [n]");
			stringBuilder.AppendLine("  ecocultivation, ecocrop <name>, ecoplant <name>, ecocare <name>");
			stringBuilder.AppendLine("  ecoitems, ecoterritory, ecodestroy <anchor>");
			stringBuilder.AppendLine("  ecohold [enter|exit], ecorelease <pen>, ecocapture, ecogear");
			stringBuilder.AppendLine("  ecomechanic <name>, ecomanual, ecocompat, ecosave");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("MECHANICS");
			foreach (MechanicDef mechanic in EcoData.Mechanics.Mechanics)
			{
				stringBuilder.AppendLine("  " + mechanic.Name + " - " + mechanic.Summary);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("SPECIES (" + EcoCatalog.AllRuntimeOrdered().Count + " discovered, " + EcoData.Species.Species.Count + " authored)");
			foreach (IGrouping<string, SpeciesRuntime> item in from x in EcoCatalog.AllRuntimeOrdered()
				group x by x.Def.Tier)
			{
				stringBuilder.AppendLine("  " + item.Key + ": " + string.Join(", ", item.Select((SpeciesRuntime x) => x.Name).ToArray()));
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("CROPS");
			foreach (CropDef crop in EcoData.Crops.Crops)
			{
				stringBuilder.AppendLine("  " + crop.Name + " - " + crop.Role);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("MOONS");
			foreach (SelectableLevel item2 in EcoCatalog.AllLevels())
			{
				stringBuilder.AppendLine("  " + item2.PlanetName + " (level " + item2.levelID + ")");
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Use ecofind <text> to search every sheet.");
			return stringBuilder.ToString();
		}

		public static string Status()
		{
			StartOfRound instance = StartOfRound.Instance;
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "LETHAL ECOLOGY // CURRENT MOON");
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.currentLevel == (Object)null)
			{
				stringBuilder.AppendLine("No moon selected. Start or load a game first.");
				return stringBuilder.ToString();
			}
			SelectableLevel currentLevel = instance.currentLevel;
			MoonState orCreate = EcologyRuntime.GetOrCreate(currentLevel);
			MoonDef moonDef = EcoCatalog.MoonForLevelOrFallback(currentLevel);
			BudgetResult budgetResult = EcologyRuntime.ComputeBudget(currentLevel, orCreate);
			string text = ((Plugin.Profile != null) ? Plugin.Profile.Value.ToString() : "FullEcology");
			stringBuilder.AppendLine(currentLevel.PlanetName + "  //  risk " + Safe(currentLevel.riskLevel, "?") + "  //  level " + currentLevel.levelID);
			stringBuilder.AppendLine(moonDef.Identity + "  //  " + moonDef.Biome);
			stringBuilder.AppendLine("Profile: " + text);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("FOOD       " + Mathf.RoundToInt(orCreate.Food).ToString().PadLeft(3) + " / 100  " + Bar(orCreate.Food, 100f, 22));
			stringBuilder.AppendLine("STABILITY  " + EcologyRuntime.StabilityName(orCreate.Stability) + " (" + Signed(EcologyRuntime.StabilityModifier(orCreate.Stability)) + ")");
			stringBuilder.AppendLine("BUDGET     Outside " + budgetResult.OutsideBudget + "  |  Inside " + budgetResult.InsideBudget + "  |  Day " + budgetResult.DaytimeBudget);
			stringBuilder.AppendLine("           base " + budgetResult.OutsideBase + " + food " + Signed(budgetResult.FoodModifier) + " + stability " + Signed(budgetResult.StabilityModifier) + " - apex " + budgetResult.ApexPower);
			stringBuilder.AppendLine();
			int num = orCreate.Species.Values.Count((SpeciesState s) => s.Present && !s.Introduced);
			int num2 = orCreate.Species.Values.Count((SpeciesState s) => s.Present && s.Introduced);
			stringBuilder.AppendLine("SPECIES    " + num + " native / " + num2 + " introduced");
			stringBuilder.AppendLine("CROPS      " + orCreate.Crops.Count + " active");
			stringBuilder.AppendLine("HOLD       " + UsedPens() + " / " + HoldCapacity() + " pens");
			stringBuilder.AppendLine("TERRITORY  " + TerritorySummary(orCreate));
			stringBuilder.AppendLine("CYCLE      " + orCreate.Cycle + "  //  visits " + orCreate.Visits);
			stringBuilder.AppendLine();
			List<SpeciesState> list = (from s in orCreate.Species.Values
				where s.Present && s.Population > 0f
				orderby PopulationPower(s) descending
				select s).Take(6).ToList();
			if (list.Count > 0)
			{
				stringBuilder.AppendLine("DOMINANT SPECIES");
				foreach (SpeciesState item in list)
				{
					SpeciesRuntime runtime = EcoCatalog.GetRuntime(item.Key);
					int num3 = ((runtime != null) ? EcologyRuntime.SpawnCap(orCreate, runtime) : 0);
					stringBuilder.AppendLine("  " + Pad(item.Name, 22) + " pop " + Mathf.RoundToInt(item.Population).ToString().PadLeft(3) + "  cap " + num3 + "  " + ((runtime != null) ? ("p" + runtime.Power.ToString("0.0")) : ""));
				}
			}
			if (orCreate.Log.Count > 0)
			{
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("LAST LOG");
				stringBuilder.AppendLine("  " + orCreate.Log[orCreate.Log.Count - 1]);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Try ecobudget, ecocensus, ecoweb, or ecoterritory.");
			return stringBuilder.ToString();
		}

		public static string Moons()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "MOON SURVEY");
			stringBuilder.AppendLine("Name                         Food  Stab      Out  In   Apex");
			stringBuilder.AppendLine("----------------------------------------------------------------");
			foreach (SelectableLevel item in EcoCatalog.AllLevels())
			{
				MoonState orCreate = EcologyRuntime.GetOrCreate(item);
				BudgetResult budgetResult = EcologyRuntime.ComputeBudget(item, orCreate);
				MoonDef moonDef = EcoCatalog.MoonForLevelOrFallback(item);
				string text = ((moonDef.Apex != null && moonDef.Apex.Count > 0) ? string.Join(", ", moonDef.Apex.ToArray()) : "-");
				stringBuilder.AppendLine(Pad(item.PlanetName, 28) + " " + Mathf.RoundToInt(orCreate.Food).ToString().PadLeft(3) + "  " + Pad(EcologyRuntime.StabilityName(orCreate.Stability), 8) + " " + budgetResult.OutsideBudget.ToString().PadLeft(4) + " " + budgetResult.InsideBudget.ToString().PadLeft(3) + "  " + text);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Use ecomoon <name>. Food drives both outside and inside budgets.");
			return stringBuilder.ToString();
		}

		public static string MoonDetail(string query)
		{
			StringBuilder stringBuilder = new StringBuilder();
			MoonDef moon = EcoCatalog.FindMoon(query);
			SelectableLevel val = FindLevel(query, moon);
			if ((Object)(object)val == (Object)null)
			{
				Header(stringBuilder, "MOON NOT FOUND");
				stringBuilder.AppendLine("No moon matches '" + query + "'.");
				stringBuilder.AppendLine("Use ecomoons for the full list.");
				return stringBuilder.ToString();
			}
			moon = EcoCatalog.MoonForLevelOrFallback(val);
			MoonState orCreate = EcologyRuntime.GetOrCreate(val);
			BudgetResult budgetResult = EcologyRuntime.ComputeBudget(val, orCreate);
			Header(stringBuilder, val.PlanetName.ToUpperInvariant() + " // " + moon.Identity.ToUpperInvariant());
			stringBuilder.AppendLine("Biome: " + moon.Biome);
			stringBuilder.AppendLine("Risk: " + Safe(val.riskLevel, "?") + "   Level ID: " + val.levelID);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Food       " + Mathf.RoundToInt(orCreate.Food) + " / 100  " + Bar(orCreate.Food, 100f, 24));
			stringBuilder.AppendLine("Stability  " + EcologyRuntime.StabilityName(orCreate.Stability) + " (" + Signed(EcologyRuntime.StabilityModifier(orCreate.Stability)) + ")");
			stringBuilder.AppendLine("Budget     Outside " + budgetResult.OutsideBudget + " | Inside " + budgetResult.InsideBudget + " | Day " + budgetResult.DaytimeBudget);
			stringBuilder.AppendLine("Bases      " + budgetResult.OutsideBase + " outside | " + budgetResult.InsideBase + " inside | " + budgetResult.DaytimeBase + " day");
			stringBuilder.AppendLine("Modifiers  food " + Signed(budgetResult.FoodModifier) + " | stability " + Signed(budgetResult.StabilityModifier) + " | apex -" + budgetResult.ApexPower);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("APEX");
			stringBuilder.AppendLine("  " + ((moon.Apex != null && moon.Apex.Count > 0) ? string.Join(", ", moon.Apex.ToArray()) : "No named apex."));
			stringBuilder.AppendLine("  " + TerritorySummary(orCreate));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("NATIVE POOL");
			List<SpeciesRuntime> list = EcoCatalog.SpeciesForLevel(val);
			foreach (SpeciesRuntime item in from x in list
				orderby EcoCatalog.TierOrder(x.Def.Tier), x.Name
				select x)
			{
				SpeciesState speciesState = EcologyRuntime.GetSpeciesState(orCreate, item.Key);
				stringBuilder.AppendLine("  " + Pad(item.Name, 22) + " " + Pad(item.Def.Tier, 10) + " pop " + Mathf.RoundToInt(speciesState?.Population ?? 0f).ToString().PadLeft(3));
			}
			if (list.Count == 0)
			{
				stringBuilder.AppendLine("  Runtime pool not available yet.");
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("CROPS");
			stringBuilder.AppendLine("  " + ((moon.Crops != null && moon.Crops.Count > 0) ? string.Join(", ", moon.Crops.ToArray()) : "None listed."));
			stringBuilder.AppendLine("HAZARDS");
			foreach (string item2 in moon.Hazards ?? new List<string>())
			{
				stringBuilder.AppendLine("  - " + item2);
			}
			stringBuilder.AppendLine();
			AppendWrapped(stringBuilder, moon.Notes, "  ");
			if (!string.IsNullOrWhiteSpace(moon.Project))
			{
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("PROJECT");
				AppendWrapped(stringBuilder, moon.Project, "  ");
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Use ecocensus, ecoweb, or ecocultivation for live detail.");
			return stringBuilder.ToString();
		}

		public static string SpeciesIndex()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "SPECIES INDEX");
			MoonState moonState = EcologyRuntime.CurrentMoon();
			List<SpeciesRuntime> list = EcoCatalog.AllRuntimeOrdered();
			if (list.Count == 0)
			{
				foreach (SpeciesDef item in from x in EcoData.Species.Species
					orderby EcoCatalog.TierOrder(x.Tier), x.Name
					select x)
				{
					stringBuilder.AppendLine("  " + Pad(item.Name, 24) + " " + Pad(item.Tier, 10) + " p" + item.Power.ToString("0.0") + "  [dormant]");
				}
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("The game assembly has not loaded a level pool yet.");
				return stringBuilder.ToString();
			}
			foreach (IGrouping<string, SpeciesRuntime> item2 in from x in list
				group x by x.Def.Tier into g
				orderby EcoCatalog.TierOrder(g.Key)
				select g)
			{
				stringBuilder.AppendLine(item2.Key.ToUpperInvariant());
				foreach (SpeciesRuntime item3 in item2.OrderBy((SpeciesRuntime x) => x.Name))
				{
					SpeciesState speciesState = ((moonState != null) ? EcologyRuntime.GetSpeciesState(moonState, item3.Key) : null);
					string text = ((speciesState != null && speciesState.Present) ? "*" : " ");
					string text2 = ((speciesState != null) ? Mathf.RoundToInt(speciesState.Population).ToString().PadLeft(3) : "  -");
					string text3 = ((speciesState != null) ? EcologyRuntime.SpawnCap(moonState, item3).ToString() : "-");
					stringBuilder.AppendLine(" " + text + " " + Pad(item3.Name, 22) + " p" + item3.Power.ToString("0.0").PadLeft(4) + " pop " + text2 + " cap " + text3 + "  " + item3.Habitat);
				}
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("* = present on current moon. Use ecospecies <name>.");
			return stringBuilder.ToString();
		}

		public static string SpeciesDetail(string query)
		{
			StringBuilder stringBuilder = new StringBuilder();
			SpeciesRuntime runtime = EcoCatalog.GetRuntime(query);
			SpeciesDef speciesDef = ((runtime == null) ? EcoCatalog.FindAuthored(query) : runtime.Def);
			if (speciesDef == null)
			{
				Header(stringBuilder, "SPECIES NOT FOUND");
				stringBuilder.AppendLine("No species matches '" + query + "'.");
				stringBuilder.AppendLine("Use ecospecies for the index.");
				return stringBuilder.ToString();
			}
			Header(stringBuilder, speciesDef.Name.ToUpperInvariant());
			stringBuilder.AppendLine("Tier: " + speciesDef.Tier + "   Habitat: " + ((runtime != null) ? runtime.Habitat : speciesDef.Habitat));
			stringBuilder.AppendLine("Power: " + ((runtime != null) ? runtime.Power.ToString("0.0") : speciesDef.Power.ToString("0.0")) + "   Capturable: " + (speciesDef.Capturable ? ("yes (" + speciesDef.PenCost + " pen" + ((speciesDef.PenCost == 1) ? "" : "s") + ")") : "no"));
			if (runtime != null)
			{
				stringBuilder.AppendLine("Spawn cap limit: " + runtime.OriginalMaxCount + "   Runtime: live");
			}
			else
			{
				stringBuilder.AppendLine("Runtime: dormant on this install");
			}
			MoonState moonState = EcologyRuntime.CurrentMoon();
			if (moonState != null)
			{
				SpeciesState speciesState = EcologyRuntime.GetSpeciesState(moonState, speciesDef.Name);
				if (speciesState != null)
				{
					stringBuilder.AppendLine("Current moon: " + moonState.LevelName);
					stringBuilder.AppendLine("  Population " + Mathf.RoundToInt(speciesState.Population) + " / 100   Spawn cap " + ((runtime != null) ? EcologyRuntime.SpawnCap(moonState, runtime) : 0) + "   Killed " + speciesState.Killed + "   Captured " + speciesState.Captured);
				}
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("ROLE");
			AppendWrapped(stringBuilder, speciesDef.Role, "  ");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("DIET");
			AppendWrapped(stringBuilder, speciesDef.Diet, "  ");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("PREYS");
			AppendList(stringBuilder, speciesDef.Preys);
			stringBuilder.AppendLine("PREDATORS");
			AppendList(stringBuilder, speciesDef.Predators);
			stringBuilder.AppendLine("HOSTS");
			AppendList(stringBuilder, speciesDef.Hosts);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("TRAITS (C = canon, D = mod)");
			foreach (string item in speciesDef.Traits ?? new List<string>())
			{
				stringBuilder.AppendLine("  - " + item);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("PLAYER CONTROLS");
			foreach (string item2 in speciesDef.Controls ?? new List<string>())
			{
				stringBuilder.AppendLine("  - " + item2);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("CANON NOTES");
			foreach (string item3 in speciesDef.Canon ?? new List<string>())
			{
				stringBuilder.AppendLine("  C  " + item3);
			}
			stringBuilder.AppendLine("MOD NOTES");
			foreach (string item4 in speciesDef.Mod ?? new List<string>())
			{
				stringBuilder.AppendLine("  D  " + item4);
			}
			stringBuilder.AppendLine();
			AppendWrapped(stringBuilder, speciesDef.Notes, "  ");
			return stringBuilder.ToString();
		}

		public static string Web()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "FOOD WEB // CURRENT MOON");
			SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			MoonState moonState = EcologyRuntime.CurrentMoon();
			if ((Object)(object)val == (Object)null || moonState == null)
			{
				stringBuilder.AppendLine("No current moon.");
				return stringBuilder.ToString();
			}
			stringBuilder.AppendLine(val.PlanetName + "  Food " + Mathf.RoundToInt(moonState.Food) + "  " + EcologyRuntime.StabilityName(moonState.Stability));
			stringBuilder.AppendLine();
			string[] array = new string[6] { "Producer", "Primary", "Secondary", "Apex", "Parasite", "Anomaly" };
			foreach (string tier in array)
			{
				var list = (from s in moonState.Species.Values
					where s.Present && s.Population > 0f
					select new
					{
						State = s,
						Runtime = EcoCatalog.GetRuntime(s.Key)
					} into x
					where x.Runtime != null && EcoCatalog.TierIs(x.Runtime.Def.Tier, tier)
					orderby x.State.Population descending
					select x).ToList();
				if (list.Count == 0)
				{
					continue;
				}
				stringBuilder.AppendLine(tier.ToUpperInvariant());
				foreach (var item in list)
				{
					stringBuilder.AppendLine("  " + Pad(item.State.Name, 24) + " " + Mathf.RoundToInt(item.State.Population).ToString().PadLeft(3) + "  " + Bar(item.State.Population, 100f, 14));
				}
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("KEY LINKS");
			int num = 0;
			foreach (SpeciesState item2 in from x in moonState.Species.Values
				where x.Present && x.Population >= 10f
				orderby x.Population descending
				select x)
			{
				SpeciesRuntime runtime = EcoCatalog.GetRuntime(item2.Key);
				if (runtime == null)
				{
					continue;
				}
				foreach (string item3 in runtime.Def.Preys ?? new List<string>())
				{
					string key = EcoCatalog.Normalize(item3);
					if (moonState.Species.TryGetValue(key, out var value) && value.Present)
					{
						stringBuilder.AppendLine("  " + item2.Name + " <- " + value.Name);
						num++;
						if (num >= 10)
						{
							break;
						}
					}
				}
				if (num >= 10)
				{
					break;
				}
			}
			if (num == 0)
			{
				stringBuilder.AppendLine("  No direct links are recorded yet.");
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Arrow points from food to eater. Use ecospecies for a full sheet.");
			return stringBuilder.ToString();
		}

		public static string Budget()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "POWER BUDGET");
			SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			if ((Object)(object)val == (Object)null)
			{
				stringBuilder.AppendLine("No current moon. The formula is global:");
				stringBuilder.AppendLine("  Outside = Base + FoodModifier + StabilityModifier - ApexResidents");
				stringBuilder.AppendLine("  Inside = Base + FoodModifier + StabilityModifier");
				stringBuilder.AppendLine("  FoodModifier = floor((Food - 50) / 10), clamp -5..+10");
				stringBuilder.AppendLine("  Stability: Thriving +2, Stable 0, Stressed -2, Collapsing -4");
				stringBuilder.AppendLine("  Each occupied Relay Spire or Burrow subtracts its resident power from Outside.");
				return stringBuilder.ToString();
			}
			MoonState orCreate = EcologyRuntime.GetOrCreate(val);
			BudgetResult budgetResult = EcologyRuntime.ComputeBudget(val, orCreate);
			stringBuilder.AppendLine(val.PlanetName);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("OUTSIDE");
			stringBuilder.AppendLine("  Base            " + budgetResult.OutsideBase);
			stringBuilder.AppendLine("  Food " + Mathf.RoundToInt(orCreate.Food) + "        " + Signed(budgetResult.FoodModifier));
			stringBuilder.AppendLine("  Stability       " + Signed(budgetResult.StabilityModifier) + "  (" + EcologyRuntime.StabilityName(orCreate.Stability) + ")");
			stringBuilder.AppendLine("  Apex residents  -" + budgetResult.ApexPower);
			stringBuilder.AppendLine("  = Budget        " + budgetResult.OutsideBudget);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("INSIDE");
			stringBuilder.AppendLine("  Base            " + budgetResult.InsideBase);
			stringBuilder.AppendLine("  Food + Stability " + Signed(budgetResult.FoodModifier + budgetResult.StabilityModifier));
			stringBuilder.AppendLine("  = Budget        " + budgetResult.InsideBudget);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("DAYTIME");
			stringBuilder.AppendLine("  Base            " + budgetResult.DaytimeBase);
			stringBuilder.AppendLine("  = Budget        " + budgetResult.DaytimeBudget);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("FOOD MODIFIER TABLE");
			stringBuilder.AppendLine("  Food  0-9: -5   10-19: -4   20-29: -3   30-39: -2");
			stringBuilder.AppendLine("  40-49: -1   50-59: 0   60-69: +1   70-79: +2");
			stringBuilder.AppendLine("  80-89: +3   90-99: +4   100: +5 (cap +10 is available to special effects)");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("SPAWN CAP");
			stringBuilder.AppendLine("  pop < 10  -> 0 spawns");
			stringBuilder.AppendLine("  pop >= 10 -> ceil(pop / 20), maximum 5");
			stringBuilder.AppendLine("  Apex territory -> one spawn slot per occupied anchor");
			stringBuilder.AppendLine();
			List<SpeciesRuntime> source = EcoCatalog.SpeciesForLevel(val);
			stringBuilder.AppendLine("CURRENT CAPS");
			foreach (SpeciesRuntime item in from x in source
				orderby x.Power descending, x.Name
				select x)
			{
				SpeciesState speciesState = EcologyRuntime.GetSpeciesState(orCreate, item.Key);
				if (speciesState != null)
				{
					stringBuilder.AppendLine("  " + Pad(item.Name, 24) + " pop " + Mathf.RoundToInt(speciesState.Population).ToString().PadLeft(3) + "  cap " + EcologyRuntime.SpawnCap(orCreate, item) + "  cost " + item.Power.ToString("0.0"));
				}
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Apex residents cost " + ((Plugin.ApexPower != null) ? Plugin.ApexPower.Value.ToString("0.0") : "12") + " power each.");
			return stringBuilder.ToString();
		}

		public static string Census()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "SPECIES CENSUS");
			SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			MoonState moonState = EcologyRuntime.CurrentMoon();
			if ((Object)(object)val == (Object)null || moonState == null)
			{
				stringBuilder.AppendLine("No current moon.");
				return stringBuilder.ToString();
			}
			stringBuilder.AppendLine(val.PlanetName + "   Food " + Mathf.RoundToInt(moonState.Food) + "   " + EcologyRuntime.StabilityName(moonState.Stability));
			stringBuilder.AppendLine();
			string[] array = new string[5] { "Outside", "Inside", "Daytime", "Outside / Inside", "Any" };
			foreach (string habitat in array)
			{
				var list = (from s in moonState.Species.Values
					select new
					{
						State = s,
						Runtime = EcoCatalog.GetRuntime(s.Key)
					} into x
					where x.Runtime != null && (x.Runtime.Habitat == habitat || (habitat == "Any" && x.Runtime.Habitat == "Any"))
					orderby x.State.Population descending
					select x).ToList();
				if (list.Count == 0)
				{
					continue;
				}
				stringBuilder.AppendLine(habitat.ToUpperInvariant());
				foreach (var item in list)
				{
					int num = EcologyRuntime.SpawnCap(moonState, item.Runtime);
					string text = ((!item.State.Present) ? "EXTINCT" : (item.State.Introduced ? "INTRO" : ""));
					stringBuilder.AppendLine("  " + Pad(item.State.Name, 22) + " " + Mathf.RoundToInt(item.State.Population).ToString().PadLeft(3) + "  cap " + num + "  p" + item.Runtime.Power.ToString("0.0").PadLeft(4) + "  " + text);
				}
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Species below 10 population cannot spawn. Protect producers first.");
			return stringBuilder.ToString();
		}

		public static string Territories()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "APEX TERRITORIES");
			SelectableLevel obj = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			MoonState moonState = EcologyRuntime.CurrentMoon();
			if ((Object)(object)obj == (Object)null || moonState == null)
			{
				stringBuilder.AppendLine("No current moon.");
				return stringBuilder.ToString();
			}
			stringBuilder.AppendLine("Old Birds claim Relay Spires. Earth Leviathans claim Burrows.");
			stringBuilder.AppendLine("One resident per intact anchor. Each resident costs " + ((Plugin.ApexPower != null) ? Plugin.ApexPower.Value.ToString("0.0") : "12") + " power.");
			stringBuilder.AppendLine();
			if (moonState.Territories.Count == 0)
			{
				stringBuilder.AppendLine("No apex anchors on this moon.");
				return stringBuilder.ToString();
			}
			foreach (TerritoryState item in from x in moonState.Territories.Values
				orderby x.Type, x.Id
				select x)
			{
				string value = EcoSim.TerritoryLabel(item);
				string value2 = (item.Destroyed ? "DESTROYED" : (item.Occupied ? "OCCUPIED" : "EMPTY"));
				stringBuilder.AppendLine(Pad(value, 20) + " " + Pad(value2, 10) + " " + Safe(item.ResidentName, "-"));
				if (!item.Destroyed)
				{
					stringBuilder.AppendLine("  ID: " + item.Id + "   demolition: ecodestroy " + item.Id);
				}
				if (!item.Occupied && !item.Destroyed)
				{
					stringBuilder.AppendLine("  Reclaim in " + item.ReclaimIn + " cycle(s) if Food and Stability hold.");
				}
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("REDUCTION");
			stringBuilder.AppendLine("  ecodestroy <id> buys a demolition charge and permanently removes the anchor.");
			stringBuilder.AppendLine("  Relay charge $150; Burrow charge $200. Both are host-only.");
			stringBuilder.AppendLine("  Kill the resident with a Nutcracker shotgun (Old Bird) or explosives (Leviathan).");
			stringBuilder.AppendLine("  Drop the moon budget below 12 to suppress new apex spawns.");
			stringBuilder.AppendLine("  Ironroot and stable soil prevent new Burrows.");
			return stringBuilder.ToString();
		}

		public static string Cultivation()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "CULTIVATION");
			SelectableLevel val = (((Object)(object)StartOfRound.Instance != (Object)null) ? StartOfRound.Instance.currentLevel : null);
			MoonState moonState = EcologyRuntime.CurrentMoon();
			if ((Object)(object)val == (Object)null || moonState == null)
			{
				stringBuilder.AppendLine("No current moon.");
				return stringBuilder.ToString();
			}
			stringBuilder.AppendLine(val.PlanetName + "   Food " + Mathf.RoundToInt(moonState.Food) + "   " + EcologyRuntime.StabilityName(moonState.Stability));
			stringBuilder.AppendLine();
			if (moonState.Crops.Count == 0)
			{
				stringBuilder.AppendLine("No active crops.");
			}
			else
			{
				stringBuilder.AppendLine("ACTIVE CROPS");
				foreach (CropState item in moonState.Crops.Values.OrderBy((CropState x) => x.Name))
				{
					stringBuilder.AppendLine("  " + Pad(item.Name, 18) + " x" + item.Amount + "  hp " + Mathf.RoundToInt(item.Health) + "  blight " + Mathf.RoundToInt(item.Blight) + (item.Pollinated ? "  pollinated" : ""));
				}
				stringBuilder.AppendLine();
			}
			stringBuilder.AppendLine("AVAILABLE CROPS");
			foreach (CropDef item2 in EcoCatalog.CropsForMoon(val))
			{
				stringBuilder.AppendLine("  " + Pad(item2.Name, 18) + " $" + item2.Cost + "  food +" + item2.FoodPerCycle.ToString("0.0") + "  stab " + Signed(Mathf.RoundToInt(item2.StabilityPerCycle)) + "  blight " + item2.BlightRisk);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("ecoplant <name> buys and plants a crop on this moon.");
			stringBuilder.AppendLine("ecocare <name> spends credits to reduce blight.");
			stringBuilder.AppendLine("ecocrop <name> shows requirements and exact steps.");
			return stringBuilder.ToString();
		}

		public static string CropDetail(string query)
		{
			StringBuilder stringBuilder = new StringBuilder();
			CropDef cropDef = EcoCatalog.FindCrop(query);
			if (cropDef == null)
			{
				Header(stringBuilder, "CROP NOT FOUND");
				stringBuilder.AppendLine("No crop matches '" + query + "'.");
				return stringBuilder.ToString();
			}
			Header(stringBuilder, cropDef.Name.ToUpperInvariant());
			stringBuilder.AppendLine(cropDef.Kind + "  //  " + cropDef.Role);
			stringBuilder.AppendLine("Cost $" + cropDef.Cost + "   Food +" + cropDef.FoodPerCycle.ToString("0.0") + "   Stability " + Signed(Mathf.RoundToInt(cropDef.StabilityPerCycle)) + "   Blight " + cropDef.BlightRisk);
			stringBuilder.AppendLine("Pollinator: " + cropDef.Pollinator);
			stringBuilder.AppendLine("Habitat: " + cropDef.Habitat);
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("EFFECT");
			AppendWrapped(stringBuilder, cropDef.Effect, "  ");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("REQUIREMENTS");
			foreach (string item in cropDef.Requirements ?? new List<string>())
			{
				stringBuilder.AppendLine("  - " + item);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("STEPS");
			int num = 1;
			foreach (string item2 in cropDef.Steps ?? new List<string>())
			{
				stringBuilder.AppendLine("  " + num++ + ". " + item2);
			}
			if (!string.IsNullOrWhiteSpace(cropDef.Warning))
			{
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("WARNING");
				AppendWrapped(stringBuilder, cropDef.Warning, "  ");
			}
			return stringBuilder.ToString();
		}

		public static string Hold()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "SPECIMEN HOLD");
			HoldState hold = EcologyRuntime.Data.Hold;
			int num = HoldCapacity();
			stringBuilder.AppendLine("Pens: " + UsedPens() + " / " + num + " used");
			stringBuilder.AppendLine("Live specimens stay with the ship between moons.");
			stringBuilder.AppendLine();
			if (hold.Specimens.Count == 0)
			{
				stringBuilder.AppendLine("The hold is empty.");
				stringBuilder.AppendLine();
				stringBuilder.AppendLine("Stun a capturable creature, stand within 5 m, and press " + ((Plugin.CaptureKeyName != null) ? Plugin.CaptureKeyName.Value : "F8") + ".");
				return stringBuilder.ToString();
			}
			stringBuilder.AppendLine("PEN  SPECIMEN               COST  HEALTH  STRESS  NOTE");
			stringBuilder.AppendLine("---------------------------------------------------------------");
			foreach (SpecimenState item in hold.Specimens.OrderBy((SpecimenState x) => x.Pen))
			{
				string text = (item.Juvenile ? ("juvenile " + Mathf.RoundToInt(item.Growth) + "%") : (item.Hive ? "live hive" : "adult"));
				stringBuilder.AppendLine(Pad(item.Pen.ToString(), 4) + " " + Pad(item.Name, 22) + " " + Pad(item.PenCost.ToString(), 5) + " " + Pad(Mathf.RoundToInt(item.Health).ToString(), 7) + " " + Pad(Mathf.RoundToInt(item.Stress).ToString(), 7) + " " + text);
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("ecohold enter   visit the containment deck");
			stringBuilder.AppendLine("ecohold exit    return to the ship");
			stringBuilder.AppendLine("ecorelease <pen> release on the current moon");
			return stringBuilder.ToString();
		}

		public static string Gear()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "CAPTURE GEAR");
			stringBuilder.AppendLine("Capture key: " + ((Plugin.CaptureKeyName != null) ? Plugin.CaptureKeyName.Value : "F8"));
			stringBuilder.AppendLine("Hold deck key: F9");
			stringBuilder.AppendLine("Requirement: the creature must be stunned, not dead.");
			stringBuilder.AppendLine("Range: 5 m from the host player.");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("PEN COSTS");
			stringBuilder.AppendLine("  Small (1)   Manticoil, Locust, Hoarding Bug, Snare Flea");
			stringBuilder.AppendLine("  Medium (2)  Baboon Hawk, Bunker Spider, Thumper, Butler, Hive");
			stringBuilder.AppendLine("  Large (3)   Nutcracker, Eyeless Dog, Adult Sapsucker");
			stringBuilder.AppendLine("  Huge (4)    Forest Keeper, Old Bird, Bracken, Cave Dweller");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("NOT CAPTURABLE");
			stringBuilder.AppendLine("  Earth Leviathan, Coil-Head, Jester, Ghost Girl, Barber, Clay Surgeon.");
			stringBuilder.AppendLine("  Cadaver Growths, Cadaver Bloom, and Vain Shroud are sample-only.");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("Capture removes the creature from the moon and lowers its population.");
			return stringBuilder.ToString();
		}

		public static string Items()
		{
			StringBuilder stringBuilder = new StringBuilder();
			Header(stringBuilder, "ECOLOGY TOOLS AND ITEMS");
			stringBuilder.AppendLine("This version deliberately adds no inventory items. Every tool is a");
			stringBuilder.AppendLine("terminal action or a keybind, so vanilla clients need no mod item IDs.");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("FIELD CAPTURE KIT");
			stringBuilder.AppendLine("  Unlock: always available.");
			stringBuilder.AppendLine("  Use: stun a creature, stand within 5 m, press " + ((Plugin.CaptureKeyName != null) ? Plugin.CaptureKeyName.Value : "F8") + ".");
			stringBuilder.AppendLine("  Cost: free. Requires an empty pen in the Specimen Hold.");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("SPECIMEN HOLD");
			stringBuilder.AppendLine("  Unlock: always available on the host ship.");
			stringBuilder.AppendLine("  Use