Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of DeathTweaks v1.0.2
plugins/DeathTweaks/DeathTweaks.dll
Decompiled 3 days agousing 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) { } } }