using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Runic.Foundation.Core; using RunicStorage.Engine; using RunicStorage.Runtime; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Runic Storage")] [assembly: AssemblyDescription("Secure cached chest hover and player-directed authorized storage tools for Valheim.")] [assembly: AssemblyCompany("Chazman")] [assembly: AssemblyProduct("Runic Storage")] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [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 Runic.Foundation.Core { public static class RunicIdentifier { public static bool IsValid(string value) { if (string.IsNullOrEmpty(value) || value.Length > 128) { return false; } bool flag = false; bool flag2 = false; foreach (char c in value) { if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { flag = true; flag2 = false; continue; } switch (c) { case '-': if (!flag || flag2) { return false; } flag2 = true; break; case '.': if (!flag || flag2) { return false; } flag = false; flag2 = false; break; default: return false; } } if (flag) { return !flag2; } return false; } public static string Require(string value, string parameterName) { if (!IsValid(value)) { throw new ArgumentException("Runic identifiers must be lowercase dot-separated tokens containing only ASCII letters, digits, and internal hyphens.", parameterName); } return value; } } public sealed class InputChord : IEquatable { private readonly IReadOnlyList _modifiers; public string DeviceId { get; } public string PrimaryControl { get; } public IReadOnlyList Modifiers => _modifiers; public string CanonicalId { get; } public InputChord(string deviceId, string primaryControl, IEnumerable modifiers = null) { DeviceId = RunicIdentifier.Require(deviceId, "deviceId"); PrimaryControl = RequireControl(primaryControl, "primaryControl"); string text = CanonicalizeControl(PrimaryControl); SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); if (modifiers != null) { foreach (string modifier in modifiers) { string text2 = RequireControl(modifier, "modifiers"); string text3 = CanonicalizeControl(text2); if (string.Equals(text3, text, StringComparison.Ordinal)) { throw new ArgumentException("The primary control cannot also be a modifier.", "modifiers"); } if (sortedDictionary.ContainsKey(text3)) { throw new ArgumentException("Duplicate input modifier: " + text2, "modifiers"); } sortedDictionary.Add(text3, text2); } } string[] array = new string[sortedDictionary.Count]; int num = 0; foreach (string value in sortedDictionary.Values) { array[num++] = value; } _modifiers = Array.AsReadOnly(array); StringBuilder stringBuilder = new StringBuilder(DeviceId).Append(':'); foreach (string key in sortedDictionary.Keys) { stringBuilder.Append(key).Append('+'); } stringBuilder.Append(text); CanonicalId = stringBuilder.ToString(); } public bool Equals(InputChord other) { if (other != null) { return string.Equals(CanonicalId, other.CanonicalId, StringComparison.Ordinal); } return false; } public override bool Equals(object obj) { return Equals(obj as InputChord); } public override int GetHashCode() { return StringComparer.Ordinal.GetHashCode(CanonicalId); } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < _modifiers.Count; i++) { if (i > 0) { stringBuilder.Append(" + "); } stringBuilder.Append(_modifiers[i]); } if (_modifiers.Count > 0) { stringBuilder.Append(" + "); } return stringBuilder.Append(PrimaryControl).Append(" [").Append(DeviceId) .Append(']') .ToString(); } private static string RequireControl(string value, string parameterName) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("An input control name is required.", parameterName); } string text = value.Trim(); if (text.Length > 64) { throw new ArgumentException("Input control names cannot exceed 64 characters.", parameterName); } string text2 = text; foreach (char c in text2) { if (char.IsControl(c) || c == ':' || c == '+' || c == '|') { throw new ArgumentException("The input control name contains a reserved character.", parameterName); } } if (CanonicalizeControl(text).Length == 0) { throw new ArgumentException("The input control name has no canonical characters.", parameterName); } return text; } private static string CanonicalizeControl(string value) { StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { if (!char.IsWhiteSpace(c) && c != '_' && c != '-') { stringBuilder.Append(char.ToLowerInvariant(c)); } } return stringBuilder.ToString(); } } public sealed class KeybindingDescriptor { public string ModuleId { get; } public string BindingId { get; } public string QualifiedId => ModuleId + "/" + BindingId; public string DisplayName { get; } public InputChord Chord { get; } public string Context { get; } public KeybindingDescriptor(string moduleId, string bindingId, string displayName, InputChord chord, string context = "gameplay") { ModuleId = RunicIdentifier.Require(moduleId, "moduleId"); BindingId = RunicIdentifier.Require(bindingId, "bindingId"); Context = RunicIdentifier.Require(context, "context"); if (string.IsNullOrWhiteSpace(displayName)) { throw new ArgumentException("A keybinding display name is required.", "displayName"); } Chord = chord ?? throw new ArgumentNullException("chord"); DisplayName = displayName.Trim(); } } public sealed class KeybindingConflict { public InputChord Chord { get; } public IReadOnlyList Bindings { get; } internal KeybindingConflict(InputChord chord, IReadOnlyList bindings) { Chord = chord; Bindings = bindings; } } public enum KeybindingChangeKind { Registered, Unregistered } public sealed class KeybindingsChangedEventArgs : EventArgs { public KeybindingChangeKind Kind { get; } public KeybindingDescriptor Binding { get; } public KeybindingConflict Conflict { get; } internal KeybindingsChangedEventArgs(KeybindingChangeKind kind, KeybindingDescriptor binding, KeybindingConflict conflict) { Kind = kind; Binding = binding; Conflict = conflict; } } public sealed class KeybindingConflictRegistry { private sealed class BindingEntry { internal KeybindingDescriptor Binding { get; } internal long Token { get; } internal BindingEntry(KeybindingDescriptor binding, long token) { Binding = binding; Token = token; } } private readonly object _sync = new object(); private readonly Dictionary _byQualifiedId = new Dictionary(StringComparer.Ordinal); private readonly Dictionary> _byChord = new Dictionary>(StringComparer.Ordinal); private long _nextToken; public event EventHandler Changed; public KeybindingRegistration Register(KeybindingDescriptor binding) { if (binding == null) { throw new ArgumentNullException("binding"); } long token; KeybindingConflict conflict; lock (_sync) { if (_byQualifiedId.ContainsKey(binding.QualifiedId)) { throw new InvalidOperationException("A keybinding is already registered as '" + binding.QualifiedId + "'."); } if (_nextToken == long.MaxValue) { throw new InvalidOperationException("The keybinding registration token space is exhausted."); } token = ++_nextToken; BindingEntry bindingEntry = new BindingEntry(binding, token); _byQualifiedId.Add(binding.QualifiedId, bindingEntry); if (!_byChord.TryGetValue(binding.Chord.CanonicalId, out var value)) { value = new List(); _byChord.Add(binding.Chord.CanonicalId, value); } value.Add(bindingEntry); conflict = CreateConflictLocked(binding.Chord.CanonicalId); } RaiseChanged(new KeybindingsChangedEventArgs(KeybindingChangeKind.Registered, binding, conflict)); return new KeybindingRegistration(this, binding, token); } public bool Unregister(string moduleId, string bindingId) { RunicIdentifier.Require(moduleId, "moduleId"); RunicIdentifier.Require(bindingId, "bindingId"); return Unregister(moduleId + "/" + bindingId, (long?)null); } public bool TryGetBinding(string moduleId, string bindingId, out KeybindingDescriptor binding) { binding = null; if (!RunicIdentifier.IsValid(moduleId) || !RunicIdentifier.IsValid(bindingId)) { return false; } lock (_sync) { if (!_byQualifiedId.TryGetValue(moduleId + "/" + bindingId, out var value)) { return false; } binding = value.Binding; return true; } } public IReadOnlyList GetBindings() { lock (_sync) { List list = new List(_byQualifiedId.Count); foreach (BindingEntry value in _byQualifiedId.Values) { list.Add(value.Binding); } list.Sort((KeybindingDescriptor left, KeybindingDescriptor right) => StringComparer.Ordinal.Compare(left.QualifiedId, right.QualifiedId)); return list.AsReadOnly(); } } public IReadOnlyList GetConflicts() { lock (_sync) { List list = new List(); foreach (string key in _byChord.Keys) { KeybindingConflict keybindingConflict = CreateConflictLocked(key); if (keybindingConflict != null) { list.Add(keybindingConflict); } } list.Sort((KeybindingConflict left, KeybindingConflict right) => StringComparer.Ordinal.Compare(left.Chord.CanonicalId, right.Chord.CanonicalId)); return list.AsReadOnly(); } } public IReadOnlyList GetConflictsFor(string moduleId) { RunicIdentifier.Require(moduleId, "moduleId"); IReadOnlyList conflicts = GetConflicts(); List list = new List(); foreach (KeybindingConflict item in conflicts) { foreach (KeybindingDescriptor binding in item.Bindings) { if (string.Equals(binding.ModuleId, moduleId, StringComparison.Ordinal)) { list.Add(item); break; } } } return list.AsReadOnly(); } internal bool IsActive(string qualifiedId, long token) { lock (_sync) { BindingEntry value; return _byQualifiedId.TryGetValue(qualifiedId, out value) && value.Token == token; } } internal bool Unregister(string qualifiedId, long? requiredToken) { KeybindingDescriptor binding; KeybindingConflict conflict; lock (_sync) { if (!_byQualifiedId.TryGetValue(qualifiedId, out var entry) || (requiredToken.HasValue && entry.Token != requiredToken.Value)) { return false; } binding = entry.Binding; _byQualifiedId.Remove(qualifiedId); List list = _byChord[binding.Chord.CanonicalId]; list.RemoveAll((BindingEntry candidate) => candidate.Token == entry.Token); if (list.Count == 0) { _byChord.Remove(binding.Chord.CanonicalId); } conflict = CreateConflictLocked(binding.Chord.CanonicalId); } RaiseChanged(new KeybindingsChangedEventArgs(KeybindingChangeKind.Unregistered, binding, conflict)); return true; } private KeybindingConflict CreateConflictLocked(string chordId) { if (!_byChord.TryGetValue(chordId, out var value) || value.Count < 2) { return null; } List list = new List(value.Count); foreach (BindingEntry item in value) { list.Add(item.Binding); } list.Sort((KeybindingDescriptor left, KeybindingDescriptor right) => StringComparer.Ordinal.Compare(left.QualifiedId, right.QualifiedId)); return new KeybindingConflict(list[0].Chord, list.AsReadOnly()); } private void RaiseChanged(KeybindingsChangedEventArgs arguments) { EventHandler eventHandler = this.Changed; if (eventHandler == null) { return; } Delegate[] invocationList = eventHandler.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { EventHandler eventHandler2 = (EventHandler)invocationList[i]; try { eventHandler2(this, arguments); } catch (Exception) { } } } } public sealed class KeybindingRegistration : IDisposable { private readonly KeybindingConflictRegistry _registry; private readonly long _token; public KeybindingDescriptor Binding { get; } public bool IsActive => _registry.IsActive(Binding.QualifiedId, _token); internal KeybindingRegistration(KeybindingConflictRegistry registry, KeybindingDescriptor binding, long token) { _registry = registry; Binding = binding; _token = token; } public void Dispose() { _registry.Unregister(Binding.QualifiedId, _token); } } } namespace RunicStorage { [BepInPlugin("chazman.RunicStorage", "Runic Storage", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string Guid = "chazman.RunicStorage"; public const string Name = "Runic Storage"; public const string Version = "1.0.0"; private readonly List _keybindingRegistrations = new List(); private readonly KeybindingConflictRegistry _keybindings = new KeybindingConflictRegistry(); private static readonly FieldInfo DragItemField = AccessTools.Field(typeof(InventoryGui), "m_dragItem"); private static readonly FieldInfo CurrentContainerField = AccessTools.Field(typeof(InventoryGui), "m_currentContainer"); private readonly Harmony _harmony = new Harmony("chazman.RunicStorage"); private readonly StorageInputReader _inputReader = new StorageInputReader(); private StorageActions _actions; private StorageSearchPanel _searchPanel; private bool _readyMessageShown; internal static ContainerIndex Index { get; private set; } internal static ManualLogSource Log { get; private set; } private static StorageSearchPanel ActiveSearchPanel { get; set; } internal static bool SearchPanelOpen => ActiveSearchPanel?.IsOpen ?? false; private void Awake() { Log = ((BaseUnityPlugin)this).Logger; PluginConfig.Bind(((BaseUnityPlugin)this).Config); try { ((BaseUnityPlugin)this).Config.SettingChanged += OnSettingChanged; ZInput.OnInputLayoutChanged += OnInputLayoutChanged; Localization.OnLanguageChange = (Action)Delegate.Combine(Localization.OnLanguageChange, new Action(ContainerHoverContents.InvalidateConfiguration)); Index = new ContainerIndex(); _searchPanel = new StorageSearchPanel(); ActiveSearchPanel = _searchPanel; _actions = new StorageActions(Index, _searchPanel); RegisterKeybindings(); _harmony.PatchAll(typeof(Plugin).Assembly); Index.RefreshLoadedContainers(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Runic Storage v1.0.0 ready: chest hover, Quick Stack, Restock, Search, Sort, Store All, and Consolidate use native local ownership."); if (!ContainerHoverContents.IsSupported) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Chest-content hover is unavailable because its installed Container/PrivateArea adapter signatures did not match. Other Storage features remain enabled."); } LogConfigurationSummary("startup"); } catch (Exception arg) { Shutdown(); ((BaseUnityPlugin)this).Logger.LogError((object)string.Format("{0} failed closed during startup: {1}", "Runic Storage", arg)); } } private void Update() { if (_actions == null) { return; } try { ShowReadyMessage(); _searchPanel?.Tick(); StorageSearchPanel searchPanel = _searchPanel; if (searchPanel != null && searchPanel.IsOpen) { return; } StorageRouteContext context = CaptureRouteContext(); StorageActionEdges num = _inputReader.ReadKeyboardEdges(); StorageActionEdges storageActionEdges = _inputReader.ReadControllerEdges(context); StorageActionEdges edges = num | storageActionEdges; StorageActionRequest request = StorageActionRouter.Select(edges); if (!request.IsPresent) { return; } StorageRouteDecision storageRouteDecision = StorageActionRouter.Route(request, context); if (PluginConfig.DebugTransfers.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("input-detected edges=" + StorageInputReader.DescribeEdges(edges) + " selected=" + StorageActionDiagnostics.ActionCode(request.Action) + " source=" + StorageActionDiagnostics.OriginCode(request.Origin) + " route=" + storageRouteDecision.Outcome.ToString().ToLowerInvariant() + " reason=" + StorageActionDiagnostics.RouteReasonCode(storageRouteDecision.Reason))); } if (storageRouteDecision.Outcome != StorageRouteOutcome.Execute) { string text = StorageActionDiagnostics.RouteFeedback(storageRouteDecision.Reason); if (text.Length != 0) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null); } } return; } switch (request.Action) { case StorageActionKind.QuickStack: _actions.QuickStack(); break; case StorageActionKind.StoreAllOpenedContainer: _actions.StoreAllOpenedContainer(); break; case StorageActionKind.Restock: _actions.Restock(); break; case StorageActionKind.SortOpenedContainer: _actions.SortOpenedContainer(); break; case StorageActionKind.Consolidate: _actions.ConsolidateCarriedStacks(); break; case StorageActionKind.Search: _actions.Search(); break; } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Runic Storage action stopped after restoring the in-flight item move; earlier completed moves, if any, remain applied: " + ex)); Player localPlayer2 = Player.m_localPlayer; if ((Object)(object)localPlayer2 != (Object)null) { ((Character)localPlayer2).Message((MessageType)2, "Runic Storage stopped safely; completed moves may remain. Check BepInEx/LogOutput.log.", 0, (Sprite)null); } } } private void OnGUI() { try { _searchPanel?.Draw(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Storage search list closed after a UI error: " + ex.Message)); _searchPanel?.Close(); } } private void OnDestroy() { Shutdown(); } private void RegisterKeybindings() { //IL_0016: 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) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) DisposeKeybindings(); RegisterKey("quick-stack", "Quick Stack", PluginConfig.QuickStackKey.Value); RegisterKey("restock", "Restock", PluginConfig.RestockKey.Value); RegisterKey("sort", "Sort Opened Container", PluginConfig.SortOpenedContainerKey.Value); RegisterKey("store-all", "Store All Into Opened Container", PluginConfig.StoreAllOpenedContainerKey.Value); RegisterKey("consolidate", "Consolidate Carried Stacks", PluginConfig.ConsolidateKey.Value); RegisterKey("search", "Search Nearby Storage", PluginConfig.SearchKey.Value); if (PluginConfig.ControllerShortcuts.Value) { RegisterControllerKey("controller-quick-stack", "Quick Stack (Controller)", PluginConfig.ControllerQuickStack.Value); RegisterControllerKey("controller-restock", "Restock (Controller)", PluginConfig.ControllerRestock.Value); RegisterControllerKey("controller-sort", "Sort Opened Container (Controller)", PluginConfig.ControllerSort.Value); RegisterControllerKey("controller-consolidate", "Consolidate (Controller)", PluginConfig.ControllerConsolidate.Value); RegisterControllerKey("controller-search", "Search (Controller)", PluginConfig.ControllerSearch.Value); } } private void RegisterKey(string bindingId, string displayName, KeyboardShortcut shortcut) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { list.Add(((object)modifier).ToString()); } _keybindingRegistrations.Add(_keybindings.Register(new KeybindingDescriptor("runic.storage", bindingId, displayName, new InputChord("keyboard", ((object)((KeyboardShortcut)(ref shortcut)).MainKey).ToString(), list)))); } private void RegisterControllerKey(string bindingId, string displayName, string action) { string text = (action ?? string.Empty).Trim(); string text2 = (PluginConfig.ControllerModifier.Value ?? string.Empty).Trim(); if (text.Length == 0 || text2.Length == 0 || string.Equals(text, text2, StringComparison.Ordinal)) { return; } try { _keybindingRegistrations.Add(_keybindings.Register(new KeybindingDescriptor("runic.storage", bindingId, displayName, new InputChord("controller", text, new string[1] { text2 })))); } catch (ArgumentException ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Controller keybinding " + displayName + " was not registered: " + ex.Message)); } } private void OnSettingChanged(object sender, SettingChangedEventArgs arguments) { ContainerHoverContents.InvalidateConfiguration(); _inputReader.InvalidateControllerBindings(); StorageControllerCollisionGuard.Reset(); try { RegisterKeybindings(); LogConfigurationSummary("configuration-changed"); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Storage keybinding refresh failed: " + ex.Message)); } } private void OnInputLayoutChanged() { _inputReader.InvalidateControllerBindings(); StorageControllerCollisionGuard.Reset(); ConfigEntry debugTransfers = PluginConfig.DebugTransfers; if (debugTransfers != null && debugTransfers.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)"Controller input layout changed; Storage controller bindings will be re-resolved once."); } } private void DisposeKeybindings() { for (int num = _keybindingRegistrations.Count - 1; num >= 0; num--) { try { _keybindingRegistrations[num].Dispose(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not unregister a storage keybinding cleanly: " + ex.Message)); } } _keybindingRegistrations.Clear(); } internal static StorageRouteContext CaptureRouteContext() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown //IL_002f: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown Player localPlayer = Player.m_localPlayer; bool flag = InventoryGui.IsVisible(); bool flag2 = (Object)localPlayer != (Object)null && (Object)localPlayer == (Object)Player.m_localPlayer && ((Character)localPlayer).IsOwner(); return new StorageRouteContext(PluginConfig.Enabled?.Value ?? false, (Object)localPlayer == (Object)null || InputBlocked(), HasDraggedItem(), flag, flag && HasOpenedContainer(), StoreGui.IsVisible(), Minimap.IsOpen(), flag2, flag2); } private static bool InputBlocked() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown if (Console.IsVisible() || Menu.IsVisible() || TextInput.IsVisible()) { return true; } if ((Object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return true; } if ((Object)TextViewer.instance != (Object)null && TextViewer.instance.IsVisible()) { return true; } if (Hud.InRadial() || Hud.IsPieceSelectionVisible() || GameCamera.InFreeFly() || PlayerCustomizaton.IsBarberGuiVisible()) { return true; } Player localPlayer = Player.m_localPlayer; if (!((Object)localPlayer == (Object)null) && !((Character)localPlayer).IsDead() && !((Character)localPlayer).InCutscene()) { return ((Character)localPlayer).IsTeleporting(); } return true; } private static bool HasDraggedItem() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown if ((Object)InventoryGui.instance != (Object)null) { return DragItemField?.GetValue(InventoryGui.instance) is ItemData; } return false; } private static bool HasOpenedContainer() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown if ((Object)InventoryGui.instance != (Object)null) { return CurrentContainerField?.GetValue(InventoryGui.instance) is Container; } return false; } private void ShowReadyMessage() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) if (!_readyMessageShown && PluginConfig.Enabled.Value && PluginConfig.ShowReadyMessage.Value && !((Object)Player.m_localPlayer == (Object)null) && !((Object)MessageHud.instance == (Object)null)) { _readyMessageShown = true; string text = (PluginConfig.ControllerShortcuts.Value ? (" Controller: hold " + PluginConfig.ControllerModifier.Value + "; see config for routes.") : string.Empty); ((Character)Player.m_localPlayer).Message((MessageType)1, "Runic Storage ready — " + ShortcutLabel(PluginConfig.QuickStackKey.Value) + " Quick Stack; " + ShortcutLabel(PluginConfig.RestockKey.Value) + " Restock; " + ShortcutLabel(PluginConfig.SearchKey.Value) + " Search; " + ShortcutLabel(PluginConfig.ConsolidateKey.Value) + " Consolidate; open a chest and use " + ShortcutLabel(PluginConfig.SortOpenedContainerKey.Value) + " to Sort or " + ShortcutLabel(PluginConfig.StoreAllOpenedContainerKey.Value) + " to Store All." + text, 0, (Sprite)null); } } private void LogConfigurationSummary(string reason) { //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Unknown result type (might be due to invalid IL or missing references) string text = (PluginConfig.ControllerShortcuts.Value ? ("hold " + PluginConfig.ControllerModifier.Value + ": quick=" + PluginConfig.ControllerQuickStack.Value + ", restock=" + PluginConfig.ControllerRestock.Value + ", sort=" + PluginConfig.ControllerSort.Value + ", consolidate=" + PluginConfig.ControllerConsolidate.Value + ", search=" + PluginConfig.ControllerSearch.Value) : "disabled"); ((BaseUnityPlugin)this).Logger.LogInfo((object)($"Storage configuration ({reason}): Enabled={PluginConfig.Enabled.Value}; " + $"Range={PluginConfig.RangeMeters.Value:0.##}m; MaxCandidates={PluginConfig.MaximumCandidates.Value}; " + $"ProtectHotbar={PluginConfig.ProtectHotbar.Value}; DebugTransfers={PluginConfig.DebugTransfers.Value}; " + $"Hover=[enabled={PluginConfig.ShowContentsOnHover.Value}, kinds={PluginConfig.HoverMaximumItemKinds.Value}, " + $"perLine={PluginConfig.HoverItemsPerLine.Value}, characters={PluginConfig.HoverMaximumCharacters.Value}, " + $"stacks={PluginConfig.HoverMaximumStacksExamined.Value}, " + $"snapshotCharacters={PluginConfig.HoverMaximumSnapshotCharacters.Value}, " + string.Format("retry={0:0.##}s, signatures={1}]; ", PluginConfig.HoverRefreshIntervalSeconds.Value, ContainerHoverContents.IsSupported ? "ready" : "unavailable") + "keyboard=[quick " + ShortcutLabel(PluginConfig.QuickStackKey.Value) + ", restock " + ShortcutLabel(PluginConfig.RestockKey.Value) + ", sort " + ShortcutLabel(PluginConfig.SortOpenedContainerKey.Value) + ", store-all " + ShortcutLabel(PluginConfig.StoreAllOpenedContainerKey.Value) + ", consolidate " + ShortcutLabel(PluginConfig.ConsolidateKey.Value) + ", search " + ShortcutLabel(PluginConfig.SearchKey.Value) + "]; controller=[" + text + "]; controller-validation=" + _inputReader.ControllerStatusSummary() + ".")); } private static string ShortcutLabel(KeyboardShortcut shortcut) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { list.Add(((object)modifier).ToString()); } list.Add(((object)((KeyboardShortcut)(ref shortcut)).MainKey).ToString()); return string.Join("+", list); } private void Shutdown() { ((BaseUnityPlugin)this).Config.SettingChanged -= OnSettingChanged; ZInput.OnInputLayoutChanged -= OnInputLayoutChanged; Localization.OnLanguageChange = (Action)Delegate.Remove(Localization.OnLanguageChange, new Action(ContainerHoverContents.InvalidateConfiguration)); _inputReader.InvalidateControllerBindings(); StorageControllerCollisionGuard.Reset(); StorageSearchGameplayInputGuard.Reset(); ContainerHoverContents.Reset(); try { _harmony.UnpatchSelf(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not unpatch Runic Storage cleanly: " + ex.Message)); } DisposeKeybindings(); _actions = null; _searchPanel?.Dispose(); _searchPanel = null; ActiveSearchPanel = null; Index = null; Log = null; } } internal static class PluginConfig { internal static ConfigEntry Enabled { get; private set; } internal static ConfigEntry ShowReadyMessage { get; private set; } internal static ConfigEntry RangeMeters { get; private set; } internal static ConfigEntry MaximumCandidates { get; private set; } internal static ConfigEntry ProtectHotbar { get; private set; } internal static ConfigEntry ShowContentsOnHover { get; private set; } internal static ConfigEntry HoverMaximumItemKinds { get; private set; } internal static ConfigEntry HoverItemsPerLine { get; private set; } internal static ConfigEntry HoverMaximumCharacters { get; private set; } internal static ConfigEntry HoverMaximumStacksExamined { get; private set; } internal static ConfigEntry HoverMaximumSnapshotCharacters { get; private set; } internal static ConfigEntry HoverRefreshIntervalSeconds { get; private set; } internal static ConfigEntry QuickStackKey { get; private set; } internal static ConfigEntry RestockKey { get; private set; } internal static ConfigEntry RestockTargets { get; private set; } internal static ConfigEntry SortOpenedContainerKey { get; private set; } internal static ConfigEntry StoreAllOpenedContainerKey { get; private set; } internal static ConfigEntry LockedContainerSlots { get; private set; } internal static ConfigEntry ConsolidateKey { get; private set; } internal static ConfigEntry SearchKey { get; private set; } internal static ConfigEntry SearchItem { get; private set; } internal static ConfigEntry SearchMenuFontSize { get; private set; } internal static ConfigEntry SearchMenuFontColor { get; private set; } internal static ConfigEntry ControllerShortcuts { get; private set; } internal static ConfigEntry ControllerModifier { get; private set; } internal static ConfigEntry ControllerQuickStack { get; private set; } internal static ConfigEntry ControllerRestock { get; private set; } internal static ConfigEntry ControllerSort { get; private set; } internal static ConfigEntry ControllerConsolidate { get; private set; } internal static ConfigEntry ControllerSearch { get; private set; } internal static ConfigEntry DebugTransfers { get; private set; } internal static void Bind(ConfigFile config) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Expected O, but got Unknown //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0129: 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_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Expected O, but got Unknown //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Expected O, but got Unknown //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Expected O, but got Unknown //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Unknown result type (might be due to invalid IL or missing references) //IL_039f: Unknown result type (might be due to invalid IL or missing references) //IL_03a9: Expected O, but got Unknown Enabled = config.Bind("General", "Enabled", true, "Enable Runic Storage gameplay actions."); ShowReadyMessage = config.Bind("General", "ShowReadyMessage", true, "Show a one-time in-world control reminder after the local player loads."); RangeMeters = config.Bind("Discovery", "RangeMeters", 20f, new ConfigDescription("Nearby storage radius. Hard limited to 50 meters.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 50f), Array.Empty())); MaximumCandidates = config.Bind("Discovery", "MaximumCandidates", 64, new ConfigDescription("Maximum cached containers examined per action.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 256), Array.Empty())); ProtectHotbar = config.Bind("Safety", "ProtectHotbar", true, "Never quick-stack, store-all, or consolidate items in the top-row hotbar."); ShowContentsOnHover = config.Bind("Hover", "ShowContents", true, "List a closed authorized container's synchronized contents in its hover text. Private, busy, unsynchronized, or Runic-reserved containers retain vanilla text only."); HoverMaximumItemKinds = config.Bind("Hover", "MaximumItemKinds", 8, new ConfigDescription("Maximum distinct item kinds shown in one hover summary.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 24), Array.Empty())); HoverItemsPerLine = config.Bind("Hover", "ItemsPerLine", 3, new ConfigDescription("Maximum item kinds placed on each summary line.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 4), Array.Empty())); HoverMaximumCharacters = config.Bind("Hover", "MaximumCharacters", 320, new ConfigDescription("Hard character ceiling for the complete generated contents suffix, including formatting tags.", (AcceptableValueBase)(object)new AcceptableValueRange(64, 1024), Array.Empty())); HoverMaximumStacksExamined = config.Bind("Hover", "MaximumStacksExamined", 256, new ConfigDescription("Maximum inventory stacks examined when rebuilding one changed summary. Additional stacks are reported as unscanned.", (AcceptableValueBase)(object)new AcceptableValueRange(16, 1024), Array.Empty())); HoverMaximumSnapshotCharacters = config.Bind("Hover", "MaximumSnapshotCharacters", 262144, new ConfigDescription("Maximum persisted Base64 inventory characters accepted for an exact hover snapshot. Oversized third-party containers retain vanilla text.", (AcceptableValueBase)(object)new AcceptableValueRange(16384, 1048576), Array.Empty())); HoverRefreshIntervalSeconds = config.Bind("Hover", "FailedRefreshRetrySeconds", 0.5f, new ConfigDescription("Retry delay after a container cannot prove an exact synchronized inventory snapshot. Stable summaries remain cached by exact persisted item-payload evidence.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 5f), Array.Empty())); QuickStackKey = config.Bind("Keys", "QuickStack", new KeyboardShortcut((KeyCode)113, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Deposit eligible carried stacks into authorized nearby containers already holding that item."); RestockKey = config.Bind("Keys", "Restock", new KeyboardShortcut((KeyCode)114, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Restock configured carried targets from authorized nearby storage."); RestockTargets = config.Bind("Restock", "Targets", "Wood=50,Stone=50", "Comma-separated prefab/name targets, for example Wood=50,Stone=50."); SortOpenedContainerKey = config.Bind("Keys", "SortOpenedContainer", new KeyboardShortcut((KeyCode)115, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Sort the currently opened authorized container by category, name, quality, then weight."); StoreAllOpenedContainerKey = config.Bind("Keys", "StoreAllOpenedContainer", new KeyboardShortcut((KeyCode)97, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Store every eligible carried item in the currently opened authorized locally owned container."); LockedContainerSlots = config.Bind("Sort", "LockedSlots", string.Empty, "Semicolon-separated zero-based slots to leave fixed, for example 0,0;1,0."); ConsolidateKey = config.Bind("Keys", "ConsolidateCarriedStacks", new KeyboardShortcut((KeyCode)99, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Safely consolidate compatible carried stacks while respecting protected slots and equipment."); SearchKey = config.Bind("Keys", "Search", new KeyboardShortcut((KeyCode)102, (KeyCode[])(object)new KeyCode[1] { (KeyCode)308 }), "Open a selectable list of item kinds in authorized nearby containers and highlight every matching chest."); SearchItem = config.Bind("Search", "SearchItem", "Wood", "Legacy setting retained for configuration compatibility; Alt+F now opens the complete nearby-item list."); SearchMenuFontSize = config.Bind("Search", "MenuFontSize", 14, new ConfigDescription("Font size for every title, label, text field, and button in the Alt+F nearby-item window.", (AcceptableValueBase)(object)new AcceptableValueRange(10, 32), Array.Empty())); StorageSearchMenuFontColor migrated; bool num = TryReadAndRemoveLegacySearchMenuFontColor(config, out migrated); SearchMenuFontColor = config.Bind("Search", "MenuFontColor", StorageSearchMenuFontColor.LightGray, "Named font color for the complete Alt+F nearby-item window. Configuration Manager presents the available colors as a dropdown."); if (num) { SearchMenuFontColor.Value = migrated; } ControllerShortcuts = config.Bind("Controller", "Enabled", true, "Enable controller chords resolved through Valheim's current ZInput action map."); ControllerModifier = config.Bind("Controller", "ModifierAction", "JoyAltKeys", "Valheim ZInput action held as the controller modifier. Change only to an existing action name."); ControllerQuickStack = config.Bind("Controller", "QuickStackAction", "JoyDPadDown", "Valheim ZInput action pressed with ModifierAction to Quick Stack."); ControllerRestock = config.Bind("Controller", "RestockAction", "JoyDPadUp", "Valheim ZInput action pressed with ModifierAction to Restock."); ControllerSort = config.Bind("Controller", "SortOpenedContainerAction", "JoyRStick", "Valheim ZInput action pressed with ModifierAction to sort the opened container."); ControllerConsolidate = config.Bind("Controller", "ConsolidateAction", "JoyDPadLeft", "Valheim ZInput action pressed with ModifierAction to consolidate carried stacks."); ControllerSearch = config.Bind("Controller", "SearchAction", "JoyDPadRight", "Valheim ZInput action pressed with ModifierAction to search nearby storage."); DebugTransfers = config.Bind("Diagnostics", "DebugTransfers", false, "Log detected controls, routing decisions, discovery counts, action summaries, transfer legs, and stable no-op/denial codes."); } private static bool TryReadAndRemoveLegacySearchMenuFontColor(ConfigFile config, out StorageSearchMenuFontColor migrated) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown ConfigDefinition val = new ConfigDefinition("Search", "MenuFontColor"); bool result = HasOrphanedValue(config, val); ConfigEntry val2 = config.Bind(val, StorageSearchMenuFontColor.LightGray.ToString(), new ConfigDescription("Legacy Alt+F menu color migration entry.", (AcceptableValueBase)null, Array.Empty())); migrated = StorageSearchMenuAppearance.NormalizeFontColorConfigValue(val2.Value); if (!config.Remove(val)) { throw new InvalidOperationException("Runic Storage could not replace its legacy MenuFontColor setting."); } return result; } private static bool HasOrphanedValue(ConfigFile config, ConfigDefinition definition) { object obj = typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(config, null); if (obj == null) { obj = typeof(ConfigFile).GetField("k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(config); } if (obj is IDictionary dictionary) { return dictionary.ContainsKey(definition); } return false; } } } namespace RunicStorage.Runtime { internal static class ContainerHoverContents { private delegate bool CheckAccessDelegate(Container container, long playerId); private delegate string TranslateDelegate(Localization localization, string key); private delegate bool WardStateDelegate(PrivateArea area); private delegate bool WardContainsDelegate(PrivateArea area, Vector3 point, float radius); private sealed class CacheEntry { private WeakReference _persistedReference; internal HoverPersistedEvidence Evidence; internal bool HasEvidence; internal long ConfigurationGeneration; internal float NextRetryAt; internal bool Verified; internal string Suffix; internal string BaseHoverText; internal string CombinedHoverText; internal long LastAccess; internal bool TryGetPersistedReference(out string persisted) { persisted = null; if (_persistedReference != null) { return _persistedReference.TryGetTarget(out persisted); } return false; } internal void SetPersistedReference(string persisted) { if (persisted == null) { _persistedReference = null; } else if (_persistedReference == null) { _persistedReference = new WeakReference(persisted); } else { _persistedReference.SetTarget(persisted); } } } private const int MaximumCacheEntries = 512; private const int MaximumWardAreasExamined = 4096; private static readonly CheckAccessDelegate CheckAccess = ResolveCheckAccess(); private static readonly TranslateDelegate Translate = ResolveTranslate(); private static readonly FieldRef Loading = ResolveLoading(); private static readonly List WardAreas = ResolveWardAreas(); private static readonly WardStateDelegate WardEnabled = ResolveWardState("IsEnabled"); private static readonly WardStateDelegate WardLocalAccess = ResolveWardState("HaveLocalAccess"); private static readonly WardContainsDelegate WardContains = ResolveWardContains(); private static readonly Dictionary Cache = new Dictionary(); private static long _configurationGeneration = 1L; private static long _accessSequence; internal static bool IsSupported { get { if (CheckAccess != null && Translate != null && Loading != null && WardAreas != null && WardEnabled != null && WardLocalAccess != null) { return WardContains != null; } return false; } } internal static void Append(Container container, ref string hoverText) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) if (!IsSupported || (Object)container == (Object)null || !HoverBaseTextPolicy.Allows(hoverText)) { return; } Player localPlayer = Player.m_localPlayer; ConfigEntry enabled = PluginConfig.Enabled; bool flag = enabled != null && enabled.Value && (PluginConfig.ShowContentsOnHover?.Value ?? false); if (!flag || (Object)localPlayer == (Object)null || !((Behaviour)container).isActiveAndEnabled || (int)container.m_privacy == 0 || IsBusy(container, null)) { return; } ZNetView val = ValheimContainerIdentity.NetworkView(container); ZDO val2 = (((Object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null || Loading.Invoke(container) || IsBusy(container, val2)) { return; } Vector3 position = ((Component)container).transform.position; Vector3 val3 = position - ((Component)localPlayer).transform.position; bool flag2 = HoverRangePolicy.IsWithinPhysicalReach(((Vector3)(ref val3)).sqrMagnitude, localPlayer.m_maxInteractDistance); if (!flag2) { return; } bool flag3 = !container.m_checkGuardStone || HasStrictWardAccess(position); if (!flag3) { return; } long playerID = localPlayer.GetPlayerID(); bool flag4; try { flag4 = CheckAccess(container, playerID); } catch { return; } if (!flag4) { return; } bool durableClaimAbsent = true; if (!HoverDisclosurePolicy.AllowsBeforeSynchronization(new HoverDisclosureFacts(flag, localPlayerAvailable: true, containerActive: true, networkObjectValid: true, nonPrivate: true, flag3, flag4, flag2, closed: true, mutationIdle: true, durableClaimAbsent, synchronized: false))) { return; } CacheEntry orCreate = GetOrCreate(container); orCreate.LastAccess = ++_accessSequence; string text = val2.GetString(ZDOVars.s_items, string.Empty); bool samePersistedEvidence; HoverPersistedEvidence expectedEvidence = CaptureEvidence(orCreate, text, out samePersistedEvidence); float realtimeSinceStartup = Time.realtimeSinceStartup; switch (HoverCachePolicy.Decide(orCreate.Verified, samePersistedEvidence, orCreate.ConfigurationGeneration, _configurationGeneration, orCreate.NextRetryAt, realtimeSinceStartup)) { case HoverCacheDecision.SuppressUntilRetry: return; case HoverCacheDecision.Refresh: if (!TryRefresh(container, val, val2, text, expectedEvidence, realtimeSinceStartup, orCreate, localPlayer, playerID)) { return; } break; } if (HoverDisclosurePolicy.Allows(new HoverDisclosureFacts(flag, localPlayerAvailable: true, containerActive: true, networkObjectValid: true, nonPrivate: true, flag3, flag4, flag2, closed: true, mutationIdle: true, durableClaimAbsent, orCreate.Verified)) && !string.IsNullOrEmpty(orCreate.Suffix)) { if (!string.Equals(orCreate.BaseHoverText, hoverText, StringComparison.Ordinal)) { orCreate.BaseHoverText = hoverText; orCreate.CombinedHoverText = hoverText + orCreate.Suffix; } hoverText = orCreate.CombinedHoverText; } } internal static void Invalidate(Container container) { if ((Object)(object)container != (Object)null) { Cache.Remove(container); } } internal static void InvalidateConfiguration() { _configurationGeneration++; if (_configurationGeneration == 0L) { _configurationGeneration = 1L; } } internal static void Reset() { Cache.Clear(); _configurationGeneration = 1L; _accessSequence = 0L; } internal static bool TryGetSynchronizedReadSnapshot(Container container, out Inventory inventory, out long revision) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown inventory = null; revision = 0L; if ((Object)container == (Object)null || Loading == null) { return false; } try { ZNetView val = ValheimContainerIdentity.NetworkView(container); ZDO val2 = (((Object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 == null || Loading.Invoke(container) || IsBusy(container, val2)) { return false; } string text = val2.GetString(ZDOVars.s_items, string.Empty); HoverPersistedEvidence evidence = HoverPersistedEvidence.Capture(text, PluginConfig.HoverMaximumSnapshotCharacters.Value); if (!TryGetExactInventory(container, text, evidence, out var inventory2)) { return false; } ZNetView val3 = ValheimContainerIdentity.NetworkView(container); ZDO val4 = (((Object)val3 != (Object)null && val3.IsValid()) ? val3.GetZDO() : null); string a = ((val4 != null) ? val4.GetString(ZDOVars.s_items, string.Empty) : null); if ((Object)(object)val3 != (Object)(object)val || val4 != val2 || val4 == null || Loading.Invoke(container) || IsBusy(container, val4) || !string.Equals(a, text, StringComparison.Ordinal)) { return false; } inventory = inventory2; revision = val4.DataRevision; return true; } catch { inventory = null; revision = 0L; return false; } } private static bool TryRefresh(Container container, ZNetView expectedView, ZDO expectedZdo, string expectedPersistedItems, HoverPersistedEvidence expectedEvidence, float now, CacheEntry entry, Player expectedPlayer, long expectedPlayerId) { //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Expected O, but got Unknown //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Expected O, but got Unknown //IL_025a: Expected O, but got Unknown //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_026c: Expected O, but got Unknown //IL_027d: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) entry.Verified = false; entry.Evidence = expectedEvidence; entry.HasEvidence = true; entry.SetPersistedReference(expectedEvidence.IsAdmissible ? expectedPersistedItems : null); entry.ConfigurationGeneration = _configurationGeneration; entry.NextRetryAt = now + Mathf.Clamp(PluginConfig.HoverRefreshIntervalSeconds.Value, 0.1f, 5f); entry.Suffix = string.Empty; entry.BaseHoverText = null; entry.CombinedHoverText = null; try { if (!expectedEvidence.IsAdmissible || !TryGetExactInventory(container, expectedPersistedItems, expectedEvidence, out var inventory)) { return false; } List allItems = inventory.GetAllItems(); int num = Math.Min(allItems.Count, Mathf.Clamp(PluginConfig.HoverMaximumStacksExamined.Value, 16, 1024)); int num2 = allItems.Count - num; List list = new List(num); for (int i = 0; i < num; i++) { ItemData val = allItems[i]; if (val != null && val.m_stack > 0) { string text = ValheimContainerIdentity.ResourceId(val); if (text.Length == 0 || text.Length > 256) { num2++; } else { list.Add(new HoverContentEntry(text, DisplayName(val, text), val.m_stack)); } } } string suffix = ContainerHoverSummaryFormatter.Format(list, Mathf.Clamp(PluginConfig.HoverMaximumItemKinds.Value, 1, 24), Mathf.Clamp(PluginConfig.HoverItemsPerLine.Value, 1, 4), Mathf.Clamp(PluginConfig.HoverMaximumCharacters.Value, 64, 1024), num2); ZNetView val2 = ValheimContainerIdentity.NetworkView(container); ZDO val3 = (((Object)val2 != (Object)null && val2.IsValid()) ? val2.GetZDO() : null); string text2 = ((val3 != null) ? val3.GetString(ZDOVars.s_items, string.Empty) : null); HoverPersistedEvidence hoverPersistedEvidence = HoverPersistedEvidence.Capture(text2, PluginConfig.HoverMaximumSnapshotCharacters.Value); if ((Object)(object)val2 == (Object)(object)expectedView && val3 == expectedZdo && val3 != null && hoverPersistedEvidence.Equals(expectedEvidence) && string.Equals(text2, expectedPersistedItems, StringComparison.Ordinal)) { ConfigEntry enabled = PluginConfig.Enabled; if (enabled != null && enabled.Value) { ConfigEntry showContentsOnHover = PluginConfig.ShowContentsOnHover; if (showContentsOnHover != null && showContentsOnHover.Value && !((Object)Player.m_localPlayer != (Object)expectedPlayer) && !((Object)expectedPlayer == (Object)null) && ((Behaviour)container).isActiveAndEnabled && (int)container.m_privacy != 0 && !Loading.Invoke(container) && !IsBusy(container, val3)) { Vector3 position = ((Component)container).transform.position; Vector3 val4 = position - ((Component)expectedPlayer).transform.position; if (!HoverRangePolicy.IsWithinPhysicalReach(((Vector3)(ref val4)).sqrMagnitude, expectedPlayer.m_maxInteractDistance) || (container.m_checkGuardStone && !HasStrictWardAccess(position)) || !CheckAccess(container, expectedPlayerId)) { return false; } entry.Suffix = suffix; entry.Verified = true; entry.NextRetryAt = 0f; return true; } } } return false; } catch { return false; } } private static bool TryGetExactInventory(Container container, string persisted, HoverPersistedEvidence evidence, out Inventory inventory) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown inventory = null; if ((Object)container == (Object)null || !evidence.IsAdmissible || Loading.Invoke(container)) { return false; } inventory = container.GetInventory(); if (inventory == null) { return false; } List allItems = inventory.GetAllItems(); if (string.IsNullOrEmpty(persisted)) { return allItems.Count == 0; } if (!HoverSnapshotBounds.AllowsEnvelope(allItems.Count, persisted.Length, PluginConfig.HoverMaximumSnapshotCharacters.Value)) { return false; } int maximumSerializedBytes = HoverSnapshotBounds.SerializedByteCeiling(PluginConfig.HoverMaximumSnapshotCharacters.Value); if (!HasBoundedSerializedShape(allItems, maximumSerializedBytes)) { return false; } ZPackage val = new ZPackage(); inventory.Save(val); if (!HoverSnapshotBounds.MatchesExactSerializedSize(val.Size(), persisted.Length, PluginConfig.HoverMaximumSnapshotCharacters.Value)) { return false; } return string.Equals(val.GetBase64(), persisted, StringComparison.Ordinal); } private static bool IsBusy(Container container, ZDO zdo) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown if ((Object)container == (Object)null || container.IsInUse() || ((Object)container.m_wagon != (Object)null && container.m_wagon.InUse())) { return true; } if (zdo != null) { return zdo.GetBool(ZDOVars.s_inUse, false); } return false; } private static bool HasBoundedSerializedShape(List items, int maximumSerializedBytes) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown if (items == null || items.Count > 1024 || maximumSerializedBytes <= 0) { return false; } long nextBytes = 8L; int num = 0; for (int i = 0; i < items.Count; i++) { ItemData val = items[i]; if (val == null || (Object)val.m_dropPrefab == (Object)null) { return false; } nextBytes += 64; if (!HoverSnapshotBounds.TryAddStringEstimate(nextBytes, ((Object)val.m_dropPrefab).name?.Length ?? 0, maximumSerializedBytes, out nextBytes) || !HoverSnapshotBounds.TryAddStringEstimate(nextBytes, val.m_crafterName?.Length ?? 0, maximumSerializedBytes, out nextBytes)) { return false; } Dictionary customData = val.m_customData; int num2 = customData?.Count ?? 0; if (!HoverSnapshotBounds.AllowsCustomDataAddition(num, num2)) { return false; } num += num2; if (customData == null) { continue; } foreach (KeyValuePair item in customData) { if (!HoverSnapshotBounds.TryAddStringEstimate(nextBytes, item.Key?.Length ?? 0, maximumSerializedBytes, out nextBytes) || !HoverSnapshotBounds.TryAddStringEstimate(nextBytes, item.Value?.Length ?? 0, maximumSerializedBytes, out nextBytes)) { return false; } } } return nextBytes <= maximumSerializedBytes; } private static bool HasStrictWardAccess(Vector3 position) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0049: Unknown result type (might be due to invalid IL or missing references) try { int count = WardAreas.Count; if (count > 4096) { return false; } for (int i = 0; i < count; i++) { PrivateArea val = WardAreas[i]; if (!((Object)val == (Object)null)) { bool num = WardEnabled(val); bool flag = num && WardContains(val, position, 0f); bool localAccess = !flag || WardLocalAccess(val); if (StrictWardDisclosurePolicy.IsHostileOverlap(num, flag, localAccess)) { return false; } } } return true; } catch { return false; } } private static string DisplayName(ItemData item, string fallback) { if (!HoverLabelPolicy.TryPrepareLocalizationToken(item?.m_shared?.m_name, out var token)) { return HoverLabelPolicy.NormalizeDisplayLabel(fallback, "Item"); } try { Localization instance = Localization.instance; string key; return HoverLabelPolicy.NormalizeDisplayLabel((instance != null && HoverLabelPolicy.TryGetTranslationKey(token, out key)) ? Translate(instance, key) : HoverLabelPolicy.WithoutLocalizationMarker(token), fallback); } catch { return HoverLabelPolicy.NormalizeDisplayLabel(HoverLabelPolicy.WithoutLocalizationMarker(token), fallback); } } private static HoverPersistedEvidence CaptureEvidence(CacheEntry entry, string persisted, out bool samePersistedEvidence) { if (persisted == null) { persisted = string.Empty; } string persisted2 = null; if (entry.HasEvidence && entry.TryGetPersistedReference(out persisted2) && (object)persisted2 == persisted) { samePersistedEvidence = true; return entry.Evidence; } HoverPersistedEvidence hoverPersistedEvidence = HoverPersistedEvidence.Capture(persisted, PluginConfig.HoverMaximumSnapshotCharacters.Value); samePersistedEvidence = entry.HasEvidence && entry.Evidence.Equals(hoverPersistedEvidence); if (samePersistedEvidence && hoverPersistedEvidence.IsAdmissible) { samePersistedEvidence = persisted2 != null && string.Equals(persisted2, persisted, StringComparison.Ordinal); } if (samePersistedEvidence && hoverPersistedEvidence.IsAdmissible) { entry.SetPersistedReference(persisted); } return hoverPersistedEvidence; } private static CacheEntry GetOrCreate(Container container) { if (Cache.TryGetValue(container, out var value)) { return value; } if (Cache.Count >= 512) { EvictOldest(); } value = new CacheEntry(); Cache.Add(container, value); return value; } private static void EvictOldest() { Container val = null; long num = long.MaxValue; foreach (KeyValuePair item in Cache) { if (item.Value.LastAccess < num) { val = item.Key; num = item.Value.LastAccess; } } if ((Object)(object)val != (Object)null) { Cache.Remove(val); } } private static CheckAccessDelegate ResolveCheckAccess() { try { MethodInfo methodInfo = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null); return (methodInfo == null) ? null : AccessTools.MethodDelegate(methodInfo, (object)null, true); } catch { return null; } } private static TranslateDelegate ResolveTranslate() { try { MethodInfo methodInfo = AccessTools.Method(typeof(Localization), "Translate", new Type[1] { typeof(string) }, (Type[])null); return (methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != typeof(string)) ? null : AccessTools.MethodDelegate(methodInfo, (object)null, true); } catch { return null; } } private static FieldRef ResolveLoading() { try { return AccessTools.FieldRefAccess("m_loading"); } catch { return null; } } private static List ResolveWardAreas() { try { return AccessTools.Field(typeof(PrivateArea), "m_allAreas")?.GetValue(null) as List; } catch { return null; } } private static WardStateDelegate ResolveWardState(string methodName) { try { MethodInfo methodInfo = AccessTools.Method(typeof(PrivateArea), methodName, Type.EmptyTypes, (Type[])null); return (methodInfo == null) ? null : AccessTools.MethodDelegate(methodInfo, (object)null, true); } catch { return null; } } private static WardContainsDelegate ResolveWardContains() { try { MethodInfo methodInfo = AccessTools.Method(typeof(PrivateArea), "IsInside", new Type[2] { typeof(Vector3), typeof(float) }, (Type[])null); return (methodInfo == null) ? null : AccessTools.MethodDelegate(methodInfo, (object)null, true); } catch { return null; } } } internal sealed class ContainerIndex { private readonly struct CellKey : IEquatable { internal int X { get; } internal int Z { get; } internal CellKey(int x, int z) { X = x; Z = z; } internal static CellKey From(Vector3 value) { //IL_0000: 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) return new CellKey(ContainerSpatialPolicy.CellCoordinate(value.x), ContainerSpatialPolicy.CellCoordinate(value.z)); } public bool Equals(CellKey other) { if (X == other.X) { return Z == other.Z; } return false; } public override bool Equals(object obj) { if (obj is CellKey other) { return Equals(other); } return false; } public override int GetHashCode() { return (X * 397) ^ Z; } } private readonly struct Entry { internal int InstanceId { get; } internal Container Container { get; } internal Entry(int instanceId, Container container) { InstanceId = instanceId; Container = container; } } private readonly struct Membership { internal CellKey Cell { get; } internal string EndpointId { get; } internal Container Container { get; } internal Membership(CellKey cell, string endpointId, Container container) { Cell = cell; EndpointId = endpointId ?? string.Empty; Container = container; } } private readonly struct Candidate { internal SpatialCandidateKey Key { get; } internal Container Container { get; } internal Candidate(SpatialCandidateKey key, Container container) { Key = key; Container = container; } } private sealed class CandidateComparer : IComparer { internal static CandidateComparer Instance { get; } = new CandidateComparer(); public int Compare(Candidate left, Candidate right) { return left.Key.CompareTo(right.Key); } } private readonly object _gate = new object(); private readonly Dictionary> _cells = new Dictionary>(); private readonly Dictionary _membership = new Dictionary(); private readonly Dictionary> _endpointMembers = new Dictionary>(StringComparer.Ordinal); internal void Add(Container container) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)container == (Object)null) { return; } int instanceID; Vector3 position; try { instanceID = ((Object)container).GetInstanceID(); position = ((Component)container).transform.position; } catch { return; } lock (_gate) { if (!Finite(position)) { RemoveLocked(instanceID, container); return; } CellKey cellKey = CellKey.From(position); Membership value; bool num = _membership.TryGetValue(instanceID, out value); bool flag = num && value.Cell.Equals(cellKey); if (ContainerSpatialPolicy.CanRetain(exactContainerPresent: flag && _cells.TryGetValue(value.Cell, out var value2) && ContainsExact(value2, instanceID, container), membershipExists: num, cellUnchanged: flag, endpointUnchanged: !string.IsNullOrEmpty(value.EndpointId))) { return; } string endpointId; try { endpointId = ValheimContainerIdentity.EndpointId(container); } catch { return; } RemoveLocked(instanceID, container); if (ContainerSpatialPolicy.CanIndexEndpoint(endpointId)) { if (!_cells.TryGetValue(cellKey, out value2)) { value2 = new List(); _cells.Add(cellKey, value2); } value2.Add(new Entry(instanceID, container)); _membership.Add(instanceID, new Membership(cellKey, endpointId, container)); AddEndpoint(endpointId, instanceID); } } } internal int RefreshLoadedContainers() { Container[] array; try { array = Object.FindObjectsByType((FindObjectsSortMode)0); } catch { return 0; } for (int i = 0; i < array.Length; i++) { Add(array[i]); } return array.Length; } internal void Remove(Container container) { if (container == null) { return; } int instanceID; try { instanceID = ((Object)container).GetInstanceID(); } catch { return; } lock (_gate) { RemoveLocked(instanceID, container); } } internal IReadOnlyList Nearest(Vector3 origin, float radius, int maximum, out bool truncated) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) if (maximum <= 0) { throw new ArgumentOutOfRangeException("maximum"); } if (!Finite(origin) || float.IsNaN(radius) || float.IsInfinity(radius) || radius <= 0f) { truncated = false; return Array.Empty(); } radius = Math.Min(50f, radius); maximum = Math.Min(256, maximum); float num = radius * radius; CellKey cellKey = CellKey.From(origin - new Vector3(radius, 0f, radius)); CellKey cellKey2 = CellKey.From(origin + new Vector3(radius, 0f, radius)); SortedSet sortedSet = new SortedSet(CandidateComparer.Instance); int num2 = 0; lock (_gate) { for (int i = cellKey.X; i <= cellKey2.X; i++) { for (int j = cellKey.Z; j <= cellKey2.Z; j++) { CellKey key = new CellKey(i, j); if (!_cells.TryGetValue(key, out var value)) { continue; } for (int num3 = value.Count - 1; num3 >= 0; num3--) { Entry entry = value[num3]; Container container = entry.Container; if ((Object)(object)container == (Object)null) { value.RemoveAt(num3); RemoveIndexesLocked(entry.InstanceId); } else { try { if (((Behaviour)container).isActiveAndEnabled) { Vector3 val = ((Component)container).transform.position - origin; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (!float.IsNaN(sqrMagnitude) && !float.IsInfinity(sqrMagnitude) && !(sqrMagnitude > num) && _membership.TryGetValue(entry.InstanceId, out var value2) && ContainerSpatialPolicy.CanIndexEndpoint(value2.EndpointId) && _endpointMembers.TryGetValue(value2.EndpointId, out var value3) && ContainerSpatialPolicy.IsUniqueEndpointMemberCount(value3.Count)) { if (num2 < int.MaxValue) { num2++; } string endpointId = value2.EndpointId; sortedSet.Add(new Candidate(new SpatialCandidateKey(sqrMagnitude, endpointId, entry.InstanceId), container)); if (sortedSet.Count > maximum) { sortedSet.Remove(sortedSet.Max); } } } } catch { } } } if (value.Count == 0) { _cells.Remove(key); } } } } List list = new List(sortedSet.Count); foreach (Candidate item in sortedSet) { list.Add(item.Container); } truncated = num2 > maximum; return list.AsReadOnly(); } internal bool TryGet(string endpointId, out Container container) { container = null; if (string.IsNullOrEmpty(endpointId)) { return false; } lock (_gate) { if (!_endpointMembers.TryGetValue(endpointId, out var value) || !ContainerSpatialPolicy.IsUniqueEndpointMemberCount(value.Count)) { return false; } using HashSet.Enumerator enumerator = value.GetEnumerator(); if (enumerator.MoveNext()) { int current = enumerator.Current; if (!_membership.TryGetValue(current, out var value2) || !string.Equals(value2.EndpointId, endpointId, StringComparison.Ordinal) || (Object)(object)value2.Container == (Object)null) { RemoveIndexesLocked(current); return false; } container = value2.Container; return true; } } return false; } private void RemoveLocked(int instanceId, Container container) { if (!_membership.TryGetValue(instanceId, out var value)) { return; } if (_cells.TryGetValue(value.Cell, out var value2)) { for (int num = value2.Count - 1; num >= 0; num--) { if (value2[num].InstanceId == instanceId || value2[num].Container == container) { value2.RemoveAt(num); } } if (value2.Count == 0) { _cells.Remove(value.Cell); } } RemoveIndexesLocked(instanceId); } private void RemoveIndexesLocked(int instanceId) { if (!_membership.TryGetValue(instanceId, out var value)) { return; } _membership.Remove(instanceId); if (!string.IsNullOrEmpty(value.EndpointId) && _endpointMembers.TryGetValue(value.EndpointId, out var value2)) { value2.Remove(instanceId); if (value2.Count == 0) { _endpointMembers.Remove(value.EndpointId); } } } private void AddEndpoint(string endpointId, int instanceId) { if (!string.IsNullOrEmpty(endpointId)) { if (!_endpointMembers.TryGetValue(endpointId, out var value)) { value = new HashSet(); _endpointMembers.Add(endpointId, value); } value.Add(instanceId); } } private static bool ContainsExact(List entries, int instanceId, Container container) { for (int i = 0; i < entries.Count; i++) { if (entries[i].InstanceId == instanceId && entries[i].Container == container) { return true; } } return false; } private static bool Finite(Vector3 value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (!float.IsNaN(value.x) && !float.IsInfinity(value.x) && !float.IsNaN(value.y) && !float.IsInfinity(value.y) && !float.IsNaN(value.z)) { return !float.IsInfinity(value.z); } return false; } } internal static class ValheimContainerIdentity { internal unsafe static string EndpointId(Container container) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)container == (Object)null) { return string.Empty; } ZNetView val = NetworkView(container); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid()) ? val.GetZDO() : null); if (val2 != null) { ZDOID uid = val2.m_uid; return "valheim.zdo:" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); } return string.Empty; } internal static ZNetView NetworkView(Container container) { if ((Object)(object)container == (Object)null) { return null; } if (!((Object)(object)container.m_rootObjectOverride != (Object)null)) { return ((Component)container).GetComponent(); } return container.m_rootObjectOverride; } internal static string TypeId(Container container) { string text = (((Object)(object)container == (Object)null) ? "container" : ((Object)((Component)container).gameObject).name); if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - "(Clone)".Length); } if (!string.IsNullOrWhiteSpace(text)) { return text.Trim(); } return "container"; } internal static string ResourceId(ItemData item) { if (item == null) { return string.Empty; } if ((Object)(object)item.m_dropPrefab != (Object)null && !string.IsNullOrWhiteSpace(((Object)item.m_dropPrefab).name)) { string text = ((Object)item.m_dropPrefab).name; if (text.EndsWith("(Clone)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - "(Clone)".Length); } return text.Trim(); } if (item.m_shared != null && !string.IsNullOrWhiteSpace(item.m_shared.m_name)) { return item.m_shared.m_name.Trim(); } return string.Empty; } } [HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")] internal static class StorageSearchCursorLeasePatch { [HarmonyPostfix] [HarmonyPriority(0)] private static void Postfix() { StorageSearchPanel.RenewCursorLease(); } } [HarmonyPatch(typeof(Player), "TakeInput")] internal static class StorageSearchInputGatePatch { [HarmonyAfter(new string[] { "chazman.RunicBuildCamera" })] private static void Postfix(Player __instance, ref bool __result) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && Plugin.SearchPanelOpen) { __result = false; } } } [HarmonyPatch(typeof(Container), "Awake")] internal static class ContainerAwakePatch { private static void Postfix(Container __instance) { ContainerHoverContents.Invalidate(__instance); Plugin.Index?.Add(__instance); } } [HarmonyPatch(typeof(Container), "OnDestroyed")] internal static class ContainerDestroyPatch { private static void Postfix(Container __instance, bool __runOriginal) { if (__runOriginal) { ContainerHoverContents.Invalidate(__instance); Plugin.Index?.Remove(__instance); } } } [HarmonyPatch(typeof(Container), "CheckForChanges")] internal static class ContainerSpatialRefreshPatch { private static void Postfix(Container __instance) { Plugin.Index?.Add(__instance); } } [HarmonyPatch(typeof(Container), "GetHoverText", new Type[] { })] internal static class ContainerHoverTextPatch { [HarmonyPriority(0)] private static void Postfix(Container __instance, ref string __result) { try { ContainerHoverContents.Append(__instance, ref __result); } catch { } } } internal sealed class ControllerBindingState { internal string Signature { get; } internal string ModifierName { get; } internal string QuickStackName { get; } internal string RestockName { get; } internal string SortName { get; } internal string ConsolidateName { get; } internal string SearchName { get; } internal ButtonDef Modifier { get; } internal ButtonDef QuickStack { get; } internal ButtonDef Restock { get; } internal ButtonDef Sort { get; } internal ButtonDef Consolidate { get; } internal ButtonDef Search { get; } internal bool ModifierValid => Modifier != null; internal bool QuickStackValid => QuickStack != null; internal bool RestockValid => Restock != null; internal bool SortValid => Sort != null; internal bool ConsolidateValid => Consolidate != null; internal bool SearchValid => Search != null; internal int ValidRouteCount => (QuickStackValid ? 1 : 0) + (RestockValid ? 1 : 0) + (SortValid ? 1 : 0) + (ConsolidateValid ? 1 : 0) + (SearchValid ? 1 : 0); internal ControllerBindingState(string signature, string modifierName, string quickStackName, string restockName, string sortName, string consolidateName, string searchName, ButtonDef modifier, ButtonDef quickStack, ButtonDef restock, ButtonDef sort, ButtonDef consolidate, ButtonDef search) { Signature = signature; ModifierName = modifierName; QuickStackName = quickStackName; RestockName = restockName; SortName = sortName; ConsolidateName = consolidateName; SearchName = searchName; Modifier = modifier; QuickStack = quickStack; Restock = restock; Sort = sort; Consolidate = consolidate; Search = search; } } internal static class StorageControllerCollisionGuard { private static StorageActionEdges _pendingEdge; private static ButtonDef _latchedModifier; private static ControllerBindingState _sessionBindings; private static ControllerSessionPaths _sessionPaths; private static int _releasedFrame = -1; private static int _capturedFrame = -1; private static int _lastProbeFrame = -1; internal static StorageActionEdges ObserveAndConsume(ControllerBindingState bindings, StorageRouteContext context) { Observe(bindings, context); return ConsumePendingEdge(); } internal static StorageActionEdges ConsumePendingEdge() { StorageActionEdges pendingEdge = _pendingEdge; _pendingEdge = StorageActionEdges.None; return pendingEdge; } internal static bool ShouldSuppress(string buttonName) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Invalid comparison between Unknown and I4 ConfigEntry enabled = PluginConfig.Enabled; if (enabled == null || !enabled.Value) { return false; } UpdateReleaseState(); ConfigEntry controllerShortcuts = PluginConfig.ControllerShortcuts; if (controllerShortcuts != null && controllerShortcuts.Value && ZInput.instance != null) { bool flag; if (_latchedModifier != null) { flag = _latchedModifier.Held; } else { string text = (PluginConfig.ControllerModifier?.Value ?? string.Empty).Trim(); ButtonDef val = ((text.Length == 0) ? null : ZInput.instance.GetButtonDef(text)); flag = val != null && val.Held; } if (flag && _lastProbeFrame != Time.frameCount) { _lastProbeFrame = Time.frameCount; Observe(_sessionBindings ?? StorageControllerBindings.Resolve(), Plugin.CaptureRouteContext()); } } if (_latchedModifier == null || ZInput.instance == null) { return false; } ButtonDef buttonDef = ZInput.instance.GetButtonDef(buttonName); if (buttonDef == null || (int)buttonDef.Source != 180) { return false; } string actionPath = buttonDef.GetActionPath(true); if (actionPath != null) { return _sessionPaths.Contains(actionPath); } return false; } internal static void Reset() { _pendingEdge = StorageActionEdges.None; _latchedModifier = null; _sessionBindings = null; _sessionPaths = default(ControllerSessionPaths); _releasedFrame = -1; _capturedFrame = -1; _lastProbeFrame = -1; } private static void Observe(ControllerBindingState bindings, StorageRouteContext context) { UpdateReleaseState(); if (bindings == null || !bindings.ModifierValid || !bindings.Modifier.Held || _capturedFrame == Time.frameCount) { return; } StorageActionEdges storageActionEdges = StorageActionEdges.None; if (bindings.SortValid && bindings.Sort.Pressed) { storageActionEdges = StorageActionEdges.ControllerSort; } else if (bindings.QuickStackValid && bindings.QuickStack.Pressed) { storageActionEdges = StorageActionEdges.ControllerQuickStack; } else if (bindings.RestockValid && bindings.Restock.Pressed) { storageActionEdges = StorageActionEdges.ControllerRestock; } else if (bindings.ConsolidateValid && bindings.Consolidate.Pressed) { storageActionEdges = StorageActionEdges.ControllerConsolidate; } else if (bindings.SearchValid && bindings.Search.Pressed) { storageActionEdges = StorageActionEdges.ControllerSearch; } if (storageActionEdges == StorageActionEdges.None) { return; } _capturedFrame = Time.frameCount; _pendingEdge = storageActionEdges; StorageActionRequest request = StorageActionRouter.Select(storageActionEdges); StorageRouteDecision decision = StorageActionRouter.Route(request, context); bool flag = _latchedModifier != null; if (ControllerChordSessionPolicy.Decide(flag, decision) == ControllerChordDisposition.ReportWithoutConsume) { return; } if (!flag) { StartSession(bindings); } _releasedFrame = -1; if (PluginConfig.DebugTransfers.Value) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("input-consumed source=controller action=" + StorageActionDiagnostics.ActionCode(request.Action) + " result=" + ((decision.Outcome == StorageRouteOutcome.Execute) ? "authorized" : "blocked-in-owned-session") + " modifier-session=active configured-button-aliases=suppressed-until-full-release")); } } } private static void UpdateReleaseState() { if (_latchedModifier != null) { if (_latchedModifier.Held || AnySessionPrimaryHeld()) { _releasedFrame = -1; } else if (_releasedFrame < 0) { _releasedFrame = Time.frameCount; } else if (_releasedFrame != Time.frameCount) { _latchedModifier = null; _sessionBindings = null; _sessionPaths = default(ControllerSessionPaths); _releasedFrame = -1; } } } private static void StartSession(ControllerBindingState bindings) { _latchedModifier = bindings.Modifier; _sessionBindings = bindings; _sessionPaths = new ControllerSessionPaths(Path(bindings.Modifier), Path(bindings.QuickStack), Path(bindings.Restock), Path(bindings.Sort), Path(bindings.Consolidate), Path(bindings.Search)); } private static bool AnySessionPrimaryHeld() { if (!IsHeld(_sessionBindings?.QuickStack) && !IsHeld(_sessionBindings?.Restock) && !IsHeld(_sessionBindings?.Sort) && !IsHeld(_sessionBindings?.Consolidate)) { return IsHeld(_sessionBindings?.Search); } return true; } private static bool IsHeld(ButtonDef definition) { if (definition != null) { return definition.Held; } return false; } private static string Path(ButtonDef definition) { return ((definition != null) ? definition.GetActionPath(true) : null) ?? string.Empty; } } [HarmonyPatch(typeof(ZInput), "GetButton", new Type[] { typeof(string) })] internal static class StorageControllerGetButtonPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })] private static bool Prefix(string name, ref bool __result) { return AllowOrConsume(name, ref __result); } private static bool AllowOrConsume(string name, ref bool result) { if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name)) { result = false; return false; } if (!StorageControllerCollisionGuard.ShouldSuppress(name)) { return true; } result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })] internal static class StorageControllerGetButtonDownPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })] private static bool Prefix(string name, ref bool __result) { if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name)) { __result = false; return false; } if (!StorageControllerCollisionGuard.ShouldSuppress(name)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonUp", new Type[] { typeof(string) })] internal static class StorageControllerGetButtonUpPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })] private static bool Prefix(string name, ref bool __result) { if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name)) { __result = false; return false; } if (!StorageControllerCollisionGuard.ShouldSuppress(name)) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonPressedTimer", new Type[] { typeof(string) })] internal static class StorageControllerPressedTimerPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })] private static bool Prefix(string name, ref float __result) { if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name)) { __result = 0f; return false; } if (!StorageControllerCollisionGuard.ShouldSuppress(name)) { return true; } __result = 0f; return false; } } [HarmonyPatch(typeof(ZInput), "GetButtonLastPressedTimer", new Type[] { typeof(string) })] internal static class StorageControllerLastPressedTimerPatch { [HarmonyPriority(800)] [HarmonyBefore(new string[] { "chazman.RunicAgriculture", "chazman.RunicInventory" })] private static bool Prefix(string name, ref float __result) { if (StorageSearchGameplayInputGuard.ShouldSuppressPrimaryAttack(name)) { __result = 0f; return false; } if (!StorageControllerCollisionGuard.ShouldSuppress(name)) { return true; } __result = 0f; return false; } } [HarmonyPatch(typeof(ZInput), "GetKeyDown", new Type[] { typeof(KeyCode), typeof(bool) })] internal static class StorageSearchEscapeKeyPatch { [HarmonyPriority(800)] private static bool Prefix(KeyCode key, ref bool __result) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (!StorageSearchGameplayInputGuard.ShouldSuppressEscape(key)) { return true; } __result = false; return false; } } internal sealed class RaycastHitDistanceComparer : IComparer { internal static readonly RaycastHitDistanceComparer Instance = new RaycastHitDistanceComparer(); public int Compare(RaycastHit left, RaycastHit right) { return ((RaycastHit)(ref left)).distance.CompareTo(((RaycastHit)(ref right)).distance); } } internal sealed class StorageActions { private sealed class SearchAccumulator { private readonly List _containers = new List(); internal string ResourceId { get; } internal string DisplayName { get; } internal int Quantity { get; set; } internal SearchAccumulator(string resourceId, string displayName) { ResourceId = resourceId; DisplayName = displayName; } internal void AddContainer(Container container) { if ((Object)(object)container != (Object)null && !_containers.Contains(container)) { _containers.Add(container); } } internal StorageSearchEntry ToEntry() { return new StorageSearchEntry(ResourceId, DisplayName, Quantity, _containers.AsReadOnly()); } } private static readonly FieldInfo CurrentContainerField = AccessTools.Field(typeof(InventoryGui), "m_currentContainer"); private static readonly MethodInfo InventoryChangedMethod = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null); private readonly ContainerIndex _index; private readonly StorageSearchPanel _searchPanel; internal StorageActions(ContainerIndex index, StorageSearchPanel searchPanel) { _index = index ?? throw new ArgumentNullException("index"); _searchPanel = searchPanel ?? throw new ArgumentNullException("searchPanel"); } internal void QuickStack() { if (!TryBeginMutation("Quick Stack", out var player, out var mutationLease)) { return; } using (mutationLease) { Inventory inventory = ((Humanoid)player).GetInventory(); IReadOnlyList readOnlyList = Nearby(player, requireWritable: true, "quick-stack"); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; Dictionary dictionary = new Dictionary(); HashSet hashSet = new HashSet(); List list = new List(inventory.GetAllItems()); list.Sort(CompareGridPosition); if (!TryCaptureProtection(list, out var snapshot, out var failureCode)) { Message(player, "Runic Storage: carried-item protection could not be proven; Quick Stack changed nothing."); LogAction("quick-stack", failureCode, $"stacks={list.Count} moved=0"); return; } int num9 = 0; for (int i = 0; i < list.Count; i++) { ItemData val = list[i]; StorageProtectionState storageProtectionState = snapshot.StateAt(i); if (IsProtected(player, val, storageProtectionState)) { num4++; if (storageProtectionState == StorageProtectionState.Locked) { num9++; } if (val != null && (val.m_equipped || ((Humanoid)player).IsItemEquiped(val))) { num6++; } else if (val != null && PluginConfig.ProtectHotbar.Value && val.m_gridPos.y == 0) { num5++; } continue; } string text = ValheimContainerIdentity.ResourceId(val); if (text.Length == 0) { continue; } num3++; int stack = val.m_stack; bool flag = false; foreach (Container item in readOnlyList) { if (val.m_stack <= 0) { break; } if (!dictionary.TryGetValue(item, out var value)) { if (hashSet.Contains(item)) { continue; } if (!StorageContainerAuthority.TryGetSynchronizedServerInventory(item, out value)) { hashSet.Add(item); num8++; LogTransfer(PlayerEndpointId(player), ValheimContainerIdentity.EndpointId(item), text, 0, "ownership.denied"); continue; } dictionary[item] = value; } if (ContainsResource(value, text)) { if (!flag) { num2 = AddSaturated(num2, stack); num7++; flag = true; } int stack2 = val.m_stack; int num10 = ValheimContainerService.MoveUpTo(inventory, value, val, stack2, player, mutationLease, null, item); num = AddSaturated(num, num10); LogTransfer(PlayerEndpointId(player), ValheimContainerIdentity.EndpointId(item), text, num10, (num10 > 0) ? "ok" : "destination.full"); if (num10 >= stack2) { break; } } } } QuickStackNoOpReason reason = QuickStackDiagnostics.Classify(new QuickStackObservation(list.Count, num3, num4, readOnlyList.Count, num7, num)); int num11 = Math.Max(0, num2 - num); string text2 = ((num > 0) ? $"Runic Storage: moved {num} item(s); {num11} matching item(s) remained." : QuickStackNoOpFeedback(reason, num5, num6, num9)); Message(player, text2); LogAction("quick-stack", (num > 0) ? "ok" : QuickStackDiagnostics.ReasonCode(reason), $"carriedStacks={list.Count} eligibleStacks={num3} protectedStacks={num4} " + $"hotbarProtected={num5} equippedProtected={num6} itemLockProtected={num9} " + $"authorizedContainers={readOnlyList.Count} matchedStacks={num7} " + $"ownershipDenied={num8} moved={num} remainder={num11}"); } } internal void StoreAllOpenedContainer() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) if (!TryBeginMutation("Store All", out var player, out var mutationLease)) { return; } using (mutationLease) { Container val = (Container)(((Object)InventoryGui.instance == (Object)null || CurrentContainerField == null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)val == (Object)null) { Message(player, "Runic Storage: open a container before using Store All."); LogAction("store-all-opened-container", "ui.open-container-required", "container=false moved=0"); return; } if ((int)val.m_privacy == 0 || !ValheimContainerService.CanDiscover(val, player.GetPlayerID(), requireWritable: true, allowCurrentUse: true)) { Message(player, "Runic Storage: that container is personal, busy, or denied; Store All changed nothing."); LogAction("store-all-opened-container", "container.denied", "authorized=false moved=0"); return; } if (!StorageContainerAuthority.TryGetExactOpenedLocalOwnerInventory(val, player, out var inventory)) { Message(player, "Runic Storage: ownership changed before Store All; nothing was changed."); LogAction("store-all-opened-container", "ownership.denied", "owner=false moved=0"); return; } Inventory inventory2 = ((Humanoid)player).GetInventory(); List list = new List(inventory2.GetAllItems()); list.Sort(CompareGridPosition); if (!TryCaptureProtection(list, out var snapshot, out var failureCode)) { Message(player, "Runic Storage: carried-item protection could not be proven; Store All changed nothing."); LogAction("store-all-opened-container", failureCode, $"stacks={list.Count} moved=0"); return; } int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; for (int i = 0; i < list.Count; i++) { ItemData val2 = list[i]; if (val2 != null && val2.m_stack > 0) { if (val2.m_shared != null && val2.m_shared.m_questItem) { num4++; continue; } if (IsProtected(player, val2, snapshot.StateAt(i))) { num3++; continue; } num2 = AddSaturated(num2, val2.m_stack); int stack = val2.m_stack; int num5 = ValheimContainerService.MoveUpTo(inventory2, inventory, val2, stack, player, mutationLease, null, val); num = AddSaturated(num, num5); LogTransfer(PlayerEndpointId(player), ValheimContainerIdentity.EndpointId(val), ValheimContainerIdentity.ResourceId(val2), num5, (num5 > 0) ? "ok" : "destination.full"); } } int num6 = Math.Max(0, num2 - num); string result; string text; if (num > 0) { result = ((num6 == 0) ? "ok" : "partial"); text = ((num6 == 0) ? $"Runic Storage: stored {num} item(s); protected, hotbar, equipped, and quest items were kept." : $"Runic Storage: stored {num} item(s); {num6} eligible item(s) could not fit."); } else if (list.Count == 0) { result = "inventory.empty"; text = "Runic Storage: your carried inventory is empty."; } else if (num2 == 0) { result = "inventory.all-protected"; text = "Runic Storage: every carried stack is protected, equipped, on the protected hotbar, or a quest item."; } else { result = "destination.full"; text = "Runic Storage: the opened container has no room for eligible carried items."; } Message(player, text); LogAction("store-all-opened-container", result, $"stacks={list.Count} eligibleQuantity={num2} protectedStacks={num3} questStacks={num4} moved={num} remainder={num6}"); } } internal void Restock() { if (!TryBeginMutation("Restock", out var player, out var mutationLease)) { return; } using (mutationLease) { Dictionary dictionary = ParseTargets(PluginConfig.RestockTargets.Value); if (dictionary.Count == 0) { Message(player, "Runic Storage: Restock.Targets has no valid Item=Amount entries."); LogAction("restock", "config.targets-invalid", "targets=0"); return; } Inventory inventory = ((Humanoid)player).GetInventory(); List list = new List(inventory.GetAllItems()); list.Sort(CompareGridPosition); if (!TryCaptureProtection(list, out var snapshot, out var failureCode)) { Message(player, "Runic Storage: carried-item protection could not be proven; Restock changed nothing."); LogAction("restock", failureCode, $"stacks={list.Count} moved=0"); return; } int num = 0; foreach (KeyValuePair item in dictionary) { num = AddSaturated(num, Math.Max(0, item.Value - CountMatching(inventory, item.Key))); } if (num == 0) { Message(player, "Runic Storage: every configured restock target is already met."); LogAction("restock", "targets.already-met", $"targets={dictionary.Count} requested=0"); return; } foreach (KeyValuePair item2 in dictionary) { if (Math.Max(0, item2.Value - CountMatching(inventory, item2.Key)) > 0 && HasProtectedPartialMatch(player, list, in snapshot, item2.Key)) { Message(player, "Runic Storage: Restock would modify a protected partial target stack; nothing was changed."); LogAction("restock", "protection.target-partial", "target=" + SafeLogValue(item2.Key) + " moved=0"); return; } } IReadOnlyList readOnlyList = Nearby(player, requireWritable: true, "restock", allowCurrentUse: true); if (readOnlyList.Count == 0) { Message(player, "Runic Storage: no authorized public container is available in range for restocking."); LogAction("restock", "discovery.none-authorized", $"targets={dictionary.Count} requested={num}"); return; } int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; Dictionary dictionary2 = new Dictionary(); HashSet hashSet = new HashSet(); foreach (KeyValuePair item3 in dictionary) { int num6 = CountMatching(inventory, item3.Key); int num7 = Math.Max(0, item3.Value - num6); foreach (Container item4 in readOnlyList) { if (num7 == 0) { break; } if (!dictionary2.TryGetValue(item4, out var value)) { if (hashSet.Contains(item4)) { continue; } if (!TryGetWritableInventory(item4, player, out value)) { hashSet.Add(item4); num3++; continue; } dictionary2[item4] = value; } foreach (ItemData item5 in new List(value.GetAllItems())) { if (num7 == 0) { break; } if (Matches(item5, item3.Key)) { num4++; int maximumQuantity = Math.Min(num7, item5.m_stack); int num8 = ValheimContainerService.MoveUpTo(value, inventory, item5, maximumQuantity, player, mutationLease, item4); num7 -= num8; num2 = AddSaturated(num2, num8); LogTransfer(ValheimContainerIdentity.EndpointId(item4), PlayerEndpointId(player), item3.Key, num8, (num8 > 0) ? "ok" : "player.full"); if (num8 == 0) { break; } } } } num5 = AddSaturated(num5, num7); } string result; string text; if (num2 > 0) { result = ((num5 == 0) ? "ok" : "partial"); text = ((num5 == 0) ? $"Runic Storage: restocked {num2} item(s); all configured targets are met." : $"Runic Storage: restocked {num2} item(s); {num5} target item(s) remain unavailable or could not fit."); } else if (num4 == 0) { result = "resource.targets-unavailable"; text = "Runic Storage: the missing configured target items were not found in authorized nearby containers."; } else { result = "player.full-or-ownership-unavailable"; text = "Runic Storage: matching items were found, but your inventory was full or container ownership changed."; } Message(player, text); LogAction("restock", result, $"targets={dictionary.Count} requested={num} sources={readOnlyList.Count} " + $"matchingStacks={num4} ownershipDenied={num3} moved={num2} unmet={num5}"); } } internal void Search() { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { LogAction("search", "player.unavailable", "player=false"); return; } IReadOnlyList readOnlyList = Nearby(localPlayer, requireWritable: false, "search", allowCurrentUse: true); if (readOnlyList.Count == 0) { Message(localPlayer, "Runic Storage: no authorized public container is visible in the configured range."); LogAction("search", "discovery.none-authorized", "containers=0"); return; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); int num = 0; int num2 = 0; for (int i = 0; i < readOnlyList.Count; i++) { Container container = readOnlyList[i]; if (!TryGetSearchInventory(container, localPlayer, out var inventory)) { num++; continue; } List allItems = inventory.GetAllItems(); for (int j = 0; j < allItems.Count && num2 < 8192; j++) { num2++; ItemData val = allItems[j]; string text = ValheimContainerIdentity.ResourceId(val); if (text.Length == 0 || val == null || val.m_stack <= 0) { continue; } if (!dictionary.TryGetValue(text, out var value)) { if (dictionary.Count >= StorageSearchPanel.EntryLimit) { continue; } value = new SearchAccumulator(text, FriendlyItemName(val)); dictionary.Add(text, value); } value.Quantity = AddSaturated(value.Quantity, val.m_stack); value.AddContainer(container); } } List list = new List(dictionary.Count); foreach (SearchAccumulator value2 in dictionary.Values) { list.Add(value2.ToEntry()); } list.Sort(delegate(StorageSearchEntry left, StorageSearchEntry right) { int num3 = StringComparer.OrdinalIgnoreCase.Compare(left.DisplayName, right.DisplayName); return (num3 == 0) ? StringComparer.Ordinal.Compare(left.ResourceId, right.ResourceId) : num3; }); if (list.Count == 0) { Message(localPlayer, "Runic Storage: the nearby synchronized containers are empty."); LogAction("search", "inventory.empty", $"containers={readOnlyList.Count} unsynchronized={num}"); return; } _searchPanel.Open(list); Message(localPlayer, "Runic Storage: choose an item from the nearby-chest list."); LogAction("search", "ok", $"containers={readOnlyList.Count} unsynchronized={num} kinds={list.Count} stacks={num2}"); } internal void SortOpenedContainer() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) if (!TryBeginMutation("Sort", out var player, out var mutationLease)) { return; } using (mutationLease) { Container val = (Container)(((Object)InventoryGui.instance == (Object)null || CurrentContainerField == null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)val == (Object)null) { Message(player, "Runic Storage: open a container before sorting."); LogAction("sort-opened-container", "ui.open-container-required", "container=false"); return; } if ((int)val.m_privacy == 0 || !ValheimContainerService.CanDiscover(val, player.GetPlayerID(), requireWritable: true, allowCurrentUse: true)) { Message(player, "Runic Storage: that container is personal, busy, or denied."); LogAction("sort-opened-container", "container.denied", "authorized=false"); return; } if (!StorageContainerAuthority.TryGetExactOpenedServerOwnerInventory(val, player, out var inventory)) { Message(player, "Runic Storage: ownership changed before the sort; nothing was changed."); LogAction("sort-opened-container", "ownership.denied", "owner=false"); return; } if (!ValheimContainerService.CanRoundTrip(inventory)) { Message(player, "Runic Storage: this container contains an item that cannot be restored exactly under the current item definitions; sort was safely skipped."); LogAction("sort-opened-container", "inventory.not-roundtrippable", "changed=false"); return; } ZPackage backup = ValheimContainerService.SaveInventory(inventory); HashSet hashSet = ParseLockedSlots(PluginConfig.LockedContainerSlots.Value, inventory.GetWidth(), inventory.GetHeight()); List list = new List(); List list2 = new List(); List list3 = new List(inventory.GetAllItems()); if (list3.Count == 0) { Message(player, "Runic Storage: the opened container is already empty."); LogAction("sort-opened-container", "inventory.empty", $"lockedSlots={hashSet.Count}"); return; } foreach (ItemData item in list3) { if (!hashSet.Contains(SlotKey(item.m_gridPos.x, item.m_gridPos.y))) { list.Add(item); } } for (int i = 0; i < inventory.GetHeight(); i++) { for (int j = 0; j < inventory.GetWidth(); j++) { if (!hashSet.Contains(SlotKey(j, i))) { list2.Add(new Vector2i(j, i)); } } } if (list.Count == 0) { Message(player, "Runic Storage: every occupied slot is locked; nothing was moved."); LogAction("sort-opened-container", "inventory.all-locked", $"stacks={list3.Count} lockedSlots={hashSet.Count}"); return; } bool flag = false; try { Inventory obj = ValheimContainerService.CloneInventory(inventory); ApplySortExact(obj, hashSet); string @base = ValheimContainerService.SaveInventory(obj).GetBase64(); list.Sort(CompareItems); for (int k = 0; k < list.Count && k < list2.Count; k++) { if (list[k].m_gridPos.x != list2[k].x || list[k].m_gridPos.y != list2[k].y) { flag = true; } list[k].m_gridPos = list2[k]; } if (flag) { InventoryChangedMethod.Invoke(inventory, null); } if (!string.Equals(ValheimContainerService.SaveInventory(inventory).GetBase64(), @base, StringComparison.Ordinal)) { throw new InvalidOperationException("The opened container changed during nonthrowing sort publication."); } } catch (Exception ex) { try { ValheimContainerService.RestoreInventory(inventory, backup); } catch (Exception ex2) { throw new AggregateException("The opened-container sort failed and its exact rollback failed.", ex, ex2); } throw; } Message(player, flag ? $"Runic Storage: sorted {list.Count} stack(s); {hashSet.Count} slot(s) locked." : $"Runic Storage: {list.Count} movable stack(s) were already sorted; nothing changed."); LogAction("sort-opened-container", flag ? "ok" : "inventory.already-sorted", $"stacks={list3.Count} movable={list.Count} lockedSlots={hashSet.Count} changed={flag}"); } } internal void ConsolidateCarriedStacks() { if (!TryBeginMutation("Consolidate", out var player, out var mutationLease)) { return; } using (mutationLease) { Inventory inventory = ((Humanoid)player).GetInventory(); List list = new List(inventory.GetAllItems()); list.Sort(CompareGridPosition); if (!TryCaptureProtection(list, out var snapshot, out var failureCode)) { Message(player, "Runic Storage: carried-item protection could not be proven; consolidation changed nothing."); LogAction("consolidate", failureCode, $"stacks={list.Count} changed=false"); return; } if (!ValheimContainerService.CanRoundTrip(inventory)) { Message(player, "Runic Storage: your inventory contains an item that cannot be restored exactly under the current item definitions; consolidation was safely skipped."); LogAction("consolidate", "inventory.not-roundtrippable", "changed=false"); return; } ZPackage backup = ValheimContainerService.SaveInventory(inventory); if (list.Count == 0) { Message(player, "Runic Storage: your carried inventory is empty."); LogAction("consolidate", "inventory.empty", "stacks=0"); return; } int num = 0; for (int i = 0; i < list.Count; i++) { if (IsProtected(player, list[i], snapshot.StateAt(i))) { num++; } } if (num == list.Count) { Message(player, "Runic Storage: every carried stack is equipped or in a protected hotbar slot."); LogAction("consolidate", "inventory.all-protected", $"stacks={list.Count} protectedStacks={num}"); return; } int num2 = 0; try { Inventory obj = ValheimContainerService.CloneInventory(inventory); int num3 = ApplyConsolidation(obj, player, in snapshot); string @base = ValheimContainerService.SaveInventory(obj).GetBase64(); num2 = ApplyConsolidation(inventory, player, in snapshot); if (num2 != num3) { throw new InvalidOperationException("The carried inventory changed after consolidation preflight."); } if (num2 > 0) { InventoryChangedMethod.Invoke(inventory, null); } if (!string.Equals(ValheimContainerService.SaveInventory(inventory).GetBase64(), @base, StringComparison.Ordinal)) { throw new InvalidOperationException("The carried inventory changed during nonthrowing consolidation publication."); } } catch (Exception ex) { try { ValheimContainerService.RestoreInventory(inventory, backup, player, mutationLease); } catch (Exception ex2) { throw new AggregateException("Carried-stack consolidation and its exact rollback both failed.", ex, ex2); } throw; } Message(player, (num2 > 0) ? $"Runic Storage: consolidated {num2} item(s) into compatible stacks." : "Runic Storage: no compatible partial backpack stacks needed consolidation; protected/equipped or metadata-different stacks were left alone."); LogAction("consolidate", (num2 > 0) ? "ok" : "inventory.no-compatible-stacks", $"stacks={list.Count} protectedStacks={num} moved={num2}"); } } private static int ApplyConsolidation(Inventory inventory, Player player, in StorageProtectionSnapshot protection) { List list = new List(inventory.GetAllItems()); list.Sort(CompareGridPosition); if (protection.Count != 0 && list.Count != protection.Count) { throw new InvalidOperationException("The carried inventory shape changed after protection capture."); } int num = 0; for (int i = 0; i < list.Count; i++) { ItemData val = list[i]; if (val == null || val.m_stack <= 0 || IsProtected(player, val, protection.StateAt(i))) { continue; } int num2 = ((val.m_shared == null) ? val.m_stack : val.m_shared.m_maxStackSize); for (int j = i + 1; j < list.Count; j++) { if (val.m_stack >= num2) { break; } ItemData val2 = list[j]; if (val2 != null && val2.m_stack > 0 && !IsProtected(player, val2, protection.StateAt(j)) && CanMerge(val, val2)) { int num3 = Math.Min(num2 - val.m_stack, val2.m_stack); val.m_stack += num3; val2.m_stack -= num3; num = AddSaturated(num, num3); if (val2.m_stack == 0 && !inventory.RemoveItem(val2)) { throw new InvalidOperationException("A consolidation source changed during mutation."); } } } } return num; } private static bool ApplySortExact(Inventory inventory, HashSet lockedSlots) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) List list = new List(); List list2 = new List(); foreach (ItemData allItem in inventory.GetAllItems()) { if (!lockedSlots.Contains(SlotKey(allItem.m_gridPos.x, allItem.m_gridPos.y))) { list.Add(allItem); } } for (int i = 0; i < inventory.GetHeight(); i++) { for (int j = 0; j < inventory.GetWidth(); j++) { if (!lockedSlots.Contains(SlotKey(j, i))) { list2.Add(new Vector2i(j, i)); } } } list.Sort(CompareItems); bool flag = false; for (int k = 0; k < list.Count && k < list2.Count; k++) { flag |= list[k].m_gridPos.x != list2[k].x || list[k].m_gridPos.y != list2[k].y; list[k].m_gridPos = list2[k]; } return flag; } private IReadOnlyList Nearby(Player player, bool requireWritable, string action, bool allowCurrentUse = false) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Clamp(PluginConfig.RangeMeters.Value, 1f, 50f); int num2 = Mathf.Clamp(PluginConfig.MaximumCandidates.Value, 1, 256); int num3 = _index.RefreshLoadedContainers(); bool truncated; IReadOnlyList readOnlyList = _index.Nearest(((Component)player).transform.position, num, num2, out truncated); List list = new List(); int num4 = 0; int num5 = 0; foreach (Container item in readOnlyList) { if ((int)item.m_privacy == 0) { num4++; } else if (!ValheimContainerService.CanDiscover(item, player.GetPlayerID(), requireWritable, allowCurrentUse)) { num5++; } else { list.Add(item); } } if (PluginConfig.DebugTransfers.Value) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)($"action={action} phase=discovery rangeMeters={num:0.##} maximumCandidates={num2} " + $"loadedRefresh={num3} indexed={readOnlyList.Count} authorized={list.Count} personalExcluded={num4} " + $"deniedOrBusy={num5} truncated={truncated}")); } if (truncated) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("action=" + action + " result=discovery.truncated remedy=increase-MaximumCandidates-or-reduce-range")); } } } return list.AsReadOnly(); } private static bool TryGetWritableInventory(Container container, Player player, out Inventory inventory) { if ((Object)(object)InventoryGui.instance != (Object)null && CurrentContainerField != null && CurrentContainerField.GetValue(InventoryGui.instance) == container && StorageContainerAuthority.TryGetExactOpenedLocalOwnerInventory(container, player, out inventory)) { return true; } return StorageContainerAuthority.TryGetSynchronizedServerInventory(container, out inventory); } private static bool TryGetSearchInventory(Container container, Player player, out Inventory inventory) { if ((Object)(object)InventoryGui.instance != (Object)null && CurrentContainerField != null && CurrentContainerField.GetValue(InventoryGui.instance) == container && StorageContainerAuthority.TryGetExactOpenedLocalOwnerInventory(container, player, out inventory)) { return true; } long revision; return ContainerHoverContents.TryGetSynchronizedReadSnapshot(container, out inventory, out revision); } private static string FriendlyItemName(ItemData item) { string text = item?.m_shared?.m_name ?? ValheimContainerIdentity.ResourceId(item); try { if (Localization.instance != null && !string.IsNullOrEmpty(text)) { text = Localization.instance.Localize(text); } } catch { } text = (text ?? string.Empty).Replace("<", string.Empty).Replace(">", string.Empty).Replace("\r", string.Empty) .Replace("\n", string.Empty) .Trim(); if (text.Length == 0) { text = ValheimContainerIdentity.ResourceId(item); } if (text.Length > 64) { return text.Substring(0, 64); } return text; } private static bool TryBeginMutation(string action, out Player player, out StorageMutationLease mutationLease) { mutationLease = null; if (!TryResolveOwnedLocalPlayer(action, out player)) { return false; } if (StorageMutationLease.TryBegin(player, out mutationLease)) { return true; } Message(player, "Runic Storage: another Storage action is already in progress."); ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)(action + " denied with storage.operation-active.")); } return false; } private static bool TryResolveOwnedLocalPlayer(string action, out Player player) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_0048: Expected O, but got Unknown player = Player.m_localPlayer; if ((Object)player == (Object)null) { return false; } if (!PluginConfig.Enabled.Value) { Message(player, "Runic Storage is disabled in configuration."); return false; } if ((Object)player == (Object)Player.m_localPlayer && ((Character)player).IsOwner()) { return true; } Message(player, "Runic Storage: " + action + " requires the owning local player; no carried items were changed."); ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)(action + " denied with authority.local-player-owner-required.")); } return false; } private static bool IsProtected(Player player, ItemData item, StorageProtectionState typedState = StorageProtectionState.NotApplicable) { if (item != null && !item.m_equipped && !((Humanoid)player).IsItemEquiped(item) && (!PluginConfig.ProtectHotbar.Value || item.m_gridPos.y != 0)) { return typedState == StorageProtectionState.Locked; } return true; } private static bool TryCaptureProtection(IReadOnlyList items, out StorageProtectionSnapshot snapshot, out string failureCode) where T : class { return StorageItemProtection.TryCapture(items, out snapshot, out failureCode); } private static bool HasProtectedPartialMatch(Player player, IReadOnlyList items, in StorageProtectionSnapshot protection, string query) { for (int i = 0; i < items.Count; i++) { ItemData val = items[i]; if (Matches(val, query) && IsProtected(player, val, protection.StateAt(i))) { int num = val?.m_shared?.m_maxStackSize ?? val?.m_stack ?? 0; if (val != null && val.m_stack > 0 && val.m_stack < num) { return true; } } } return false; } private static bool ContainsResource(Inventory inventory, string resourceId) { foreach (ItemData allItem in inventory.GetAllItems()) { if (string.Equals(ValheimContainerIdentity.ResourceId(allItem), resourceId, StringComparison.Ordinal)) { return true; } } return false; } private static int CountMatching(Inventory inventory, string query) { int num = 0; foreach (ItemData allItem in inventory.GetAllItems()) { if (Matches(allItem, query)) { num = AddSaturated(num, Math.Max(0, allItem.m_stack)); } } return num; } private static bool Matches(ItemData item, string query) { if (item == null) { return false; } if (string.Equals(ValheimContainerIdentity.ResourceId(item), query, StringComparison.OrdinalIgnoreCase)) { return true; } if (string.Equals((item.m_shared?.m_name ?? string.Empty).TrimStart('$'), query.TrimStart('$'), StringComparison.OrdinalIgnoreCase)) { return true; } return false; } private static bool CanMerge(ItemData left, ItemData right) { if (left.m_shared != right.m_shared || !string.Equals(ValheimContainerIdentity.ResourceId(left), ValheimContainerIdentity.ResourceId(right), StringComparison.Ordinal) || left.m_quality != right.m_quality || left.m_variant != right.m_variant || left.m_worldLevel != right.m_worldLevel || left.m_crafterID != right.m_crafterID || !string.Equals(left.m_crafterName ?? string.Empty, right.m_crafterName ?? string.Empty, StringComparison.Ordinal) || left.m_pickedUp != right.m_pickedUp || left.m_equipped != right.m_equipped || !left.m_durability.Equals(right.m_durability)) { return false; } return DictionaryEquals(left.m_customData, right.m_customData); } private static bool DictionaryEquals(IDictionary left, IDictionary right) { int num = left?.Count ?? 0; if (num != (right?.Count ?? 0)) { return false; } if (num == 0) { return true; } foreach (KeyValuePair item in left) { if (!right.TryGetValue(item.Key, out var value) || !string.Equals(item.Value, value, StringComparison.Ordinal)) { return false; } } return true; } private static int CompareItems(ItemData left, ItemData right) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) int num = ((left.m_shared == null) ? int.MaxValue : ((int)left.m_shared.m_itemType)); int value = ((right.m_shared == null) ? int.MaxValue : ((int)right.m_shared.m_itemType)); int num2 = num.CompareTo(value); if (num2 != 0) { return num2; } string x = left.m_shared?.m_name ?? ValheimContainerIdentity.ResourceId(left); string y = right.m_shared?.m_name ?? ValheimContainerIdentity.ResourceId(right); int num3 = StringComparer.OrdinalIgnoreCase.Compare(x, y); if (num3 != 0) { return num3; } int num4 = right.m_quality.CompareTo(left.m_quality); if (num4 != 0) { return num4; } float num5 = ((left.m_shared == null) ? 0f : left.GetWeight(left.m_stack)); float value2 = ((right.m_shared == null) ? 0f : right.GetWeight(right.m_stack)); return num5.CompareTo(value2); } private static int CompareGridPosition(ItemData left, ItemData right) { int num = left.m_gridPos.y.CompareTo(right.m_gridPos.y); if (num == 0) { return left.m_gridPos.x.CompareTo(right.m_gridPos.x); } return num; } private static Dictionary ParseTargets(string value) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); string[] array = (value ?? string.Empty).Split(','); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split('='); if (array2.Length == 2 && int.TryParse(array2[1].Trim(), out var result) && result >= 0) { string text = array2[0].Trim(); if (text.Length != 0) { dictionary[text] = result; } } } return dictionary; } private static HashSet ParseLockedSlots(string value, int width, int height) { HashSet hashSet = new HashSet(StringComparer.Ordinal); string[] array = (value ?? string.Empty).Split(';'); for (int i = 0; i < array.Length; i++) { string[] array2 = array[i].Split(','); if (array2.Length == 2 && int.TryParse(array2[0], out var result) && int.TryParse(array2[1], out var result2) && result >= 0 && result < width && result2 >= 0 && result2 < height) { hashSet.Add(SlotKey(result, result2)); } } return hashSet; } private static string SlotKey(int x, int y) { return x + "," + y; } private static string CompassDirection(Vector3 delta) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Atan2(delta.x, delta.z) * 57.29578f; if (num < 0f) { num += 360f; } string[] array = new string[8] { "north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest" }; return array[Mathf.RoundToInt(num / 45f) % array.Length]; } private static void Message(Player player, string text) { if ((Object)(object)player != (Object)null) { ((Character)player).Message((MessageType)2, text, 0, (Sprite)null); } } private static int AddSaturated(int left, int right) { if (left <= int.MaxValue - right) { return left + right; } return int.MaxValue; } private static string PlayerEndpointId(Player player) { return "valheim.player:" + player.GetPlayerID().ToString(CultureInfo.InvariantCulture); } private static void LogTransfer(string source, string destination, string resource, int quantity, string result) { if (PluginConfig.DebugTransfers.Value) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"transfer source={source} destination={destination} resource={resource} quantity={quantity} result={result}"); } } } private static string QuickStackNoOpFeedback(QuickStackNoOpReason reason, int hotbarProtected, int equippedProtected, int itemLockProtected) { return reason switch { QuickStackNoOpReason.InventoryEmpty => "Runic Storage: your carried inventory is empty.", QuickStackNoOpReason.AllStacksProtected => $"Runic Storage: no backpack stack is eligible; {hotbarProtected} hotbar, {equippedProtected} equipped, and {itemLockProtected} typed-lock stack(s) are protected.", QuickStackNoOpReason.NoAuthorizedContainers => $"Runic Storage: no authorized public container is available within {Mathf.Clamp(PluginConfig.RangeMeters.Value, 1f, 50f):0.#} m.", QuickStackNoOpReason.NoMatchingResources => "Runic Storage: no eligible backpack item matches an item already stored in an authorized nearby container.", _ => "Runic Storage: matching containers were full or ownership changed; nothing moved.", }; } private static void LogAction(string action, string result, string details) { if (PluginConfig.DebugTransfers.Value) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("action=" + action + " result=" + result + " " + details)); } } } private static string SafeLogValue(string value) { string text = (value ?? string.Empty).Replace('\r', ' ').Replace('\n', ' ').Trim(); if (text.Length > 64) { text = text.Substring(0, 64); } return text.Replace(' ', '_'); } } internal static class StorageContainerAuthority { internal const string ContainerLoadMethodName = "Load"; internal const string ContainerLoadingFieldName = "m_loading"; private static readonly MethodInfo LoadMethod = AccessTools.Method(typeof(Container), "Load", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(Container).FullName, "Load"); private static readonly FieldInfo LoadingField = AccessTools.Field(typeof(Container), "m_loading") ?? throw new MissingFieldException(typeof(Container).FullName, "m_loading"); private static readonly FieldInfo CurrentContainerField = AccessTools.Field(typeof(InventoryGui), "m_currentContainer") ?? throw new MissingFieldException(typeof(InventoryGui).FullName, "m_currentContainer"); private static readonly FieldInfo DragItemField = AccessTools.Field(typeof(InventoryGui), "m_dragItem") ?? throw new MissingFieldException(typeof(InventoryGui).FullName, "m_dragItem"); internal static bool TryGetSynchronizedServerInventory(Container container, out Inventory inventory) { return TryGetOwnedInventory(container, allowInUse: false, out inventory); } internal static bool TryGetExactOpenedServerOwnerInventory(Container container, Player player, out Inventory inventory) { return TryGetExactOpenedLocalOwnerInventory(container, player, out inventory); } internal static bool TryGetExactOpenedLocalOwnerInventory(Container container, Player player, out Inventory inventory) { inventory = null; if ((Object)(object)container == (Object)null || (Object)(object)player == (Object)null || player != Player.m_localPlayer || !((Character)player).IsOwner() || (Object)(object)InventoryGui.instance == (Object)null || CurrentContainerField.GetValue(InventoryGui.instance) != container || DragItemField.GetValue(InventoryGui.instance) != null || !((Behaviour)container).isActiveAndEnabled || !container.IsInUse() || ((Object)(object)container.m_wagon != (Object)null && container.m_wagon.InUse())) { return false; } return TryGetOwnedInventory(container, allowInUse: true, out inventory); } private static bool TryGetOwnedInventory(Container container, bool allowInUse, out Inventory inventory) { inventory = null; if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)container == (Object)null || !((Behaviour)container).isActiveAndEnabled || !container.IsOwner() || (!allowInUse && (container.IsInUse() || ((Object)(object)container.m_wagon != (Object)null && container.m_wagon.InUse()))) || (bool)LoadingField.GetValue(container)) { return false; } ZNetView val = ValheimContainerIdentity.NetworkView(container); ZDO val2 = (((Object)(object)val != (Object)null && val.IsValid() && val.IsOwner()) ? val.GetZDO() : null); if (val2 == null || val2.GetOwner() != ZNet.GetUID()) { return false; } if (allowInUse && !val2.GetBool(ZDOVars.s_inUse, false)) { return false; } try { LoadMethod.Invoke(container, Array.Empty()); inventory = container.GetInventory(); if (inventory == null) { return false; } string text = val2.GetString(ZDOVars.s_items, string.Empty); return string.IsNullOrEmpty(text) ? (inventory.GetAllItems().Count == 0) : string.Equals(ValheimContainerService.SaveInventory(inventory).GetBase64(), text, StringComparison.Ordinal); } catch { inventory = null; return false; } } } internal static class StorageControllerBindings { private static bool _hasCachedState; private static ControllerBindingCacheKey _cachedKey; private static ControllerBindingState _cachedState; internal static ControllerBindingState Resolve() { string text = Name(PluginConfig.ControllerModifier); string text2 = Name(PluginConfig.ControllerQuickStack); string text3 = Name(PluginConfig.ControllerRestock); string text4 = Name(PluginConfig.ControllerSort); string text5 = Name(PluginConfig.ControllerConsolidate); string text6 = Name(PluginConfig.ControllerSearch); ControllerBindingCacheKey controllerBindingCacheKey = new ControllerBindingCacheKey(ZInput.instance, text, text2, text3, text4, text5, text6); if (_hasCachedState && _cachedKey.Equals(controllerBindingCacheKey)) { return _cachedState; } ButtonDef val = ResolveGamepad(text); HashSet usedNames = new HashSet(StringComparer.Ordinal); HashSet usedPaths = new HashSet(StringComparer.Ordinal); if (val != null && !Register(text, val, usedNames, usedPaths)) { val = null; } ButtonDef val2 = ResolveUnique(text2, usedNames, usedPaths); ButtonDef val3 = ResolveUnique(text3, usedNames, usedPaths); ButtonDef val4 = ResolveUnique(text4, usedNames, usedPaths); ButtonDef val5 = ResolveUnique(text5, usedNames, usedPaths); ButtonDef val6 = ResolveUnique(text6, usedNames, usedPaths); ControllerBindingState controllerBindingState = new ControllerBindingState(string.Join("|", text, Path(val), text2, Path(val2), text3, Path(val3), text4, Path(val4), text5, Path(val5), text6, Path(val6)), text, text2, text3, text4, text5, text6, val, val2, val3, val4, val5, val6); _cachedKey = controllerBindingCacheKey; _cachedState = controllerBindingState; _hasCachedState = true; return controllerBindingState; } internal static void Invalidate() { _hasCachedState = false; _cachedKey = default(ControllerBindingCacheKey); _cachedState = null; } private static ButtonDef ResolveUnique(string action, ISet usedNames, ISet usedPaths) { ButtonDef val = ResolveGamepad(action); if (val != null && Register(action, val, usedNames, usedPaths)) { return val; } return null; } private static ButtonDef ResolveGamepad(string action) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Invalid comparison between Unknown and I4 if (action.Length == 0 || action.Length > 64 || !action.StartsWith("Joy", StringComparison.Ordinal) || ZInput.instance == null) { return null; } ButtonDef buttonDef = ZInput.instance.GetButtonDef(action); if (buttonDef != null && (int)buttonDef.Source == 180 && !string.IsNullOrEmpty(buttonDef.GetActionPath(true))) { return buttonDef; } return null; } private static bool Register(string action, ButtonDef definition, ISet usedNames, ISet usedPaths) { string text = Path(definition); if (text.Length == 0 || usedNames.Contains(action) || usedPaths.Contains(text)) { return false; } usedNames.Add(action); usedPaths.Add(text); return true; } private static string Path(ButtonDef definition) { return ((definition != null) ? definition.GetActionPath(true) : null) ?? string.Empty; } private static string Name(ConfigEntry entry) { return (entry?.Value ?? string.Empty).Trim(); } } internal sealed class StorageInputReader { private string _loggedControllerSignature = string.Empty; internal StorageActionEdges ReadKeyboardEdges() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) StorageActionEdges storageActionEdges = StorageActionEdges.None; if (ShortcutDown(PluginConfig.SortOpenedContainerKey.Value)) { storageActionEdges |= StorageActionEdges.KeyboardSort; } if (ShortcutDown(PluginConfig.StoreAllOpenedContainerKey.Value)) { storageActionEdges |= StorageActionEdges.KeyboardStoreAll; } if (ShortcutDown(PluginConfig.QuickStackKey.Value)) { storageActionEdges |= StorageActionEdges.KeyboardQuickStack; } if (ShortcutDown(PluginConfig.RestockKey.Value)) { storageActionEdges |= StorageActionEdges.KeyboardRestock; } if (ShortcutDown(PluginConfig.ConsolidateKey.Value)) { storageActionEdges |= StorageActionEdges.KeyboardConsolidate; } if (ShortcutDown(PluginConfig.SearchKey.Value)) { storageActionEdges |= StorageActionEdges.KeyboardSearch; } return storageActionEdges; } internal StorageActionEdges ReadControllerEdges(StorageRouteContext context) { if (!PluginConfig.ControllerShortcuts.Value || ZInput.instance == null) { return StorageActionEdges.None; } StorageActionEdges storageActionEdges = StorageControllerCollisionGuard.ConsumePendingEdge(); if (storageActionEdges != StorageActionEdges.None) { return storageActionEdges; } if (_loggedControllerSignature.Length != 0) { string text = (PluginConfig.ControllerModifier.Value ?? string.Empty).Trim(); ButtonDef val = ((text.Length == 0) ? null : ZInput.instance.GetButtonDef(text)); if (val == null || !val.Held) { return StorageActionEdges.None; } } ControllerBindingState bindings = StorageControllerBindings.Resolve(); LogControllerStatus(bindings); return StorageControllerCollisionGuard.ObserveAndConsume(bindings, context); } internal void InvalidateControllerBindings() { _loggedControllerSignature = string.Empty; StorageControllerBindings.Invalidate(); } internal string ControllerStatusSummary() { if (!PluginConfig.ControllerShortcuts.Value) { return "disabled"; } if (ZInput.instance == null) { return "waiting-for-zinput"; } ControllerBindingState controllerBindingState = StorageControllerBindings.Resolve(); if (!controllerBindingState.ModifierValid) { return "invalid-modifier"; } return controllerBindingState.ValidRouteCount + "/5-routes-valid"; } internal static string DescribeEdges(StorageActionEdges edges) { if (edges == StorageActionEdges.None) { return "none"; } List values = new List(); Add(values, edges, StorageActionEdges.KeyboardSort, "keyboard:sort-opened-container"); Add(values, edges, StorageActionEdges.KeyboardStoreAll, "keyboard:store-all-opened-container"); Add(values, edges, StorageActionEdges.KeyboardQuickStack, "keyboard:quick-stack"); Add(values, edges, StorageActionEdges.KeyboardRestock, "keyboard:restock"); Add(values, edges, StorageActionEdges.KeyboardConsolidate, "keyboard:consolidate"); Add(values, edges, StorageActionEdges.KeyboardSearch, "keyboard:search"); Add(values, edges, StorageActionEdges.ControllerSort, "controller:sort-opened-container"); Add(values, edges, StorageActionEdges.ControllerQuickStack, "controller:quick-stack"); Add(values, edges, StorageActionEdges.ControllerRestock, "controller:restock"); Add(values, edges, StorageActionEdges.ControllerConsolidate, "controller:consolidate"); Add(values, edges, StorageActionEdges.ControllerSearch, "controller:search"); return string.Join(",", values); } private void LogControllerStatus(ControllerBindingState bindings) { if (string.Equals(_loggedControllerSignature, bindings.Signature, StringComparison.Ordinal)) { return; } _loggedControllerSignature = bindings.Signature; if (!bindings.ModifierValid) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Controller shortcuts are unavailable: ModifierAction '" + bindings.ModifierName + "' is not an existing Joy* ZInput action.")); } return; } LogRouteStatus("QuickStack", bindings.QuickStackName, bindings.QuickStackValid); LogRouteStatus("Restock", bindings.RestockName, bindings.RestockValid); LogRouteStatus("SortOpenedContainer", bindings.SortName, bindings.SortValid); LogRouteStatus("Consolidate", bindings.ConsolidateName, bindings.ConsolidateValid); LogRouteStatus("Search", bindings.SearchName, bindings.SearchValid); ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)($"Controller controls (validation={bindings.ValidRouteCount}/5-routes-valid): hold {bindings.ModifierName}; " + "QuickStack=" + bindings.QuickStackName + ", Restock=" + bindings.RestockName + ", SortOpenedContainer=" + bindings.SortName + ", Consolidate=" + bindings.ConsolidateName + ", Search=" + bindings.SearchName + ". An authorized chord owns its modifier plus all configured Storage primary paths until full release.")); } } private static void LogRouteStatus(string route, string action, bool valid) { if (!valid) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Controller route " + route + " is disabled: '" + action + "' is missing, non-gamepad, duplicates the modifier, or duplicates an earlier route.")); } } } private static bool ShortcutDown(KeyboardShortcut shortcut) { //IL_0002: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey, false)) { return false; } foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers) { if ((int)modifier == 0 || !ZInput.GetKey(modifier, false)) { return false; } } return true; } private static void Add(ICollection values, StorageActionEdges edges, StorageActionEdges expected, string label) { if ((edges & expected) != StorageActionEdges.None) { values.Add(label); } } } internal sealed class StorageMutationLease : IDisposable { private static int _active; private int _owned; internal Player Player { get; } internal Inventory Inventory { get; } private StorageMutationLease(Player player) { Player = player; Inventory = ((Humanoid)player).GetInventory(); _owned = 1; } internal static bool TryBegin(Player player, out StorageMutationLease lease) { lease = null; if ((Object)(object)player == (Object)null || player != Player.m_localPlayer || !((Character)player).IsOwner() || Interlocked.CompareExchange(ref _active, 1, 0) != 0) { return false; } lease = new StorageMutationLease(player); return true; } internal bool Covers(Player player, Inventory inventory) { if (Volatile.Read(in _owned) == 1 && Player == player) { return Inventory == inventory; } return false; } public void Dispose() { if (Interlocked.Exchange(ref _owned, 0) == 1) { Volatile.Write(ref _active, 0); } } } internal static class StorageSearchGameplayInputGuard { private static readonly StorageSearchAttackSuppression AttackSuppression = new StorageSearchAttackSuppression(); private static bool _pollingPickerEscape; private static int _pickerEscapeFrame = -1; internal static bool PollPickerEscape() { _pollingPickerEscape = true; try { bool keyDown = ZInput.GetKeyDown((KeyCode)27, false); if (keyDown) { _pickerEscapeFrame = Time.frameCount; } return keyDown; } finally { _pollingPickerEscape = false; } } internal static bool ShouldSuppressEscape(KeyCode key) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Invalid comparison between Unknown and I4 if ((int)key != 27 || _pollingPickerEscape) { return false; } if (!Plugin.SearchPanelOpen) { return Time.frameCount == _pickerEscapeFrame; } return true; } internal static void CaptureGuiPointer(Event current) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Invalid comparison between Unknown and I4 //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Invalid comparison between Unknown and I4 if (current != null && current.button == 0 && ((int)current.type == 0 || (int)current.type == 1 || (int)current.type == 3)) { AttackSuppression.CapturePrimaryPointer(); } } internal static bool ShouldSuppressPrimaryAttack(string actionName) { if (!StorageSearchAttackSuppression.IsPrimaryAttack(actionName)) { return false; } bool primaryAttackHeld = false; try { ZInput instance = ZInput.instance; if (instance != null) { ButtonDef buttonDef = instance.GetButtonDef("Attack"); primaryAttackHeld = buttonDef != null && buttonDef.Held; } } catch { primaryAttackHeld = false; } return AttackSuppression.ShouldSuppress(actionName, Plugin.SearchPanelOpen, primaryAttackHeld, Time.frameCount); } internal static void Reset() { AttackSuppression.Reset(); _pollingPickerEscape = false; _pickerEscapeFrame = -1; } } internal sealed class StorageSearchEntry { internal string ResourceId { get; } internal string DisplayName { get; } internal int Quantity { get; } internal IReadOnlyList Containers { get; } internal StorageSearchEntry(string resourceId, string displayName, int quantity, IReadOnlyList containers) { ResourceId = resourceId ?? string.Empty; DisplayName = displayName ?? string.Empty; Quantity = Math.Max(0, quantity); Containers = containers ?? Array.Empty(); } } internal sealed class StorageSearchPanel : IDisposable { private const int MaximumEntries = 256; private readonly List _entries = new List(); private string _filter = string.Empty; private bool _open; private bool _focusFilter; private bool _cursorVisible; private CursorLockMode _cursorLock; private GameObject _canvasObject; private RectTransform _panelRect; private RectTransform _entryContent; private ScrollRect _scrollRect; private TMP_InputField _filterInput; private StorageSearchVanillaTheme _theme; private int _themeSourceToken = int.MinValue; private int _appearanceFontSize = -1; private StorageSearchMenuColor _appearanceFontColor; private float _savedScrollPosition = 1f; private float _nextThemeSourceCheck; internal bool IsOpen => _open; internal static int EntryLimit => 256; internal void Open(IReadOnlyList entries) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) Close(); _entries.Clear(); if (entries != null) { for (int i = 0; i < entries.Count; i++) { if (_entries.Count >= 256) { break; } if (entries[i] != null) { _entries.Add(entries[i]); } } } _filter = string.Empty; _savedScrollPosition = 1f; _cursorVisible = Cursor.visible; _cursorLock = Cursor.lockState; _open = true; _focusFilter = true; RenewCursorLease(); EnsureNativeView(force: true); } internal void Tick() { if (_open) { RenewCursorLease(); EnsureNativeView(force: false); if (_focusFilter && Object.op_Implicit((Object)(object)_filterInput)) { ((Selectable)_filterInput).Select(); _filterInput.ActivateInputField(); _focusFilter = false; } if (StorageSearchGameplayInputGuard.PollPickerEscape()) { Close(); } } } internal void Draw() { if (_open) { RenewCursorLease(); StorageSearchGameplayInputGuard.CaptureGuiPointer(Event.current); } } internal static void RenewCursorLease() { if (Plugin.SearchPanelOpen) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } } internal void Close() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (_open) { _open = false; _focusFilter = false; if (Object.op_Implicit((Object)(object)_filterInput)) { _filterInput.DeactivateInputField(false); } DestroyNativeView(); Cursor.visible = _cursorVisible; Cursor.lockState = _cursorLock; } } public void Dispose() { Close(); DestroyNativeView(); _entries.Clear(); StorageSearchHighlight.ClearAll(); } private void EnsureNativeView(bool force) { //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) int num = _themeSourceToken; if (force || !Object.op_Implicit((Object)(object)_canvasObject) || Time.unscaledTime >= _nextThemeSourceCheck) { num = StorageSearchVanillaTheme.CurrentSourceToken(); _nextThemeSourceCheck = Time.unscaledTime + 1f; } int num2 = PluginConfig.SearchMenuFontSize?.Value ?? 14; StorageSearchMenuColor storageSearchMenuColor = StorageSearchMenuAppearance.ResolveFontColor(PluginConfig.SearchMenuFontColor?.Value ?? StorageSearchMenuFontColor.LightGray); if (!force && Object.op_Implicit((Object)(object)_canvasObject) && _themeSourceToken == num && _appearanceFontSize == num2 && _appearanceFontColor.Equals(storageSearchMenuColor)) { return; } if (Object.op_Implicit((Object)(object)_scrollRect)) { _savedScrollPosition = _scrollRect.verticalNormalizedPosition; } DestroyNativeView(); _themeSourceToken = num; _appearanceFontSize = num2; _appearanceFontColor = storageSearchMenuColor; _theme = StorageSearchVanillaTheme.Create(); StorageSearchMenuLayout layout = StorageSearchMenuAppearance.LayoutFor(num2); Color textColor = ToColor(storageSearchMenuColor); try { BuildNativeView(layout, textColor); } catch (Exception ex) { DestroyNativeView(); _themeSourceToken = num; _theme = StorageSearchVanillaTheme.CreateFallback(); try { BuildNativeView(layout, textColor); } catch (Exception ex2) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)("Runic Storage could not create the native Alt+F menu; search closed safely. Native=" + ex.Message + "; fallback=" + ex2.Message)); } Close(); } } } private void BuildNativeView(StorageSearchMenuLayout layout, Color textColor) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Expected O, but got Unknown //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_03b0: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03d1: Expected O, but got Unknown //IL_03d9: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_03fa: Expected O, but got Unknown //IL_0433: Unknown result type (might be due to invalid IL or missing references) _canvasObject = new GameObject("RunicStorageNativeSearchCanvas", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster) }); ((Object)_canvasObject).hideFlags = (HideFlags)61; Canvas component = _canvasObject.GetComponent(); component.renderMode = (RenderMode)0; Canvas val = (Object.op_Implicit((Object)(object)InventoryGui.instance) ? ((Component)InventoryGui.instance).GetComponentInParent() : null); component.sortingOrder = (Object.op_Implicit((Object)(object)val) ? (val.sortingOrder + 100) : 1000); CopyCanvasScale(_theme.SourceScaler, _canvasObject.GetComponent()); RectTransform component2 = _canvasObject.GetComponent(); Stretch(component2); Stretch(((Graphic)CreateImage("SceneScrim", (Transform)(object)component2, null, (Type)0, new Color(0f, 0f, 0f, 0.68f))).rectTransform); Image val2 = CreateImage("ValheimSearchPanel", (Transform)(object)component2, _theme.PanelSprite, _theme.PanelType, _theme.HasNativePanel ? _theme.PanelColor : Color32.op_Implicit(new Color32((byte)60, (byte)42, (byte)29, byte.MaxValue))); if (Object.op_Implicit((Object)(object)_theme.PanelMaterial)) { ((Graphic)val2).material = _theme.PanelMaterial; } _panelRect = ((Graphic)val2).rectTransform; RectTransform panelRect = _panelRect; RectTransform panelRect2 = _panelRect; Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0.5f, 0.5f); panelRect2.anchorMax = val3; panelRect.anchorMin = val3; _panelRect.pivot = new Vector2(0.5f, 0.5f); _panelRect.anchoredPosition = Vector2.zero; _panelRect.sizeDelta = new Vector2((float)layout.PreferredWindowWidth, (float)layout.PreferredWindowHeight); VerticalLayoutGroup obj = ((Component)val2).gameObject.AddComponent(); ((LayoutGroup)obj).padding = new RectOffset(28, 28, 34, 26); ((HorizontalOrVerticalLayoutGroup)obj).spacing = 7f; ((LayoutGroup)obj).childAlignment = (TextAnchor)1; ((HorizontalOrVerticalLayoutGroup)obj).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)obj).childForceExpandWidth = true; SetHeight(((Component)CreateText(((Component)val2).transform, "Runic Storage — Nearby Items", layout.FontSize, textColor, (TextAlignmentOptions)514)).gameObject, layout.FontSize + 18); SetHeight(((Component)CreateText(((Component)val2).transform, "Select an item to mark every nearby chest that contains it.", layout.FontSize, textColor, (TextAlignmentOptions)513)).gameObject, layout.FontSize * 2 + 10); RectTransform val4 = CreateRect("FilterRow", ((Component)val2).transform); SetHeight(((Component)val4).gameObject, Math.Max(layout.ControlHeight, _theme.ControlHeight)); HorizontalLayoutGroup obj2 = ((Component)val4).gameObject.AddComponent(); ((HorizontalOrVerticalLayoutGroup)obj2).spacing = 8f; ((LayoutGroup)obj2).childAlignment = (TextAnchor)4; ((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = true; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = false; SetWidth(((Component)CreateText((Transform)(object)val4, "Filter", layout.FontSize, textColor, (TextAlignmentOptions)4097)).gameObject, Math.Max(52f, (float)layout.FontSize * 4f)); _filterInput = CreateInput((Transform)(object)val4, layout, textColor); LayoutElement obj3 = ((Component)_filterInput).gameObject.AddComponent(); obj3.flexibleWidth = 1f; obj3.minWidth = 120f; ((UnityEvent)CreateButton((Transform)(object)val4, "Clear", layout, textColor).onClick).AddListener(new UnityAction(ClearFilter)); ((UnityEvent)CreateButton((Transform)(object)val4, "Close", layout, textColor).onClick).AddListener(new UnityAction(Close)); _scrollRect = CreateScrollView(((Component)val2).transform); LayoutElement obj4 = ((Component)_scrollRect).gameObject.AddComponent(); obj4.flexibleHeight = 1f; obj4.minHeight = 140f; RebuildEntryRows(layout, textColor); Canvas.ForceUpdateCanvases(); _scrollRect.verticalNormalizedPosition = Mathf.Clamp01(_savedScrollPosition); _focusFilter = true; } private TMP_InputField CreateInput(Transform parent, StorageSearchMenuLayout layout, Color textColor) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) Image val = CreateImage("FilterInput", parent, _theme.InputSprite, _theme.InputType, Object.op_Implicit((Object)(object)_theme.InputSprite) ? _theme.InputColor : Color32.op_Implicit(new Color32((byte)37, (byte)24, (byte)16, byte.MaxValue))); if (Object.op_Implicit((Object)(object)_theme.InputMaterial)) { ((Graphic)val).material = _theme.InputMaterial; } TMP_InputField obj = ((Component)val).gameObject.AddComponent(); ((Selectable)obj).targetGraphic = (Graphic)(object)val; obj.characterLimit = 64; obj.lineType = (LineType)0; ((Selectable)obj).transition = (Transition)1; obj.customCaretColor = true; obj.caretColor = textColor; obj.selectionColor = new Color(textColor.r, textColor.g, textColor.b, 0.38f); RectTransform val2 = CreateRect("Text Area", ((Component)val).transform); Stretch(val2, 9f, 9f, 3f, 3f); ((Component)val2).gameObject.AddComponent(); TMP_Text val3 = CreateText((Transform)(object)val2, _filter, layout.FontSize, textColor, (TextAlignmentOptions)4097); Stretch(val3.rectTransform); TMP_Text val4 = CreateText((Transform)(object)val2, "type to filter…", layout.FontSize, new Color(textColor.r, textColor.g, textColor.b, 0.52f), (TextAlignmentOptions)4097); Stretch(val4.rectTransform); val4.fontStyle = (FontStyles)2; obj.textViewport = val2; obj.textComponent = val3; obj.placeholder = (Graphic)(object)val4; obj.SetTextWithoutNotify(_filter); ((UnityEvent)(object)obj.onValueChanged).AddListener((UnityAction)OnFilterChanged); return obj; } private ScrollRect CreateScrollView(Transform parent) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown Image val = CreateImage("NearbyItems", parent, _theme.InsetSprite, _theme.InsetType, Object.op_Implicit((Object)(object)_theme.InsetSprite) ? _theme.InsetColor : Color32.op_Implicit(new Color32((byte)24, (byte)16, (byte)11, (byte)242))); if (Object.op_Implicit((Object)(object)_theme.InsetMaterial)) { ((Graphic)val).material = _theme.InsetMaterial; } ScrollRect obj = ((Component)val).gameObject.AddComponent(); obj.horizontal = false; obj.vertical = true; obj.movementType = (MovementType)2; obj.scrollSensitivity = 34f; RectTransform val2 = CreateRect("Viewport", ((Component)val).transform); Stretch(val2, 6f, 25f, 6f, 6f); ((Component)val2).gameObject.AddComponent(); _entryContent = CreateRect("Content", (Transform)(object)val2); _entryContent.anchorMin = new Vector2(0f, 1f); _entryContent.anchorMax = new Vector2(1f, 1f); _entryContent.pivot = new Vector2(0.5f, 1f); _entryContent.anchoredPosition = Vector2.zero; _entryContent.sizeDelta = Vector2.zero; VerticalLayoutGroup obj2 = ((Component)_entryContent).gameObject.AddComponent(); ((LayoutGroup)obj2).padding = new RectOffset(3, 3, 3, 3); ((HorizontalOrVerticalLayoutGroup)obj2).spacing = 5f; ((LayoutGroup)obj2).childAlignment = (TextAnchor)1; ((HorizontalOrVerticalLayoutGroup)obj2).childControlHeight = true; ((HorizontalOrVerticalLayoutGroup)obj2).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)obj2).childForceExpandWidth = true; ((Component)_entryContent).gameObject.AddComponent().verticalFit = (FitMode)2; obj.viewport = val2; obj.content = _entryContent; Scrollbar verticalScrollbar = CreateScrollbar(((Component)val).transform); obj.verticalScrollbar = verticalScrollbar; obj.verticalScrollbarVisibility = (ScrollbarVisibility)1; obj.verticalScrollbarSpacing = 4f; return obj; } private Scrollbar CreateScrollbar(Transform parent) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) Image val = CreateImage("Scrollbar", parent, _theme.ScrollbarSprite, _theme.ScrollbarType, Object.op_Implicit((Object)(object)_theme.ScrollbarSprite) ? _theme.ScrollbarColor : Color32.op_Implicit(new Color32((byte)32, (byte)21, (byte)14, (byte)245))); if (Object.op_Implicit((Object)(object)_theme.ScrollbarMaterial)) { ((Graphic)val).material = _theme.ScrollbarMaterial; } RectTransform rectTransform = ((Graphic)val).rectTransform; rectTransform.anchorMin = new Vector2(1f, 0f); rectTransform.anchorMax = Vector2.one; rectTransform.pivot = new Vector2(1f, 0.5f); rectTransform.offsetMin = new Vector2(-20f, 5f); rectTransform.offsetMax = new Vector2(-4f, -5f); Scrollbar obj = ((Component)val).gameObject.AddComponent(); RectTransform val2 = CreateRect("Sliding Area", (Transform)(object)rectTransform); Stretch(val2, 2f, 2f, 2f, 2f); Image val3 = CreateImage("Handle", (Transform)(object)val2, _theme.ScrollbarHandleSprite, _theme.ScrollbarHandleType, Object.op_Implicit((Object)(object)_theme.ScrollbarHandleSprite) ? _theme.ScrollbarHandleColor : Color32.op_Implicit(new Color32((byte)183, (byte)122, (byte)53, byte.MaxValue))); if (Object.op_Implicit((Object)(object)_theme.ScrollbarHandleMaterial)) { ((Graphic)val3).material = _theme.ScrollbarHandleMaterial; } ((Graphic)val3).rectTransform.anchorMin = Vector2.zero; ((Graphic)val3).rectTransform.anchorMax = Vector2.one; ((Graphic)val3).rectTransform.offsetMin = Vector2.zero; ((Graphic)val3).rectTransform.offsetMax = Vector2.zero; obj.handleRect = ((Graphic)val3).rectTransform; ((Selectable)obj).targetGraphic = (Graphic)(object)val3; obj.direction = (Direction)2; ((Selectable)obj).transition = (Transition)1; ((Selectable)obj).colors = _theme.ButtonColors; obj.value = 1f; return obj; } private void RebuildEntryRows(StorageSearchMenuLayout layout, Color textColor) { //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown if (!Object.op_Implicit((Object)(object)_entryContent)) { return; } for (int num = ((Transform)_entryContent).childCount - 1; num >= 0; num--) { GameObject gameObject = ((Component)((Transform)_entryContent).GetChild(num)).gameObject; gameObject.SetActive(false); Object.Destroy((Object)(object)gameObject); } int num2 = 0; for (int i = 0; i < _entries.Count; i++) { StorageSearchEntry storageSearchEntry = _entries[i]; if (MatchesFilter(storageSearchEntry, _filter)) { num2++; string label = storageSearchEntry.DisplayName + " ×" + storageSearchEntry.Quantity + " — " + storageSearchEntry.Containers.Count + ((storageSearchEntry.Containers.Count == 1) ? " chest" : " chests"); Button obj = CreateButton((Transform)(object)_entryContent, label, layout, textColor, Math.Max(layout.ItemHeight, _theme.ControlHeight)); StorageSearchEntry selected = storageSearchEntry; ((UnityEvent)obj.onClick).AddListener((UnityAction)delegate { Select(selected); }); } } if (num2 == 0) { SetHeight(((Component)CreateText((Transform)(object)_entryContent, "No nearby item matches that filter.", layout.FontSize, textColor, (TextAlignmentOptions)514)).gameObject, Math.Max(layout.ItemHeight * 2, 60)); } } private Button CreateButton(Transform parent, string label, StorageSearchMenuLayout layout, Color textColor, float height = -1f) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Invalid comparison between Unknown and I4 //IL_00c4: 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) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) Image val = CreateImage("Button_" + label, parent, _theme.ButtonSprite, _theme.ButtonImageType, _theme.HasNativeButton ? _theme.ButtonImageColor : Color32.op_Implicit(new Color32((byte)74, (byte)47, (byte)30, byte.MaxValue))); if (Object.op_Implicit((Object)(object)_theme.ButtonMaterial)) { ((Graphic)val).material = _theme.ButtonMaterial; } Button val2 = ((Component)val).gameObject.AddComponent