Decompiled source of Extra Lives v3.0.0

ExtraLives.dll

Decompiled 20 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExtraLives.Patches;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("MiMoCode")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("ExtraLives")]
[assembly: AssemblyTitle("ExtraLives")]
[assembly: AssemblyVersion("1.0.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 ExtraLives
{
	internal static class Configuration
	{
		internal static ConfigEntry<int> extraLives;

		internal static ConfigEntry<bool> showUI;

		internal static ConfigEntry<bool> resetOnNewRun;

		internal static ConfigEntry<bool> debugMode;

		internal static void Init(ConfigFile config)
		{
			extraLives = config.Bind<int>("General", "ExtraLives", 3, "Number of retry attempts before normal progression resumes. Set to 0 to disable.");
			showUI = config.Bind<bool>("General", "ShowUI", true, "Show remaining retries counter on screen.");
			resetOnNewRun = config.Bind<bool>("General", "ResetOnNewRun", true, "Reset retries at the start of each new run.");
			debugMode = config.Bind<bool>("General", "DebugMode", false, "Enable debug logging for troubleshooting.");
		}
	}
	[BepInPlugin("com.mimocode.extralives", "Extra Lives", "3.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		private static FieldInfo _healthField;

		private static FieldInfo _maxHealthField;

		private static FieldInfo _spectatingField;

		internal static Plugin Instance { get; private set; } = null;

		internal static ManualLogSource Logger => Instance._logger;

		internal Harmony? Harmony { get; set; }

		private ManualLogSource _logger => ((BaseUnityPlugin)this).Logger;

		internal static int MaxRetries { get; set; } = 3;

		internal static int RetriesRemaining { get; set; } = 0;

		internal static int SavedLevelIndex { get; set; } = -1;

		internal static bool HasStoredLevel { get; set; } = false;

		internal static bool RedirectActive { get; set; } = false;

		internal static bool IsRetrying { get; set; } = false;

		internal static string CurrentSaveName { get; set; } = "";

		internal static bool ShowEpicScreen { get; set; } = false;

		internal static float EpicScreenTimer { get; set; } = 0f;

		private void Awake()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			//IL_007a: Expected O, but got Unknown
			Instance = this;
			((Component)this).gameObject.transform.parent = null;
			((Object)((Component)this).gameObject).hideFlags = (HideFlags)61;
			ConfigFile config = new ConfigFile(Path.Combine(Paths.ConfigPath, "ExtraLives.cfg"), true);
			Configuration.Init(config);
			MaxRetries = Configuration.extraLives.Value;
			if (Harmony == null)
			{
				Harmony val = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
				Harmony val2 = val;
				Harmony = val;
			}
			GameDirectorPatch.Patch(Harmony);
			RunManagerPatch.Patch(Harmony);
			ChatPatch.Patch(Harmony);
			Logger.LogInfo((object)$"{((BaseUnityPlugin)this).Info.Metadata.GUID} v{((BaseUnityPlugin)this).Info.Metadata.Version} loaded!");
		}

		private void Update()
		{
			if (ShowEpicScreen)
			{
				EpicScreenTimer += Time.deltaTime;
				if (EpicScreenTimer > 4f)
				{
					ShowEpicScreen = false;
					EpicScreenTimer = 0f;
				}
			}
			ChatPatch.TryLateRegister();
		}

		internal static int GetCurrentLevel()
		{
			try
			{
				if ((Object)(object)StatsManager.instance != (Object)null)
				{
					return StatsManager.instance.GetRunStatLevel();
				}
			}
			catch
			{
			}
			return 0;
		}

		internal static bool IsModActive()
		{
			return true;
		}

		internal static void UpdateSaveName()
		{
			try
			{
				if ((Object)(object)StatsManager.instance == (Object)null)
				{
					return;
				}
				FieldInfo field = typeof(StatsManager).GetField("saveFileCurrent", BindingFlags.Instance | BindingFlags.NonPublic);
				if (field != null)
				{
					string text = (field.GetValue(StatsManager.instance) as string) ?? "";
					if (!string.IsNullOrEmpty(text) && text != CurrentSaveName)
					{
						CurrentSaveName = text;
						LoadRetriesFromSave();
					}
				}
			}
			catch
			{
			}
		}

		internal static int GetPlayerHealth()
		{
			try
			{
				PlayerAvatar val = SemiFunc.PlayerAvatarLocal();
				if ((Object)(object)val?.playerHealth == (Object)null)
				{
					return -1;
				}
				if (_healthField == null)
				{
					_healthField = typeof(PlayerHealth).GetField("health", BindingFlags.Instance | BindingFlags.NonPublic);
				}
				if (_healthField != null)
				{
					return (int)_healthField.GetValue(val.playerHealth);
				}
			}
			catch
			{
			}
			return -1;
		}

		internal static int GetMaxHealth()
		{
			try
			{
				PlayerAvatar val = SemiFunc.PlayerAvatarLocal();
				if ((Object)(object)val?.playerHealth == (Object)null)
				{
					return 100;
				}
				if (_maxHealthField == null)
				{
					_maxHealthField = typeof(PlayerHealth).GetField("maxHealth", BindingFlags.Instance | BindingFlags.NonPublic);
				}
				if (_maxHealthField != null)
				{
					return (int)_maxHealthField.GetValue(val.playerHealth);
				}
			}
			catch
			{
			}
			return 100;
		}

		internal static void RestorePlayerHealth()
		{
			try
			{
				PlayerAvatar val = SemiFunc.PlayerAvatarLocal();
				if ((Object)(object)val?.playerHealth == (Object)null)
				{
					return;
				}
				int maxHealth = GetMaxHealth();
				int playerHealth = GetPlayerHealth();
				if (playerHealth >= 0 && maxHealth > 0)
				{
					int num = maxHealth - playerHealth;
					if (num > 0)
					{
						val.playerHealth.Heal(num, false);
						Logger.LogInfo((object)$"Health restored: {playerHealth} → {maxHealth}");
					}
				}
			}
			catch
			{
			}
		}

		internal static bool IsPlayerSpectating()
		{
			try
			{
				PlayerAvatar val = SemiFunc.PlayerAvatarLocal();
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				if (_spectatingField == null)
				{
					_spectatingField = typeof(PlayerAvatar).GetField("spectating", BindingFlags.Instance | BindingFlags.NonPublic);
				}
				if (_spectatingField != null)
				{
					return (bool)_spectatingField.GetValue(val);
				}
			}
			catch
			{
			}
			return false;
		}

		internal static void OnDeathSave()
		{
			try
			{
				int currentLevel = GetCurrentLevel();
				Logger.LogInfo((object)$"OnDeathSave: level={currentLevel}");
				if (currentLevel < 0)
				{
					Logger.LogInfo((object)"Level < 0 — normal death.");
					return;
				}
				if (!HasStoredLevel)
				{
					RetriesRemaining = MaxRetries;
					Logger.LogInfo((object)$"First death. Retries: {RetriesRemaining}/{MaxRetries}.");
				}
				else
				{
					Logger.LogInfo((object)$"Retry death. Retries: {RetriesRemaining}/{MaxRetries}.");
					if (RetriesRemaining <= 0)
					{
						Logger.LogInfo((object)"No retries left.");
						return;
					}
				}
				RetriesRemaining--;
				Logger.LogInfo((object)$"Life consumed on death. Retries: {RetriesRemaining}/{MaxRetries}");
				SavedLevelIndex = currentLevel;
				HasStoredLevel = true;
				RedirectActive = RetriesRemaining > 0;
				SaveRetriesToSave();
				Logger.LogInfo((object)$"Saved level {SavedLevelIndex}. Saving...");
				SemiFunc.SaveFileSave();
			}
			catch (Exception ex)
			{
				Logger.LogWarning((object)("OnDeathSave: " + ex.Message));
			}
		}

		internal static bool ConsumeRetry()
		{
			if (!HasStoredLevel || !RedirectActive || RetriesRemaining <= 0)
			{
				return false;
			}
			RetriesRemaining--;
			IsRetrying = true;
			Logger.LogInfo((object)$"Retry consumed: {SavedLevelIndex}, left={RetriesRemaining}");
			if (RetriesRemaining <= 0)
			{
				RedirectActive = false;
			}
			SaveRetriesToSave();
			return true;
		}

		internal static void AddLife()
		{
			if (RetriesRemaining >= MaxRetries)
			{
				Logger.LogInfo((object)$"Max lives ({MaxRetries}).");
				return;
			}
			RetriesRemaining++;
			SaveRetriesToSave();
			Logger.LogInfo((object)$"+1 life! {RetriesRemaining}/{MaxRetries}");
		}

		internal static void ResetRetries()
		{
			RetriesRemaining = MaxRetries;
			HasStoredLevel = false;
			RedirectActive = false;
			SavedLevelIndex = -1;
			IsRetrying = false;
			ShowEpicScreen = false;
			EpicScreenTimer = 0f;
		}

		internal static void SaveRetriesToSave()
		{
			try
			{
				PlayerPrefs.SetInt("EL_" + CurrentSaveName + "_R", RetriesRemaining);
				PlayerPrefs.SetInt("EL_" + CurrentSaveName + "_L", SavedLevelIndex);
				PlayerPrefs.SetInt("EL_" + CurrentSaveName + "_H", HasStoredLevel ? 1 : 0);
				PlayerPrefs.SetInt("EL_" + CurrentSaveName + "_A", RedirectActive ? 1 : 0);
				PlayerPrefs.Save();
			}
			catch
			{
			}
		}

		private static void LoadRetriesFromSave()
		{
			try
			{
				if (!string.IsNullOrEmpty(CurrentSaveName))
				{
					RetriesRemaining = PlayerPrefs.GetInt("EL_" + CurrentSaveName + "_R", MaxRetries);
					SavedLevelIndex = PlayerPrefs.GetInt("EL_" + CurrentSaveName + "_L", -1);
					HasStoredLevel = PlayerPrefs.GetInt("EL_" + CurrentSaveName + "_H", 0) == 1;
					RedirectActive = PlayerPrefs.GetInt("EL_" + CurrentSaveName + "_A", 0) == 1;
					Logger.LogInfo((object)$"Loaded: save={CurrentSaveName}, retries={RetriesRemaining}, level={SavedLevelIndex}, stored={HasStoredLevel}");
				}
			}
			catch
			{
			}
		}

		private void OnGUI()
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_015e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: 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_0181: Expected O, but got Unknown
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			if (ShowEpicScreen)
			{
				DrawEpicScreen();
			}
			else if (Configuration.showUI.Value && (SemiFunc.RunIsLevel() || SemiFunc.RunIsShop()) && IsModActive() && (RetriesRemaining < MaxRetries || HasStoredLevel) && (IsPlayerSpectating() || SemiFunc.RunIsShop()))
			{
				float num = (float)Screen.height / 1080f;
				GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(num, num, 1f));
				float num2 = 350f;
				float num3 = 70f;
				float num4 = ((float)Screen.width / num - num2) / 2f;
				float num5 = 20f;
				GUI.Box(new Rect(num4, num5, num2, num3), "");
				GUIStyle val = new GUIStyle(GUI.skin.label)
				{
					fontSize = 14,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)1
				};
				val.normal.textColor = Color.cyan;
				GUIStyle val2 = val;
				GUIStyle val3 = new GUIStyle(GUI.skin.label)
				{
					fontSize = 20,
					fontStyle = (FontStyle)1,
					alignment = (TextAnchor)4
				};
				val3.normal.textColor = Color.yellow;
				GUIStyle val4 = val3;
				GUI.Label(new Rect(num4, num5 + 5f, num2, 20f), "EXTRA LIVES", val2);
				string text = (HasStoredLevel ? $"Retries Left: {RetriesRemaining}/{MaxRetries}" : $"Retries: {RetriesRemaining}/{MaxRetries}");
				GUI.Label(new Rect(num4, num5 + 28f, num2, 35f), text, val4);
			}
		}

		private void DrawEpicScreen()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: 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_0055: 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_007c: 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_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Expected O, but got Unknown
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Expected O, but got Unknown
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a0: Unknown result type (might be due to invalid IL or missing references)
			float num = (float)Screen.height / 1080f;
			GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(num, num, 1f));
			float num2 = (float)Screen.width / num;
			float num3 = (float)Screen.height / num;
			GUI.color = new Color(0f, 0f, 0f, 0.85f);
			GUI.DrawTexture(new Rect(0f, 0f, num2, num3), (Texture)(object)Texture2D.whiteTexture);
			GUI.color = Color.white;
			float num4 = Mathf.Clamp01(EpicScreenTimer / 0.5f);
			float num5 = ((EpicScreenTimer > 3f) ? Mathf.Clamp01(4f - EpicScreenTimer) : 1f);
			float num6 = num4 * num5;
			GUIStyle val = new GUIStyle(GUI.skin.label)
			{
				fontSize = 60,
				fontStyle = (FontStyle)1,
				alignment = (TextAnchor)4
			};
			val.normal.textColor = new Color(1f, 0.2f, 0.2f, num6);
			GUIStyle val2 = val;
			GUIStyle val3 = new GUIStyle(GUI.skin.label)
			{
				fontSize = 30,
				fontStyle = (FontStyle)1,
				alignment = (TextAnchor)4
			};
			val3.normal.textColor = new Color(1f, 1f, 0.3f, num6);
			GUIStyle val4 = val3;
			if (RetriesRemaining > 0)
			{
				GUI.Label(new Rect(0f, num3 * 0.35f, num2, 80f), "LIFE LOST!", val2);
				GUI.Label(new Rect(0f, num3 * 0.5f, num2, 50f), $"Remaining: {RetriesRemaining} / {MaxRetries}", val4);
			}
			else
			{
				GUI.Label(new Rect(0f, num3 * 0.35f, num2, 80f), "GAME OVER", val2);
				GUI.Label(new Rect(0f, num3 * 0.5f, num2, 50f), "No retries left", val4);
			}
		}
	}
}
namespace ExtraLives.Patches
{
	internal static class ChatPatch
	{
		internal static bool _commandRegistered;

		private static float _retryTimer;

		internal static void Patch(Harmony harmony)
		{
		}

		internal static void TryLateRegister()
		{
			if (_commandRegistered)
			{
				return;
			}
			_retryTimer += Time.deltaTime;
			if (_retryTimer < 2f)
			{
				return;
			}
			_retryTimer = 0f;
			try
			{
				Type type = null;
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly in assemblies)
				{
					type = assembly.GetType("DebugCommandHandler");
					if (type != null)
					{
						break;
					}
				}
				if (!(type == null))
				{
					Object val = Object.FindObjectOfType(type);
					if (!(val == (Object)null))
					{
						RegisterCommand(val, type);
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Command register attempt: " + ex.Message));
			}
		}

		private static void RegisterCommand(object handler, Type handlerType)
		{
			if (_commandRegistered)
			{
				return;
			}
			try
			{
				Type type = null;
				Type[] nestedTypes = handlerType.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
				foreach (Type type2 in nestedTypes)
				{
					if (type2.Name == "ChatCommand")
					{
						type = type2;
						break;
					}
				}
				if (type == null)
				{
					return;
				}
				ConstructorInfo constructorInfo = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public)[0];
				Action<bool, string[]> action = delegate
				{
					Plugin.Logger.LogInfo((object)"Command: getlivegameofrepo");
					if (SemiFunc.IsMasterClient() || !GameManager.Multiplayer())
					{
						Plugin.AddLife();
						ShowNotification();
					}
				};
				Func<bool, string, string[], List<string>> func = (bool a, string b, string[] c) => new List<string>();
				Func<bool> func2 = () => true;
				object obj = constructorInfo.Invoke(new object[6] { "getlivegameofrepo", "Add +1 extra life (host only, max 3)", action, func, func2, false });
				MethodInfo method = handlerType.GetMethod("Register", BindingFlags.Instance | BindingFlags.Public);
				if (method != null)
				{
					method.Invoke(handler, new object[1] { obj });
					_commandRegistered = true;
					Plugin.Logger.LogInfo((object)"Chat command registered: getlivegameofrepo");
				}
			}
			catch (Exception ex)
			{
				Plugin.Logger.LogWarning((object)("Register command failed: " + ex.Message));
			}
		}

		private static void ShowNotification()
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				SemiFunc.UIBigMessage($"+1 LIFE! ({Plugin.RetriesRemaining}/{Plugin.MaxRetries})", "", 50f, Color.green, Color.white);
			}
			catch
			{
			}
		}
	}
	internal static class GameDirectorPatch
	{
		internal static void Patch(Harmony harmony)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			Type typeFromHandle = typeof(GameDirector);
			if (!(typeFromHandle == null))
			{
				MethodInfo method = typeFromHandle.GetMethod("gameStateStart", BindingFlags.Instance | BindingFlags.NonPublic);
				if (method != null)
				{
					harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(GameDirectorPatch).GetMethod("StartPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Plugin.Logger.LogInfo((object)"Patched: GameDirector.gameStateStart");
				}
			}
		}

		private static void StartPrefix()
		{
			Plugin.UpdateSaveName();
			if (Plugin.IsRetrying)
			{
				Plugin.Logger.LogInfo((object)"gameStateStart: retrying → restore health + epic screen");
				Plugin.RestorePlayerHealth();
				Plugin.IsRetrying = false;
				Plugin.ShowEpicScreen = true;
				Plugin.EpicScreenTimer = 0f;
			}
		}
	}
	internal static class RunManagerPatch
	{
		internal static void Patch(Harmony harmony)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			Type typeFromHandle = typeof(RunManager);
			if (!(typeFromHandle == null))
			{
				MethodInfo method = typeFromHandle.GetMethod("ChangeLevel", BindingFlags.Instance | BindingFlags.Public);
				if (method != null)
				{
					harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(RunManagerPatch).GetMethod("ChangeLevelPrefix", BindingFlags.Static | BindingFlags.NonPublic)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					Plugin.Logger.LogInfo((object)"Patched: RunManager.ChangeLevel");
				}
			}
		}

		private static bool ChangeLevelPrefix(ref bool _completedLevel, ref bool _levelFailed, ref ChangeLevelType _changeLevelType)
		{
			Plugin.UpdateSaveName();
			if (_levelFailed && !Plugin.IsRetrying)
			{
				int currentLevel = Plugin.GetCurrentLevel();
				bool flag = Plugin.IsModActive();
				Plugin.Logger.LogInfo((object)$"ChangeLevel: levelFailed, level={currentLevel}, active={flag}, retries={Plugin.RetriesRemaining}");
				if (flag && (!Plugin.HasStoredLevel || Plugin.RetriesRemaining > 0) && (SemiFunc.IsMasterClient() || !GameManager.Multiplayer()))
				{
					Plugin.Logger.LogInfo((object)"→ Save & redirect to Shop");
					Plugin.OnDeathSave();
					_changeLevelType = (ChangeLevelType)5;
					_levelFailed = false;
					_completedLevel = false;
					return true;
				}
				if (flag)
				{
					Plugin.Logger.LogInfo((object)"→ No retries. Game over.");
				}
				return true;
			}
			if (!_levelFailed && Plugin.RedirectActive && Plugin.HasStoredLevel && Plugin.RetriesRemaining > 0)
			{
				Plugin.Logger.LogInfo((object)"ChangeLevel: shop departure with retries → retry same level");
				Plugin.IsRetrying = true;
				Plugin.RedirectActive = false;
				Plugin.SaveRetriesToSave();
				_completedLevel = false;
				_levelFailed = false;
				return true;
			}
			if (Plugin.IsRetrying)
			{
				Plugin.Logger.LogInfo((object)$"ChangeLevel: retrying. completed={_completedLevel}");
				_completedLevel = false;
				_levelFailed = false;
			}
			return true;
		}
	}
}