using System; using System.Collections; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; 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("JustRetryLimited")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("JustRetryLimited")] [assembly: AssemblyTitle("JustRetryLimited")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [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 JustRetryLimited { internal static class MyPluginInfo { public const string PLUGIN_GUID = "justretylimited.plugin"; public const string PLUGIN_NAME = "JustRetryLimited"; public const string PLUGIN_VERSION = "1.0.1"; } public enum HealMode { Fixed, Percentage, FullHeal } [BepInPlugin("justretylimited.plugin", "JustRetryLimited", "1.0.1")] public class Plugin : BaseUnityPlugin { internal static ManualLogSource Logger; private static ConfigEntry _isEnabled; private static ConfigEntry _unlimitedRetries; private static ConfigEntry _maxRetries; private static ConfigEntry _shouldResetLevel; private static ConfigEntry _shouldHealAfterFail; private static ConfigEntry _healMode; private static ConfigEntry _healthToHeal; private static ConfigEntry _healthPercentage; private static ConfigEntry _shouldResetUpgrades; public static bool IsEnabled => _isEnabled.Value; public static bool UnlimitedRetries => _unlimitedRetries.Value; public static int MaxRetries => _maxRetries.Value; public static bool ShouldResetLevel => _shouldResetLevel.Value; public static bool ShouldHealAfterFail => _shouldHealAfterFail.Value; public static HealMode CurrentHealMode => _healMode.Value; public static int HealthToHealValue => _healthToHeal.Value; public static int HealthPercentageValue => _healthPercentage.Value; public static bool ShouldResetUpgrades => _shouldResetUpgrades.Value; public static bool DidFailLevel { get; set; } public static int RetryCount { get; set; } internal static Plugin Instance { get; private set; } private void Awake() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Expected O, but got Unknown //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Expected O, but got Unknown Instance = this; Logger = ((BaseUnityPlugin)this).Logger; Logger.LogInfo((object)"Plugin justretylimited.plugin is loaded!"); Harmony val = new Harmony("justretylimited.plugin"); val.PatchAll(); _isEnabled = ((BaseUnityPlugin)this).Config.Bind("Settings", "IsEnabled", true, "Toggle the plugin on or off."); _unlimitedRetries = ((BaseUnityPlugin)this).Config.Bind("Settings", "UnlimitedRetries", false, "If true, retries are unlimited and MaxRetries is ignored."); _maxRetries = ((BaseUnityPlugin)this).Config.Bind("Settings", "MaxRetries", 3, new ConfigDescription("Maximum number of retries per level before the game ends normally.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 99), Array.Empty())); _shouldResetLevel = ((BaseUnityPlugin)this).Config.Bind("Settings", "ShouldResetLevel", false, "If true, restarts from level one when failing a level."); _shouldHealAfterFail = ((BaseUnityPlugin)this).Config.Bind("Settings", "ShouldHealAfterFail", true, "If true, all players are healed when retrying after a failed level."); _healMode = ((BaseUnityPlugin)this).Config.Bind("Settings", "HealMode", HealMode.FullHeal, "Fixed = heal up to a flat HP value. Percentage = heal up to a percentage of max HP. FullHeal = heal up to max HP (accounts for health upgrades)."); _healthToHeal = ((BaseUnityPlugin)this).Config.Bind("Settings", "HealthToHeal", 100, new ConfigDescription("Target HP to heal up to when retrying. Only used if HealMode is Fixed. If players already have more than this, they are not healed.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 500), Array.Empty())); _healthPercentage = ((BaseUnityPlugin)this).Config.Bind("Settings", "HealthPercentage", 100, new ConfigDescription("Percentage of max HP to heal up to when retrying. Only used if HealMode is Percentage. 100 = full heal.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); _shouldResetUpgrades = ((BaseUnityPlugin)this).Config.Bind("Settings", "ShouldResetUpgrades", false, "If true, resets all upgrades when failing a level (only if ShouldResetLevel is true)."); } public void StartHealCoroutine() { ((MonoBehaviour)this).StartCoroutine(HealPlayersDelayed()); } private IEnumerator HealPlayersDelayed() { if (!ShouldHealAfterFail || !DidFailLevel || !SemiFunc.IsMasterClientOrSingleplayer()) { yield break; } yield return (object)new WaitForSeconds(2f); for (float elapsed = 0f; elapsed <= 6f; elapsed += 1f) { foreach (PlayerAvatar player in SemiFunc.PlayerGetAll()) { PlayerHealth health = player?.playerHealth; if (!((Object)(object)health == (Object)null)) { Traverse healthField = Traverse.Create((object)health).Field("health"); Traverse maxHealthField = Traverse.Create((object)health).Field("maxHealth"); int currentHp = healthField.GetValue(); int maxHp = maxHealthField.GetValue(); int targetHp = CurrentHealMode switch { HealMode.FullHeal => maxHp, HealMode.Percentage => Mathf.RoundToInt((float)maxHp * ((float)HealthPercentageValue / 100f)), _ => Mathf.Min(HealthToHealValue, maxHp), }; int missing = targetHp - currentHp; if (missing > 0) { health.HealOther(missing, true); Logger.LogInfo((object)$"Healed {((Object)player).name} by {missing} to reach {targetHp}/{maxHp}"); } } } yield return (object)new WaitForSeconds(1f); } } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] public static class ChangeLevelPatch { private static void Prefix(RunManager __instance, ref bool _completedLevel, ref bool _levelFailed, ref ChangeLevelType _changeLevelType) { if (!Plugin.IsEnabled) { return; } if (!SemiFunc.RunIsLevel()) { Plugin.Logger.LogInfo((object)"Not an extraction level, skipping retry logic."); return; } Plugin.DidFailLevel = _levelFailed; Plugin.Logger.LogInfo((object)$"Level failed: {_levelFailed}"); if (!Plugin.DidFailLevel) { Plugin.RetryCount = 0; Plugin.Logger.LogInfo((object)"Level completed, retry counter reset."); return; } if (!Plugin.UnlimitedRetries && Plugin.RetryCount >= Plugin.MaxRetries) { Plugin.Logger.LogInfo((object)$"Retry limit of {Plugin.MaxRetries} reached, ending run."); Plugin.RetryCount = 0; return; } Plugin.RetryCount++; Plugin.Logger.LogInfo((object)($"Retry {Plugin.RetryCount}" + (Plugin.UnlimitedRetries ? " (unlimited)" : $" of {Plugin.MaxRetries}"))); if (Plugin.ShouldResetLevel) { Plugin.Logger.LogInfo((object)"Resetting to level one"); __instance.levelsCompleted = 0; Traverse.Create((object)__instance).Field("loadLevel").SetValue((object)0); __instance.SetRunLevel(); if (Plugin.ShouldResetUpgrades && (Object)(object)StatsManager.instance != (Object)null) { StatsManager.instance.ResetAllStats(); } StatsManager instance = StatsManager.instance; if (instance != null) { instance.StuffNeedingResetAtTheEndOfAScene(); } TutorialDirector.instance.TipCancel(); ItemManager.instance.FetchLocalPlayersInventory(); ItemManager.instance.powerCrystals.Clear(); SemiFunc.StatSyncAll(); SessionManager.instance.Reset(); } __instance.RestartScene(); Traverse.Create((object)RoundDirector.instance).Field("totalHaul").SetValue((object)0); Traverse.Create((object)RoundDirector.instance).Field("currentHaul").SetValue((object)0); Traverse.Create((object)RoundDirector.instance).Field("currentHaulMax").SetValue((object)0); Traverse.Create((object)RoundDirector.instance).Field("extractionPointSurplus").SetValue((object)0); Plugin.Logger.LogInfo((object)"Scene restarted, queuing heal..."); Plugin.Instance.StartHealCoroutine(); } } }