Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Extra Lives v3.0.0
ExtraLives.dll
Decompiled 20 hours agousing 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; } } }