Decompiled source of DoubleDamageConfigurator v0.5.1

DoubleDamageConfigurator.dll

Decompiled 3 weeks ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using AssetHelperLib.BundleTools;
using AssetsTools.NET;
using AssetsTools.NET.Extra;
using BepInEx;
using BepInEx.Logging;
using GlobalEnums;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Silksong.AssetHelper.Core;
using Silksong.ModMenu.Elements;
using Silksong.ModMenu.Models;
using Silksong.ModMenu.Plugin;
using Silksong.ModMenu.Screens;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("DoubleDamageConfigurator")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("0.5.1.0")]
[assembly: AssemblyInformationalVersion("0.5.1+29664d613fd7138c15b6b511b689b3e42b2ca243")]
[assembly: AssemblyProduct("DoubleDamageConfigurator")]
[assembly: AssemblyTitle("DoubleDamageConfigurator")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/json-commits/ss-double-damage-configurator")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.5.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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 BepInEx
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class BepInAutoPluginAttribute : Attribute
	{
		public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace BepInEx.Preloader.Core.Patching
{
	[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
	[Conditional("CodeGeneration")]
	[Embedded]
	internal sealed class PatcherAutoPluginAttribute : Attribute
	{
		public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null)
		{
		}
	}
}
namespace Microsoft.CodeAnalysis
{
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace DoubleDamageConfigurator
{
	public static class DamageCap
	{
		public static bool ShouldCap(bool resolvedToCatalog, bool isOff)
		{
			return resolvedToCatalog && !isOff;
		}

		public static int Apply(int damageAmount, bool shouldCap)
		{
			if (!shouldCap || damageAmount <= 1)
			{
				return damageAmount;
			}
			return 1;
		}
	}
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInPlugin("io.github.change_me.doubledamageconfigurator", "DoubleDamageConfigurator", "0.5.1")]
	public class DoubleDamageConfiguratorPlugin : BaseUnityPlugin, IModMenuCustomMenu, IModMenuInterface
	{
		private readonly ConcurrentQueue<Action> _mainThread = new ConcurrentQueue<Action>();

		private EnemyAssetScanner _scanner = null;

		private EnemySettings _settings = null;

		private EnemyCatalog _catalog = EnemyCatalog.Empty;

		private ScanUiState _scanUi;

		private AbstractMenuScreen? _openScreen;

		private Action? _refreshScanBanner;

		private string _catalogPath = string.Empty;

		private string _settingsPath = string.Empty;

		private string _gameVersion = string.Empty;

		public const string Id = "io.github.change_me.doubledamageconfigurator";

		public static string Name => "DoubleDamageConfigurator";

		public static string Version => "0.5.1";

		public LocalizedText ModMenuName()
		{
			return LocalizedText.Raw("Double Damage Configurator");
		}

		public AbstractMenuScreen BuildCustomMenu()
		{
			_openScreen = EnemyListMenu.Build(_catalog, _settings, () => _scanUi, StartRegenerate, delegate(Action refresh)
			{
				_refreshScanBanner = refresh;
			}, ((BaseUnityPlugin)this).Logger);
			return _openScreen;
		}

		private void Awake()
		{
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			_gameVersion = Application.version;
			_catalogPath = Path.Combine(Paths.ConfigPath, CatalogCache.FileName);
			_settingsPath = Path.Combine(Paths.ConfigPath, EnemySettings.FileName);
			_settings = EnemySettings.Load(_settingsPath);
			TakeDamageHook.Settings = _settings;
			_scanner = new EnemyAssetScanner(((BaseUnityPlugin)this).Logger, delegate(Action action)
			{
				_mainThread.Enqueue(action);
			});
			CatalogCacheFile catalogCacheFile = CatalogCache.TryLoad(_catalogPath);
			if (catalogCacheFile != null && catalogCacheFile.Status == ScanStatus.Complete && catalogCacheFile.GameVersion == _gameVersion)
			{
				AssignCatalog(catalogCacheFile.Catalog);
				_scanUi = new ScanUiState(ScanPhase.Ready, _catalog.Enemies.Count);
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Loaded enemy catalog with {_catalog.Enemies.Count} entries for game {_gameVersion}.");
			}
			else
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Enemy catalog missing, stale, or incomplete. Starting scan.");
				_scanUi = new ScanUiState(ScanPhase.Running, 0);
				StartScan();
			}
			new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID).PatchAll(typeof(TakeDamageHook).Assembly);
		}

		private void AssignCatalog(EnemyCatalog catalog)
		{
			_catalog = catalog;
			TakeDamageHook.SetCatalog(catalog);
		}

		private void Update()
		{
			Action result;
			while (_mainThread.TryDequeue(out result))
			{
				result();
			}
		}

		private void StartRegenerate()
		{
			if (!_scanner.IsScanning)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Regenerating enemy catalog.");
				_scanUi = new ScanUiState(ScanPhase.Running, _catalog.Enemies.Count);
				StartScan();
			}
		}

		private void StartScan()
		{
			_scanner.TryBeginScan(_gameVersion, _catalogPath, AssetPaths.BundleFolder, OnScanProgress, OnScanCompleted);
		}

		private void OnScanProgress(int filesDone, int filesTotal)
		{
			_scanUi = new ScanUiState(ScanPhase.Running, _catalog.Enemies.Count, filesDone, filesTotal);
			_refreshScanBanner?.Invoke();
		}

		private void OnScanCompleted()
		{
			_refreshScanBanner = null;
			CatalogCacheFile catalogCacheFile = CatalogCache.TryLoad(_catalogPath);
			if (catalogCacheFile == null)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Enemy catalog scan finished but no cache file was readable.");
				_scanUi = new ScanUiState(ScanPhase.Failed, _catalog.Enemies.Count);
			}
			else
			{
				switch (catalogCacheFile.Status)
				{
				case ScanStatus.Complete:
					if (catalogCacheFile.GameVersion != _gameVersion)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("Enemy catalog game version " + catalogCacheFile.GameVersion + " does not match " + _gameVersion + "."));
						_scanUi = new ScanUiState(ScanPhase.Failed, _catalog.Enemies.Count);
					}
					else
					{
						AssignCatalog(catalogCacheFile.Catalog);
						_scanUi = new ScanUiState(ScanPhase.Ready, _catalog.Enemies.Count);
						((BaseUnityPlugin)this).Logger.LogInfo((object)$"Enemy catalog ready with {_catalog.Enemies.Count} entries.");
					}
					break;
				case ScanStatus.Incomplete:
					((BaseUnityPlugin)this).Logger.LogWarning((object)"Enemy catalog is still incomplete after the scan.");
					_scanUi = new ScanUiState(ScanPhase.Failed, _catalog.Enemies.Count);
					break;
				default:
					throw new ArgumentOutOfRangeException("Status", catalogCacheFile.Status, "Unknown ScanStatus.");
				}
			}
			if (_openScreen != null && _openScreen == MenuScreenNavigation.CurrentModMenuScreen)
			{
				AbstractMenuScreen val = BuildCustomMenu();
				MenuScreenNavigation.Show(val, (HistoryMode)1);
			}
		}
	}
	public sealed class EnemyAssetScanner
	{
		private sealed class ScanCensus
		{
			public int BundlesAttempted;

			public int SceneBundlesAccepted;

			public int AssetsFilesScanned;

			public int CreateFromFileCalls;

			public int CreateFromFileSkipped;

			public int MonoBehavioursVisited;

			public int GetBaseFieldOk;

			public int FilesWithHits;

			public int FilesWithHealth;

			public int FilesNeedingLookup;

			public long MemoryAtStart;

			public long MemoryAtEnd;
		}

		private readonly struct FoundEnemy
		{
			public string DisplayName { get; }

			public int MaxDamageDealt { get; }

			public bool IsBoss { get; }

			public FoundEnemy(string displayName, int maxDamageDealt, bool isBoss)
			{
				DisplayName = displayName;
				MaxDamageDealt = maxDamageDealt;
				IsBoss = isBoss;
			}
		}

		private const int DamageThreshold = 2;

		private readonly ManualLogSource _log;

		private readonly Action<Action> _runOnMain;

		private int _running;

		public bool IsScanning => Volatile.Read(in _running) != 0;

		public EnemyAssetScanner(ManualLogSource log, Action<Action> runOnMain)
		{
			_log = log ?? throw new ArgumentNullException("log");
			_runOnMain = runOnMain ?? throw new ArgumentNullException("runOnMain");
		}

		public bool TryBeginScan(string gameVersion, string catalogPath, string bundleFolder, Action<int, int> onProgressOnMain, Action onCompletedOnMain)
		{
			if (string.IsNullOrWhiteSpace(gameVersion))
			{
				throw new ArgumentException("Game version is required.", "gameVersion");
			}
			if (string.IsNullOrWhiteSpace(catalogPath))
			{
				throw new ArgumentException("Catalog path is required.", "catalogPath");
			}
			if (onProgressOnMain == null)
			{
				throw new ArgumentNullException("onProgressOnMain");
			}
			if (onCompletedOnMain == null)
			{
				throw new ArgumentNullException("onCompletedOnMain");
			}
			if (Interlocked.CompareExchange(ref _running, 1, 0) != 0)
			{
				return false;
			}
			ThreadPool.QueueUserWorkItem(delegate
			{
				try
				{
					RunScan(gameVersion, catalogPath, bundleFolder ?? string.Empty, onProgressOnMain);
				}
				catch (Exception arg)
				{
					_log.LogError((object)$"Enemy catalog scan failed: {arg}");
				}
				finally
				{
					Interlocked.Exchange(ref _running, 0);
					_runOnMain(onCompletedOnMain);
				}
			});
			return true;
		}

		private void RunScan(string gameVersion, string catalogPath, string bundleFolder, Action<int, int> onProgressOnMain)
		{
			string text = Path.Combine(Path.GetDirectoryName(catalogPath) ?? string.Empty, CatalogCache.TempFileName);
			if (File.Exists(text))
			{
				File.Delete(text);
			}
			if (!File.Exists(catalogPath))
			{
				CatalogCache.WriteIncompletePlaceholder(catalogPath, gameVersion);
			}
			if (!Directory.Exists(bundleFolder))
			{
				_log.LogError((object)("Bundle folder does not exist: " + bundleFolder));
				return;
			}
			List<string> list = new List<string>();
			foreach (string item in Directory.EnumerateFiles(bundleFolder, "*", SearchOption.AllDirectories))
			{
				if (!IsSkippableBundlePath(item))
				{
					list.Add(item);
				}
			}
			_log.LogInfo((object)$"Walking {list.Count} files under {bundleFolder}");
			ReportProgress(0, list.Count, onProgressOnMain);
			List<FoundEnemy> list2 = new List<FoundEnemy>();
			int num = 0;
			int num2 = 0;
			ScanCensus scanCensus = new ScanCensus();
			AssetsManager val = BundleUtils.CreateDefaultManager();
			try
			{
				scanCensus.MemoryAtStart = GC.GetTotalMemory(forceFullCollection: false);
				for (int i = 0; i < list.Count; i++)
				{
					string text2 = list[i];
					try
					{
						if (TryScanBundle(val, text2, list2, scanCensus))
						{
							num++;
							if (num % 50 == 0)
							{
								_log.LogInfo((object)$"Scan progress: {num} scene bundles, {list2.Count} candidate hits, {num2} errors");
							}
						}
					}
					catch (Exception ex)
					{
						num2++;
						_log.LogWarning((object)("Skipping bundle " + text2 + ": " + ex.Message));
					}
					ReportProgress(i + 1, list.Count, onProgressOnMain);
				}
			}
			finally
			{
				val.UnloadAll(false);
			}
			scanCensus.MemoryAtEnd = GC.GetTotalMemory(forceFullCollection: false);
			EnemyCatalog enemyCatalog = BuildCatalog(list2);
			CatalogCache.WriteCompleteAtomic(catalogPath, text, gameVersion, enemyCatalog);
			_log.LogInfo((object)$"Enemy catalog complete. Scenes={num} enemies={enemyCatalog.Enemies.Count} errors={num2}");
			_log.LogInfo((object)($"Scan census: bundlesAttempted={scanCensus.BundlesAttempted} sceneBundlesAccepted={scanCensus.SceneBundlesAccepted} " + $"assetsFilesScanned={scanCensus.AssetsFilesScanned} createFromFileCalls={scanCensus.CreateFromFileCalls} " + $"createFromFileSkipped={scanCensus.CreateFromFileSkipped} monoBehavioursVisited={scanCensus.MonoBehavioursVisited} " + $"getBaseFieldOk={scanCensus.GetBaseFieldOk} filesWithHits={scanCensus.FilesWithHits} " + $"filesWithHealth={scanCensus.FilesWithHealth} filesNeedingLookup={scanCensus.FilesNeedingLookup} " + $"memoryStart={scanCensus.MemoryAtStart} memoryEnd={scanCensus.MemoryAtEnd}"));
		}

		private void ReportProgress(int done, int total, Action<int, int> onProgress)
		{
			if (done == 0 || done == total || done % 25 == 0)
			{
				int d = done;
				int t = total;
				_runOnMain(delegate
				{
					onProgress(d, t);
				});
			}
		}

		private bool TryScanBundle(AssetsManager mgr, string path, List<FoundEnemy> found, ScanCensus census)
		{
			census.BundlesAttempted++;
			BundleFileInstance val;
			try
			{
				val = mgr.LoadBundleFile(path, true);
			}
			catch (Exception ex)
			{
				_log.LogDebug((object)("Not a loadable bundle (" + Path.GetFileName(path) + "): " + ex.Message));
				return false;
			}
			try
			{
				SceneBundleInfo val2 = default(SceneBundleInfo);
				if (!BundleUtils.TryFindAssetsFiles(mgr, val, ref val2))
				{
					return false;
				}
				AssetsFileInstance fileInst = mgr.LoadAssetsFileFromBundle(val, val2.mainAfileInstIndex, false);
				ScanAssetsFile(mgr, fileInst, found, census);
				if (val2.sharedAssetsAfileIndex >= 0 && val2.sharedAssetsAfileIndex != val2.mainAfileInstIndex)
				{
					AssetsFileInstance fileInst2 = mgr.LoadAssetsFileFromBundle(val, val2.sharedAssetsAfileIndex, false);
					ScanAssetsFile(mgr, fileInst2, found, census);
				}
				census.SceneBundlesAccepted++;
				return true;
			}
			finally
			{
				mgr.UnloadBundleFile(val);
			}
		}

		private static void ScanAssetsFile(AssetsManager mgr, AssetsFileInstance fileInst, List<FoundEnemy> found, ScanCensus census)
		{
			census.AssetsFilesScanned++;
			HashSet<long> hashSet = new HashSet<long>();
			HashSet<long> titleGos = new HashSet<long>();
			List<(long, int)> list = new List<(long, int)>();
			CollectFromFile(mgr, fileInst, hashSet, titleGos, list, census);
			if (list.Count > 0)
			{
				census.FilesWithHits++;
			}
			if (hashSet.Count > 0)
			{
				census.FilesWithHealth++;
			}
			if (list.Count > 0 && hashSet.Count > 0)
			{
				census.FilesNeedingLookup++;
			}
			if (list.Count == 0 || hashSet.Count == 0)
			{
				census.CreateFromFileSkipped++;
				return;
			}
			census.CreateFromFileCalls++;
			GameObjectLookup lookup = GameObjectLookup.CreateFromFile(mgr, fileInst);
			EmitFoundEnemies(lookup, hashSet, titleGos, list, found);
		}

		private static void CollectFromFile(AssetsManager mgr, AssetsFileInstance fileInst, HashSet<long> healthGos, HashSet<long> titleGos, List<(long GoPathId, int Damage)> hits, ScanCensus census)
		{
			foreach (AssetFileInfo item in fileInst.file.GetAssetsOfType((AssetClassID)114))
			{
				census.MonoBehavioursVisited++;
				AssetTypeValueField baseField;
				try
				{
					baseField = mgr.GetBaseField(fileInst, item, (AssetReadFlags)0);
				}
				catch (Exception)
				{
					continue;
				}
				if (baseField == null || baseField.IsDummy)
				{
					continue;
				}
				census.GetBaseFieldOk++;
				if (!TryGetGameObjectPathId(baseField, out var goPathId))
				{
					continue;
				}
				if (ContainsDisplayBossTitle(baseField))
				{
					titleGos.Add(goPathId);
				}
				if (IsHealthManager(baseField))
				{
					healthGos.Add(goPathId);
				}
				else
				{
					if (!IsDamageHero(baseField))
					{
						continue;
					}
					int asInt = baseField["damageDealt"].AsInt;
					if (asInt >= 2)
					{
						bool flag;
						try
						{
							flag = IsEnemyHazardType(baseField["hazardType"].AsInt);
						}
						catch (ArgumentOutOfRangeException)
						{
							continue;
						}
						if (flag)
						{
							hits.Add((goPathId, asInt));
						}
					}
				}
			}
		}

		private static void EmitFoundEnemies(GameObjectLookup lookup, HashSet<long> healthGos, HashSet<long> titleGos, List<(long GoPathId, int Damage)> hits, List<FoundEnemy> found)
		{
			Dictionary<long, int> dictionary = new Dictionary<long, int>();
			foreach (var (goPathId, num) in hits)
			{
				if (TryFindHealthManagerGo(lookup, goPathId, healthGos, out var healthGo) && (!dictionary.TryGetValue(healthGo, out var value) || num > value))
				{
					dictionary[healthGo] = num;
				}
			}
			GameObjectInfo val = default(GameObjectInfo);
			foreach (KeyValuePair<long, int> item in dictionary)
			{
				if (lookup.TryLookupGameObject(item.Key, ref val) && val != null)
				{
					string hierarchyPath = val.GameObjectName ?? string.Empty;
					string text = EnemyNames.Canonical(LeafName(hierarchyPath));
					if (!string.IsNullOrEmpty(text))
					{
						bool isBoss = titleGos.Contains(item.Key) || TitleOnParentSubtree(lookup, item.Key, titleGos);
						found.Add(new FoundEnemy(text, item.Value, isBoss));
					}
				}
			}
		}

		private static EnemyCatalog BuildCatalog(List<FoundEnemy> found)
		{
			List<EnemyRecord> list = new List<EnemyRecord>(found.Count);
			foreach (FoundEnemy item in found)
			{
				list.Add(new EnemyRecord(new EnemyId(item.DisplayName), item.DisplayName, item.MaxDamageDealt, item.IsBoss));
			}
			return new EnemyCatalog(list);
		}

		private static bool TitleOnParentSubtree(GameObjectLookup lookup, long healthGo, HashSet<long> titleGos)
		{
			GameObjectInfo val = default(GameObjectInfo);
			if (!lookup.TryLookupGameObject(healthGo, ref val) || val == null || val.ParentPathId == 0)
			{
				return false;
			}
			GameObjectInfo val2 = default(GameObjectInfo);
			if (!lookup.TryLookupTransfrom(val.ParentPathId, ref val2) || val2 == null)
			{
				return false;
			}
			long gameObjectPathId = val2.GameObjectPathId;
			if (titleGos.Contains(gameObjectPathId))
			{
				return true;
			}
			foreach (long titleGo in titleGos)
			{
				if (IsUnderOrIs(lookup, titleGo, gameObjectPathId))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsUnderOrIs(GameObjectLookup lookup, long go, long ancestorOrSelf)
		{
			if (go == ancestorOrSelf)
			{
				return true;
			}
			HashSet<long> hashSet = new HashSet<long>();
			long num = go;
			GameObjectInfo val = default(GameObjectInfo);
			GameObjectInfo val2 = default(GameObjectInfo);
			while (hashSet.Add(num))
			{
				if (!lookup.TryLookupGameObject(num, ref val) || val == null)
				{
					return false;
				}
				if (val.ParentPathId == 0)
				{
					return false;
				}
				if (!lookup.TryLookupTransfrom(val.ParentPathId, ref val2) || val2 == null)
				{
					return false;
				}
				num = val2.GameObjectPathId;
				if (num == ancestorOrSelf)
				{
					return true;
				}
			}
			return false;
		}

		private static bool ContainsDisplayBossTitle(AssetTypeValueField root)
		{
			Stack<AssetTypeValueField> stack = new Stack<AssetTypeValueField>();
			stack.Push(root);
			while (stack.Count > 0)
			{
				AssetTypeValueField val = stack.Pop();
				if (val == null || val.IsDummy)
				{
					continue;
				}
				try
				{
					string type = val.TemplateField.Type;
					if ((type == "string" || type == "TypelessData") ? true : false)
					{
						string asString = val.AsString;
						if (asString.Contains("DisplayBossTitle", StringComparison.Ordinal))
						{
							return true;
						}
					}
				}
				catch (Exception)
				{
				}
				foreach (AssetTypeValueField item in val)
				{
					if (item != null && !item.IsDummy)
					{
						stack.Push(item);
					}
				}
			}
			return false;
		}

		private static bool TryFindHealthManagerGo(GameObjectLookup lookup, long goPathId, HashSet<long> healthGos, out long healthGo)
		{
			HashSet<long> hashSet = new HashSet<long>();
			long num = goPathId;
			GameObjectInfo val = default(GameObjectInfo);
			GameObjectInfo val2 = default(GameObjectInfo);
			while (hashSet.Add(num))
			{
				if (healthGos.Contains(num))
				{
					healthGo = num;
					return true;
				}
				if (!lookup.TryLookupGameObject(num, ref val) || val == null)
				{
					healthGo = 0L;
					return false;
				}
				if (val.ParentPathId == 0)
				{
					healthGo = 0L;
					return false;
				}
				if (!lookup.TryLookupTransfrom(val.ParentPathId, ref val2) || val2 == null)
				{
					healthGo = 0L;
					return false;
				}
				num = val2.GameObjectPathId;
			}
			healthGo = 0L;
			return false;
		}

		private static bool IsHealthManager(AssetTypeValueField mb)
		{
			return !mb["hp"].IsDummy && !mb["enemyType"].IsDummy && !mb["hasSpecialDeath"].IsDummy;
		}

		private static bool IsDamageHero(AssetTypeValueField mb)
		{
			return !mb["damageDealt"].IsDummy && !mb["hazardType"].IsDummy && !mb["OnDamagedHero"].IsDummy;
		}

		private static bool TryGetGameObjectPathId(AssetTypeValueField mb, out long goPathId)
		{
			AssetTypeValueField val = mb["m_GameObject"];
			if (val.IsDummy)
			{
				goPathId = 0L;
				return false;
			}
			AssetTypeValueField val2 = val["m_FileID"];
			if (!val2.IsDummy && val2.AsInt != 0)
			{
				goPathId = 0L;
				return false;
			}
			AssetTypeValueField val3 = val["m_PathID"];
			if (val3.IsDummy)
			{
				goPathId = 0L;
				return false;
			}
			goPathId = val3.AsLong;
			return goPathId != 0;
		}

		private static bool IsEnemyHazardType(int hazardType)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			HazardType val = (HazardType)hazardType;
			HazardType val2 = val;
			HazardType val3 = val2;
			if ((int)val3 > 1)
			{
				if (val3 - 2 <= 10)
				{
					return false;
				}
				throw new ArgumentOutOfRangeException("hazardType", hazardType, "Unknown HazardType.");
			}
			return true;
		}

		private static bool IsSkippableBundlePath(string path)
		{
			string extension = Path.GetExtension(path);
			return extension.Equals(".json", StringComparison.OrdinalIgnoreCase) || extension.Equals(".hash", StringComparison.OrdinalIgnoreCase) || extension.Equals(".xml", StringComparison.OrdinalIgnoreCase) || extension.Equals(".manifest", StringComparison.OrdinalIgnoreCase) || extension.Equals(".txt", StringComparison.OrdinalIgnoreCase);
		}

		private static string LeafName(string hierarchyPath)
		{
			int num = hierarchyPath.LastIndexOf('/');
			return (num < 0) ? hierarchyPath : hierarchyPath.Substring(num + 1);
		}
	}
	public readonly struct EnemyId : IEquatable<EnemyId>
	{
		public string Value { get; }

		public EnemyId(string value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				throw new ArgumentException("EnemyId cannot be empty.", "value");
			}
			Value = value;
		}

		public bool Equals(EnemyId other)
		{
			return string.Equals(Value, other.Value, StringComparison.Ordinal);
		}

		public override bool Equals(object? obj)
		{
			return obj is EnemyId other && Equals(other);
		}

		public override int GetHashCode()
		{
			return StringComparer.Ordinal.GetHashCode(Value);
		}

		public override string ToString()
		{
			return Value;
		}

		public static bool operator ==(EnemyId left, EnemyId right)
		{
			return left.Equals(right);
		}

		public static bool operator !=(EnemyId left, EnemyId right)
		{
			return !left.Equals(right);
		}
	}
	public readonly struct EnemyRecord
	{
		public EnemyId Id { get; }

		public string DisplayName { get; }

		public int MaxDamageDealt { get; }

		public bool IsBoss { get; }

		public EnemyRecord(EnemyId id, string displayName, int maxDamageDealt, bool isBoss = false)
		{
			Id = id;
			DisplayName = displayName ?? throw new ArgumentNullException("displayName");
			MaxDamageDealt = maxDamageDealt;
			IsBoss = isBoss;
		}
	}
	public enum ScanStatus
	{
		Complete,
		Incomplete
	}
	public sealed class EnemyCatalog
	{
		public static EnemyCatalog Empty { get; } = new EnemyCatalog(Array.Empty<EnemyRecord>());

		public IReadOnlyList<EnemyRecord> Enemies { get; }

		public EnemyCatalog(IEnumerable<EnemyRecord> enemies)
		{
			if (enemies == null)
			{
				throw new ArgumentNullException("enemies");
			}
			Dictionary<string, EnemyRecord> dictionary = new Dictionary<string, EnemyRecord>(StringComparer.Ordinal);
			foreach (EnemyRecord enemy in enemies)
			{
				if (enemy.MaxDamageDealt < 2)
				{
					continue;
				}
				string text = EnemyNames.Canonical(enemy.DisplayName);
				if (!string.IsNullOrWhiteSpace(text))
				{
					EnemyId id = new EnemyId(text);
					if (dictionary.TryGetValue(text, out var value))
					{
						int maxDamageDealt = ((enemy.MaxDamageDealt > value.MaxDamageDealt) ? enemy.MaxDamageDealt : value.MaxDamageDealt);
						dictionary[text] = new EnemyRecord(id, text, maxDamageDealt, value.IsBoss || enemy.IsBoss);
					}
					else
					{
						dictionary[text] = new EnemyRecord(id, text, enemy.MaxDamageDealt, enemy.IsBoss);
					}
				}
			}
			List<EnemyRecord> list = new List<EnemyRecord>(dictionary.Values);
			list.Sort(CompareEnemies);
			Enemies = list;
		}

		private static int CompareEnemies(EnemyRecord left, EnemyRecord right)
		{
			int num = string.CompareOrdinal(left.DisplayName, right.DisplayName);
			return (num != 0) ? num : string.CompareOrdinal(left.Id.Value, right.Id.Value);
		}
	}
	internal static class EnemyNames
	{
		public static string Canonical(string dumpName)
		{
			if (string.IsNullOrWhiteSpace(dumpName))
			{
				return string.Empty;
			}
			string text = dumpName.Trim();
			int num = text.LastIndexOf(" (", StringComparison.Ordinal);
			if (num <= 0 || !text.EndsWith(")", StringComparison.Ordinal))
			{
				return text;
			}
			string text2 = text.Substring(num + 2, text.Length - num - 3);
			if (text2.Length == 0)
			{
				return text;
			}
			foreach (char c in text2)
			{
				if (c < '0' || c > '9')
				{
					return text;
				}
			}
			return text.Substring(0, num);
		}
	}
	public sealed class CatalogCacheFile
	{
		public string GameVersion { get; }

		public ScanStatus Status { get; }

		public EnemyCatalog Catalog { get; }

		public CatalogCacheFile(string gameVersion, ScanStatus status, EnemyCatalog catalog)
		{
			GameVersion = gameVersion ?? throw new ArgumentNullException("gameVersion");
			Status = status;
			Catalog = catalog ?? throw new ArgumentNullException("catalog");
		}
	}
	internal static class CatalogCache
	{
		private sealed class CatalogFileDto
		{
			public string? GameVersion { get; set; }

			public string? Status { get; set; }

			public List<EnemyDto>? Enemies { get; set; }
		}

		private sealed class EnemyDto
		{
			public string? Id { get; set; }

			public string? DisplayName { get; set; }

			public int MaxDamageDealt { get; set; }

			public bool IsBoss { get; set; }
		}

		public static string FileName { get; } = "DoubleDamageConfigurator.enemy-catalog.json";

		public static string TempFileName { get; } = "DoubleDamageConfigurator.enemy-catalog.json.tmp";

		public static CatalogCacheFile? TryLoad(string path)
		{
			if (!File.Exists(path))
			{
				return null;
			}
			try
			{
				string text = File.ReadAllText(path);
				if (text.Length == 0)
				{
					return null;
				}
				CatalogFileDto catalogFileDto = JsonConvert.DeserializeObject<CatalogFileDto>(text);
				if (catalogFileDto == null || string.IsNullOrWhiteSpace(catalogFileDto.GameVersion))
				{
					return null;
				}
				ScanStatus status = ParseStatus(catalogFileDto.Status);
				List<EnemyRecord> list = new List<EnemyRecord>();
				if (catalogFileDto.Enemies != null)
				{
					foreach (EnemyDto enemy in catalogFileDto.Enemies)
					{
						if (enemy != null && !string.IsNullOrWhiteSpace(enemy.Id) && enemy.DisplayName != null)
						{
							string text2 = EnemyNames.Canonical(enemy.DisplayName);
							if (!string.IsNullOrWhiteSpace(text2))
							{
								list.Add(new EnemyRecord(new EnemyId(text2), text2, enemy.MaxDamageDealt, enemy.IsBoss));
							}
						}
					}
				}
				return new CatalogCacheFile(catalogFileDto.GameVersion, status, new EnemyCatalog(list));
			}
			catch (JsonException)
			{
				return null;
			}
			catch (ArgumentOutOfRangeException)
			{
				return null;
			}
			catch (IOException)
			{
				return null;
			}
		}

		public static void WriteIncompletePlaceholder(string path, string gameVersion)
		{
			CatalogFileDto dto = new CatalogFileDto
			{
				GameVersion = gameVersion,
				Status = "Incomplete",
				Enemies = new List<EnemyDto>()
			};
			WriteDto(path, dto);
		}

		public static void WriteCompleteAtomic(string catalogPath, string tempPath, string gameVersion, EnemyCatalog catalog)
		{
			List<EnemyDto> list = new List<EnemyDto>(catalog.Enemies.Count);
			foreach (EnemyRecord enemy in catalog.Enemies)
			{
				list.Add(new EnemyDto
				{
					Id = enemy.Id.Value,
					DisplayName = enemy.DisplayName,
					MaxDamageDealt = enemy.MaxDamageDealt,
					IsBoss = enemy.IsBoss
				});
			}
			CatalogFileDto dto = new CatalogFileDto
			{
				GameVersion = gameVersion,
				Status = "Complete",
				Enemies = list
			};
			WriteDto(tempPath, dto);
			ReplaceCatalog(tempPath, catalogPath);
		}

		private static void WriteDto(string path, CatalogFileDto dto)
		{
			string directoryName = Path.GetDirectoryName(path);
			if (!string.IsNullOrEmpty(directoryName))
			{
				Directory.CreateDirectory(directoryName);
			}
			File.WriteAllText(path, JsonConvert.SerializeObject((object)dto, (Formatting)1));
		}

		private static void ReplaceCatalog(string tempPath, string catalogPath)
		{
			if (File.Exists(catalogPath))
			{
				File.Replace(tempPath, catalogPath, null);
			}
			else
			{
				File.Move(tempPath, catalogPath);
			}
		}

		private static ScanStatus ParseStatus(string? raw)
		{
			if (!(raw == "Complete"))
			{
				if (raw == "Incomplete")
				{
					return ScanStatus.Incomplete;
				}
				throw new ArgumentOutOfRangeException("raw", raw, "Unknown catalog scan status.");
			}
			return ScanStatus.Complete;
		}
	}
	internal static class EnemyListMenu
	{
		private sealed class CatalogSession
		{
			private readonly EnemyCatalog _catalog;

			private readonly EnemySettings _settings;

			private readonly JournalPortraitIndex _journal;

			private readonly Func<ScanUiState> _getScan;

			private readonly TextLabel _status;

			private readonly TextLabel _empty;

			private readonly Slot[] _slots;

			private readonly ScrollingPane _pane;

			private EnemyListQuery _query = new EnemyListQuery(EnemyTab.All, string.Empty);

			private bool _rebinding;

			public FreeGroup Root { get; }

			public CatalogSession(EnemyCatalog catalog, EnemySettings settings, JournalPortraitIndex journal, Func<ScanUiState> getScan, Action onRegenerate, Action<Action> bindStatusRefresh)
			{
				//IL_0055: Unknown result type (might be due to invalid IL or missing references)
				//IL_005b: Expected O, but got Unknown
				//IL_0063: Unknown result type (might be due to invalid IL or missing references)
				//IL_0069: Expected O, but got Unknown
				//IL_0138: Unknown result type (might be due to invalid IL or missing references)
				//IL_013f: Expected O, but got Unknown
				//IL_0142: Unknown result type (might be due to invalid IL or missing references)
				//IL_015b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0166: Unknown result type (might be due to invalid IL or missing references)
				//IL_016d: Expected O, but got Unknown
				//IL_018b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0195: Expected O, but got Unknown
				//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
				//IL_01dd: Expected O, but got Unknown
				//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
				//IL_02e3: Expected O, but got Unknown
				//IL_0306: Unknown result type (might be due to invalid IL or missing references)
				//IL_0311: Unknown result type (might be due to invalid IL or missing references)
				//IL_0318: Expected O, but got Unknown
				//IL_031c: Unknown result type (might be due to invalid IL or missing references)
				//IL_033a: Unknown result type (might be due to invalid IL or missing references)
				CatalogSession catalogSession = this;
				_catalog = catalog;
				_settings = settings;
				_journal = journal;
				_getScan = getScan;
				TextLabel val = new TextLabel(LocalizedText.Raw("On limits that enemy to 1 mask. Off keeps vanilla damage."));
				((MenuElement)val).SetFontSizes((FontSizes)0);
				VerticalGroup val2 = new VerticalGroup();
				val2.Add((IMenuEntity)(object)RegenerateButton(_getScan, delegate
				{
					onRegenerate();
					catalogSession.Rebind();
				}));
				TextInput<string> val3 = new TextInput<string>(LocalizedText.Raw("Search"), (ITextModel<string>)(object)TextModels.ForStrings());
				val3.OnTextValueChanged += delegate(string text)
				{
					if (!catalogSession._rebinding && catalogSession._getScan().Occupancy().ShowRows)
					{
						catalogSession._query = catalogSession._query.WithSearch(text);
						catalogSession.Rebind();
					}
				};
				val2.Add((IMenuEntity)(object)val3);
				ChoiceElement<EnemyTab> val4 = new ChoiceElement<EnemyTab>(LocalizedText.Raw("Show"), (IChoiceModel<EnemyTab>)(object)ChoiceModels.ForNamedValues<EnemyTab>((IEnumerable<ValueTuple<EnemyTab, string>>)new(EnemyTab, string)[4]
				{
					(EnemyTab.All, "All"),
					(EnemyTab.Enemies, "Enemies"),
					(EnemyTab.Bosses, "Bosses"),
					(EnemyTab.Other, "Other")
				}));
				((SelectableValueElement<EnemyTab>)(object)val4).OnValueChanged += delegate(EnemyTab tab)
				{
					if (!catalogSession._rebinding && catalogSession._getScan().Occupancy().ShowRows)
					{
						catalogSession._query = catalogSession._query.WithTab(tab);
						catalogSession.Rebind();
					}
				};
				val2.Add((IMenuEntity)(object)val4);
				FreeGroup val5 = new FreeGroup();
				val5.Add((IMenuEntity)(object)val, Vector2.zero);
				val5.Add((IMenuEntity)(object)val2, new Vector2(0f, 0f - SpacingConstants.VSPACE_SMALL));
				VerticalGroup val6 = new VerticalGroup();
				val6.HideInactiveElements = true;
				_status = new TextLabel(LocalizedText.Raw(getScan().Format()));
				IMenuEntityExtensions[(IMenuEntity)(object)_status] = getScan().Occupancy().ShowBanner;
				val6.Add((IMenuEntity)(object)_status);
				_empty = new TextLabel(LocalizedText.Raw("No enemies match."));
				IMenuEntityExtensions[(IMenuEntity)(object)_empty] = false;
				val6.Add((IMenuEntity)(object)_empty);
				EnemyListView enemyListView = EnemyListQueryApply.Apply(catalog.Enemies, journal, new EnemyListQuery(EnemyTab.All, string.Empty));
				_slots = new Slot[enemyListView.Rows.Count];
				for (int num = 0; num < enemyListView.Rows.Count; num++)
				{
					EnemyListRow row = enemyListView.Rows[num];
					settings.SyncRowOff(row);
					ChoiceElement<bool> val7 = new ChoiceElement<bool>(LocalizedText.Raw(row.Label), (IChoiceModel<bool>)(object)ChoiceModels.ForBool());
					Image portrait = AttachPortrait(val7);
					int slotIndex = num;
					((SelectableValueElement<bool>)(object)val7).OnValueChanged += delegate(bool value)
					{
						catalogSession.OnSlotChanged(slotIndex, value);
					};
					_slots[num] = new Slot(val7, portrait, row);
					val6.Add((IMenuEntity)(object)val7);
				}
				_pane = new ScrollingPane((INavigableMenuEntity)(object)val6);
				_pane.Axes = (ScrollAxes)1;
				_pane.ViewportSize = new Vector2(1920f, SpacingConstants.VSPACE_MEDIUM * 5f);
				FreeGroup val8 = new FreeGroup();
				val8.Add((IMenuEntity)(object)val5, Vector2.zero);
				val8.Add((IMenuEntity)(object)_pane, new Vector2(0f, 0f - ChromeHeight));
				Root = val8;
				bindStatusRefresh(RefreshStatus);
			}

			private void RefreshStatus()
			{
				if (_status.Text != null)
				{
					_status.Text.text = _getScan().Format();
				}
			}

			public void Rebind()
			{
				_rebinding = true;
				try
				{
					EnemyListView enemyListView = EnemyListQueryApply.Apply(_catalog.Enemies, _journal, _query);
					HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
					for (int i = 0; i < enemyListView.Rows.Count; i++)
					{
						EnemyListRow enemyListRow = enemyListView.Rows[i];
						for (int j = 0; j < enemyListRow.Members.Count; j++)
						{
							hashSet.Add(enemyListRow.Members[j].Id.Value);
						}
					}
					EnemyListOccupancy enemyListOccupancy = _getScan().Occupancy();
					if (enemyListOccupancy.ShowBanner && (Object)(object)_status.Text != (Object)null)
					{
						_status.Text.text = _getScan().Format();
					}
					IMenuEntityExtensions[(IMenuEntity)(object)_status] = enemyListOccupancy.ShowBanner;
					IMenuEntityExtensions[(IMenuEntity)(object)_empty] = enemyListOccupancy.ShowRows && enemyListView.IsEmpty;
					for (int k = 0; k < _slots.Length; k++)
					{
						BindSlot(_slots[k], enemyListOccupancy.ShowRows && RowTouches(_slots[k].Row, hashSet));
					}
					_pane.QueueContentResize();
				}
				finally
				{
					_rebinding = false;
				}
			}

			private void BindSlot(Slot slot, bool visible)
			{
				//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
				IMenuEntityExtensions[(IMenuEntity)(object)slot.Choice] = visible;
				if (visible)
				{
					_settings.SyncRowOff(slot.Row);
					slot.Choice.LabelText.text = slot.Row.Label;
					((SelectableValueElement<bool>)(object)slot.Choice).Value = !_settings.IsRowOff(slot.Row);
					if (_journal.TryGetIcon(slot.Row.Primary.DisplayName, out Sprite sprite) && (Object)(object)sprite != (Object)null)
					{
						slot.Portrait.sprite = sprite;
						((Graphic)slot.Portrait).color = Color.white;
					}
					else
					{
						ClearPortrait(slot.Portrait);
					}
				}
			}

			private void OnSlotChanged(int slotIndex, bool value)
			{
				if (!_rebinding && _getScan().Occupancy().ShowRows)
				{
					EnemyListRow row = _slots[slotIndex].Row;
					if (value)
					{
						_settings.SetRowOn(row);
					}
					else
					{
						_settings.SetRowOff(row);
					}
				}
			}

			private static bool RowTouches(EnemyListRow row, HashSet<string> visibleIds)
			{
				for (int i = 0; i < row.Members.Count; i++)
				{
					if (visibleIds.Contains(row.Members[i].Id.Value))
					{
						return true;
					}
				}
				return false;
			}
		}

		private sealed class Slot
		{
			public ChoiceElement<bool> Choice { get; }

			public Image Portrait { get; }

			public EnemyListRow Row { get; }

			public Slot(ChoiceElement<bool> choice, Image portrait, EnemyListRow row)
			{
				Choice = choice;
				Portrait = portrait;
				Row = row;
			}
		}

		private const float PortraitSize = 90f;

		private const float PortraitInset = 8f;

		private const float ChoiceGutter = 200f;

		private static readonly float ChromeHeight = SpacingConstants.VSPACE_SMALL + 3f * SpacingConstants.VSPACE_MEDIUM;

		private static readonly Color PlaceholderColor = new Color(0.12f, 0.12f, 0.12f, 1f);

		public static AbstractMenuScreen Build(EnemyCatalog catalog, EnemySettings settings, Func<ScanUiState> getScan, Action onRegenerate, Action<Action> bindStatusRefresh, ManualLogSource log)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			settings.RebindFrom(catalog);
			CatalogSession catalogSession = new CatalogSession(catalog, settings, new JournalPortraitIndex(log), getScan, onRegenerate, bindStatusRefresh);
			catalogSession.Rebind();
			return (AbstractMenuScreen)new BasicMenuScreen(LocalizedText.Raw("Double Damage Configurator"), (INavigableMenuEntity)(object)catalogSession.Root);
		}

		private static TextButton RegenerateButton(Func<ScanUiState> getScan, Action onRegenerate)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Expected O, but got Unknown
			TextButton val = new TextButton(LocalizedText.Raw("Regenerate list"));
			val.OnSubmit = (Action)Delegate.Combine(val.OnSubmit, (Action)delegate
			{
				if (getScan().Phase != ScanPhase.Running)
				{
					onRegenerate();
				}
			});
			return val;
		}

		private static Image AttachPortrait(ChoiceElement<bool> choice)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Expected O, but got Unknown
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			RectTransform rectTransform = ((Graphic)choice.LabelText).rectTransform;
			GameObject val = new GameObject("Portrait");
			val.transform.SetParent(((Transform)rectTransform).parent, false);
			val.transform.SetAsFirstSibling();
			Image val2 = val.AddComponent<Image>();
			val2.preserveAspect = true;
			((Graphic)val2).raycastTarget = false;
			ClearPortrait(val2);
			RectTransform rectTransform2 = ((Graphic)val2).rectTransform;
			rectTransform2.anchorMin = new Vector2(rectTransform.anchorMin.x, 0.5f);
			rectTransform2.anchorMax = new Vector2(rectTransform.anchorMin.x, 0.5f);
			rectTransform2.pivot = new Vector2(0f, 0.5f);
			rectTransform2.sizeDelta = new Vector2(90f, 90f);
			float x = rectTransform.offsetMin.x;
			rectTransform2.anchoredPosition = new Vector2(x + 8f, 0f);
			rectTransform.offsetMin = new Vector2(x + 8f + 90f, rectTransform.offsetMin.y);
			rectTransform.offsetMax = new Vector2(rectTransform.offsetMax.x - 200f, rectTransform.offsetMax.y);
			return val2;
		}

		private static void ClearPortrait(Image image)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			image.sprite = null;
			((Graphic)image).color = PlaceholderColor;
		}
	}
	public enum EnemyTab
	{
		All,
		Enemies,
		Bosses,
		Other
	}
	public readonly struct JournalMatch
	{
		public int Order { get; }

		public bool IsOther { get; }

		public string LocalizedName { get; }

		public JournalMatch(int order, bool isOther, string localizedName)
		{
			if (order < 0)
			{
				throw new ArgumentOutOfRangeException("order", order, "Journal order cannot be negative.");
			}
			Order = order;
			IsOther = isOther;
			LocalizedName = localizedName ?? string.Empty;
		}
	}
	public interface IJournalIndex
	{
		bool TryMatch(string displayName, out JournalMatch match);
	}
	public sealed class EmptyJournalIndex : IJournalIndex
	{
		public static EmptyJournalIndex Instance { get; } = new EmptyJournalIndex();

		public bool TryMatch(string displayName, out JournalMatch match)
		{
			match = default(JournalMatch);
			return false;
		}
	}
	public readonly struct EnemyListQuery
	{
		public EnemyTab Tab { get; }

		public string Search { get; }

		public EnemyListQuery(EnemyTab tab, string search)
		{
			Tab = tab;
			Search = search ?? string.Empty;
		}

		public EnemyListQuery WithTab(EnemyTab tab)
		{
			return new EnemyListQuery(tab, Search);
		}

		public EnemyListQuery WithSearch(string search)
		{
			return new EnemyListQuery(Tab, search);
		}
	}
	public readonly struct EnemyListRow
	{
		public IReadOnlyList<EnemyRecord> Members { get; }

		public string Label { get; }

		public EnemyRecord Primary => Members[0];

		public bool IsBoss
		{
			get
			{
				for (int i = 0; i < Members.Count; i++)
				{
					if (Members[i].IsBoss)
					{
						return true;
					}
				}
				return false;
			}
		}

		public EnemyListRow(IReadOnlyList<EnemyRecord> members, string label)
		{
			if (members == null || members.Count == 0)
			{
				throw new ArgumentException("EnemyListRow requires at least one member.", "members");
			}
			Members = members;
			Label = label ?? throw new ArgumentNullException("label");
		}
	}
	public readonly struct EnemyListView
	{
		public IReadOnlyList<EnemyListRow> Rows { get; }

		public bool IsEmpty => Rows.Count == 0;

		public EnemyListView(IReadOnlyList<EnemyListRow> rows)
		{
			Rows = rows ?? throw new ArgumentNullException("rows");
		}
	}
	public static class EnemyListQueryApply
	{
		public static EnemyListView Apply(IReadOnlyList<EnemyRecord> enemies, IJournalIndex journal, EnemyListQuery query)
		{
			if (enemies == null)
			{
				throw new ArgumentNullException("enemies");
			}
			if (journal == null)
			{
				throw new ArgumentNullException("journal");
			}
			Dictionary<string, List<(EnemyRecord, JournalMatch)>> dictionary = new Dictionary<string, List<(EnemyRecord, JournalMatch)>>(StringComparer.Ordinal);
			List<EnemyRecord> list = new List<EnemyRecord>();
			foreach (EnemyRecord enemy in enemies)
			{
				if (journal.TryMatch(enemy.DisplayName, out var match) && !string.IsNullOrEmpty(match.LocalizedName))
				{
					if (!dictionary.TryGetValue(match.LocalizedName, out var value))
					{
						value = new List<(EnemyRecord, JournalMatch)>();
						dictionary[match.LocalizedName] = value;
					}
					value.Add((enemy, match));
				}
				else
				{
					list.Add(enemy);
				}
			}
			List<(EnemyListRow, bool, bool)> list2 = new List<(EnemyListRow, bool, bool)>(dictionary.Count + list.Count);
			foreach (KeyValuePair<string, List<(EnemyRecord, JournalMatch)>> item3 in dictionary)
			{
				List<EnemyRecord> list3 = new List<EnemyRecord>(item3.Value.Count);
				for (int i = 0; i < item3.Value.Count; i++)
				{
					list3.Add(item3.Value[i].Item1);
				}
				JournalMatch item = item3.Value[0].Item2;
				list2.Add((new EnemyListRow(list3, item3.Key), true, item.IsOther));
			}
			for (int j = 0; j < list.Count; j++)
			{
				EnemyRecord enemyRecord = list[j];
				list2.Add((new EnemyListRow(new EnemyRecord[1] { enemyRecord }, enemyRecord.DisplayName), false, false));
			}
			List<EnemyListRow> list4 = new List<EnemyListRow>(list2.Count);
			foreach (var (item2, matched, isOther) in list2)
			{
				if (MatchesTab(query.Tab, item2.IsBoss, matched, isOther))
				{
					list4.Add(item2);
				}
			}
			ApplySearch(list4, query.Search);
			list4.Sort(CompareRows);
			return new EnemyListView(list4);
		}

		private static bool MatchesTab(EnemyTab tab, bool isBoss, bool matched, bool isOther)
		{
			return tab switch
			{
				EnemyTab.All => true, 
				EnemyTab.Enemies => matched && !isOther, 
				EnemyTab.Bosses => isBoss, 
				EnemyTab.Other => !matched || isOther, 
				_ => throw new ArgumentOutOfRangeException("tab", tab, "Unknown EnemyTab."), 
			};
		}

		private static void ApplySearch(List<EnemyListRow> filtered, string search)
		{
			string text = search.Trim();
			if (text.Length == 0)
			{
				return;
			}
			for (int num = filtered.Count - 1; num >= 0; num--)
			{
				EnemyListRow row = filtered[num];
				if (!ContainsIgnoreCase(row.Label, text) && !MemberDisplayNameMatches(row, text))
				{
					filtered.RemoveAt(num);
				}
			}
		}

		private static bool MemberDisplayNameMatches(EnemyListRow row, string needle)
		{
			for (int i = 0; i < row.Members.Count; i++)
			{
				if (ContainsIgnoreCase(row.Members[i].DisplayName, needle))
				{
					return true;
				}
			}
			return false;
		}

		private static bool ContainsIgnoreCase(string haystack, string needle)
		{
			return haystack.IndexOf(needle, StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static int CompareRows(EnemyListRow left, EnemyListRow right)
		{
			int num = string.Compare(left.Label, right.Label, StringComparison.OrdinalIgnoreCase);
			return (num != 0) ? num : string.CompareOrdinal(left.Primary.Id.Value, right.Primary.Id.Value);
		}
	}
	public readonly struct EnemySetting
	{
		public EnemyId Id { get; }

		public string DisplayName { get; }

		public EnemySetting(EnemyId id, string displayName)
		{
			Id = id;
			DisplayName = displayName ?? throw new ArgumentNullException("displayName");
		}
	}
	public sealed class EnemySettings
	{
		private sealed class SettingsFileDto
		{
			public List<SettingDto>? Off { get; set; }
		}

		private sealed class SettingDto
		{
			public string? Id { get; set; }

			public string? DisplayName { get; set; }
		}

		private readonly List<EnemySetting> _off = new List<EnemySetting>();

		private readonly Dictionary<string, int> _indexById = new Dictionary<string, int>(StringComparer.Ordinal);

		private readonly string _path;

		public static string FileName { get; } = "DoubleDamageConfigurator.enemy-settings.json";

		public IReadOnlyList<EnemySetting> OffChoices => _off;

		private EnemySettings(string path)
		{
			_path = path;
		}

		public static EnemySettings Load(string path)
		{
			EnemySettings enemySettings = new EnemySettings(path);
			if (!File.Exists(path))
			{
				return enemySettings;
			}
			try
			{
				string text = File.ReadAllText(path);
				if (text.Length == 0)
				{
					return enemySettings;
				}
				SettingsFileDto settingsFileDto = JsonConvert.DeserializeObject<SettingsFileDto>(text);
				if (settingsFileDto?.Off == null)
				{
					return enemySettings;
				}
				foreach (SettingDto item in settingsFileDto.Off)
				{
					if (item != null && !string.IsNullOrWhiteSpace(item.Id) && item.DisplayName != null)
					{
						string text2 = EnemyNames.Canonical(item.DisplayName);
						string value = EnemyNames.Canonical(item.Id);
						if (!string.IsNullOrWhiteSpace(text2) && !string.IsNullOrWhiteSpace(value))
						{
							enemySettings.Upsert(new EnemySetting(new EnemyId(value), text2));
						}
					}
				}
			}
			catch (JsonException)
			{
				return enemySettings;
			}
			catch (IOException)
			{
				return enemySettings;
			}
			return enemySettings;
		}

		public void RebindFrom(EnemyCatalog catalog)
		{
			HashSet<string> hashSet = UniqueDisplayNames(catalog);
			bool flag = false;
			foreach (EnemyRecord enemy in catalog.Enemies)
			{
				if (_indexById.ContainsKey(enemy.Id.Value) || !hashSet.Contains(enemy.DisplayName))
				{
					continue;
				}
				int num = 0;
				int num2 = -1;
				for (int i = 0; i < _off.Count; i++)
				{
					if (string.Equals(_off[i].DisplayName, enemy.DisplayName, StringComparison.Ordinal))
					{
						num++;
						num2 = i;
					}
				}
				if (num == 1)
				{
					EnemySetting enemySetting = _off[num2];
					EnemySetting value = new EnemySetting(enemy.Id, enemySetting.DisplayName);
					_off[num2] = value;
					_indexById.Remove(enemySetting.Id.Value);
					_indexById[value.Id.Value] = num2;
					flag = true;
				}
			}
			if (flag)
			{
				Save();
			}
		}

		public bool IsOff(EnemyId id)
		{
			return _indexById.ContainsKey(id.Value);
		}

		public bool IsRowOff(EnemyListRow row)
		{
			for (int i = 0; i < row.Members.Count; i++)
			{
				if (IsOff(row.Members[i].Id))
				{
					return true;
				}
			}
			return false;
		}

		public void SetOff(EnemyId id, string displayName)
		{
			Upsert(new EnemySetting(id, displayName));
			Save();
		}

		public void SetOn(EnemyId id)
		{
			if (_indexById.TryGetValue(id.Value, out var value))
			{
				_off.RemoveAt(value);
				RebuildIndex();
				Save();
			}
		}

		public void SetRowOff(EnemyListRow row)
		{
			bool flag = false;
			for (int i = 0; i < row.Members.Count; i++)
			{
				EnemyRecord enemyRecord = row.Members[i];
				if (!IsOff(enemyRecord.Id))
				{
					Upsert(new EnemySetting(enemyRecord.Id, enemyRecord.DisplayName));
					flag = true;
				}
			}
			if (flag)
			{
				Save();
			}
		}

		public void SetRowOn(EnemyListRow row)
		{
			bool flag = false;
			for (int i = 0; i < row.Members.Count; i++)
			{
				EnemyId id = row.Members[i].Id;
				if (_indexById.TryGetValue(id.Value, out var value))
				{
					_off.RemoveAt(value);
					RebuildIndex();
					flag = true;
				}
			}
			if (flag)
			{
				Save();
			}
		}

		public void SyncRowOff(EnemyListRow row)
		{
			if (!IsRowOff(row))
			{
				return;
			}
			bool flag = true;
			for (int i = 0; i < row.Members.Count; i++)
			{
				if (!IsOff(row.Members[i].Id))
				{
					flag = false;
					break;
				}
			}
			if (!flag)
			{
				SetRowOff(row);
			}
		}

		public void Save()
		{
			List<SettingDto> list = new List<SettingDto>(_off.Count);
			foreach (EnemySetting item in _off)
			{
				list.Add(new SettingDto
				{
					Id = item.Id.Value,
					DisplayName = item.DisplayName
				});
			}
			SettingsFileDto settingsFileDto = new SettingsFileDto
			{
				Off = list
			};
			string directoryName = Path.GetDirectoryName(_path);
			if (!string.IsNullOrEmpty(directoryName))
			{
				Directory.CreateDirectory(directoryName);
			}
			File.WriteAllText(_path, JsonConvert.SerializeObject((object)settingsFileDto, (Formatting)1));
		}

		private void Upsert(EnemySetting setting)
		{
			if (_indexById.TryGetValue(setting.Id.Value, out var value))
			{
				_off[value] = setting;
				return;
			}
			_indexById[setting.Id.Value] = _off.Count;
			_off.Add(setting);
		}

		private void RebuildIndex()
		{
			_indexById.Clear();
			for (int i = 0; i < _off.Count; i++)
			{
				_indexById[_off[i].Id.Value] = i;
			}
		}

		private static HashSet<string> UniqueDisplayNames(EnemyCatalog catalog)
		{
			Dictionary<string, int> dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
			foreach (EnemyRecord enemy in catalog.Enemies)
			{
				dictionary.TryGetValue(enemy.DisplayName, out var value);
				dictionary[enemy.DisplayName] = value + 1;
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, int> item in dictionary)
			{
				if (item.Value == 1)
				{
					hashSet.Add(item.Key);
				}
			}
			return hashSet;
		}
	}
	public sealed class JournalPortraitIndex : IJournalIndex
	{
		private readonly ManualLogSource _log;

		private readonly Dictionary<string, EnemyJournalRecord> _exact = new Dictionary<string, EnemyJournalRecord>(StringComparer.Ordinal);

		private readonly HashSet<string> _ambiguous = new HashSet<string>(StringComparer.Ordinal);

		private readonly List<(string Key, EnemyJournalRecord Record)> _keys = new List<(string, EnemyJournalRecord)>();

		private readonly Dictionary<EnemyJournalRecord, int> _order = new Dictionary<EnemyJournalRecord, int>();

		private readonly bool _empty;

		private bool _logged;

		public JournalPortraitIndex(ManualLogSource log)
		{
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			_log = log ?? throw new ArgumentNullException("log");
			List<EnemyJournalRecord> list = null;
			try
			{
				list = EnemyJournalManager.GetAllEnemies();
			}
			catch (Exception arg)
			{
				LogOnce($"Enemy journal is unavailable. All dump names will show under Other. {arg}");
				_empty = true;
				return;
			}
			if (list == null || list.Count == 0)
			{
				LogOnce("Enemy journal list is empty. All dump names will show under Other.");
				_empty = true;
				return;
			}
			try
			{
				for (int i = 0; i < list.Count; i++)
				{
					EnemyJournalRecord val = list[i];
					if (!((Object)(object)val == (Object)null))
					{
						_order[val] = i;
						IndexKey(Normalize(((Object)val).name), val);
						IndexKey(Normalize(((object)val.DisplayName/*cast due to .constrained prefix*/).ToString()), val);
					}
				}
			}
			catch (Exception arg2)
			{
				_exact.Clear();
				_ambiguous.Clear();
				_keys.Clear();
				_order.Clear();
				LogOnce($"Enemy journal index failed. All dump names will show under Other. {arg2}");
				_empty = true;
				return;
			}
			_empty = _order.Count == 0;
			if (_empty)
			{
				LogOnce("Enemy journal list is empty. All dump names will show under Other.");
			}
		}

		public bool TryMatch(string displayName, out JournalMatch match)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Invalid comparison between Unknown and I4
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			match = default(JournalMatch);
			if (_empty)
			{
				return false;
			}
			try
			{
				if (!TryResolve(displayName, out EnemyJournalRecord record, out int order))
				{
					return false;
				}
				match = new JournalMatch(order, (int)record.RecordType == 1, ((object)record.DisplayName/*cast due to .constrained prefix*/).ToString());
				return true;
			}
			catch (Exception arg)
			{
				LogOnce($"Enemy journal match failed. Treating the name as unmatched / Other. {arg}");
				match = default(JournalMatch);
				return false;
			}
		}

		public bool TryGetIcon(string displayName, out Sprite sprite)
		{
			sprite = null;
			if (_empty)
			{
				return false;
			}
			try
			{
				if (!TryResolve(displayName, out EnemyJournalRecord record, out int _))
				{
					return false;
				}
				Sprite iconSprite = record.IconSprite;
				if ((Object)(object)iconSprite != (Object)null)
				{
					sprite = iconSprite;
					return true;
				}
				Sprite enemySprite = record.EnemySprite;
				if ((Object)(object)enemySprite != (Object)null)
				{
					sprite = enemySprite;
					return true;
				}
				return false;
			}
			catch (Exception arg)
			{
				LogOnce($"Enemy journal icon failed. Using the placeholder. {arg}");
				sprite = null;
				return false;
			}
		}

		private bool TryResolve(string displayName, out EnemyJournalRecord record, out int order)
		{
			record = null;
			order = 0;
			if (string.IsNullOrEmpty(displayName))
			{
				return false;
			}
			EnemyJournalRecord val = FindByGetRecord(displayName);
			if ((Object)(object)val == (Object)null)
			{
				val = FindExact(displayName);
			}
			if ((Object)(object)val == (Object)null)
			{
				val = FindFuzzy(displayName);
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			record = val;
			order = OrderOf(record);
			return true;
		}

		private static EnemyJournalRecord? FindByGetRecord(string displayName)
		{
			EnemyJournalRecord record = EnemyJournalManager.GetRecord(displayName);
			if ((Object)(object)record != (Object)null)
			{
				return record;
			}
			record = EnemyJournalManager.GetRecord(displayName.Replace(" ", string.Empty));
			if ((Object)(object)record != (Object)null)
			{
				return record;
			}
			record = EnemyJournalManager.GetRecord(displayName.Replace(' ', '_'));
			if ((Object)(object)record != (Object)null)
			{
				return record;
			}
			return null;
		}

		private EnemyJournalRecord? FindExact(string displayName)
		{
			string text = Normalize(displayName);
			if (text.Length == 0)
			{
				return null;
			}
			if (_exact.TryGetValue(text, out EnemyJournalRecord value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			return null;
		}

		private EnemyJournalRecord? FindFuzzy(string displayName)
		{
			string text = Normalize(displayName);
			if (text.Length == 0)
			{
				return null;
			}
			EnemyJournalRecord val = null;
			foreach (var (text2, val2) in _keys)
			{
				if (text2.Length != 0 && !((Object)(object)val2 == (Object)null) && FuzzyHit(text2, text))
				{
					if ((Object)(object)val == (Object)null)
					{
						val = val2;
					}
					else if ((Object)(object)val != (Object)(object)val2)
					{
						return null;
					}
				}
			}
			return val;
		}

		private static bool FuzzyHit(string key, string needle)
		{
			return key.Length >= 4 && needle.Length >= 4 && (key.StartsWith(needle, StringComparison.Ordinal) || needle.StartsWith(key, StringComparison.Ordinal));
		}

		private int OrderOf(EnemyJournalRecord record)
		{
			int value;
			return _order.TryGetValue(record, out value) ? value : _order.Count;
		}

		private void IndexKey(string key, EnemyJournalRecord record)
		{
			_keys.Add((key, record));
			if (key.Length == 0 || _ambiguous.Contains(key))
			{
				return;
			}
			if (_exact.TryGetValue(key, out EnemyJournalRecord value))
			{
				if (!((Object)(object)value == (Object)(object)record))
				{
					_exact.Remove(key);
					_ambiguous.Add(key);
				}
			}
			else
			{
				_exact[key] = record;
			}
		}

		private static string Normalize(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return string.Empty;
			}
			return value.ToLowerInvariant().Replace(" ", string.Empty).Replace("_", string.Empty);
		}

		private void LogOnce(string message)
		{
			if (!_logged)
			{
				_logged = true;
				_log.LogWarning((object)message);
			}
		}
	}
	public enum ScanPhase
	{
		Ready,
		Running,
		Failed
	}
	public readonly struct ScanUiState
	{
		public ScanPhase Phase { get; }

		public int EnemyCount { get; }

		public int FilesDone { get; }

		public int FilesTotal { get; }

		public ScanUiState(ScanPhase phase, int enemyCount)
			: this(phase, enemyCount, 0, 0)
		{
		}

		public ScanUiState(ScanPhase phase, int enemyCount, int filesDone, int filesTotal)
		{
			if (enemyCount < 0)
			{
				throw new ArgumentOutOfRangeException("enemyCount", enemyCount, "Enemy count cannot be negative.");
			}
			if (filesDone < 0)
			{
				throw new ArgumentOutOfRangeException("filesDone", filesDone, "Files done cannot be negative.");
			}
			if (filesTotal < 0)
			{
				throw new ArgumentOutOfRangeException("filesTotal", filesTotal, "Files total cannot be negative.");
			}
			if (filesTotal > 0 && filesDone > filesTotal)
			{
				throw new ArgumentOutOfRangeException("filesDone", filesDone, "Files done cannot exceed files total.");
			}
			if ((uint)phase > 2u)
			{
				throw new ArgumentOutOfRangeException("phase", phase, "Unknown ScanPhase.");
			}
			Phase = phase;
			EnemyCount = enemyCount;
			FilesDone = filesDone;
			FilesTotal = filesTotal;
		}

		public string Format()
		{
			switch (Phase)
			{
			case ScanPhase.Ready:
				return (EnemyCount == 1) ? "List ready. 1 enemy." : ("List ready. " + EnemyCount + " enemies.");
			case ScanPhase.Running:
			{
				if (FilesTotal == 0)
				{
					return "Scanning game files…";
				}
				int num = FilesDone * 100 / FilesTotal;
				return "Scanning… " + num + "% (" + FilesDone + " / " + FilesTotal + ")";
			}
			case ScanPhase.Failed:
				return "Scan incomplete. Try regenerate.";
			default:
				throw new ArgumentOutOfRangeException("Phase", Phase, "Unknown ScanPhase.");
			}
		}

		public EnemyListOccupancy Occupancy()
		{
			return Phase switch
			{
				ScanPhase.Ready => new EnemyListOccupancy(showBanner: false, showRows: true), 
				ScanPhase.Running => new EnemyListOccupancy(showBanner: true, showRows: false), 
				ScanPhase.Failed => new EnemyListOccupancy(showBanner: true, showRows: true), 
				_ => throw new ArgumentOutOfRangeException("Phase", Phase, "Unknown ScanPhase."), 
			};
		}
	}
	public readonly struct EnemyListOccupancy
	{
		public bool ShowBanner { get; }

		public bool ShowRows { get; }

		public EnemyListOccupancy(bool showBanner, bool showRows)
		{
			ShowBanner = showBanner;
			ShowRows = showRows;
		}
	}
	internal static class TakeDamageHook
	{
		[HarmonyPatch(typeof(HeroController), "TakeDamage", new Type[]
		{
			typeof(GameObject),
			typeof(CollisionSide),
			typeof(int),
			typeof(HazardType),
			typeof(DamagePropertyFlags)
		})]
		private static class Patch
		{
			[HarmonyPrefix]
			private static void Prefix(GameObject go, ref int damageAmount, HazardType hazardType)
			{
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0013: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Invalid comparison between Unknown and I4
				if (damageAmount > 1 && ((int)hazardType == 0 || (int)hazardType == 1))
				{
					EnemySettings settings = Settings;
					if (settings != null && TryResolve(go, out var id))
					{
						damageAmount = DamageCap.Apply(damageAmount, DamageCap.ShouldCap(resolvedToCatalog: true, settings.IsOff(id)));
					}
				}
			}
		}

		internal static EnemySettings? Settings;

		private static HashSet<string> _catalogIds = new HashSet<string>(StringComparer.Ordinal);

		internal static void SetCatalog(EnemyCatalog catalog)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (EnemyRecord enemy in catalog.Enemies)
			{
				hashSet.Add(enemy.Id.Value);
			}
			_catalogIds = hashSet;
		}

		private static bool TryResolve(GameObject go, out EnemyId id)
		{
			id = default(EnemyId);
			if ((Object)(object)go == (Object)null)
			{
				return false;
			}
			Transform val = go.transform;
			while ((Object)(object)val != (Object)null)
			{
				if ((Object)(object)((Component)val).GetComponent<HealthManager>() == (Object)null)
				{
					val = val.parent;
					continue;
				}
				string text = EnemyNames.Canonical(((Object)((Component)val).gameObject).name);
				if (text.Length == 0 || !_catalogIds.Contains(text))
				{
					return false;
				}
				id = new EnemyId(text);
				return true;
			}
			return false;
		}
	}
}