Decompiled source of Joive BG NoDeathPenalty v1.0.1

BG-NoDeathPenalty-Joive.dll

Decompiled a month ago
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<bool> keepPlayerInventory;

		private static ConfigEntry<bool> keepStockpileSupplies;

		private static ConfigEntry<bool> keepLevelProgression;

		private static ConfigEntry<bool> retrySameDayOnLoss;

		private static ConfigEntry<bool> debugLogs;

		private static List<int> preRunPlayedHistory;

		private static List<int> 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<bool>("General", "KeepPlayerInventory", true, "Prevent player inventory from dropping/clearing after death or end-of-run respawn.");
			keepStockpileSupplies = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "KeepStockpileSupplies", true, "Prevent the home stockpile/resource box from being cleared on loss/reset.");
			keepLevelProgression = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "KeepLevelProgression", true, "Try to keep level progression/history when a loss resets the day.");
			retrySameDayOnLoss = ((BaseUnityPlugin)this).Config.Bind<bool>("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<bool>("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<int> CopyList(List<int> source)
		{
			if (source != null)
			{
				return new List<int>(source);
			}
			return null;
		}

		private static int Count(List<int> 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);
				}
			}
		}
	}
}