Decompiled source of SilksongTweaks v1.0.0

BepInEx/plugins/SilksongTweaks.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using SilksongTweaks.Modules;
using SilksongTweaks.Rules;
using SilksongTweaks.Ui;
using UnityEngine;

[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("SilksongTweaks")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+58ec265190fc9f177178d92ee57f7273a21c6c75")]
[assembly: AssemblyProduct("SilksongTweaks")]
[assembly: AssemblyTitle("SilksongTweaks")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace SilksongTweaks
{
	public enum HookKind
	{
		Method,
		PropertyGetter,
		Field
	}
	public sealed class HookTarget
	{
		public string DeclaringType { get; }

		public string Member { get; }

		public HookKind Kind { get; }

		public string UsedBy { get; }

		public HookTarget(string declaringType, string member, HookKind kind, string usedBy)
		{
			DeclaringType = declaringType;
			Member = member;
			Kind = kind;
			UsedBy = usedBy;
		}

		public override string ToString()
		{
			return $"{DeclaringType}.{Member} ({Kind}, for {UsedBy})";
		}
	}
	public static class HookTargets
	{
		public static readonly HookTarget HeroRespawned = new HookTarget("HeroController", "HeroRespawned", HookKind.Method, "ReturnToDeath");

		public static readonly HookTarget Die = new HookTarget("HeroController", "Die", HookKind.Method, "ReturnToDeath, KeepRosaries");

		public static readonly HookTarget BeginSceneTransition = new HookTarget("GameManager", "BeginSceneTransition", HookKind.Method, "ReturnToDeath");

		public static readonly HookTarget SetHazardRespawn = new HookTarget("HeroController", "SetHazardRespawn", HookKind.Method, "ReturnToDeath");

		public static readonly HookTarget CocoonBroken = new HookTarget("HeroController", "CocoonBroken", HookKind.Method, "KeepRosaries");

		public static readonly HookTarget CorpseMoneyPool = new HookTarget("PlayerData", "HeroCorpseMoneyPool", HookKind.Field, "KeepRosaries");

		public static readonly HookTarget CurrentMaxHealth = new HookTarget("PlayerData", "CurrentMaxHealth", HookKind.PropertyGetter, "MaxHealth");

		public static readonly HookTarget GetInt = new HookTarget("PlayerData", "GetInt", HookKind.Method, "MaxHealth");

		public static readonly HookTarget MaxHealthField = new HookTarget("PlayerData", "maxHealth", HookKind.Field, "MaxHealth");

		public static readonly HookTarget PlayerDataAddHealth = new HookTarget("PlayerData", "AddHealth", HookKind.Method, "MaxHealth");

		public static readonly HookTarget PlayerDataTakeHealth = new HookTarget("PlayerData", "TakeHealth", HookKind.Method, "MaxHealth");

		public static readonly HookTarget PlayerDataMaxHealth = new HookTarget("PlayerData", "MaxHealth", HookKind.Method, "MaxHealth");

		public static readonly HookTarget TakeDamage = new HookTarget("HeroController", "TakeDamage", HookKind.Method, "DamageTaken");

		public static IReadOnlyList<HookTarget> All { get; } = new List<HookTarget>
		{
			HeroRespawned, Die, BeginSceneTransition, SetHazardRespawn, CocoonBroken, CorpseMoneyPool, CurrentMaxHealth, GetInt, MaxHealthField, PlayerDataAddHealth,
			PlayerDataTakeHealth, PlayerDataMaxHealth, TakeDamage
		};
	}
	public interface ITweakModule
	{
		string Id { get; }

		string DisplayName { get; }

		string Description { get; }

		TweakStatus Status { get; }

		long LastFiredUtcTicks { get; }

		IReadOnlyList<ISettingRow> Settings { get; }

		void BindConfig(ConfigFile config);

		TweakStatus TryApply(Harmony harmony);
	}
	public sealed class ModuleRegistry
	{
		private readonly List<ITweakModule> _modules = new List<ITweakModule>();

		private readonly ManualLogSource _log;

		public IReadOnlyList<ITweakModule> Modules => _modules;

		public int ActiveCount { get; private set; }

		public ModuleRegistry(ManualLogSource log)
		{
			_log = log;
		}

		public void Add(ITweakModule module)
		{
			_modules.Add(module);
		}

		public void ApplyAll(ConfigFile config, Harmony harmony)
		{
			ActiveCount = 0;
			foreach (ITweakModule module in _modules)
			{
				try
				{
					module.BindConfig(config);
				}
				catch (Exception arg)
				{
					_log.LogError((object)$"[{module.Id}] config binding failed: {arg}");
					continue;
				}
				TweakStatus tweakStatus = module.TryApply(harmony);
				if (tweakStatus.State == TweakState.Unavailable)
				{
					_log.LogWarning((object)("[" + module.Id + "] UNAVAILABLE: " + tweakStatus.Reason));
					continue;
				}
				ActiveCount++;
				_log.LogInfo((object)("[" + module.Id + "] hooked OK"));
			}
			_log.LogInfo((object)$"{ActiveCount}/{_modules.Count} tweak(s) hooked successfully");
		}
	}
	[BepInPlugin("com.dloizides.silksongtweaks", "Silksong Tweaks", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.dloizides.silksongtweaks";

		public const string PluginName = "Silksong Tweaks";

		public const string PluginVersion = "1.0.0";

		private ModuleRegistry _registry;

		private TweakWindow _window;

		private ConfigEntry<KeyCode> _toggleKey;

		private ConfigEntry<KeyCode> _gamepadToggleButton;

		private ConfigEntry<bool> _freezeWhileOpen;

		private bool _uiBroken;

		private bool _wasVisible;

		private float _timeScaleBeforeOpen = 1f;

		public static Plugin Instance { get; private set; }

		public static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				_toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "ToggleKey", (KeyCode)289, "Opens and closes the Silksong Tweaks panel.");
				_gamepadToggleButton = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("General", "GamepadToggleButton", (KeyCode)336, "Gamepad button that opens the panel. JoystickButton6 is Back/View/Share.");
				_freezeWhileOpen = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "FreezeGameWhileOpen", true, "Pause the game while the panel is open, so navigating it does not also move Hornet. Turn off if it conflicts with anything.");
				_registry = new ModuleRegistry(Log);
				_registry.Add(new ReturnToDeathModule());
				_registry.Add(new KeepRosariesModule());
				_registry.Add(new MaxHealthModule());
				_registry.Add(new DamageTakenModule());
				_registry.ApplyAll(((BaseUnityPlugin)this).Config, new Harmony("com.dloizides.silksongtweaks"));
				_window = new TweakWindow(_registry);
				Log.LogInfo((object)string.Format("{0} {1} ready. Press {2} for the panel.", "Silksong Tweaks", "1.0.0", _toggleKey.Value));
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"startup failed: {arg}");
			}
		}

		public Coroutine Run(IEnumerator routine)
		{
			return ((MonoBehaviour)this).StartCoroutine(routine);
		}

		private void Update()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			if (_window != null && _toggleKey != null)
			{
				if (Input.GetKeyDown(_toggleKey.Value) || (_gamepadToggleButton != null && Input.GetKeyDown(_gamepadToggleButton.Value)))
				{
					_window.Visible = !_window.Visible;
				}
				_window.HandleInput(Time.unscaledDeltaTime);
				ApplyFreeze();
			}
		}

		private void ApplyFreeze()
		{
			if (_freezeWhileOpen == null || !_freezeWhileOpen.Value)
			{
				_wasVisible = _window.Visible;
			}
			else if (_window.Visible != _wasVisible)
			{
				_wasVisible = _window.Visible;
				if (_window.Visible)
				{
					_timeScaleBeforeOpen = Time.timeScale;
					Time.timeScale = 0f;
				}
				else
				{
					Time.timeScale = ((_timeScaleBeforeOpen <= 0f) ? 1f : _timeScaleBeforeOpen);
				}
			}
		}

		private void OnDestroy()
		{
			if (_wasVisible && Time.timeScale == 0f)
			{
				Time.timeScale = 1f;
			}
		}

		private void OnGUI()
		{
			if (_window == null || _uiBroken)
			{
				return;
			}
			try
			{
				_window.Draw();
			}
			catch (Exception arg)
			{
				_uiBroken = true;
				Log.LogError((object)$"UI disabled after a draw error: {arg}");
			}
		}
	}
	public interface ISettingRow
	{
		string Label { get; }

		string Tooltip { get; }
	}
	public sealed class BoolRow : ISettingRow
	{
		public string Label { get; }

		public string Tooltip { get; }

		public ConfigEntry<bool> Entry { get; }

		public BoolRow(string label, string tooltip, ConfigEntry<bool> entry)
		{
			Label = label;
			Tooltip = tooltip;
			Entry = entry;
		}
	}
	public sealed class IntRow : ISettingRow
	{
		public string Label { get; }

		public string Tooltip { get; }

		public ConfigEntry<int> Entry { get; }

		public int Min { get; }

		public int Max { get; }

		public IntRow(string label, string tooltip, ConfigEntry<int> entry, int min, int max)
		{
			Label = label;
			Tooltip = tooltip;
			Entry = entry;
			Min = min;
			Max = max;
		}
	}
	public sealed class FloatRow : ISettingRow
	{
		public string Label { get; }

		public string Tooltip { get; }

		public ConfigEntry<float> Entry { get; }

		public float Min { get; }

		public float Max { get; }

		public string Format { get; }

		public FloatRow(string label, string tooltip, ConfigEntry<float> entry, float min, float max, string format)
		{
			Label = label;
			Tooltip = tooltip;
			Entry = entry;
			Min = min;
			Max = max;
			Format = format;
		}
	}
	public sealed class KeyRow : ISettingRow
	{
		public string Label { get; }

		public string Tooltip { get; }

		public ConfigEntry<KeyCode> Entry { get; }

		public KeyRow(string label, string tooltip, ConfigEntry<KeyCode> entry)
		{
			Label = label;
			Tooltip = tooltip;
			Entry = entry;
		}
	}
	public enum TweakState
	{
		Active,
		Disabled,
		Unavailable
	}
	public sealed class TweakStatus
	{
		public TweakState State { get; }

		public string Reason { get; }

		public static TweakStatus Active { get; } = new TweakStatus(TweakState.Active, string.Empty);

		public static TweakStatus Disabled { get; } = new TweakStatus(TweakState.Disabled, string.Empty);

		private TweakStatus(TweakState state, string reason)
		{
			State = state;
			Reason = reason;
		}

		public static TweakStatus Unavailable(string reason)
		{
			return new TweakStatus(TweakState.Unavailable, reason ?? "unknown");
		}
	}
}
namespace SilksongTweaks.Ui
{
	public static class Conflicts
	{
		private static readonly string[] Known = new string[3] { "ReBack.dll", "com.blueraja.rosaries_never_permanently_lost.dll", "CustomDifficulty" };

		public static IReadOnlyList<string> Detect()
		{
			List<string> list = new List<string>();
			try
			{
				string pluginPath = Paths.PluginPath;
				if (string.IsNullOrEmpty(pluginPath) || !Directory.Exists(pluginPath))
				{
					return list;
				}
				string[] known = Known;
				foreach (string text in known)
				{
					string path = Path.Combine(pluginPath, text);
					if (File.Exists(path) || Directory.Exists(path))
					{
						list.Add(text);
						continue;
					}
					string[] files = Directory.GetFiles(pluginPath, text, SearchOption.AllDirectories);
					int num = 0;
					if (num < files.Length)
					{
						string path2 = files[num];
						list.Add(Path.GetFileName(path2));
					}
				}
			}
			catch
			{
			}
			return list;
		}
	}
	public sealed class PanelInput
	{
		private const float DeadZone = 0.5f;

		private const float FirstRepeatDelay = 0.35f;

		private const float RepeatInterval = 0.09f;

		private static bool _axesUnavailable;

		private float _verticalHeldFor;

		private float _horizontalHeldFor;

		private int _lastVertical;

		private int _lastHorizontal;

		public int Vertical { get; private set; }

		public int Horizontal { get; private set; }

		public bool Activate { get; private set; }

		public bool Cancel { get; private set; }

		public void Sample(float unscaledDelta)
		{
			Vertical = Step(RawVertical(), ref _lastVertical, ref _verticalHeldFor, unscaledDelta);
			Horizontal = Step(RawHorizontal(), ref _lastHorizontal, ref _horizontalHeldFor, unscaledDelta);
			Activate = Input.GetKeyDown((KeyCode)330) || Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)32);
			Cancel = Input.GetKeyDown((KeyCode)331) || Input.GetKeyDown((KeyCode)27);
		}

		private static int RawVertical()
		{
			if (Input.GetKey((KeyCode)273))
			{
				return -1;
			}
			if (Input.GetKey((KeyCode)274))
			{
				return 1;
			}
			float num = SafeAxis("Vertical");
			if (num > 0.5f)
			{
				return -1;
			}
			if (num < -0.5f)
			{
				return 1;
			}
			return 0;
		}

		private static int RawHorizontal()
		{
			if (Input.GetKey((KeyCode)276))
			{
				return -1;
			}
			if (Input.GetKey((KeyCode)275))
			{
				return 1;
			}
			float num = SafeAxis("Horizontal");
			if (num < -0.5f)
			{
				return -1;
			}
			if (num > 0.5f)
			{
				return 1;
			}
			return 0;
		}

		private static float SafeAxis(string name)
		{
			if (_axesUnavailable)
			{
				return 0f;
			}
			try
			{
				return Input.GetAxisRaw(name);
			}
			catch
			{
				_axesUnavailable = true;
				Plugin.Log.LogWarning((object)("Input axis '" + name + "' is not defined by the game; gamepad sticks disabled. Keyboard navigation still works."));
				return 0f;
			}
		}

		private static int Step(int raw, ref int last, ref float heldFor, float delta)
		{
			if (raw == 0)
			{
				last = 0;
				heldFor = 0f;
				return 0;
			}
			if (raw != last)
			{
				last = raw;
				heldFor = 0f;
				return raw;
			}
			heldFor += delta;
			if (heldFor < 0.35f)
			{
				return 0;
			}
			heldFor -= 0.09f;
			return raw;
		}
	}
	public sealed class Theme
	{
		public static readonly Color Silk = new Color(0.93f, 0.91f, 0.86f);

		public static readonly Color Muted = new Color(0.62f, 0.6f, 0.58f);

		public static readonly Color Accent = new Color(0.85f, 0.44f, 0.52f);

		public static readonly Color Good = new Color(0.55f, 0.8f, 0.55f);

		public static readonly Color Warn = new Color(0.92f, 0.74f, 0.35f);

		public static readonly Color Bad = new Color(0.88f, 0.45f, 0.45f);

		public static readonly Color Panel = new Color(0.09f, 0.09f, 0.12f, 0.96f);

		private bool _built;

		public GUIStyle Window { get; private set; }

		public GUIStyle SectionTitle { get; private set; }

		public GUIStyle SectionDesc { get; private set; }

		public GUIStyle RowLabel { get; private set; }

		public GUIStyle Value { get; private set; }

		public GUIStyle Badge { get; private set; }

		public GUIStyle Footer { get; private set; }

		public GUIStyle Toast { get; private set; }

		public GUIStyle SelectedRow { get; private set; }

		public void EnsureBuilt()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_0038: Expected O, but got Unknown
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Expected O, but got Unknown
			//IL_00dc: Expected O, but got Unknown
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Expected O, but got Unknown
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Expected O, but got Unknown
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0174: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Expected O, but got Unknown
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Expected O, but got Unknown
			//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01eb: Expected O, but got Unknown
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_020b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_0218: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0226: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Expected O, but got Unknown
			//IL_023e: Expected O, but got Unknown
			//IL_025d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0277: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_028c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0296: Expected O, but got Unknown
			//IL_0296: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Expected O, but got Unknown
			//IL_02ac: Expected O, but got Unknown
			//IL_02da: Unknown result type (might be due to invalid IL or missing references)
			if (!_built)
			{
				_built = true;
				Window = new GUIStyle(GUI.skin.window)
				{
					padding = new RectOffset(14, 14, 24, 12)
				};
				Window.normal.background = SolidTexture(Panel);
				Window.onNormal.background = Window.normal.background;
				Window.normal.textColor = Silk;
				Window.onNormal.textColor = Silk;
				Window.fontStyle = (FontStyle)1;
				SectionTitle = new GUIStyle(GUI.skin.label)
				{
					fontStyle = (FontStyle)1,
					fontSize = 14,
					margin = new RectOffset(0, 0, 10, 2)
				};
				SectionTitle.normal.textColor = Accent;
				SectionDesc = new GUIStyle(GUI.skin.label)
				{
					fontSize = 11,
					wordWrap = true
				};
				SectionDesc.normal.textColor = Muted;
				RowLabel = new GUIStyle(GUI.skin.label)
				{
					fontSize = 12
				};
				RowLabel.normal.textColor = Silk;
				Value = new GUIStyle(GUI.skin.label)
				{
					fontSize = 12,
					alignment = (TextAnchor)5,
					fontStyle = (FontStyle)1
				};
				Value.normal.textColor = Silk;
				Badge = new GUIStyle(GUI.skin.label)
				{
					fontSize = 10,
					alignment = (TextAnchor)5,
					fontStyle = (FontStyle)1
				};
				Footer = new GUIStyle(GUI.skin.label)
				{
					fontSize = 10,
					wordWrap = true
				};
				Footer.normal.textColor = Muted;
				Toast = new GUIStyle(GUI.skin.box)
				{
					fontSize = 15,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)4,
					padding = new RectOffset(18, 18, 12, 12)
				};
				Toast.normal.background = SolidTexture(new Color(0.06f, 0.06f, 0.08f, 0.88f));
				Toast.normal.textColor = Silk;
				SelectedRow = new GUIStyle
				{
					padding = new RectOffset(6, 6, 1, 1),
					margin = new RectOffset(-6, -6, 0, 0)
				};
				SelectedRow.normal.background = SolidTexture(new Color(Accent.r, Accent.g, Accent.b, 0.22f));
			}
		}

		private static Texture2D SolidTexture(Color color)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false);
			val.SetPixel(0, 0, color);
			val.Apply();
			((Object)val).hideFlags = (HideFlags)61;
			return val;
		}
	}
	public sealed class TweakWindow
	{
		private struct NavRow
		{
			public ITweakModule Module;

			public ISettingRow Row;
		}

		private const int WindowId = 5327699;

		private static readonly Vector2 Size = new Vector2(430f, 560f);

		private const int FloatSliderSteps = 20;

		private readonly ModuleRegistry _registry;

		private readonly Theme _theme = new Theme();

		private readonly PanelInput _input = new PanelInput();

		private readonly Dictionary<string, bool> _listening = new Dictionary<string, bool>();

		private readonly List<NavRow> _nav = new List<NavRow>();

		private IReadOnlyList<string> _conflicts;

		private Rect _rect = new Rect(60f, 60f, Size.x, Size.y);

		private Vector2 _scroll;

		private bool _showCredits;

		private int _focus;

		private int _drawIndex;

		public bool Visible { get; set; }

		public TweakWindow(ModuleRegistry registry)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			_registry = registry;
		}

		public void HandleInput(float unscaledDelta)
		{
			if (!Visible)
			{
				return;
			}
			RebuildNav();
			if (_nav.Count == 0)
			{
				return;
			}
			_input.Sample(unscaledDelta);
			if (_input.Cancel)
			{
				Visible = false;
				return;
			}
			if (_input.Vertical != 0)
			{
				_focus = (_focus + _input.Vertical + _nav.Count) % _nav.Count;
			}
			if (_input.Horizontal != 0)
			{
				Adjust(_nav[_focus], _input.Horizontal);
			}
			if (_input.Activate)
			{
				Activate(_nav[_focus]);
			}
		}

		private void RebuildNav()
		{
			_nav.Clear();
			foreach (ITweakModule module in _registry.Modules)
			{
				if (module.Status.State == TweakState.Unavailable)
				{
					continue;
				}
				foreach (ISettingRow setting in module.Settings)
				{
					_nav.Add(new NavRow
					{
						Module = module,
						Row = setting
					});
				}
			}
			if (_focus >= _nav.Count)
			{
				_focus = 0;
			}
		}

		private void Adjust(NavRow nav, int direction)
		{
			if (nav.Row is BoolRow boolRow)
			{
				boolRow.Entry.Value = direction > 0;
			}
			else if (nav.Row is IntRow intRow)
			{
				intRow.Entry.Value = Mathf.Clamp(intRow.Entry.Value + direction, intRow.Min, intRow.Max);
			}
			else if (nav.Row is FloatRow floatRow)
			{
				float num = (floatRow.Max - floatRow.Min) / 20f;
				floatRow.Entry.Value = Mathf.Clamp(floatRow.Entry.Value + (float)direction * num, floatRow.Min, floatRow.Max);
			}
		}

		private void Activate(NavRow nav)
		{
			if (nav.Row is BoolRow boolRow)
			{
				boolRow.Entry.Value = !boolRow.Entry.Value;
			}
			else if (nav.Row is KeyRow keyRow)
			{
				string key = nav.Module.Id + ":" + keyRow.Label;
				_listening[key] = !Listening(key);
			}
		}

		private bool Listening(string key)
		{
			bool value;
			return _listening.TryGetValue(key, out value) && value;
		}

		public void Draw()
		{
			//IL_0034: 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_00ae: Expected O, but got Unknown
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			_theme.EnsureBuilt();
			DrawCountdownToast();
			if (Visible)
			{
				if (_conflicts == null)
				{
					_conflicts = Conflicts.Detect();
				}
				_rect = GUILayout.Window(5327699, _rect, new WindowFunction(DrawBody), $"  SILKSONG TWEAKS   ·   {_registry.ActiveCount}/{_registry.Modules.Count} active", _theme.Window, (GUILayoutOption[])(object)new GUILayoutOption[2]
				{
					GUILayout.Width(Size.x),
					GUILayout.Height(Size.y)
				});
			}
		}

		private void DrawCountdownToast()
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Expected O, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			ReturnToDeathModule returnToDeathModule = null;
			foreach (ITweakModule module in _registry.Modules)
			{
				returnToDeathModule = module as ReturnToDeathModule;
				if (returnToDeathModule != null)
				{
					break;
				}
			}
			if (returnToDeathModule != null && !(returnToDeathModule.CountdownRemaining <= 0f))
			{
				string text = $"Returning to where you died in {returnToDeathModule.CountdownRemaining:0.0}s" + $"\nPress {returnToDeathModule.CancelKey} to stay here";
				Vector2 val = _theme.Toast.CalcSize(new GUIContent(text));
				GUI.Label(new Rect(((float)Screen.width - val.x) * 0.5f, (float)Screen.height * 0.14f, val.x, val.y), text, _theme.Toast);
			}
		}

		private void DrawBody(int id)
		{
			//IL_001e: 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_00c0: Unknown result type (might be due to invalid IL or missing references)
			if (_conflicts != null && _conflicts.Count > 0)
			{
				DrawConflictWarning();
			}
			_scroll = GUILayout.BeginScrollView(_scroll, Array.Empty<GUILayoutOption>());
			_drawIndex = 0;
			foreach (ITweakModule module in _registry.Modules)
			{
				DrawModule(module);
			}
			GUILayout.Space(6f);
			GUILayout.Label("Move: D-pad / left stick / arrows    Change: left-right    Toggle: A / Enter\nClose: B / Esc / the open button", _theme.Footer, Array.Empty<GUILayoutOption>());
			GUILayout.Space(6f);
			DrawCredits();
			GUILayout.EndScrollView();
			GUI.DragWindow(new Rect(0f, 0f, Size.x, 22f));
		}

		private void DrawConflictWarning()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			Color textColor = _theme.SectionDesc.normal.textColor;
			_theme.SectionDesc.normal.textColor = Theme.Bad;
			GUILayout.Label("CONFLICT: these mods do the same job and will fight this one.\n  · " + string.Join("\n  · ", ToArray(_conflicts)) + "\nRemove them from BepInEx/plugins.", _theme.SectionDesc, Array.Empty<GUILayoutOption>());
			_theme.SectionDesc.normal.textColor = textColor;
			GUILayout.Space(6f);
		}

		private void DrawModule(ITweakModule module)
		{
			GUILayout.Label(module.DisplayName, _theme.SectionTitle, Array.Empty<GUILayoutOption>());
			Widgets.StatusBadge(_theme, module.Status, module.LastFiredUtcTicks);
			if (module.Status.State == TweakState.Unavailable)
			{
				GUILayout.Label(module.Description, _theme.SectionDesc, Array.Empty<GUILayoutOption>());
				GUILayout.Space(6f);
				return;
			}
			foreach (ISettingRow setting in module.Settings)
			{
				bool selected = _drawIndex == _focus;
				_drawIndex++;
				DrawRow(module, setting, selected);
			}
			GUILayout.Space(6f);
		}

		private void DrawRow(ITweakModule module, ISettingRow row, bool selected)
		{
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			if (selected)
			{
				GUILayout.BeginHorizontal(_theme.SelectedRow, Array.Empty<GUILayoutOption>());
			}
			if (row is BoolRow boolRow)
			{
				bool value = boolRow.Entry.Value;
				Widgets.Toggle(_theme, boolRow.Label, boolRow.Tooltip, ref value);
				if (value != boolRow.Entry.Value)
				{
					boolRow.Entry.Value = value;
				}
			}
			else if (row is IntRow intRow)
			{
				int value2 = intRow.Entry.Value;
				Widgets.IntSlider(_theme, intRow.Label, intRow.Tooltip, ref value2, intRow.Min, intRow.Max);
				if (value2 != intRow.Entry.Value)
				{
					intRow.Entry.Value = value2;
				}
			}
			else if (row is FloatRow floatRow)
			{
				float value3 = floatRow.Entry.Value;
				Widgets.FloatSlider(_theme, floatRow.Label, floatRow.Tooltip, ref value3, new FloatRange(floatRow.Min, floatRow.Max, floatRow.Format));
				if (!Mathf.Approximately(value3, floatRow.Entry.Value))
				{
					floatRow.Entry.Value = value3;
				}
			}
			else if (row is KeyRow keyRow)
			{
				string key = module.Id + ":" + keyRow.Label;
				bool listening = Listening(key);
				KeyCode value4 = keyRow.Entry.Value;
				Widgets.KeyBinder(_theme, keyRow.Label, keyRow.Tooltip, ref value4, ref listening);
				_listening[key] = listening;
				if (value4 != keyRow.Entry.Value)
				{
					keyRow.Entry.Value = value4;
				}
			}
			if (selected)
			{
				GUILayout.EndHorizontal();
			}
		}

		private void DrawCredits()
		{
			_showCredits = GUILayout.Toggle(_showCredits, _showCredits ? "Credits" : "Credits ...", Array.Empty<GUILayoutOption>());
			if (_showCredits)
			{
				GUILayout.Label("Silksong Tweaks is an independent implementation, built against the game's own API. It replaces three mods whose ideas it owes a debt to — with thanks to:\n  - Xiaohai (XiaohaiMod) - ReBack, for returning to the death location\n  - BlueRaja - rosaries never permanently lost, for cocoon merging\n  - Ericky1694 - CustomDifficulty, for health and damage tuning\n\nMIT licensed. Built on BepInEx and HarmonyX.", _theme.Footer, Array.Empty<GUILayoutOption>());
			}
		}

		private static string[] ToArray(IReadOnlyList<string> items)
		{
			string[] array = new string[items.Count];
			for (int i = 0; i < items.Count; i++)
			{
				array[i] = items[i];
			}
			return array;
		}
	}
	public static class Widgets
	{
		private const float LabelWidth = 190f;

		private const float ValueWidth = 60f;

		private const float RowHeight = 22f;

		public static void Toggle(Theme theme, string label, string tooltip, ref bool value)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) });
			GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) });
			GUILayout.FlexibleSpace();
			value = GUILayout.Toggle(value, value ? " ON" : " OFF", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) });
			GUILayout.EndHorizontal();
		}

		public static void IntSlider(Theme theme, string label, string tooltip, ref int value, int min, int max)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) });
			GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) });
			value = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)value, (float)min, (float)max, Array.Empty<GUILayoutOption>()));
			GUILayout.Label(value.ToString(), theme.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) });
			GUILayout.EndHorizontal();
		}

		public static void FloatSlider(Theme theme, string label, string tooltip, ref float value, FloatRange range)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) });
			GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) });
			value = GUILayout.HorizontalSlider(value, range.Min, range.Max, Array.Empty<GUILayoutOption>());
			GUILayout.Label(Format(value, range.Format), theme.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) });
			GUILayout.EndHorizontal();
		}

		public static void KeyBinder(Theme theme, string label, string tooltip, ref KeyCode value, ref bool listening)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Expected I4, but got Unknown
			GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) });
			GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) });
			GUILayout.FlexibleSpace();
			if (GUILayout.Button(listening ? "press a key..." : ((object)Unsafe.As<KeyCode, KeyCode>(ref value)/*cast due to .constrained prefix*/).ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }))
			{
				listening = !listening;
			}
			if (listening && Event.current != null && Event.current.isKey && (int)Event.current.keyCode != 0)
			{
				value = (KeyCode)(int)Event.current.keyCode;
				listening = false;
				Event.current.Use();
			}
			GUILayout.EndHorizontal();
		}

		public static void StatusBadge(Theme theme, TweakStatus status, long lastFiredTicks)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: 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_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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)
			string text;
			Color textColor;
			switch (status.State)
			{
			case TweakState.Active:
				text = ((lastFiredTicks > 0) ? ("ACTIVE  ·  fired " + Ago(lastFiredTicks)) : "ACTIVE  ·  not yet fired");
				textColor = ((lastFiredTicks > 0) ? Theme.Good : Theme.Warn);
				break;
			case TweakState.Disabled:
				text = "OFF";
				textColor = Theme.Muted;
				break;
			default:
				text = "UNAVAILABLE  ·  " + status.Reason;
				textColor = Theme.Bad;
				break;
			}
			Color textColor2 = theme.Badge.normal.textColor;
			theme.Badge.normal.textColor = textColor;
			GUILayout.Label(text, theme.Badge, Array.Empty<GUILayoutOption>());
			theme.Badge.normal.textColor = textColor2;
		}

		private static string Format(float value, string format)
		{
			if (format == "0%")
			{
				return Mathf.RoundToInt(value * 100f) + "%";
			}
			if (format == "0.0s")
			{
				return value.ToString("0.0") + "s";
			}
			return value.ToString("0.00") + "x";
		}

		private static string Ago(long utcTicks)
		{
			double totalSeconds = (DateTime.UtcNow - new DateTime(utcTicks, DateTimeKind.Utc)).TotalSeconds;
			if (totalSeconds < 2.0)
			{
				return "just now";
			}
			if (totalSeconds < 60.0)
			{
				return (int)totalSeconds + "s ago";
			}
			if (totalSeconds < 3600.0)
			{
				return (int)(totalSeconds / 60.0) + "m ago";
			}
			return (int)(totalSeconds / 3600.0) + "h ago";
		}
	}
	public struct FloatRange
	{
		public float Min { get; }

		public float Max { get; }

		public string Format { get; }

		public FloatRange(float min, float max, string format)
		{
			Min = min;
			Max = max;
			Format = format;
		}
	}
}
namespace SilksongTweaks.Rules
{
	public static class DamageRules
	{
		public static int Scale(int incoming, float multiplier)
		{
			if (incoming <= 0)
			{
				return incoming;
			}
			if (multiplier <= 0f)
			{
				return 0;
			}
			int num = (int)Math.Round((double)incoming * (double)multiplier);
			if (num < 1)
			{
				num = 1;
			}
			return num;
		}
	}
	public static class HealthRules
	{
		public const int MinMasks = 1;

		public const int MaxMasks = 20;

		public static int Resolve(int vanillaMax, int configuredMasks, bool enabled)
		{
			if (!enabled)
			{
				return vanillaMax;
			}
			int num = Clamp(configuredMasks, 1, 20);
			if (vanillaMax <= num)
			{
				return num;
			}
			return vanillaMax;
		}

		private static int Clamp(int value, int min, int max)
		{
			if (value < min)
			{
				return min;
			}
			if (value > max)
			{
				return max;
			}
			return value;
		}
	}
	public static class CocoonRules
	{
		public static int Merge(int previousPool, int carriedThisDeath, float keepFraction)
		{
			if (previousPool < 0)
			{
				previousPool = 0;
			}
			if (carriedThisDeath < 0)
			{
				carriedThisDeath = 0;
			}
			if (keepFraction <= 0f)
			{
				return carriedThisDeath;
			}
			if (keepFraction > 1f)
			{
				keepFraction = 1f;
			}
			long num = (long)(int)((double)previousPool * (double)keepFraction) + (long)carriedThisDeath;
			if (num <= int.MaxValue)
			{
				return (int)num;
			}
			return int.MaxValue;
		}
	}
}
namespace SilksongTweaks.Modules
{
	public sealed class DamageTakenModule : ModuleBase
	{
		private const float MinMultiplier = 0f;

		private const float MaxMultiplier = 3f;

		private static DamageTakenModule _instance;

		private ConfigEntry<float> _multiplier;

		public override string Id => "DamageTaken";

		public override string DisplayName => "Damage taken";

		public override string Description => "Scale how much damage Hornet takes.";

		protected override void BindSettings(ConfigFile config)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			_multiplier = config.Bind<float>(Id, "Multiplier", 1f, new ConfigDescription("1 = vanilla. 0.5 = half damage. 0 = invulnerable. Above 1 makes the game harder.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			AddRow(new FloatRow("Multiplier", "1 = vanilla, 0 = invulnerable.", _multiplier, 0f, 3f, "0.00x"));
		}

		protected override TweakStatus Apply(Harmony harmony)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Expected O, but got Unknown
			_instance = this;
			string error;
			MethodBase methodBase = ModuleBase.Resolve(HookTargets.TakeDamage, out error);
			if (methodBase == null)
			{
				return TweakStatus.Unavailable(error);
			}
			harmony.Patch(methodBase, new HarmonyMethod(typeof(DamageTakenModule), "TakeDamagePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			return TweakStatus.Active;
		}

		private static void TakeDamagePrefix(ref int damageAmount)
		{
			DamageTakenModule instance = _instance;
			if (instance != null && instance.IsOn && damageAmount > 0)
			{
				int num = DamageRules.Scale(damageAmount, instance._multiplier.Value);
				if (num != damageAmount)
				{
					damageAmount = num;
					instance.MarkFired();
				}
			}
		}
	}
	public sealed class KeepRosariesModule : ModuleBase
	{
		private static KeepRosariesModule _instance;

		private ConfigEntry<float> _keepFraction;

		private int _poolBeforeDeath;

		private bool _armed;

		public override string Id => "KeepRosaries";

		public override string DisplayName => "Keep rosaries";

		public override string Description => "Dying twice no longer destroys your first cocoon.";

		protected override void BindSettings(ConfigFile config)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			_keepFraction = config.Bind<float>(Id, "KeepFraction", 1f, new ConfigDescription("Fraction of the previous cocoon carried into the new one. 1 = lose nothing, 0 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			AddRow(new FloatRow("Keep from old cocoon", "1 = lose nothing, 0 = vanilla behaviour.", _keepFraction, 0f, 1f, "0%"));
		}

		protected override TweakStatus Apply(Harmony harmony)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			_instance = this;
			string error;
			MethodBase methodBase = ModuleBase.Resolve(HookTargets.Die, out error);
			if (methodBase == null)
			{
				return TweakStatus.Unavailable(error);
			}
			string error2;
			MethodBase methodBase2 = ModuleBase.Resolve(HookTargets.HeroRespawned, out error2);
			if (methodBase2 == null)
			{
				return TweakStatus.Unavailable(error2);
			}
			harmony.Patch(methodBase, new HarmonyMethod(typeof(KeepRosariesModule), "DiePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch(methodBase2, (HarmonyMethod)null, new HarmonyMethod(typeof(KeepRosariesModule), "HeroRespawnedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			return TweakStatus.Active;
		}

		private static void DiePrefix()
		{
			KeepRosariesModule instance = _instance;
			if (instance != null && instance.IsOn)
			{
				PlayerData instance2 = PlayerData.instance;
				if (instance2 != null)
				{
					instance._poolBeforeDeath = instance2.HeroCorpseMoneyPool;
					instance._armed = true;
				}
			}
		}

		private static void HeroRespawnedPostfix()
		{
			KeepRosariesModule instance = _instance;
			if (instance == null || !instance.IsOn || !instance._armed)
			{
				return;
			}
			instance._armed = false;
			PlayerData instance2 = PlayerData.instance;
			if (instance2 != null)
			{
				int num = CocoonRules.Merge(instance._poolBeforeDeath, instance2.HeroCorpseMoneyPool, instance._keepFraction.Value);
				if (num != instance2.HeroCorpseMoneyPool)
				{
					Plugin.Log.LogInfo((object)($"[KeepRosaries] cocoon {instance2.HeroCorpseMoneyPool} -> {num} " + $"(rescued {num - instance2.HeroCorpseMoneyPool} from the previous cocoon)"));
					instance2.HeroCorpseMoneyPool = num;
					instance.MarkFired();
				}
			}
		}
	}
	public sealed class MaxHealthModule : ModuleBase
	{
		private const string MaxHealthKey = "maxHealth";

		private const string MaxHealthField = "maxHealth";

		private static MaxHealthModule _instance;

		private static int _rewritten;

		private ConfigEntry<int> _masks;

		public override string Id => "MaxHealth";

		public override string DisplayName => "Max masks";

		public override string Description => "Raise Hornet's maximum health.";

		protected override void BindSettings(ConfigFile config)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			_masks = config.Bind<int>(Id, "Masks", 8, new ConfigDescription("Maximum masks. Never lowers you below what you have earned in-game.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 20), Array.Empty<object>()));
			AddRow(new IntRow("Masks", "How many masks Hornet has at full health.", _masks, 1, 20));
			_masks.SettingChanged += delegate
			{
				RefreshMaskDisplay();
			};
			base.EnabledEntry.SettingChanged += delegate
			{
				RefreshMaskDisplay();
			};
		}

		private static void RefreshMaskDisplay()
		{
			try
			{
				HeroController instance = HeroController.instance;
				if (!((Object)(object)instance == (Object)null))
				{
					MethodInfo methodInfo = AccessTools.Method(typeof(HeroController), "MaxHealth", (Type[])null, (Type[])null);
					if (methodInfo == null)
					{
						Plugin.Log.LogWarning((object)"[MaxHealth] HeroController.MaxHealth() not found; HUD will refresh on its own next bench.");
					}
					else
					{
						methodInfo.Invoke(instance, null);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[MaxHealth] HUD refresh failed: " + ex.Message));
			}
		}

		protected override TweakStatus Apply(Harmony harmony)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			_instance = this;
			string error;
			MethodBase methodBase = ModuleBase.Resolve(HookTargets.CurrentMaxHealth, out error);
			if (methodBase == null)
			{
				return TweakStatus.Unavailable(error);
			}
			MethodBase methodBase2 = ModuleBase.Resolve(HookTargets.GetInt, out error);
			if (methodBase2 == null)
			{
				return TweakStatus.Unavailable(error);
			}
			harmony.Patch(methodBase, (HarmonyMethod)null, new HarmonyMethod(typeof(MaxHealthModule), "CurrentMaxHealthPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch(methodBase2, (HarmonyMethod)null, new HarmonyMethod(typeof(MaxHealthModule), "GetIntPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			TweakStatus tweakStatus = RestoreInvariantIn(harmony);
			if (tweakStatus != null)
			{
				return tweakStatus;
			}
			if (_rewritten == 0)
			{
				return TweakStatus.Unavailable("no maxHealth field reads found to rewrite — the game's health code changed");
			}
			base.Log.LogInfo((object)$"[MaxHealth] restored max-health invariant at {_rewritten} site(s)");
			return TweakStatus.Active;
		}

		private TweakStatus RestoreInvariantIn(Harmony harmony)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			HookTarget[] obj = new HookTarget[3]
			{
				HookTargets.PlayerDataAddHealth,
				HookTargets.PlayerDataTakeHealth,
				HookTargets.PlayerDataMaxHealth
			};
			HarmonyMethod val = new HarmonyMethod(typeof(MaxHealthModule), "MaxHealthTranspiler", (Type[])null);
			HookTarget[] array = obj;
			for (int i = 0; i < array.Length; i++)
			{
				string error;
				MethodBase methodBase = ModuleBase.Resolve(array[i], out error);
				if (methodBase == null)
				{
					return TweakStatus.Unavailable(error);
				}
				harmony.Patch(methodBase, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			return null;
		}

		private static IEnumerable<CodeInstruction> MaxHealthTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo getter = AccessTools.PropertyGetter(typeof(PlayerData), "CurrentMaxHealth");
			foreach (CodeInstruction instruction in instructions)
			{
				if (instruction.opcode == OpCodes.Ldfld && instruction.operand is FieldInfo { Name: "maxHealth" } fieldInfo && fieldInfo.DeclaringType == typeof(PlayerData))
				{
					instruction.opcode = OpCodes.Call;
					instruction.operand = getter;
					_rewritten++;
				}
				yield return instruction;
			}
		}

		private static void CurrentMaxHealthPostfix(ref int __result)
		{
			MaxHealthModule instance = _instance;
			if (instance != null && instance.IsOn)
			{
				int num = HealthRules.Resolve(__result, instance._masks.Value, enabled: true);
				if (num != __result)
				{
					__result = num;
					instance.MarkFired();
				}
			}
		}

		private static void GetIntPostfix(string intName, ref int __result)
		{
			MaxHealthModule instance = _instance;
			if (instance != null && instance.IsOn && !(intName != "maxHealth"))
			{
				__result = HealthRules.Resolve(__result, instance._masks.Value, enabled: true);
			}
		}
	}
	public abstract class ModuleBase : ITweakModule
	{
		private readonly List<ISettingRow> _rows = new List<ISettingRow>();

		private TweakStatus _hookStatus = TweakStatus.Unavailable("not applied yet");

		protected ConfigEntry<bool> EnabledEntry { get; private set; }

		protected ManualLogSource Log => Plugin.Log;

		public abstract string Id { get; }

		public abstract string DisplayName { get; }

		public abstract string Description { get; }

		public TweakStatus Status
		{
			get
			{
				if (_hookStatus.State == TweakState.Unavailable)
				{
					return _hookStatus;
				}
				if (EnabledEntry == null || EnabledEntry.Value)
				{
					return TweakStatus.Active;
				}
				return TweakStatus.Disabled;
			}
			protected set
			{
				_hookStatus = value;
			}
		}

		public long LastFiredUtcTicks { get; private set; }

		public IReadOnlyList<ISettingRow> Settings => _rows;

		public bool IsOn
		{
			get
			{
				if (Status.State != TweakState.Unavailable && EnabledEntry != null)
				{
					return EnabledEntry.Value;
				}
				return false;
			}
		}

		public void BindConfig(ConfigFile config)
		{
			EnabledEntry = config.Bind<bool>(Id, "Enabled", true, Description);
			AddRow(new BoolRow("Enabled", Description, EnabledEntry));
			BindSettings(config);
		}

		protected abstract void BindSettings(ConfigFile config);

		protected void AddRow(ISettingRow row)
		{
			_rows.Add(row);
		}

		public TweakStatus TryApply(Harmony harmony)
		{
			try
			{
				Status = Apply(harmony);
			}
			catch (Exception ex)
			{
				Status = TweakStatus.Unavailable(ex.Message);
				Log.LogError((object)$"[{Id}] failed to apply: {ex}");
			}
			return Status;
		}

		protected abstract TweakStatus Apply(Harmony harmony);

		public void MarkFired()
		{
			LastFiredUtcTicks = DateTime.UtcNow.Ticks;
		}

		protected static MethodBase Resolve(HookTarget target, out string error)
		{
			Type type = AccessTools.TypeByName(target.DeclaringType);
			if (type == null)
			{
				error = "type '" + target.DeclaringType + "' not found";
				return null;
			}
			MethodBase methodBase = ((target.Kind == HookKind.PropertyGetter) ? AccessTools.PropertyGetter(type, target.Member) : AccessTools.Method(type, target.Member, (Type[])null, (Type[])null));
			if (methodBase == null)
			{
				error = target.DeclaringType + "." + target.Member + " not found";
				return null;
			}
			error = null;
			return methodBase;
		}
	}
	public sealed class ReturnToDeathModule : ModuleBase
	{
		private const float MinCountdown = 0f;

		private const float MaxCountdown = 15f;

		private const float ArrivalTimeout = 15f;

		private const float SettleSeconds = 0.35f;

		private static ReturnToDeathModule _instance;

		private ConfigEntry<float> _countdown;

		private ConfigEntry<KeyCode> _cancelKey;

		private string _deathScene;

		private string _deathGate;

		private Vector3 _deathPosition;

		private bool _hasRecord;

		private bool _returning;

		public override string Id => "ReturnToDeath";

		public override string DisplayName => "Return to death spot";

		public override string Description => "Travel back to where you died after respawning.";

		public float CountdownRemaining { get; private set; }

		public KeyCode CancelKey
		{
			get
			{
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				if (_cancelKey == null)
				{
					return (KeyCode)110;
				}
				return _cancelKey.Value;
			}
		}

		protected override void BindSettings(ConfigFile config)
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			_countdown = config.Bind<float>(Id, "CountdownSeconds", 5f, new ConfigDescription("Seconds before the return happens, giving you time to cancel.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 15f), Array.Empty<object>()));
			_cancelKey = config.Bind<KeyCode>(Id, "CancelKey", (KeyCode)110, "Press during the countdown to stay at the bench.");
			AddRow(new FloatRow("Countdown", "Seconds before returning.", _countdown, 0f, 15f, "0.0s"));
			AddRow(new KeyRow("Cancel key", "Press during the countdown to stay put.", _cancelKey));
		}

		protected override TweakStatus Apply(Harmony harmony)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Expected O, but got Unknown
			_instance = this;
			string error;
			MethodBase methodBase = ModuleBase.Resolve(HookTargets.Die, out error);
			if (methodBase == null)
			{
				return TweakStatus.Unavailable(error);
			}
			MethodBase methodBase2 = ModuleBase.Resolve(HookTargets.HeroRespawned, out error);
			if (methodBase2 == null)
			{
				return TweakStatus.Unavailable(error);
			}
			if (ModuleBase.Resolve(HookTargets.BeginSceneTransition, out error) == null)
			{
				return TweakStatus.Unavailable(error);
			}
			harmony.Patch(methodBase, new HarmonyMethod(typeof(ReturnToDeathModule), "DiePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch(methodBase2, (HarmonyMethod)null, new HarmonyMethod(typeof(ReturnToDeathModule), "HeroRespawnedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			return TweakStatus.Active;
		}

		private static void DiePrefix()
		{
			//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_00a6: Unknown result type (might be due to invalid IL or missing references)
			ReturnToDeathModule instance = _instance;
			if (instance == null || !instance.IsOn)
			{
				return;
			}
			HeroController instance2 = HeroController.instance;
			GameManager instance3 = GameManager.instance;
			if (!((Object)(object)instance2 == (Object)null) && !((Object)(object)instance3 == (Object)null))
			{
				instance._deathScene = instance3.GetSceneNameString();
				instance._deathPosition = ((Component)instance2).transform.position;
				instance._deathGate = FindGateInCurrentScene();
				instance._hasRecord = !string.IsNullOrEmpty(instance._deathGate);
				if (!instance._hasRecord)
				{
					Plugin.Log.LogWarning((object)("[ReturnToDeath] no transition point found in '" + instance._deathScene + "'. Staying at the bench rather than risking a transition with no entry gate."));
				}
				else
				{
					Plugin.Log.LogInfo((object)($"[ReturnToDeath] recorded death in '{instance._deathScene}' at {instance._deathPosition} " + "via gate '" + instance._deathGate + "'"));
				}
			}
		}

		private static string FindGateInCurrentScene()
		{
			List<TransitionPoint> transitionPoints = TransitionPoint.TransitionPoints;
			if (transitionPoints == null || transitionPoints.Count == 0)
			{
				return null;
			}
			string text = null;
			foreach (TransitionPoint item in transitionPoints)
			{
				if (!((Object)(object)item == (Object)null) && !string.IsNullOrEmpty(((Object)item).name))
				{
					if (!item.isADoor)
					{
						return ((Object)item).name;
					}
					if (text == null)
					{
						text = ((Object)item).name;
					}
				}
			}
			return text;
		}

		private static void HeroRespawnedPostfix()
		{
			ReturnToDeathModule instance = _instance;
			if (instance != null && instance.IsOn && instance._hasRecord && !instance._returning)
			{
				Plugin.Instance.Run(instance.ReturnRoutine());
			}
		}

		private IEnumerator ReturnRoutine()
		{
			_returning = true;
			CountdownRemaining = _countdown.Value;
			while (CountdownRemaining > 0f)
			{
				if (Input.GetKeyDown(CancelKey))
				{
					Plugin.Log.LogInfo((object)"[ReturnToDeath] cancelled by player");
					Finish();
					yield break;
				}
				CountdownRemaining -= Time.unscaledDeltaTime;
				yield return null;
			}
			CountdownRemaining = 0f;
			yield return Travel();
			Finish();
		}

		private IEnumerator Travel()
		{
			GameManager gm = GameManager.instance;
			if ((Object)(object)gm == (Object)null || string.IsNullOrEmpty(_deathScene) || string.IsNullOrEmpty(_deathGate))
			{
				yield break;
			}
			yield return WaitUntilRespawnFinished();
			if (gm.GetSceneNameString() != _deathScene)
			{
				Plugin.Log.LogInfo((object)("[ReturnToDeath] entering '" + _deathScene + "' via gate '" + _deathGate + "'"));
				gm.BeginSceneTransition(new SceneLoadInfo
				{
					SceneName = _deathScene,
					EntryGateName = _deathGate
				});
				float waited = 0f;
				while (waited < 15f)
				{
					GameManager instance = GameManager.instance;
					if ((Object)(object)instance != (Object)null && instance.GetSceneNameString() == _deathScene && (Object)(object)HeroController.instance != (Object)null)
					{
						break;
					}
					waited += Time.unscaledDeltaTime;
					yield return null;
				}
				if (waited >= 15f)
				{
					Plugin.Log.LogWarning((object)"[ReturnToDeath] timed out waiting for the scene; leaving you where you are");
					yield break;
				}
				yield return (object)new WaitForSeconds(0.35f);
			}
			PlaceHeroAtDeathSpot();
			MarkFired();
		}

		private static IEnumerator WaitUntilRespawnFinished()
		{
			float waited = 0f;
			while (waited < 15f)
			{
				GameManager instance = GameManager.instance;
				HeroController instance2 = HeroController.instance;
				if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && !instance.RespawningHero)
				{
					break;
				}
				waited += Time.unscaledDeltaTime;
				yield return null;
			}
			yield return (object)new WaitForSeconds(0.35f);
		}

		private void PlaceHeroAtDeathSpot()
		{
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: 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_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			HeroController instance = HeroController.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			MethodInfo methodInfo = AccessTools.Method(typeof(HeroController), "TryFindGroundPoint", (Type[])null, (Type[])null);
			if (methodInfo != null)
			{
				object[] array = new object[3]
				{
					(object)default(Vector2),
					Vector2.op_Implicit(_deathPosition),
					true
				};
				object obj = methodInfo.Invoke(instance, array);
				bool flag = default(bool);
				int num;
				if (obj is bool)
				{
					flag = (bool)obj;
					num = 1;
				}
				else
				{
					num = 0;
				}
				if (((uint)num & (flag ? 1u : 0u)) != 0)
				{
					Vector2 val = (Vector2)array[0];
					((Component)instance).transform.position = new Vector3(val.x, val.y, ((Component)instance).transform.position.z);
					Plugin.Log.LogInfo((object)$"[ReturnToDeath] placed at ground point {val}");
					return;
				}
			}
			((Component)instance).transform.position = _deathPosition;
			Plugin.Log.LogInfo((object)$"[ReturnToDeath] placed at recorded position {_deathPosition}");
		}

		private void Finish()
		{
			CountdownRemaining = 0f;
			_returning = false;
			_hasRecord = false;
		}
	}
}