Decompiled source of SelectiveKeepOnDeath v1.0.0

SelectiveKeepOnDeath.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("SelectiveKeepOnDeath")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SelectiveKeepOnDeath")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("7edf0da5-ac68-4ab4-b6c1-73f2453e37d4")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace SelectiveKeepOnDeath;

public static class SkillDrainPatch
{
	[HarmonyPatch(typeof(Player), "Awake")]
	private static class PlayerAwakePatch
	{
		private static void Postfix(Player __instance)
		{
			if (vanillaCooldown < 0f)
			{
				vanillaCooldown = __instance.m_hardDeathCooldown;
				SelectiveKeepOnDeathPlugin.Log.LogInfo((object)$"Vanilla \"No skill drain\" duration: {vanillaCooldown} seconds.");
			}
			__instance.m_hardDeathCooldown = GetCooldown();
		}
	}

	[HarmonyPatch(typeof(Player), "HardDeath")]
	private static class HardDeathPatch
	{
		private static bool Prefix(ref bool __result)
		{
			if (SelectiveKeepOnDeathPlugin.NoSkillDrainEnabled.Value)
			{
				return true;
			}
			__result = true;
			return false;
		}
	}

	[HarmonyPatch(typeof(SEMan), "AddStatusEffect", new Type[]
	{
		typeof(int),
		typeof(bool),
		typeof(int),
		typeof(float),
		typeof(short)
	})]
	private static class SoftDeathIconPatch
	{
		private static void Postfix(SEMan __instance, int nameHash, StatusEffect __result)
		{
			if (!((Object)(object)__result == (Object)null) && nameHash == SEMan.s_statusEffectSoftDeath)
			{
				Character obj = StatusEffectOwner.Invoke(__instance);
				Player val = (Player)(object)((obj is Player) ? obj : null);
				if (!((Object)(object)val == (Object)null))
				{
					__result.m_ttl = val.m_hardDeathCooldown;
					StatusEffectTime.Invoke(__result) = TimeSinceDeath.Invoke(val);
				}
			}
		}
	}

	private static readonly FieldRef<Player, float> TimeSinceDeath = AccessTools.FieldRefAccess<Player, float>("m_timeSinceDeath");

	private static readonly FieldRef<StatusEffect, float> StatusEffectTime = AccessTools.FieldRefAccess<StatusEffect, float>("m_time");

	private static readonly FieldRef<SEMan, Character> StatusEffectOwner = AccessTools.FieldRefAccess<SEMan, Character>("m_character");

	private static float vanillaCooldown = -1f;

	public static void ApplyToLocalPlayer()
	{
		Player localPlayer = Player.m_localPlayer;
		if (!((Object)(object)localPlayer == (Object)null))
		{
			localPlayer.m_hardDeathCooldown = GetCooldown();
			if (IsProtectionOver(localPlayer))
			{
				((Character)localPlayer).GetSEMan().RemoveStatusEffect(SEMan.s_statusEffectSoftDeath, false);
			}
		}
	}

	private static float GetCooldown()
	{
		float value = SelectiveKeepOnDeathPlugin.NoSkillDrainDuration.Value;
		if (!(value >= 0f))
		{
			return vanillaCooldown;
		}
		return value;
	}

	private static bool IsProtectionOver(Player player)
	{
		if (SelectiveKeepOnDeathPlugin.NoSkillDrainEnabled.Value)
		{
			return TimeSinceDeath.Invoke(player) > player.m_hardDeathCooldown;
		}
		return true;
	}
}
[HarmonyPatch(typeof(Player), "CreateTombStone")]
public static class TombStonePatch
{
	public class KeptItem
	{
		public ItemData Item;

		public bool WasEquipped;
	}

	private static readonly FieldRef<Inventory, List<ItemData>> InventoryItems = AccessTools.FieldRefAccess<Inventory, List<ItemData>>("m_inventory");

	private static readonly MethodInfo InventoryChanged = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null);

	private static void Prefix(Player __instance, out List<KeptItem> __state)
	{
		__state = null;
		if (!SelectiveKeepOnDeathPlugin.Enabled.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
		{
			return;
		}
		List<ItemData> list = InventoryItems.Invoke(((Humanoid)__instance).GetInventory());
		__state = new List<KeptItem>();
		foreach (ItemData item in list)
		{
			if (SelectiveKeepOnDeathPlugin.ShouldKeep(item))
			{
				__state.Add(new KeptItem
				{
					Item = item,
					WasEquipped = item.m_equipped
				});
			}
		}
		foreach (KeptItem item2 in __state)
		{
			list.Remove(item2.Item);
		}
	}

	private static void Finalizer(Player __instance, List<KeptItem> __state)
	{
		if (__state == null || __state.Count == 0)
		{
			return;
		}
		Inventory inventory = ((Humanoid)__instance).GetInventory();
		List<ItemData> list = InventoryItems.Invoke(inventory);
		foreach (KeptItem item in __state)
		{
			list.Add(item.Item);
		}
		InventoryChanged.Invoke(inventory, new object[2] { false, false });
		foreach (KeptItem item2 in __state)
		{
			if (item2.WasEquipped && !((Humanoid)__instance).IsItemEquiped(item2.Item))
			{
				((Humanoid)__instance).EquipItem(item2.Item, false);
			}
		}
	}
}
[BepInPlugin("kafka.selectivekeepondeath", "SelectiveKeepOnDeath", "1.0.0")]
public class SelectiveKeepOnDeathPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "kafka.selectivekeepondeath";

	public const string PluginName = "SelectiveKeepOnDeath";

	public const string PluginVersion = "1.0.0";

	public static ConfigEntry<bool> Enabled;

	public static ConfigEntry<bool> KeepNonTeleportable;

	public static ConfigEntry<bool> KeepWeapons;

	public static ConfigEntry<bool> KeepArmor;

	public static ConfigEntry<bool> KeepUtilityAndTrinkets;

	public static ConfigEntry<bool> KeepTools;

	public static ConfigEntry<bool> KeepAmmo;

	public static ConfigEntry<bool> KeepConsumables;

	public static ConfigEntry<bool> KeepMaterials;

	public static ConfigEntry<bool> KeepTrophies;

	public static ConfigEntry<bool> KeepMisc;

	public static ConfigEntry<string> AlwaysKeepItems;

	public static ConfigEntry<string> AlwaysDropItems;

	public static ConfigEntry<bool> NoSkillDrainEnabled;

	public static ConfigEntry<float> NoSkillDrainDuration;

	public static ManualLogSource Log;

	private readonly Harmony harmony = new Harmony("kafka.selectivekeepondeath");

	private DateTime lastReloadTime;

	private const long ReloadDelayTicks = 10000000L;

	private void Awake()
	{
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", true, "Turn the mod on or off. When off, death works like vanilla.");
		KeepNonTeleportable = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Rules", "KeepNonTeleportable", false, "Keep items that can't go through portals (ores, metals, eggs, etc.). When false, those items always go to your tombstone (won't be kept on death), even if their category is kept.");
		KeepWeapons = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Categories", "KeepWeapons", true, "Weapons, bows, shields and torches. Includes axes and pickaxes, which the game treats as weapons.");
		KeepArmor = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Categories", "KeepArmor", true, "Helmets, chest, legs and capes.");
		KeepUtilityAndTrinkets = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Categories", "KeepUtilityAndTrinkets", true, "Belts and other utility items, and trinkets.");
		KeepTools = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Categories", "KeepTools", true, "Hammer, hoe, cultivator and other items the game classes as tools. Axes and pickaxes are not included; they follow KeepWeapons.");
		KeepAmmo = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Categories", "KeepAmmo", true, "Arrows, bolts and other ammo.");
		KeepConsumables = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Categories", "KeepConsumables", true, "Food and meads.");
		KeepMaterials = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Categories", "KeepMaterials", true, "Crafting materials (wood, stone, leather, etc.).");
		KeepTrophies = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Categories", "KeepTrophies", true, "Trophies.");
		KeepMisc = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Categories", "KeepMisc", true, "Fish and anything that doesn't fit another category.");
		AlwaysKeepItems = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Item Lists", "AlwaysKeepItems", "", "Comma-separated item prefab names that are always kept, overriding the rules and categories above (example: Copper,Tin). Does not override AlwaysDropItems.");
		AlwaysDropItems = ((BaseUnityPlugin)this).Config.Bind<string>("4 - Item Lists", "AlwaysDropItems", "", "Comma-separated item prefab names that always go to your tombstone, overriding everything else (example: Coins,SurtlingCore).");
		NoSkillDrainEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("5 - Skill Drain", "NoSkillDrainEnabled", true, "After a death, the game protects you from losing skills on your next death for a while (the \"No skill drain\" buff). Set to false to remove that protection, so every death drains skills.");
		NoSkillDrainDuration = ((BaseUnityPlugin)this).Config.Bind<float>("5 - Skill Drain", "NoSkillDrainDuration", -1f, "How long the \"No skill drain\" protection lasts, in seconds. -1 uses the game's own value (the vanilla duration is written to the BepInEx log when you load into a world).");
		Log = ((BaseUnityPlugin)this).Logger;
		NoSkillDrainEnabled.SettingChanged += delegate
		{
			SkillDrainPatch.ApplyToLocalPlayer();
		};
		NoSkillDrainDuration.SettingChanged += delegate
		{
			SkillDrainPatch.ApplyToLocalPlayer();
		};
		harmony.PatchAll(Assembly.GetExecutingAssembly());
		SetupConfigWatcher();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"SelectiveKeepOnDeath 1.0.0 loaded!");
	}

	private void SetupConfigWatcher()
	{
		FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Paths.ConfigPath, Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath));
		fileSystemWatcher.Changed += ReloadConfig;
		fileSystemWatcher.Created += ReloadConfig;
		fileSystemWatcher.Renamed += ReloadConfig;
		fileSystemWatcher.IncludeSubdirectories = true;
		fileSystemWatcher.SynchronizingObject = ThreadingHelper.SynchronizingObject;
		fileSystemWatcher.EnableRaisingEvents = true;
	}

	private void ReloadConfig(object sender, FileSystemEventArgs e)
	{
		DateTime now = DateTime.Now;
		if (File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath) && now.Ticks - lastReloadTime.Ticks >= 10000000)
		{
			try
			{
				((BaseUnityPlugin)this).Config.Reload();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Config reloaded.");
			}
			catch
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("There was an issue loading " + ((BaseUnityPlugin)this).Config.ConfigFilePath));
			}
			lastReloadTime = now;
		}
	}

	public static bool ShouldKeep(ItemData item)
	{
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		string item2 = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : "");
		if (ParseList(AlwaysDropItems.Value).Contains(item2))
		{
			return false;
		}
		if (ParseList(AlwaysKeepItems.Value).Contains(item2))
		{
			return true;
		}
		if (!item.m_shared.m_teleportable && !KeepNonTeleportable.Value)
		{
			return false;
		}
		return IsCategoryKept(item.m_shared.m_itemType);
	}

	private static bool IsCategoryKept(ItemType type)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Expected I4, but got Unknown
		switch (type - 1)
		{
		case 2:
		case 3:
		case 4:
		case 13:
		case 14:
		case 19:
		case 21:
			return KeepWeapons.Value;
		case 5:
		case 6:
		case 9:
		case 10:
		case 11:
		case 16:
			return KeepArmor.Value;
		case 17:
		case 23:
			return KeepUtilityAndTrinkets.Value;
		case 18:
			return KeepTools.Value;
		case 8:
		case 22:
			return KeepAmmo.Value;
		case 1:
			return KeepConsumables.Value;
		case 0:
			return KeepMaterials.Value;
		case 12:
			return KeepTrophies.Value;
		default:
			return KeepMisc.Value;
		}
	}

	private static HashSet<string> ParseList(string value)
	{
		return new HashSet<string>(from s in value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
			select s.Trim() into s
			where s.Length > 0
			select s, StringComparer.OrdinalIgnoreCase);
	}
}