using 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 Enabled { get; private set; } internal static ConfigEntry HoldToRepeat { get; private set; } internal static ConfigEntry TransferGestures { get; private set; } internal static ConfigEntry DragTransfer { get; private set; } internal static ConfigEntry AutoCloseDoors { get; private set; } internal static ConfigEntry EquipmentRestore { get; private set; } internal static ConfigEntry MenuMemory { get; private set; } internal static ConfigEntry TextEntryPolish { get; private set; } internal static ConfigEntry PickupFilters { get; private set; } internal static ConfigEntry DoorDelaySeconds { get; private set; } internal static ConfigEntry DoorRecentUseSeconds { get; private set; } internal static ConfigEntry DoorObstructionRadius { get; private set; } internal static ConfigEntry PortalTextLimit { get; private set; } internal static ConfigEntry SignTextLimit { get; private set; } internal static ConfigEntry TameTextLimit { get; private set; } internal static ConfigEntry TextCommitRange { get; private set; } internal static ConfigEntry FilteredPickupItems { get; private set; } internal static ConfigEntry FilterQuestItems { get; private set; } internal static ConfigEntry PickupBypassControllerModifier { get; private set; } internal static ConfigEntry VerboseLogging { get; private set; } internal static event Action 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(1f, 60f), Array.Empty())); DoorRecentUseSeconds = Bind(config, "Door Auto-Close", "RecentUseSafetySeconds", 1.5f, new ConfigDescription("Minimum quiet time before closing.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 10f), Array.Empty())); DoorObstructionRadius = Bind(config, "Door Auto-Close", "ObstructionRadiusMeters", 0.9f, new ConfigDescription("Non-alloc character/movable-body check around the doorway.", (AcceptableValueBase)(object)new AcceptableValueRange(0.35f, 2f), Array.Empty())); PortalTextLimit = Bind(config, "Text Entry", "PortalCharacterLimit", 10, new ConfigDescription("Cannot exceed vanilla's portal limit.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), Array.Empty())); SignTextLimit = Bind(config, "Text Entry", "SignCharacterLimit", 50, new ConfigDescription("Upper bound; a sign's smaller prefab limit still wins.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 200), Array.Empty())); TameTextLimit = Bind(config, "Text Entry", "TameCharacterLimit", 10, new ConfigDescription("Cannot exceed vanilla's tame-name limit.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), Array.Empty())); 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(2f, 15f), Array.Empty())); 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(InteractionInputBindings.CreateControllerModifierOptions()), Array.Empty())); 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 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 Bind(ConfigFile file, string section, string key, T value, string description) { return file.Bind(section, key, value, description); } private static ConfigEntry Bind(ConfigFile file, string section, string key, T value, ConfigDescription description) { return file.Bind(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 _keybindings = new List(); 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 readOnlyList = InteractionInputBindings.CreateDescriptors(InteractionConfig.PickupBypassControllerModifier.Value); if (!BindingsMatch(readOnlyList)) { ReplaceKeybindings(readOnlyList); } } private bool BindingsMatch(IReadOnlyList 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 replacements) { if (replacements == null || replacements.Count != 6) { throw new InvalidOperationException("Interaction keybinding catalog is incomplete."); } List list = new List(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 Timers = new List(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(); 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(); 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()) || (Object.op_Implicit((Object)(object)attachedRigidbody) && !attachedRigidbody.isKinematic)) { return true; } } } return result; } private static bool FeatureOn() { ConfigEntry 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 Inputs = new ConditionalWeakTable(); 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(); 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 Memory = new BoundedContextMemory(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(); 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() : 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(typeof(Door), "RPC_UseDoor", new Type[2] { typeof(long), typeof(bool) }); _doorCanInteract = DelegateFor(typeof(Door), "CanInteract", Array.Empty()); _cookingHaveDone = DelegateFor(typeof(CookingStation), "HaveDoneItem", Array.Empty()); _gridButtonPosition = DelegateFor(typeof(InventoryGrid), "GetButtonPos", new Type[1] { typeof(GameObject) }); _containerCheckAccess = DelegateFor(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }); _setRecipe = DelegateFor(typeof(InventoryGui), "SetRecipe", new Type[2] { typeof(int), typeof(bool) }); _setActiveGroup = DelegateFor(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(Type type, string name, params Type[] parameters) where T : Delegate { return AccessTools.MethodDelegate(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 { 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 _entries; private long _sequence; internal int Count => _entries.Count; internal BoundedContextMemory(int capacity, IEqualityComparer comparer = null) { if (capacity < 1) { throw new ArgumentOutOfRangeException("capacity"); } _capacity = capacity; _entries = new Dictionary(comparer ?? EqualityComparer.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 list = new List(_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 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 { private readonly IReadOnlyList _modifiers; internal string DeviceId { get; } internal string PrimaryControl { get; } internal IReadOnlyList 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[])modifiers.Clone())); for (int i = 0; i < array.Length; i++) { array[i] = Require(array[i], "modifiers"); } Array.Sort(array, (IComparer?)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 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 _rules; internal int Count => _rules.Count; internal bool Truncated { get; } private PickupFilterSet(HashSet 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 hashSet = new HashSet(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; } } }