using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Joive")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Keeps level retry progress and supplies after death/loss in Burglin Gnomes.")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1")] [assembly: AssemblyProduct("BG NoDeathPenalty")] [assembly: AssemblyTitle("BG-NoDeathPenalty-Joive")] [assembly: AssemblyVersion("1.0.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 BGNoDeathPenalty { [BepInPlugin("joive.bg.nodeathpenalty", "BG NoDeathPenalty", "1.0.1")] public sealed class NoDeathPenaltyPlugin : BaseUnityPlugin { private static NoDeathPenaltyPlugin Instance; private Harmony harmony; private static ConfigEntry keepPlayerInventory; private static ConfigEntry keepStockpileSupplies; private static ConfigEntry keepLevelProgression; private static ConfigEntry retrySameDayOnLoss; private static ConfigEntry debugLogs; private static List preRunPlayedHistory; private static List preRunShownHistory; private static int levelBeforeRun = -1; private static bool runStarted; private static bool runWon; private static bool resetGameInProgress; private static bool skipResetSaveOnce; private void Awake() { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown Instance = this; keepPlayerInventory = ((BaseUnityPlugin)this).Config.Bind("General", "KeepPlayerInventory", true, "Prevent player inventory from dropping/clearing after death or end-of-run respawn."); keepStockpileSupplies = ((BaseUnityPlugin)this).Config.Bind("General", "KeepStockpileSupplies", true, "Prevent the home stockpile/resource box from being cleared on loss/reset."); keepLevelProgression = ((BaseUnityPlugin)this).Config.Bind("General", "KeepLevelProgression", true, "Try to keep level progression/history when a loss resets the day."); retrySameDayOnLoss = ((BaseUnityPlugin)this).Config.Bind("General", "RetrySameDayOnLoss", true, "After an unsuccessful run, restore the pre-run level selection so the same day can be replayed."); debugLogs = ((BaseUnityPlugin)this).Config.Bind("General", "DebugLogs", true, "Write detailed patch activity to BepInEx LogOutput.log."); harmony = new Harmony("joive.bg.nodeathpenalty"); PatchSafe(typeof(InventoryBase), "DropInventoryContents", typeof(NoDeathPenaltyPlugin), "InventoryDropPrefix"); PatchSafe(typeof(InventoryBase), "ClearInventory", typeof(NoDeathPenaltyPlugin), "InventoryClearPrefix"); PatchSafe(typeof(LevelProgressionHandler), "OnConfirmLevel", typeof(NoDeathPenaltyPlugin), "OnConfirmLevelPrefix"); PatchSafe(typeof(GameProgressionManager), "OnGameStarted", typeof(NoDeathPenaltyPlugin), "OnGameStartedPrefix"); PatchSafe(typeof(GameProgressionManager), "OnGameWon", typeof(NoDeathPenaltyPlugin), "OnGameWonPostfix", postfix: true); PatchSafe(typeof(GameProgressionManager), "OnGameEnded", typeof(NoDeathPenaltyPlugin), "OnGameEndedPostfix", postfix: true); PatchSafe(typeof(GameProgressionManager), "ResetGame", typeof(NoDeathPenaltyPlugin), "ResetGamePrefix"); PatchSafe(typeof(GameProgressionManager), "ResetGame", typeof(NoDeathPenaltyPlugin), "ResetGamePostfix", postfix: true); PatchSafe(typeof(GameProgressionManager), "SaveGame", typeof(NoDeathPenaltyPlugin), "SaveGamePrefix"); Log("BG NoDeathPenalty 1.0.1 loaded. Home reset is no longer blocked; this prevents missing-house softlocks."); } private void OnDestroy() { Harmony obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } private void PatchSafe(Type targetType, string targetMethod, Type patchType, string patchMethod, bool postfix = false) { //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(targetType, targetMethod, (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(patchType, patchMethod, (Type[])null, (Type[])null); if (methodInfo == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Missing target method: " + targetType.Name + "." + targetMethod)); return; } if (methodInfo2 == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Missing patch method: " + patchType.Name + "." + patchMethod)); return; } if (postfix) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Log("Patched " + targetType.Name + "." + targetMethod + " -> " + patchMethod); } catch (Exception arg) { ((BaseUnityPlugin)this).Logger.LogWarning((object)$"Failed to patch {targetType.Name}.{targetMethod}: {arg}"); } } public static bool InventoryDropPrefix(InventoryBase __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (keepPlayerInventory.Value && IsPlayerInventory(__instance)) { Log("Blocked player inventory drop."); return false; } return true; } public static bool InventoryClearPrefix(InventoryBase __instance) { if ((Object)(object)__instance == (Object)null) { return true; } if (keepPlayerInventory.Value && IsPlayerInventory(__instance)) { Log("Blocked player inventory clear."); return false; } if (keepStockpileSupplies.Value && IsStockpileInventory(__instance)) { Log("Blocked stockpile/resource box clear."); return false; } return true; } public static void OnConfirmLevelPrefix(LevelProgressionHandler __instance) { CaptureLevelState(__instance, "level confirmed"); } public static void OnGameStartedPrefix(GameProgressionManager __instance) { if (!((Object)(object)__instance == (Object)null)) { levelBeforeRun = __instance.CurrentLevel; runStarted = true; runWon = false; CaptureLevelState(__instance.LevelProgressionHandler, "game started"); Log($"Game started. Level before run={levelBeforeRun}"); } } public static void OnGameWonPostfix(GameProgressionManager __instance) { runWon = true; preRunPlayedHistory = null; preRunShownHistory = null; levelBeforeRun = -1; runStarted = false; resetGameInProgress = false; skipResetSaveOnce = false; Log("Game won. Progression snapshot cleared."); } public unsafe static void OnGameEndedPostfix(GameProgressionManager __instance, GameEndedReason reason) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 if (!((Object)(object)__instance == (Object)null) && keepLevelProgression.Value && !runWon && ((int)reason == 1 || (int)reason == 2)) { Log("Loss/end detected: " + ((object)(*(GameEndedReason*)(&reason))/*cast due to .constrained prefix*/).ToString() + ". Waiting for normal reset, then restoring level state."); } } public static bool ResetGamePrefix(GameProgressionManager __instance) { if ((Object)(object)__instance == (Object)null || !keepLevelProgression.Value || !runStarted || runWon) { return true; } resetGameInProgress = true; skipResetSaveOnce = true; if (levelBeforeRun < 0) { levelBeforeRun = Math.Max(0, __instance.CurrentLevel - 1); } CaptureLevelState(__instance.LevelProgressionHandler, "before ResetGame"); Log("ResetGame will run normally so the house/UI respawn. SaveGame inside ResetGame will be skipped once, then level state restored."); return true; } public static void ResetGamePostfix(GameProgressionManager __instance) { if ((Object)(object)__instance == (Object)null || !resetGameInProgress) { return; } try { RestoreRetryState(__instance, "ResetGame postfix"); } finally { resetGameInProgress = false; skipResetSaveOnce = false; } } public static bool SaveGamePrefix(GameProgressionManager __instance) { if (skipResetSaveOnce && keepLevelProgression.Value) { skipResetSaveOnce = false; Log("Skipped SaveGame inside ResetGame to avoid deleting/reverting progression while retrying the same day."); return false; } return true; } private static void CaptureLevelState(LevelProgressionHandler handler, string reason) { if (keepLevelProgression.Value && retrySameDayOnLoss.Value && handler != null) { preRunPlayedHistory = CopyList(handler.PlayedLevelHistory); preRunShownHistory = CopyList(handler.ShowedLevelHistory); Log($"Captured level history ({reason}): played={Count(preRunPlayedHistory)}, shown={Count(preRunShownHistory)}"); } } private static void RestoreRetryState(GameProgressionManager progression, string reason) { try { if (!((Object)(object)progression == (Object)null)) { int num = ((levelBeforeRun >= 0) ? levelBeforeRun : Math.Max(0, progression.CurrentLevel)); SetNetworkVariableValue(progression, "n_level", num); SetNetworkVariableValue(progression, "n_day", 1); if (retrySameDayOnLoss.Value && preRunPlayedHistory != null && preRunShownHistory != null) { progression.LevelProgressionHandler.SetFromSavedState(CopyList(preRunPlayedHistory), CopyList(preRunShownHistory)); Log($"Restored pre-run level options/history for retry. played={Count(preRunPlayedHistory)}, shown={Count(preRunShownHistory)}"); } progression.SelectedLevelIndex = 0; runStarted = false; runWon = false; Log($"Restored retry state after {reason}. CurrentLevel set to {num}."); } } catch (Exception ex) { NoDeathPenaltyPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogWarning((object)("Failed to restore retry state: " + ex)); } } } private static void SetNetworkVariableValue(object owner, string fieldName, int value) { object obj = AccessTools.Field(owner.GetType(), fieldName)?.GetValue(owner); (obj?.GetType().GetProperty("Value"))?.SetValue(obj, value, null); } private static bool IsPlayerInventory(InventoryBase inventory) { return inventory is PlayerInventory; } private static bool IsStockpileInventory(InventoryBase inventory) { try { GameProgressionManager instance = GameProgressionManager.Instance; return (Object)(object)instance != (Object)null && (Object)(object)instance.Deposit != (Object)null && instance.Deposit.ResourceInventory == inventory; } catch { return false; } } private static List CopyList(List source) { if (source != null) { return new List(source); } return null; } private static int Count(List list) { return list?.Count ?? 0; } private static void Log(string message) { if (debugLogs != null && debugLogs.Value) { NoDeathPenaltyPlugin instance = Instance; if (instance != null) { ((BaseUnityPlugin)instance).Logger.LogInfo((object)message); } } } } }