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 RunicInteraction v1.0.11
plugins/RunicInteraction/RunicInteraction.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Runic.Localization; using RunicInteraction.Core; using RunicInteraction.Integration; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Runic Interaction")] [assembly: AssemblyDescription("Small, reversible Valheim interaction conveniences with vanilla outcomes.")] [assembly: AssemblyCompany("Chazman")] [assembly: AssemblyProduct("Runic Interaction")] [assembly: AssemblyCopyright("Copyright © 2026 Chazman")] [assembly: ComVisible(false)] [assembly: Guid("37b6d7d9-ad61-4e8a-9b3b-4d64586e47d0")] [assembly: AssemblyFileVersion("1.0.10.0")] [assembly: AssemblyInformationalVersion("1.0.10")] [assembly: InternalsVisibleTo("RunicInteraction.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.0.10.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Runic.Localization { internal sealed class LanguageCatalog { private readonly Dictionary<string, string> _english; private Dictionary<string, string> _selected = new Dictionary<string, string>(); private readonly Func<string, string> _read; private string _language; internal LanguageCatalog(string english, Func<string, string> read) { _english = Parse(english); _read = read; } internal static Dictionary<string, string> Parse(string json) { if (string.IsNullOrWhiteSpace(json)) { return new Dictionary<string, string>(); } if (json.Length > 2097152) { throw new InvalidDataException("Language file exceeds 2 MiB."); } DataContractJsonSerializer dataContractJsonSerializer = new DataContractJsonSerializer(typeof(Dictionary<string, string>), new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }); using MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json.TrimStart('\ufeff'))); return ((Dictionary<string, string>)dataContractJsonSerializer.ReadObject(stream)) ?? new Dictionary<string, string>(); } internal void Select(string language) { language = (string.IsNullOrWhiteSpace(language) ? "English" : language); if (string.Equals(language, _language, StringComparison.Ordinal)) { return; } _language = language; _selected = new Dictionary<string, string>(); if (language.IndexOfAny(new char[4] { '/', '\\', ':', '\0' }) >= 0 || language == "." || language == "..") { return; } try { _selected = Parse(_read(language)); } catch (Exception) { } } internal string Get(string key) { _english.TryGetValue(key, out var value); if (_selected.TryGetValue(key, out var value2) && !string.IsNullOrWhiteSpace(value2) && (value == null || SameFields(value, value2))) { return value2; } return value ?? key; } internal string English(string key) { if (!_english.TryGetValue(key, out var value)) { return key; } return value; } internal string TranslateEnglish(string text) { foreach (KeyValuePair<string, string> item in _english) { if (item.Value == text) { return Get(item.Key); } } return text; } internal string Format(string key, params object[] args) { _english.TryGetValue(key, out var value); string text = Get(key); if (value != null && !SameFields(value, text)) { text = value; } try { return string.Format(CultureInfo.CurrentCulture, text, args); } catch (FormatException) { try { return string.Format(CultureInfo.CurrentCulture, value ?? key, args); } catch (FormatException) { return value ?? key; } } } private static bool SameFields(string a, string b) { try { return Fields(a).SetEquals(Fields(b)); } catch (FormatException) { return false; } } private static HashSet<int> Fields(string text) { HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < text.Length; i++) { if (text[i] == '}') { if (i + 1 >= text.Length || text[i + 1] != '}') { throw new FormatException(); } i++; } else { if (text[i] != '{') { continue; } if (i + 1 < text.Length && text[i + 1] == '{') { i++; continue; } int num = ++i; for (; i < text.Length && char.IsDigit(text[i]); i++) { } if (num == i || !int.TryParse(text.Substring(num, i - num), out var result)) { throw new FormatException(); } hashSet.Add(result); for (; i < text.Length && text[i] != '}'; i++) { if (text[i] == '{') { throw new FormatException(); } } if (i == text.Length) { throw new FormatException(); } } } return hashSet; } } internal static class RunicText { private static readonly Assembly Owner = typeof(RunicText).Assembly; private static readonly string Module = Owner.GetName().Name; private static readonly LanguageCatalog Catalog = Create(); private static LanguageCatalog Create() { using Stream stream = Owner.GetManifestResourceStream("Runic.Localization.English.json"); using StreamReader streamReader = ((stream == null) ? null : new StreamReader(stream)); return new LanguageCatalog(streamReader?.ReadToEnd() ?? "{}", ReadLanguage); } private static string ReadLanguage(string language) { string path = Path.Combine(Path.Combine(Path.GetDirectoryName(Owner.Location), "Translations", Module), language + ".json"); if (!File.Exists(path)) { return null; } return File.ReadAllText(path); } private static void SelectLanguage() { string language = "English"; try { Localization instance = Localization.instance; language = ((instance != null) ? instance.GetSelectedLanguage() : null) ?? "English"; } catch { } Catalog.Select(language); } internal static string Get(string key) { SelectLanguage(); return Catalog.Get(key); } internal static string English(string key) { return Catalog.English(key); } internal static string TranslateEnglish(string text) { SelectLanguage(); return Catalog.TranslateEnglish(text); } internal static string Format(string key, params object[] args) { SelectLanguage(); return Catalog.Format(key, args); } } } namespace RunicInteraction { internal static class InteractionConfig { internal static ConfigEntry<bool> Enabled { get; private set; } internal static ConfigEntry<bool> HoldToRepeat { get; private set; } internal static ConfigEntry<bool> TransferGestures { get; private set; } internal static ConfigEntry<bool> DragTransfer { get; private set; } internal static ConfigEntry<bool> AutoCloseDoors { get; private set; } internal static ConfigEntry<bool> EquipmentRestore { get; private set; } internal static ConfigEntry<bool> MenuMemory { get; private set; } internal static ConfigEntry<bool> TextEntryPolish { get; private set; } internal static ConfigEntry<bool> PickupFilters { get; private set; } internal static ConfigEntry<float> DoorDelaySeconds { get; private set; } internal static ConfigEntry<float> DoorRecentUseSeconds { get; private set; } internal static ConfigEntry<float> DoorObstructionRadius { get; private set; } internal static ConfigEntry<int> PortalTextLimit { get; private set; } internal static ConfigEntry<int> SignTextLimit { get; private set; } internal static ConfigEntry<int> TameTextLimit { get; private set; } internal static ConfigEntry<float> TextCommitRange { get; private set; } internal static ConfigEntry<string> FilteredPickupItems { get; private set; } internal static ConfigEntry<bool> FilterQuestItems { get; private set; } internal static ConfigEntry<string> PickupBypassControllerModifier { get; private set; } internal static ConfigEntry<bool> VerboseLogging { get; private set; } internal static event Action<ConfigDefinition> Changed; internal static void Bind(ConfigFile config) { //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Expected O, but got Unknown //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected O, but got Unknown //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Expected O, but got Unknown //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Expected O, but got Unknown //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Expected O, but got Unknown //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Expected O, but got Unknown //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Expected O, but got Unknown //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Expected O, but got Unknown Enabled = Bind(config, "General", "Enabled", value: true, RunicText.Get("text_969a2758a6e8")); HoldToRepeat = Bind(config, "Features", "HoldToRepeat", value: true, RunicText.Get("text_1c5b71cb56c5")); TransferGestures = Bind(config, "Features", "TransferGestures", value: true, RunicText.Get("text_d349ca13adb4")); DragTransfer = Bind(config, "Features", "DragTransfer", value: false, RunicText.Get("text_1caa13a20179")); AutoCloseDoors = Bind(config, "Features", "AutoCloseDoors", value: false, RunicText.Get("text_15ff80582fc6")); EquipmentRestore = Bind(config, "Features", "EquipmentRestore", value: true, RunicText.Get("text_da01cc78d2db")); MenuMemory = Bind(config, "Features", "MenuMemory", value: true, RunicText.Get("text_004aab945c86")); TextEntryPolish = Bind(config, "Features", "TextEntryPolish", value: true, RunicText.Get("text_57eb2c40e975")); PickupFilters = Bind(config, "Features", "PickupFilters", value: true, RunicText.Get("text_c59edfb36499")); DoorDelaySeconds = Bind(config, "Door Auto-Close", "DelaySeconds", 4f, new ConfigDescription(RunicText.Get("text_c31d1b556390"), (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>())); DoorRecentUseSeconds = Bind(config, "Door Auto-Close", "RecentUseSafetySeconds", 1.5f, new ConfigDescription(RunicText.Get("text_01d5f8ff074b"), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>())); DoorObstructionRadius = Bind(config, "Door Auto-Close", "ObstructionRadiusMeters", 0.9f, new ConfigDescription(RunicText.Get("text_621cf88ff9fd"), (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.35f, 2f), Array.Empty<object>())); PortalTextLimit = Bind(config, "Text Entry", "PortalCharacterLimit", 10, new ConfigDescription(RunicText.Get("text_80bdb7a5e105"), (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())); SignTextLimit = Bind(config, "Text Entry", "SignCharacterLimit", 50, new ConfigDescription(RunicText.Get("text_efb7940f6a02"), (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 200), Array.Empty<object>())); TameTextLimit = Bind(config, "Text Entry", "TameCharacterLimit", 10, new ConfigDescription(RunicText.Get("text_1d1774e82937"), (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())); TextCommitRange = Bind(config, "Text Entry", "CommitRangeMeters", 6f, new ConfigDescription(RunicText.Get("text_23091f7a28ae"), (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 15f), Array.Empty<object>())); FilteredPickupItems = Bind(config, "Pickup Filter", "Items", string.Empty, RunicText.Get("text_70899a5a16d4")); FilterQuestItems = Bind(config, "Pickup Filter", "AllowQuestItemFiltering", value: false, RunicText.Get("text_79f444c62fdd")); PickupBypassControllerModifier = Bind(config, "Controller", "PickupBypassModifierAction", "JoyRStick", new ConfigDescription(RunicText.Get("text_53992ef4d815"), (AcceptableValueBase)(object)new AcceptableValueList<string>(InteractionInputBindings.CreateControllerModifierOptions()), Array.Empty<object>())); VerboseLogging = Bind(config, "Diagnostics", "VerboseLogging", value: false, RunicText.Get("text_e4ef5138e21c")); config.SettingChanged += delegate(object _, SettingChangedEventArgs arguments) { Action<ConfigDefinition> changed = InteractionConfig.Changed; if (changed != null) { object obj; if (arguments == null) { obj = null; } else { ConfigEntryBase changedSetting = arguments.ChangedSetting; obj = ((changedSetting != null) ? changedSetting.Definition : null); } changed((ConfigDefinition)obj); } }; } private static ConfigEntry<T> Bind<T>(ConfigFile file, string section, string key, T value, string description) { return file.Bind<T>(section, key, value, description); } private static ConfigEntry<T> Bind<T>(ConfigFile file, string section, string key, T value, ConfigDescription description) { return file.Bind<T>(section, key, value, description); } } internal static class Diagnostics { private static ManualLogSource _log; internal static void Initialize(ManualLogSource log) { _log = log; } internal static void Info(string message) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)message); } } internal static void Warn(string message) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)message); } } internal static void Error(string message) { ManualLogSource log = _log; if (log != null) { log.LogError((object)message); } } internal static void Error(Exception exception, string message) { ManualLogSource log = _log; if (log != null) { log.LogError((object)(message + " " + exception.GetType().Name + ": " + exception.Message)); } } internal static void Trace(string message) { if (InteractionConfig.VerboseLogging != null && InteractionConfig.VerboseLogging.Value) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)message); } } } } [BepInPlugin("chazman.RunicInteraction", "Runic Interaction", "1.0.10")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicInteraction"; public const string Name = "Runic Interaction"; public const string Version = "1.0.10"; public const string ModuleId = "runic.interaction"; private readonly List<KeybindingDescriptor> _keybindings = new List<KeybindingDescriptor>(); private Harmony _harmony; private InteractionRuntime _runtime; private bool _configurationSubscribed; private bool _shuttingDown; internal static bool RuntimeReady { get; private set; } private void Awake() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown Diagnostics.Initialize(((BaseUnityPlugin)this).Logger); InteractionConfig.Bind(((BaseUnityPlugin)this).Config); InteractionConfig.Changed += OnConfigurationChanged; _configurationSubscribed = true; try { if (!ValheimAccess.Initialize(out var problem)) { throw new MissingMethodException(problem); } _runtime = new InteractionRuntime(); _runtime.Initialize(); _harmony = new Harmony("chazman.RunicInteraction"); _harmony.PatchAll(typeof(Plugin).Assembly); DoorAutoCloseRuntime.Initialize(); RegisterKeybindings(); RuntimeReady = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Runic Interaction v1.0.10 ready for Valheim " + ValheimAccess.ReadGameVersion() + ". Hold-repeat uses Valheim's 0.2 s cadence and every repeated item still passes the original station callback.")); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Door auto-close is off by default. Drag-sweep and generic filter memory are explicit disabled gates; ordinary vanilla dragging and menu behavior remain available."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Runic Interaction startup failed closed; all interactions remain vanilla. " + ex.GetType().Name + ": " + ex.Message)); ShutdownRuntime(); } } private void Update() { if (!RuntimeReady || _runtime == null) { return; } try { _runtime.Tick(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Runic Interaction runtime faulted and was disabled for this session; vanilla behavior was restored. " + ex.GetType().Name + ": " + ex.Message)); ShutdownRuntime(); } } private void OnConfigurationChanged(ConfigDefinition definition) { if (!RuntimeReady || _runtime == null) { return; } try { if (definition == (ConfigDefinition)null || (definition.Section == "Controller" && definition.Key == "PickupBypassModifierAction")) { RefreshKeybindings(); } _runtime.OnConfigurationChanged(); Diagnostics.Info("Interaction configuration refreshed; no restart is required."); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_5d38524af925")); } } private void OnDestroy() { ShutdownRuntime(); Diagnostics.Initialize(null); } private void RegisterKeybindings() { ReplaceKeybindings(InteractionInputBindings.CreateDescriptors(InteractionConfig.PickupBypassControllerModifier.Value)); } private void RefreshKeybindings() { IReadOnlyList<KeybindingDescriptor> readOnlyList = InteractionInputBindings.CreateDescriptors(InteractionConfig.PickupBypassControllerModifier.Value); if (!BindingsMatch(readOnlyList)) { ReplaceKeybindings(readOnlyList); } } private bool BindingsMatch(IReadOnlyList<KeybindingDescriptor> candidates) { if (candidates == null || candidates.Count != _keybindings.Count) { return false; } for (int i = 0; i < candidates.Count; i++) { KeybindingDescriptor keybindingDescriptor = _keybindings[i]; KeybindingDescriptor keybindingDescriptor2 = candidates[i]; if (!string.Equals(keybindingDescriptor.QualifiedId, keybindingDescriptor2.QualifiedId, StringComparison.Ordinal) || !string.Equals(keybindingDescriptor.Context, keybindingDescriptor2.Context, StringComparison.Ordinal) || !keybindingDescriptor.Chord.Equals(keybindingDescriptor2.Chord)) { return false; } } return true; } private void ReplaceKeybindings(IReadOnlyList<KeybindingDescriptor> replacements) { if (replacements == null || replacements.Count != 6) { throw new InvalidOperationException("Interaction keybinding catalog is incomplete."); } List<KeybindingDescriptor> list = new List<KeybindingDescriptor>(replacements.Count); for (int i = 0; i < replacements.Count; i++) { list.Add(replacements[i] ?? throw new InvalidOperationException("Interaction keybinding catalog contains an empty entry.")); } _keybindings.Clear(); _keybindings.AddRange(list); } private void DisposeKeybindings() { _keybindings.Clear(); } private void ShutdownRuntime() { if (_shuttingDown) { return; } _shuttingDown = true; RuntimeReady = false; if (_configurationSubscribed) { InteractionConfig.Changed -= OnConfigurationChanged; _configurationSubscribed = false; } try { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } catch (Exception ex) { Diagnostics.Warn("Could not remove every interaction patch: " + ex.Message); } _harmony = null; try { _runtime?.Shutdown(); } catch (Exception ex2) { Diagnostics.Warn("Runtime cleanup was incomplete: " + ex2.Message); } _runtime = null; DisposeKeybindings(); _shuttingDown = false; } } } namespace RunicInteraction.Integration { internal static class DoorAutoCloseRuntime { internal readonly struct DoorOpenCapture { internal Door Door { get; } internal Player Actor { get; } internal ZDOID Id { get; } internal uint Revision { get; } internal int PrefabHash { get; } internal bool Eligible { get { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)Door) && Object.op_Implicit((Object)(object)Actor)) { ZDOID id = Id; return !((ZDOID)(ref id)).IsNone(); } return false; } } internal DoorOpenCapture(Door door, Player actor, ZDOID id, uint revision, int prefabHash) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) Door = door; Actor = actor; Id = id; Revision = revision; PrefabHash = prefabHash; } } private sealed class PendingDoorTimer { internal Door Door; internal Player Actor; internal ZDOID Id; internal int ExpectedPrefabHash; internal int OpenState; internal float DueRealtime; internal float ExpiresRealtime; } private const int MaximumTimers = 64; private const int TickBudget = 8; private const float MaximumLifetimeSeconds = 70f; private static readonly List<PendingDoorTimer> Timers = new List<PendingDoorTimer>(64); private static readonly Collider[] Obstructions = (Collider[])(object)new Collider[32]; private static int _cursor; private static bool _closing; internal static void Initialize() { Shutdown(); } internal static void ObserveState(Door door) { //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (_closing || !FeatureOn() || !Object.op_Implicit((Object)(object)door) || !Object.op_Implicit((Object)(object)localPlayer) || !((Character)localPlayer).IsOwner() || ((Character)localPlayer).IsDead() || !IsPlayerBuilt(door)) { return; } ZNetView component = ((Component)door).GetComponent<ZNetView>(); ZDO val = ((Object.op_Implicit((Object)(object)component) && component.IsValid()) ? component.GetZDO() : null); if (val == null || !val.IsValid() || ((ZDOID)(ref val.m_uid)).IsNone()) { return; } int num = val.GetInt(ZDOVars.s_state, 0); for (int i = 0; i < Timers.Count; i++) { if (!(Timers[i].Id != val.m_uid)) { if (num == 0) { Timers.RemoveAt(i); } else if (Timers[i].OpenState != num) { Timers[i].OpenState = num; Timers[i].DueRealtime = Time.realtimeSinceStartup + EffectiveDelaySeconds(); } return; } } if (num != 0 && !door.m_canNotBeClosed && !Object.op_Implicit((Object)(object)door.m_keyItem) && Timers.Count < 64 && PrivateArea.CheckAccess(((Component)door).transform.position, 0f, false, false)) { PendingDoorTimer pendingDoorTimer = NewTimer(new DoorOpenCapture(door, localPlayer, val.m_uid, val.DataRevision, val.GetPrefab()), Time.realtimeSinceStartup); pendingDoorTimer.OpenState = num; Timers.Add(pendingDoorTimer); } } internal static DoorOpenCapture BeforeInteract(Door door, Humanoid character, bool hold) { //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if (!(!FeatureOn() || !Object.op_Implicit((Object)(object)door) || hold)) { Player val = (Player)(object)((character is Player) ? character : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && ((Character)val).IsOwner() && !door.m_canNotBeClosed && !Object.op_Implicit((Object)(object)door.m_keyItem) && IsPlayerBuilt(door)) { ZNetView component = ((Component)door).GetComponent<ZNetView>(); ZDO val2 = ((Object.op_Implicit((Object)(object)component) && component.IsValid()) ? component.GetZDO() : null); if (val2 == null || !val2.IsValid() || ((ZDOID)(ref val2.m_uid)).IsNone() || val2.GetInt(ZDOVars.s_state, 0) != 0) { return default(DoorOpenCapture); } return new DoorOpenCapture(door, val, val2.m_uid, val2.DataRevision, val2.GetPrefab()); } } return default(DoorOpenCapture); } internal static void AfterInteract(DoorOpenCapture capture, bool vanillaAccepted) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (!capture.Eligible || !vanillaAccepted || !FeatureOn() || !IsPlayerBuilt(capture.Door)) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; for (int i = 0; i < Timers.Count; i++) { if (!(Timers[i].Id != capture.Id)) { Timers[i] = NewTimer(capture, realtimeSinceStartup); return; } } if (Timers.Count >= 64) { Diagnostics.Warn("Door auto-close reached its bounded timer capacity; this door remains open."); } else { Timers.Add(NewTimer(capture, realtimeSinceStartup)); } } internal static void Tick() { if (!FeatureOn()) { Shutdown(); return; } int num = Math.Min(8, Timers.Count); for (int i = 0; i < num; i++) { if (Timers.Count <= 0) { break; } if (_cursor >= Timers.Count) { _cursor = 0; } int cursor = _cursor; if (TickOne(Timers[cursor])) { Timers.RemoveAt(cursor); if (_cursor >= Timers.Count) { _cursor = 0; } } else { _cursor++; } } } internal static void OnConfigurationChanged() { if (!FeatureOn()) { Shutdown(); } } internal static void ObserveWardTopologyMutation(ZDO zdo) { } internal static void Shutdown() { Timers.Clear(); _cursor = 0; } private static PendingDoorTimer NewTimer(DoorOpenCapture capture, float now) { //IL_0022: 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) return new PendingDoorTimer { Door = capture.Door, Actor = capture.Actor, Id = capture.Id, ExpectedPrefabHash = capture.PrefabHash, DueRealtime = now + EffectiveDelaySeconds(), ExpiresRealtime = now + 70f }; } private static bool TickOne(PendingDoorTimer timer) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) float realtimeSinceStartup = Time.realtimeSinceStartup; if (timer == null || realtimeSinceStartup >= timer.ExpiresRealtime || !Object.op_Implicit((Object)(object)timer.Door) || !Object.op_Implicit((Object)(object)timer.Actor)) { return true; } if ((Object)(object)timer.Actor != (Object)(object)Player.m_localPlayer || !((Character)timer.Actor).IsOwner() || ((Character)timer.Actor).IsDead() || !IsPlayerBuilt(timer.Door)) { return true; } ZNetView component = ((Component)timer.Door).GetComponent<ZNetView>(); ZDO val = ((Object.op_Implicit((Object)(object)component) && component.IsValid()) ? component.GetZDO() : null); if (val == null || !val.IsValid() || val.m_uid != timer.Id || val.GetPrefab() != timer.ExpectedPrefabHash || val.GetInt(ZDOVars.s_state, 0) == 0) { return true; } if (realtimeSinceStartup < timer.DueRealtime) { return false; } if (timer.Door.m_canNotBeClosed || Object.op_Implicit((Object)(object)timer.Door.m_keyItem)) { return true; } if (!ValheimAccess.DoorCanInteract(timer.Door)) { return false; } if (!PrivateArea.CheckAccess(((Component)timer.Door).transform.position, 0f, false, false)) { return true; } if (IsObstructed(timer.Door)) { timer.DueRealtime = realtimeSinceStartup + 0.5f; return false; } if (!component.IsOwner()) { component.ClaimOwnership(); timer.DueRealtime = realtimeSinceStartup + 0.25f; return false; } try { _closing = true; ValheimAccess.CloseDoor(timer.Door, ZNet.GetUID()); return val.GetInt(ZDOVars.s_state, 0) == 0; } catch (Exception ex) { Diagnostics.Warn("Door auto-close stopped: " + ex.GetType().Name + "."); return true; } finally { _closing = false; } } private static bool IsPlayerBuilt(Door door) { Piece component = ((Component)door).GetComponent<Piece>(); if (Object.op_Implicit((Object)(object)component)) { return component.IsPlacedByPlayer(); } return false; } private static bool IsObstructed(Door door) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) int num = Physics.OverlapSphereNonAlloc(((Component)door).transform.position + Vector3.up, InteractionConfig.DoorObstructionRadius.Value, Obstructions, -1, (QueryTriggerInteraction)1); bool result = num >= Obstructions.Length; for (int i = 0; i < Math.Min(num, Obstructions.Length); i++) { Collider val = Obstructions[i]; Obstructions[i] = null; if (Object.op_Implicit((Object)(object)val) && !((Component)val).transform.IsChildOf(((Component)door).transform)) { Rigidbody attachedRigidbody = val.attachedRigidbody; if (Object.op_Implicit((Object)(object)((Component)val).GetComponentInParent<Character>()) || (Object.op_Implicit((Object)(object)attachedRigidbody) && !attachedRigidbody.isKinematic)) { return true; } } } return result; } private static bool FeatureOn() { ConfigEntry<bool> enabled = InteractionConfig.Enabled; if (enabled != null && enabled.Value) { return InteractionConfig.AutoCloseDoors?.Value ?? false; } return false; } private static float EffectiveDelaySeconds() { return Mathf.Max(Mathf.Clamp(InteractionConfig.DoorDelaySeconds.Value, 1f, 60f), Mathf.Clamp(InteractionConfig.DoorRecentUseSeconds.Value, 0.5f, 10f)); } } [HarmonyPatch(typeof(Door), "UpdateState", new Type[] { })] internal static class DoorStateAutoClosePatch { private static void Postfix(Door __instance) { try { DoorAutoCloseRuntime.ObserveState(__instance); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_99c4c74cd304")); } } } internal static class EquipmentRestoreRuntime { internal readonly struct EquipCapture { internal bool Local { get; } internal bool Tool { get; } internal ItemData Right { get; } internal ItemData Left { get; } internal EquipCapture(bool local, bool tool, ItemData right, ItemData left) { Local = local; Tool = tool; Right = right; Left = left; } } private static ItemData _right; private static ItemData _left; private static ItemData _tool; private static int _dueFrame = -1; private static ItemData _swimRight; private static ItemData _swimLeft; private static int _swimDueFrame = -1; private static bool _restoring; internal static void BeforeHideHands(Humanoid actor, bool onlyRightHand) { if (!(!FeatureOn() || onlyRightHand) && !_restoring && Object.op_Implicit((Object)(object)actor) && !((Object)(object)actor != (Object)(object)Player.m_localPlayer)) { Player val = (Player)(object)((actor is Player) ? actor : null); if (Object.op_Implicit((Object)(object)val) && ((Character)val).IsSwimming() && !((Character)val).IsOnGround()) { _swimRight = (IsCombatHand(actor.RightItem) ? actor.RightItem : null); _swimLeft = (IsCombatHand(actor.LeftItem) ? actor.LeftItem : null); _swimDueFrame = -1; } } } internal static void AfterShowHands(Humanoid actor, bool onlyRightHand) { if (!(!FeatureOn() || onlyRightHand) && !_restoring && Object.op_Implicit((Object)(object)actor) && !((Object)(object)actor != (Object)(object)Player.m_localPlayer) && (_swimRight != null || _swimLeft != null)) { if (SwimCandidatesRestored(actor)) { ClearSwim(); } else { _swimDueFrame = Time.frameCount + 1; } } } internal static EquipCapture BeforeEquip(Humanoid actor, ItemData item) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 bool flag = Object.op_Implicit((Object)(object)actor) && (Object)(object)actor == (Object)(object)Player.m_localPlayer; bool flag2 = item != null && (int)item.m_shared.m_itemType == 19; if (!flag || !flag2 || _restoring) { return new EquipCapture(flag, flag2, null, null); } return new EquipCapture(local: true, tool: true, IsCombatHand(actor.RightItem) ? actor.RightItem : null, IsCombatHand(actor.LeftItem) ? actor.LeftItem : null); } internal static void AfterEquip(Humanoid actor, ItemData item, bool result, EquipCapture capture) { if (!result || !capture.Local || _restoring || item == null) { return; } if (capture.Tool) { _tool = item; _dueFrame = -1; if (capture.Right != null || capture.Left != null) { _right = capture.Right; _left = capture.Left; } } else if (IsCombatHand(item)) { Clear(); } } internal static void AfterUnequip(Humanoid actor, ItemData item) { if (!_restoring && Object.op_Implicit((Object)(object)actor) && !((Object)(object)actor != (Object)(object)Player.m_localPlayer) && item != null && item == _tool) { if (_right == null && _left == null) { Clear(); } else { _dueFrame = Time.frameCount + 1; } } } internal static void Tick() { Player localPlayer = Player.m_localPlayer; if (!FeatureOn() || !Object.op_Implicit((Object)(object)localPlayer)) { Clear(); return; } ProcessSwimRestore(localPlayer); if (_tool != null && !((Humanoid)localPlayer).GetInventory().ContainsItem(_tool)) { _tool = null; if (_right != null || _left != null) { _dueFrame = Time.frameCount + 1; } } if (_dueFrame < 0 || Time.frameCount < _dueFrame) { return; } bool temporaryToolStillEquipped = _tool != null && ((Humanoid)localPlayer).IsItemEquiped(_tool); bool flag = HasUnrelatedCombat((Humanoid)(object)localPlayer, _right, _left); if (!EquipmentRestorePolicy.MayRestore(enabled: true, (Object)(object)localPlayer == (Object)(object)Player.m_localPlayer, !((Character)localPlayer).IsDead(), ((Character)localPlayer).IsSwimming() && !((Character)localPlayer).IsOnGround(), ((Character)localPlayer).InAttack(), ((Character)localPlayer).InDodge(), temporaryToolStillEquipped, flag)) { if (flag) { Clear(); } else { _dueFrame = Time.frameCount + 1; } return; } ItemData val = ((((Humanoid)localPlayer).RightItem == _right || ((Humanoid)localPlayer).LeftItem == _right) ? null : (LegalCandidate(localPlayer, _right) ? _right : null)); ItemData val2 = ((((Humanoid)localPlayer).RightItem == _left || ((Humanoid)localPlayer).LeftItem == _left) ? null : (LegalCandidate(localPlayer, _left) ? _left : null)); Clear(); if (val == null && val2 == null) { return; } _restoring = true; try { if (val != null) { ((Humanoid)localPlayer).EquipItem(val, true); } if (val2 != null) { ((Humanoid)localPlayer).EquipItem(val2, true); } } finally { _restoring = false; } } internal static void OnConfigurationChanged() { if (!FeatureOn()) { Clear(); } } internal static void Shutdown() { Clear(); } private static bool LegalCandidate(Player player, ItemData item) { if (item != null && ((Humanoid)player).GetInventory().ContainsItem(item) && IsCombatHand(item)) { if (item.m_shared.m_useDurability) { return item.m_durability > 0f; } return true; } return false; } private static void ProcessSwimRestore(Player player) { if (_swimDueFrame < 0 || Time.frameCount < _swimDueFrame) { return; } if (SwimCandidatesRestored((Humanoid)(object)player)) { ClearSwim(); return; } bool flag = HasUnrelatedCombat((Humanoid)(object)player, _swimRight, _swimLeft); if (!EquipmentRestorePolicy.MayRestore(enabled: true, (Object)(object)player == (Object)(object)Player.m_localPlayer, !((Character)player).IsDead(), ((Character)player).IsSwimming() && !((Character)player).IsOnGround(), ((Character)player).InAttack(), ((Character)player).InDodge(), temporaryToolStillEquipped: false, flag)) { if (flag) { ClearSwim(); } else { _swimDueFrame = Time.frameCount + 1; } return; } ItemData val = ((((Humanoid)player).RightItem == _swimRight) ? null : (LegalCandidate(player, _swimRight) ? _swimRight : null)); ItemData val2 = ((((Humanoid)player).LeftItem == _swimLeft) ? null : (LegalCandidate(player, _swimLeft) ? _swimLeft : null)); ClearSwim(); if (val == null && val2 == null) { return; } _restoring = true; try { if (val != null) { ((Humanoid)player).EquipItem(val, true); } if (val2 != null) { ((Humanoid)player).EquipItem(val2, true); } } finally { _restoring = false; } } private static bool SwimCandidatesRestored(Humanoid actor) { if (_swimRight == null || actor.RightItem == _swimRight) { if (_swimLeft != null) { return actor.LeftItem == _swimLeft; } return true; } return false; } private static bool HasUnrelatedCombat(Humanoid actor, ItemData storedRight, ItemData storedLeft) { if (!IsCombatHand(actor.RightItem) || actor.RightItem == storedRight || actor.RightItem == storedLeft) { if (IsCombatHand(actor.LeftItem) && actor.LeftItem != storedRight) { return actor.LeftItem != storedLeft; } return false; } return true; } private static bool IsCombatHand(ItemData item) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 if (item == null) { return false; } ItemType itemType = item.m_shared.m_itemType; if (itemType - 3 <= 2 || (int)itemType == 14 || (int)itemType == 22) { return true; } return false; } private static void Clear() { _right = null; _left = null; _tool = null; _dueFrame = -1; ClearSwim(); } private static void ClearSwim() { _swimRight = null; _swimLeft = null; _swimDueFrame = -1; } private static bool FeatureOn() { if (InteractionConfig.Enabled.Value) { return InteractionConfig.EquipmentRestore.Value; } return false; } } [HarmonyPatch(typeof(Smelter), "Awake")] internal static class SmelterAwakePatch { private static void Postfix(Smelter __instance) { if (!Plugin.RuntimeReady) { return; } try { HoldRepeatRuntime.Register(__instance.m_addOreSwitch); HoldRepeatRuntime.Register(__instance.m_addWoodSwitch); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_42d8b2177ba1")); } } } [HarmonyPatch(typeof(CookingStation), "Awake")] internal static class CookingStationAwakePatch { private static void Postfix(CookingStation __instance) { if (!Plugin.RuntimeReady) { return; } try { HoldRepeatRuntime.Register(__instance.m_addFoodSwitch); HoldRepeatRuntime.Register(__instance.m_addFuelSwitch); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_d3c1294ddc0b")); } } } [HarmonyPatch(typeof(ShieldGenerator), "Start")] internal static class ShieldGeneratorStartPatch { private static void Postfix(ShieldGenerator __instance) { if (!Plugin.RuntimeReady) { return; } try { HoldRepeatRuntime.Register(__instance.m_addFuelSwitch); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_fb46743df3f5")); } } } [HarmonyPatch(typeof(Switch), "Interact", new Type[] { typeof(Humanoid), typeof(bool), typeof(bool) })] internal static class SwitchInteractPatch { private static void Prefix(Switch __instance, bool __1, out HoldRepeatRuntime.IntervalState __state) { __state = default(HoldRepeatRuntime.IntervalState); if (!Plugin.RuntimeReady) { return; } try { __state = HoldRepeatRuntime.Begin(__instance, __1); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_a66398fb3ecb")); } } private static Exception Finalizer(Switch __instance, HoldRepeatRuntime.IntervalState __state, Exception __exception) { try { HoldRepeatRuntime.End(__instance, __state); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_0f48285fbb18")); } return __exception; } } [HarmonyPatch(typeof(CookingStation), "Interact", new Type[] { typeof(Humanoid), typeof(bool), typeof(bool) })] internal static class CookingStationInteractPatch { private static void Prefix(CookingStation __instance, ref bool __1) { if (!Plugin.RuntimeReady) { return; } try { if (HoldRepeatRuntime.ShouldConvertCookingHold(__instance, __1)) { __1 = false; } } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_d17138c02b22")); } } } [HarmonyPatch(typeof(Fermenter), "Interact", new Type[] { typeof(Humanoid), typeof(bool), typeof(bool) })] internal static class FermenterInteractPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicProduction" })] private static void Prefix(Fermenter __instance, ref bool __1) { if (!Plugin.RuntimeReady) { return; } try { if (HoldRepeatRuntime.ShouldConvertFermenterHold(__instance, __1)) { __1 = false; } } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_8820de8c479e")); } } } [HarmonyPatch(typeof(Door), "Interact", new Type[] { typeof(Humanoid), typeof(bool), typeof(bool) })] internal static class DoorInteractAutoClosePatch { private static void Prefix(Door __instance, Humanoid __0, bool __1, out DoorAutoCloseRuntime.DoorOpenCapture __state) { __state = default(DoorAutoCloseRuntime.DoorOpenCapture); if (!Plugin.RuntimeReady) { return; } try { __state = DoorAutoCloseRuntime.BeforeInteract(__instance, __0, __1); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_aa888bf56a6f")); } } private static void Postfix(bool __result, DoorAutoCloseRuntime.DoorOpenCapture __state) { if (!Plugin.RuntimeReady) { return; } try { DoorAutoCloseRuntime.AfterInteract(__state, __result); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_f4e0e643a03c")); } } } [HarmonyPatch(typeof(ZDOMan), "AddToSector", new Type[] { typeof(ZDO), typeof(SectorIndex) })] internal static class InteractionWardZdoAddedPatch { private static void Postfix(ZDO zdo) { if (!Plugin.RuntimeReady) { return; } try { DoorAutoCloseRuntime.ObserveWardTopologyMutation(zdo); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_1730dd88ea22")); } } } [HarmonyPatch(typeof(ZDOMan), "RemoveFromSector", new Type[] { typeof(ZDO), typeof(SectorIndex) })] internal static class InteractionWardZdoRemovedPatch { private static void Prefix(ZDO zdo) { if (!Plugin.RuntimeReady) { return; } try { DoorAutoCloseRuntime.ObserveWardTopologyMutation(zdo); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_60c31af3a764")); } } } [HarmonyPatch(typeof(InventoryGrid), "OnLeftClick", new Type[] { typeof(UIInputHandler) })] internal static class InventoryGridLeftClickPatch { private static bool Prefix(InventoryGrid __instance, UIInputHandler __0) { if (!Plugin.RuntimeReady) { return true; } try { return !TransferGestureRuntime.TryHandleAltClick(__instance, __0); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_6fddbb3ebab5")); return true; } } } [HarmonyPatch(typeof(InventoryGui), "OnSelectedItem", new Type[] { typeof(InventoryGrid), typeof(ItemData), typeof(Vector2i), typeof(Modifier) })] internal static class InventoryGuiSelectedItemPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicInventory" })] private static bool Prefix(InventoryGrid __0, ItemData __1, Modifier __3) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.RuntimeReady) { return true; } try { return TransferGestureRuntime.GuardVanillaMove(__0, __1, __3); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_d9837f81b1dc")); return false; } } } [HarmonyPatch(typeof(Humanoid), "EquipItem", new Type[] { typeof(ItemData), typeof(bool) })] internal static class HumanoidEquipItemPatch { [HarmonyAfter(new string[] { "chazman.RunicInventory" })] [HarmonyPriority(0)] private static void Prefix(Humanoid __instance, ItemData __0, bool __runOriginal, out EquipmentRestoreRuntime.EquipCapture __state) { __state = default(EquipmentRestoreRuntime.EquipCapture); if (!__runOriginal || !Plugin.RuntimeReady) { return; } try { __state = EquipmentRestoreRuntime.BeforeEquip(__instance, __0); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_4ab8a58ad577")); } } [HarmonyBefore(new string[] { "chazman.RunicInventory" })] [HarmonyPriority(800)] private static void Postfix(Humanoid __instance, ItemData __0, bool __result, EquipmentRestoreRuntime.EquipCapture __state) { if (!Plugin.RuntimeReady) { return; } try { EquipmentRestoreRuntime.AfterEquip(__instance, __0, __result, __state); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_9e9d7ca87ce2")); } } } [HarmonyPatch(typeof(Humanoid), "UnequipItem", new Type[] { typeof(ItemData), typeof(bool) })] internal static class HumanoidUnequipItemPatch { private static void Postfix(Humanoid __instance, ItemData __0) { if (!Plugin.RuntimeReady) { return; } try { EquipmentRestoreRuntime.AfterUnequip(__instance, __0); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_9ca7a3fcbd23")); } } } [HarmonyPatch(typeof(Humanoid), "HideHandItems", new Type[] { typeof(bool), typeof(bool) })] internal static class HumanoidHideHandItemsPatch { private static void Prefix(Humanoid __instance, bool __0) { if (!Plugin.RuntimeReady) { return; } try { EquipmentRestoreRuntime.BeforeHideHands(__instance, __0); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_e6ff5935cebf")); } } } [HarmonyPatch(typeof(Humanoid), "ShowHandItems", new Type[] { typeof(bool), typeof(bool) })] internal static class HumanoidShowHandItemsPatch { private static void Postfix(Humanoid __instance, bool __0) { if (!Plugin.RuntimeReady) { return; } try { EquipmentRestoreRuntime.AfterShowHands(__instance, __0); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_df2167e667d8")); } } } [HarmonyPatch(typeof(InventoryGui), "SetupCrafting")] internal static class InventoryGuiSetupCraftingPatch { private static void Prefix(InventoryGui __instance, out bool __state) { __state = false; if (!Plugin.RuntimeReady) { return; } try { MenuMemoryRuntime.BeginSetup(__instance); __state = true; } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_430319a022f5")); } } private static void Postfix(InventoryGui __instance, bool __state) { if (!Plugin.RuntimeReady || !__state) { return; } try { MenuMemoryRuntime.CompleteSetup(__instance); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_259640619909")); } } } [HarmonyPatch(typeof(InventoryGui), "Hide")] internal static class InventoryGuiHidePatch { private static void Prefix(InventoryGui __instance) { if (!Plugin.RuntimeReady) { return; } try { MenuMemoryRuntime.BeforeHide(__instance); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_ad92c83b16f9")); } } } [HarmonyPatch(typeof(InventoryGui), "OnTabCraftPressed")] internal static class InventoryGuiCraftTabPatch { private static void Prefix(InventoryGui __instance, out bool __state) { __state = false; if (!Plugin.RuntimeReady) { return; } try { MenuMemoryRuntime.BeforeContextChange(__instance); __state = true; } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_ff6fc7caf8d0")); } } private static void Postfix(InventoryGui __instance, bool __state) { if (!Plugin.RuntimeReady || !__state) { return; } try { MenuMemoryRuntime.AfterContextChange(__instance); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_99dc9c90b600")); } } } [HarmonyPatch(typeof(InventoryGui), "OnTabUpgradePressed")] internal static class InventoryGuiUpgradeTabPatch { private static void Prefix(InventoryGui __instance, out bool __state) { __state = false; if (!Plugin.RuntimeReady) { return; } try { MenuMemoryRuntime.BeforeContextChange(__instance); __state = true; } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_c0159cfc3de6")); } } private static void Postfix(InventoryGui __instance, bool __state) { if (!Plugin.RuntimeReady || !__state) { return; } try { MenuMemoryRuntime.AfterContextChange(__instance); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_f3e01a0bbd55")); } } } [HarmonyPatch(typeof(TextInput), "RequestText", new Type[] { typeof(TextReceiver), typeof(string), typeof(int) })] internal static class TextInputRequestPatch { private static void Prefix(TextReceiver __0, ref int __2) { if (!Plugin.RuntimeReady) { return; } try { TextEntryRuntime.Begin(__0, ref __2); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_0ddb738f2976")); } } } [HarmonyPatch(typeof(TextInput), "setText", new Type[] { typeof(string) })] internal static class TextInputCommitPatch { private static bool Prefix(TextInput __instance, string __0) { if (!Plugin.RuntimeReady) { return true; } try { return TextEntryRuntime.ValidateCommit(__instance, __0); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_1867a65d1d62")); try { ValheimAccess.ClearQueuedTextReceiver(__instance); } catch (Exception exception2) { Diagnostics.Error(exception2, RunicText.Get("text_b0f855d94c95")); } return false; } } } [HarmonyPatch(typeof(TextInput), "Hide")] internal static class TextInputHidePatch { private static void Postfix(TextInput __instance) { if (!Plugin.RuntimeReady) { return; } try { TextEntryRuntime.AfterHide(__instance); } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_17a2cf6465cb")); } } } [HarmonyPatch(typeof(Humanoid), "Pickup", new Type[] { typeof(GameObject), typeof(bool), typeof(bool) })] internal static class HumanoidPickupPatch { [HarmonyPriority(0)] [HarmonyAfter(new string[] { "chazman.RunicInventory" })] private static bool Prefix(Humanoid __instance, GameObject __0, ref bool __result) { if (!Plugin.RuntimeReady) { return true; } try { if (PickupFilterRuntime.Allow(__instance, __0)) { return true; } __result = false; return false; } catch (Exception exception) { Diagnostics.Error(exception, RunicText.Get("text_3a8dd69f38c1")); return true; } } } internal static class HoldRepeatRuntime { private sealed class Marker { } internal readonly struct IntervalState { internal bool Changed { get; } internal float Previous { get; } internal IntervalState(bool changed, float previous) { Changed = changed; Previous = previous; } } private static readonly ConditionalWeakTable<Switch, Marker> Inputs = new ConditionalWeakTable<Switch, Marker>(); internal static void Register(Switch input) { if (!Object.op_Implicit((Object)(object)input)) { return; } try { Inputs.GetValue(input, (Switch _) => new Marker()); } catch (ArgumentException) { } } internal static IntervalState Begin(Switch input, bool hold) { if (!hold || !FeatureOn() || !Object.op_Implicit((Object)(object)input) || !Inputs.TryGetValue(input, out var _)) { return default(IntervalState); } float holdRepeatInterval = input.m_holdRepeatInterval; input.m_holdRepeatInterval = float.Epsilon; return new IntervalState(changed: true, holdRepeatInterval); } internal static void End(Switch input, IntervalState state) { if (state.Changed && Object.op_Implicit((Object)(object)input)) { input.m_holdRepeatInterval = state.Previous; } } internal static bool ShouldConvertCookingHold(CookingStation station, bool hold) { if (hold && FeatureOn() && Object.op_Implicit((Object)(object)station) && !Object.op_Implicit((Object)(object)station.m_addFoodSwitch)) { return !ValheimAccess.CookingHasDoneItem(station); } return false; } internal static bool ShouldConvertFermenterHold(Fermenter station, bool hold) { if (!hold || !FeatureOn() || !Object.op_Implicit((Object)(object)station)) { return false; } ZNetView component = ((Component)station).GetComponent<ZNetView>(); ZDO val = (Object.op_Implicit((Object)(object)component) ? component.GetZDO() : null); if (val != null) { return string.IsNullOrEmpty(val.GetString(ZDOVars.s_content, "")); } return false; } private static bool FeatureOn() { if (InteractionConfig.Enabled.Value) { return InteractionConfig.HoldToRepeat.Value; } return false; } } internal sealed class InteractionRuntime { private bool _dragWarning; private bool _filterMemoryWarning; internal void Initialize() { PickupFilterRuntime.Refresh(); OnConfigurationChanged(); } internal void Tick() { DoorAutoCloseRuntime.Tick(); EquipmentRestoreRuntime.Tick(); } internal void OnConfigurationChanged() { PickupFilterRuntime.Refresh(); DoorAutoCloseRuntime.OnConfigurationChanged(); EquipmentRestoreRuntime.OnConfigurationChanged(); MenuMemoryRuntime.OnConfigurationChanged(); TextEntryRuntime.OnConfigurationChanged(); TransferGestureRuntime.OnConfigurationChanged(); if (InteractionConfig.DragTransfer.Value && !_dragWarning) { _dragWarning = true; Diagnostics.Warn("DragTransfer remains disabled: Valheim 1.0.7 exposes no authority-safe drag-sweep transaction boundary. Ordinary vanilla dragging is unchanged."); } if (InteractionConfig.MenuMemory.Value && !_filterMemoryWarning) { _filterMemoryWarning = true; Diagnostics.Info("Menu memory is active for context/category/recipe selection. Filter memory is disabled because Valheim 1.0.7 exposes no stable filter state."); } if (!InteractionConfig.DragTransfer.Value) { _dragWarning = false; } if (!InteractionConfig.MenuMemory.Value) { _filterMemoryWarning = false; } } internal void Shutdown() { DoorAutoCloseRuntime.Shutdown(); EquipmentRestoreRuntime.Shutdown(); MenuMemoryRuntime.Shutdown(); TextEntryRuntime.Shutdown(); PickupFilterRuntime.Shutdown(); } } internal static class MenuMemoryRuntime { private const int MaximumContexts = 64; private static readonly BoundedContextMemory<string, MenuSelection> Memory = new BoundedContextMemory<string, MenuSelection>(64, StringComparer.Ordinal); private static string _activeContext; private static string _pendingContext; private static MenuSelection _pendingSelection; private static bool _hasPending; internal static void BeginSetup(InventoryGui gui) { if (FeatureOn()) { Capture(gui, _activeContext); _pendingContext = CurrentContext(); _hasPending = Memory.TryGet(_pendingContext, out _pendingSelection); } } internal static void CompleteSetup(InventoryGui gui) { if (FeatureOn()) { _activeContext = _pendingContext ?? CurrentContext(); if (_hasPending) { Restore(gui, _pendingSelection); } _pendingContext = null; _hasPending = false; } } internal static void BeforeContextChange(InventoryGui gui) { Capture(gui, _activeContext ?? CurrentContext()); } internal static void AfterContextChange(InventoryGui gui) { if (FeatureOn()) { string key = (_activeContext = CurrentContext()); if (Memory.TryGet(key, out var value)) { Restore(gui, value); } } } internal static void BeforeHide(InventoryGui gui) { if (FeatureOn()) { Capture(gui, _activeContext ?? CurrentContext()); } } internal static void OnConfigurationChanged() { if (!FeatureOn()) { Memory.Clear(); _activeContext = null; _pendingContext = null; _hasPending = false; } } internal static void Shutdown() { Memory.Clear(); _activeContext = null; _pendingContext = null; _hasPending = false; } private static void Capture(InventoryGui gui, string context) { if (!((Object)(object)gui == (Object)null) && !string.IsNullOrEmpty(context)) { object selectedRecipePair = ValheimAccess.GetSelectedRecipePair(gui); Recipe pairRecipe = ValheimAccess.GetPairRecipe(selectedRecipePair); ItemData pairItem = ValheimAccess.GetPairItem(selectedRecipePair); string recipePrefab = (Object.op_Implicit((Object)(object)pairRecipe) ? ((Object)pairRecipe).name : string.Empty); Memory.Put(context, new MenuSelection(recipePrefab, pairItem != null, gui.ActiveGroup)); } } private static void Restore(InventoryGui gui, MenuSelection selection) { if ((Object)(object)gui == (Object)null) { return; } if (selection.ActiveGroup >= 0) { ValheimAccess.SetActiveGroup(gui, selection.ActiveGroup, sound: false); } if (!selection.HasRecipe) { return; } IList availableRecipePairs = ValheimAccess.GetAvailableRecipePairs(gui); if (availableRecipePairs == null) { return; } for (int i = 0; i < availableRecipePairs.Count; i++) { object? pair = availableRecipePairs[i]; Recipe pairRecipe = ValheimAccess.GetPairRecipe(pair); bool flag = ValheimAccess.GetPairItem(pair) != null; if (Object.op_Implicit((Object)(object)pairRecipe) && flag == selection.Upgrade && string.Equals(((Object)pairRecipe).name, selection.RecipePrefab, StringComparison.Ordinal)) { ValheimAccess.SetRecipe(gui, i, center: false); break; } } } private static string CurrentContext() { Player localPlayer = Player.m_localPlayer; CraftingStation val = (Object.op_Implicit((Object)(object)localPlayer) ? localPlayer.GetCurrentCraftingStation() : null); string obj = (Object.op_Implicit((Object)(object)val) ? Utils.GetPrefabName(((Component)val).gameObject) : "handcraft"); InventoryGui instance = InventoryGui.instance; string text = (((Object)(object)instance != (Object)null && instance.InUpradeTab()) ? "upgrade" : "craft"); return obj + ":" + text; } private static bool FeatureOn() { if (InteractionConfig.Enabled.Value) { return InteractionConfig.MenuMemory.Value; } return false; } } internal static class PickupFilterRuntime { private static PickupFilterSet _filters = PickupFilterSet.Parse(string.Empty); private static string _controllerModifierAction = "JoyRStick"; internal static void Refresh() { _filters = PickupFilterSet.Parse(InteractionConfig.FilteredPickupItems.Value); _controllerModifierAction = InteractionInputBindings.ResolveControllerModifier(InteractionConfig.PickupBypassControllerModifier.Value, out var usedDefault); if (_filters.Truncated) { Diagnostics.Warn("Pickup filter input exceeded 128 unique rules; extra rules were ignored."); } if (usedDefault) { Diagnostics.Warn("Pickup bypass controller modifier was outside the bounded installed-action set; JoyRStick remains active."); } Diagnostics.Trace("Pickup filter compiled " + _filters.Count + " exact rules."); } internal static bool Allow(Humanoid actor, GameObject worldObject) { if (!FeatureOn() || !Object.op_Implicit((Object)(object)actor) || (Object)(object)actor != (Object)(object)Player.m_localPlayer || !Object.op_Implicit((Object)(object)worldObject)) { return true; } if (BypassHeld()) { return true; } ItemDrop component = worldObject.GetComponent<ItemDrop>(); if (!Object.op_Implicit((Object)(object)component) || component.m_itemData == null) { return true; } if (component.m_itemData.m_shared.m_questItem && !InteractionConfig.FilterQuestItems.Value) { return true; } string prefabName = (Object.op_Implicit((Object)(object)component.m_itemData.m_dropPrefab) ? ((Object)component.m_itemData.m_dropPrefab).name : ((Object)worldObject).name); if (!_filters.Matches(prefabName, component.m_itemData.m_shared.m_name)) { return true; } return false; } internal static void Shutdown() { _filters = PickupFilterSet.Parse(string.Empty); } private static bool BypassHeld() { if (!ZInput.GetKey((KeyCode)308, true) && !ZInput.GetKey((KeyCode)307, true)) { return ZInput.GetButton(_controllerModifierAction); } return true; } private static bool FeatureOn() { if (InteractionConfig.Enabled.Value && InteractionConfig.PickupFilters.Value) { return _filters.Count > 0; } return false; } } internal static class TextEntryRuntime { private static TextReceiver _receiver; private static TextEntryKind _kind; private static int _limit; internal static void Begin(TextReceiver receiver, ref int requestedLimit) { if (FeatureOn()) { _receiver = receiver; _kind = Classify(receiver); if (_kind == TextEntryKind.Unsupported) { _limit = 0; return; } int num = ConfiguredLimit(_kind, requestedLimit); _limit = Mathf.Max(1, Mathf.Min(requestedLimit, num)); requestedLimit = _limit; } } internal static bool ValidateCommit(TextInput input, string value) { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) if (!FeatureOn() || _kind == TextEntryKind.Unsupported) { return true; } TextReceiver queuedTextReceiver = ValheimAccess.GetQueuedTextReceiver(input); bool num = queuedTextReceiver != null && queuedTextReceiver == _receiver; TextReceiver receiver = _receiver; MonoBehaviour val = (MonoBehaviour)(object)((receiver is MonoBehaviour) ? receiver : null); Player localPlayer = Player.m_localPlayer; ZNetView val2 = (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponent<ZNetView>() : null); bool flag = num && Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)val2) && val2.IsValid(); bool authorized = flag && IsAuthorized(val, _kind); float num2 = ((_kind == TextEntryKind.Tame) ? 15f : InteractionConfig.TextCommitRange.Value); bool inRange = flag && Object.op_Implicit((Object)(object)localPlayer) && Vector3.Distance(((Component)localPlayer).transform.position, ((Component)val).transform.position) <= num2; TextEntryDecision textEntryDecision = TextEntryPolicy.Validate(_kind, value, _limit, flag, authorized, inRange); if (textEntryDecision.Allowed) { return true; } ValheimAccess.ClearQueuedTextReceiver(input); if (Object.op_Implicit((Object)(object)localPlayer)) { if (textEntryDecision.Reason == "permission-changed") { ((Character)localPlayer).Message((MessageType)2, "$piece_noaccess", 0, (Sprite)null, false); } else if (textEntryDecision.Reason == "out-of-range") { ((Character)localPlayer).Message((MessageType)2, "$msg_outofrange", 0, (Sprite)null, false); } else { ((Character)localPlayer).Message((MessageType)2, "$msg_blocked", 0, (Sprite)null, false); } } Diagnostics.Trace("Text entry declined safely: " + textEntryDecision.Reason + "."); ClearSession(); return false; } internal static void AfterHide(TextInput input) { if ((Object)(object)input != (Object)null) { ValheimAccess.ClearQueuedTextReceiver(input); } ClearSession(); } internal static void OnConfigurationChanged() { if (!FeatureOn()) { ClearSession(); } } internal static void Shutdown() { ClearSession(); } private static bool IsAuthorized(MonoBehaviour receiver, TextEntryKind kind) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (kind == TextEntryKind.Tame) { Tameable val = (Tameable)(object)((receiver is Tameable) ? receiver : null); if (val != null) { return val.IsTamed(); } return false; } return PrivateArea.CheckAccess(((Component)receiver).transform.position, 0f, false, false); } private static TextEntryKind Classify(TextReceiver receiver) { if (receiver is TeleportWorld) { return TextEntryKind.Portal; } if (receiver is Sign) { return TextEntryKind.Sign; } if (receiver is Tameable) { return TextEntryKind.Tame; } return TextEntryKind.Unsupported; } private static int ConfiguredLimit(TextEntryKind kind, int fallback) { return kind switch { TextEntryKind.Portal => InteractionConfig.PortalTextLimit.Value, TextEntryKind.Sign => InteractionConfig.SignTextLimit.Value, TextEntryKind.Tame => InteractionConfig.TameTextLimit.Value, _ => fallback, }; } private static void ClearSession() { _receiver = null; _kind = TextEntryKind.Unsupported; _limit = 0; } private static bool FeatureOn() { if (InteractionConfig.Enabled.Value) { return InteractionConfig.TextEntryPolish.Value; } return false; } } internal static class TransferGestureRuntime { private static bool _lockWarning; internal static bool TryHandleAltClick(InventoryGrid grid, UIInputHandler handler) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) if (!FeatureOn() || !Object.op_Implicit((Object)(object)grid) || !Object.op_Implicit((Object)(object)handler)) { return false; } if (ZInput.GetKey((KeyCode)306, true) || ZInput.GetKey((KeyCode)305, true) || ZInput.GetKey((KeyCode)304, true) || ZInput.GetKey((KeyCode)303, true) || (!ZInput.GetKey((KeyCode)308, true) && !ZInput.GetKey((KeyCode)307, true))) { return false; } InventoryGui instance = InventoryGui.instance; Container currentContainer = ValheimAccess.GetCurrentContainer(instance); if (!Object.op_Implicit((Object)(object)currentContainer) || ValheimAccess.HasDrag(instance)) { return false; } Vector2i buttonPosition = ValheimAccess.GetButtonPosition(grid, ((Component)handler).gameObject); Inventory inventory = grid.GetInventory(); ItemData val = ((inventory != null) ? inventory.GetItemAt(buttonPosition.x, buttonPosition.y) : null); if (val == null || grid.m_onSelected == null) { return false; } if (!CanMove(currentContainer, val, instance, notify: true)) { return true; } grid.m_onSelected(grid, val, buttonPosition, (Modifier)2); return true; } internal static bool GuardVanillaMove(InventoryGrid grid, ItemData item, Modifier modifier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)modifier != 2 || !FeatureOn()) { return true; } InventoryGui instance = InventoryGui.instance; Container currentContainer = ValheimAccess.GetCurrentContainer(instance); if (!Object.op_Implicit((Object)(object)currentContainer)) { return true; } return CanMove(currentContainer, item, instance, notify: true); } internal static void OnConfigurationChanged() { _lockWarning = false; } private static bool CanMove(Container container, ItemData item, InventoryGui gui, bool notify) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; long playerId = (Object.op_Implicit((Object)(object)localPlayer) ? localPlayer.GetPlayerID() : 0); bool flag = Object.op_Implicit((Object)(object)localPlayer) && ValheimAccess.ContainerAllows(container, playerId); bool flag2 = Object.op_Implicit((Object)(object)localPlayer) && PrivateArea.CheckAccess(((Component)container).transform.position, 0f, false, false); bool num = TransferGesturePolicy.MayTransfer(FeatureOn(), Object.op_Implicit((Object)(object)container), Object.op_Implicit((Object)(object)container) && container.IsOwner(), flag, flag2, item != null, item?.m_shared.m_questItem ?? false, ValheimAccess.HasDrag(gui), itemProtectionAllowsTransfer: true); TransferItemProtection transferItemProtection = TransferItemProtection.NotApplicable; if (num) { transferItemProtection = ItemProtectionQueryAdapter.Resolve(item); } if (num && ItemProtectionQueryAdapter.AllowsTransfer(transferItemProtection)) { return true; } if (transferItemProtection == TransferItemProtection.Unknown && !_lockWarning) { _lockWarning = true; Diagnostics.Warn("A transfer was denied because inventory.item-locks was advertised but its typed protection query was missing, incompatible, indeterminate, or faulted."); } if (notify && Object.op_Implicit((Object)(object)localPlayer)) { if (!flag2) { ((Character)localPlayer).Message((MessageType)2, "$piece_noaccess", 0, (Sprite)null, false); } else if (!flag) { ((Character)localPlayer).Message((MessageType)2, "$msg_cantopen", 0, (Sprite)null, false); } else if (!ItemProtectionQueryAdapter.AllowsTransfer(transferItemProtection)) { ((Character)localPlayer).Message((MessageType)2, "$msg_blocked", 0, (Sprite)null, false); } } return false; } private static bool FeatureOn() { if (InteractionConfig.Enabled.Value) { return InteractionConfig.TransferGestures.Value; } return false; } } internal static class ValheimAccess { internal delegate void DoorRpcDelegate(Door instance, long sender, bool forward); internal delegate bool DoorCanInteractDelegate(Door instance); internal delegate bool CookingHaveDoneDelegate(CookingStation instance); internal delegate Vector2i GridButtonPositionDelegate(InventoryGrid instance, GameObject button); internal delegate bool ContainerCheckAccessDelegate(Container instance, long playerId); internal delegate void InventoryGuiSetRecipeDelegate(InventoryGui instance, int index, bool center); internal delegate void InventoryGuiSetActiveGroupDelegate(InventoryGui instance, int index, bool sound); internal const string AuditedGameVersion = "1.0.15"; private const BindingFlags InstanceAll = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static DoorRpcDelegate _doorRpc; private static DoorCanInteractDelegate _doorCanInteract; private static CookingHaveDoneDelegate _cookingHaveDone; private static GridButtonPositionDelegate _gridButtonPosition; private static ContainerCheckAccessDelegate _containerCheckAccess; private static InventoryGuiSetRecipeDelegate _setRecipe; private static InventoryGuiSetActiveGroupDelegate _setActiveGroup; private static FieldInfo _currentContainer; private static FieldInfo _dragObject; private static FieldInfo _queuedTextReceiver; private static FieldInfo _selectedRecipe; private static FieldInfo _availableRecipes; private static PropertyInfo _pairRecipe; private static PropertyInfo _pairItem; private static bool _ready; internal static string ReadGameVersion(Type versionType = null) { try { versionType = versionType ?? typeof(Player).Assembly.GetType("Version", throwOnError: false); return versionType?.GetProperty("CurrentVersion", BindingFlags.Static | BindingFlags.Public)?.GetValue(null, null)?.ToString() ?? "unknown"; } catch (Exception) { return "unknown"; } } internal static bool Initialize(out string problem) { problem = string.Empty; try { _doorRpc = DelegateFor<DoorRpcDelegate>(typeof(Door), "RPC_UseDoor", new Type[2] { typeof(long), typeof(bool) }); _doorCanInteract = DelegateFor<DoorCanInteractDelegate>(typeof(Door), "CanInteract", Array.Empty<Type>()); _cookingHaveDone = DelegateFor<CookingHaveDoneDelegate>(typeof(CookingStation), "HaveDoneItem", Array.Empty<Type>()); _gridButtonPosition = DelegateFor<GridButtonPositionDelegate>(typeof(InventoryGrid), "GetButtonPos", new Type[1] { typeof(GameObject) }); _containerCheckAccess = DelegateFor<ContainerCheckAccessDelegate>(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }); _setRecipe = DelegateFor<InventoryGuiSetRecipeDelegate>(typeof(InventoryGui), "SetRecipe", new Type[2] { typeof(int), typeof(bool) }); _setActiveGroup = DelegateFor<InventoryGuiSetActiveGroupDelegate>(typeof(InventoryGui), "SetActiveGroup", new Type[2] { typeof(int), typeof(bool) }); _currentContainer = ExactField(typeof(InventoryGui), "m_currentContainer"); _dragObject = ExactField(typeof(InventoryGui), "m_dragGo"); _queuedTextReceiver = ExactField(typeof(TextInput), "m_queuedSign"); _selectedRecipe = ExactField(typeof(InventoryGui), "m_selectedRecipe"); _availableRecipes = ExactField(typeof(InventoryGui), "m_availableRecipes"); Type? nestedType = typeof(InventoryGui).GetNestedType("RecipeDataPair", BindingFlags.NonPublic); if (nestedType == null) { throw new MissingMemberException("InventoryGui.RecipeDataPair"); } _pairRecipe = ExactProperty(nestedType, "Recipe", typeof(Recipe)); _pairItem = ExactProperty(nestedType, "ItemData", typeof(ItemData)); Exact(typeof(Switch), "Interact", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(bool), typeof(bool)); Exact(typeof(Smelter), "Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(CookingStation), "Awake", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(CookingStation), "Interact", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(bool), typeof(bool)); Exact(typeof(Fermenter), "Interact", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(bool), typeof(bool)); Exact(typeof(ShieldGenerator), "Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Door), "Interact", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(Humanoid), typeof(bool), typeof(bool)); Exact(typeof(InventoryGrid), "OnLeftClick", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(UIInputHandler)); Exact(typeof(InventoryGui), "OnSelectedItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(InventoryGrid), typeof(ItemData), typeof(Vector2i), typeof(Modifier)); Exact(typeof(Humanoid), "EquipItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ItemData), typeof(bool)); Exact(typeof(Humanoid), "UnequipItem", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(ItemData), typeof(bool)); Exact(typeof(Humanoid), "HideHandItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(bool), typeof(bool)); Exact(typeof(Humanoid), "ShowHandItems", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(bool), typeof(bool)); Exact(typeof(InventoryGui), "SetupCrafting", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(InventoryGui), "Hide", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(InventoryGui), "OnTabCraftPressed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(InventoryGui), "OnTabUpgradePressed", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(TextInput), "RequestText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(TextReceiver), typeof(string), typeof(int)); Exact(typeof(TextInput), "setText", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(string)); Exact(typeof(TextInput), "Hide", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Exact(typeof(Humanoid), "Pickup", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, typeof(GameObject), typeof(bool), typeof(bool)); _ready = true; return true; } catch (Exception ex) { _ready = false; problem = ex.GetType().Name + ": " + ex.Message; return false; } } internal static bool CookingHasDoneItem(CookingStation station) { if (_ready && Object.op_Implicit((Object)(object)station)) { return _cookingHaveDone(station); } return false; } internal static bool DoorCanInteract(Door door) { if (_ready && Object.op_Implicit((Object)(object)door)) { return _doorCanInteract(door); } return false; } internal static void CloseDoor(Door door, long sender) { _doorRpc(door, sender, forward: false); } internal static Vector2i GetButtonPosition(InventoryGrid grid, GameObject button) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) return _gridButtonPosition(grid, button); } internal static Container GetCurrentContainer(InventoryGui gui) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown if (!((Object)(object)gui == (Object)null)) { return (Container)_currentContainer.GetValue(gui); } return null; } internal static bool HasDrag(InventoryGui gui) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown if ((Object)(object)gui != (Object)null) { return Object.op_Implicit((Object)(GameObject)_dragObject.GetValue(gui)); } return false; } internal static bool ContainerAllows(Container container, long playerId) { if (Object.op_Implicit((Object)(object)container)) { return _containerCheckAccess(container, playerId); } return false; } internal static TextReceiver GetQueuedTextReceiver(TextInput input) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown if (!((Object)(object)input == (Object)null)) { return (TextReceiver)_queuedTextReceiver.GetValue(input); } return null; } internal static void ClearQueuedTextReceiver(TextInput input) { if ((Object)(object)input != (Object)null) { _queuedTextReceiver.SetValue(input, null); } } internal static object GetSelectedRecipePair(InventoryGui gui) { if (!((Object)(object)gui == (Object)null)) { return _selectedRecipe.GetValue(gui); } return null; } internal static IList GetAvailableRecipePairs(InventoryGui gui) { if (!((Object)(object)gui == (Object)null)) { return _availableRecipes.GetValue(gui) as IList; } return null; } internal static Recipe GetPairRecipe(object pair) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown if (pair != null) { return (Recipe)_pairRecipe.GetValue(pair, null); } return null; } internal static ItemData GetPairItem(object pair) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown if (pair != null) { return (ItemData)_pairItem.GetValue(pair, null); } return null; } internal static void SetRecipe(InventoryGui gui, int index, bool center) { _setRecipe(gui, index, center); } internal static void SetActiveGroup(InventoryGui gui, int group, bool sound) { _setActiveGroup(gui, group, sound); } private static T DelegateFor<T>(Type type, string name, params Type[] parameters) where T : Delegate { return AccessTools.MethodDelegate<T>(Exact(type, name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, parameters), (object)null, true); } private static MethodInfo Exact(Type type, string name, BindingFlags flags, params Type[] parameters) { return type.GetMethod(name, flags, null, parameters, null) ?? throw new MissingMethodException(type.FullName, name); } private static FieldInfo ExactField(Type type, string name) { return type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ?? throw new MissingFieldException(type.FullName, name); } private static PropertyInfo ExactProperty(Type type, string name, Type propertyType) { PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null || property.PropertyType != propertyType) { throw new MissingMemberException(type.FullName, name); } return property; } } } namespace RunicInteraction.Core { internal sealed class BoundedContextMemory<TKey, TValue> { private readonly struct Entry { internal TValue Value { get; } internal long Sequence { get; } internal Entry(TValue value, long sequence) { Value = value; Sequence = sequence; } } private readonly int _capacity; private readonly Dictionary<TKey, Entry> _entries; private long _sequence; internal int Count => _entries.Count; internal BoundedContextMemory(int capacity, IEqualityComparer<TKey> comparer = null) { if (capacity < 1) { throw new ArgumentOutOfRangeException("capacity"); } _capacity = capacity; _entries = new Dictionary<TKey, Entry>(comparer ?? EqualityComparer<TKey>.Default); } internal void Put(TKey key, TValue value) { long sequence = NextSequence(); if (_entries.ContainsKey(key)) { _entries[key] = new Entry(value, sequence); return; } if (_entries.Count >= _capacity) { EvictOldest(); } _entries.Add(key, new Entry(value, sequence)); } internal bool TryGet(TKey key, out TValue value) { if (!_entries.TryGetValue(key, out var value2)) { value = default(TValue); return false; } _entries[key] = new Entry(value2.Value, NextSequence()); value = value2.Value; return true; } internal void Clear() { _entries.Clear(); } private long NextSequence() { if (_sequence == long.MaxValue) { long num = 0L; List<TKey> list = new List<TKey>(_entries.Keys); list.Sort((TKey left, TKey right) => _entries[left].Sequence.CompareTo(_entries[right].Sequence)); foreach (TKey item in list) { Entry entry = _entries[item]; _entries[item] = new Entry(entry.Value, ++num); } _sequence = num; } return ++_sequence; } private void EvictOldest() { bool flag = false; TKey key = default(TKey); long num = 0L; foreach (KeyValuePair<TKey, Entry> entry in _entries) { if (!flag || entry.Value.Sequence < num) { flag = true; key = entry.Key; num = entry.Value.Sequence; } } if (flag) { _entries.Remove(key); } } } internal sealed class InteractionInputChord : IEquatable<InteractionInputChord> { private readonly IReadOnlyList<string> _modifiers; internal string DeviceId { get; } internal string PrimaryControl { get; } internal IReadOnlyList<string> Modifiers => _modifiers; internal string CanonicalId { get; } internal InteractionInputChord(string deviceId, string primaryControl, params string[] modifiers) { DeviceId = Require(deviceId, "deviceId"); PrimaryControl = Require(primaryControl, "primaryControl"); string[] array = ((modifiers == null) ? Array.Empty<string>() : ((string[])modifiers.Clone())); for (int i = 0; i < array.Length; i++) { array[i] = Require(array[i], "modifiers"); } Array.Sort(array, (IComparer<string>?)StringComparer.OrdinalIgnoreCase); _modifiers = Array.AsReadOnly(array); CanonicalId = Canonical(DeviceId) + ":" + string.Join("+", array).ToLowerInvariant() + ":" + Canonical(PrimaryControl); } public bool Equals(InteractionInputChord other) { if (other != null) { return string.Equals(CanonicalId, other.CanonicalId, StringComparison.Ordinal); } return false; } public override bool Equals(object value) { return Equals(value as InteractionInputChord); } public override int GetHashCode() { return StringComparer.Ordinal.GetHashCode(CanonicalId); } private static string Require(string value, string parameter) { string text = value?.Trim(); if (string.IsNullOrEmpty(text) || text.Length > 64) { throw new ArgumentException("An input identifier between 1 and 64 characters is required.", parameter); } return text; } private static string Canonical(string value) { return value.Replace(" ", string.Empty).Replace("_", string.Empty).Replace("-", string.Empty) .ToLowerInvariant(); } } internal sealed class KeybindingDescriptor { internal string BindingId { get; } internal string QualifiedId => "runic.interaction/" + BindingId; internal string DisplayName { get; } internal InteractionInputChord Chord { get; } internal string Context { get; } internal KeybindingDescriptor(string bindingId, string displayName, InteractionInputChord chord, string context) { BindingId = bindingId ?? throw new ArgumentNullException("bindingId"); DisplayName = displayName ?? throw new ArgumentNullException("displayName"); Chord = chord ?? throw new ArgumentNullException("chord"); Context = context ?? throw new ArgumentNullException("context"); } } internal static class InteractionInputBindings { internal const string PickupBypassControllerPrimary = "JoyUse"; internal const string DefaultPickupBypassControllerModifier = "JoyRStick"; internal const int BindingCount = 6; internal const int MaximumControllerModifierOptions = 14; internal const string DefaultBindingCatalog = "transfer-left-alt|keyboard|Mouse0|LeftAlt|inventory\ntransfer-right-alt|keyboard|Mouse0|RightAlt|inventory\ntransfer-controller|controller|JoyButtonX|JoyLTrigger|inventory\npickup-bypass-left-alt|keyboard|Use|LeftAlt|interaction\npickup-bypass-right-alt|keyboard|Use|RightAlt|interaction\npickup-bypass-controller|controller|JoyUse|JoyRStick|interaction"; private static readonly string[] ControllerModifierOptions = new string[14] { "JoyButtonB", "JoyButtonY", "JoyBack", "JoyStart", "JoyLStick", "JoyRStick", "JoyDPadLeft", "JoyDPadRight", "JoyDPadUp", "JoyDPadDown", "JoyLBumper", "JoyRBumper", "JoyLTrigger", "JoyRTrigger" }; internal static string[] CreateControllerModifierOptions() { return (string[])ControllerModifierOptions.Clone(); } internal static string ResolveControllerModifier(string configured, out bool usedDefault) { string a = (configured ?? string.Empty).Trim(); for (int i = 0; i < ControllerModifierOptions.Length; i++) { string text = ControllerModifierOptions[i]; if (string.Equals(a, text, StringComparison.OrdinalIgnoreCase)) { usedDefault = false; return text; } } usedDefault = true; return "JoyRStick"; } internal static IReadOnlyList<KeybindingDescriptor> CreateDescriptors(string controllerModifier) { bool usedDefault; string modifier = ResolveControllerModifier(controllerModifier, out usedDefault); return Array.AsReadOnly(new KeybindingDescriptor[6] { Binding("transfer-left-alt", "Full-stack transfer (left Alt)", "keyboard", "Mouse0", "LeftAlt", "inventory"), Binding("transfer-right-alt", "Full-stack transfer (right Alt)", "keyboard", "Mouse0", "RightAlt", "inventory"), Binding("transfer-controller", "Full-stack transfer (controller)", "controller", "JoyButtonX", "JoyLTrigger", "inventory"), Binding("pickup-bypass-left-alt", "Bypass pickup filter (left Alt)", "keyboard", "Use", "LeftAlt", "interaction"), Binding("pickup-bypass-right-alt", "Bypass pickup filter (right Alt)", "keyboard", "Use", "RightAlt", "interaction"), Binding("pickup-bypass-controller", "Bypass pickup filter (controller)", "controller", "JoyUse", modifier, "interaction") }); } private static KeybindingDescriptor Binding(string id, string display, string device, string primary, string modifier, string context) { return new KeybindingDescriptor(id, display, new InteractionInputChord(device, primary, modifier), context); } } internal enum DoorCloseDecision { Remove, Wait, Close } internal static class DoorClosePolicy { internal static DoorCloseDecision Evaluate(bool valid, bool owner, bool open, bool eligible, bool obstructed, float quietSeconds, float requiredQuietSeconds) { if (!valid || !open || !owner) { return DoorCloseDecision.Remove; } if (!eligible || obstructed || quietSeconds < requiredQuietSeconds) { return DoorCloseDecision.Wait; } return DoorCloseDecision.Close; } } internal enum TextEntryKind { Unsupported, Portal, Sign, Tame } internal readonly struct TextEntryDecision { internal bool Allowed { get; } internal string Reason { get; } internal TextEntryDecision(bool allowed, string reason) { Allowed = allowed; Reason = reason ?? string.Empty; } } internal static class TextEntryPolicy { internal static TextEntryDecision Validate(TextEntryKind kind, string value, int limit, bool receiverValid, bool authorized, bool inRange) { if (kind == TextEntryKind.Unsupported) { return new TextEntryDecision(allowed: true, string.Empty); } if (!receiverValid) { return new TextEntryDecision(allowed: false, "receiver-invalid"); } if (!authorized) { return new TextEntryDecision(allowed: false, "permission-changed"); } if (!inRange) { return new TextEntryDecision(allowed: false, "out-of-range"); } if (value == null) { return new TextEntryDecision(allowed: false, "null-text"); } if (value.Length > limit) { return new TextEntryDecision(allowed: false, "too-long"); } for (int i = 0; i < value.Length; i++) { char c = value[i]; if (char.IsHighSurrogate(c)) { if (i + 1 >= value.Length || !char.IsLowSurrogate(value[i + 1])) { return new TextEntryDecision(allowed: false, "invalid-unicode"); } i++; continue; } if (char.IsLowSurrogate(c)) { return new TextEntryDecision(allowed: false, "invalid-unicode"); } if (char.IsControl(c) && (kind != TextEntryKind.Sign || (c != '\n' && c != '\t'))) { return new TextEntryDecision(allowed: false, "control-character"); } } return new TextEntryDecision(allowed: true, string.Empty); } } internal sealed class PickupFilterSet { internal const int MaximumRules = 128; private readonly HashSet<string> _rules; internal int Count => _rules.Count; internal bool Truncated { get; } private PickupFilterSet(HashSet<string> rules, bool truncated) { _rules = rules; Truncated = truncated; } internal bool Matches(string prefabName, string sharedName) { if (string.IsNullOrEmpty(prefabName) || !_rules.Contains(prefabName)) { if (!string.IsNullOrEmpty(sharedName)) { return _rules.Contains(sharedName); } return false; } return true; } internal static PickupFilterSet Parse(string text) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); bool truncated = false; if (!string.IsNullOrEmpty(text)) { string[] array = text.Split(new char[4] { ',', ';', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length != 0 && text2.Length <= 128) { if (hashSet.Count >= 128 && !hashSet.Contains(text2)) { truncated = true; } else { hashSet.Add(text2); } } } } return new PickupFilterSet(hashSet, truncated); } } internal readonly struct MenuSelection { internal string RecipePrefab { get; } internal bool Upgrade { get; } internal int ActiveGroup { get; } internal bool HasRecipe => RecipePrefab.Length != 0; internal MenuSelection(string recipePrefab, bool upgrade, int activeGroup) { RecipePrefab = recipePrefab ?? string.Empty; Upgrade = upgrade; ActiveGroup = activeGroup; } } internal static class EquipmentRestorePolicy { internal static bool MayRestore(bool enabled, bool isLocalPlayer, bool alive, bool swimming, bool attacking, bool dodging, bool temporaryToolStillEquipped, bool userSelectedReplacement) { if (enabled && isLocalPlayer && alive && !swimming && !attacking && !dodging && !temporaryToolStillEquipped) { return !userSelectedReplacement; } return false; } } internal static class TransferGesturePolicy { internal static bool MayTransfer(bool enabled, bool containerOpen, bool containerOwned, bool permission, bool wardAccess, bool hasItem, bool questItem, bool dragActive, bool itemProtectionAllowsTransfer) { return enabled && containerOpen && containerOwned && permission && wardAccess && hasItem && !questItem && !dragActive && itemProtectionAllowsTransfer; } } internal enum TransferItemProtection { NotApplicable, Unlocked, Locked, Unknown } internal static class ItemProtectionQueryAdapter { private const string PluginGuid = "chazman.RunicInventory"; private const string ApiTypeName = "RunicInventory.Api.InventoryIntegrationApi"; private static MethodInfo _method; private static object[] _arguments; private static bool _resolved; internal static TransferItemProtection Resolve(object nativeItem) { if (!Chainloader.PluginInfos.ContainsKey("chazman.RunicInventory")) { return TransferItemProtection.NotApplicable; } if (nativeItem == null || !TryResolveMethod()) { return TransferItemProtection.Unknown; } try { _arguments[0] = nativeItem; _arguments[1] = 0; object obj = _method.Invoke(null, _arguments); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } int num2 = num & (flag ? 1 : 0); int num3 = ((_arguments[1] is int num4) ? num4 : 0); return (num2 != 0) ? (num3 switch { 2 => TransferItemProtection.Locked, 1 => TransferItemProtection.Unlocked, _ => TransferItemProtection.Unknown, }) : TransferItemProtection.NotApplicable; } catch { _resolved = false; _method = null; _arguments = null; return TransferItemProtection.Unknown; } finally { if (_arguments != null) { _arguments[0] = null; _arguments[1] = 0; } } } internal static bool AllowsTransfer(TransferItemProtection protection) { if (protection != TransferItemProtection.NotApplicable) { return protection == TransferItemProtection.Unlocked; } return true; } private static bool TryResolveMethod() { if (_resolved) { return _method != null; } _resolved = true; if (!Chainloader.PluginInfos.TryGetValue("chazman.RunicInventory", out var value)) { return false; } _method = ((value == null) ? null : ((object)value.Instance)?.GetType().Assembly.GetType("RunicInventory.Api.InventoryIntegrationApi", throwOnError: false))?.GetMethod("TryGetProtection", BindingFlags.Static | BindingFlags.Public, null, new Type[2] { typeof(object), typeof(int).MakeByRefType() }, null); if (_method == null || _method.ReturnType != typeof(bool)) { return false; } _arguments = new object[2] { null, 0 }; return true; } } }