Decompiled source of DeathTweaks v1.0.2

plugins/DeathTweaks/DeathTweaks.dll

Decompiled 3 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using DeathTweaks.Compat;
using DeathTweaks.Rules;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("assembly_valheim")]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("Egor Fadeev")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Configurable death behaviour for Valheim: keep, drop or destroy items, keep food, tune skill loss, choose the respawn point.")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2+037c5bdb127169e3d74dcf10c2a7860b71e21468")]
[assembly: AssemblyProduct("Death Tweaks")]
[assembly: AssemblyTitle("DeathTweaks")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[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 DeathTweaks
{
	internal static class DeathContext
	{
		public static bool InOnDeath { get; private set; }

		public static void Enter()
		{
			InOnDeath = true;
		}

		public static void Exit()
		{
			InOnDeath = false;
		}
	}
	internal static class DeathInventory
	{
		private sealed class HeldItem
		{
			public ItemData Item { get; }

			public bool WasEquipped { get; }

			public HeldItem(ItemData item, bool wasEquipped)
			{
				Item = item;
				WasEquipped = wasEquipped;
			}
		}

		private static readonly List<HeldItem> Held = new List<HeldItem>();

		public static bool Prepare(Player player)
		{
			if (Held.Count > 0)
			{
				Plugin.Log.LogWarning((object)$"Discarding {Held.Count} stale held items from a previous death");
				Held.Clear();
			}
			DeathRules deathRules = Plugin.Settings.BuildRules();
			WorldDeathModifiers worldDeathModifiers = ReadWorldModifiers();
			Inventory inventory = ((Humanoid)player).GetInventory();
			List<ItemData> allItems = inventory.GetAllItems();
			Func<ItemData, bool> func = null;
			if (Plugin.Settings.KeepQuickSlotItems.Value)
			{
				func = QuickSlotMods.GetQuickSlotClassifier();
				if (func == null)
				{
					Plugin.Log.LogWarning((object)("KeepQuickSlotItems is on but no supported quick slot mod is available (" + QuickSlotMods.SupportedMods + "); quick slot items are treated as regular items"));
				}
			}
			Plugin.Debug(string.Format("Death of {0}: {1} items, inventory {2}x{3}, rules: {4}, world modifiers: {5}, quick slot mod: {6}", player.GetPlayerName(), allItems.Count, inventory.GetWidth(), inventory.GetHeight(), deathRules.Describe(), worldDeathModifiers, QuickSlotMods.Active?.Name ?? "none"));
			List<ItemData> list = new List<ItemData>();
			List<ItemData> list2 = new List<ItemData>();
			List<ItemData> list3 = new List<ItemData>();
			foreach (ItemData item2 in allItems)
			{
				ItemFacts item = Describe(item2, func);
				ItemFate itemFate = deathRules.Resolve(in item, worldDeathModifiers);
				Plugin.Debug(string.Format("  {0} at [{1},{2}] ({3}{4}{5}{6}) -> {7}", item.DisplayName, item2.m_gridPos.x, item2.m_gridPos.y, item.TypeName, item.Equipped ? ", equipped" : "", item.QuickSlot ? ", quick slot" : "", item.Hotbar ? ", hotbar" : "", itemFate));
				switch (itemFate)
				{
				case ItemFate.Keep:
					list.Add(item2);
					break;
				case ItemFate.Destroy:
					list3.Add(item2);
					break;
				default:
					list2.Add(item2);
					break;
				}
			}
			if (list2.Count == 0 && list3.Count == 0)
			{
				Plugin.Debug("Nothing leaves the inventory, skipping tombstone");
				return false;
			}
			foreach (ItemData item3 in list3)
			{
				Unequip(player, item3);
				inventory.RemoveItem(item3);
			}
			foreach (ItemData item4 in list)
			{
				Held.Add(new HeldItem(item4, item4.m_equipped));
				allItems.Remove(item4);
			}
			if (Plugin.Settings.UseTombStone.Value)
			{
				inventory.Changed(false, false);
				return true;
			}
			foreach (ItemData item5 in list2)
			{
				if (TryScatterOnGround(player, item5))
				{
					Unequip(player, item5);
					allItems.Remove(item5);
				}
			}
			inventory.Changed(false, false);
			return false;
		}

		public static void RestoreHeld(Player player)
		{
			if (Held.Count == 0)
			{
				return;
			}
			List<ItemData> allItems = ((Humanoid)player).GetInventory().GetAllItems();
			foreach (HeldItem item in Held)
			{
				item.Item.m_equipped = item.WasEquipped;
				if (!allItems.Contains(item.Item))
				{
					allItems.Add(item.Item);
				}
			}
			Plugin.Debug($"Restored {Held.Count} kept items");
			Held.Clear();
			((Humanoid)player).GetInventory().Changed(false, false);
		}

		private static ItemFacts Describe(ItemData item, Func<ItemData, bool>? inQuickSlot)
		{
			SharedData shared = item.m_shared;
			return new ItemFacts(((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : "", shared.m_name, ((object)Unsafe.As<ItemType, ItemType>(ref shared.m_itemType)/*cast due to .constrained prefix*/).ToString(), item.m_equipped, item.m_gridPos.y == 0, inQuickSlot?.Invoke(item) ?? false, shared.m_teleportable, shared.m_questItem);
		}

		private static WorldDeathModifiers ReadWorldModifiers()
		{
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return WorldDeathModifiers.None;
			}
			return new WorldDeathModifiers(instance.GetGlobalKey((GlobalKeys)23), instance.GetGlobalKey((GlobalKeys)19), instance.GetGlobalKey((GlobalKeys)20), instance.GetGlobalKey((GlobalKeys)21));
		}

		private static void Unequip(Player player, ItemData item)
		{
			if (item.m_equipped)
			{
				((Humanoid)player).UnequipItem(item, false);
			}
		}

		private static bool TryScatterOnGround(Player player, ItemData item)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)item.m_dropPrefab == (Object)null)
			{
				Plugin.Log.LogWarning((object)(item.m_shared.m_name + " has no drop prefab and cannot be placed on the ground; keeping it"));
				return false;
			}
			try
			{
				Vector3 val = ((Component)player).transform.position + Vector3.up * 0.5f + Random.insideUnitSphere * 0.3f;
				Quaternion val2 = Quaternion.Euler(0f, (float)Random.Range(0, 360), 0f);
				ItemDrop.DropItem(item, 0, val, val2);
				return true;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not drop " + item.m_shared.m_name + " on the ground; keeping it: " + ex.Message));
				return false;
			}
		}
	}
	public sealed class ModConfig
	{
		private static readonly string[] ItemTypeNames = Enum.GetNames(typeof(ItemType));

		private readonly ConfigFile _config;

		public ConfigEntry<bool> Enabled { get; }

		public ConfigEntry<bool> IsDebug { get; }

		public ConfigEntry<string> KeepItemTypes { get; }

		public ConfigEntry<string> DropItemTypes { get; }

		public ConfigEntry<string> DestroyItemTypes { get; }

		public ConfigEntry<string> KeepItemNames { get; }

		public ConfigEntry<string> DropItemNames { get; }

		public ConfigEntry<string> DestroyItemNames { get; }

		public ConfigEntry<bool> KeepAllItems { get; }

		public ConfigEntry<bool> DestroyAllItems { get; }

		public ConfigEntry<bool> KeepEquippedItems { get; }

		public ConfigEntry<bool> KeepTeleportableItems { get; }

		public ConfigEntry<bool> KeepHotbarItems { get; }

		public ConfigEntry<bool> KeepQuickSlotItems { get; }

		public ConfigEntry<bool> UseTombStone { get; }

		public ConfigEntry<bool> CreateDeathEffects { get; }

		public ConfigEntry<bool> KeepFoodLevels { get; }

		public ConfigEntry<bool> UseFixedSpawnCoordinates { get; }

		public ConfigEntry<bool> SpawnAtStart { get; }

		public ConfigEntry<Vector3> FixedSpawnCoordinates { get; }

		public ConfigEntry<bool> NoSkillProtection { get; }

		public ConfigEntry<bool> ReduceSkills { get; }

		public ConfigEntry<float> SkillReduceFactor { get; }

		public ModConfig(ConfigFile config)
		{
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02db: Expected O, but got Unknown
			_config = config;
			string text = string.Join(", ", ItemTypeNames);
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable this mod.");
			IsDebug = config.Bind<bool>("General", "IsDebug", false, "Log every item decision on death.");
			KeepItemTypes = config.Bind<string>("ItemLists", "KeepItemTypes", "", "Item types to keep (comma-separated). Valid types: " + text);
			DropItemTypes = config.Bind<string>("ItemLists", "DropItemTypes", "", "Item types to drop even when KeepTeleportableItems would keep them (comma-separated).");
			DestroyItemTypes = config.Bind<string>("ItemLists", "DestroyItemTypes", "", "Item types to destroy (comma-separated). Overrides the keep and drop lists.");
			KeepItemNames = config.Bind<string>("ItemLists", "KeepItems", "", "Items to keep (comma-separated). Use prefab names, for example: Iron,IronScrap,CopperOre");
			DropItemNames = config.Bind<string>("ItemLists", "DropItems", "", "Items to drop even when KeepTeleportableItems would keep them (comma-separated prefab names).");
			DestroyItemNames = config.Bind<string>("ItemLists", "DestroyItems", "", "Items to destroy (comma-separated prefab names). Overrides the keep and drop lists.");
			KeepAllItems = config.Bind<bool>("Toggles", "KeepAllItems", false, "Keep everything. Overrides all other item options.");
			DestroyAllItems = config.Bind<bool>("Toggles", "DestroyAllItems", false, "Destroy everything except quest items. Overrides all other item options except KeepAllItems.");
			KeepEquippedItems = config.Bind<bool>("Toggles", "KeepEquippedItems", false, "Keep equipped items. Overrides the item lists.");
			KeepTeleportableItems = config.Bind<bool>("Toggles", "KeepTeleportableItems", false, "Keep items that can go through portals. Does not override the item lists.");
			KeepHotbarItems = config.Bind<bool>("Toggles", "KeepHotbarItems", false, "Keep items in the first inventory row. Overrides the item lists.");
			KeepQuickSlotItems = config.Bind<bool>("Toggles", "KeepQuickSlotItems", false, "Keep items in quick slots. Overrides the item lists. EquipmentAndQuickSlots 3.x: quick slots. Extra Slots: quick, food, ammo and misc slots.");
			UseTombStone = config.Bind<bool>("Toggles", "UseTombStone", true, "Put dropped items in a tombstone. When false they are scattered on the ground.");
			CreateDeathEffects = config.Bind<bool>("Toggles", "CreateDeathEffects", true, "Create the death effects (ragdoll and particles).");
			KeepFoodLevels = config.Bind<bool>("Toggles", "KeepFoodLevels", false, "Keep active food after respawn.");
			UseFixedSpawnCoordinates = config.Bind<bool>("Spawn", "UseFixedSpawnCoordinates", false, "Respawn at FixedSpawnCoordinates after death.");
			SpawnAtStart = config.Bind<bool>("Spawn", "SpawnAtStart", false, "Respawn at the start location after death. Takes precedence over UseFixedSpawnCoordinates.");
			FixedSpawnCoordinates = config.Bind<Vector3>("Spawn", "FixedSpawnCoordinates", Vector3.zero, "World coordinates used when UseFixedSpawnCoordinates is on.");
			NoSkillProtection = config.Bind<bool>("Skills", "NoSkillProtection", false, "Disable the skill-loss protection that normally follows a recent death.");
			ReduceSkills = config.Bind<bool>("Skills", "ReduceSkills", true, "Lower skills on death. When false, skills are never lowered or reset, whatever the world modifiers say.");
			SkillReduceFactor = config.Bind<float>("Skills", "SkillReduceFactor", 0.25f, new ConfigDescription("Fraction of each skill lost on a hard death (vanilla: 0.25). Multiplied by the SkillReductionRate world modifier.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		}

		public void Reload()
		{
			_config.Reload();
		}

		public DeathRules BuildRules()
		{
			return DeathRules.Parse(new DeathRuleSettings
			{
				KeepAllItems = KeepAllItems.Value,
				DestroyAllItems = DestroyAllItems.Value,
				KeepEquippedItems = KeepEquippedItems.Value,
				KeepHotbarItems = KeepHotbarItems.Value,
				KeepQuickSlotItems = KeepQuickSlotItems.Value,
				KeepTeleportableItems = KeepTeleportableItems.Value,
				KeepItemTypes = KeepItemTypes.Value,
				DropItemTypes = DropItemTypes.Value,
				DestroyItemTypes = DestroyItemTypes.Value,
				KeepItemNames = KeepItemNames.Value,
				DropItemNames = DropItemNames.Value,
				DestroyItemNames = DestroyItemNames.Value
			}, ItemTypeNames, delegate(string warning)
			{
				Plugin.Log.LogWarning((object)warning);
			});
		}
	}
	[BepInPlugin("muindor.DeathTweaks", "Death Tweaks", "1.0.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private Harmony? _harmony;

		public static ManualLogSource Log { get; private set; }

		public static ModConfig Settings { get; private set; }

		public static bool Enabled
		{
			get
			{
				if (Settings != null)
				{
					return Settings.Enabled.Value;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings = new ModConfig(((BaseUnityPlugin)this).Config);
			_harmony = new Harmony("muindor.DeathTweaks");
			_harmony.PatchAll(typeof(Plugin).Assembly);
			Log.LogInfo((object)"Death Tweaks 1.0.2 loaded");
			ReportQuickSlotSupport();
		}

		private static void ReportQuickSlotSupport()
		{
			QuickSlotMods.Provider active = QuickSlotMods.Active;
			if (active != null)
			{
				_ = active.Classifier;
			}
			else if (Settings.KeepQuickSlotItems.Value)
			{
				Log.LogWarning((object)("KeepQuickSlotItems is on but no supported quick slot mod is loaded (" + QuickSlotMods.SupportedMods + "); quick slot items are treated as regular items"));
			}
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		public static void Debug(string message)
		{
			if (Settings != null && Settings.IsDebug.Value)
			{
				Log.LogInfo((object)message);
			}
		}

		public static void PatchFailed(string patch, Exception exception)
		{
			Log.LogError((object)$"{patch} failed, falling back to vanilla behaviour: {exception}");
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "muindor.DeathTweaks";

		public const string PLUGIN_NAME = "Death Tweaks";

		public const string PLUGIN_VERSION = "1.0.2";
	}
}
namespace DeathTweaks.Rules
{
	public sealed class DeathRules
	{
		private static readonly char[] Separators = new char[2] { ',', ';' };

		private readonly DeathRuleSettings _settings;

		private readonly HashSet<string> _keepTypes;

		private readonly HashSet<string> _dropTypes;

		private readonly HashSet<string> _destroyTypes;

		private readonly HashSet<string> _keepNames;

		private readonly HashSet<string> _dropNames;

		private readonly HashSet<string> _destroyNames;

		private DeathRules(DeathRuleSettings settings, HashSet<string> keepTypes, HashSet<string> dropTypes, HashSet<string> destroyTypes, HashSet<string> keepNames, HashSet<string> dropNames, HashSet<string> destroyNames)
		{
			_settings = settings;
			_keepTypes = keepTypes;
			_dropTypes = dropTypes;
			_destroyTypes = destroyTypes;
			_keepNames = keepNames;
			_dropNames = dropNames;
			_destroyNames = destroyNames;
		}

		public static DeathRules Parse(DeathRuleSettings settings, ICollection<string> knownTypeNames, Action<string>? warn = null)
		{
			if (settings == null)
			{
				throw new ArgumentNullException("settings");
			}
			if (knownTypeNames == null)
			{
				throw new ArgumentNullException("knownTypeNames");
			}
			HashSet<string> known = new HashSet<string>(knownTypeNames, StringComparer.OrdinalIgnoreCase);
			return new DeathRules(settings, ParseTypes("KeepItemTypes", settings.KeepItemTypes, known, warn), ParseTypes("DropItemTypes", settings.DropItemTypes, known, warn), ParseTypes("DestroyItemTypes", settings.DestroyItemTypes, known, warn), ParseList(settings.KeepItemNames), ParseList(settings.DropItemNames), ParseList(settings.DestroyItemNames));
		}

		public ItemFate Resolve(in ItemFacts item, WorldDeathModifiers world)
		{
			if (world == null)
			{
				throw new ArgumentNullException("world");
			}
			if (world.KeepInventory || _settings.KeepAllItems)
			{
				return ItemFate.Keep;
			}
			if (item.QuestItem)
			{
				return ItemFate.Keep;
			}
			if (item.Equipped && world.KeepEquip)
			{
				return ItemFate.Keep;
			}
			ItemFate itemFate = ResolveFromSettings(in item);
			if (itemFate == ItemFate.Drop && (world.DeleteItems || (world.DeleteUnequipped && !item.Equipped)))
			{
				return ItemFate.Destroy;
			}
			return itemFate;
		}

		public string Describe()
		{
			StringBuilder stringBuilder = new StringBuilder();
			AppendFlag(stringBuilder, "KeepAllItems", _settings.KeepAllItems);
			AppendFlag(stringBuilder, "DestroyAllItems", _settings.DestroyAllItems);
			AppendFlag(stringBuilder, "KeepEquippedItems", _settings.KeepEquippedItems);
			AppendFlag(stringBuilder, "KeepHotbarItems", _settings.KeepHotbarItems);
			AppendFlag(stringBuilder, "KeepQuickSlotItems", _settings.KeepQuickSlotItems);
			AppendFlag(stringBuilder, "KeepTeleportableItems", _settings.KeepTeleportableItems);
			AppendList(stringBuilder, "KeepItemTypes", _keepTypes);
			AppendList(stringBuilder, "DropItemTypes", _dropTypes);
			AppendList(stringBuilder, "DestroyItemTypes", _destroyTypes);
			AppendList(stringBuilder, "KeepItems", _keepNames);
			AppendList(stringBuilder, "DropItems", _dropNames);
			AppendList(stringBuilder, "DestroyItems", _destroyNames);
			if (stringBuilder.Length != 0)
			{
				return stringBuilder.ToString();
			}
			return "no item rules active (everything is dropped)";
		}

		private ItemFate ResolveFromSettings(in ItemFacts item)
		{
			if (_settings.DestroyAllItems)
			{
				return ItemFate.Destroy;
			}
			if (_settings.KeepEquippedItems && item.Equipped)
			{
				return ItemFate.Keep;
			}
			if (_settings.KeepHotbarItems && item.Hotbar)
			{
				return ItemFate.Keep;
			}
			if (_settings.KeepQuickSlotItems && item.QuickSlot)
			{
				return ItemFate.Keep;
			}
			if (_destroyTypes.Contains(item.TypeName) || MatchesName(_destroyNames, in item))
			{
				return ItemFate.Destroy;
			}
			if (_keepTypes.Contains(item.TypeName) || MatchesName(_keepNames, in item))
			{
				return ItemFate.Keep;
			}
			if (_dropTypes.Contains(item.TypeName) || MatchesName(_dropNames, in item))
			{
				return ItemFate.Drop;
			}
			if (_settings.KeepTeleportableItems && item.Teleportable)
			{
				return ItemFate.Keep;
			}
			return ItemFate.Drop;
		}

		private static bool MatchesName(HashSet<string> names, in ItemFacts item)
		{
			if (names.Count == 0)
			{
				return false;
			}
			if (item.PrefabName.Length <= 0 || !names.Contains(item.PrefabName))
			{
				if (item.SharedName.Length > 0)
				{
					return names.Contains(item.SharedName);
				}
				return false;
			}
			return true;
		}

		private static HashSet<string> ParseTypes(string settingName, string raw, HashSet<string> known, Action<string>? warn)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (string item in ParseList(raw))
			{
				if (known.Contains(item))
				{
					hashSet.Add(item);
					continue;
				}
				warn?.Invoke(settingName + ": unknown item type '" + item + "' ignored. Valid types: " + string.Join(", ", ToSortedArray(known)));
			}
			return hashSet;
		}

		private static HashSet<string> ParseList(string raw)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			if (string.IsNullOrEmpty(raw))
			{
				return hashSet;
			}
			string[] array = raw.Split(Separators);
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length > 0)
				{
					hashSet.Add(text);
				}
			}
			return hashSet;
		}

		private static string[] ToSortedArray(HashSet<string> set)
		{
			string[] array = new string[set.Count];
			set.CopyTo(array);
			Array.Sort(array, (IComparer<string>?)StringComparer.OrdinalIgnoreCase);
			return array;
		}

		private static void AppendFlag(StringBuilder sb, string name, bool value)
		{
			if (value)
			{
				if (sb.Length > 0)
				{
					sb.Append("; ");
				}
				sb.Append(name);
			}
		}

		private static void AppendList(StringBuilder sb, string name, HashSet<string> values)
		{
			if (values.Count != 0)
			{
				if (sb.Length > 0)
				{
					sb.Append("; ");
				}
				sb.Append(name).Append('=').Append(string.Join(",", ToSortedArray(values)));
			}
		}
	}
	public sealed class DeathRuleSettings
	{
		public bool KeepAllItems { get; set; }

		public bool DestroyAllItems { get; set; }

		public bool KeepEquippedItems { get; set; }

		public bool KeepHotbarItems { get; set; }

		public bool KeepQuickSlotItems { get; set; }

		public bool KeepTeleportableItems { get; set; }

		public string KeepItemTypes { get; set; } = "";

		public string DropItemTypes { get; set; } = "";

		public string DestroyItemTypes { get; set; } = "";

		public string KeepItemNames { get; set; } = "";

		public string DropItemNames { get; set; } = "";

		public string DestroyItemNames { get; set; } = "";
	}
	public readonly struct ItemFacts
	{
		public string PrefabName { get; }

		public string SharedName { get; }

		public string TypeName { get; }

		public bool Equipped { get; }

		public bool Hotbar { get; }

		public bool QuickSlot { get; }

		public bool Teleportable { get; }

		public bool QuestItem { get; }

		public string DisplayName
		{
			get
			{
				if (PrefabName.Length <= 0)
				{
					return SharedName;
				}
				return PrefabName;
			}
		}

		public ItemFacts(string prefabName, string sharedName, string typeName, bool equipped, bool hotbar, bool quickSlot, bool teleportable, bool questItem)
		{
			PrefabName = prefabName ?? "";
			SharedName = sharedName ?? "";
			TypeName = typeName ?? "";
			Equipped = equipped;
			Hotbar = hotbar;
			QuickSlot = quickSlot;
			Teleportable = teleportable;
			QuestItem = questItem;
		}
	}
	public enum ItemFate
	{
		Keep,
		Drop,
		Destroy
	}
	public sealed class WorldDeathModifiers
	{
		public static readonly WorldDeathModifiers None = new WorldDeathModifiers(keepInventory: false, keepEquip: false, deleteItems: false, deleteUnequipped: false);

		public bool KeepInventory { get; }

		public bool KeepEquip { get; }

		public bool DeleteItems { get; }

		public bool DeleteUnequipped { get; }

		public bool Any
		{
			get
			{
				if (!KeepInventory && !KeepEquip && !DeleteItems)
				{
					return DeleteUnequipped;
				}
				return true;
			}
		}

		public WorldDeathModifiers(bool keepInventory, bool keepEquip, bool deleteItems, bool deleteUnequipped)
		{
			KeepInventory = keepInventory;
			KeepEquip = keepEquip;
			DeleteItems = deleteItems;
			DeleteUnequipped = deleteUnequipped;
		}

		public override string ToString()
		{
			if (!Any)
			{
				return "none";
			}
			List<string> list = new List<string>(4);
			if (KeepInventory)
			{
				list.Add("DeathKeepInventory");
			}
			if (KeepEquip)
			{
				list.Add("DeathKeepEquip");
			}
			if (DeleteItems)
			{
				list.Add("DeathDeleteItems");
			}
			if (DeleteUnequipped)
			{
				list.Add("DeathDeleteUnequipped");
			}
			return string.Join(", ", list.ToArray());
		}
	}
}
namespace DeathTweaks.Patches
{
	[HarmonyPatch(typeof(Player), "CreateDeathEffects")]
	internal static class Player_CreateDeathEffects_Patch
	{
		private static bool Prefix()
		{
			if (Plugin.Enabled)
			{
				return Plugin.Settings.CreateDeathEffects.Value;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Player), "OnDeath")]
	internal static class Player_OnDeath_Patch
	{
		private static void Prefix(Player __instance, ref List<Food>? __state)
		{
			DeathContext.Enter();
			if (Plugin.Enabled && Plugin.Settings.KeepFoodLevels.Value && ((Character)__instance).IsOwner())
			{
				__state = new List<Food>(__instance.GetFoods());
			}
		}

		private static void Postfix(Player __instance, List<Food>? __state)
		{
			if (__state == null || __state.Count == 0)
			{
				return;
			}
			List<Food> foods = __instance.GetFoods();
			foreach (Food item in __state)
			{
				if (!foods.Contains(item))
				{
					foods.Add(item);
				}
			}
			Plugin.Debug($"Kept {__state.Count} food items through death");
		}

		private static Exception? Finalizer(Exception? __exception)
		{
			DeathContext.Exit();
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Skills), "OnDeath")]
	internal static class Skills_OnDeath_Patch
	{
		private static bool Prefix(Skills __instance, ref float __state)
		{
			if (!Plugin.Enabled)
			{
				return true;
			}
			if (!Plugin.Settings.ReduceSkills.Value)
			{
				Plugin.Debug("Skill loss skipped (ReduceSkills is off)");
				return false;
			}
			__state = __instance.m_DeathLowerFactor;
			__instance.m_DeathLowerFactor = Plugin.Settings.SkillReduceFactor.Value;
			Plugin.Debug($"Lowering skills by {__instance.m_DeathLowerFactor} x {Game.m_skillReductionRate} (world modifier)");
			return true;
		}

		private static Exception? Finalizer(Skills __instance, float __state, Exception? __exception)
		{
			if (Plugin.Enabled && Plugin.Settings.ReduceSkills.Value)
			{
				__instance.m_DeathLowerFactor = __state;
			}
			return __exception;
		}
	}
	[HarmonyPatch(typeof(Skills), "Clear")]
	internal static class Skills_Clear_Patch
	{
		private static bool Prefix()
		{
			if (Plugin.Enabled && DeathContext.InOnDeath && !Plugin.Settings.ReduceSkills.Value)
			{
				Plugin.Debug("Skill reset skipped (ReduceSkills is off)");
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Player), "HardDeath")]
	internal static class Player_HardDeath_Patch
	{
		private static bool Prefix(ref bool __result)
		{
			if (!Plugin.Enabled || !Plugin.Settings.NoSkillProtection.Value)
			{
				return true;
			}
			__result = true;
			return false;
		}
	}
	[HarmonyPatch(typeof(Game), "FindSpawnPoint")]
	internal static class Game_FindSpawnPoint_Patch
	{
		private static Vector3? _lastRejectedTarget;

		private static bool Prefix(Game __instance, ref Vector3 point, ref bool usedLogoutPoint, float dt, ref bool __result)
		{
			if (!Plugin.Enabled || !__instance.m_respawnAfterDeath)
			{
				return true;
			}
			try
			{
				return !TryResolveSpawnPoint(__instance, dt, ref point, ref usedLogoutPoint, ref __result);
			}
			catch (Exception exception)
			{
				Plugin.PatchFailed("Game_FindSpawnPoint_Patch", exception);
				return true;
			}
		}

		private static bool TryResolveSpawnPoint(Game game, float dt, ref Vector3 point, ref bool usedLogoutPoint, ref bool ready)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: 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_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			ModConfig settings = Plugin.Settings;
			Vector3 val2;
			bool flag;
			if (settings.SpawnAtStart.Value)
			{
				Vector3 val = default(Vector3);
				if (!ZoneSystem.instance.GetLocationIcon(game.m_StartLocation, ref val))
				{
					WarnOnce(Vector3.zero, "Start location '" + game.m_StartLocation + "' not found, using vanilla respawn");
					return false;
				}
				val2 = val + Vector3.up * 2f;
				flag = false;
			}
			else
			{
				if (!settings.UseFixedSpawnCoordinates.Value)
				{
					return false;
				}
				val2 = settings.FixedSpawnCoordinates.Value;
				flag = true;
			}
			game.m_respawnWait += dt;
			usedLogoutPoint = false;
			ZNet.instance.SetReferencePosition(val2);
			ready = (!flag || game.m_respawnWait > game.m_respawnLoadDuration) && ZNetScene.instance.IsAreaReady(val2);
			if (ready)
			{
				float num = default(float);
				if (!ZoneSystem.instance.GetGroundHeight(val2, ref num))
				{
					WarnOnce(val2, $"No ground at spawn point {val2}, using vanilla respawn");
					ready = false;
					return false;
				}
				float num2 = Mathf.Max(num, ZoneSystem.instance.m_waterLevel);
				if (val2.y < num2)
				{
					val2.y = num2 + 0.25f;
				}
				Plugin.Debug($"Respawning at {val2}");
			}
			point = val2;
			return true;
		}

		private static void WarnOnce(Vector3 target, string message)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: 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)
			Vector3? lastRejectedTarget = _lastRejectedTarget;
			if (!lastRejectedTarget.HasValue || !(lastRejectedTarget.GetValueOrDefault() == target))
			{
				_lastRejectedTarget = target;
				Plugin.Log.LogWarning((object)message);
			}
		}
	}
	[HarmonyPatch(typeof(Terminal), "InitTerminal")]
	internal static class Terminal_InitTerminal_Patch
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ConsoleEvent <0>__Execute;
		}

		private const string Command = "deathtweaks";

		private static void Postfix()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: 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_0025: Expected O, but got Unknown
			object obj = <>O.<0>__Execute;
			if (obj == null)
			{
				ConsoleEvent val = Execute;
				<>O.<0>__Execute = val;
				obj = (object)val;
			}
			new ConsoleCommand("deathtweaks", "deathtweaks [reload|status] - reload the Death Tweaks config or show the active item rules", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		private static void Execute(ConsoleEventArgs args)
		{
			Terminal context = args.Context;
			string text = ((args.Args.Length > 1) ? args.Args[1].ToLowerInvariant() : "status");
			if (!(text == "reload"))
			{
				if (text == "status")
				{
					context.AddString("Death Tweaks 1.0.2: " + (Plugin.Enabled ? "enabled" : "disabled"));
					context.AddString("Item rules: " + Plugin.Settings.BuildRules().Describe());
					context.AddString($"Tombstone: {Plugin.Settings.UseTombStone.Value}, keep food: {Plugin.Settings.KeepFoodLevels.Value}, reduce skills: {Plugin.Settings.ReduceSkills.Value} (factor {Plugin.Settings.SkillReduceFactor.Value})");
				}
				else
				{
					context.AddString("Usage: deathtweaks [reload|status]");
				}
			}
			else
			{
				Plugin.Settings.Reload();
				context.AddString("Death Tweaks: config reloaded");
			}
		}
	}
	[HarmonyPatch(typeof(Player), "CreateTombStone")]
	internal static class Player_CreateTombStone_Patch
	{
		private static bool Prefix(Player __instance)
		{
			if (!Plugin.Enabled || !DeathContext.InOnDeath)
			{
				return true;
			}
			try
			{
				return DeathInventory.Prepare(__instance);
			}
			catch (Exception exception)
			{
				Plugin.PatchFailed("Player_CreateTombStone_Patch", exception);
				TryRestoreHeld(__instance);
				return true;
			}
		}

		private static Exception? Finalizer(Player __instance, Exception? __exception)
		{
			TryRestoreHeld(__instance);
			return __exception;
		}

		private static void TryRestoreHeld(Player player)
		{
			try
			{
				DeathInventory.RestoreHeld(player);
			}
			catch (Exception exception)
			{
				Plugin.PatchFailed("Player_CreateTombStone_Patch.RestoreHeld", exception);
			}
		}
	}
}
namespace DeathTweaks.Compat
{
	internal static class QuickSlotMods
	{
		internal abstract class Provider
		{
			private bool _resolved;

			private Func<ItemData, bool>? _classifier;

			public string Name { get; }

			public string PluginGuid { get; }

			public string AssemblyName { get; }

			public string ApiTypeName { get; }

			public string SlotDescription { get; }

			public bool IsLoaded => Chainloader.PluginInfos.ContainsKey(PluginGuid);

			public string LoadedVersion
			{
				get
				{
					if (!Chainloader.PluginInfos.TryGetValue(PluginGuid, out var value) || ((value != null) ? value.Metadata : null) == null)
					{
						return "?";
					}
					return value.Metadata.Version.ToString();
				}
			}

			public Func<ItemData, bool>? Classifier
			{
				get
				{
					if (_resolved)
					{
						return _classifier;
					}
					_resolved = true;
					try
					{
						Type type = FindApiType();
						_classifier = ((type == null) ? null : CreateClassifier(type));
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)(Name + " API could not be resolved: " + ex.Message));
						_classifier = null;
					}
					if (_classifier == null)
					{
						Plugin.Log.LogWarning((object)(Name + " " + LoadedVersion + " is installed but its API (" + ApiTypeName + ") was not found or has changed. Update " + Name + "; quick slot items are treated as regular items."));
					}
					else
					{
						Plugin.Log.LogInfo((object)("Quick slot support: " + Name + " " + LoadedVersion + " (" + SlotDescription + ")"));
					}
					return _classifier;
				}
			}

			protected Provider(string name, string pluginGuid, string assemblyName, string apiTypeName, string slotDescription)
			{
				Name = name;
				PluginGuid = pluginGuid;
				AssemblyName = assemblyName;
				ApiTypeName = apiTypeName;
				SlotDescription = slotDescription;
			}

			protected abstract Func<ItemData, bool>? CreateClassifier(Type api);

			protected static MethodInfo? PublicStatic(Type api, string name, params Type[] parameters)
			{
				return api.GetMethod(name, BindingFlags.Static | BindingFlags.Public, null, parameters, null);
			}

			private Type? FindApiType()
			{
				if (Chainloader.PluginInfos.TryGetValue(PluginGuid, out var value) && (Object)(object)((value != null) ? value.Instance : null) != (Object)null)
				{
					Type type = ((object)value.Instance).GetType().Assembly.GetType(ApiTypeName, throwOnError: false);
					if (type != null)
					{
						return type;
					}
				}
				return Type.GetType(ApiTypeName + ", " + AssemblyName, throwOnError: false);
			}
		}

		private sealed class EquipmentAndQuickSlotsProvider : Provider
		{
			public EquipmentAndQuickSlotsProvider()
				: base("EquipmentAndQuickSlots", "randyknapp.mods.equipmentandquickslots", "EquipmentAndQuickSlots", "EquipmentAndQuickSlots.API", "quick slots")
			{
			}

			protected override Func<ItemData, bool>? CreateClassifier(Type api)
			{
				MethodInfo isSlotCell = Provider.PublicStatic(api, "IsSlotCell", typeof(int), typeof(int), typeof(string).MakeByRefType());
				if (isSlotCell == null)
				{
					return null;
				}
				return delegate(ItemData item)
				{
					object[] array = new object[3]
					{
						item.m_gridPos.x,
						item.m_gridPos.y,
						null
					};
					object obj = isSlotCell.Invoke(null, array);
					return obj is bool && (bool)obj && array[2] is string text && text.StartsWith("Quick", StringComparison.Ordinal);
				};
			}
		}

		private sealed class ExtraSlotsProvider : Provider
		{
			public ExtraSlotsProvider()
				: base("Extra Slots", "shudnal.ExtraSlots", "ExtraSlots", "ExtraSlots.API", "quick, food, ammo and misc slots")
			{
			}

			protected override Func<ItemData, bool>? CreateClassifier(Type api)
			{
				MethodInfo isSlotPosition = Provider.PublicStatic(api, "IsGridPositionASlot", typeof(Vector2i));
				MethodInfo isEquipmentSlotItem = Provider.PublicStatic(api, "IsItemInEquipmentSlot", typeof(ItemData));
				if (isSlotPosition == null || isEquipmentSlotItem == null)
				{
					return null;
				}
				return delegate(ItemData item)
				{
					//IL_0010: Unknown result type (might be due to invalid IL or missing references)
					object obj = isSlotPosition.Invoke(null, new object[1] { item.m_gridPos });
					if (obj is bool && (bool)obj)
					{
						obj = isEquipmentSlotItem.Invoke(null, new object[1] { item });
						if (obj is bool)
						{
							return !(bool)obj;
						}
						return false;
					}
					return false;
				};
			}
		}

		public const string EquipmentAndQuickSlotsGuid = "randyknapp.mods.equipmentandquickslots";

		public const string ExtraSlotsGuid = "shudnal.ExtraSlots";

		private static readonly Provider[] Providers = new Provider[2]
		{
			new EquipmentAndQuickSlotsProvider(),
			new ExtraSlotsProvider()
		};

		public static string SupportedMods => "EquipmentAndQuickSlots 3.x, Extra Slots";

		public static Provider? Active
		{
			get
			{
				Provider[] providers = Providers;
				foreach (Provider provider in providers)
				{
					if (provider.IsLoaded)
					{
						return provider;
					}
				}
				return null;
			}
		}

		public static Func<ItemData, bool>? GetQuickSlotClassifier()
		{
			Provider provider = Active;
			Func<ItemData, bool> classifier = provider?.Classifier;
			if (provider == null || classifier == null)
			{
				return null;
			}
			return delegate(ItemData item)
			{
				try
				{
					return classifier(item);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)(provider.Name + " API call failed for " + item.m_shared.m_name + ", treating it as a regular item: " + ex.Message));
					return false;
				}
			};
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}