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.1.0
plugins/DeathTweaks/DeathTweaks.dll
Decompiled 2 months 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 Muindor.ServerConfig; 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.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0+be4ea95e7c7c23e6b4cf630ceb5d310166dc0954")] [assembly: AssemblyProduct("Death Tweaks")] [assembly: AssemblyTitle("DeathTweaks")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.0.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 Muindor.ServerConfig { public static class ConfigChannel { private static ConfigSync? _sync; private static ManualLogSource? _log; private static string _rpcName = string.Empty; public static void Activate(ConfigSync sync, ManualLogSource log) { _sync = sync; _log = log; _rpcName = sync.PluginGuid + ".ServerConfig"; sync.Changed += Broadcast; } internal static void OnNewConnection(ZNet net, ZNetPeer peer) { if (_sync != null && !net.IsServer() && peer != null && peer.m_server && peer.m_rpc != null) { _sync.Clear(); peer.m_rpc.Register<string>(_rpcName, (Action<ZRpc, string>)Receive); } } internal static void OnPeerInfo(ZNet net, ZRpc rpc) { if (_sync == null || !net.IsServer()) { return; } ZNetPeer peer = net.GetPeer(rpc); if (peer != null && peer.IsReady() && peer.m_rpc != null) { peer.m_rpc.Invoke(_rpcName, new object[1] { _sync.BuildPayload().Serialize() }); ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)("Sent the configuration to " + peer.m_playerName + " (ConfigPriority " + (_sync.Priority.Value ? "on" : "off") + ")")); } } } internal static void OnSessionEnd() { _sync?.Clear(); } internal static void PatchFailed(string patch, Exception exception) { ManualLogSource? log = _log; if (log != null) { log.LogError((object)$"{patch} failed, the configuration channel may be inactive: {exception}"); } } private static void Broadcast() { ZNet instance = ZNet.instance; if (_sync == null || (Object)(object)instance == (Object)null || !instance.IsServer()) { return; } string text = _sync.BuildPayload().Serialize(); int num = 0; foreach (ZNetPeer peer in instance.GetPeers()) { if (peer.IsReady() && peer.m_rpc != null) { peer.m_rpc.Invoke(_rpcName, new object[1] { text }); num++; } } if (num > 0) { ManualLogSource? log = _log; if (log != null) { log.LogInfo((object)$"A setting changed; sent the configuration to {num} player(s)"); } } } private static void Receive(ZRpc rpc, string text) { if (_sync == null) { return; } ZNet instance = ZNet.instance; ConfigPayload payload; string error; if ((Object)(object)instance != (Object)null && instance.IsServer()) { ManualLogSource? log = _log; if (log != null) { log.LogWarning((object)"Ignored a configuration sent to this server by a client"); } } else if (!ConfigPayload.TryParse(text, out payload, out error)) { ManualLogSource? log2 = _log; if (log2 != null) { log2.LogWarning((object)("Ignored the configuration from the server, keeping the local settings: " + error)); } } else { _sync.Apply(payload); } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] internal static class ZNet_OnNewConnection_Patch { private static void Postfix(ZNet __instance, ZNetPeer peer) { try { ConfigChannel.OnNewConnection(__instance, peer); } catch (Exception exception) { ConfigChannel.PatchFailed("OnNewConnection", exception); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] internal static class ZNet_RPC_PeerInfo_Patch { private static void Postfix(ZNet __instance, ZRpc rpc) { try { ConfigChannel.OnPeerInfo(__instance, rpc); } catch (Exception exception) { ConfigChannel.PatchFailed("RPC_PeerInfo", exception); } } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] internal static class ZNet_OnDestroy_Patch { private static void Postfix() { try { ConfigChannel.OnSessionEnd(); } catch (Exception exception) { ConfigChannel.PatchFailed("OnDestroy", exception); } } } public sealed class ConfigPayload { public const string Header = "muindor.config/1"; public string ModVersion { get; } public bool Priority { get; } public IList<KeyValuePair<string, string>> Values { get; } public ConfigPayload(string modVersion, bool priority, IEnumerable<KeyValuePair<string, string>>? values = null) { ModVersion = modVersion ?? string.Empty; Priority = priority; List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); if (values != null) { list.AddRange(values); } Values = list; } public string Serialize() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("muindor.config/1").Append('\n'); stringBuilder.Append(Escape(ModVersion, escapeEquals: false)).Append('\n'); stringBuilder.Append(Priority ? "true" : "false").Append('\n'); foreach (KeyValuePair<string, string> value in Values) { stringBuilder.Append(Escape(value.Key, escapeEquals: true)).Append('=').Append(Escape(value.Value ?? string.Empty, escapeEquals: false)) .Append('\n'); } return stringBuilder.ToString(); } public static bool TryParse(string? text, out ConfigPayload? payload, out string error) { payload = null; if (string.IsNullOrEmpty(text)) { error = "the payload is empty"; return false; } string[] array = text.Split(new char[1] { '\n' }); if (array.Length < 3) { error = "the payload is truncated"; return false; } if (Trim(array[0]) != "muindor.config/1") { error = "unknown format \"" + Trim(array[0]) + "\", expected \"muindor.config/1\""; return false; } string modVersion = Unescape(Trim(array[1])); string text2 = Trim(array[2]); if (text2 != "true" && text2 != "false") { error = "\"" + text2 + "\" is not a valid priority flag"; return false; } List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); for (int i = 3; i < array.Length; i++) { string text3 = Trim(array[i]); if (text3.Length != 0) { int num = IndexOfSeparator(text3); if (num < 0) { error = $"line {i + 1} has no key/value separator"; return false; } string text4 = Unescape(text3.Substring(0, num)); if (text4.Length == 0) { error = $"line {i + 1} has an empty key"; return false; } list.Add(new KeyValuePair<string, string>(text4, Unescape(text3.Substring(num + 1)))); } } payload = new ConfigPayload(modVersion, text2 == "true", list); error = string.Empty; return true; } private static string Trim(string line) { if (!line.EndsWith("\r", StringComparison.Ordinal)) { return line; } return line.Substring(0, line.Length - 1); } private static int IndexOfSeparator(string line) { for (int i = 0; i < line.Length; i++) { if (line[i] == '\\') { i++; } else if (line[i] == '=') { return i; } } return -1; } private static string Escape(string value, bool escapeEquals) { StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { switch (c) { case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '=': if (escapeEquals) { stringBuilder.Append("\\="); continue; } break; } stringBuilder.Append(c); } return stringBuilder.ToString(); } private static string Unescape(string value) { if (value.IndexOf('\\') < 0) { return value; } StringBuilder stringBuilder = new StringBuilder(value.Length); for (int i = 0; i < value.Length; i++) { if (value[i] != '\\' || i + 1 >= value.Length) { stringBuilder.Append(value[i]); continue; } char c = value[++i]; switch (c) { case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; default: stringBuilder.Append(c); break; } } return stringBuilder.ToString(); } } public sealed class ConfigSync { private const string SyncedNote = "[synced] "; private readonly Dictionary<string, ISyncedEntry> _byKey = new Dictionary<string, ISyncedEntry>(StringComparer.Ordinal); private readonly List<ISyncedEntry> _entries = new List<ISyncedEntry>(); private readonly HashSet<ConfigEntryBase> _watched = new HashSet<ConfigEntryBase>(); private readonly ConfigFile _config; private readonly ManualLogSource _log; public string PluginGuid { get; } public string ModName { get; } public string ModVersion { get; } public ConfigEntry<bool> Priority { get; } public bool Locked { get; private set; } public string? ServerVersion { get; private set; } public int LockedCount { get; private set; } public event Action? Changed; public ConfigSync(ConfigFile config, string pluginGuid, string modName, string modVersion, ManualLogSource log) { _config = config; _log = log; PluginGuid = pluginGuid; ModName = modName; ModVersion = modVersion; Priority = config.Bind<bool>("Server", "ConfigPriority", false, "Server only, and off by default. Turn it on in the configuration of a dedicated server, or of the player hosting the session, to have every client that also has the mod run on the settings marked [synced] below instead of its own. The clients' own config files are never touched: their settings come back the moment they disconnect. This never makes the mod required on either side - players without it are unaffected, and a player with it can still join a server that does not have it."); _watched.Add((ConfigEntryBase)(object)Priority); config.SettingChanged += OnSettingChanged; } public SyncedEntry<T> Bind<T>(string section, string key, T defaultValue, string description) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>())); } public SyncedEntry<T> Bind<T>(string section, string key, T defaultValue, ConfigDescription description) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown ConfigDescription val = new ConfigDescription("[synced] " + description.Description, description.AcceptableValues, description.Tags); ConfigEntry<T> val2 = _config.Bind<T>(section, key, defaultValue, val); SyncedEntry<T> syncedEntry = new SyncedEntry<T>(val2); _byKey.Add(syncedEntry.Key, syncedEntry); _entries.Add(syncedEntry); _watched.Add((ConfigEntryBase)(object)val2); return syncedEntry; } public ConfigPayload BuildPayload() { bool value = Priority.Value; List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(value ? _entries.Count : 0); if (value) { foreach (ISyncedEntry entry in _entries) { try { list.Add(new KeyValuePair<string, string>(entry.Key, entry.LocalAsString())); } catch (Exception ex) { _log.LogWarning((object)(entry.Key + " could not be sent to the clients: " + ex.Message)); } } } return new ConfigPayload(ModVersion, value, list); } public void Apply(ConfigPayload payload) { ClearValues(); if (!payload.Priority) { Locked = false; ServerVersion = null; LockedCount = 0; _log.LogInfo((object)("The server runs " + ModName + " " + payload.ModVersion + " with ConfigPriority off; keeping the local settings")); return; } int num = 0; List<string> list = new List<string>(); foreach (KeyValuePair<string, string> value2 in payload.Values) { string error; if (!_byKey.TryGetValue(value2.Key, out ISyncedEntry value)) { list.Add(value2.Key); } else if (value.ApplyServerValue(value2.Value, out error)) { num++; } else { _log.LogWarning((object)("Keeping the local " + value2.Key + ": the server sent a value this version cannot read, " + error)); } } Locked = true; ServerVersion = payload.ModVersion; LockedCount = num; int num2 = _entries.Count - num; string text = ((list.Count > 0) ? string.Format(", {0} unknown here ({1})", list.Count, string.Join(", ", list.ToArray())) : string.Empty); string text2 = ((num2 > 0) ? $", {num2} kept local" : string.Empty); _log.LogInfo((object)$"The server ({ModName} {payload.ModVersion}) decides the settings: {num} applied{text2}{text}"); } public void Clear() { if (Locked || LockedCount != 0) { ClearValues(); Locked = false; ServerVersion = null; LockedCount = 0; _log.LogInfo((object)"Back to the local settings"); } } public string Describe() { if (!Locked) { return "local settings"; } return $"settings from the server ({ModName} {ServerVersion}, {LockedCount} values)"; } private void ClearValues() { foreach (ISyncedEntry entry in _entries) { entry.ClearServerValue(); } } private void OnSettingChanged(object sender, SettingChangedEventArgs args) { if (this.Changed != null) { ConfigEntryBase changedSetting = args.ChangedSetting; if (changedSetting != null && _watched.Contains(changedSetting)) { this.Changed(); } } } } public interface ISyncedEntry { string Key { get; } string LocalAsString(); bool ApplyServerValue(string text, out string error); void ClearServerValue(); } public sealed class SyncedEntry<T> : ISyncedEntry { private T _serverValue; private bool _hasServerValue; public ConfigEntry<T> Local { get; } public string Key { get; } public T Value { get { if (!_hasServerValue) { return Local.Value; } return _serverValue; } } public T LocalValue { get { return Local.Value; } set { Local.Value = value; } } public bool IsLocked => _hasServerValue; internal SyncedEntry(ConfigEntry<T> local) { Local = local; Key = ((ConfigEntryBase)local).Definition.Section + "." + ((ConfigEntryBase)local).Definition.Key; } public string LocalAsString() { return TomlTypeConverter.ConvertToString((object)Local.Value, typeof(T)); } public bool ApplyServerValue(string text, out string error) { object obj; try { obj = TomlTypeConverter.ConvertToValue(text, typeof(T)); } catch (Exception ex) { error = "\"" + text + "\" is not a valid " + typeof(T).Name + " (" + ex.Message + ")"; return false; } ConfigDescription description = ((ConfigEntryBase)Local).Description; AcceptableValueBase val = ((description != null) ? description.AcceptableValues : null); if (val != null && !val.IsValid(obj)) { obj = val.Clamp(obj); } _serverValue = (T)obj; _hasServerValue = true; error = string.Empty; return true; } public void ClearServerValue() { _hasServerValue = false; _serverValue = default(T); } public override string ToString() { T value = Value; return ((value != null) ? value.ToString() : null) ?? string.Empty; } } } 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 ConfigSync Sync { get; } public SyncedEntry<bool> Enabled { get; } public ConfigEntry<bool> IsDebug { get; } public SyncedEntry<string> KeepItemTypes { get; } public SyncedEntry<string> DropItemTypes { get; } public SyncedEntry<string> DestroyItemTypes { get; } public SyncedEntry<string> KeepItemNames { get; } public SyncedEntry<string> DropItemNames { get; } public SyncedEntry<string> DestroyItemNames { get; } public SyncedEntry<bool> KeepAllItems { get; } public SyncedEntry<bool> DestroyAllItems { get; } public SyncedEntry<bool> KeepEquippedItems { get; } public SyncedEntry<bool> KeepTeleportableItems { get; } public SyncedEntry<bool> KeepHotbarItems { get; } public SyncedEntry<bool> KeepQuickSlotItems { get; } public SyncedEntry<bool> UseTombStone { get; } public SyncedEntry<bool> CreateDeathEffects { get; } public SyncedEntry<bool> KeepFoodLevels { get; } public SyncedEntry<bool> UseFixedSpawnCoordinates { get; } public SyncedEntry<bool> SpawnAtStart { get; } public SyncedEntry<Vector3> FixedSpawnCoordinates { get; } public SyncedEntry<bool> NoSkillProtection { get; } public SyncedEntry<bool> ReduceSkills { get; } public SyncedEntry<float> SkillReduceFactor { get; } public ModConfig(ConfigFile config) { //IL_02da: Unknown result type (might be due to invalid IL or missing references) //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Expected O, but got Unknown _config = config; string text = string.Join(", ", ItemTypeNames); Sync = new ConfigSync(config, "muindor.DeathTweaks", "Death Tweaks", "1.1.0", Plugin.Log); Enabled = Sync.Bind("General", "Enabled", defaultValue: true, "Enable this mod."); IsDebug = config.Bind<bool>("General", "IsDebug", false, "Log every item decision on death."); KeepItemTypes = Sync.Bind("ItemLists", "KeepItemTypes", "", "Item types to keep (comma-separated). Valid types: " + text); DropItemTypes = Sync.Bind("ItemLists", "DropItemTypes", "", "Item types to drop even when KeepTeleportableItems would keep them (comma-separated)."); DestroyItemTypes = Sync.Bind("ItemLists", "DestroyItemTypes", "", "Item types to destroy (comma-separated). Overrides the keep and drop lists."); KeepItemNames = Sync.Bind("ItemLists", "KeepItems", "", "Items to keep (comma-separated). Use prefab names, for example: Iron,IronScrap,CopperOre"); DropItemNames = Sync.Bind("ItemLists", "DropItems", "", "Items to drop even when KeepTeleportableItems would keep them (comma-separated prefab names)."); DestroyItemNames = Sync.Bind("ItemLists", "DestroyItems", "", "Items to destroy (comma-separated prefab names). Overrides the keep and drop lists."); KeepAllItems = Sync.Bind("Toggles", "KeepAllItems", defaultValue: false, "Keep everything. Overrides all other item options."); DestroyAllItems = Sync.Bind("Toggles", "DestroyAllItems", defaultValue: false, "Destroy everything except quest items. Overrides all other item options except KeepAllItems."); KeepEquippedItems = Sync.Bind("Toggles", "KeepEquippedItems", defaultValue: false, "Keep equipped items. Overrides the item lists."); KeepTeleportableItems = Sync.Bind("Toggles", "KeepTeleportableItems", defaultValue: false, "Keep items that can go through portals. Does not override the item lists."); KeepHotbarItems = Sync.Bind("Toggles", "KeepHotbarItems", defaultValue: false, "Keep items in the first inventory row. Overrides the item lists."); KeepQuickSlotItems = Sync.Bind("Toggles", "KeepQuickSlotItems", defaultValue: false, "Keep items in quick slots. Overrides the item lists. EquipmentAndQuickSlots 3.x: quick slots. Extra Slots: quick, food, ammo and misc slots."); UseTombStone = Sync.Bind("Toggles", "UseTombStone", defaultValue: true, "Put dropped items in a tombstone. When false they are scattered on the ground."); CreateDeathEffects = Sync.Bind("Toggles", "CreateDeathEffects", defaultValue: true, "Create the death effects (ragdoll and particles)."); KeepFoodLevels = Sync.Bind("Toggles", "KeepFoodLevels", defaultValue: false, "Keep active food after respawn."); UseFixedSpawnCoordinates = Sync.Bind("Spawn", "UseFixedSpawnCoordinates", defaultValue: false, "Respawn at FixedSpawnCoordinates after death."); SpawnAtStart = Sync.Bind("Spawn", "SpawnAtStart", defaultValue: false, "Respawn at the start location after death. Takes precedence over UseFixedSpawnCoordinates."); FixedSpawnCoordinates = Sync.Bind<Vector3>("Spawn", "FixedSpawnCoordinates", Vector3.zero, "World coordinates used when UseFixedSpawnCoordinates is on."); NoSkillProtection = Sync.Bind("Skills", "NoSkillProtection", defaultValue: false, "Disable the skill-loss protection that normally follows a recent death."); ReduceSkills = Sync.Bind("Skills", "ReduceSkills", defaultValue: true, "Lower skills on death. When false, skills are never lowered or reset, whatever the world modifiers say."); SkillReduceFactor = Sync.Bind("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.1.0")] [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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Settings = new ModConfig(((BaseUnityPlugin)this).Config); ConfigChannel.Activate(Settings.Sync, Log); _harmony = new Harmony("muindor.DeathTweaks"); _harmony.PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)"Death Tweaks 1.1.0 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.1.0"; } } 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.1.0: " + (Plugin.Enabled ? "enabled" : "disabled") + ", " + Plugin.Settings.Sync.Describe()); 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, " + Plugin.Settings.Sync.Describe()); } } } [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) { } } }