Decompiled source of NineSolsQoL v1.1.0

NineSolsQoL.dll

Decompiled 5 minutes ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Cysharp.Threading.Tasks;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using NineSolsQoL.Core;
using NineSolsQoL.Features.Abilities.EarlyAirDash;
using NineSolsQoL.Features.Abilities.FastHeal;
using NineSolsQoL.Features.Combat.DashInvulnerability;
using NineSolsQoL.Features.Combat.ParryFromBehind;
using NineSolsQoL.Features.Combat.PerfectParryWindow;
using NineSolsQoL.Features.Death.NoDeathPenalty;
using NineSolsQoL.Features.Death.RespawnNearDeath;
using NineSolsQoL.Features.Hud.BossHPNumbers;
using NineSolsQoL.Features.Loot.JinMultipliers;
using NineSolsQoL.Features.Loot.LootMagnet;
using NineSolsQoL.Features.Travel.EarlyTeleport;
using NineSolsQoL.Features.Travel.TeleportFromAnyNode;
using RCGMaker.Core;
using RCGSetting;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
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("NineSolsQoL")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Quality of life tweaks for Nine Sols")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+1ac071bbc3df2d8414ad0b00ab7ea705d7788826")]
[assembly: AssemblyProduct("Nine Sols QoL")]
[assembly: AssemblyTitle("NineSolsQoL")]
[assembly: AssemblyVersion("1.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
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 NineSolsQoL
{
	internal static class Log
	{
		private static ManualLogSource? source;

		public static bool DebugEnabled => DebugTools.DebugLogging?.Value ?? false;

		public static void Init(ManualLogSource logSource)
		{
			source = logSource;
		}

		public static void Info(object data)
		{
			ManualLogSource? obj = source;
			if (obj != null)
			{
				obj.LogInfo(data);
			}
		}

		public static void Warning(object data)
		{
			ManualLogSource? obj = source;
			if (obj != null)
			{
				obj.LogWarning(data);
			}
		}

		public static void Error(object data)
		{
			ManualLogSource? obj = source;
			if (obj != null)
			{
				obj.LogError(data);
			}
		}

		public static void Debug(object data)
		{
			if (DebugEnabled)
			{
				ManualLogSource? obj = source;
				if (obj != null)
				{
					obj.LogInfo(data);
				}
			}
		}
	}
	[BepInPlugin("diegorv.NineSolsQoL", "Nine Sols QoL", "1.1.0")]
	public class Plugin : BaseUnityPlugin
	{
		private readonly List<Harmony> harmonies = new List<Harmony>();

		private readonly HashSet<string> failed = new HashSet<string>();

		private bool debugToolsFailed;

		private void Awake()
		{
			Log.Init(((BaseUnityPlugin)this).Logger);
			RegisterLifeCycle();
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
			Init("Debug", DebugTools.Init);
			Init("EarlyAirDash", EarlyAirDashFeature.Init);
			Init("FastHeal", FastHealFeature.Init);
			Init("PerfectParryWindow", PerfectParryWindowFeature.Init);
			Init("ParryFromBehind", ParryFromBehindFeature.Init);
			Init("DashInvulnerability", DashInvulnerabilityFeature.Init);
			Init("RespawnNearDeath", RespawnNearDeathFeature.Init);
			Init("NoDeathPenalty", NoDeathPenaltyFeature.Init);
			Init("BossHPNumbers", BossHPNumbersFeature.Init);
			Init("LootMagnet", LootMagnetFeature.Init);
			Init("JinMultipliers", JinMultipliersFeature.Init);
			Init("TeleportFromAnyNode", TeleportFromAnyNodeFeature.Init);
			Init("EarlyTeleport", EarlyTeleportFeature.Init);
			((BaseUnityPlugin)this).Config.SaveOnConfigSet = true;
			try
			{
				((BaseUnityPlugin)this).Config.Save();
			}
			catch (Exception ex)
			{
				Log.Warning("Could not save " + ((BaseUnityPlugin)this).Config.ConfigFilePath + ", the settings still apply: " + ex.Message);
			}
			Log.Info("Nine Sols QoL 1.1.0 loaded on '" + ((Object)((Component)this).gameObject).name + "'");
		}

		private void Init(string feature, Action<ConfigFile> init)
		{
			try
			{
				init(((BaseUnityPlugin)this).Config);
			}
			catch (Exception arg)
			{
				failed.Add(feature);
				Log.Error($"{feature} is off because its setup failed: {arg}");
			}
		}

		private void Start()
		{
			Patch("EarlyAirDash", typeof(EarlyAirDashPatches));
			Patch("FastHeal", typeof(FastHealPatches));
			Patch("PerfectParryWindow", typeof(PerfectParryWindowPatches), "supidowagon.PerfectParry", "nozwock.PerfectParry");
			Patch("ParryFromBehind", typeof(ParryFromBehindPatches), "supidowagon.AnyDirectionParry");
			Patch("DashInvulnerability", typeof(DashInvulnerabilityPatches));
			Patch("RespawnNearDeath", typeof(RespawnNearDeathPatches));
			Patch("NoDeathPenalty", new Type[2]
			{
				typeof(NoDeathPenaltyPatches),
				typeof(NoDeathPenaltyTranspiler)
			}, "nozwock.NoDeathPenalty");
			Patch("LootMagnet", typeof(LootMagnetPatches), "nozwock.FarawayPickup");
			Patch("JinMultipliers", typeof(JinMultipliersPatches), "JinMultiplier");
			Patch("TeleportFromAnyNode", typeof(TeleportFromAnyNodePatches), "nozwock.TeleportFromAnyNode", "TeleportFromAnywhere");
			Patch("EarlyTeleport", typeof(EarlyTeleportPatches));
			BossHPNumbersFeature.Active = !failed.Contains("BossHPNumbers") && !IsReplaced("BossHPNumbers", "HPNumbers");
		}

		private void Update()
		{
			if (debugToolsFailed || failed.Contains("Debug"))
			{
				return;
			}
			try
			{
				DebugTools.Update();
			}
			catch (Exception arg)
			{
				debugToolsFailed = true;
				Log.Error($"The debug tools (F9 dump, [hitch] lines) are off until the game restarts, after an error: {arg}");
			}
		}

		private void LateUpdate()
		{
			BossHPNumbersFeature.LateUpdate();
		}

		private void OnDestroy()
		{
			BossHPNumbersFeature.Destroy();
			foreach (Harmony harmony in harmonies)
			{
				Unpatch(harmony);
			}
			Log.Info("Nine Sols QoL unloaded");
		}

		private static void Unpatch(Harmony harmony)
		{
			try
			{
				harmony.UnpatchSelf();
			}
			catch (Exception arg)
			{
				Log.Error($"Undoing the patches of {harmony.Id} failed: {arg}");
			}
		}

		private static bool IsReplaced(string feature, params string[] replacedMods)
		{
			string text = replacedMods.FirstOrDefault(Chainloader.PluginInfos.ContainsKey);
			if (text == null)
			{
				return false;
			}
			Log.Warning(feature + " is off because " + text + " is loaded and does the same");
			return true;
		}

		private void Patch(string feature, Type patches, params string[] replacedMods)
		{
			Patch(feature, new Type[1] { patches }, replacedMods);
		}

		private void Patch(string feature, Type[] patches, params string[] replacedMods)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			if (failed.Contains(feature) || IsReplaced(feature, replacedMods))
			{
				return;
			}
			Harmony val = new Harmony("diegorv.NineSolsQoL." + feature);
			harmonies.Add(val);
			try
			{
				foreach (Type type in patches)
				{
					List<MethodInfo> list = val.CreateClassProcessor(type, true).Patch();
					if (list == null || list.Count == 0)
					{
						throw new InvalidOperationException(type.Name + " patched nothing: no [HarmonyPatch] method declared in it");
					}
				}
			}
			catch (Exception arg)
			{
				Log.Error($"{feature} is off because patching the game failed: {arg}");
				Unpatch(val);
				return;
			}
			Log.Debug($"{feature}: {val.GetPatchedMethods().Count()} game method(s) patched");
		}

		private void RegisterLifeCycle()
		{
			Keep(((Component)this).gameObject);
			GameObject managerObject = Chainloader.ManagerObject;
			if ((Object)(object)managerObject != (Object)null && (Object)(object)managerObject != (Object)(object)((Component)this).gameObject)
			{
				Keep(managerObject);
			}
		}

		private static void Keep(GameObject target)
		{
			try
			{
				((Object)target).hideFlags = (HideFlags)61;
				RCGLifeCycle.DontDestroyForever(target);
				Log.Info("Registered '" + ((Object)target).name + "' in RCGLifeCycle");
			}
			catch (Exception arg)
			{
				Log.Error($"Could not keep '{((Object)target).name}' alive: {arg}");
			}
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "diegorv.NineSolsQoL";

		public const string PLUGIN_NAME = "Nine Sols QoL";

		public const string PLUGIN_VERSION = "1.1.0";
	}
}
namespace NineSolsQoL.Features.Travel
{
	internal static class TeleportMenus
	{
		public static TeleportPointData? SittingNode(GameCore core)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			Player player = core.player;
			if ((Object)(object)player == (Object)null || (int)player.CurrentStateType != 46)
			{
				return null;
			}
			SavePanel savePanelUiController = core.savePanelUiController;
			SavePoint currentSavePointGameObjectOnScene = savePanelUiController.CurrentSavePointGameObjectOnScene;
			TeleportPointData currentSavePoint = savePanelUiController.CurrentSavePoint;
			if ((Object)(object)currentSavePointGameObjectOnScene == (Object)null || (Object)(object)currentSavePoint == (Object)null || (Object)(object)currentSavePointGameObjectOnScene.BindingTeleportPoint != (Object)(object)currentSavePoint || (Object)(object)currentSavePointGameObjectOnScene.fluteCutScene == (Object)null)
			{
				return null;
			}
			return currentSavePoint;
		}

		public static bool PlayerIsSitting()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			Player i = Player.i;
			if ((Object)(object)i != (Object)null)
			{
				return (int)i.CurrentStateType == 46;
			}
			return false;
		}

		public static bool IsOnTeleportButton(AbstractConditionComp condition, GameCore core)
		{
			UIControlButton teleportButton = core.savePanelUiController.TeleportButton;
			if ((Object)(object)teleportButton == (Object)null)
			{
				return false;
			}
			Transform transform = ((Component)condition).transform;
			if (!((Object)(object)transform == (Object)(object)((Component)teleportButton).transform))
			{
				return (Object)(object)transform.parent == (Object)(object)((Component)teleportButton).transform;
			}
			return true;
		}

		public static bool IsOnTeleportTab(AbstractConditionComp condition)
		{
			UITabsItem componentInParent = ((Component)condition).GetComponentInParent<UITabsItem>(true);
			if ((Object)(object)componentInParent != (Object)null)
			{
				return IsTeleportTab(componentInParent);
			}
			return false;
		}

		public static UITabsItem? TeleportTab()
		{
			if (!SingletonBehaviour<UIManager>.IsAvailable())
			{
				return null;
			}
			MenuUIPanel menuUI = SingletonBehaviour<UIManager>.Instance.menuUI;
			if ((Object)(object)menuUI == (Object)null || (Object)(object)menuUI.tabsUI == (Object)null)
			{
				return null;
			}
			UITabsItem[] componentsInChildren = ((Component)menuUI.tabsUI).GetComponentsInChildren<UITabsItem>(true);
			foreach (UITabsItem val in componentsInChildren)
			{
				if (IsTeleportTab(val))
				{
					return val;
				}
			}
			return null;
		}

		public static bool TabMenuAvailable()
		{
			if (!SingletonBehaviour<UIManager>.IsAvailable())
			{
				return false;
			}
			ScriptableDataBool isTabMenuAvailable = SingletonBehaviour<UIManager>.Instance.IsTabMenuAvailable;
			if ((Object)(object)isTabMenuAvailable != (Object)null)
			{
				return isTabMenuAvailable.CurrentValue;
			}
			return false;
		}

		public static PlayerAbilityData? TeleportAbility()
		{
			UITabsItem val = TeleportTab();
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			AbilityActivatedCondition[] componentsInChildren = ((Component)val).GetComponentsInChildren<AbilityActivatedCondition>(true);
			foreach (AbilityActivatedCondition val2 in componentsInChildren)
			{
				if ((Object)(object)val2.ability.AbilityData != (Object)null)
				{
					return val2.ability.AbilityData;
				}
			}
			return null;
		}

		public static bool PavilionButtonValid(GameCore core)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Invalid comparison between Unknown and I4
			TeleportPointData val = (((Object)(object)core.allTeleportPoints != (Object)null) ? core.allTeleportPoints.AGSavePoint : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			TeleportToSavePointAction[] componentsInChildren = ((Component)core.savePanelUiController).GetComponentsInChildren<TeleportToSavePointAction>(true);
			foreach (TeleportToSavePointAction val2 in componentsInChildren)
			{
				if ((Object)(object)val2.teleportPointData != (Object)(object)val || (int)val2.teleportCutscene != 1)
				{
					continue;
				}
				UIControlButton componentInParent = ((Component)val2).GetComponentInParent<UIControlButton>(true);
				if (!((Object)(object)componentInParent == (Object)null))
				{
					if (componentInParent.IsAllValid)
					{
						return ConditionHelper.IsAllValid(((AbstractStateAction)val2).conditions);
					}
					return false;
				}
			}
			return false;
		}

		private static bool IsTeleportTab(UITabsItem tab)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			if ((int)tab.PanelType == 0 && SingletonBehaviour<UIManager>.IsAvailable() && (Object)(object)tab.bindPanel != (Object)null)
			{
				return (Object)(object)tab.bindPanel == (Object)(object)SingletonBehaviour<UIManager>.Instance.teleportPanel;
			}
			return false;
		}
	}
}
namespace NineSolsQoL.Features.Travel.TeleportFromAnyNode
{
	internal static class TeleportFromAnyNodeFeature
	{
		public static ConfigEntry<bool> Enabled;

		public static void Init(ConfigFile config)
		{
			Enabled = new ConfigSection(config, "TeleportFromAnyNode", "--------- Travel ---------", "Teleport from any node").Bind("Enabled", defaultValue: true, "Teleport from every Root Node that shows the Pavilion button, not just from the Pavilion's: its Teleport button opens the list of Root Nodes. The node you leave becomes the Last Node, as when you go to the Pavilion. Needs the game's teleport, which the story unlocks later on (or Early teleport). Off: the game's rule, teleport only from the Pavilion's Root Node");
			DebugTools.AddDumpSection(Describe);
		}

		public static bool ShowTeleport(TeleportPointMatchSavePanelCondition condition)
		{
			try
			{
				if (!Enabled.Value || ((AbstractConditionComp)condition).FinalResultInverted || !SingletonBehaviour<GameCore>.IsAvailable())
				{
					return false;
				}
				GameCore instance = SingletonBehaviour<GameCore>.Instance;
				TeleportPointCollection allTeleportPoints = instance.allTeleportPoints;
				if ((Object)(object)instance.savePanelUiController == (Object)null || (Object)(object)allTeleportPoints == (Object)null || (Object)(object)condition.bindedTeleportPointData != (Object)(object)allTeleportPoints.AGSavePoint)
				{
					return false;
				}
				TeleportPointData val = TeleportMenus.SittingNode(instance);
				if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)allTeleportPoints.AGSavePoint)
				{
					return false;
				}
				bool flag = TeleportMenus.IsOnTeleportButton((AbstractConditionComp)(object)condition, instance);
				if (flag ? (!TeleportMenus.TabMenuAvailable()) : (!TeleportMenus.IsOnTeleportTab((AbstractConditionComp)(object)condition)))
				{
					return false;
				}
				if (!TeleportMenus.PavilionButtonValid(instance))
				{
					return false;
				}
				Log.Debug("[teleport] the " + (flag ? "Teleport button" : "Teleport tab") + " may show at '" + ((Object)val).name + "' (its Pavilion button is shown)");
				return true;
			}
			catch (Exception arg)
			{
				Log.Error($"TeleportFromAnyNode failed, the Teleport stays as the game decides: {arg}");
				return false;
			}
		}

		public static void RememberNodeLeft()
		{
			try
			{
				if (!Enabled.Value || !SingletonBehaviour<GameCore>.IsAvailable())
				{
					return;
				}
				GameCore instance = SingletonBehaviour<GameCore>.Instance;
				TeleportPointCollection allTeleportPoints = instance.allTeleportPoints;
				TeleportPointData val = (((Object)(object)instance.savePanelUiController != (Object)null) ? TeleportMenus.SittingNode(instance) : null);
				if (!((Object)(object)val == (Object)null) && !((Object)(object)allTeleportPoints == (Object)null) && !((Object)(object)val == (Object)(object)allTeleportPoints.AGSavePoint))
				{
					if (string.IsNullOrEmpty(((GameFlagBase)val).FinalSaveID) || !((GameFlagBaseCollection<TeleportPointData>)(object)allTeleportPoints).gameFlagDataList.Contains(val))
					{
						Log.Warning("TeleportFromAnyNode: '" + ((Object)val).name + "' is not in the game's list of Root Nodes, so it does not become the Last Node");
						return;
					}
					((GameFlagBaseCollection<TeleportPointData>)(object)allTeleportPoints).SetLastSaveItem((GameFlagDescriptable)(object)val);
					Log.Debug("[teleport] '" + ((Object)val).name + "' is the Last Node now");
				}
			}
			catch (Exception arg)
			{
				Log.Error($"TeleportFromAnyNode could not store the Last Node: {arg}");
			}
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			TeleportPointData val = TeleportMenus.SittingNode(core);
			UIControlButton teleportButton = core.savePanelUiController.TeleportButton;
			TeleportPointData val2 = (((Object)(object)core.allTeleportPoints != (Object)null) ? core.allTeleportPoints.LastSavedSavePoint : null);
			return new string[1] { string.Format("TeleportFromAnyNode: enabled={0} sitting at {1} ", Enabled.Value, ((Object)(object)val != (Object)null) ? ("'" + ((Object)val).name + "'") : "no Root Node") + "pavilion button valid=" + (((Object)(object)val != (Object)null) ? TeleportMenus.PavilionButtonValid(core).ToString() : "-") + " " + $"teleport button shown={(Object)(object)teleportButton != (Object)null && ((Component)teleportButton).gameObject.activeInHierarchy} " + "last node=" + (((Object)(object)val2 != (Object)null) ? ("'" + ((Object)val2).name + "'") : "none") };
		}
	}
	[HarmonyPatch]
	internal static class TeleportFromAnyNodePatches
	{
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		private static void AfterPavilionCheck(TeleportPointMatchSavePanelCondition __instance, ref bool __result)
		{
			if (!__result && TeleportFromAnyNodeFeature.ShowTeleport(__instance))
			{
				__result = true;
			}
		}

		[HarmonyPatch(typeof(TeleportPointButton), "SubmitImplementation")]
		[HarmonyPrefix]
		private static void BeforeTeleport()
		{
			TeleportFromAnyNodeFeature.RememberNodeLeft();
		}
	}
}
namespace NineSolsQoL.Features.Travel.EarlyTeleport
{
	internal static class EarlyTeleportFeature
	{
		public static ConfigEntry<bool> Enabled;

		public static void Init(ConfigFile config)
		{
			Enabled = new ConfigSection(config, "EarlyTeleport", "--------- Travel ---------", "Early teleport").Bind("Enabled", defaultValue: false, "Use the game's teleport before the story unlocks it: the Teleport button shows at the Pavilion's Root Node, and with Teleport from any node at every Root Node that shows the Pavilion button. You can only go to Root Nodes you have activated. Nothing is unlocked in your save, and once the game gives you the teleport its own rules apply. Off: the game's rule, no teleport until the story unlocks it");
			DebugTools.AddDumpSection(Describe);
		}

		public static bool UnlockTab(AbilityActivatedCondition condition)
		{
			try
			{
				if (!Enabled.Value || ((AbstractConditionComp)condition).FinalResultInverted)
				{
					return false;
				}
				PlayerAbilityData abilityData = condition.ability.AbilityData;
				if ((Object)(object)abilityData == (Object)null || ((GameFlagDescriptable)abilityData).IsAcquired || !TeleportMenus.PlayerIsSitting() || !TeleportMenus.IsOnTeleportTab((AbstractConditionComp)(object)condition))
				{
					return false;
				}
				Log.Debug("[early teleport] the Teleport tab may show: '" + ((Object)abilityData).name + "' is not acquired yet");
				return true;
			}
			catch (Exception arg)
			{
				Log.Error($"EarlyTeleport failed, the Teleport tab stays as the game decides: {arg}");
				return false;
			}
		}

		public static bool UnlockButton(GameFlagPropertyCondition condition)
		{
			try
			{
				if (!Enabled.Value || ((AbstractConditionComp)condition).FinalResultInverted || !SingletonBehaviour<GameCore>.IsAvailable())
				{
					return false;
				}
				GameCore instance = SingletonBehaviour<GameCore>.Instance;
				if ((Object)(object)instance.savePanelUiController == (Object)null || !TeleportMenus.IsOnTeleportButton((AbstractConditionComp)(object)condition, instance) || !TeleportMenus.TabMenuAvailable())
				{
					return false;
				}
				PlayerAbilityData val = TeleportMenus.TeleportAbility();
				FlagFieldBoolEntry fieldBool = condition.FieldBool;
				if ((Object)(object)val == (Object)null || ((GameFlagDescriptable)val).IsAcquired || fieldBool == null || fieldBool.IsResultInverted || (Object)(object)((FlagFieldEntry<bool>)(object)fieldBool).flagBase != (Object)(object)val)
				{
					return false;
				}
				Log.Debug("[early teleport] the Teleport button may show: '" + ((Object)val).name + "' is not acquired yet");
				return true;
			}
			catch (Exception arg)
			{
				Log.Error($"EarlyTeleport failed, the Teleport button stays as the game decides: {arg}");
				return false;
			}
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			PlayerAbilityData ability = TeleportMenus.TeleportAbility();
			string arg = (((Object)(object)ability != (Object)null) ? $"'{((Object)ability).name}' acquired={((GameFlagDescriptable)ability).IsAcquired}" : "not found");
			UIControlButton teleportButton = core.savePanelUiController.TeleportButton;
			string arg2 = (((Object)(object)teleportButton == (Object)null) ? "no button" : string.Join(", ", from c in ((Component)teleportButton).GetComponentsInChildren<GameFlagPropertyCondition>(true)
				where !((AbstractConditionComp)c).FinalResultInverted && TeleportMenus.IsOnTeleportButton((AbstractConditionComp)(object)c, core)
				select "'" + ((Object)c).name + "' " + DescribeFlag(c.FieldBool, ability)));
			return new string[1] { $"EarlyTeleport: enabled={Enabled.Value} ability {arg} unlock flag {arg2}" };
		}

		private static string DescribeFlag(FlagFieldBoolEntry? entry, PlayerAbilityData? ability)
		{
			if (entry == null)
			{
				return "none";
			}
			Object flagBase = (Object)(object)((FlagFieldEntry<bool>)(object)entry).flagBase;
			return string.Format("{0}.{1}={2} ", (flagBase != (Object)null) ? ("'" + flagBase.name + "'") : "none", ((FlagFieldEntry<bool>)(object)entry).fieldName, ((FlagFieldEntry<bool>)(object)entry).Value) + $"inverted={entry.IsResultInverted} isTheAbility={flagBase != (Object)null && flagBase == (Object)(object)ability}";
		}
	}
	[HarmonyPatch]
	internal static class EarlyTeleportPatches
	{
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		private static void AfterAbilityCheck(AbilityActivatedCondition __instance, ref bool __result)
		{
			if (!__result && EarlyTeleportFeature.UnlockTab(__instance))
			{
				__result = true;
			}
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		private static void AfterFlagCheck(GameFlagPropertyCondition __instance, ref bool __result)
		{
			if (!__result && EarlyTeleportFeature.UnlockButton(__instance))
			{
				__result = true;
			}
		}
	}
}
namespace NineSolsQoL.Features.Loot.LootMagnet
{
	internal static class LootMagnetFeature
	{
		public static ConfigEntry<bool> Enabled = null;

		public static ConfigEntry<float> Delay = null;

		private static readonly Dictionary<Type, int> flights = new Dictionary<Type, int>();

		private static int playerFrame = -1;

		private static Player? playerToFlyTo;

		private static bool playerDead;

		private static bool failed;

		public static void Init(ConfigFile config)
		{
			ConfigSection configSection = new ConfigSection(config, "LootMagnet", "--------- Loot and Jin ---------", "Loot magnet");
			Enabled = configSection.Bind("Enabled", defaultValue: true, "Jin, health, qi, ammo and arrows dropped anywhere in the room fly to Yi on their own. The death bag and key items still have to be picked up by hand. Off: drops only come to Yi when Yi touches them");
			Delay = configSection.Bind("Delay", 0.5f, "Seconds after a drop appears before it flies to Yi, so you still see it pop out. A drop that falls into a pit flies back too. 0: at once", (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0f, 3f));
			DebugTools.AddDumpSection(Describe);
		}

		public static bool BeforeUpdate(DropItem item)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Invalid comparison between Unknown and I4
			if (!Enabled.Value || failed)
			{
				return true;
			}
			try
			{
				States state = item.state;
				if ((int)state == 2)
				{
					return !HoldWhileDead(item);
				}
				if ((int)state != 0 && (int)state != 1)
				{
					return true;
				}
				if (!item.IsTouchPlayerToPick || !item.IsNeedGroundToFlyToPlayer)
				{
					return true;
				}
				StartFlight(item);
			}
			catch (Exception arg)
			{
				failed = true;
				Log.Error($"LootMagnet is off until the game restarts, after an error with '{((Object)item).name}': {arg}");
			}
			return true;
		}

		private static void StartFlight(DropItem item)
		{
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			DropItemPrivates dropItems = GamePrivates.DropItems;
			if (dropItems == null || !dropItems.AfterLevelReset.Invoke(item) || (Object)(object)dropItems.FlyTarget.Invoke(item) != (Object)null)
			{
				return;
			}
			IDropping effect = dropItems.Effect.Invoke(item);
			if (!IsLooseDrop(effect))
			{
				return;
			}
			UpdatePlayer();
			Transform val = (((Object)(object)playerToFlyTo != (Object)null) ? playerToFlyTo.CenterAnchor : null);
			if (!((Object)(object)val == (Object)null))
			{
				ref float reference = ref dropItems.FlyCounter.Invoke(item);
				if (item.ForceFlyToPlayerAfterTime <= 0f)
				{
					reference += Time.deltaTime;
				}
				if (!(reference < Delay.Value))
				{
					dropItems.TurnOffSolid(item);
					dropItems.FlyTarget.Invoke(item) = val;
					item.state = (States)2;
					Count(effect);
				}
			}
		}

		private static bool HoldWhileDead(DropItem item)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			UpdatePlayer();
			DropItemPrivates dropItems = GamePrivates.DropItems;
			if (!playerDead || dropItems == null)
			{
				return false;
			}
			if (NoDeathPenaltyFeature.LastDeathKeepsJin && dropItems.Effect.Invoke(item) is DropMoney)
			{
				return false;
			}
			Rigidbody2D val = dropItems.Body.Invoke(item);
			if ((Object)(object)val != (Object)null)
			{
				val.velocity = Vector2.zero;
			}
			return true;
		}

		private static bool IsLooseDrop(IDropping? effect)
		{
			if (effect is AbstractDropping || effect is DropArrow)
			{
				return true;
			}
			return false;
		}

		private static void UpdatePlayer()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Invalid comparison between Unknown and I4
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Invalid comparison between Unknown and I4
			if (playerFrame != Time.frameCount)
			{
				playerFrame = Time.frameCount;
				GameCore val = GameAccess.CoreInLevel();
				playerDead = (Object)(object)val != (Object)null && (int)val.player.CurrentStateType == 11;
				playerToFlyTo = (((Object)(object)val != (Object)null && (int)val.currentCoreState == 1 && !playerDead) ? val.player : null);
			}
		}

		private static void Count(IDropping effect)
		{
			Type type = ((object)effect).GetType();
			flights.TryGetValue(type, out var value);
			flights[type] = value + 1;
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			string text = ((flights.Count > 0) ? string.Join(", ", flights.Select<KeyValuePair<Type, int>, string>((KeyValuePair<Type, int> f) => $"{f.Key.Name} {f.Value}")) : "none");
			yield return $"LootMagnet: enabled={Enabled.Value} delay={Delay.Value}s failed={failed} flights: {text}";
			if (GamePrivates.DropItems == null)
			{
				yield return "LootMagnet: DropItem's private fields are missing";
			}
		}
	}
	[HarmonyPatch]
	internal static class LootMagnetPatches
	{
		[HarmonyPatch(typeof(DropItem), "Update")]
		[HarmonyPrefix]
		private static bool BeforeDropUpdate(DropItem __instance)
		{
			return LootMagnetFeature.BeforeUpdate(__instance);
		}
	}
}
namespace NineSolsQoL.Features.Loot.JinMultipliers
{
	internal static class JinMultipliersFeature
	{
		private const int MaxMultiplier = 1000;

		private const long MaxGold = 2000000000L;

		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<int> MonsterDrops;

		public static ConfigEntry<int> TransmuteUntoWealth;

		public static ConfigEntry<int> Selling;

		private static int coinsMultiplied;

		private static long extraJinFromCoins;

		public static void Init(ConfigFile config)
		{
			ConfigSection configSection = new ConfigSection(config, "JinMultipliers", "--------- Loot and Jin ---------", "Jin multipliers");
			AcceptableValueRange<int> range = new AcceptableValueRange<int>(1, 1000);
			Enabled = configSection.Bind("Enabled", defaultValue: true, "Multiply the Jin you earn by the multipliers below. Off: the game's amounts. Note: the game keeps at most 99,999 Jin whenever you buy or sell, and the excess is lost, so spend it before going above that");
			MonsterDrops = configSection.Bind("MonsterDrops", 1, "Multiplies the Jin of every coin you pick up, dropped by enemies, chests and breakable objects (the game counts them all as enemy drops). Vanilla: 1. The death bag is never multiplied", (AcceptableValueBase?)(object)range);
			TransmuteUntoWealth = configSection.Bind("TransmuteUntoWealth", 1, "Multiplies the 10 Jin that the Transmute Unto Wealth skill gives for each enemy killed by a talisman explosion. Vanilla: 1", (AcceptableValueBase?)(object)range, advanced: false, "Transmute Unto Wealth");
			Selling = configSection.Bind("Selling", 1, "Multiplies the Jin you receive when you sell an item, such as recycling at the 3D printer. The shop shows the multiplied amount. Prices you pay are unchanged. At 99,999 Jin a sale still takes the item but the Jin is lost, as in the game. Vanilla: 1", (AcceptableValueBase?)(object)range);
			DebugTools.AddDumpSection(Describe);
		}

		public static int MultiplyEarning(PlayerGamePlayData data, int amount, GoldSourceTag source)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Invalid comparison between Unknown and I4
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Invalid comparison between Unknown and I4
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Invalid comparison between Unknown and I4
			try
			{
				if (!Enabled.Value || amount <= 0 || ApplicationCore.IsInBossMemoryMode)
				{
					return amount;
				}
				int num = (((int)source <= 1) ? MonsterDrops.Value : (((int)source != 5) ? 1 : TransmuteUntoWealth.Value));
				int num2 = num;
				if (num2 <= 1)
				{
					return amount;
				}
				int num3 = Multiply(amount, num2, data.CurrentGold);
				if ((int)source != 5)
				{
					coinsMultiplied++;
					extraJinFromCoins += num3 - amount;
				}
				else
				{
					Log.Debug($"[jin] Transmute Unto Wealth: {amount} x {num2} = {num3} Jin");
				}
				return num3;
			}
			catch (Exception arg)
			{
				Log.Error($"JinMultipliers failed, adding the game's amount: {arg}");
				return amount;
			}
		}

		public static void AfterTrade(MerchandiseData merchandise)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Invalid comparison between Unknown and I4
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!Enabled.Value)
				{
					return;
				}
				ModifyScriptableDataIntEntry outcomeResult = merchandise.OutcomeResult;
				if (outcomeResult == null || outcomeResult.ModifyAmount <= 0 || !IsGold(outcomeResult.ModifyFlag))
				{
					return;
				}
				if ((int)merchandise.merchandiseType != 1 || (Object)(object)merchandise.mainMaterial == (Object)null)
				{
					Log.Debug($"[jin] '{((Object)merchandise).name}' paid {outcomeResult.ModifyAmount} Jin but is not a sale " + $"({merchandise.merchandiseType}, item to sell " + (((Object)(object)merchandise.mainMaterial != (Object)null) ? ((Object)merchandise.mainMaterial).name : "none") + "): not multiplied");
					return;
				}
				int value = Selling.Value;
				if (value > 1)
				{
					int num = SaleAmount(outcomeResult.ModifyAmount);
					ScriptableDataFloat modifyFlag = outcomeResult.ModifyFlag;
					((AbstractScriptableData<FlagFieldFloat, float>)(object)modifyFlag).CurrentValue = ((AbstractScriptableData<FlagFieldFloat, float>)(object)modifyFlag).CurrentValue + (float)(num - outcomeResult.ModifyAmount);
					Log.Debug($"[jin] sold '{((Object)merchandise).name}': {outcomeResult.ModifyAmount} x {value} = {num} Jin, " + $"gold now {((AbstractScriptableData<FlagFieldFloat, float>)(object)outcomeResult.ModifyFlag).CurrentValue}");
				}
			}
			catch (Exception arg)
			{
				Log.Error($"JinMultipliers (selling) failed, the game's amount was added: {arg}");
			}
		}

		public static void AfterShopRowUpdate(MerchandiseItemButton row)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Invalid comparison between Unknown and I4
			try
			{
				if (!Enabled.Value || Selling.Value <= 1)
				{
					return;
				}
				MerchandiseData bindData = ((UITableButton<MerchandiseData>)(object)row).bindData;
				if (!((Object)(object)bindData == (Object)null) && !((Object)(object)row.priceText == (Object)null) && !((Object)(object)bindData.mainMaterial == (Object)null) && (int)bindData.merchandiseType == 1 && row.HasEnoughMaterial)
				{
					ModifyScriptableDataIntEntry outcomeResult = bindData.OutcomeResult;
					if (outcomeResult != null && outcomeResult.ModifyAmount > 0 && IsGold(outcomeResult.ModifyFlag))
					{
						row.priceText.text = SaleAmount(outcomeResult.ModifyAmount).ToString();
					}
				}
			}
			catch (Exception arg)
			{
				Log.Error($"JinMultipliers shop price failed: {arg}");
			}
		}

		private static int SaleAmount(int amount)
		{
			return (int)Math.Min((long)amount * (long)Selling.Value, 2000000000L);
		}

		private static int Multiply(int amount, int multiplier, int gold)
		{
			long val = 2000000000L - (long)Math.Max(gold, 0);
			return (int)Math.Max(amount, Math.Min((long)amount * (long)multiplier, val));
		}

		private static bool IsGold(ScriptableDataFloat? flag)
		{
			if ((Object)(object)flag == (Object)null || !SingletonBehaviour<GameCore>.IsAvailable())
			{
				return false;
			}
			PlayerGamePlayData playerGameData = SingletonBehaviour<GameCore>.Instance.playerGameData;
			if ((Object)(object)playerGameData != (Object)null)
			{
				return flag == playerGameData.goldField;
			}
			return false;
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			PlayerGamePlayData playerGameData = core.playerGameData;
			ScriptableDataFloat goldField = playerGameData.goldField;
			ScriptableDataFloat externalRepository = goldField.ExternalRepository;
			string arg = (((Object)(object)externalRepository != (Object)null) ? $"'{((Object)externalRepository).name}' holding {((AbstractScriptableData<FlagFieldFloat, float>)(object)externalRepository).CurrentValue}" : "none (the excess is lost)");
			return new string[2]
			{
				$"JinMultipliers: enabled={Enabled.Value} multipliers monster={MonsterDrops.Value} " + $"transmute={TransmuteUntoWealth.Value} sell={Selling.Value}",
				$"JinMultipliers: gold={playerGameData.CurrentGold} cap={goldField.MaxValue} overflowTo={arg} " + $"coinsMultiplied={coinsMultiplied} extraJinFromCoins={extraJinFromCoins}"
			};
		}
	}
	[HarmonyPatch]
	internal static class JinMultipliersPatches
	{
		[HarmonyPatch(typeof(PlayerGamePlayData), "AddGold")]
		[HarmonyPrefix]
		private static void BeforeAddGold(PlayerGamePlayData __instance, ref int amount, GoldSourceTag sourceTag)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			amount = JinMultipliersFeature.MultiplyEarning(__instance, amount, sourceTag);
		}

		[HarmonyPatch(typeof(MerchandiseData), "Trade")]
		[HarmonyPostfix]
		private static void AfterTrade(MerchandiseData __instance)
		{
			JinMultipliersFeature.AfterTrade(__instance);
		}

		[HarmonyPatch(typeof(MerchandiseItemButton), "UpdateView")]
		[HarmonyPostfix]
		private static void AfterShopRowUpdate(MerchandiseItemButton __instance)
		{
			JinMultipliersFeature.AfterShopRowUpdate(__instance);
		}
	}
}
namespace NineSolsQoL.Features.Hud.BossHPNumbers
{
	internal static class BossHPNumbersFeature
	{
		private static readonly Color InternalInjuryColor = new Color(0.5f, 1f, 1f, 1f);

		public static ConfigEntry<bool> Enabled = null;

		public static bool Active;

		private static HPNumber bossHP = null;

		private static HPNumber bossInternal = null;

		private static GameObject? root;

		private static bool failed;

		public static void Init(ConfigFile config)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			ConfigSection configSection = new ConfigSection(config, "BossHPNumbers", "--------- HUD ---------", "Boss HP numbers");
			Enabled = configSection.Bind("Enabled", defaultValue: true, "Show the boss's HP, and optionally its internal injury, as numbers while the game shows the boss bar and its HUD. Off: no numbers, as in the game");
			bossHP = new HPNumber(configSection, "BossHP", "the boss's HP", on: true, new Vector2(950f, 99f), Color.white);
			bossInternal = new HPNumber(configSection, "BossInternalHP", "the boss's internal injury (the HP it regains over time unless a talisman explosion cashes it in)", on: false, new Vector2(1030f, 99f), InternalInjuryColor);
			DebugTools.AddDumpSection(Describe);
		}

		public static void LateUpdate()
		{
			if (!Active || failed)
			{
				return;
			}
			try
			{
				GameCore val = (Enabled.Value ? GameAccess.CoreInLevel() : null);
				if ((Object)(object)val == (Object)null || !HudShown() || CoverShown())
				{
					Hide();
					return;
				}
				if ((Object)(object)root == (Object)null)
				{
					CreateRoot();
				}
				if (!root.activeSelf)
				{
					root.SetActive(true);
				}
				PostureSystem val2 = BossPosture(val);
				bossHP.Show(((Object)(object)val2 != (Object)null) ? new int?(val2.GetPostureValue()) : ((int?)null));
				bossInternal.Show(((Object)(object)val2 != (Object)null) ? new int?((int)val2.InternalInjury) : ((int?)null));
			}
			catch (Exception arg)
			{
				failed = true;
				Log.Error($"BossHPNumbers is off until the game restarts, after an error: {arg}");
				try
				{
					Hide();
				}
				catch (Exception)
				{
				}
			}
		}

		private static void Hide()
		{
			if ((Object)(object)root != (Object)null && root.activeSelf)
			{
				root.SetActive(false);
			}
		}

		public static void Destroy()
		{
			if ((Object)(object)root != (Object)null)
			{
				Object.Destroy((Object)(object)root);
			}
			root = null;
		}

		private static bool HudShown()
		{
			if (!SingletonBehaviour<UIManager>.IsAvailable())
			{
				return false;
			}
			RuntimeConditionVote playerInGameUIDisplayVote = SingletonBehaviour<UIManager>.Instance.playerInGameUIDisplayVote;
			if (playerInGameUIDisplayVote != null)
			{
				return playerInGameUIDisplayVote.Result;
			}
			return false;
		}

		private static bool CoverShown()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if (!SingletonBehaviour<ApplicationUIGroupManager>.IsAvailable())
			{
				return false;
			}
			Image blackCover = SingletonBehaviour<ApplicationUIGroupManager>.Instance.blackCover;
			if ((Object)(object)blackCover != (Object)null && ((Component)blackCover).gameObject.activeInHierarchy)
			{
				return ((Graphic)blackCover).color.a > 0.01f;
			}
			return false;
		}

		private static PostureSystem? BossPosture(GameCore core)
		{
			UIBossHP val = (((Object)(object)core.monsterHpUI != (Object)null) ? core.monsterHpUI.CurrentBossHP : null);
			PostureSystem val2 = (((Object)(object)val != (Object)null) ? val.bindingPosture : null);
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			MonsterBase bindMonster = val2.BindMonster;
			if ((Object)(object)bindMonster != (Object)null)
			{
				MonsterState currentMonsterState = bindMonster.currentMonsterState;
				BossPhaseChangeState val3 = (BossPhaseChangeState)(object)((currentMonsterState is BossPhaseChangeState) ? currentMonsterState : null);
				if (val3 != null && !val3.UseLoopAngry)
				{
					return null;
				}
			}
			return val2;
		}

		private static void CreateRoot()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Expected O, but got Unknown
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			root = new GameObject("NineSolsQoL BossHPNumbers");
			Object.DontDestroyOnLoad((Object)(object)root);
			root.AddComponent<Canvas>().renderMode = (RenderMode)0;
			CanvasScaler obj = root.AddComponent<CanvasScaler>();
			obj.uiScaleMode = (ScaleMode)1;
			obj.referenceResolution = new Vector2(1920f, 1080f);
			obj.matchWidthOrHeight = 1f;
			bossHP.Create(root.transform);
			bossInternal.Create(root.transform);
			RCGLifeCycle.DontDestroyForever(root);
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			PostureSystem val = BossPosture(core);
			return new string[1] { $"BossHPNumbers: enabled={Enabled.Value} active={Active} failed={failed} hudShown={HudShown()} " + "canvas=" + (((Object)(object)root == (Object)null) ? "none" : (root.activeSelf ? "shown" : "hidden")) + " boss=" + (((Object)(object)val != (Object)null) ? $"{val.GetPostureValue()}/{val.MaxPostureValue} internal={val.InternalInjury}" : "no bar") };
		}
	}
	internal sealed class HPNumber
	{
		private static readonly Vector2 BottomCentre = new Vector2(0.5f, 0f);

		private static readonly Vector2 ReferenceCentre = new Vector2(960f, 0f);

		private readonly string key;

		private readonly ConfigEntry<bool> show;

		private readonly ConfigEntry<Vector2> position;

		private readonly ConfigEntry<int> fontSize;

		private readonly ConfigEntry<Color> color;

		private TextMeshProUGUI? text;

		private int shown;

		public HPNumber(ConfigSection section, string key, string what, bool on, Vector2 at, Color tint)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			this.key = key;
			show = section.Bind(key, on, "Show " + what);
			position = section.Bind<Vector2>(key + "Position", at, "Where the number starts (its left end, vertically centred), in pixels from the bottom-left corner of a 1920x1080 screen; other resolutions scale it");
			fontSize = section.Bind(key + "FontSize", 19, "Font size", (AcceptableValueBase?)(object)new AcceptableValueRange<int>(8, 72));
			color = section.Bind<Color>(key + "Color", tint, "Colour");
		}

		public void Create(Transform canvas)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(key, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(canvas, false);
			text = val.AddComponent<TextMeshProUGUI>();
			((Graphic)text).raycastTarget = false;
			((TMP_Text)text).enableWordWrapping = false;
			((TMP_Text)text).alignment = (TextAlignmentOptions)4097;
			RectTransform rectTransform = ((TMP_Text)text).rectTransform;
			rectTransform.anchorMin = BottomCentre;
			rectTransform.anchorMax = BottomCentre;
			rectTransform.pivot = new Vector2(0f, 0.5f);
			rectTransform.sizeDelta = new Vector2(300f, 100f);
			shown = int.MinValue;
			val.SetActive(false);
		}

		public void Show(int? value)
		{
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)text == (Object)null)
			{
				return;
			}
			bool flag = value.HasValue && show.Value;
			if (((Component)text).gameObject.activeSelf != flag)
			{
				((Component)text).gameObject.SetActive(flag);
			}
			if (flag)
			{
				if (value.Value != shown)
				{
					shown = value.Value;
					((TMP_Text)text).SetText("{0}", (float)shown);
				}
				RectTransform rectTransform = ((TMP_Text)text).rectTransform;
				Vector2 val = position.Value - ReferenceCentre;
				if (rectTransform.anchoredPosition != val)
				{
					rectTransform.anchoredPosition = val;
				}
				((TMP_Text)text).fontSize = fontSize.Value;
				((Graphic)text).color = color.Value;
			}
		}
	}
}
namespace NineSolsQoL.Features.Death.RespawnNearDeath
{
	internal static class ArenaTracker
	{
		private const float Margin = 64f;

		private static string entranceScene = "";

		private static string entranceName = "";

		private static Vector3? entranceRespawn;

		public static void OnSceneLoaded(GameLevel level)
		{
			if (level.SceneName != entranceScene)
			{
				entranceRespawn = null;
			}
		}

		public static void OnWalkTrigger(PlayerWalkToTrigger trigger)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (GamePrivates.WasTriggered(trigger))
				{
					return;
				}
				GameCore val = GameAccess.CoreInLevel();
				if ((Object)(object)val == (Object)null)
				{
					return;
				}
				Vector3 origin = ((Component)val.gameLevel).transform.position;
				Collider2D[] componentsInChildren = ((Component)trigger).GetComponentsInChildren<Collider2D>();
				List<Bounds> areas = componentsInChildren.Select((Collider2D c) => Relative(c.bounds, origin)).ToList();
				entranceScene = val.gameLevel.SceneName;
				entranceName = ((Component)trigger).transform.HierarchyPath();
				entranceRespawn = NewestTrailPointOutside(val, areas);
				if (!Log.DebugEnabled)
				{
					return;
				}
				Vector3 position = ((Component)val.player).transform.position;
				Log.Debug($"Fight entrance '{entranceName}' fired with the player at {position}: respawn point " + (entranceRespawn.HasValue ? $"{entranceRespawn.Value + origin}" : "none found"));
				Collider2D[] array = componentsInChildren;
				foreach (Collider2D val2 in array)
				{
					Log.Debug("  entrance area '" + ((Component)val2).transform.HierarchyPath() + "' " + val2.bounds.Describe());
				}
				if ((Object)(object)trigger.walkTo != (Object)null)
				{
					Log.Debug($"  walks the player to {trigger.walkTo.position}");
				}
				array = Physics2D.OverlapPointAll(Vector2.op_Implicit(position));
				foreach (Collider2D val3 in array)
				{
					if (val3.isTrigger && !componentsInChildren.Contains(val3) && !((Object)(object)((Component)val3).GetComponent<PlayerSensor>() == (Object)null))
					{
						Log.Debug("  player also inside sensor '" + ((Component)val3).transform.HierarchyPath() + "' " + val3.bounds.Describe());
					}
				}
			}
			catch (Exception arg)
			{
				Log.Error($"ArenaTracker.OnWalkTrigger failed: {arg}");
			}
		}

		public static Vector3? FindBossRespawn(GameCore core, out string how)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			string sceneName = core.gameLevel.SceneName;
			if (entranceRespawn.HasValue && entranceScene == sceneName)
			{
				how = "before the fight entrance '" + entranceName + "'";
				return entranceRespawn;
			}
			Vector3 origin = ((Component)core.gameLevel).transform.position;
			List<Bounds> list = (from area in Object.FindObjectsOfType<BossArea>()
				select ((Component)area).GetComponent<Collider2D>() into c
				where (Object)(object)c != (Object)null && ((Behaviour)c).enabled
				select Relative(c.bounds, origin)).ToList();
			if (list.Count > 0)
			{
				Vector3? result = NewestTrailPointOutside(core, list);
				if (result.HasValue)
				{
					how = "outside the boss area";
					return result;
				}
			}
			if (SafeTrail.Scene == sceneName && SafeTrail.StartsAtEntrance && SafeTrail.Points.Count > 0)
			{
				how = "where the player entered the scene";
				return SafeTrail.Points[0];
			}
			how = "no fight entrance, boss area or scene entrance known";
			return null;
		}

		public static IEnumerable<string> Describe(GameCore core)
		{
			yield return entranceRespawn.HasValue ? $"last fight entrance '{entranceName}' in {entranceScene}, respawn point {entranceRespawn} (relative)" : "no fight entrance fired yet";
			PlayerWalkToTrigger[] array = Object.FindObjectsOfType<PlayerWalkToTrigger>(true);
			foreach (PlayerWalkToTrigger val in array)
			{
				string arg = string.Join(", ", from c in ((Component)val).GetComponentsInChildren<Collider2D>(true)
					select c.bounds.Describe());
				yield return $"walk trigger '{((Component)val).transform.HierarchyPath()}' active={((Behaviour)val).isActiveAndEnabled} " + $"triggered={GamePrivates.WasTriggered(val)} areas=[{arg}] " + "walkTo=" + (((Object)(object)val.walkTo != (Object)null) ? ((object)val.walkTo.position/*cast due to .constrained prefix*/).ToString() : "none");
			}
			BossArea[] array2 = Object.FindObjectsOfType<BossArea>(true);
			foreach (BossArea val2 in array2)
			{
				Collider2D component = ((Component)val2).GetComponent<Collider2D>();
				yield return $"boss area '{((Component)val2).transform.HierarchyPath()}' active={((Behaviour)val2).isActiveAndEnabled} " + "bounds=" + (((Object)(object)component != (Object)null) ? component.bounds.Describe() : "none");
			}
		}

		private static Vector3? NewestTrailPointOutside(GameCore core, List<Bounds> areas)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			if (SafeTrail.Scene != core.gameLevel.SceneName)
			{
				return null;
			}
			IReadOnlyList<Vector3> points = SafeTrail.Points;
			for (int num = points.Count - 1; num >= 0; num--)
			{
				Vector3 point = points[num];
				if (!areas.Any((Bounds area) => IsInside(area, point)))
				{
					return point;
				}
			}
			return null;
		}

		private static bool IsInside(Bounds area, Vector3 point)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			if (point.x >= ((Bounds)(ref area)).min.x - 64f && point.x <= ((Bounds)(ref area)).max.x + 64f && point.y >= ((Bounds)(ref area)).min.y - 64f)
			{
				return point.y <= ((Bounds)(ref area)).max.y + 64f;
			}
			return false;
		}

		private static Bounds Relative(Bounds bounds, Vector3 origin)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: 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_0014: Unknown result type (might be due to invalid IL or missing references)
			return new Bounds(((Bounds)(ref bounds)).center - origin, ((Bounds)(ref bounds)).size);
		}
	}
	internal sealed class DeathSnapshot
	{
		public GameLevel Level;

		public Vector3 Position;

		public Vector3 SafeGround;

		public bool InBossFight;

		private static DeathSnapshot? pending;

		public static void Record()
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: 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)
			pending = null;
			try
			{
				GameCore val = GameAccess.CoreInLevel();
				if (!((Object)(object)val == (Object)null))
				{
					Player player = val.player;
					pending = new DeathSnapshot
					{
						Level = val.gameLevel,
						Position = ((Component)player).transform.position,
						SafeGround = player.lastSafeGroundPosition,
						InBossFight = GameAccess.IsBossFightActive(val)
					};
					if (Log.DebugEnabled)
					{
						Log.Debug($"Died in {val.CurrentSceneName} at {pending.Position}: last safe ground {pending.SafeGround}, " + $"boss fight {pending.InBossFight}, carrying Jin {val.playerGameData.CurrentGold} " + $"XP {val.playerGameData.CurrentExp}, death bag rate {((AbstractStatData)player.statData.DeathPenaltyRate).Value}");
					}
				}
			}
			catch (Exception arg)
			{
				pending = null;
				Log.Error($"DeathSnapshot.Record failed: {arg}");
			}
		}

		public static DeathSnapshot? Take()
		{
			DeathSnapshot? result = pending;
			pending = null;
			return result;
		}
	}
	internal static class RespawnController
	{
		public static bool TryRespawn(PlayerDeadState deadState)
		{
			try
			{
				return Respawn(deadState);
			}
			catch (Exception arg)
			{
				Log.Error($"Respawn failed, using the game's normal respawn: {arg}");
				return false;
			}
		}

		private static bool Respawn(PlayerDeadState deadState)
		{
			DeathSnapshot deathSnapshot = DeathSnapshot.Take();
			GameCore val = GameAccess.CoreInLevel();
			if ((Object)(object)val == (Object)null || deathSnapshot == null)
			{
				Log.Info("Normal respawn: no death recorded in a loaded level");
				return false;
			}
			string text = WhyNormalRespawn(val, deadState, deathSnapshot);
			if (text != null)
			{
				Log.Info("Normal respawn: " + text);
				return false;
			}
			if (!deathSnapshot.InBossFight)
			{
				return RespawnOnSafeGround(val, deathSnapshot);
			}
			return RespawnAfterBossFight(val);
		}

		private static string? WhyNormalRespawn(GameCore core, PlayerDeadState deadState, DeathSnapshot death)
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Invalid comparison between Unknown and I4
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			if (!RespawnNearDeathFeature.Enabled.Value)
			{
				return "disabled in the config";
			}
			if (DebugSetting.IsDebugMode)
			{
				return "game debug mode";
			}
			if (deadState.CanQuickRevive())
			{
				return "this map has its own quick revive";
			}
			if (ScriptableObjectSingleton<VRChallengeConfig>.Instance.IsChallengingVR)
			{
				return "VR challenge";
			}
			if (core.playerGameData.memoryMode.CurrentValue)
			{
				return "memory mode";
			}
			if (core.IsInLeaderboardChallengeMode)
			{
				return "leaderboard challenge";
			}
			if ((Object)(object)core.tutorialSettings.FindNextTutorialEntry() != (Object)null)
			{
				return "the game respawns you into a tutorial";
			}
			if ((int)core.currentCoreState != 1)
			{
				return $"game state {core.currentCoreState}";
			}
			if ((Object)(object)death.Level != (Object)(object)core.gameLevel)
			{
				return "the level changed since the death";
			}
			return null;
		}

		private static bool RespawnOnSafeGround(GameCore core, DeathSnapshot death)
		{
			//IL_0001: 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_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			if (death.SafeGround == Vector3.zero)
			{
				Log.Info("Normal respawn: no safe ground recorded");
				return false;
			}
			Log.Debug($"Respawning on the last safe ground {death.SafeGround} (died at {death.Position})");
			Respawner.QuickReviveAt(core, death.SafeGround);
			return true;
		}

		private static bool RespawnAfterBossFight(GameCore core)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			if (!RespawnNearDeathFeature.BossRespawnOutsideArena.Value)
			{
				Log.Info("Normal respawn: boss respawn disabled in the config");
				return false;
			}
			string how;
			Vector3? val = ArenaTracker.FindBossRespawn(core, out how);
			if (!val.HasValue)
			{
				Log.Info("Normal respawn: died in a boss fight but found no respawn point (" + how + ")");
				return false;
			}
			Log.Debug("Died in a boss fight: reloading " + core.CurrentSceneName + " with the player at " + $"{val.Value + ((Component)core.gameLevel).transform.position} ({how})");
			Respawner.ReloadSceneAt(core, val.Value);
			return true;
		}
	}
	internal static class Respawner
	{
		private const float FadeTime = 0.2f;

		public static void QuickReviveAt(GameCore core, Vector3 position)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			bool isNeedDeathPenalty = core.player.IsNeedDeathPenalty;
			if (isNeedDeathPenalty)
			{
				core.playerGameData.PlayerDeathPenalty();
			}
			Log.Debug("Death penalty " + (isNeedDeathPenalty ? "applied with the game's PlayerDeathPenalty" : "not needed"));
			UniTaskExtensions.Forget(core.FadeToBlack(0.2f, (UnityAction)delegate
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				FinishQuickRevive(position);
			}, 0f));
		}

		private static void FinishQuickRevive(Vector3 position)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			GameCore val = GameAccess.CoreInLevel();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			Player player = val.player;
			try
			{
				player.ResetParent();
				SafeTrail.Paused = true;
				try
				{
					val.ResetLevel(false);
				}
				finally
				{
					SafeTrail.Paused = false;
				}
				player.RestoreEverything();
				player.ChangeState((PlayerStateType)0, false);
				player.SetPosition(position, true);
				player.ForceOnGround();
				player.SafeGroundRecorder.ForceSave(position);
				player.IsJustDead = false;
				val.gameLevel.UpdateCameraBounds();
				if (SingletonBehaviour<CameraManager>.IsAvailable())
				{
					SingletonBehaviour<CameraManager>.Instance.ResetCamera2DDockerToPlayer();
				}
				val.soundManager.ambienceManager.BGMBackToNormalState();
			}
			catch (Exception arg)
			{
				Log.Error($"Quick revive failed, reloading at the last Root Node: {arg}");
				player.RespawnAtSavePoint();
				return;
			}
			val.FadeOutBlack(0.2f, 1f, (UnityAction)null);
			Log.Debug($"Respawned at {position}");
			DebugTools.Watch("quick revive", 5f);
		}

		public static void ReloadSceneAt(GameCore core, Vector3 levelPosition)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: 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)
			//IL_004d: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			GamePrivates.ClearLastTeleportPoint(core);
			ChangeSceneData val = default(ChangeSceneData);
			Scene scene = ((Component)core.gameLevel).gameObject.scene;
			val.sceneName = ((Scene)(ref scene)).name;
			val.playerSpawnPosition = (SpawnPositionDelegate)(() => SpawnPosition(levelPosition));
			val.ChangedDoneEvent = core.player.AllFull;
			ChangeSceneData val2 = val;
			val2.panData.panType = (CameraPanType)0;
			UniTaskExtensions.Forget(core.ChangeScene(val2, true, false, 0f));
			DebugTools.Watch("boss reload", 15f);
		}

		private static Vector3 SpawnPosition(Vector3 levelPosition)
		{
			//IL_0021: 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_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			GameCore val = GameAccess.CoreInLevel();
			Vector3 val2 = (((Object)(object)val != (Object)null) ? ((Component)val.gameLevel).transform.position : Vector3.zero);
			return levelPosition + val2;
		}
	}
	internal static class RespawnNearDeathFeature
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<bool> BossRespawnOutsideArena;

		public static void Init(ConfigFile config)
		{
			ConfigSection configSection = new ConfigSection(config, "RespawnNearDeath", "--------- Death ---------", "Respawn near death");
			Enabled = configSection.Bind("Enabled", defaultValue: true, "Respawn near where you died, on the last safe ground, instead of at the last Root Node. Off: the game's respawn at the last Root Node for every death");
			BossRespawnOutsideArena = configSection.Bind("BossRespawnOutsideArena", defaultValue: true, "After dying in a boss fight, reload the room so the fight starts over and respawn just outside the arena. Off: the game's respawn at the last Root Node after boss deaths");
			DebugTools.AddDumpSection((GameCore core) => new string[1] { SafeTrail.Describe() }.Concat(ArenaTracker.Describe(core)));
		}
	}
	[HarmonyPatch]
	internal static class RespawnNearDeathPatches
	{
		[HarmonyPatch(typeof(PlayerDeadState), "OnStateEnter")]
		[HarmonyPrefix]
		private static void BeforeDeath()
		{
			DeathSnapshot.Record();
		}

		[HarmonyPatch(typeof(PlayerDeadState), "Revive")]
		[HarmonyPrefix]
		private static bool BeforeRevive(PlayerDeadState __instance)
		{
			return !RespawnController.TryRespawn(__instance);
		}

		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		[HarmonyPostfix]
		private static void AfterSafeGround(SafeGroundRecorder __instance, Vector3 value)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			SafeTrail.Record(__instance, value);
		}

		[HarmonyPatch(typeof(GameCore), "SceneLoaded")]
		[HarmonyPostfix]
		private static void AfterSceneLoaded(GameLevel newLevel)
		{
			try
			{
				SafeTrail.OnSceneLoaded(newLevel);
				ArenaTracker.OnSceneLoaded(newLevel);
			}
			catch (Exception arg)
			{
				Log.Error($"RespawnNearDeath could not reset its data for the new scene: {arg}");
			}
		}

		[HarmonyPatch(typeof(PlayerWalkToTrigger), "Triggered")]
		[HarmonyPrefix]
		private static void BeforeWalkTrigger(PlayerWalkToTrigger __instance)
		{
			ArenaTracker.OnWalkTrigger(__instance);
		}
	}
	internal static class SafeTrail
	{
		private const int MaxPoints = 500;

		private const float MinSpacing = 16f;

		private static readonly List<Vector3> points = new List<Vector3>();

		public static bool Paused;

		public static IReadOnlyList<Vector3> Points => points;

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

		public static bool StartsAtEntrance { get; private set; }

		public static void OnSceneLoaded(GameLevel level)
		{
			Restart(level.SceneName, startsAtEntrance: true);
		}

		public static void Record(SafeGroundRecorder recorder, Vector3 position)
		{
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			if (Paused)
			{
				return;
			}
			try
			{
				GameCore val = GameAccess.CoreInLevel();
				if ((Object)(object)val == (Object)null || (Object)(object)recorder != (Object)(object)val.player.SafeGroundRecorder)
				{
					return;
				}
				GameLevel gameLevel = val.gameLevel;
				if (gameLevel.SceneName != Scene)
				{
					Restart(gameLevel.SceneName, startsAtEntrance: false);
				}
				Vector3 val2 = position - ((Component)gameLevel).transform.position;
				if (points.Count <= 0 || !(Vector2.Distance(Vector2.op_Implicit(points[points.Count - 1]), Vector2.op_Implicit(val2)) < 16f))
				{
					points.Add(val2);
					if (points.Count > 500)
					{
						points.RemoveAt(250);
					}
				}
			}
			catch (Exception arg)
			{
				Log.Error($"SafeTrail.Record failed: {arg}");
			}
		}

		public static string Describe()
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if (points.Count == 0)
			{
				return "trail in '" + Scene + "': empty";
			}
			return $"trail in '{Scene}': {points.Count} points, startsAtEntrance={StartsAtEntrance}, " + $"first {points[0]}, last {points[points.Count - 1]} (relative to the level root)";
		}

		private static void Restart(string scene, bool startsAtEntrance)
		{
			Scene = scene;
			StartsAtEntrance = startsAtEntrance;
			points.Clear();
			Log.Debug($"Safe ground trail restarted in {scene} (startsAtEntrance={startsAtEntrance})");
		}
	}
}
namespace NineSolsQoL.Features.Death.NoDeathPenalty
{
	internal static class NoDeathPenaltyFeature
	{
		public static ConfigEntry<bool> Enabled;

		public static bool Patched;

		private static bool keepAtPenalty;

		public static bool LastDeathKeepsJin => keepAtPenalty;

		public static void Init(ConfigFile config)
		{
			Enabled = new ConfigSection(config, "NoDeathPenalty", "--------- Death ---------", "No death penalty").Bind("Enabled", defaultValue: true, "Keep your Jin and experience when you die. The death bag still appears as usual (or is carried by the enemy that killed you), but empty: you can still pick it up, for the skills that react to that. Off: the game's penalty, where the Jin and experience you carry go into the death bag, and dying again before you pick it up loses them");
			DebugTools.AddDumpSection(Describe);
		}

		public static bool WillStoreBag(PlayerDeadRecord record)
		{
			try
			{
				return record.CanStoreDeadRecord.IsValid;
			}
			catch (Exception arg)
			{
				Log.Error($"NoDeathPenalty could not read CanStoreDeadRecord: {arg}");
				return false;
			}
		}

		public static void AfterBagStored(PlayerDeadRecord record, bool bagStored)
		{
			keepAtPenalty = false;
			try
			{
				if (!Enabled.Value || !Patched)
				{
					Log.Debug($"[death] the game's penalty applies (NoDeathPenalty enabled={Enabled.Value} " + $"patched={Patched})");
					return;
				}
				keepAtPenalty = true;
				if (bagStored)
				{
					((FlagField<float>)(object)record.ContainMoney).CurrentValue = 0f;
					((FlagField<float>)(object)record.ContainEXP).CurrentValue = 0f;
				}
				Log.Debug("[death] Jin and experience will be kept: " + (bagStored ? "the death bag is empty" : "the game stored no new death bag"));
			}
			catch (Exception arg)
			{
				Log.Error($"NoDeathPenalty failed at death: {arg}");
			}
		}

		public static void SetGoldAtPenalty(PlayerGamePlayData data, int value)
		{
			if (!keepAtPenalty)
			{
				data.CurrentGold = value;
			}
		}

		public static void SetExpAtPenalty(PlayerGamePlayData data, int value)
		{
			if (!keepAtPenalty)
			{
				data.CurrentExp = value;
			}
			else
			{
				Log.Debug($"[death] the death penalty kept {data.CurrentGold} Jin and {data.CurrentExp} experience");
			}
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			return new string[1] { $"NoDeathPenalty: enabled={Enabled.Value} patched={Patched} lastDeathKeeps={keepAtPenalty} " + $"penaltyPending={core.player.IsNeedDeathPenalty} exp={core.playerGameData.CurrentExp} " + $"bagExp={((FlagField<float>)(object)core.deadRecord.ContainEXP).CurrentValue}" };
		}
	}
	[HarmonyPatch]
	internal static class NoDeathPenaltyPatches
	{
		[HarmonyPatch(typeof(PlayerDeadRecord), "StorePlayerDataBeforeDead")]
		[HarmonyPrefix]
		private static void BeforeStoreBag(PlayerDeadRecord __instance, out bool __state)
		{
			__state = NoDeathPenaltyFeature.WillStoreBag(__instance);
		}

		[HarmonyPatch(typeof(PlayerDeadRecord), "StorePlayerDataBeforeDead")]
		[HarmonyPostfix]
		private static void AfterStoreBag(PlayerDeadRecord __instance, bool __state)
		{
			NoDeathPenaltyFeature.AfterBagStored(__instance, __state);
		}
	}
	[HarmonyPatch]
	internal static class NoDeathPenaltyTranspiler
	{
		[HarmonyTargetMethod]
		private static MethodBase? PenaltyBody()
		{
			Type type = AccessTools.Method(typeof(PlayerGamePlayData), "PlayerDeathPenalty", (Type[])null, (Type[])null)?.GetCustomAttribute<AsyncStateMachineAttribute>()?.StateMachineType;
			if (!(type != null))
			{
				return null;
			}
			return AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null);
		}

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> KeepJinAndExperience(IEnumerable<CodeInstruction> instructions)
		{
			NoDeathPenaltyFeature.Patched = false;
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo setGold = AccessTools.PropertySetter(typeof(PlayerGamePlayData), "CurrentGold");
			MethodInfo setExp = AccessTools.PropertySetter(typeof(PlayerGamePlayData), "CurrentExp");
			MethodInfo methodInfo = AccessTools.Method(typeof(NoDeathPenaltyFeature), "SetGoldAtPenalty", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(NoDeathPenaltyFeature), "SetExpAtPenalty", (Type[])null, (Type[])null);
			if (!Transpile.Has("NoDeathPenalty is off", "PlayerGamePlayData.CurrentGold or CurrentExp", setGold, setExp, methodInfo, methodInfo2))
			{
				return list;
			}
			int num = Transpile.FindOne(list, (List<CodeInstruction> c, int i) => i > 0 && CodeInstructionExtensions.LoadsConstant(c[i - 1], 0L) && CodeInstructionExtensions.Calls(c[i], setGold), "NoDeathPenalty is off", "zeroings of Jin", "PlayerDeathPenalty");
			int num2 = Transpile.FindOne(list, (List<CodeInstruction> c, int i) => i > 0 && CodeInstructionExtensions.LoadsConstant(c[i - 1], 0L) && CodeInstructionExtensions.Calls(c[i], setExp), "NoDeathPenalty is off", "zeroings of experience", "PlayerDeathPenalty");
			if (num < 0 || num2 < 0)
			{
				return list;
			}
			Transpile.CallInstead(list[num], methodInfo);
			Transpile.CallInstead(list[num2], methodInfo2);
			NoDeathPenaltyFeature.Patched = true;
			return list;
		}
	}
}
namespace NineSolsQoL.Features.Combat.PerfectParryWindow
{
	internal static class PerfectParryWindowFeature
	{
		private const float VanillaFrames = 8f;

		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<int> PerfectParryFrames;

		public static bool Patched;

		public static void Init(ConfigFile config)
		{
			ConfigSection configSection = new ConfigSection(config, "PerfectParryWindow", "--------- Combat ---------", "Perfect parry window");
			Enabled = configSection.Bind("Enabled", defaultValue: true, "Change how early you can press parry before a hit and still get a perfect parry. Off: the game's window, 8 frames (133 ms)");
			PerfectParryFrames = configSection.Bind("PerfectParryFrames", 12, "The perfect-parry window, in frames at 60 fps (the game measures time, so it is the same at any frame rate). Vanilla: 8 (133 ms). The game's penalty for mashing parry keeps its shape: pressing again soon after a missed parry gives 3/4 of this window (vanilla 6 frames), and more misses remove it. The game forgets a parry press after 0.5 s (30 frames at 60 fps), so values near 30 make almost every parry perfect", (AcceptableValueBase?)(object)new AcceptableValueRange<int>(1, 30));
			DebugTools.AddDumpSection(Describe);
		}

		public static float PerfectWindow(float[] windows, int level, float elapsed)
		{
			float num = windows[level];
			try
			{
				float num2 = Scale(num);
				if (Log.DebugEnabled)
				{
					Log.Debug($"[parry] level {level}: pressed {Ms(elapsed)} before the check, window {Ms(num2)} " + "(vanilla " + Ms(num) + "): " + Outcome(elapsed, num2, num));
				}
				return num2;
			}
			catch (Exception arg)
			{
				Log.Error($"PerfectParryWindow failed, using the game's window: {arg}");
				return num;
			}
		}

		private static float Scale(float vanilla)
		{
			if (!Enabled.Value || vanilla <= 0f)
			{
				return vanilla;
			}
			return vanilla * ((float)PerfectParryFrames.Value / 8f);
		}

		private static string Outcome(float elapsed, float window, float vanilla)
		{
			bool flag = elapsed <= window;
			if (flag == elapsed <= vanilla)
			{
				if (!flag)
				{
					return "imperfect";
				}
				return "perfect";
			}
			if (!flag)
			{
				return "imperfect (perfect in vanilla)";
			}
			return "perfect (imperfect in vanilla)";
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			PlayerParryState state = core.player.GetState<PlayerParryState>((PlayerStateType)17);
			float[] array = (((Object)(object)state != (Object)null) ? GamePrivates.ParryWindows(state) : null);
			string text = ((array != null) ? $"{MsList(array)} -> {MsList(array.Select(Scale))}, spamLevel {state.spamLevel}" : "parry state not found");
			return new string[1] { $"PerfectParryWindow: enabled={Enabled.Value} frames={PerfectParryFrames.Value} patched={Patched} " + "windows " + text };
		}

		private static string Ms(float seconds)
		{
			return $"{seconds * 1000f:0.0} ms";
		}

		private static string MsList(IEnumerable<float> seconds)
		{
			return "[" + string.Join(", ", seconds.Select((float s) => $"{s * 1000f:0}")) + "] ms";
		}
	}
	[HarmonyPatch]
	internal static class PerfectParryWindowPatches
	{
		[HarmonyPatch(typeof(PlayerParryState), "Parried")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> ReadWindowFromMod(IEnumerable<CodeInstruction> instructions)
		{
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Expected O, but got Unknown
			PerfectParryWindowFeature.Patched = false;
			List<CodeInstruction> list = instructions.ToList();
			FieldInfo windows = AccessTools.Field(typeof(PlayerParryState), "spamDatas");
			FieldInfo level = AccessTools.Field(typeof(PlayerParryState), "spamLevel");
			MethodInfo methodInfo = AccessTools.Method(typeof(PerfectParryWindowFeature), "PerfectWindow", (Type[])null, (Type[])null);
			if (!Transpile.Has("PerfectParryWindow is off", "PlayerParryState.spamDatas or spamLevel", windows, level, methodInfo))
			{
				return list;
			}
			int num = Transpile.FindOne(list, (List<CodeInstruction> c, int i) => i + 5 < c.Count && CodeInstructionExtensions.IsLdloc(c[i], (LocalBuilder)null) && CodeInstructionExtensions.IsLdarg(c[i + 1], (int?)0) && CodeInstructionExtensions.LoadsField(c[i + 2], windows, false) && CodeInstructionExtensions.IsLdarg(c[i + 3], (int?)0) && CodeInstructionExtensions.LoadsField(c[i + 4], level, false) && c[i + 5].opcode == OpCodes.Ldelem_R4, "PerfectParryWindow is off", "reads of the parry window", "PlayerParryState.Parried");
			if (num < 0)
			{
				return list;
			}
			CodeInstruction val = list[num + 5];
			CodeInstruction val2 = new CodeInstruction(list[num].opcode, list[num].operand);
			val2.labels.AddRange(val.labels);
			val.labels.Clear();
			Transpile.CallInstead(val, methodInfo);
			list.Insert(num + 5, val2);
			PerfectParryWindowFeature.Patched = true;
			return list;
		}
	}
}
namespace NineSolsQoL.Features.Combat.ParryFromBehind
{
	internal static class ParryFromBehindFeature
	{
		public static ConfigEntry<bool> Enabled;

		public static void Init(ConfigFile config)
		{
			Enabled = new ConfigSection(config, "ParryFromBehind", "--------- Combat ---------", "Parry from behind").Bind("Enabled", defaultValue: true, "Parry attacks that come from behind Yi. Off: the game's rule, where on the ground, walls, ledges and ropes some attacks can only be parried while Yi faces them or the stick points toward them (in mid-air, away from walls, ledges and ropes, the game already ignores facing). Attacks that cannot be parried at all stay that way");
			DebugTools.AddDumpSection(Describe);
		}

		public static bool AllowFromBehind(EffectDealer checker, DamageDealer attack)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!Enabled.Value)
				{
					return false;
				}
				Player i = Player.i;
				PlayerHealth val = (((Object)(object)i != (Object)null) ? i.health : null);
				if ((Object)(object)val == (Object)null || checker != ((Health)val).parryDealer)
				{
					return false;
				}
				if (Log.DebugEnabled)
				{
					Log.Debug("[parry direction] '" + ((Object)attack).name + "' can be parried from behind (attack rule " + $"{attack.facingDetectionRule}, parry rule {checker.facingDetectionRule})");
				}
				return true;
			}
			catch (Exception arg)
			{
				Log.Error($"ParryFromBehind failed, using the game's facing rule: {arg}");
				return false;
			}
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			Player player = core.player;
			EffectDealer val = (((Object)(object)player.health != (Object)null) ? ((Health)player.health).parryDealer : null);
			if (!((Object)(object)val == (Object)null))
			{
				PlayerParryState state = player.GetState<PlayerParryState>((PlayerStateType)17);
				bool flag = (Object)(object)state != (Object)null && val == state.parryEffectDealer;
				return new string[1] { $"ParryFromBehind: enabled={Enabled.Value} parryDealer='{((Component)val).transform.HierarchyPath()}' " + $"rule={val.facingDetectionRule} sameAsParryState={flag}" };
			}
			return new string[1] { $"ParryFromBehind: enabled={Enabled.Value} no parry dealer" };
		}
	}
	[HarmonyPatch]
	internal static class ParryFromBehindPatches
	{
		[HarmonyPatch(typeof(EffectDealer), "FacingRuleCheck", new Type[]
		{
			typeof(DamageDealer),
			typeof(Vector3),
			typeof(Vector2)
		})]
		[HarmonyPostfix]
		private static void AfterFacingCheck(EffectDealer __instance, DamageDealer damageDealer, ref bool __result)
		{
			if (!__result && ParryFromBehindFeature.AllowFromBehind(__instance, damageDealer))
			{
				__result = true;
			}
		}
	}
}
namespace NineSolsQoL.Features.Combat.DashInvulnerability
{
	internal static class DashInvulnerabilityFeature
	{
		private const float VanillaAfterDash = 0.05f;

		public static ConfigEntry<bool> Enabled = null;

		public static ConfigEntry<float> AfterDash = null;

		public static bool Patched;

		private static int keptDashes;

		private static float lastDashEnd = float.NegativeInfinity;

		private static float latestDodgeEnd = float.NegativeInfinity;

		public static void Init(ConfigFile config)
		{
			ConfigSection configSection = new ConfigSection(config, "DashInvulnerability", "--------- Combat ---------", "Dash invulnerability");
			Enabled = configSection.Bind("Enabled", defaultValue: true, "Keep Yi invulnerable for a little longer after a dash, and keep a dash's invulnerability when another dash starts or ends during that time (in the game, the end of the first dash turns it off). Solid traps still hurt during a dash, as in the game. Off: the game's behaviour");
			AfterDash = configSection.Bind("InvulnerabilityAfterDash", 0.1f, "Seconds Yi stays invulnerable after a dash ends. Attacks can't be parried during that time either. Vanilla: 0.05", (AcceptableValueBase?)(object)new AcceptableValueRange<float>(0.05f, 0.5f));
			DebugTools.AddDumpSection(Describe);
		}

		public static float InvulnerabilityAfterDash()
		{
			float num = CurrentDelay();
			lastDashEnd = Time.time;
			latestDodgeEnd = lastDashEnd + num - Time.deltaTime;
			return num;
		}

		private static float CurrentDelay()
		{
			try
			{
				return Enabled.Value ? AfterDash.Value : 0.05f;
			}
			catch (Exception arg)
			{
				Log.Error($"DashInvulnerability failed, using the game's delay: {arg}");
				return 0.05f;
			}
		}

		public static bool BeforeDodgeEnd()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Invalid comparison between Unknown and I4
			try
			{
				Player i = Player.i;
				bool flag = (Object)(object)i != (Object)null && (int)i.CurrentStateType == 30 && !i.IsWeak;
				bool flag2 = Time.time < latestDodgeEnd - 0.002f;
				if (!flag && !flag2)
				{
					return true;
				}
				if (!Enabled.Value)
				{
					Log.Debug("[dash] another dash followed: the game turns the invulnerability off now (DashInvulnerability is off)");
					return true;
				}
				keptDashes++;
				Log.Debug(flag ? "[dash] a new dash started right after the previous one: its invulnerability is kept" : "[dash] a later dash ended first: its invulnerability is kept until its own end");
				return false;
			}
			catch (Exception arg)
			{
				Log.Error($"DashInvulnerability failed, running the game's DodgeEnd: {arg}");
				return true;
			}
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			return new string[1] { $"DashInvulnerability: enabled={Enabled.Value} patched={Patched} afterDash={CurrentDelay()}s " + $"keptDashes={keptDashes} weak={core.player.IsWeak}" };
		}
	}
	[HarmonyPatch]
	internal static class DashInvulnerabilityPatches
	{
		[HarmonyPatch(typeof(PlayerRollState), "OnStateExit")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> ReadDelayFromMod(IEnumerable<CodeInstruction> instructions)
		{
			DashInvulnerabilityFeature.Patched = false;
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo dodgeEnd = AccessTools.Method(typeof(PlayerRollState), "DodgeEnd", (Type[])null, (Type[])null);
			MethodInfo addTask = AccessTools.Method(typeof(Timer), "AddTask", new Type[4]
			{
				typeof(Action),
				typeof(float),
				typeof(MonoBehaviour),
				typeof(string)
			}, (Type[])null);
			MethodInfo methodInfo = AccessTools.Method(typeof(DashInvulnerabilityFeature), "InvulnerabilityAfterDash", (Type[])null, (Type[])null);
			if (!Transpile.Has("DashInvulnerability keeps the game's delay", "PlayerRollState.DodgeEnd or Timer.AddTask", dodgeEnd, addTask, methodInfo))
			{
				return list;
			}
			int num = Transpile.FindOne(list, (List<CodeInstruction> c, int i) => i >= 2 && i + 3 < c.Count && c[i].opcode == OpCodes.Ldc_R4 && c[i - 2].opcode == OpCodes.Ldftn && object.Equals(c[i - 2].operand, dodgeEnd) && CodeInstructionExtensions.Calls(c[i + 3], addTask), "DashInvulnerability keeps the game's delay", "DodgeEnd timers", "PlayerRollState.OnStateExit");
			if (num < 0)
			{
				return list;
			}
			Transpile.CallInstead(list[num], methodInfo);
			DashInvulnerabilityFeature.Patched = true;
			return list;
		}

		[HarmonyPatch(typeof(PlayerRollState), "DodgeEnd")]
		[HarmonyPrefix]
		private static bool BeforeDodgeEnd()
		{
			return DashInvulnerabilityFeature.BeforeDodgeEnd();
		}
	}
}
namespace NineSolsQoL.Features.Abilities.FastHeal
{
	internal static class FastHealFeature
	{
		public static ConfigEntry<bool> Enabled;

		public static bool Patched;

		public static void Init(ConfigFile config)
		{
			Enabled = new ConfigSection(config, "FastHeal", "--------- Abilities ---------", "Fast heal").Bind("Enabled", defaultValue: true, "Heal with the pipe as fast as with the Quick Dose jade, without equipping it: the animation plays 1.67 times faster, so a heal takes 60% of the time. With the jade equipped nothing changes. Off: the game's speed, faster only with the jade");
			DebugTools.AddDumpSection(Describe);
		}

		public static bool SpeedUpHeal(bool jade)
		{
			if (jade || !Enabled.Value)
			{
				return jade;
			}
			Log.Debug("[heal] at the Quick Dose jade's speed, without the jade");
			return true;
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			PlayerDrinkPotionState state = core.player.GetState<PlayerDrinkPotionState>((PlayerStateType)27);
			PlayerAbilityData val = (((Object)(object)state != (Object)null) ? state.SpeedUpHealJade.AbilityData : null);
			string arg = (((Object)(object)val != (Object)null) ? $"'{((Object)val).name}' acquired={((GameFlagDescriptable)val).IsAcquired} equipped={val.IsEquipped}" : "none");
			return new string[1] { $"FastHeal: enabled={Enabled.Value} patched={Patched} jade {arg}" };
		}
	}
	[HarmonyPatch]
	internal static class FastHealPatches
	{
		[HarmonyPatch(typeof(PlayerDrinkPotionState), "OnStateEnter")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> HealFaster(IEnumerable<CodeInstruction> instructions)
		{
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			FastHealFeature.Patched = false;
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo isSpeedUpHeal = AccessTools.PropertyGetter(typeof(PlayerDrinkPotionState), "isSpeedUpHeal");
			MethodInfo methodInfo = AccessTools.Method(typeof(FastHealFeature), "SpeedUpHeal", (Type[])null, (Type[])null);
			if (!Transpile.Has("FastHeal is off", "PlayerDrinkPotionState.isSpeedUpHeal", isSpeedUpHeal, methodInfo))
			{
				return list;
			}
			int num = Transpile.FindOne(list, (List<CodeInstruction> c, int i) => CodeInstructionExtensions.Calls(c[i], isSpeedUpHeal), "FastHeal is off", "checks of the heal jade", "PlayerDrinkPotionState.OnStateEnter");
			if (num < 0)
			{
				return list;
			}
			list.Insert(num + 1, new CodeInstruction(OpCodes.Call, (object)methodInfo));
			FastHealFeature.Patched = true;
			return list;
		}
	}
}
namespace NineSolsQoL.Features.Abilities.EarlyAirDash
{
	internal static class EarlyAirDashFeature
	{
		public static ConfigEntry<bool> Enabled;

		public static bool Patched;

		public static void Init(ConfigFile config)
		{
			Enabled = new ConfigSection(config, "EarlyAirDash", "--------- Abilities ---------", "Early air dash").Bind("Enabled", defaultValue: false, "Dash in the air before the game gives you its air dash upgrade: once per air time (a double jump does not give another), recharged when you land, slide on a wall, grab a ledge or rope, parry and the like, as the game does after the upgrade. Off: the game's rule, no air dash until the upgrade");
			DebugTools.AddDumpSection(Describe);
		}

		public static bool AirDashUnlocked(PlayerAbilityData upgrade)
		{
			if (upgrade.IsActivated)
			{
				return true;
			}
			if (!Enabled.Value || ((GameFlagDescriptable)upgrade).IsAcquired)
			{
				return false;
			}
			Log.Debug("[air dash] the game's air dash check passed because of EarlyAirDash (upgrade not acquired yet)");
			return true;
		}

		private static IEnumerable<string> Describe(GameCore core)
		{
			PlayerAbilityData val = (((Object)(object)core.playerAbilityCollection != (Object)null) ? core.playerAbilityCollection.RollDodgeInAirUpgrade : null);
			string arg = (((Object)(object)val != (Object)null) ? $"acquired={((GameFlagDescriptable)val).IsAcquired} equipped={val.IsEquipped}" : "missing");
			return new string[1] { $"EarlyAirDash: enabled={Enabled.Value} patched={Patched} game's upgrade {arg} " + $"airDodgeCount={core.player.airDodgeCount}" };
		}
	}
	[HarmonyPatch]
	internal static class EarlyAirDashPatches
	{
		[HarmonyPatch(typeof(Player), "DodgeCheck")]
		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> AllowAirDash(IEnumerable<CodeInstruction> instructions)
		{
			EarlyAirDashFeature.Patched = false;
			List<CodeInstruction> list = instructions.ToList();
			FieldInfo upgrade = AccessTools.Field(typeof(PlayerMainAbilityCollection), "RollDodgeInAirUpgrade");
			MethodInfo isActivated = AccessTools.PropertyGetter(typeof(PlayerAbilityData), "IsActivated");
			MethodInfo methodInfo = AccessTools.Method(typeof(EarlyAirDashFeature), "AirDashUnlocked", (Type[])null, (Type[])null);
			if (!Transpile.Has("EarlyAirDash is off", "PlayerMainAbilityCollection.RollDodgeInAirUpgrade or PlayerAbilityData.IsActivated", upgrade, isActivated, methodInfo))
			{
				return list;
			}
			int num = Transpile.FindOne(list, (List<CodeInstruction> c, int i) => i > 0 && CodeInstructionExtensions.LoadsField(c[i - 1], upgrade, false) && CodeInstructionExtensions.Calls(c[i], isActivated), "EarlyAirDash is off", "checks of the air dash upgrade", "Player.DodgeCheck");
			if (num < 0)
			{
				return list;
			}
			Transpile.CallInstead(list[num], methodInfo);
			EarlyAirDashFeature.Patched = true;
			return list;
		}
	}
}
namespace NineSolsQoL.Core
{
	internal static class MenuCategory
	{
		public const string Abilities = "--------- Abilities ---------";

		public const string Combat = "--------- Combat ---------";

		public const string Death = "--------- Death ---------";

		public const string Hud = "--------- HUD ---------";

		public const string Loot = "--------- Loot and Jin ---------";

		public const string Travel = "--------- Travel ---------";

		public const string Debug = "--------- Debug ---------";
	}
	internal sealed class ConfigSection
	{
		private const string Indent = "    ";

		private static int nextOrder;

		private readonly ConfigFile config;

		private readonly string section;

		private readonly string category;

		private readonly string? title;

		public ConfigSection(ConfigFile config, string section, string category, string? title)
		{
			this.config = config;
			this.section = section;
			this.category = category;
			this.title = title;
		}

		public ConfigEntry<T> Bind<T>(string key, T defaultValue, string description, AcceptableValueBase? range = null, bool advanced = false, string? name = null)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Expected O, but got Unknown
			string text = ((key == "Enabled" && title != null) ? title : (name ?? Words(key)));
			if (title != null && key != "Enabled")
			{
				text = "    " + text;
			}
			ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes
			{
				Order = nextOrder--,
				IsAdvanced = advanced,
				Category = category,
				DispName = text
			};
			return config.Bind<T>(section, key, defaultValue, new ConfigDescription(description, range, new object[1] { configurationManagerAttributes }));
		}

		private static string Words(string key)
		{
			List<string> list = new List<string>();
			int num = 0;
			for (int i = 1; i < key.Length; i++)
			{
				char c = ((i + 1 < key.Length) ? key[i + 1] : ' ');
				if (char.IsUpper(key[i]) && (char.IsLower(key[i - 1]) || char.IsLower(c)))
				{
					list.Add(key.Substring(num, i - num));
					num = i;
				}
			}
			list.Add(key.Substring(num));
			return string.Join(" ", list.Select((string word, int num2) => (num2 != 0 && !word.All(char.IsUpper)) ? word.ToLowerInvariant() : word));
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public int? Order;

		public bool? IsAdvanced;

		public string? Category;

		public string? DispName;
	}
	internal static class DebugTools
	{
		private const float DumpRadius = 48f;

		private const float WatchInterval = 0.5f;

		private const float HitchSeconds = 0.1f;

		public static ConfigEntry<bool> DebugLogging = null;

		public static ConfigEntry<KeyboardShortcut> DumpKey = null;

		private static readonly List<Func<GameCore, IEnumerable<string>>> dumpSections = new List<Func<GameCore, IEnumerable<string>>>();

		private static string watchReason = "";

		private static float watchUntil;

		private static float nextWatchLog;

		private static float lastFrameTime;

		public static void Init(ConfigFile config)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			ConfigSection configSection = new ConfigSection(config, "Debug", "--------- Debug ---------", null);
			DebugLogging = configSection.Bind("DebugLogging", defaultValue: false, "Write detailed diagnostics to the BepInEx log", null, advanced: true);
			DumpKey = configSection.Bind<KeyboardShortcut>("DumpKey", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "Writes the player state, each feature's state and every collider around the player to the BepInEx log", null, advanced: true);
		}

		public static void AddDumpSection(Func<GameCore, IEnumerable<string>> section)
		{
			dumpSections.Add(section);
		}

		public static void Update()
		{
			//IL_000a: 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)
			CheckHitch();
			KeyboardShortcut value = DumpKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				DumpSurroundings();
			}
			TickWatch();
		}

		public static void Watch(string reason, float seconds = 10f)
		{
			watchReason = reason;
			watchUntil = Time.realtimeSinceStartup + seconds;
			nextWatchLog = 0f;
		}

		private static void TickWatch()
		{
			if (Log.DebugEnabled)
			{
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (!(realtimeSinceStartup >= watchUntil) && !(realtimeSinceStartup < nextWatchLog))
				{
					nextWatchLog = realtimeSinceStartup + 0.5f;
					GameCore val = GameAccess.CoreInLevel();
					Log.Info("[watch " + watchReason + "] " + (((Object)(object)val != (Object)null) ? (DescribeState(val) + " | " + DescribeDeathBag(val)) : "no level loaded"));
				}
			}
		}

		private static void CheckHitch()
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Invalid comparison between Unknown and I4
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			float num = realtimeSinceStartup - lastFrameTime;
			bool num2 = lastFrameTime == 0f;
			lastFrameTime = realtimeSinceStartup;
			if (!num2 && !(num < 0.1f) && DebugLogging.Value)
			{
				GameCore val = GameAccess.CoreInLevel();
				if (!((Object)(object)val == (Object)null) && (int)val.currentCoreState == 1)
				{
					Log.Info($"[hitch] a frame took {num * 1000f:0} ms in {val.CurrentSceneName} " + $"(player state {val.player.CurrentStateType})");
				}
			}
		}

		public static string DescribeState(GameCore core)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			Player player = core.player;
			return $"core={core.currentCoreState} scene={core.CurrentSceneName} pos={((Component)player).transform.position} " + $"state={player.CurrentStateType} input={player.playerInput.currentStateType} " + $"lockMoving={player.lockMoving} freeze={player.freeze} isCloaking={player.isCloaking} " + $"active={((Component)player).gameObject.activeInHierarchy} timeScale={Time.timeScale}";
		}

		public static string DescribeDeathBag(GameCore core)
		{
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			PlayerDeadRecord deadRecord = cor