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.0
RunicInteraction.dll
Decompiled 9 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; 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.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: InternalsVisibleTo("RunicInteraction.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.0.0.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 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_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Expected O, but got Unknown //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Expected O, but got Unknown //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Expected O, but got Unknown //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Expected O, but got Unknown //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Expected O, but got Unknown //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Expected O, but got Unknown //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Expected O, but got Unknown //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Expected O, but got Unknown Enabled = Bind(config, "General", "Enabled", value: true, "Master switch. When false every patch yields to vanilla behavior."); HoldToRepeat = Bind(config, "Features", "HoldToRepeat", value: true, "Let vanilla fuel/input interactions repeat at Valheim's installed 0.2 second hold cadence."); TransferGestures = Bind(config, "Features", "TransferGestures", value: true, "Add Alt-click full-stack transfer while retaining vanilla Ctrl-click and controller transfer."); DragTransfer = Bind(config, "Features", "DragTransfer", value: false, "Reserved fail-closed gate. Valheim 0.221.12 has no authority-safe drag-sweep transaction boundary."); AutoCloseDoors = Bind(config, "Features", "AutoCloseDoors", value: false, "Session-only delayed close for doors opened by the native local player. Off by default."); EquipmentRestore = Bind(config, "Features", "EquipmentRestore", value: true, "Restore the previously selected legal weapon/shield after a temporary Tool is put away."); MenuMemory = Bind(config, "Features", "MenuMemory", value: true, "Remember crafting selection and active group per station/mode for the current game session."); TextEntryPolish = Bind(config, "Features", "TextEntryPolish", value: true, "Validate portal, sign, and tame text immediately before vanilla commit."); PickupFilters = Bind(config, "Features", "PickupFilters", value: true, "Decline configured world drops before ownership or inventory mutation. Alt+Use bypasses the filter."); DoorDelaySeconds = Bind(config, "Door Auto-Close", "DelaySeconds", 4f, new ConfigDescription("Delay after the last door use.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>())); DoorRecentUseSeconds = Bind(config, "Door Auto-Close", "RecentUseSafetySeconds", 1.5f, new ConfigDescription("Minimum quiet time before closing.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>())); DoorObstructionRadius = Bind(config, "Door Auto-Close", "ObstructionRadiusMeters", 0.9f, new ConfigDescription("Non-alloc character/movable-body check around the doorway.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.35f, 2f), Array.Empty<object>())); PortalTextLimit = Bind(config, "Text Entry", "PortalCharacterLimit", 10, new ConfigDescription("Cannot exceed vanilla's portal limit.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())); SignTextLimit = Bind(config, "Text Entry", "SignCharacterLimit", 50, new ConfigDescription("Upper bound; a sign's smaller prefab limit still wins.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 200), Array.Empty<object>())); TameTextLimit = Bind(config, "Text Entry", "TameCharacterLimit", 10, new ConfigDescription("Cannot exceed vanilla's tame-name limit.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())); TextCommitRange = Bind(config, "Text Entry", "CommitRangeMeters", 6f, new ConfigDescription("Maximum portal/sign commit distance; tame naming keeps its vanilla 15 m bound.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 15f), Array.Empty<object>())); FilteredPickupItems = Bind(config, "Pickup Filter", "Items", string.Empty, "Comma/semicolon/newline-separated prefab IDs or localized item tokens, maximum 128 exact entries."); FilterQuestItems = Bind(config, "Pickup Filter", "AllowQuestItemFiltering", value: false, "Dangerous option. False guarantees quest items bypass pickup filters."); PickupBypassControllerModifier = Bind(config, "Controller", "PickupBypassModifierAction", "JoyRStick", new ConfigDescription("Generic Valheim controller action held while JoyUse intentionally bypasses a pickup filter.", (AcceptableValueBase)(object)new AcceptableValueList<string>(InteractionInputBindings.CreateControllerModifierOptions()), Array.Empty<object>())); VerboseLogging = Bind(config, "Diagnostics", "VerboseLogging", value: false, "Log bounded feature decisions without logging inventory contents or text-entry content."); 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.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicInteraction"; public const string Name = "Runic Interaction"; public const string Version = "1.0.0"; 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.0 ready for Valheim 0.221.12. 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, "Configuration refresh failed; previous runtime state remains active."); } } 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 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; internal static void Initialize() { Shutdown(); } internal static DoorOpenCapture BeforeInteract(Door door, Humanoid character, bool hold) { //IL_00a9: 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)) { 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_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!capture.Eligible || !vanillaAccepted || !FeatureOn()) { 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: 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; } 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) || !ValheimAccess.DoorCanInteract(timer.Door)) { return true; } 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 { 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; } } 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)); } } 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, "Smelter input registration failed; its switches remain vanilla."); } } } [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, "Cooking input registration failed; its switches remain vanilla."); } } } [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, "Shield fuel registration failed; its switch remains vanilla."); } } } [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, "Hold-repeat preparation failed; this input remains vanilla."); } } private static Exception Finalizer(Switch __instance, HoldRepeatRuntime.IntervalState __state, Exception __exception) { try { HoldRepeatRuntime.End(__instance, __state); } catch (Exception exception) { Diagnostics.Error(exception, "Hold-repeat interval restoration failed."); } 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, "Cooking hold-repeat decision failed; vanilla hold handling won."); } } } [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, "Fermenter hold-repeat decision failed; vanilla hold handling won."); } } } [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, "Door opening capture failed; no auto-close intent will be created."); } } private static void Postfix(bool __result, DoorAutoCloseRuntime.DoorOpenCapture __state) { if (!Plugin.RuntimeReady) { return; } try { DoorAutoCloseRuntime.AfterInteract(__state, __result); } catch (Exception exception) { Diagnostics.Error(exception, "Door auto-close approval failed; the door remains at its vanilla state."); } } } [HarmonyPatch(typeof(ZDOMan), "AddToSector", new Type[] { typeof(ZDO), typeof(Vector2i) })] internal static class InteractionWardZdoAddedPatch { private static void Postfix(ZDO zdo) { if (!Plugin.RuntimeReady) { return; } try { DoorAutoCloseRuntime.ObserveWardTopologyMutation(zdo); } catch (Exception exception) { Diagnostics.Error(exception, "Door ward index add observation failed closed."); } } } [HarmonyPatch(typeof(ZDOMan), "RemoveFromSector", new Type[] { typeof(ZDO), typeof(Vector2i) })] internal static class InteractionWardZdoRemovedPatch { private static void Prefix(ZDO zdo) { if (!Plugin.RuntimeReady) { return; } try { DoorAutoCloseRuntime.ObserveWardTopologyMutation(zdo); } catch (Exception exception) { Diagnostics.Error(exception, "Door ward index remove observation failed closed."); } } } [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, "Alt-transfer failed closed; vanilla click handling won."); 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, "Transfer validation failed closed; the move was declined."); 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, "Equipment capture failed; vanilla equip handling won."); } } [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, "Equipment restore capture failed; no item was forced."); } } } [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, "Equipment restore scheduling failed; no item was forced."); } } } [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, "Swim equipment capture failed; vanilla hide handling won."); } } } [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, "Swim equipment fallback was not scheduled; vanilla show handling won."); } } } [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, "Menu context capture failed; setup remains vanilla."); } } private static void Postfix(InventoryGui __instance, bool __state) { if (!Plugin.RuntimeReady || !__state) { return; } try { MenuMemoryRuntime.CompleteSetup(__instance); } catch (Exception exception) { Diagnostics.Error(exception, "Menu context restore failed; vanilla selection remains active."); } } } [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, "Menu selection capture failed; inventory close remains vanilla."); } } } [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, "Craft-tab selection capture failed; tab behavior remains vanilla."); } } private static void Postfix(InventoryGui __instance, bool __state) { if (!Plugin.RuntimeReady || !__state) { return; } try { MenuMemoryRuntime.AfterContextChange(__instance); } catch (Exception exception) { Diagnostics.Error(exception, "Craft-tab selection restore failed; vanilla selection remains active."); } } } [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, "Upgrade-tab selection capture failed; tab behavior remains vanilla."); } } private static void Postfix(InventoryGui __instance, bool __state) { if (!Plugin.RuntimeReady || !__state) { return; } try { MenuMemoryRuntime.AfterContextChange(__instance); } catch (Exception exception) { Diagnostics.Error(exception, "Upgrade-tab selection restore failed; vanilla selection remains active."); } } } [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, "Text-entry setup failed; vanilla limits remain in force."); } } } [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, "Text-entry validation faulted; commit was declined safely."); try { ValheimAccess.ClearQueuedTextReceiver(__instance); } catch (Exception exception2) { Diagnostics.Error(exception2, "Stale text receiver cleanup also failed."); } 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, "Text cancellation cleanup failed; no commit was synthesized."); } } } [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, "Pickup filter faulted; vanilla pickup handling won."); 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 0.221.12 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 0.221.12 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); } else if (textEntryDecision.Reason == "out-of-range") { ((Character)localPlayer).Message((MessageType)2, "$msg_outofrange", 0, (Sprite)null); } else { ((Character)localPlayer).Message((MessageType)2, "$msg_blocked", 0, (Sprite)null); } } 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); } else if (!flag) { ((Character)localPlayer).Message((MessageType)2, "$msg_cantopen", 0, (Sprite)null); } else if (!ItemProtectionQueryAdapter.AllowsTransfer(transferItemProtection)) { ((Character)localPlayer).Message((MessageType)2, "$msg_blocked", 0, (Sprite)null); } } 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); 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 bool Initialize(out string problem) { problem = string.Empty; try { string text = (string)Exact(typeof(Player).Assembly.GetType("Version", throwOnError: true), "GetVersionString", BindingFlags.Static | BindingFlags.Public, typeof(bool)).Invoke(null, new object[1] { false }); if (!string.Equals(text, "0.221.12", StringComparison.Ordinal)) { throw new MissingMethodException("Runic Interaction 1.0.0 is audited for Valheim 0.221.12; installed " + text + "."); } _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; } } }