Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of AutoChestSort v1.0.0
AutoChestSort.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using AutoChestSort.Chests; using AutoChestSort.Commands; using AutoChestSort.Config; using AutoChestSort.Core; using AutoChestSort.Core.Config; using AutoChestSort.Core.Groups; using AutoChestSort.Core.Inventory; using AutoChestSort.Core.Localization; using AutoChestSort.Core.Logging; using AutoChestSort.Core.Network; using AutoChestSort.Core.Sorting; using AutoChestSort.Core.Sorting.Distribution; using AutoChestSort.Core.Ui; using AutoChestSort.Groups; using AutoChestSort.Localization; using AutoChestSort.Network; using AutoChestSort.Sorting; using AutoChestSort.Ui; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("Nexxy")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Nexxy")] [assembly: AssemblyDescription("Valheim chest auto-sorting mod (BepInEx).")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+1f8132cb0513483b73b1f973e223d866478e81b9")] [assembly: AssemblyProduct("AutoChestSort")] [assembly: AssemblyTitle("AutoChestSort")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AutoChestSort { internal static class AcsLog { private static readonly LogChangeGate Changes = new LogChangeGate(); private static ManualLogSource _log; private static bool _configKnown; internal static bool DebugEnabled { get; private set; } internal static string DebugSource { get { if (!_configKnown) { return "default"; } return "config"; } } internal static void Install(ManualLogSource log) { _log = log; } internal static bool ApplyConfigDebug(bool enabled) { _configKnown = true; if (DebugEnabled != enabled) { if (!enabled) { Info("debug logging disabled (config)"); } DebugEnabled = enabled; if (enabled) { Info("debug logging enabled (config)"); } } return DebugEnabled; } internal static void Info(string message) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)message); } } internal static bool InfoIfChanged(string key, string state, string message) { if (!Changes.HasChanged(key, state)) { Debug(message); return false; } Info(message); return true; } internal static bool WarningIfChanged(string key, string state, string message) { if (!Changes.HasChanged(key, state)) { Debug(message); return false; } Warning(message); return true; } internal static void ForgetChangeStates() { Changes.Clear(); } internal static void Warning(string message) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)message); } } internal static void Error(string message) { ManualLogSource log = _log; if (log != null) { log.LogError((object)message); } } internal static void Error(string message, Exception error) { ManualLogSource log = _log; if (log != null) { log.LogError((object)(message + ": " + error)); } } internal static void Debug(string message) { if (DebugEnabled) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)message); } } } } [BepInPlugin("nexxy.valheim.autochestsort", "AutoChestSort", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public class AutoChestSortPlugin : BaseUnityPlugin { private Harmony _harmony; private void Awake() { //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) AcsLog.Install(((BaseUnityPlugin)this).Logger); AcsConfigService.Install(((BaseUnityPlugin)this).Config); AcsChestCatalog.Install(); AcsLog.Info("Hello from ACS v1.0.0 on Valheim " + ReadGameVersion()); AcsLog.Debug("Core reserved chest names: " + string.Join(", ", ReservedNames.All)); AcsLocalization.Register(((BaseUnityPlugin)this).Info.Metadata, ((BaseUnityPlugin)this).Logger); string text = "ACS localization: registered=" + AcsLocalization.IsRegistered + ", tokens=" + AcsTokens.Count + ", languages=" + string.Join("/", ToArray(AcsTokens.Languages)) + ", batchMode=" + Application.isBatchMode; if (AcsLocalization.IsRegistered) { AcsLog.Debug(text); } else { AcsLog.Warning(text + " - texts fall back to their raw tokens"); } bool allowVanillaClients = AcsConfigService.Current.AllowVanillaClients; AcsLog.Info("ACS network gate: " + ((object)(CompatibilityLevel)5/*cast due to .constrained prefix*/).ToString() + " / " + ((object)(VersionStrictness)3/*cast due to .constrained prefix*/).ToString() + ", plugin version 1.0.0, allowVanillaClients=" + (allowVanillaClients ? "true" : "false") + " (a client with ACS must match this version; a client without ACS " + (allowVanillaClients ? "may join" : "is turned away at the handshake") + ")"); AcsCommands.Install(); _harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "nexxy.valheim.autochestsort"); ContainerOpenSignal.Install(_harmony); VanillaClientGate.Install(_harmony); AcsLocalization.CatchUpIfAlreadyInitialized(); } private static string[] ToArray(IReadOnlyList<string> values) { string[] array = new string[values.Count]; for (int i = 0; i < values.Count; i++) { array[i] = values[i]; } return array; } private void OnDestroy() { AcsConfigService.Shutdown(); if (_harmony != null) { _harmony.UnpatchSelf(); _harmony = null; } } private static string ReadGameVersion() { try { return GetVersionStringDirect(); } catch (Exception ex) { try { return GetVersionStringReflected(); } catch (Exception ex2) { return "unknown (" + ex.GetType().Name + " / " + ex2.GetType().Name + ")"; } } } [MethodImpl(MethodImplOptions.NoInlining)] private static string GetVersionStringDirect() { return Version.GetVersionString(false); } [MethodImpl(MethodImplOptions.NoInlining)] private static string GetVersionStringReflected() { Type type = AccessTools.TypeByName("Version"); if (type == null) { return "unknown (type Version not found)"; } MethodInfo method = type.GetMethod("GetVersionString", BindingFlags.Static | BindingFlags.Public); if (method != null) { ParameterInfo[] parameters = method.GetParameters(); object[] array = new object[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { array[i] = parameters[i].DefaultValue; } return Convert.ToString(method.Invoke(null, array)); } PropertyInfo property = type.GetProperty("CurrentVersion", BindingFlags.Static | BindingFlags.Public); if (property != null) { return Convert.ToString(property.GetValue(null, null)); } return "unknown (no version API on type Version)"; } } public static class PluginInfo { public const string Guid = "nexxy.valheim.autochestsort"; public const string Name = "AutoChestSort"; public const string Version = "1.0.0"; } internal static class AcsVersion { public const string Value = "1.0.0"; } } namespace AutoChestSort.Ui { internal sealed class AcsDialog { private const float Width = 520f; private const float Height = 300f; private GameObject _root; private Text _title; private Text _body; private Text _warning; private Button _yes; private Button _no; private Button _cancel; private Text _yesLabel; private Text _noLabel; private Text _cancelLabel; private Action _onYes; private Action _onNo; private Action _onCancel; internal bool IsOpen { get { if ((Object)(object)_root != (Object)null) { return _root.activeSelf; } return false; } } internal bool Show(string title, string body, string warning, string yes, string no, string cancel, Action onYes, Action onNo, Action onCancel) { if (!EnsureBuilt()) { return false; } _onYes = onYes; _onNo = onNo; _onCancel = onCancel; _title.text = title ?? string.Empty; _body.text = body ?? string.Empty; bool flag = !string.IsNullOrEmpty(warning); ((Component)_warning).gameObject.SetActive(flag); _warning.text = (flag ? warning : string.Empty); SetButton(_yes, _yesLabel, yes); SetButton(_no, _noLabel, no); SetButton(_cancel, _cancelLabel, cancel); _root.SetActive(true); _root.transform.SetAsLastSibling(); return true; } internal void Hide() { _onYes = null; _onNo = null; _onCancel = null; if ((Object)(object)_root != (Object)null) { _root.SetActive(false); } } internal void Destroy() { Hide(); if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } } private static void SetButton(Button button, Text label, string caption) { bool flag = !string.IsNullOrEmpty(caption); ((Component)button).gameObject.SetActive(flag); if (flag) { label.text = caption; } } private bool EnsureBuilt() { if ((Object)(object)_root != (Object)null && (Object)(object)GUIManager.CustomGUIFront != (Object)null && (Object)(object)_root.transform.parent == (Object)(object)GUIManager.CustomGUIFront.transform) { return true; } try { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); _root = null; } GUIManager instance = GUIManager.Instance; GameObject customGUIFront = GUIManager.CustomGUIFront; if (instance == null || (Object)(object)customGUIFront == (Object)null || (Object)(object)instance.AveriaSerifBold == (Object)null) { return false; } Build(instance, customGUIFront); return true; } catch (Exception error) { AcsLog.Error("ui: building the ACS dialog failed", error); _root = null; return false; } } private void Build(GUIManager manager, GameObject parent) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected O, but got Unknown //IL_0131: 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_014d: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Expected O, but got Unknown _root = manager.CreateWoodpanel(parent.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, 520f, 300f, false); ((Object)_root).name = "ACS_Dialog"; _title = MakeText(manager, "ACS_DialogTitle", 18, manager.ValheimOrange, new Vector2(0f, 122f), 480f, 26f, (TextAnchor)4); _body = MakeText(manager, "ACS_DialogBody", 16, manager.ValheimBeige, new Vector2(0f, 52f), 480f, 84f, (TextAnchor)4); _warning = MakeText(manager, "ACS_DialogWarning", 14, manager.ValheimYellow, new Vector2(0f, -48f), 480f, 68f, (TextAnchor)4); _yes = MakeButton(manager, "ACS_DialogYes", new Vector2(-150f, -120f), out _yesLabel, (UnityAction)delegate { Fire(_onYes); }); _no = MakeButton(manager, "ACS_DialogNo", new Vector2(0f, -120f), out _noLabel, (UnityAction)delegate { Fire(_onNo); }); _cancel = MakeButton(manager, "ACS_DialogCancel", new Vector2(150f, -120f), out _cancelLabel, (UnityAction)delegate { Fire(_onCancel); }); _root.SetActive(false); } private Text MakeText(GUIManager manager, string name, int size, Color color, Vector2 position, float width, float height, TextAnchor anchor) { //IL_001b: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) GameObject obj = manager.CreateText(string.Empty, _root.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), position, manager.AveriaSerifBold, size, color, true, Color.black, width, height, false); ((Object)obj).name = name; Text component = obj.GetComponent<Text>(); component.alignment = anchor; component.horizontalOverflow = (HorizontalWrapMode)0; component.verticalOverflow = (VerticalWrapMode)1; return component; } private Button MakeButton(GUIManager manager, string name, Vector2 position, out Text label, UnityAction action) { //IL_001b: 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) GameObject val = manager.CreateButton(string.Empty, _root.transform, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), position, 130f, 34f); ((Object)val).name = name; label = val.GetComponentInChildren<Text>(); Button component = val.GetComponent<Button>(); ((UnityEvent)component.onClick).AddListener(action); return component; } private void Fire(Action action) { try { action?.Invoke(); } catch (Exception error) { AcsLog.Error("ui: a dialog button failed", error); Hide(); AcsUiText.MessageToken("$acs_err_server"); } } } internal static class AcsUiText { internal static string Status(GroupResponse response) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) if (response == null) { return string.Empty; } string token = AcsStatusTokens.TokenForOrFallback(response.Status); return AcsStatusTokens.PlaceholderCount(response.Status) switch { 2 => AcsLocalization.Format(token, Display(response), response.AffectedChests), 1 => AcsLocalization.Format(token, Display(response)), _ => AcsLocalization.Get(token), }; } internal static string Learned(GroupResponse response) { string token = ChestPanelTokens.LearnedToken(response.SkippedItems); if (response.SkippedItems <= 0) { return AcsLocalization.Format(token, Display(response), response.Items.Count); } return AcsLocalization.Format(token, Display(response), response.Items.Count, response.SkippedItems); } internal static string Display(GroupResponse response) { if (response == null) { return string.Empty; } if (response.DisplayName.Length <= 0) { return response.GroupName; } return response.DisplayName; } internal static string JoinItems(IReadOnlyList<string> values) { if (values == null || values.Count == 0) { return AcsLocalization.Get("$acs_ui_info_empty"); } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < values.Count; i++) { if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(values[i]); } return stringBuilder.ToString(); } internal static void Message(string text) { if (!string.IsNullOrEmpty(text)) { MessageHud instance = MessageHud.instance; if ((Object)(object)instance == (Object)null) { AcsLog.Debug("ui: no MessageHud for \"" + text + "\""); } else { instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); } } } internal static void MessageToken(string token) { if (!string.IsNullOrEmpty(token)) { Message(AcsLocalization.Get(token)); } } } internal sealed class ChestPanelWidgets { internal const float PanelWidth = 430f; internal const float CollapsedHeight = 92f; private const float NameWidth = 250f; private const float RowHeight = 30f; private const float ButtonSize = 36f; private const float ButtonPitch = 42f; private const float Margin = 8f; private const float LineHeight = 18f; private const float InfoTop = 42f; private const float StatusTop = 60f; private const float ItemsTop = 80f; private const float ItemsMinHeight = 20f; private const float ItemsMaxHeight = 200f; private static Vector2 _offset = new Vector2(0f, -6f); private float _itemsHeight = 20f; internal GameObject Root { get; private set; } internal InputField NameField { get; private set; } internal Button ResetButton { get; private set; } internal Button InfoButton { get; private set; } internal Button LearnButton { get; private set; } internal Button DeleteButton { get; private set; } internal Text InfoLine { get; private set; } internal Text StatusLine { get; private set; } internal GameObject ItemsPanel { get; private set; } internal Text ItemsText { get; private set; } internal UITooltip LearnTooltip { get; private set; } internal UITooltip DeleteTooltip { get; private set; } internal UITooltip ResetTooltip { get; private set; } internal UITooltip InfoTooltip { get; private set; } internal static Vector2 Offset => _offset; internal bool AreItemsVisible { get { if ((Object)(object)ItemsPanel != (Object)null) { return ItemsPanel.activeSelf; } return false; } } private ChestPanelWidgets() { } internal static ChestPanelWidgets TryBuild(RectTransform parent) { if ((Object)(object)parent == (Object)null) { return null; } try { GUIManager instance = GUIManager.Instance; if (instance == null || (Object)(object)instance.AveriaSerifBold == (Object)null) { return null; } ChestPanelWidgets chestPanelWidgets = new ChestPanelWidgets(); chestPanelWidgets.Build(instance, parent); return chestPanelWidgets; } catch (Exception error) { AcsLog.Error("ui: building the chest panel failed", error); return null; } } internal void MoveTo(Vector2 offset) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) _offset = offset; if ((Object)(object)Root != (Object)null) { Root.GetComponent<RectTransform>().anchoredPosition = offset; } } internal void SetItemsVisible(bool visible) { if (!((Object)(object)ItemsPanel == (Object)null) && !((Object)(object)Root == (Object)null)) { ItemsPanel.SetActive(visible); Resize(visible); } } internal void SetItemsText(string text) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ItemsText == (Object)null) { return; } ItemsText.text = text ?? string.Empty; float num = 0f; try { num = ItemsText.preferredHeight; } catch (Exception ex) { AcsLog.Debug("ui: measuring the item list failed (" + ex.GetType().Name + ")"); } float num2 = Mathf.Clamp(Mathf.Max(num, Estimate(ItemsText.text)), 20f, 200f); if (!(Mathf.Abs(num2 - _itemsHeight) < 1f)) { _itemsHeight = num2; if ((Object)(object)ItemsPanel != (Object)null) { ItemsPanel.GetComponent<RectTransform>().sizeDelta = new Vector2(414f, _itemsHeight); } Resize(AreItemsVisible); } } private static float Estimate(string text) { if (string.IsNullOrEmpty(text)) { return 20f; } return (float)(1 + text.Length / 58) * 16f + 4f; } private void Resize(bool itemsVisible) { if (!((Object)(object)Root == (Object)null)) { float num = (itemsVisible ? (80f + _itemsHeight + 8f) : 92f); Root.GetComponent<RectTransform>().SetSizeWithCurrentAnchors((Axis)1, num); } } internal void Destroy() { if ((Object)(object)Root != (Object)null) { Object.Destroy((Object)(object)Root); Root = null; } } private void Build(GUIManager manager, RectTransform parent) { //IL_000d: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected O, but got Unknown //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02f5: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_0317: 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_0383: Unknown result type (might be due to invalid IL or missing references) //IL_0398: Unknown result type (might be due to invalid IL or missing references) //IL_03ad: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Unknown result type (might be due to invalid IL or missing references) //IL_03c2: Unknown result type (might be due to invalid IL or missing references) Root = manager.CreateWoodpanel((Transform)(object)parent, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), _offset, 430f, 92f, false); ((Object)Root).name = "ACS_ChestPanel"; RectTransform component = Root.GetComponent<RectTransform>(); component.pivot = new Vector2(0.5f, 1f); component.anchoredPosition = _offset; float num = -215f; float num2 = -23f; GameObject val = manager.CreateInputField(Root.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(num + 8f + 125f, num2), (ContentType)0, AcsLocalization.Get("$acs_ui_name_placeholder"), 16, 250f, 30f); ((Object)val).name = "ACS_Name"; NameField = val.GetComponent<InputField>(); NameField.characterLimit = 32; NameField.lineType = (LineType)0; AddTooltip(val, "$acs_ui_tooltip_name"); float num3 = num + 8f + 250f + 42f - 18f; ResetButton = MakeButton(manager, "Ø", "ACS_Reset", num3, num2, "$acs_ui_tooltip_reset", out var tooltip); ResetTooltip = tooltip; num3 += 42f; InfoButton = MakeButton(manager, "i", "ACS_Info", num3, num2, "$acs_ui_tooltip_info", out var tooltip2); InfoTooltip = tooltip2; num3 += 42f; LearnButton = MakeButton(manager, "+", "ACS_Learn", num3, num2, "$acs_ui_tooltip_learn", out var tooltip3); LearnTooltip = tooltip3; num3 += 42f; DeleteButton = MakeButton(manager, "−", "ACS_Delete", num3, num2, "$acs_ui_tooltip_delete", out var tooltip4); DeleteTooltip = tooltip4; InfoLine = MakeLine(manager, "ACS_InfoLine", 42f, 14, manager.ValheimBeige); StatusLine = MakeLine(manager, "ACS_StatusLine", 60f, 13, manager.ValheimYellow); ItemsPanel = new GameObject("ACS_Items", new Type[1] { typeof(RectTransform) }); ItemsPanel.transform.SetParent(Root.transform, false); RectTransform component2 = ItemsPanel.GetComponent<RectTransform>(); component2.anchorMin = new Vector2(0.5f, 1f); component2.anchorMax = new Vector2(0.5f, 1f); component2.pivot = new Vector2(0.5f, 1f); component2.anchoredPosition = new Vector2(0f, -80f); component2.sizeDelta = new Vector2(414f, _itemsHeight); GameObject val2 = manager.CreateText(string.Empty, ItemsPanel.transform, new Vector2(0f, 0f), new Vector2(1f, 1f), Vector2.zero, manager.AveriaSerifBold, 13, manager.ValheimOrange, true, Color.black, 0f, 0f, false); ((Object)val2).name = "ACS_ItemsText"; ItemsText = val2.GetComponent<Text>(); ItemsText.alignment = (TextAnchor)0; ItemsText.horizontalOverflow = (HorizontalWrapMode)0; ItemsText.verticalOverflow = (VerticalWrapMode)1; RectTransform component3 = val2.GetComponent<RectTransform>(); component3.anchorMin = new Vector2(0f, 0f); component3.anchorMax = new Vector2(1f, 1f); component3.pivot = new Vector2(0.5f, 0.5f); component3.offsetMin = Vector2.zero; component3.offsetMax = Vector2.zero; ItemsPanel.SetActive(false); } private Text MakeLine(GUIManager manager, string name, float top, int size, Color color) { //IL_001b: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004c: 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) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) GameObject obj = manager.CreateText(string.Empty, Root.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0f, 0f - (top + 9f)), manager.AveriaSerifBold, size, color, true, Color.black, 414f, 18f, false); ((Object)obj).name = name; RectTransform component = obj.GetComponent<RectTransform>(); component.anchorMin = new Vector2(0.5f, 1f); component.anchorMax = new Vector2(0.5f, 1f); component.pivot = new Vector2(0.5f, 0.5f); component.sizeDelta = new Vector2(414f, 18f); component.anchoredPosition = new Vector2(0f, 0f - (top + 9f)); Text component2 = obj.GetComponent<Text>(); component2.alignment = (TextAnchor)3; component2.horizontalOverflow = (HorizontalWrapMode)1; component2.verticalOverflow = (VerticalWrapMode)1; return component2; } private Button MakeButton(GUIManager manager, string label, string name, float x, float y, string tooltipToken, out UITooltip tooltip) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0026: 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) GameObject val = manager.CreateButton(label, Root.transform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(x, y), 36f, 30f); ((Object)val).name = name; tooltip = AddTooltip(val, tooltipToken); return val.GetComponent<Button>(); } internal static void SetTooltipText(UITooltip tooltip, string text) { //IL_0031: 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) if ((Object)(object)tooltip == (Object)null) { return; } string text2 = text ?? string.Empty; if (string.Equals(tooltip.m_text, text2, StringComparison.Ordinal)) { return; } try { tooltip.Set(tooltip.m_topic, text2, (RectTransform)null, default(Vector2)); } catch (Exception ex) { tooltip.m_text = text2; AcsLog.Debug("ui: refreshing a tooltip failed (" + ex.GetType().Name + ")"); } } private static UITooltip AddTooltip(GameObject target, string token) { try { GameObject val = TooltipPrefab(); if ((Object)(object)val == (Object)null) { return null; } UITooltip val2 = target.GetComponent<UITooltip>(); if ((Object)(object)val2 == (Object)null) { val2 = target.AddComponent<UITooltip>(); } val2.m_tooltipPrefab = val; val2.m_text = AcsLocalization.Get(token); return val2; } catch (Exception ex) { AcsLog.Debug("ui: no tooltip for " + ((Object)target).name + " (" + ex.GetType().Name + ")"); return null; } } private static GameObject TooltipPrefab() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null) { return null; } UITooltip[] componentsInChildren = ((Component)instance).GetComponentsInChildren<UITooltip>(true); for (int i = 0; i < componentsInChildren.Length; i++) { if ((Object)(object)componentsInChildren[i] != (Object)null && (Object)(object)componentsInChildren[i].m_tooltipPrefab != (Object)null) { return componentsInChildren[i].m_tooltipPrefab; } } return null; } } internal sealed class ChestUiController : MonoBehaviour { private struct Sent { internal GroupOpcode Opcode { get; } internal ZDOID Chest { get; } internal Sent(GroupOpcode opcode, ZDOID chest) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) Opcode = opcode; Chest = chest; } } internal const float PollSeconds = 2f; internal const float WriteTimeoutSeconds = 6f; internal const float StatusSeconds = 6f; private readonly AcsDialog _dialog = new AcsDialog(); private readonly Dictionary<int, Sent> _mine = new Dictionary<int, Sent>(); private ChestPanelWidgets _widgets; private bool _handlersWired; private Container _chest; private ZDOID _chestId; private ChestPanelFacts _facts; private ChestPanelState _state; private float _pollTimer; private int _pendingWrite; private float _pendingSince; private int _storeVersion; private int _lockRevision = -1; private GroupStatus _lastQueryStatus; private GroupResponse _preview; private ConflictQueue _queue; private LearnMode _commitMode; private bool _modeChosen; private string _status = string.Empty; private float _statusUntil; private bool _itemsDirty = true; private bool _nameFocused; private bool _known; private int _buildAttempts; private readonly List<string> _items = new List<string>(); internal static ChestUiController Instance { get; private set; } internal ZDOID OpenChestId { get { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_chest != (Object)null)) { return ZDOID.None; } return _chestId; } } internal static void Install() { //IL_0025: 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_0030: Expected O, but got Unknown if (!((Object)(object)Instance != (Object)null)) { if (GUIManager.IsHeadless()) { AcsLog.Debug("ui: headless process, no chest panel"); return; } GameObject val = new GameObject("ACS_ChestUi"); Object.DontDestroyOnLoad((Object)val); Instance = val.AddComponent<ChestUiController>(); AcsLog.Info("ui: chest panel installed (poll " + 2f + " s)"); } } internal string Describe() { return "chest=" + (((Object)(object)_chest != (Object)null) ? ((object)Unsafe.As<ZDOID, ZDOID>(ref _chestId)/*cast due to .constrained prefix*/).ToString() : "none") + " name=\"" + _facts.DisplayName + "\" role=" + ((object)Unsafe.As<GroupRole, GroupRole>(ref _facts.Role)/*cast due to .constrained prefix*/).ToString() + " stacks=" + _facts.ChestStacks + " groupItems=" + _facts.GroupItems + " chests=" + _facts.ChestCount + " open(others)=" + _facts.OpenChests + " known=" + _facts.Known + " busy=" + (_pendingWrite != 0) + " dialog=" + _dialog.IsOpen + " commitMode=" + ((object)Unsafe.As<LearnMode, LearnMode>(ref _commitMode)/*cast due to .constrained prefix*/).ToString() + " modeChosen=" + _modeChosen + " storeVersion=" + _storeVersion + " lockRevision=" + _lockRevision + " widgets=" + (_widgets != null) + " " + InputBlockGuard.Describe(); } internal void MoveTo(Vector2 offset) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) _widgets?.MoveTo(offset); } internal unsafe void OnPanelGone(string reason) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_chest == (Object)null) { InputBlockGuard.Release(reason); return; } ZDOID chestId = _chestId; AcsLog.Debug("ui: chest panel closed (" + reason + "), chest " + ((object)(*(ZDOID*)(&chestId))/*cast due to .constrained prefix*/).ToString()); Teardown(reason); } private void OnDestroy() { try { Teardown("controller destroyed"); _dialog.Destroy(); _widgets?.Destroy(); _widgets = null; ItemNames.Clear(); GroupRpc.ResponseReceived -= OnResponse; } catch (Exception error) { AcsLog.Error("ui: tearing the chest panel down failed", error); } finally { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } } private void Awake() { GroupRpc.ResponseReceived += OnResponse; } private void Update() { try { Container val = CurrentContainer(); if ((Object)(object)val != (Object)(object)_chest) { if ((Object)(object)_chest != (Object)null) { Teardown("another chest"); } if ((Object)(object)val != (Object)null) { Setup(val); } } if ((Object)(object)_chest == (Object)null) { InputBlockGuard.Release("no chest open"); return; } UpdateNameFocus(); ExpirePendingWrite(); Poll(); RefreshState(); } catch (Exception error) { AcsLog.Error("ui: the chest panel update failed, closing it", error); try { Teardown("update failed"); } catch (Exception error2) { AcsLog.Error("ui: emergency teardown failed", error2); InputBlockGuard.Release("emergency"); } } } private static Container CurrentContainer() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null || ((Character)Player.m_localPlayer).IsDead()) { return null; } Container currentContainer = instance.m_currentContainer; if ((Object)(object)currentContainer == (Object)null || !currentContainer.IsOwner()) { return null; } ZDO zdo = ChestRegistry.GetZdo(currentContainer); if (zdo == null || !AcsChestCatalog.Current.IsSupported(zdo.GetPrefab())) { return null; } return currentContainer; } private void Setup(Container container) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) ZDO zdo = ChestRegistry.GetZdo(container); if (zdo != null && EnsureWidgets()) { _chest = container; _chestId = zdo.m_uid; _facts = default(ChestPanelFacts); _facts.DisplayName = ChestName.GetDisplay(zdo); _facts.Role = GroupRoles.Of(_facts.DisplayName); _facts.ChestStacks = CountStacks(container); _known = false; _pendingWrite = 0; _storeVersion = 0; _lockRevision = -1; _lastQueryStatus = (GroupStatus)0; _preview = null; _queue = null; _modeChosen = false; _commitMode = (LearnMode)0; _items.Clear(); _itemsDirty = true; _status = string.Empty; _statusUntil = 0f; _mine.Clear(); SetNameFieldText(_facts.DisplayName); _widgets.Root.SetActive(true); _widgets.SetItemsVisible(AcsConfigService.Current.EnableDebug); _pollTimer = 0f; SendQuery(); RefreshState(); AcsLog.Debug("ui: chest panel opened for " + ChestName.Describe(zdo) + ", name \"" + _facts.DisplayName + "\", stacks " + _facts.ChestStacks); } } private void Teardown(string reason) { //IL_0056: 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) InputBlockGuard.Release(reason); _dialog.Hide(); _preview = null; _queue = null; _modeChosen = false; _status = string.Empty; _statusUntil = 0f; _mine.Clear(); _pendingWrite = 0; _chest = null; _chestId = ZDOID.None; _known = false; if (_widgets != null) { if ((Object)(object)_widgets.NameField != (Object)null) { _widgets.NameField.DeactivateInputField(); } _widgets.SetItemsVisible(visible: false); if ((Object)(object)_widgets.Root != (Object)null) { _widgets.Root.SetActive(false); } } _nameFocused = false; } private bool EnsureWidgets() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_container == (Object)null) { return false; } if (_widgets != null && (Object)(object)_widgets.Root != (Object)null && (Object)(object)_widgets.Root.transform.parent == (Object)(object)((Component)instance.m_container).transform) { return true; } _handlersWired = false; _widgets?.Destroy(); _widgets = ChestPanelWidgets.TryBuild(instance.m_container); if (_widgets == null) { _buildAttempts++; if (_buildAttempts == 1 || _buildAttempts % 300 == 0) { AcsLog.Warning("ui: the chest panel could not be built yet (attempt " + _buildAttempts + "), retrying"); } return false; } WireHandlers(); AcsLog.Debug("ui: chest panel built under InventoryGui.m_container"); return true; } private void WireHandlers() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown if (!_handlersWired && _widgets != null) { _handlersWired = true; ((UnityEvent<string>)(object)_widgets.NameField.onEndEdit).AddListener((UnityAction<string>)OnNameSubmitted); ((UnityEvent)_widgets.ResetButton.onClick).AddListener(new UnityAction(OnResetClicked)); ((UnityEvent)_widgets.InfoButton.onClick).AddListener(new UnityAction(OnInfoClicked)); ((UnityEvent)_widgets.LearnButton.onClick).AddListener(new UnityAction(OnLearnClicked)); ((UnityEvent)_widgets.DeleteButton.onClick).AddListener(new UnityAction(OnDeleteClicked)); } } private void UpdateNameFocus() { bool flag = _widgets != null && (Object)(object)_widgets.NameField != (Object)null && _widgets.NameField.isFocused; if (flag != _nameFocused) { _nameFocused = flag; if (flag) { InputBlockGuard.Block(); } else { InputBlockGuard.Release("name field lost focus"); } } } private void ExpirePendingWrite() { if (_pendingWrite != 0 && !(Time.unscaledTime - _pendingSince < 6f)) { AcsLog.Warning("ui: no answer to request #" + _pendingWrite + " within " + 6f + " s, the panel is usable again"); _mine.Remove(_pendingWrite); _pendingWrite = 0; if (_dialog.IsOpen) { _dialog.Hide(); _preview = null; _queue = null; } AnnounceToken("$acs_err_server"); } } private void Poll() { _pollTimer -= Time.unscaledDeltaTime; if (!(_pollTimer > 0f)) { _pollTimer = 2f; SendQuery(); } } private void RefreshState() { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) if (_widgets == null || (Object)(object)_chest == (Object)null) { return; } ZDO zdo = ChestRegistry.GetZdo(_chest); if (zdo == null) { Teardown("chest zdo gone"); return; } string display = ChestName.GetDisplay(zdo); if (!_nameFocused && !string.Equals(display, _facts.DisplayName, StringComparison.Ordinal)) { _facts.DisplayName = display; _facts.Role = GroupRoles.Of(display); SetNameFieldText(display); } _facts.ChestStacks = CountStacks(_chest); _facts.CtrlHeld = ZInput.GetKey((KeyCode)306, false) || ZInput.GetKey((KeyCode)305, false); _facts.Known = _known; _facts.Busy = _pendingWrite != 0; _facts.DialogOpen = _dialog.IsOpen; _state = ChestPanelState.Evaluate(_facts); ((Selectable)_widgets.NameField).interactable = _state.NameFieldEnabled; _widgets.NameField.readOnly = !_state.NameFieldEnabled; if (!_state.NameFieldEnabled && _widgets.NameField.isFocused) { _widgets.NameField.DeactivateInputField(); } ((Selectable)_widgets.ResetButton).interactable = _state.ResetEnabled; ((Selectable)_widgets.InfoButton).interactable = _state.InfoEnabled; ((Selectable)_widgets.LearnButton).interactable = _state.LearnEnabled; ((Selectable)_widgets.DeleteButton).interactable = _state.DeleteEnabled; if (!_state.InfoEnabled && _widgets.AreItemsVisible) { _widgets.SetItemsVisible(visible: false); } ChestPanelWidgets.SetTooltipText(_widgets.LearnTooltip, AcsLocalization.Get((_state.LearnBlockedToken.Length > 0) ? _state.LearnBlockedToken : "$acs_ui_tooltip_learn")); ChestPanelWidgets.SetTooltipText(_widgets.DeleteTooltip, AcsLocalization.Get((_state.DeleteEnabled || _state.DeleteBlockedToken.Length == 0) ? "$acs_ui_tooltip_delete" : _state.DeleteBlockedToken)); ChestPanelWidgets.SetTooltipText(_widgets.ResetTooltip, AcsLocalization.Get((!_state.ResetEnabled && _state.Locked) ? "$acs_msg_group_locked" : "$acs_ui_tooltip_reset")); ChestPanelWidgets.SetTooltipText(_widgets.InfoTooltip, AcsLocalization.Get((_state.InfoBlockedToken.Length > 0) ? _state.InfoBlockedToken : "$acs_ui_tooltip_info")); _widgets.InfoLine.text = BuildInfoLine(); _widgets.StatusLine.text = CurrentStatus(); if (_widgets.AreItemsVisible && _itemsDirty) { _widgets.SetItemsText(BuildItemsText()); _itemsDirty = false; } } private string CurrentStatus() { if (_status.Length == 0) { return string.Empty; } if (Time.unscaledTime >= _statusUntil) { _status = string.Empty; return string.Empty; } return _status; } private void Announce(string text) { if (!string.IsNullOrEmpty(text)) { AcsUiText.Message(text); _status = text; _statusUntil = Time.unscaledTime + 6f; if ((Object)(object)_widgets?.StatusLine != (Object)null) { _widgets.StatusLine.text = text; } } } private void AnnounceToken(string token) { if (!string.IsNullOrEmpty(token)) { Announce(AcsLocalization.Get(token)); } } private string BuildInfoLine() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(AcsLocalization.Get(ChestPanelTokens.RoleToken(_facts.Role))); if (((ChestPanelFacts)(ref _facts)).IsGroup) { stringBuilder.Append(": ").Append(_facts.DisplayName); stringBuilder.Append(" ").Append(AcsLocalization.Format("$acs_ui_info_chests", _facts.ChestCount)); } if (!_known) { stringBuilder.Append(" ").Append(GroupStatusExtensions.IsOk(_lastQueryStatus) ? AcsLocalization.Get("$acs_ui_waiting") : AcsLocalization.Get(AcsStatusTokens.TokenForOrFallback(_lastQueryStatus))); } else if (_state != null && _state.Locked) { stringBuilder.Append(" ").Append(AcsLocalization.Get("$acs_msg_group_locked")); } if (AcsConfigService.Current.EnableDebug) { stringBuilder.Append(" [").Append(_chestId).Append(" v") .Append(_storeVersion) .Append(" lr") .Append(_lockRevision) .Append(" open") .Append(_facts.OpenChests) .Append("]"); } return stringBuilder.ToString(); } private string BuildItemsText() { return AcsLocalization.Get("$acs_ui_info_header") + ": " + AcsUiText.JoinItems(ItemNames.Display(_items)); } private void SetItems(List<string> items) { if (items != null && !SameItems(items)) { _items.Clear(); _items.AddRange(items); _itemsDirty = true; } } private bool SameItems(List<string> items) { if (items.Count != _items.Count) { return false; } for (int i = 0; i < items.Count; i++) { if (!string.Equals(items[i], _items[i], StringComparison.Ordinal)) { return false; } } return true; } private static int CountStacks(Container container) { try { Inventory val = ((container != null) ? container.GetInventory() : null); return (val != null) ? val.NrOfItems() : 0; } catch (Exception ex) { AcsLog.Debug("ui: reading the chest inventory failed (" + ex.GetType().Name + ")"); return 0; } } private void SetNameFieldText(string text) { if (!((Object)(object)_widgets?.NameField == (Object)null)) { _widgets.NameField.text = text ?? string.Empty; } } private void OnNameSubmitted(string typed) { try { if ((Object)(object)_chest == (Object)null || _state == null) { return; } string text = ChestNameNormalizer.ToRaw(typed); if (string.Equals(text, ChestNameNormalizer.ToRaw(_facts.DisplayName), StringComparison.Ordinal)) { SetNameFieldText(_facts.DisplayName); } else if (!_state.NameFieldEnabled) { SetNameFieldText(_facts.DisplayName); if (_state.Locked) { AnnounceToken("$acs_msg_group_locked"); } } else { Send((GroupOpcode)4, (LearnMode)0, text); } } catch (Exception error) { AcsLog.Error("ui: submitting the chest name failed", error); } } private void OnResetClicked() { try { if ((Object)(object)_chest == (Object)null || _state == null || !_state.ResetEnabled) { Explain((_state != null && _state.Locked) ? "$acs_msg_group_locked" : string.Empty); } else { Send((GroupOpcode)5, (LearnMode)0, string.Empty); } } catch (Exception error) { AcsLog.Error("ui: the reset button failed", error); } } private void OnInfoClicked() { try { if (_widgets != null && _state != null && _state.InfoEnabled) { _widgets.SetItemsVisible(!_widgets.AreItemsVisible); if (_widgets.AreItemsVisible) { _widgets.SetItemsText(BuildItemsText()); _itemsDirty = false; } } } catch (Exception error) { AcsLog.Error("ui: the info button failed", error); } } private void OnLearnClicked() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)_chest == (Object)null) && _state != null) { if (!_state.LearnEnabled) { Explain(_state.LearnBlockedToken); return; } if (_state.LearnIsMiscFastpath) { Send((GroupOpcode)6, (LearnMode)0, string.Empty); return; } _preview = null; _queue = null; _modeChosen = false; _commitMode = (LearnMode)0; Send((GroupOpcode)1, (LearnMode)0, string.Empty); } } catch (Exception error) { AcsLog.Error("ui: the learn button failed", error); } } private void OnDeleteClicked() { try { if ((Object)(object)_chest == (Object)null || _state == null) { return; } if (!_state.DeleteEnabled) { Explain(_state.DeleteBlockedToken); return; } if (!_state.DeleteArmed) { AnnounceToken("$acs_msg_delete_needs_ctrl"); return; } string displayName = _facts.DisplayName; if (!_dialog.Show(AcsLocalization.Format("$acs_dlg_delete_title", displayName), AcsLocalization.Format("$acs_dlg_delete_text", _facts.ChestCount), string.Empty, AcsLocalization.Get("$acs_dlg_button_yes"), string.Empty, AcsLocalization.Get("$acs_dlg_button_cancel"), delegate { _dialog.Hide(); Send((GroupOpcode)3, (LearnMode)0, string.Empty); }, null, Cancelled)) { AcsLog.Warning("ui: the delete confirmation could not be shown, nothing was deleted"); AnnounceToken("$acs_err_server"); } } catch (Exception error) { AcsLog.Error("ui: the delete button failed", error); } } private void Cancelled() { _dialog.Hide(); _preview = null; _queue = null; _modeChosen = false; AnnounceToken("$acs_msg_cancelled"); } private void Explain(string token) { AnnounceToken(token); } private void ShowModeStep() { if (_preview != null && _queue != null) { LearnDialogPlan plan = LearnDialogPlan.ForPreview(_preview.Removed.Count); string text = AcsUiText.Display(_preview); string body = AcsLocalization.Format("$acs_dlg_confirm_learn", text) + "\n" + AcsLocalization.Format("$acs_dlg_learn_new", AcsUiText.JoinItems(ItemNames.Display(_preview.Accepted))); string warning = (plan.ShowsRemovedWarning ? (RemovedWarning((LearnMode)0) + "\n" + AcsLocalization.Get("$acs_dlg_learn_merge_hint")) : string.Empty); if (!_dialog.Show(AcsLocalization.Format("$acs_dlg_learn_title", text), body, warning, AcsLocalization.Get(plan.PrimaryToken), plan.HasSecondary ? AcsLocalization.Get(plan.SecondaryToken) : string.Empty, AcsLocalization.Get(plan.CancelToken), delegate { //IL_000c: Unknown result type (might be due to invalid IL or missing references) ChooseMode(plan.PrimaryMode); }, plan.HasSecondary ? ((Action)delegate { //IL_000c: Unknown result type (might be due to invalid IL or missing references) ChooseMode(plan.SecondaryMode); }) : null, Cancelled)) { AcsLog.Warning("ui: the learn dialog could not be shown, the learn run is dropped - nothing was changed"); Cancelled(); } } } private unsafe void ChooseMode(LearnMode mode) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _commitMode = mode; _modeChosen = true; _dialog.Hide(); string[] obj = new string[7] { "ui: learn mode ", ((object)(*(LearnMode*)(&mode))/*cast due to .constrained prefix*/).ToString(), " chosen for \"", AcsUiText.Display(_preview), "\", ", null, null }; ConflictQueue queue = _queue; obj[5] = ((queue != null) ? queue.Total : 0).ToString(); obj[6] = " conflict(s) to answer"; AcsLog.Debug(string.Concat(obj)); ShowNextLearnStep(); } private void ShowNextLearnStep() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (_preview == null || _queue == null) { return; } if (_queue.IsCancelled) { Cancelled(); return; } if (!_modeChosen) { ShowModeStep(); return; } string text = AcsUiText.Display(_preview); string warning = RemovedWarning(_commitMode); LearnConflict current = _queue.Current; if (current != null) { string text2 = ((_queue.Total > 1) ? (AcsLocalization.Format("$acs_dlg_conflict_progress", _queue.Position, _queue.Total) + "\n") : string.Empty); if (!_dialog.Show(AcsLocalization.Format("$acs_dlg_learn_title", text), text2 + AcsLocalization.Format("$acs_dlg_conflict_text", ItemNames.Display(current.Item.Name), current.CurrentGroup), warning, AcsLocalization.Get("$acs_dlg_button_yes"), AcsLocalization.Get("$acs_dlg_button_no"), AcsLocalization.Get("$acs_dlg_button_cancel"), delegate { AnswerConflict(takeover: true); }, delegate { AnswerConflict(takeover: false); }, Cancelled)) { AcsLog.Warning("ui: the conflict dialog could not be shown, the learn run is dropped - nothing was changed"); Cancelled(); } } else { SendCommit(); } } private string RemovedWarning(LearnMode mode) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (_preview == null || _preview.Removed.Count == 0 || (int)mode != 0) { return string.Empty; } return AcsLocalization.Format("$acs_dlg_removed_header", AcsUiText.JoinItems(ItemNames.Display(_preview.Removed))) + "\n" + AcsLocalization.Get("$acs_dlg_removed_hint"); } private void AnswerConflict(bool takeover) { if (_queue != null) { _queue.Answer(takeover); _dialog.Hide(); ShowNextLearnStep(); } } private void SendQuery() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown if (!((Object)(object)_chest == (Object)null)) { int num = GroupRpc.NextRequestId(); _mine[num] = new Sent((GroupOpcode)7, _chestId); TrimPending(); if (!GroupRpc.Send(new GroupRequest((GroupOpcode)7, num, ((ZDOID)(ref _chestId)).UserID, ((ZDOID)(ref _chestId)).ID, (LearnMode)0, (string)null, 0, (IEnumerable<string>)null, (IEnumerable<string>)null))) { _mine.Remove(num); } } } private void Send(GroupOpcode opcode, LearnMode mode, string text) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown if (!((Object)(object)_chest == (Object)null)) { int num = GroupRpc.NextRequestId(); _mine[num] = new Sent(opcode, _chestId); TrimPending(); _pendingWrite = num; _pendingSince = Time.unscaledTime; if (!GroupRpc.Send(new GroupRequest(opcode, num, ((ZDOID)(ref _chestId)).UserID, ((ZDOID)(ref _chestId)).ID, mode, text, 0, (IEnumerable<string>)null, (IEnumerable<string>)null))) { _mine.Remove(num); _pendingWrite = 0; AnnounceToken("$acs_err_server"); } else { RefreshState(); } } } private void SendCommit() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown if (!((Object)(object)_chest == (Object)null) && _preview != null && _queue != null && _modeChosen) { int num = GroupRpc.NextRequestId(); _mine[num] = new Sent((GroupOpcode)2, _chestId); TrimPending(); _pendingWrite = num; _pendingSince = Time.unscaledTime; if (!GroupRpc.Send(new GroupRequest((GroupOpcode)2, num, ((ZDOID)(ref _chestId)).UserID, ((ZDOID)(ref _chestId)).ID, _commitMode, (string)null, _preview.StoreVersion, (IEnumerable<string>)_queue.Takeover, (IEnumerable<string>)_queue.Skip))) { _mine.Remove(num); _pendingWrite = 0; AnnounceToken("$acs_err_server"); } else { RefreshState(); } } } private void TrimPending() { if (_mine.Count > 32) { _mine.Clear(); _pendingWrite = 0; } } private void OnResponse(GroupResponse response) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: 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_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Invalid comparison between Unknown and I4 //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Invalid comparison between Unknown and I4 //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Invalid comparison between Unknown and I4 try { if (response == null || !_mine.TryGetValue(response.RequestId, out var value)) { return; } _mine.Remove(response.RequestId); if (_pendingWrite == response.RequestId) { _pendingWrite = 0; } if ((Object)(object)_chest == (Object)null || value.Chest != _chestId) { AcsLog.Debug("ui: dropped a late " + ((object)response.Opcode/*cast due to .constrained prefix*/).ToString() + " answer for " + ((object)value.Chest/*cast due to .constrained prefix*/).ToString()); return; } GroupOpcode opcode = response.Opcode; if ((int)opcode != 1) { if ((int)opcode != 2) { if ((int)opcode == 7) { OnQueryAnswer(response); } else { OnWriteAnswer(response); } } else { OnCommitAnswer(response); } } else { OnPreviewAnswer(response); } RefreshState(); } catch (Exception error) { AcsLog.Error("ui: handling a group answer failed", error); } } private void OnQueryAnswer(GroupResponse response) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_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) _lastQueryStatus = response.Status; if (!response.IsOk) { _known = false; return; } _known = true; _facts.Role = response.Role; _facts.GroupItems = response.Items.Count; _facts.ChestCount = response.ChestCount; _facts.OpenChests = response.OpenChests; _storeVersion = response.StoreVersion; _lockRevision = response.LockRevision; SetItems(response.Items); if (response.DisplayName.Length > 0 && !_nameFocused && !string.Equals(response.DisplayName, _facts.DisplayName, StringComparison.Ordinal)) { _facts.DisplayName = response.DisplayName; SetNameFieldText(response.DisplayName); } } private void OnPreviewAnswer(GroupResponse response) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown if (!response.IsOk) { _preview = null; _queue = null; _modeChosen = false; Announce(AcsUiText.Status(response)); return; } _preview = response; _queue = new ConflictQueue((IEnumerable<LearnConflict>)response.Conflicts); _storeVersion = response.StoreVersion; _modeChosen = false; if (response.SkippedItems > 0) { AcsLog.Debug("ui: the preview skipped " + response.SkippedItems + " stack(s) the server cannot resolve"); } ShowModeStep(); } private void OnCommitAnswer(GroupResponse response) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (!response.IsOk) { if ((int)response.Status == 25 || (int)response.Status == 35) { _preview = null; _queue = null; _modeChosen = false; _dialog.Hide(); } Announce(AcsUiText.Status(response)); } else { _preview = null; _queue = null; _modeChosen = false; _dialog.Hide(); _storeVersion = response.StoreVersion; SetItems(response.Items); _facts.GroupItems = response.Items.Count; Announce(AcsUiText.Learned(response)); _pollTimer = 0f; } } private void OnWriteAnswer(GroupResponse response) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) Announce(AcsUiText.Status(response)); if (response.IsOk) { _facts.DisplayName = response.DisplayName; _facts.Role = response.Role; SetNameFieldText(response.DisplayName); _storeVersion = response.StoreVersion; } else { ZDO zdo = ChestRegistry.GetZdo(_chest); if (zdo != null) { _facts.DisplayName = ChestName.GetDisplay(zdo); _facts.Role = GroupRoles.Of(_facts.DisplayName); SetNameFieldText(_facts.DisplayName); } } _known = false; _pollTimer = 2f; SendQuery(); } } [HarmonyPatch(typeof(Container), "GetHoverText")] internal static class ContainerHoverPatch { [HarmonyPostfix] [HarmonyWrapSafe] private static void Postfix(Container __instance, ref string __result) { try { if (!AcsConfigService.Current.ShowChestNameOnHover || (Object)(object)__instance == (Object)null) { return; } ZDO zdo = ChestRegistry.GetZdo(__instance); if (zdo != null && AcsChestCatalog.Current.IsSupported(zdo.GetPrefab())) { string display = ChestName.GetDisplay(zdo); if (display.Length != 0) { string line = AcsLocalization.Format("$acs_ui_hover_name", display); __result = Insert(__result, line); } } } catch (Exception ex) { AcsLog.Debug("ui: the hover text of a chest could not be extended (" + ex.GetType().Name + ": " + ex.Message + ")"); } } private static string Insert(string hover, string line) { if (string.IsNullOrEmpty(hover)) { return line; } int num = hover.IndexOf('\n'); if (num < 0) { return hover + "\n" + line; } return hover.Substring(0, num) + "\n" + line + hover.Substring(num); } } internal static class InputBlockGuard { internal static bool IsBlocking { get; private set; } internal static int Taken { get; private set; } internal static int Released { get; private set; } internal static void Block() { if (IsBlocking) { return; } try { GUIManager.BlockInput(true); IsBlocking = true; Taken++; AcsLog.Debug("ui: input blocked (name field focused), taken=" + Taken); } catch (Exception error) { AcsLog.Error("ui: blocking the input failed", error); } } internal static void Release(string reason) { if (!IsBlocking) { return; } try { GUIManager.BlockInput(false); } catch (Exception error) { AcsLog.Error("ui: releasing the input failed (" + reason + ")", error); } finally { IsBlocking = false; Released++; AcsLog.Debug("ui: input released (" + reason + "), released=" + Released); } } internal static string Describe() { return "blocking=" + IsBlocking + " taken=" + Taken + " released=" + Released; } } [HarmonyPatch(typeof(InventoryGui))] internal static class InventoryGuiPatches { [HarmonyPostfix] [HarmonyWrapSafe] [HarmonyPatch("Hide")] private static void HidePostfix() { ChestUiController.Instance?.OnPanelGone("InventoryGui.Hide"); } [HarmonyPostfix] [HarmonyWrapSafe] [HarmonyPatch("CloseContainer")] private static void CloseContainerPostfix() { ChestUiController.Instance?.OnPanelGone("InventoryGui.CloseContainer"); } [HarmonyPostfix] [HarmonyWrapSafe] [HarmonyPatch("UpdateContainer")] private static void UpdateContainerPostfix(InventoryGui __instance) { if ((Object)(object)__instance.m_currentContainer == (Object)null || !__instance.m_currentContainer.IsOwner()) { ChestUiController.Instance?.OnPanelGone("InventoryGui.UpdateContainer (no owned container)"); } } } internal static class ItemNames { private static readonly Dictionary<string, string> Cache = new Dictionary<string, string>(StringComparer.Ordinal); internal static void Clear() { Cache.Clear(); } internal static string Display(string prefabName) { if (string.IsNullOrEmpty(prefabName)) { return string.Empty; } if (Cache.TryGetValue(prefabName, out var value)) { return value; } string text = Resolve(prefabName); if (!string.Equals(text, prefabName, StringComparison.Ordinal)) { Cache[prefabName] = text; } return text; } internal static List<string> Display(IReadOnlyList<string> prefabNames) { List<string> list = new List<string>(prefabNames?.Count ?? 0); if (prefabNames == null) { return list; } for (int i = 0; i < prefabNames.Count; i++) { list.Add(Display(prefabNames[i])); } return list; } private static string Resolve(string prefabName) { try { string text = SharedName(prefabName); if (string.IsNullOrEmpty(text)) { return prefabName; } Localization instance = Localization.instance; if (instance == null) { return prefabName; } string text2 = instance.Localize(text); return string.IsNullOrWhiteSpace(text2) ? prefabName : text2.Trim(); } catch (Exception ex) { AcsLog.Debug("ui: resolving the item name of \"" + prefabName + "\" failed (" + ex.GetType().Name + ")"); return prefabName; } } private static string SharedName(string prefabName) { ObjectDB instance = ObjectDB.instance; GameObject val = default(GameObject); if ((Object)(object)instance != (Object)null && instance.TryGetItemPrefab(prefabName, ref val) && (Object)(object)val != (Object)null) { string text = TokenOf(val); if (text.Length > 0) { return text; } } ZNetScene instance2 = ZNetScene.instance; if ((Object)(object)instance2 != (Object)null) { GameObject prefab = instance2.GetPrefab(prefabName); if ((Object)(object)prefab != (Object)null) { return TokenOf(prefab); } } return string.Empty; } private static string TokenOf(GameObject prefab) { ItemDrop component = prefab.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null) { return string.Empty; } return component.m_itemData.m_shared.m_name ?? string.Empty; } } } namespace AutoChestSort.Sorting { internal static class ContainerOpenSignal { internal const string RequestOpenRpc = "RPC_RequestOpen"; internal const string OpenResponseRpc = "RPC_OpenResponse"; private static readonly int RequestOpenHash = StableHash.Compute("RPC_RequestOpen"); private static readonly int OpenResponseHash = StableHash.Compute("RPC_OpenResponse"); private static bool _installed; internal static int Seen { get; private set; } internal static bool IsInstalled => _installed; internal static void Install(Harmony harmony) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Expected O, but got Unknown if (_installed || harmony == null) { return; } try { Type? typeFromHandle = typeof(ZRoutedRpc); MethodInfo methodInfo = AccessTools.Method(typeFromHandle, "HandleRoutedRPC", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeFromHandle, "RouteRPC", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(ContainerOpenSignal), "OnRoutedRpc", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { AcsLog.Warning("sorter: the open signal snoop is off - ZRoutedRpc." + ((methodInfo == null) ? "HandleRoutedRPC" : "RouteRPC") + " was not found; InUse, the owner revision jump and the revision stability window still protect every move"); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(methodInfo3), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _installed = true; AcsLog.Debug("sorter: open signal snoop installed on ZRoutedRpc.HandleRoutedRPC and ZRoutedRpc.RouteRPC (decision X2)"); } catch (Exception error) { AcsLog.Error("sorter: installing the open signal snoop failed, falling back to InUse plus the owner revision jump", error); } } private static void OnRoutedRpc(RoutedRPCData __0) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) try { if (__0 != null && (__0.m_methodHash == RequestOpenHash || __0.m_methodHash == OpenResponseHash) && !((ZDOID)(ref __0.m_targetZDO)).IsNone()) { Sorter.NoteChestOpened(__0.m_targetZDO, __0.m_senderPeerID); Seen++; } } catch (Exception error) { AcsLog.Error("sorter: reading an open signal failed", error); } } } internal static class ItemProgressionRuntimeCheck { private static bool _loggedThisSession; internal static bool IsObjectDbReady { get { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance != (Object)null && instance.m_items != null) { return instance.m_items.Count > 0; } return false; } } internal static void Reset() { _loggedThisSession = false; } internal static bool LogOnce() { if (_loggedThisSession || !IsObjectDbReady) { return false; } _loggedThisSession = true; foreach (string item in Report()) { AcsLog.Debug(item); } return true; } internal static IReadOnlyList<string> Report() { try { if (!IsObjectDbReady) { return new List<string> { "sorter progression: the item database is not up yet, ask again in a moment" }; } List<string> list = new List<string>(); List<string> list2 = new List<string>(); Collect(list, list2); return ItemProgressionAudit.Check((IEnumerable<string>)list, (IEnumerable<string>)list2).Describe(12); } catch (Exception error) { AcsLog.Error("sorter progression: checking the progression table failed", error); return new List<string> { "sorter progression: the check failed, see the server log" }; } } private static void Collect(List<string> names, List<string> stackables) { List<GameObject> items = ObjectDB.instance.m_items; for (int i = 0; i < items.Count; i++) { GameObject val = items[i]; if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(((Object)val).name)) { names.Add(((Object)val).name); ItemDrop component = val.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null && component.m_itemData != null && component.m_itemData.m_shared != null && component.m_itemData.m_shared.m_maxStackSize > 1) { stackables.Add(((Object)val).name); } } } } } internal enum RelocationKind { UnassignedOut = 1, ForeignGroupOut, MiscIn } internal static class RelocationKindText { internal static string RoleOf(RelocationKind kind) { if (kind != RelocationKind.MiscIn) { return "group chest"; } return "misc chest"; } internal static string StageOf(RelocationKind kind) { return kind switch { RelocationKind.UnassignedOut => "unassigned", RelocationKind.ForeignGroupOut => "foreign group", _ => "misc", }; } internal static bool FromGroupChest(RelocationKind kind) { return kind != RelocationKind.MiscIn; } internal static MisplacedKind WantedOf(RelocationKind kind) { return (MisplacedKind)(kind switch { RelocationKind.UnassignedOut => 2, RelocationKind.ForeignGroupOut => 1, _ => 0, }); } } internal sealed class Sorter : MonoBehaviour { private struct CandidateMeter { internal static readonly CandidateMeter Empty; private long _scans; private long _total; private int _max; private int _last; internal void Note(int candidates) { _scans++; _total += candidates; _last = candidates; if (candidates > _max) { _max = candidates; } } internal string Describe() { if (_scans == 0L) { return "no scan yet"; } double num = (double)_total / (double)_scans; return "last " + _last + ", avg " + num.ToString("F1", CultureInfo.InvariantCulture) + ", max " + _max + " over " + _scans + " scan(s)"; } } private readonly struct WaitKey : IEquatable<WaitKey> { internal ChestId Chest { get; } internal int PrefabHash { get; } internal WaitKey(ChestId chest, int prefabHash) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) Chest = chest; PrefabHash = prefabHash; } public bool Equals(WaitKey other) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) if (Chest == other.Chest) { return PrefabHash == other.PrefabHash; } return false; } public override bool Equals(object obj) { if (obj is WaitKey other) { return Equals(other); } return false; } public override int GetHashCode() { //IL_0001: 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) return (((object)Chest/*cast due to .constrained prefix*/).GetHashCode() * 397) ^ PrefabHash; } } private struct PendingVerification { internal ChestId SourceId; internal ChestId TargetId; internal int PrefabHash; internal int Amount; internal int ExpectedSourceAfter; internal int ExpectedTargetAfter; internal bool InPlaceMerge; internal bool Beautify; internal int ExpectedRecordsAfter; internal int ExpectedFingerprint; internal IReadOnlyList<ItemRecord> ExpectedLayout; internal byte[] BytesBefore; internal byte[] BytesAfter; internal uint TargetRevision; internal uint SourceRevision; internal string Item; internal int DelaySeconds; internal double DueAt; } private readonly struct ItemInfo { internal string PrefabName { get; } internal int MaxStack { get; } internal ItemInfo(string prefabName, int maxStack) { PrefabName = prefabName ?? string.Empty; MaxStack = maxStack; } } private const double SummarySeconds = 60.0; private const double RelocationQuietFactor = 2.0; private const double BeautifyQuietFactor = 60.0; private const double BeautifyBackoffSeconds = 600.0; private const int BeautifyLossEvidencePerSession = 3; private static GameObject _host; private readonly SortRotation _rotation = new SortRotation(); private readonly SortRotation _distributionSources = new SortRotation(); private readonly SortRotation _unassignedSources = new SortRotation(); private readonly SortRotation _foreignSources = new SortRotation(); private readonly SortRotation _miscSources = new SortRotation(); private readonly SortRotation _beautifySources = new SortRotation(); private readonly ChestTidyMemo _tidyAt = new ChestTidyMemo(); private readonly BeautifyBackoff _beautifyBackoff = new BeautifyBackoff(600.0); private readonly SortCursorTable _cursors = new SortCursorTable(); private readonly SortCursorTable _unassignedCursors = new SortCursorTable(); private readonly SortCursorTable _foreignCursors = new SortCursorTable(); private readonly SortCursorTable _miscCursors = new SortCursorTable(); private readonly UnsortableTracker _unsortable = new UnsortableTracker(); private readonly ChestStabilityTracker _stability = new ChestStabilityTracker(); private readonly ConflictCounters _counters = new ConflictCounters(); private readonly BlockedPauseTracker _pause = new BlockedPauseTracker(); private readonly MovedLedger _ledger = new MovedLedger(); private readonly InventoryCache _inventories = new InventoryCache(); private readonly SortBudget _budget = new SortBudget(); private readonly TickStatistics _tickStats = new TickStatistics(); private readonly SorterSummaryWindow _summary = new SorterSummaryWindow(); private readonly RelocationRateMeter _relocations = new RelocationRateMeter(); private readonly LogChangeGate _gridGuard = new LogChangeGate(); private readonly Dictionary<int, ItemInfo> _itemCache = new Dictionary<int, ItemInfo>(); private readonly HashSet<long> _reportedUnknownItems = new HashSet<long>(); private readonly List<ChestId> _activeInputs = new List<ChestId>(); private readonly List<ChestId> _activeRelocationSources = new List<ChestId>(); private readonly List<ChestId> _activeDistributionSources = new List<ChestId>(); private readonly List<ChestId> _activeBeautifySources = new List<ChestId>(); private readonly List<ChestSnapshot> _candidates = new List<ChestSnapshot>(); private readonly HashSet<ChestId> _activeClusters = new HashSet<ChestId>(); private double _activeClustersAt = -1.0; private CandidateMeter _inputCandidates = CandidateMeter.Empty; private CandidateMeter _relocationCandidates = CandidateMeter.Empty; private CandidateMeter _distributionCandidates = CandidateMeter.Empty; private readonly List<ChestId> _blockedThisRound = new List<ChestId>(); private readonly List<string> _recentMoves = new List<string>(); private readonly List<PendingVerification> _pendingVerifications = new List<PendingVerification>(); private readonly PendingWriteSet _pendingWrites = new PendingWriteSet(); private readonly Dictionary<WaitKey, string> _lastWaitReason = new Dictionary<WaitKey, string>(); private const string StageBeautify = "beautify"; private const string StageInput = "input"; private readonly StageBlockLog _stageBlocks = new StageBlockLog(); private bool _isServer; private bool _checkedRole; private double _nextTickAt; private double _nextSummaryAt; private bool _pausedByOperator; private int _miscCursor; private int _parsesThisTick; private int _cacheHitsThisTick; private SortOutcome _lastOutcome = (SortOutcome)1; private string _lastLine = "nothing yet"; private bool _beautifyWasEnabled; private bool _distributionWasEnabled; private int _beautifyLossEvidenceLeft = 3; private ChestId _lastBeautified = ChestId.None; private long _beautifyNotReady; private long _beautifyUnsafe; private long _beautifyRewrites; private const string StageDistribution = "distribution"; private readonly DistributionPlanCache _plans = new DistributionPlanCache(); private readonly DistributionCycleGuard _cycles = new DistributionCycleGuard(4, 300.0); private readonly DistributionNoticeLog _distributionNotices = new DistributionNoticeLog(); private readonly RelocationRateMeter _planRebuilds = new RelocationRateMeter(); private long _distributionDeferrals; private long _distributionStuck; private long _distributionCycleStops; private string _lastDistributionUnit = string.Empty; internal static Sorter Instance { get; private set; } private static double Now => Time.realtimeSinceStartup; internal bool IsPausedByOperator => _pausedByOperator; private static int VerifyDelayOf(AcsConfig cfg) { int num = ((cfg == null) ? AcsConfigKeys.SortIntervalSeconds.FallbackInt : cfg.SortIntervalSeconds); if (num < AcsConfigKeys.SortIntervalSeconds.MinimumInt) { num = AcsConfigKeys.SortIntervalSeconds.MinimumInt; } else if (num > AcsConfigKeys.SortIntervalSeconds.MaximumInt) { num = AcsConfigKeys.SortIntervalSeconds.MaximumInt; } return ReadBackDelay.Seconds(num); } internal static void Install() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if ((Object)(object)_host == (Object)null) { _host = new GameObject("ACS_Sorter"); Object.DontDestroyOnLoad((Object)(object)_host); Instance = _host.AddComponent<Sorter>(); } Instance.BeginSession(); } private void BeginSession() { if (_checkedRole && _isServer) { ClearWorldState("a new session started before the last one was seen to end"); } _checkedRole = false; _isServer = false; ((Behaviour)this).enabled = true; } private void ClearWorldState(string reason) { //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) _rotation.Clear(); _unassignedSources.Clear(); _foreignSources.Clear(); _miscSources.Clear(); _beautifySources.Clear(); _tidyAt.Clear(); _beautifyBackoff.Clear(); _cursors.Clear(); _unassignedCursors.Clear(); _foreignCursors.Clear(); _miscCursors.Clear(); _unsortable.Clear(); ClearDistributionState(); _stability.Clear(); _counters.Clear(); _pause.Reset(); _ledger.Clear(); _inventories.Clear(); _budget.Reset(); _tickStats.Clear(); _summary.Clear(); _relocations.Clear(); _gridGuard.Clear(); _itemCache.Clear(); _reportedUnknownItems.Clear(); _activeInputs.Clear(); _activeRelocationSources.Clear(); _activeBeautifySources.Clear(); _candidates.Clear(); _blockedThisRound.Clear(); _recentMoves.Clear(); _pendingVerifications.Clear(); _pendingWrites.Clear(); _lastWaitReason.Clear(); _stageBlocks.Clear(); _nextTickAt = 0.0; _nextSummaryAt = 0.0; _miscCursor = 0; _parsesThisTick = 0; _cacheHitsThisTick = 0; _lastOutcome = (SortOutcome)1; _lastLine = "nothing yet"; _beautifyWasEnabled = false; _beautifyLossEvidenceLeft = 3; _lastBeautified = ChestId.None; _beautifyNotReady = 0L; _beautifyUnsafe = 0L; _beautifyRewrites = 0L; ItemProgressionRuntimeCheck.Reset(); AcsLog.Info("sorter: session ended, state cleared (" + reason + ")"); } internal static void NoteChestOpened(ZDOID target, long sender) { //IL_002d: 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) Sorter instance = Instance; if (!((Object)(object)instance == (Object)null) && instance._isServer && !((ZDOID)(ref target)).IsNone()) { ZDO val = ((ZDOMan.instance == null) ? null : ZDOMan.instance.GetZDO(target)); if (val != null && AcsChestCatalog.Current.IsSupported(val.GetPrefab())) { ChestId val2 = default(ChestId); ((ChestId)(ref val2))..ctor(((ZDOID)(ref target)).UserID, ((ZDOID)(ref target)).ID); double num = AcsConfigService.Current.CloseCooldownSeconds; instance._stability.MarkOpened(val2, Now, num); AcsLog.Debug("sorter: open signal for " + ChestLookup.Describe(val) + " from peer " + sender + ", locked for " + num.ToString("F1", CultureInfo.InvariantCulture) + " s"); } } } internal void ForgetChestsNotIn(ChestCatalog catalog, string change) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (!_isServer) { AcsLog.Debug("sorter: the chest prefab list changed (" + change + "), nothing to forget in a client session"); return; } ChestCatalog val = catalog ?? ChestCatalog.Empty; HashSet<ChestId> hashSet = new HashSet<ChestId>(); IReadOnlyList<ChestEntry> readOnlyList; if (!((Object)(object)ChestIndex.Instance == (Object)null)) { readOnlyList = ChestIndex.Instance.Model.Entries(); } else { IReadOnlyList<ChestEntry> readOnlyList2 = (IReadOnlyList<ChestEntry>)(object)new ChestEntry[0]; readOnlyList = readOnlyList2; } IReadOnlyList<ChestEntry> readOnlyList3 = readOnlyList; for (int i = 0; i < readOnlyList3.Count; i++) { if (val.IsSupported(readOnlyList3[i].PrefabHash)) { hashSet.Add(readOnlyList3[i].Id); } } int num = _stability.PruneExcept((ICollection<ChestId>)hashSet); int num2 = _inventories.PruneExcept((ICollection<ChestId>)hashSet); int num3 = _unsortable.PruneExcept((ICollection<ChestId>)hashSet); int num4 = _cursors.PruneExcept((ICollection<ChestId>)hashSet) + _unassignedCursors.PruneExcept((ICollection<ChestId>)hashSet) + _foreignCursors.PruneExcept((ICollection<ChestId>)hashSet) + _miscCursors.PruneExcept((ICollection<ChestId>)hashSet); int num5 = PruneDistributionState(hashSet); int num6 = PruneWaitReasonsExcept(hashSet) + _stageBlocks.PruneExcept((ICollection<ChestId>)hashSet); int num7 = DropVerificationsExcept(hashSet); int num8 = _pendingWrites.PruneExcept((ICollection<ChestId>)hashSet); if (num8 > 0) { AcsLog.Warning("sorter: " + num8 + " read back lock(s) outlived their queue entry and were dropped with the catalog change. This is an ACS bug, please report it."); } int num9 = _tidyAt.PruneExcept((ICollection<ChestId>)hashSet) + _beautifyBackoff.PruneExcept((ICollection<ChestId>)hashSet); _rotation.Reset(); _distributionSources.Reset(); _unassignedSources.Reset(); _foreignSources.Reset(); _miscSources.Reset(); _beautifySources.Reset(); _blockedThisRound.Clear(); _gridGuard.Clear(); AcsLog.Info("sorter: the chest prefab list changed (" + change + "), forgot " + num + " stability, " + num2 + " inventory, " + num3 + " suppression, " + num4 + " cursor, " + num6 + " wait reason, " + num9 + " beautify, " + num5 + " distribution and " + num7 + " pending read back entry(ies); " + hashSet.Count + " indexed chest(s) are still supported"); } internal void Pause() { _pausedByOperator = true; AcsLog.Info("sorter: paused by operator"); } internal void Resume() { _pausedByOperator = false; _pause.Reset(); _blockedThisRound.Clear(); _rotation.Reset(); _distributionSources.Reset(); _unassignedSources.Reset(); _foreignSources.Reset(); _miscSources.Reset(); AcsLog.Info("sorter: resumed by operator"); } internal unsafe List<string> Status() { //IL_081a: Unknown result type (might be due to invalid IL or missing references) //IL_081f: Unknown result type (might be due to invalid IL or missing references) //IL_0830: Unknown result type (might be due to invalid IL or missing references) //IL_0835: Unknown result type (might be due to invalid IL or missing references) //IL_08fc: Unknown result type (might be due to invalid IL or missing references) //IL_0901: Unknown result type (might be due to invalid IL or missing references) //IL_0912: Unknown result type (might be due to invalid IL or missing references) //IL_0917: Unknown result type (might be due to invalid IL or missing references) //IL_097f: Unknown result type (might be due to invalid IL or missing references) //IL_0984: Unknown result type (might be due to invalid IL or missing references) //IL_0995: Unknown result type (might be due to invalid IL or missing references) //IL_099a: Unknown result type (might be due to invalid IL or missing references) //IL_0a05: Unknown result type (might be due to invalid IL or missing references) //IL_0a0a: Unknown result type (might be due to invalid IL or missing references) //IL_0a1b: Unknown result type (might be due to invalid IL or missing references) //IL_0a20: Unknown result type (might be due to invalid IL or missing references) //IL_0cbf: Unknown result type (might be due to invalid IL or missing references) //IL_0cc4: Unknown result type (might be due to invalid IL or missing references) //IL_0cc6: Unknown result type (might be due to invalid IL or missing references) //IL_0d44: Unknown result type (might be due to invalid IL or missing references) //IL_0d71: Unknown result type (might be due to invalid IL or missing references) //IL_0db1: Unknown result type (might be due to invalid IL or missing references) //IL_0db9: Unknown result type (might be due to invalid IL or missing references) double now = Now; List<string> list = new List<string>(); AcsConfig current = AcsConfigService.Current; list.Add("sorter: " + (_isServer ? "server session" : "not a server, idle") + ", " + (_pausedByOperator ? "PAUSED by operator" : "running") + (_pause.IsPaused ? (", blocked pause for another " + _pause.SecondsLeft(now).ToString("F1", CultureInfo.InvariantCulture) + " s") : string.Empty) + ", last outcome " + ((object)Unsafe.As<SortOutcome, SortOutcome>(ref _lastOutcome)/*cast due to .constrained prefix*/).ToString()); list.Add("sorter config: interval " + AcsConfigKeys.SortIntervalSeconds.Format((double)current.SortIntervalSeconds) + " s, graphEdgeRadius " + AcsConfigKeys.GraphEdgeRadius.Format(current.GraphEdgeRadius) + " m, activationRadius " + AcsConfigKeys.ActivationRadius.Format(current.ActivationRadius) + " m, closeCooldown " + current.CloseCooldownSeconds + " s, blockedPause " + current.BlockedPauseSeconds + " s, ignoreInUseWhenOwnerDisconnected " + current.IgnoreInUseWhenOwnerDisconnected + ", conflictWarnThreshold " + current.ConflictWarnThreshold + ", relocateMisplacedItems " + current.RelocateMisplacedItems + ", relocateUnassignedToMisc " + current.RelocateUnassignedToMisc + ", relocateItemStacks " + current.RelocateItemStacks + ", beautifyChestItemOrder " + current.BeautifyChestItemOrder + ", sortBudgetMsPerSecond " + AcsConfigKeys.SortBudgetMsPerSecond.Format(current.SortBudgetMsPerSecond)); ChestCatalog current2 = AcsChestCatalog.Current; list.Add("sorter catalog: " + current2.Count + " chest prefab(s) (" + current2.CountOf((ChestPrefabOrigin)0) + " vanilla, " + current2.CountOf((ChestPrefabOrigin)1) + " from config)" + (current2.IsEmpty ? " - EMPTY, nothing is sorted" : string.Empty) + ", " + AcsChestCatalog.Rejections.Count + " prefab(s) rejected, builds " + AcsChestCatalog.BuildCount + ", last trigger " + AcsChestCatalog.LastReason); list.Add("sorter counters: " + ((_counters.Describe().Length == 0) ? "nothing yet" : _counters.Describe())); ChestClusters val = (((Object)(object)ChestIndex.Instance == (Object)null) ? ChestClusters.Empty : ChestIndex.Instance.Clusters); ChestCluster largest = val.Largest; list.Add("sorter clusters: " + val.Describe() + ", " + _activeClusters.Count + " with a player inside activationRadius" + ((largest != null && (largest.Count > 40 || largest.BoundingBoxDiameter > 80f)) ? (" - the largest one is above the diagnostics threshold (" + 40 + " chests / " + 80 + " m), acs_dump lists its members") : string.Empty)); list.Add("sorter cluster sizes: " + DescribeClusterSizes(val)); list.Add("sorter candidates per attempt: input " + _inputCandidates.Describe() + " | relocation " + _relocationCandidates.Describe() + " | distribution unit " + _distributionCandidates.Describe()); list.Add("sorter suppression: input/relocation " + _unsortable.DescribeHitRate()); list.Add("sorter budget: " + ((object)_budget)?.ToString() + (_budget.IsEnabled ? (", " + _budget.SecondsUntilAffordable(now).ToString("F1", CultureInfo.InvariantCulture) + " s until the next attempt") : " (no time limit)") + ", budgetSkips " + _counters.BudgetSkips); list.Add("sorter cost: " + _tickStats.Describe(now) + ", " + _summary.Summaries + " release summary(ies) written, next in " + _summary.SecondsUntilDue(now).ToString("F0", CultureInfo.InvariantCulture) + " s - sortIntervalSeconds (" + AcsConfigKeys.SortIntervalSeconds.Format((double)current.SortIntervalSeconds) + " s) is only the upper bound of the pace; the time quota sets the real one"); list.Add("sorter relocation: " + (current.RelocateMisplacedItems ? "on" : "OFF") + ", tidy-up rate " + _relocations.Describe(now) + " (warns above " + 30 + "/min), relocated " + _counters.Relocated + " of " + _counters.Moves + " move(s)"); DescribeDistribution(list, current, now); list.Add("sorter beautify: " + ((current.RelocateMisplacedItems && current.BeautifyChestItemOrder) ? "on" : (current.BeautifyChestItemOrder ? "OFF (relocateMisplacedItems is off, which switches every tidy-up stage off)" : "OFF (beautifyChestItemOrder)")) + ", beautified " + _counters.Beautified + " of " + _counters.Moves + " move(s), quiet window " + ((double)current.CloseCooldownSeconds * 60.0).ToString("F0", CultureInfo.InvariantCulture) + " s, " + _tidyAt.Count + " chest(s) remembered tidy, " + _beautifyBackoff.BackedOff(now) + " chest(s) braked after " + 3 + " undone rewrite(s) (" + _beautifyBackoff.Armed + " brake(s) armed this session), skipped " + _beautifyNotReady + " x no item database, " + _beautifyUnsafe + " x unsafe chest"); string[] obj = new string[11] { "sorter beautify queue: ", _beautifySources.Count.ToString(), " group chest(s), round ", _beautifySources.Rounds.ToString(), ", last ", null, null, null, null, null, null }; ChestId last = _beautifySources.Last; object obj2; if (!((ChestId)(ref last)).IsNone) { last = _beautifySources.Last; obj2 = ((object)(*(ChestId*)(&last))/*cast due to .constrained prefix*/).ToString(); } else { obj2 = "none"; } obj[5] = (string)obj2; obj[6] = ", last rewrite "; obj[7] = (((ChestId)(ref _lastBeautified)).IsNone ? "none" : ((object)Unsafe.As<ChestId, ChestId>(ref _lastBeautified)/*cast due to .constrained prefix*/).ToString()); obj[8] = " ("; obj[9] = _beautifyRewrites.ToString(); obj[10] = " this session)"; list.Add(string.Concat(obj)); string[] obj3 = new string[21] { "sorter relocation queue: unassigned ", _unassignedSources.Count.ToString(), " group chest(s), round ", _unassignedSources.Rounds.ToString(), ", last ", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null }; last = _unassignedSources.Last; object obj4; if (!((ChestId)(ref last)).IsNone) { last = _unassignedSources.Last; obj4 = ((object)(*(ChestId*)(&last))/*cast due to .constrained prefix*/).ToString(); } else { obj4 = "none"; } obj3[5] = (string)obj4; obj3[6] = " | foreign group "; obj3[7] = _foreignSources.Count.ToString(); obj3[8] = " group chest(s), round "; obj3[9] = _foreignSources.Rounds.ToString(); obj3[10] = ", last "; last = _foreignSources.Last; object obj5; if (!((ChestId)(ref last)).IsNone) { last = _foreignSources.Last; obj5 = ((object)(*(ChestId*)(&last))/*cast due to .constrained prefix*/).ToString(); } else { obj5 = "none"; } obj3[11] = (string)obj5; obj3[12] = " | misc "; obj3[13] = _miscSources.Count.ToString(); obj3[14] = " misc chest(s), round "; obj3[15] = _miscSources.Rounds.ToString(); obj3[16] = ", last "; last = _miscSources.Last; object obj6; if (!((ChestId)(ref last)).IsNone) { last = _miscSources.Last; obj6 = ((object)(*(ChestId*)(&last))/*cast due to .constrained prefix*/).ToString(); } else { obj6 = "none"; } obj3[17] = (string)obj6; obj3[18] = ", "; obj3[19] = _inventories.Count.ToString(); obj3[20] = " cached inventory(ies)"; list.Add(string.Concat(obj3)); string text = _ledger.Describe((Func<int, string>)NameOf, 12); list.Add("sorter moved: " + ((text.Length == 0) ? "nothing yet" : text) + " (total " + _ledger.Total + " unit(s) in " + _ledger.TypeCount + " type(s), " + _pendingVerifications.Count + " move(s) waiting for their read back, " + _pendingWrites.Describe() + ")"); list.Add("sorter queue: " + _rotation.Count + " active input chest(s), round " + _rotation.Rounds + ", misc cursor " + _miscCursor + ", blocked rounds " + _pause.BlockedRounds + "/" + 3 + ", " + _unsortable.Count + " suppressed stack(s) in " + _unsortable.ChestCount + " chest(s), " + _stability.Count + " chest(s) tracked, open signals seen " + ContainerOpenSignal.Seen + " (snoop " + (ContainerOpenSignal.IsInstalled ? "on" : "off") + ")"); list.Add("acs client gate: " + VanillaClientGate.Describe()); if (_rotation.Count == 0) { list.Add("sorter queue: no input chest is in a cluster wi
AutoChestSort.Core.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using AutoChestSort.Core.Groups; using AutoChestSort.Core.Inventory; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("Nexxy")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright (c) 2026 Nexxy")] [assembly: AssemblyDescription("Pure logic of the AutoChestSort mod, free of Unity and BepInEx references.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+1f8132cb0513483b73b1f973e223d866478e81b9")] [assembly: AssemblyProduct("AutoChestSort")] [assembly: AssemblyTitle("AutoChestSort.Core")] [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] internal sealed class IsReadOnlyAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AutoChestSort.Core { public readonly struct AcsVector3 : IEquatable<AcsVector3> { public static readonly AcsVector3 Zero; public float X { get; } public float Y { get; } public float Z { get; } public AcsVector3(float x, float y, float z) { X = x; Y = y; Z = z; } public float SqrDistanceTo(AcsVector3 other) { float num = X - other.X; float num2 = Y - other.Y; float num3 = Z - other.Z; return num * num + num2 * num2 + num3 * num3; } public float DistanceTo(AcsVector3 other) { return (float)Math.Sqrt(SqrDistanceTo(other)); } public bool Equals(AcsVector3 other) { if (X.Equals(other.X) && Y.Equals(other.Y)) { return Z.Equals(other.Z); } return false; } public override bool Equals(object? obj) { if (obj is AcsVector3 other) { return Equals(other); } return false; } public override int GetHashCode() { return (((X.GetHashCode() * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode(); } public override string ToString() { return "(" + X.ToString("F1", CultureInfo.InvariantCulture) + ", " + Y.ToString("F1", CultureInfo.InvariantCulture) + ", " + Z.ToString("F1", CultureInfo.InvariantCulture) + ")"; } } public sealed class CatalogDiff { private readonly string[] _added; private readonly string[] _removed; private readonly string[] _regridded; private readonly int[] _removedHashes; private readonly HashSet<int> _removedHashSet; public static CatalogDiff None { get; } = new CatalogDiff(Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<int>()); public IReadOnlyList<string> Added => _added; public IReadOnlyList<string> Removed => _removed; public IReadOnlyList<string> Regridded => _regridded; public IReadOnlyList<int> RemovedHashes => _removedHashes; public bool HasChanges { get { if (_added.Length == 0 && _removed.Length == 0) { return _regridded.Length != 0; } return true; } } private CatalogDiff(string[] added, string[] removed, string[] regridded, int[] removedHashes) { _added = added; _removed = removed; _regridded = regridded; _removedHashes = removedHashes; _removedHashSet = new HashSet<int>(removedHashes); } public static CatalogDiff Between(ChestCatalog? previous, ChestCatalog? next) { ChestCatalog chestCatalog = previous ?? ChestCatalog.Empty; ChestCatalog chestCatalog2 = next ?? ChestCatalog.Empty; List<string> list = new List<string>(); List<string> list2 = new List<string>(); List<string> list3 = new List<string>(); List<int> list4 = new List<int>(); for (int i = 0; i < chestCatalog2.Entries.Count; i++) { ChestPrefabEntry chestPrefabEntry = chestCatalog2.Entries[i]; ChestPrefabEntry chestPrefabEntry2 = chestCatalog.Find(chestPrefabEntry.PrefabHash); if (chestPrefabEntry2 == null) { list.Add(chestPrefabEntry.Name); } else if (chestPrefabEntry2.Width != chestPrefabEntry.Width || chestPrefabEntry2.Height != chestPrefabEntry.Height) { list3.Add(chestPrefabEntry.Name + " " + chestPrefabEntry2.Width + "x" + chestPrefabEntry2.Height + " -> " + chestPrefabEntry.Width + "x" + chestPrefabEntry.Height); } } for (int j = 0; j < chestCatalog.Entries.Count; j++) { ChestPrefabEntry chestPrefabEntry3 = chestCatalog.Entries[j]; if (chestCatalog2.Find(chestPrefabEntry3.PrefabHash) == null) { list2.Add(chestPrefabEntry3.Name); list4.Add(chestPrefabEntry3.PrefabHash); } } if (list.Count == 0 && list2.Count == 0 && list3.Count == 0) { return None; } return new CatalogDiff(list.ToArray(), list2.ToArray(), list3.ToArray(), list4.ToArray()); } public bool IsRemoved(int prefabHash) { return _removedHashSet.Contains(prefabHash); } public override string ToString() { return Describe(); } public string Describe() { if (!HasChanges) { return "no change"; } StringBuilder stringBuilder = new StringBuilder(); Append(stringBuilder, "added", _added); Append(stringBuilder, "removed", _removed); Append(stringBuilder, "regridded", _regridded); return stringBuilder.ToString(); } private static void Append(StringBuilder text, string label, string[] values) { if (values.Length != 0) { if (text.Length > 0) { text.Append(", "); } text.Append(values.Length).Append(' ').Append(label) .Append(" (") .Append(string.Join(", ", values)) .Append(')'); } } } public sealed class ChestCatalog { private readonly Dictionary<int, ChestPrefabEntry> _byHash; private readonly ChestPrefabEntry[] _entries; private readonly string[] _names; private readonly int[] _hashes; public static ChestCatalog Empty { get; } = new ChestCatalog(Array.Empty<ChestPrefabEntry>()); public int Count => _entries.Length; public bool IsEmpty => _entries.Length == 0; public IReadOnlyList<ChestPrefabEntry> Entries => _entries; public IReadOnlyList<string> Names => _names; public IReadOnlyList<int> Hashes => _hashes; private ChestCatalog(ChestPrefabEntry[] entries) { _entries = entries; _byHash = new Dictionary<int, ChestPrefabEntry>(entries.Length); _names = new string[entries.Length]; _hashes = new int[entries.Length]; for (int i = 0; i < entries.Length; i++) { ChestPrefabEntry chestPrefabEntry = entries[i]; _byHash[chestPrefabEntry.PrefabHash] = chestPrefabEntry; _names[i] = chestPrefabEntry.Name; _hashes[i] = chestPrefabEntry.PrefabHash; } } public static ChestCatalog Build(IEnumerable<ChestPrefabEntry?>? entries) { if (entries == null) { return Empty; } List<ChestPrefabEntry> list = new List<ChestPrefabEntry>(); HashSet<int> hashSet = new HashSet<int>(); foreach (ChestPrefabEntry entry in entries) { if (entry != null && hashSet.Add(entry.PrefabHash)) { list.Add(entry); } } if (list.Count != 0) { return new ChestCatalog(list.ToArray()); } return Empty; } public bool IsSupported(int prefabHash) { return _byHash.ContainsKey(prefabHash); } public ChestPrefabEntry? Find(int prefabHash) { if (!_byHash.TryGetValue(prefabHash, out ChestPrefabEntry value)) { return null; } return value; } public string? NameOf(int prefabHash) { if (!_byHash.TryGetValue(prefabHash, out ChestPrefabEntry value)) { return null; } return value.Name; } public string Describe(int prefabHash) { return NameOf(prefabHash) ?? prefabHash.ToString(); } public bool TryGetGrid(int prefabHash, out int width, out int height) { if (_byHash.TryGetValue(prefabHash, out ChestPrefabEntry value)) { width = value.Width; height = value.Height; return true; } width = 0; height = 0; return false; } public int SlotCountOf(int prefabHash) { if (!_byHash.TryGetValue(prefabHash, out ChestPrefabEntry value)) { return 0; } return value.Slots; } public int CountOf(ChestPrefabOrigin origin) { int num = 0; for (int i = 0; i < _entries.Length; i++) { if (_entries[i].Origin == origin) { num++; } } return num; } public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(Count).Append(" chest prefab(s): ").Append(CountOf(ChestPrefabOrigin.Vanilla)) .Append(" vanilla, ") .Append(CountOf(ChestPrefabOrigin.Config)) .Append(" from config"); for (int i = 0; i < _entries.Length; i++) { stringBuilder.Append((i == 0) ? " - " : ", ").Append(_entries[i].Name); } return stringBuilder.ToString(); } } public sealed class ChestCluster { public ChestId Id { get; } public IReadOnlyList<ChestId> Members { get; } public int Count => Members.Count; public AcsVector3 Min { get; } public AcsVector3 Max { get; } public float BoundingBoxDiameter { get { float num = Max.X - Min.X; float num2 = Max.Y - Min.Y; float num3 = Max.Z - Min.Z; return (float)Math.Sqrt((double)num * (double)num + (double)num2 * (double)num2 + (double)num3 * (double)num3); } } internal ChestCluster(ChestId id, IReadOnlyList<ChestId> members, AcsVector3 min, AcsVector3 max) { Id = id; Members = members; Min = min; Max = max; } public bool Contains(ChestId id) { for (int i = 0; i < Members.Count; i++) { if (Members[i] == id) { return true; } } return false; } public override string ToString() { return "cluster " + Id.ToString() + ": " + Count + " chest(s), " + BoundingBoxDiameter.ToString("F1", CultureInfo.InvariantCulture) + " m across"; } } public sealed class ChestClusters { private readonly struct CellKey : IEquatable<CellKey> { private readonly int _x; private readonly int _y; private readonly int _z; private CellKey(int x, int y, int z) { _x = x; _y = y; _z = z; } internal static CellKey Of(AcsVector3 position, double edge) { return new CellKey(Floor((double)position.X / edge), Floor((double)position.Y / edge), Floor((double)position.Z / edge)); } internal CellKey Offset(int dx, int dy, int dz) { return new CellKey(_x + dx, _y + dy, _z + dz); } public bool Equals(CellKey other) { if (_x == other._x && _y == other._y) { 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) ^ _y) * 397) ^ _z; } private static int Floor(double value) { if (double.IsNaN(value)) { return 0; } double num = Math.Floor(value); if (num > 2147483647.0) { return int.MaxValue; } if (!(num < -2147483648.0)) { return (int)num; } return int.MinValue; } } private static readonly ChestId[] NoMembers = new ChestId[0]; private static readonly ChestCluster[] NoClusters = new ChestCluster[0]; public static readonly ChestClusters Empty = new ChestClusters(NoClusters, new Dictionary<ChestId, ChestCluster>(), 0, 0.0, 0, fromIncompleteScan: false, 0.0); private readonly Dictionary<ChestId, ChestCluster> _byChest; public IReadOnlyList<ChestCluster> All { get; } public int Count => All.Count; public int ChestCount { get; } public int Generation { get; } public double EdgeRadius { get; } public bool FromIncompleteScan { get; } public bool IsTrustworthy { get { if (Generation > 0) { return !FromIncompleteScan; } return false; } } public double BuildMilliseconds { get; } public ChestCluster? Largest { get { ChestCluster chestCluster = null; for (int i = 0; i < All.Count; i++) { if (chestCluster == null || All[i].Count > chestCluster.Count) { chestCluster = All[i]; } } return chestCluster; } } private ChestClusters(IReadOnlyList<ChestCluster> all, Dictionary<ChestId, ChestCluster> byChest, int generation, double edgeRadius, int chestCount, bool fromIncompleteScan, double buildMilliseconds) { All = all; _byChest = byChest; Generation = generation; EdgeRadius = edgeRadius; ChestCount = chestCount; FromIncompleteScan = fromIncompleteScan; BuildMilliseconds = buildMilliseconds; } public ChestCluster? Of(ChestId id) { if (!_byChest.TryGetValue(id, out ChestCluster value)) { return null; } return value; } public ChestId ClusterIdOf(ChestId id) { if (!_byChest.TryGetValue(id, out ChestCluster value)) { return ChestId.None; } return value.Id; } public IReadOnlyList<ChestId> MembersOf(ChestId id) { if (!_byChest.TryGetValue(id, out ChestCluster value)) { return NoMembers; } return value.Members; } public bool SameCluster(ChestId left, ChestId right) { if (!_byChest.TryGetValue(left, out ChestCluster value) || !_byChest.TryGetValue(right, out ChestCluster value2)) { return false; } return value == value2; } public int SizeOfClusterWith(ChestId id) { if (!_byChest.TryGetValue(id, out ChestCluster value)) { return 0; } return value.Count; } public static ChestClusters Build(IEnumerable<ChestEntry>? entries, double edgeRadius, int generation, bool fromIncompleteScan = false) { Stopwatch stopwatch = Stopwatch.StartNew(); List<ChestEntry> list = new List<ChestEntry>(); if (entries != null) { foreach (ChestEntry entry in entries) { if (entry != null && !entry.IsUnnamed) { list.Add(entry); } } } list.Sort((ChestEntry a, ChestEntry b) => ChestIdOrder.Compare(a.Id, b.Id)); int[] array = new int[list.Count]; int[] array2 = new int[list.Count]; for (int num = 0; num < list.Count; num++) { array[num] = num; array2[num] = 1; } double num2 = edgeRadius; if (double.IsNaN(num2) || double.IsInfinity(num2) || num2 <= 0.0) { num2 = 0.0; } if (num2 > 0.0 && list.Count > 1) { UnionNeighbours(list, array, array2, num2); } IReadOnlyList<ChestCluster> readOnlyList = Collect(list, array); Dictionary<ChestId, ChestCluster> dictionary = new Dictionary<ChestId, ChestCluster>(list.Count); for (int num3 = 0; num3 < readOnlyList.Count; num3++) { ChestCluster chestCluster = readOnlyList[num3]; for (int num4 = 0; num4 < chestCluster.Members.Count; num4++) { dictionary[chestCluster.Members[num4]] = chestCluster; } } stopwatch.Stop(); return new ChestClusters(readOnlyList, dictionary, generation, num2, list.Count, fromIncompleteScan, stopwatch.Elapsed.TotalMilliseconds); } public ChestClusters MarkedAsFromIncompleteScan() { if (FromIncompleteScan) { return this; } return new ChestClusters(All, _byChest, Generation, EdgeRadius, ChestCount, fromIncompleteScan: true, BuildMilliseconds); } public static IReadOnlyList<string> Changes(ChestClusters? previous, ChestClusters? current) { List<string> list = new List<string>(); if (previous == null || current == null || previous.Count == 0 || current.Count == 0) { return list; } for (int i = 0; i < current.All.Count; i++) { ChestCluster chestCluster = current.All[i]; List<ChestId> list2 = DistinctClusterIds(previous, chestCluster.Members); if (list2.Count > 1) { list.Add("merge: " + Describe(previous, list2) + " -> cluster " + chestCluster.Id.ToString() + " (" + chestCluster.Count + " chest(s))"); } } for (int j = 0; j < previous.All.Count; j++) { ChestCluster chestCluster2 = previous.All[j]; List<ChestId> list3 = DistinctClusterIds(current, chestCluster2.Members); if (list3.Count > 1) { list.Add("split: cluster " + chestCluster2.Id.ToString() + " (" + chestCluster2.Count + " chest(s)) -> " + Describe(current, list3)); } } return list; } public string Describe() { ChestCluster largest = Largest; return ChestCount + " chest(s) in " + Count + " cluster(s), largest " + ((largest == null) ? "none" : (largest.Id.ToString() + " with " + largest.Count + " chest(s), " + largest.BoundingBoxDiameter.ToString("F1", CultureInfo.InvariantCulture) + " m across")) + ", graphEdgeRadius " + EdgeRadius.ToString("F0", CultureInfo.InvariantCulture) + " m, generation " + Generation + ", build " + BuildMilliseconds.ToString("F2", CultureInfo.InvariantCulture) + " ms" + (FromIncompleteScan ? " (FROM AN INCOMPLETE SCAN)" : string.Empty); } private static List<ChestId> DistinctClusterIds(ChestClusters set, IReadOnlyList<ChestId> members) { List<ChestId> list = new List<ChestId>(); for (int i = 0; i < members.Count; i++) { ChestId item = set.ClusterIdOf(members[i]); if (!item.IsNone && !list.Contains(item)) { list.Add(item); } } list.Sort(ChestIdOrder.Comparer); return list; } private static string Describe(ChestClusters set, List<ChestId> ids) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < ids.Count; i++) { if (i > 0) { stringBuilder.Append(" + "); } ChestCluster chestCluster = set.Of(ids[i]); stringBuilder.Append("cluster ").Append(ids[i].ToString()).Append(" (") .Append(chestCluster?.Count ?? 0) .Append(" chest(s))"); } return stringBuilder.ToString(); } private static void UnionNeighbours(List<ChestEntry> nodes, int[] parent, int[] size, double radius) { Dictionary<CellKey, List<int>> dictionary = new Dictionary<CellKey, List<int>>(nodes.Count); CellKey[] array = new CellKey[nodes.Count]; for (int i = 0; i < nodes.Count; i++) { CellKey key = (array[i] = CellKey.Of(nodes[i].Position, radius)); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<int>(4)); } value.Add(i); } float num = (float)(radius * radius); for (int j = 0; j < nodes.Count; j++) { CellKey cellKey = array[j]; AcsVector3 position = nodes[j].Position; for (int k = -1; k <= 1; k++) { for (int l = -1; l <= 1; l++) { for (int m = -1; m <= 1; m++) { if (!dictionary.TryGetValue(cellKey.Offset(k, l, m), out var value2)) { continue; } for (int n = 0; n < value2.Count; n++) { int num2 = value2[n]; if (num2 > j && position.SqrDistanceTo(nodes[num2].Position) <= num) { Union(parent, size, j, num2); } } } } } } } private static IReadOnlyList<ChestCluster> Collect(List<ChestEntry> nodes, int[] parent) { if (nodes.Count == 0) { return NoClusters; } Dictionary<int, List<int>> dictionary = new Dictionary<int, List<int>>(); for (int i = 0; i < nodes.Count; i++) { int key = Find(parent, i); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<int>(4)); } value.Add(i); } List<ChestCluster> list2 = new List<ChestCluster>(dictionary.Count); foreach (List<int> value2 in dictionary.Values) { ChestId[] array = new ChestId[value2.Count]; AcsVector3 position = nodes[value2[0]].Position; float x = position.X; float y = position.Y; float z = position.Z; float x2 = position.X; float y2 = position.Y; float z2 = position.Z; for (int j = 0; j < value2.Count; j++) { ChestEntry chestEntry = nodes[value2[j]]; array[j] = chestEntry.Id; if (chestEntry.Position.X < x) { x = chestEntry.Position.X; } if (chestEntry.Position.Y < y) { y = chestEntry.Position.Y; } if (chestEntry.Position.Z < z) { z = chestEntry.Position.Z; } if (chestEntry.Position.X > x2) { x2 = chestEntry.Position.X; } if (chestEntry.Position.Y > y2) { y2 = chestEntry.Position.Y; } if (chestEntry.Position.Z > z2) { z2 = chestEntry.Position.Z; } } list2.Add(new ChestCluster(array[0], array, new AcsVector3(x, y, z), new AcsVector3(x2, y2, z2))); } list2.Sort((ChestCluster a, ChestCluster b) => ChestIdOrder.Compare(a.Id, b.Id)); return list2; } private static int Find(int[] parent, int index) { while (parent[index] != index) { parent[index] = parent[parent[index]]; index = parent[index]; } return index; } private static void Union(int[] parent, int[] size, int left, int right) { int num = Find(parent, left); int num2 = Find(parent, right); if (num != num2) { if (size[num] < size[num2]) { int num3 = num2; num2 = num; num = num3; } parent[num2] = num; size[num] += size[num2]; } } } public sealed class ChestEntry { public ChestId Id { get; } public int PrefabHash { get; } public AcsVector3 Position { get; } public string Name { get; } public bool IsUnnamed => Name.Length == 0; public bool IsInput => ChestNameNormalizer.IsInput(Name); public bool IsMisc => ChestNameNormalizer.IsMisc(Name); public bool IsGroupMember { get { if (!IsUnnamed) { return !ChestNameNormalizer.IsReserved(Name); } return false; } } public ChestEntry(ChestId id, int prefabHash, AcsVector3 position, string? name) { Id = id; PrefabHash = prefabHash; Position = position; Name = ChestNameNormalizer.Normalize(name); } public ChestEntry WithName(string? name) { return new ChestEntry(Id, PrefabHash, Position, name); } public override string ToString() { return PrefabHash + " " + Id.ToString() + " \"" + Name + "\" " + Position; } public string Describe(ChestCatalog? catalog) { string text = catalog?.NameOf(PrefabHash) ?? PrefabHash.ToString(); return text + " " + Id.ToString() + " \"" + Name + "\" " + Position; } } public readonly struct ChestId : IEquatable<ChestId> { public static readonly ChestId None; public long UserId { get; } public uint Id { get; } public bool IsNone { get { if (UserId == 0L) { return Id == 0; } return false; } } public ChestId(long userId, uint id) { UserId = userId; Id = id; } public static bool operator ==(ChestId left, ChestId right) { return left.Equals(right); } public static bool operator !=(ChestId left, ChestId right) { return !left.Equals(right); } public bool Equals(ChestId other) { if (UserId == other.UserId) { return Id == other.Id; } return false; } public override bool Equals(object? obj) { if (obj is ChestId other) { return Equals(other); } return false; } public override int GetHashCode() { return (UserId.GetHashCode() * 397) ^ (int)Id; } public override string ToString() { return UserId.ToString(CultureInfo.InvariantCulture) + ":" + Id.ToString(CultureInfo.InvariantCulture); } } public static class ChestIdOrder { private sealed class ChestIdComparer : IComparer<ChestId> { public int Compare(ChestId x, ChestId y) { return ChestIdOrder.Compare(x, y); } } public static readonly IComparer<ChestId> Comparer = new ChestIdComparer(); public static int Compare(ChestId left, ChestId right) { int num = left.UserId.CompareTo(right.UserId); if (num == 0) { return left.Id.CompareTo(right.Id); } return num; } public static ChestId Min(ChestId left, ChestId right) { if (Compare(left, right) > 0) { return right; } return left; } } public sealed class ChestIndexModel { private static readonly ChestEntry[] NoEntries = new ChestEntry[0]; private readonly Dictionary<ChestId, ChestEntry> _byId = new Dictionary<ChestId, ChestEntry>(); private readonly Dictionary<string, List<ChestEntry>> _byName = new Dictionary<string, List<ChestEntry>>(StringComparer.Ordinal); public int Count => _byId.Count; public IReadOnlyList<ChestEntry> Entries() { List<ChestEntry> list = new List<ChestEntry>(_byId.Count); foreach (ChestEntry value in _byId.Values) { list.Add(value); } return list; } public ChestEntry? ById(ChestId id) { if (!_byId.TryGetValue(id, out ChestEntry value)) { return null; } return value; } public IReadOnlyList<ChestEntry> ByName(string? name) { string text = ChestNameNormalizer.Normalize(name); if (text.Length == 0 || !_byName.TryGetValue(text, out List<ChestEntry> value)) { return NoEntries; } return value.ToArray(); } public int CountOf(string? name) { string text = ChestNameNormalizer.Normalize(name); if (text.Length == 0 || !_byName.TryGetValue(text, out List<ChestEntry> value)) { return 0; } return value.Count; } public IReadOnlyList<string> Names() { List<string> list = new List<string>(_byName.Count); foreach (string key in _byName.Keys) { list.Add(key); } list.Sort(StringComparer.Ordinal); return list; } public IReadOnlyList<string> GroupNames() { List<string> list = new List<string>(_byName.Count); foreach (string key in _byName.Keys) { if (!ChestNameNormalizer.IsReserved(key)) { list.Add(key); } } list.Sort(StringComparer.Ordinal); return list; } public bool IsLastChestOfGroup(ChestId id) { ChestEntry chestEntry = ById(id); if (chestEntry == null || !chestEntry.IsGroupMember) { return false; } return CountOf(chestEntry.Name) == 1; } public bool Upsert(ChestEntry entry) { if (entry == null) { throw new ArgumentNullException("entry"); } if (_byId.TryGetValue(entry.Id, out ChestEntry value)) { if (SameContent(value, entry)) { return false; } DetachName(value); } _byId[entry.Id] = entry; AttachName(entry); return true; } public bool Remove(ChestId id) { if (!_byId.TryGetValue(id, out ChestEntry value)) { return false; } DetachName(value); _byId.Remove(id); return true; } public int ReplaceAll(IEnumerable<ChestEntry> entries) { if (entries == null) { throw new ArgumentNullException("entries"); } _byId.Clear(); _byName.Clear(); int num = 0; foreach (ChestEntry entry in entries) { if (entry != null) { if (_byId.TryGetValue(entry.Id, out ChestEntry value)) { num++; DetachName(value); } _byId[entry.Id] = entry; AttachName(entry); } } return num; } public void Clear() { _byId.Clear(); _byName.Clear(); } public IReadOnlyList<ChestId> RenameGroup(string? oldName, string? newName) { string text = ChestNameNormalizer.Normalize(oldName); string text2 = ChestNameNormalizer.Normalize(newName); if (text.Length == 0 || string.Equals(text, text2, StringComparison.Ordinal)) { return Array.Empty<ChestId>(); } IReadOnlyList<ChestEntry> readOnlyList = ByName(text); List<ChestId> list = new List<ChestId>(readOnlyList.Count); foreach (ChestEntry item in readOnlyList) { Upsert(item.WithName(text2)); list.Add(item.Id); } return list; } private static bool SameContent(ChestEntry a, ChestEntry b) { if (a.PrefabHash == b.PrefabHash && a.Position.Equals(b.Position)) { return string.Equals(a.Name, b.Name, StringComparison.Ordinal); } return false; } private void AttachName(ChestEntry entry) { if (!entry.IsUnnamed) { if (!_byName.TryGetValue(entry.Name, out List<ChestEntry> value)) { value = new List<ChestEntry>(1); _byName[entry.Name] = value; } value.Add(entry); } } private void DetachName(ChestEntry entry) { if (entry.IsUnnamed || !_byName.TryGetValue(entry.Name, out List<ChestEntry> value)) { return; } for (int i = 0; i < value.Count; i++) { if (value[i].Id == entry.Id) { value.RemoveAt(i); break; } } if (value.Count == 0) { _byName.Remove(entry.Name); } } } public static class ChestNameNormalizer { public const int MaxLength = 32; public static string Normalize(string? raw) { if (string.IsNullOrEmpty(raw)) { return string.Empty; } string text = raw.Trim(); if (text.Length == 0) { return string.Empty; } string text2 = text.ToLowerInvariant(); if (text2.Length <= 32) { return text2; } return text2.Substring(0, 32).TrimEnd(Array.Empty<char>()); } public static string ToRaw(string? raw) { if (string.IsNullOrEmpty(raw)) { return string.Empty; } string text = raw.Trim(); if (text.Length == 0 || text.Length <= 32) { return text; } return text.Substring(0, 32).TrimEnd(Array.Empty<char>()); } public static bool IsReserved(string? normalized) { if (!IsInput(normalized)) { return IsMisc(normalized); } return true; } public static bool IsInput(string? normalized) { return Matches(normalized, "input"); } public static bool IsMisc(string? normalized) { return Matches(normalized, "misc"); } public static bool IsEmpty(string? normalized) { return string.IsNullOrWhiteSpace(normalized); } private static bool Matches(string? candidate, string reserved) { if (candidate != null) { return string.Equals(candidate, reserved, StringComparison.OrdinalIgnoreCase); } return false; } } public sealed class ChestPrefabEntry { public const int MaxGridSide = 16; public string Name { get; } public int PrefabHash { get; } public int Width { get; } public int Height { get; } public int Slots => Width * Height; public ChestPrefabOrigin Origin { get; } public bool IsVanilla => Origin == ChestPrefabOrigin.Vanilla; private ChestPrefabEntry(string name, int prefabHash, int width, int height, ChestPrefabOrigin origin) { Name = name; PrefabHash = prefabHash; Width = width; Height = height; Origin = origin; } public static bool TryCreate(string? name, int width, int height, ChestPrefabOrigin origin, out ChestPrefabEntry? entry, out string problem) { entry = null; string text = (name ?? string.Empty).Trim(); if (text.Length == 0) { problem = "the prefab name is empty"; return false; } if (width < 1 || height < 1 || width > 16 || height > 16) { problem = text + ": grid " + width + "x" + height + " is not plausible for a chest (expected 1 .. " + 16 + " in both directions)"; return false; } problem = string.Empty; entry = new ChestPrefabEntry(text, StableHash.Compute(text), width, height, origin); return true; } public static ChestPrefabEntry Create(string? name, int width, int height, ChestPrefabOrigin origin = ChestPrefabOrigin.Vanilla) { if (!TryCreate(name, width, height, origin, out ChestPrefabEntry entry, out string problem)) { throw new ArgumentException(problem, "name"); } return entry; } public string Describe() { return Name + " hash=" + PrefabHash + " grid=" + Width + "x" + Height + " slots=" + Slots + " (" + (IsVanilla ? "vanilla" : "config") + ")"; } public override string ToString() { return Describe(); } } public sealed class ChestPrefabList { public const int MaxEntries = 32; private static readonly char[] Separators = new char[2] { ',', ';' }; private readonly string[] _names; private readonly string[] _notices; public static ChestPrefabList Empty { get; } = new ChestPrefabList(Array.Empty<string>(), Array.Empty<string>()); public IReadOnlyList<string> Names => _names; public IReadOnlyList<string> Notices => _notices; public int Count => _names.Length; public bool IsEmpty => _names.Length == 0; public bool HasNotices => _notices.Length != 0; private ChestPrefabList(string[] names, string[] notices) { _names = names; _notices = notices; } public static ChestPrefabList Parse(string? text) { if (string.IsNullOrWhiteSpace(text)) { return Empty; } List<string> list = new List<string>(); List<string> list2 = new List<string>(); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); int num = 0; string[] array = text.Split(Separators); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0) { continue; } if (ChestPrefabs.IsVanillaName(text2)) { list2.Add(Prefix(text2) + "is a vanilla chest and always sorted, the entry is ignored"); continue; } if (!hashSet.Add(text2)) { list2.Add(Prefix(text2) + "is listed more than once, the duplicate is ignored"); continue; } if (list.Count >= 32) { num++; continue; } string text3 = ChestPrefabs.FindVanillaNameIgnoringCase(text2); if (text3 != null) { list2.Add(Prefix(text2) + "differs from the vanilla chest \"" + text3 + "\" only in case; prefab names are case sensitive, so this is a different prefab and most likely a typo"); } list.Add(text2); } if (num > 0) { list2.Add("additionalChestPrefabs: more than " + 32 + " prefab names, only the first " + 32 + " are used (" + num + " ignored)"); } if (list.Count != 0 || list2.Count != 0) { return new ChestPrefabList(list.ToArray(), list2.ToArray()); } return Empty; } public override string ToString() { if (Count != 0) { return "additionalChestPrefabs: " + Count + " name(s) " + string.Join(", ", _names) + (HasNotices ? (", " + _notices.Length + " notice(s)") : string.Empty); } return "additionalChestPrefabs: none" + (HasNotices ? (", " + _notices.Length + " notice(s)") : string.Empty); } private static string Prefix(string name) { return "additionalChestPrefabs: \"" + name + "\" "; } } public enum ChestPrefabOrigin { Vanilla, Config } public static class ChestPrefabs { public const string WoodName = "piece_chest_wood"; public const string BarrelName = "piece_chest_barrel"; public const string ReinforcedName = "piece_chest"; public const string BlackMetalName = "piece_chest_blackmetal"; public const string GraustenName = "piece_chest_grausten"; public const string WardrobeName = "piece_chest_warderobe"; public const int WoodHash = 328745978; public const int ReinforcedHash = -1443983522; public const int BlackMetalHash = 1417076401; public const int WoodGridWidth = 5; public const int WoodGridHeight = 2; public const int ReinforcedGridWidth = 6; public const int ReinforcedGridHeight = 4; public const int BlackMetalGridWidth = 8; public const int BlackMetalGridHeight = 4; public const int GraustenGridWidth = 8; public const int GraustenGridHeight = 5; public const int BarrelGridWidth = 6; public const int BarrelGridHeight = 2; public const int WardrobeGridWidth = 5; public const int WardrobeGridHeight = 10; private static readonly string[] VanillaNameList = new string[6] { "piece_chest_wood", "piece_chest_barrel", "piece_chest", "piece_chest_blackmetal", "piece_chest_grausten", "piece_chest_warderobe" }; public static IReadOnlyList<string> VanillaNames => VanillaNameList; public static ChestCatalog VerifiedVanillaCatalog { get; } = ChestCatalog.Build(new ChestPrefabEntry[3] { ChestPrefabEntry.Create("piece_chest_wood", 5, 2), ChestPrefabEntry.Create("piece_chest", 6, 4), ChestPrefabEntry.Create("piece_chest_blackmetal", 8, 4) }); public static bool IsVanillaName(string? prefabName) { if (string.IsNullOrEmpty(prefabName)) { return false; } for (int i = 0; i < VanillaNameList.Length; i++) { if (string.Equals(VanillaNameList[i], prefabName, StringComparison.Ordinal)) { return true; } } return false; } public static string? FindVanillaNameIgnoringCase(string? prefabName) { if (string.IsNullOrEmpty(prefabName)) { return null; } for (int i = 0; i < VanillaNameList.Length; i++) { if (string.Equals(VanillaNameList[i], prefabName, StringComparison.OrdinalIgnoreCase)) { return VanillaNameList[i]; } } return null; } public static bool TryGetExpectedGrid(string? prefabName, out int width, out int height) { switch (prefabName) { case "piece_chest_wood": width = 5; height = 2; return true; case "piece_chest": width = 6; height = 4; return true; case "piece_chest_blackmetal": width = 8; height = 4; return true; case "piece_chest_grausten": width = 8; height = 5; return true; case "piece_chest_barrel": width = 6; height = 2; return true; case "piece_chest_warderobe": width = 5; height = 10; return true; default: width = 0; height = 0; return false; } } public static bool HasExpectedGrid(string? prefabName) { int width; int height; return TryGetExpectedGrid(prefabName, out width, out height); } } public enum LargeClusterNotice { Quiet, Warning, BackBelow } public sealed class LargeClusterWatch { private readonly int _maxChests; private readonly float _maxMetres; private bool _warned; public bool HasWarned => _warned; public LargeClusterWatch(int maxChests, float maxMetres) { _maxChests = maxChests; _maxMetres = maxMetres; } public LargeClusterNotice Check(ChestCluster? largest) { if (largest != null && (largest.Count > _maxChests || largest.BoundingBoxDiameter > _maxMetres)) { _warned = true; return LargeClusterNotice.Warning; } if (!_warned) { return LargeClusterNotice.Quiet; } _warned = false; return LargeClusterNotice.BackBelow; } public void Reset() { _warned = false; } } public static class ReservedNames { public const string Input = "input"; public const string Misc = "misc"; public static IReadOnlyList<string> All { get; } = new string[2] { "input", "misc" }; } public static class StableHash { public static int Compute(string? value) { string text = value ?? string.Empty; int num = 5381; int num2 = num; for (int i = 0; i < text.Length && text[i] != 0; i += 2) { num = ((num << 5) + num) ^ text[i]; if (i == text.Length - 1 || text[i + 1] == '\0') { break; } num2 = ((num2 << 5) + num2) ^ text[i + 1]; } return num + num2 * 1566083941; } } public static class WorldFileName { public const int MaxLength = 64; public const string Fallback = "world"; public const string ReservedPrefix = "world_"; private static readonly string[] ReservedDeviceNames = new string[22] { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; public static string Sanitize(string? worldName) { if (string.IsNullOrWhiteSpace(worldName)) { return "world"; } StringBuilder stringBuilder = new StringBuilder(worldName.Length); bool flag = false; string text = worldName.Trim(); foreach (char c in text) { if (IsAllowed(c)) { stringBuilder.Append(c); flag = false; } else if (!flag) { stringBuilder.Append('_'); flag = true; } } string text2 = Trim(stringBuilder.ToString()); if (text2.Length > 64) { text2 = Trim(text2.Substring(0, 64)); } if (text2.Length == 0) { return "world"; } if (!IsReservedDeviceName(text2)) { return text2; } return "world_" + text2; } public static bool IsReservedDeviceName(string? name) { if (string.IsNullOrEmpty(name)) { return false; } string[] reservedDeviceNames = ReservedDeviceNames; foreach (string b in reservedDeviceNames) { if (string.Equals(name, b, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool IsAllowed(char c) { if (!char.IsLetterOrDigit(c) && c != '-' && c != '_') { return c == '.'; } return true; } private static string Trim(string value) { return value.Trim('_', '.', '-', ' '); } } public static class ZdoKeys { public const string AcsNameString = "ACS_name"; public const int AcsName = 784906341; public const string ItemsString = "items"; public const int Items = -938864442; public const string InUseString = "InUse"; public const int InUse = -58355724; } } namespace AutoChestSort.Core.Ui { public struct ChestPanelFacts { public string DisplayName; public GroupRole Role; public int ChestStacks; public int GroupItems; public int ChestCount; public int OpenChests; public bool CtrlHeld; public bool Known; public bool Busy; public bool DialogOpen; public string NormalizedName => ChestNameNormalizer.Normalize(DisplayName); public bool IsUnnamed => NormalizedName.Length == 0; public bool IsReserved => ChestNameNormalizer.IsReserved(NormalizedName); public bool IsGroup { get { if (!IsUnnamed) { return !IsReserved; } return false; } } } public sealed class ChestPanelState { public bool Locked { get; private set; } public bool WritingBlocked { get; private set; } public bool NameFieldEnabled { get; private set; } public bool ResetEnabled { get; private set; } public bool InfoEnabled { get; private set; } public bool LearnEnabled { get; private set; } public bool LearnIsMiscFastpath { get; private set; } public bool DeleteEnabled { get; private set; } public bool DeleteArmed { get; private set; } public string LearnBlockedToken { get; private set; } = string.Empty; public string DeleteBlockedToken { get; private set; } = string.Empty; public string InfoBlockedToken { get; private set; } = string.Empty; private ChestPanelState() { } public static ChestPanelState Evaluate(ChestPanelFacts facts) { bool flag = facts.OpenChests > 0; bool flag2 = !facts.Known || facts.Busy || facts.DialogOpen; bool flag3 = flag || flag2; ChestPanelState chestPanelState = new ChestPanelState { Locked = flag, WritingBlocked = flag3, NameFieldEnabled = !flag3, ResetEnabled = (!flag3 && !facts.IsUnnamed), InfoEnabled = facts.IsGroup }; if (facts.IsGroup) { chestPanelState.InfoBlockedToken = string.Empty; } else { chestPanelState.InfoBlockedToken = (facts.IsReserved ? "$acs_err_name_reserved" : "$acs_err_group_not_found"); } bool flag4 = (chestPanelState.LearnIsMiscFastpath = facts.IsUnnamed && facts.ChestStacks == 0); if (facts.IsReserved) { chestPanelState.LearnEnabled = false; chestPanelState.LearnBlockedToken = "$acs_err_name_reserved"; } else if (facts.IsUnnamed && !flag4) { chestPanelState.LearnEnabled = false; chestPanelState.LearnBlockedToken = "$acs_msg_name_required"; } else if (flag) { chestPanelState.LearnEnabled = false; chestPanelState.LearnBlockedToken = "$acs_msg_group_locked"; } else { chestPanelState.LearnEnabled = !flag2; chestPanelState.LearnBlockedToken = string.Empty; } if (!facts.IsGroup) { chestPanelState.DeleteEnabled = false; chestPanelState.DeleteArmed = false; chestPanelState.DeleteBlockedToken = (facts.IsReserved ? "$acs_err_name_reserved" : "$acs_err_group_not_found"); } else if (flag) { chestPanelState.DeleteEnabled = false; chestPanelState.DeleteArmed = false; chestPanelState.DeleteBlockedToken = "$acs_msg_group_locked"; } else if (flag2) { chestPanelState.DeleteEnabled = false; chestPanelState.DeleteArmed = false; chestPanelState.DeleteBlockedToken = string.Empty; } else { chestPanelState.DeleteEnabled = true; chestPanelState.DeleteArmed = facts.CtrlHeld; chestPanelState.DeleteBlockedToken = (facts.CtrlHeld ? string.Empty : "$acs_msg_delete_needs_ctrl"); } return chestPanelState; } } public static class ChestPanelTokens { public static string RoleToken(GroupRole role) { return role switch { GroupRole.Group => "$acs_ui_role_group", GroupRole.Input => "$acs_ui_role_input", GroupRole.Misc => "$acs_ui_role_misc", _ => "$acs_ui_role_none", }; } public static string LearnedToken(int skippedItems) { if (skippedItems <= 0) { return "$acs_msg_learned"; } return "$acs_msg_learned_skipped"; } } public sealed class ConflictQueue { private readonly List<LearnConflict> _conflicts = new List<LearnConflict>(); private readonly List<string> _takeover = new List<string>(); private readonly List<string> _skip = new List<string>(); private int _index; private bool _cancelled; public int Total => _conflicts.Count; public int Position { get { if (!IsDone) { return _index + 1; } return 0; } } public bool IsDone { get { if (!_cancelled) { return _index >= _conflicts.Count; } return true; } } public bool IsCancelled => _cancelled; public LearnConflict? Current { get { if (!IsDone) { return _conflicts[_index]; } return null; } } public IReadOnlyList<string> Takeover => _takeover; public IReadOnlyList<string> Skip => _skip; public ConflictQueue(IEnumerable<LearnConflict>? conflicts) { if (conflicts == null) { return; } HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (LearnConflict conflict in conflicts) { if (conflict.Item.Name != null && conflict.Item.Name.Length > 0 && hashSet.Add(conflict.Item.Name)) { _conflicts.Add(conflict); } } } public bool Yes() { return Answer(takeover: true); } public bool No() { return Answer(takeover: false); } public bool Answer(bool takeover) { if (IsDone) { return false; } string name = _conflicts[_index].Item.Name; (takeover ? _takeover : _skip).Add(name); _index++; return true; } public void Cancel() { _cancelled = true; _takeover.Clear(); _skip.Clear(); } public override string ToString() { return "conflicts " + Position + "/" + Total + " takeover=" + _takeover.Count + " skip=" + _skip.Count + (_cancelled ? " cancelled" : (IsDone ? " done" : string.Empty)); } } public sealed class LearnDialogPlan { public bool AsksForMode { get; private set; } public bool ShowsRemovedWarning { get; private set; } public bool ShowsMergeHint { get; private set; } public string PrimaryToken { get; private set; } = string.Empty; public LearnMode PrimaryMode { get; private set; } public string SecondaryToken { get; private set; } = string.Empty; public LearnMode SecondaryMode { get; private set; } public string CancelToken { get; private set; } = string.Empty; public bool HasSecondary => SecondaryToken.Length > 0; private LearnDialogPlan() { } public static LearnDialogPlan ForPreview(int removedCount) { bool flag = removedCount > 0; return new LearnDialogPlan { AsksForMode = flag, ShowsRemovedWarning = flag, ShowsMergeHint = flag, PrimaryToken = (flag ? "$acs_dlg_button_replace" : "$acs_dlg_button_learn"), PrimaryMode = LearnMode.Replace, SecondaryToken = (flag ? "$acs_dlg_button_merge" : string.Empty), SecondaryMode = LearnMode.Merge, CancelToken = "$acs_dlg_button_cancel" }; } public override string ToString() { return "learn dialog: " + (AsksForMode ? "replace|merge|cancel" : "learn|cancel"); } } } namespace AutoChestSort.Core.Sorting { public sealed class BeautifyBackoff { private struct Entry { internal int Streak; internal double Until; } public const int UndoneLimit = 3; private readonly Dictionary<ChestId, Entry> _entries = new Dictionary<ChestId, Entry>(); private readonly double _seconds; public double Seconds => _seconds; public int Count => _entries.Count; public long Armed { get; private set; } public BeautifyBackoff(double seconds) { _seconds = ((seconds > 0.0) ? seconds : 0.0); } public int BackedOff(double now) { int num = 0; foreach (Entry value in _entries.Values) { if (now < value.Until) { num++; } } return num; } public bool IsBackedOff(ChestId chest, double now) { if (_entries.TryGetValue(chest, out var value)) { return now < value.Until; } return false; } public double SecondsLeft(ChestId chest, double now) { if (!_entries.TryGetValue(chest, out var value) || now >= value.Until) { return 0.0; } return value.Until - now; } public int UndoneStreak(ChestId chest) { if (!_entries.TryGetValue(chest, out var value)) { return 0; } return value.Streak; } public bool NoteVerdict(ChestId chest, ChestLayoutVerdict verdict, double now) { switch (verdict) { case ChestLayoutVerdict.Unverifiable: return false; default: Clear(chest, now); return false; case ChestLayoutVerdict.Undone: { _entries.TryGetValue(chest, out var value); value.Streak++; if (value.Streak < 3) { _entries[chest] = value; return false; } value.Streak = 0; value.Until = now + _seconds; _entries[chest] = value; Armed++; return true; } } } public bool Forget(ChestId chest) { return _entries.Remove(chest); } public int PruneExcept(ICollection<ChestId>? keep) { if (keep == null || _entries.Count == 0) { return 0; } List<ChestId> list = null; foreach (ChestId key in _entries.Keys) { if (!keep.Contains(key)) { if (list == null) { list = new List<ChestId>(); } list.Add(key); } } if (list == null) { return 0; } for (int i = 0; i < list.Count; i++) { _entries.Remove(list[i]); } return list.Count; } public void Clear() { _entries.Clear(); Armed = 0L; } private void Clear(ChestId chest, double now) { if (_entries.TryGetValue(chest, out var value)) { if (now >= value.Until) { _entries.Remove(chest); return; } value.Streak = 0; _entries[chest] = value; } } } public enum PauseEnd { Running, Released, Expired, NotPaused } public sealed class BlockedPauseTracker { public const int RequiredRounds = 3; private int _blockedRounds; private bool _movedThisRound; private bool _inUseThisRound; private double _pauseUntil; public bool IsPaused => _pauseUntil > 0.0; public double PauseUntil => _pauseUntil; public int BlockedRounds => _blockedRounds; public bool PlayerBlockedThisRound => _inUseThisRound; public long Pauses { get; private set; } public void NoteMove() { _movedThisRound = true; } public bool NoteBlock(BlockReason reason) { if (reason != BlockReason.InUse) { return false; } _inUseThisRound = true; return true; } public bool EndRound(double now, double pauseSeconds) { bool movedThisRound = _movedThisRound; bool inUseThisRound = _inUseThisRound; _movedThisRound = false; _inUseThisRound = false; if (movedThisRound || !inUseThisRound) { _blockedRounds = 0; return false; } _blockedRounds++; if (_blockedRounds < 3 || pauseSeconds <= 0.0 || IsPaused) { return false; } _blockedRounds = 0; _pauseUntil = now + pauseSeconds; Pauses++; return true; } public PauseEnd Check(double now, bool released) { if (!IsPaused) { return PauseEnd.NotPaused; } if (released) { Clear(); return PauseEnd.Released; } if (now >= _pauseUntil) { Clear(); return PauseEnd.Expired; } return PauseEnd.Running; } public double SecondsLeft(double now) { double num = _pauseUntil - now; if (!IsPaused || !(num > 0.0)) { return 0.0; } return num; } public void Reset() { Clear(); _blockedRounds = 0; _movedThisRound = false; _inUseThisRound = false; } private void Clear() { _pauseUntil = 0.0; } } public enum BlockReason { None, InUse, Cooldown, RevisionUnstable, Missing, Unreadable, ReadBackPending } public sealed class ChestLayoutAudit { private readonly List<ItemRecord> _before; private readonly ChestLayoutPlan _plan; private readonly Func<int, int>? _maxStackOf; private readonly int _gridWidth; private readonly int _gridHeight; private readonly int _version; private readonly byte[] _trailer; public int RecordsBefore { get; } public int RecordsAfter { get; private set; } public int UnitsBefore { get; } public int UnitsAfter { get; private set; } public bool IsComplete { get; private set; } public string Violation { get; private set; } public bool IsValid { get { if (IsComplete) { return Violation.Length == 0; } return false; } } private ChestLayoutAudit(List<ItemRecord> before, ChestLayoutPlan plan, Func<int, int>? maxStackOf, int gridWidth, int gridHeight, int version, byte[] trailer) { _before = before; _plan = plan; _maxStackOf = maxStackOf; _gridWidth = gridWidth; _gridHeight = gridHeight; _version = version; _trailer = trailer; RecordsBefore = before.Count; RecordsAfter = before.Count; UnitsBefore = Units(before); UnitsAfter = UnitsBefore; Violation = string.Empty; } public static ChestLayoutAudit Before(InventoryPackage? package, ChestLayoutPlan? plan, Func<int, int>? maxStackOf) { List<ItemRecord> list = new List<ItemRecord>(); if (package != null) { list.AddRange(package.Items); } return new ChestLayoutAudit(list, plan ?? ChestLayoutPlan.Nothing("no plan"), maxStackOf, package?.GridWidth ?? 0, package?.GridHeight ?? 0, package?.Version ?? 0, (package == null) ? Array.Empty<byte>() : ((byte[])package.Trailer.Clone())); } public void After(InventoryPackage? package) { IsComplete = true; RecordsAfter = package?.Count ?? 0; UnitsAfter = ((package != null) ? Units(package.Items) : 0); if (package == null) { Violation = "the inventory is gone"; } else { Violation = FirstViolation(package); } } public string Describe() { return "units " + UnitsBefore + "->" + UnitsAfter + ", records " + RecordsBefore + "->" + RecordsAfter; } public override string ToString() { return Describe() + ((!IsComplete) ? " (incomplete)" : ((Violation.Length == 0) ? string.Empty : (" INVALID: " + Violation))); } private string FirstViolation(InventoryPackage package) { if (UnitsAfter != UnitsBefore) { return "the chest holds " + UnitsAfter + " unit(s) instead of " + UnitsBefore; } int num = RecordsBefore - _plan.MergedStacks; if (RecordsAfter != num) { return "the chest holds " + RecordsAfter + " record(s) instead of " + num + " (" + RecordsBefore + " minus " + _plan.MergedStacks + " merge(s))"; } string text = CheckMapping(package); if (text.Length > 0) { return text; } string text2 = CheckGrid(package); if (text2.Length > 0) { return text2; } string text3 = CheckClasses(package); if (text3.Length > 0) { return text3; } int num2 = CountNonPositive(_before); int num3 = CountNonPositive(package.Items); if (num3 > num2) { return "the rewrite created " + (num3 - num2) + " record(s) with a stack of zero or less"; } return CheckBytes(package); } private string CheckMapping(InventoryPackage package) { IReadOnlyList<int> originOf = _plan.OriginOf; if (originOf.Count != package.Count) { return "the plan maps " + originOf.Count + " record(s), the chest holds " + package.Count; } HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < package.Count; i++) { int num = originOf[i]; if (num < 0 || num >= _before.Count) { return "record " + i + " claims to come from record " + num + ", which the chest never held"; } if (!hashSet.Add(num)) { return "record " + i + " and an earlier one both claim to come from record " + num + "; the mapping is not injective"; } if (!package.Items[i].RawEqualsIgnoringGridPositionAndStack(_before[num])) { return "record " + i + " is not the record " + num + " it came from (" + _before[num]?.ToString() + " -> " + package.Items[i]?.ToString() + ")"; } } return string.Empty; } private string CheckGrid(InventoryPackage package) { if (_gridWidth <= 0 || _gridHeight <= 0) { return "the grid size of this chest is unknown"; } HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < package.Count; i++) { ItemRecord itemRecord = package.Items[i]; if (itemRecord.GridX >= _gridWidth || itemRecord.GridY >= _gridHeight) { string[] obj = new string[11] { "record ", i.ToString(), " sits at slot ", itemRecord.GridX.ToString(), ",", itemRecord.GridY.ToString(), ", outside the ", null, null, null, null }; int gridWidth = _gridWidth; obj[7] = gridWidth.ToString(); obj[8] = "x"; gridWidth = _gridHeight; obj[9] = gridWidth.ToString(); obj[10] = " grid"; return string.Concat(obj); } if (!hashSet.Add(itemRecord.GridY * _gridWidth + itemRecord.GridX)) { return "two records share the grid slot " + itemRecord.GridX + "," + itemRecord.GridY; } } return string.Empty; } private string CheckClasses(InventoryPackage package) { List<ItemRecord> list = new List<ItemRecord>(); List<int> list2 = new List<int>(); List<int> list3 = new List<int>(); List<int> list4 = new List<int>(); foreach (ItemRecord item in _before) { int num = IndexOfClass(list, item); if (num < 0) { list.Add(item); list2.Add(0); list3.Add(0); list4.Add(0); num = list.Count - 1; } int num2 = MaxStackOf(item.PrefabHash); list2[num] += item.Stack; if (num2 > 0 && item.Stack < num2) { list3[num]++; } if (item.Stack > list4[num]) { list4[num] = item.Stack; } } int[] array = new int[list.Count]; int[] array2 = new int[list.Count]; int[] array3 = new int[list.Count]; for (int i = 0; i < package.Count; i++) { ItemRecord itemRecord = package.Items[i]; int num3 = IndexOfClass(list, itemRecord); if (num3 < 0) { return "record " + i + " belongs to no item the chest held before (" + itemRecord?.ToString() + ")"; } int num4 = MaxStackOf(itemRecord.PrefabHash); array[num3] += itemRecord.Stack; if (num4 > 0 && itemRecord.Stack < num4) { array2[num3]++; } if (itemRecord.Stack > array3[num3]) { array3[num3] = itemRecord.Stack; } } for (int j = 0; j < list.Count; j++) { if (array[j] != list2[j]) { return "the item " + list[j]?.ToString() + " holds " + array[j] + " unit(s) instead of " + list2[j]; } int num5 = MaxStackOf(list[j].PrefabHash); if (num5 > 1 && list4[j] <= num5) { if (array3[j] > num5) { return "the item " + list[j]?.ToString() + " has a stack of " + array3[j] + " above its maximum of " + num5; } if (array2[j] > 1) { return "the item " + list[j]?.ToString() + " is left with " + array2[j] + " partial stacks instead of one at most"; } } else if (array2[j] > list3[j] || array3[j] > list4[j]) { return "the item " + list[j]?.ToString() + " may only be rearranged, but its stacks changed (partial " + list3[j] + "->" + array2[j] + ", biggest " + list4[j] + "->" + array3[j] + ")"; } } return string.Empty; } private string CheckBytes(InventoryPackage package) { InventoryPackage inventoryPackage; try { inventoryPackage = InventoryPackage.Parse(package.Serialize()); } catch (InventoryFormatException ex) { return "the rewritten chest does not parse back: " + ex.Message; } if (inventoryPackage.Version != _version) { string text = inventoryPackage.Version.ToString(); int version = _version; return "the rewritten chest carries package version " + text + " instead of " + version; } if (!ItemRecord.BytesEqual(inventoryPackage.Trailer, _trailer)) { return "the trailing bytes of the package changed (" + _trailer.Length + " byte(s) -> " + inventoryPackage.Trailer.Length + ")"; } if (inventoryPackage.Count != package.Count) { return "the rewritten chest parses back as " + inventoryPackage.Count + " record(s) instead of " + package.Count; } for (int i = 0; i < inventoryPackage.Count; i++) { if (!inventoryPackage.Items[i].RawEquals(package.Items[i])) { return "record " + i + " does not survive the round trip (" + package.Items[i]?.ToString() + " -> " + inventoryPackage.Items[i]?.ToString() + ")"; } } return string.Empty; } private int MaxStackOf(int prefabHash) { if (_maxStackOf == null) { return 0; } int num = _maxStackOf(prefabHash); if (num >= 0) { return num; } return 0; } private static int Units(IReadOnlyList<ItemRecord> records) { int num = 0; for (int i = 0; i < records.Count; i++) { num += records[i].Stack; } return num; } private static int CountNonPositive(IReadOnlyList<ItemRecord> records) { int num = 0; for (int i = 0; i < records.Count; i++) { if (records[i].Stack <= 0) { num++; } } return num; } private static int IndexOfClass(List<ItemRecord> samples, ItemRecord record) { for (int i = 0; i < samples.Count; i++) { if (samples[i].RawEqualsIgnoringGridPositionAndStack(record)) { return i; } } return -1; } } public sealed class ChestLayoutPlan { private static readonly ItemRecord[] NoRecords = new ItemRecord[0]; private static readonly int[] NoOrigins = new int[0]; public IReadOnlyList<ItemRecord> Records { get; } public IReadOnlyList<int> OriginOf { get; } public bool IsTidy { get; } public bool IsUnsafe { get; } public string Reason { get; } public int RecordsBefore { get; } public int MergedStacks { get; } public int MovedSlots { get; } public int RearrangedOnlyClasses { get; } public int Fingerprint { get; } public bool HasWork { get { if (Records.Count > 0 && !IsTidy) { return !IsUnsafe; } return false; } } private ChestLayoutPlan(IReadOnlyList<ItemRecord> records, IReadOnlyList<int> originOf, bool tidy, bool unsafeChest, string reason, int recordsBefore, int mergedStacks, int movedSlots, int rearrangedOnlyClasses, int fingerprint) { Records = records; OriginOf = originOf; IsTidy = tidy; IsUnsafe = unsafeChest; Reason = reason ?? string.Empty; RecordsBefore = recordsBefore; MergedStacks = mergedStacks; MovedSlots = movedSlots; RearrangedOnlyClasses = rearrangedOnlyClasses; Fingerprint = fingerprint; } public static ChestLayoutPlan Nothing(string reason) { return new ChestLayoutPlan(NoRecords, NoOrigins, tidy: false, unsafeChest: false, reason, 0, 0, 0, 0, 0); } public static ChestLayoutPlan Unusable(string reason, int recordsBefore) { return new ChestLayoutPlan(NoRecords, NoOrigins, tidy: false, unsafeChest: true, reason, recordsBefore, 0, 0, 0, 0); } internal static ChestLayoutPlan For(IReadOnlyList<ItemRecord> records, IReadOnlyList<int> originOf, bool tidy, string reason, int recordsBefore, int mergedStacks, int movedSlots, int rearrangedOnlyClasses) { return new ChestLayoutPlan(records, originOf, tidy, unsafeChest: false, reason, recordsBefore, mergedStacks, movedSlots, rearrangedOnlyClasses, ChestLayoutVerification.FingerprintOf(records)); } public string Describe() { if (IsUnsafe) { return "skipped: " + Reason; } if (!HasWork) { return Reason; } return "records " + RecordsBefore + "->" + Records.Count + ", merged " + MergedStacks + ", moved " + MovedSlots + ((RearrangedOnlyClasses > 0) ? (", kept " + RearrangedOnlyClasses) : string.Empty); } public override string ToString() { return Describe(); } public void ApplyTo(InventoryPackage package) { if (package == null) { throw new ArgumentNullException("package"); } if (!HasWork) { throw new InvalidOperationException("this plan has nothing to apply: " + Describe()); } package.ReplaceAll(Records); } } public static class ChestLayoutPlanner { private sealed class LayoutClass { private readonly List<int> _indexes = new List<int>(); internal ItemRecord Sample { get; } internal byte[] Canonical { get; } internal string? Name { get; } internal int MaxStack { get; } internal int Tier { get; } internal int Bucket { get; } internal IReadOnlyList<int> Indexes => _indexes; internal int FirstIndex => _indexes[0]; internal int Units { get; private set; } internal bool HasOverStack { get; private set; } internal bool Merges { get { if (MaxStack > 1 && MaxStack <= 65535) { return !HasOverStack; } return false; } } internal LayoutClass(ItemRecord sample, string? name, int maxStack) { Sample = sample; Canonical = sample.CanonicalWithoutPositionAndStack(); Name = name; MaxStack = maxStack; Tier = ItemProgression.TierOf(name); Bucket = ((name == null) ? 2 : ((maxStack <= 1) ? 1 : 0)); } internal void Add(int index, int stack) { _indexes.Add(index); Units += stack; if (MaxStack > 0 && stack > MaxStack) { HasOverStack = true; } } } private const int MaxStackField = 65535; private const int BucketStackable = 0; private const int BucketNotStackable = 1; private const int BucketUnknown = 2; public static ChestLayoutPlan Plan(InventoryPackage? package, Func<int, int>? maxStackOf, Func<int, string?>? sortKeyOf, bool objectDbReady) { if (!objectDbReady) { return ChestLayoutPlan.Nothing("skipped this tick: the item database is not up yet"); } if (package == null) { return ChestLayoutPlan.Unusable("the inventory could not be read", 0); } ChestLayoutPlan chestLayoutPlan = CheckFailClosed(package); if (chestLayoutPlan != null) { return chestLayoutPlan; } if (package.Count == 0) { return ChestLayoutPlan.Nothing("the chest is empty"); } List<LayoutClass> list = BuildClasses(package, maxStackOf, sortKeyOf); list.Sort(CompareClasses); List<ItemRecord> list2 = new List<ItemRecord>(package.Count); List<int> list3 = new List<int>(package.Count); int num = 0; int num2 = 0; foreach (LayoutClass item in list) { if (!item.Merges) { num2++; foreach (int index in item.Indexes) { list2.Add(package.Items[index]); list3.Add(index); } } else { num += Repack(package, item, list2, list3); } } return Finish(package, list2, list3, num, num2); } private static ChestLayoutPlan? CheckFailClosed(InventoryPackage package) { if (!package.HasGrid) { return ChestLayoutPlan.Unusable("the grid size of this chest is unknown", package.Count); } int num = package.GridWidth * package.GridHeight; if (package.Count > num) { return ChestLayoutPlan.Unusable("the chest holds " + package.Count + " record(s) but its " + package.GridWidth + "x" + package.GridHeight + " grid has only " + num + " slot(s)", package.Count); } HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < package.Count; i++) { ItemRecord itemRecord = package.Items[i]; if (itemRecord.GridX >= package.GridWidth || itemRecord.GridY >= package.GridHeight) { return ChestLayoutPlan.Unusable("record " + i + " sits at slot " + itemRecord.GridX + "," + itemRecord.GridY + ", outside the " + package.GridWidth + "x" + package.GridHeight + " grid ACS knows for this chest", package.Count); } if (itemRecord.Stack <= 0) { return ChestLayoutPlan.Unusable("record " + i + " at slot " + itemRecord.GridX + "," + itemRecord.GridY + " has a stack of " + itemRecord.Stack, package.Count); } if (!hashSet.Add(itemRecord.GridY * package.GridWidth + itemRecord.GridX)) { return ChestLayoutPlan.Unusable("two records share the grid slot " + itemRecord.GridX + "," + itemRecord.GridY, package.Count); } } return null; } private static List<LayoutClass> BuildClasses(InventoryPackage package, Func<int, int>? maxStackOf, Func<int, string?>? sortKeyOf) { List<LayoutClass> list = new List<LayoutClass>(); Dictionary<int, string> dictionary = new Dictionary<int, string>(); Dictionary<int, int> dictionary2 = new Dictionary<int, int>(); for (int i = 0; i < package.Count; i++) { ItemRecord itemRecord = package.Items[i]; LayoutClass layoutClass = null; for (int j = 0; j < list.Count; j++) { if (list[j].Sample.RawEqualsIgnoringGridPositionAndStack(itemRecord)) { layoutClass = list[j]; break; } } if (layoutClass == null) { int prefabHash = itemRecord.PrefabHash; if (!dictionary.TryGetValue(prefabHash, out var value)) { value = (dictionary[prefabHash] = sortKeyOf?.Invoke(prefabHash)); } if (!dictionary2.TryGetValue(prefabHash, out var value2)) { value2 = (dictionary2[prefabHash] = maxStackOf?.Invoke(prefabHash) ?? 0); } layoutClass = new LayoutClass(itemRecord, value, value2); list.Add(layoutClass); } layoutClass.Add(i, itemRecord.Stack); } return list; } private static int Repack(InventoryPackage package, LayoutClass group, List<ItemRecord> records, List<int> origins) { int maxStack = group.MaxStack; int units = group.Units; int num = units / maxStack; int num2 = units % maxStack; int num3 = num + ((num2 > 0) ? 1 : 0); List<int> list = new List<int>(group.Indexes); list.Sort(delegate(int left, int right) { int num6 = package.Items[right].Stack.CompareTo(package.Items[left].Stack); return (num6 == 0) ? left.CompareTo(right) : num6; }); for (int num4 = 0; num4 < num3; num4++) { int num5 = list[num4]; int stack = ((num4 < num) ? maxStack : num2); records.Add(package.Items[num5].WithStack(stack)); origins.Add(num5); } return list.Count - num3; } private static ChestLayoutPlan Finish(InventoryPackage package, List<ItemRecord> records, List<int> origins, int merged, int rearrangedOnly) { int num = 0; bool flag = records.Count == package.Count; for (int i = 0; i < records.Count; i++) { byte x = (byte)(i % package.GridWidth); byte y = (byte)(i / package.GridWidth); ItemRecord itemRecord = records[i].WithGridPosition(x, y); ItemRecord itemRecord2 = package.Items[origins[i]]; records[i] = itemRecord; if (itemRecord.GridX != itemRecord2.GridX || itemRecord.GridY != itemRecord2.GridY) { num++; } if (flag && (origins[i] != i || !itemRecord.RawEquals(itemRecord2))) { flag = false; } } return ChestLayoutPlan.For(records, origins, flag, flag ? "the chest is already tidy" : ("records " + package.Count + "->" + records.Count), package.Count, merged, num, rearrangedOnly); } private static int CompareClasses(LayoutClass left, LayoutClass right) { int num = left.Bucket.CompareTo(right.Bucket); if (num != 0) { return num; } if (left.Bucket == 2) { int num2 = left.Sample.PrefabHash.CompareTo(right.Sample.PrefabHash); if (num2 != 0) { return num2; } return left.FirstIndex.CompareTo(right.FirstIndex); } int num3 = left.Tier.CompareTo(right.Tier); if (num3 != 0) { return num3; } int num4 = string.Compare(left.Name, right.Name, StringComparison.OrdinalIgnoreCase); if (num4 != 0) { return num4; } num4 = string.CompareOrdinal(left.Name, right.Name); if (num4 != 0) { return num4; } if (left.Bucket == 1) { return left.FirstIndex.CompareTo(right.FirstIndex); } int num5 = right.Sample.Quality.CompareTo(left.Sample.Quality); if (num5 != 0) { return num5; } int num6 = left.Sample.Variant.CompareTo(right.Sample.Variant); if (num6 != 0) { return num6; } int num7 = right.Sample.WorldLevel.CompareTo(left.Sample.WorldLevel); if (num7 != 0) { return num7; } return CompareBytes(left.Canonical, right.Canonical); } private static int CompareBytes(byte[] left, byte[] right) { int num = Math.Min(left.Length, right.Length); for (int i = 0; i < num; i++) { if (left[i] != right[i]) { if (left[i] >= right[i]) { return 1; } return -1; } } return left.Length.CompareTo(right.Length); } } public enum ChestLayoutVerdict { Ok, Undone, PossibleLoss, Unverifiable } public readonly struct ChestLayoutVerification { public ChestLayoutVerdict Verdict { get; } public int MissingUnits { get; } public int RecordDelta { get; } public bool LayoutChanged { get; } public bool IsLoss => Verdict == ChestLayoutVerdict.PossibleLoss; private ChestLayoutVerification(ChestLayoutVerdict verdict, int missingUnits, int recordDelta, bool layoutChanged) { Verdict = verdict; MissingUnits = missingUnits; RecordDelta = recordDelta; LayoutChanged = layoutChanged; } public static int FingerprintOf(IReadOnlyList<ItemRecord>? records) { if (records == null) { return 0; } List<ItemRecord> list = new List<ItemRecord>(records.Count); for (int i = 0; i < records.Count; i++) { if (records[i] != null) { list.Add(records[i]); } } list.Sort(delegate(ItemRecord left, ItemRecord right) { int num2 = left.GridY.CompareTo(right.GridY); if (num2 != 0) { return num2; } int num3 = left.GridX.CompareTo(right.GridX); return (num3 == 0) ? left.PrefabHash.CompareTo(right.PrefabHash) : num3; }); int num = -2128831035; num = (num ^ list.Count) * 16777619; foreach (ItemRecord item in list) { num = (num ^ item.GridX) * 16777619; num = (num ^ item.GridY) * 16777619; num = (num ^ item.Stack) * 16777619; num = (num ^ item.PrefabHash) * 16777619; num = (num ^ item.Quality) * 16777619; num = (num ^ item.Variant) * 16777619; num = (num ^ item.WorldLevel) * 16777619; } return num; } public static ChestLayoutVerification Check(int expectedFingerprint, IReadOnlyList<ItemRecord>? expected, IReadOnlyList<ItemRecord>? actual, bool chestHeld = false) { if (actual == null) { return new ChestLayoutVerification(ChestLayoutVerdict.Unverifiable, 0, 0, layoutChanged: true); } int num = expected?.Count ?? 0; int recordDelta = actual.Count - num; if (FingerprintOf(actual) == expectedFingerprint) { return new ChestLayoutVerification(ChestLayoutVerdict.Ok, 0, recordDelta, layoutChanged: false); } int num2 = CountMissingUnits(expected, actual); if (num2 > 0) { if (!chestHeld) { return new ChestLayoutVerification(ChestLayoutVerdict.PossibleLoss, num2, recordDelta, layoutChanged: true); } return new ChestLayoutVerification(ChestLayoutVerdict.Unverifiable, num2, recordDelta, layoutChanged: true); } return new ChestLayoutVerification(ChestLayoutVerdict.Undone, 0, recordDelta, layoutChanged: true); } public override string ToString() { return Verdict.ToString() + " (missingUnits=" + MissingUnits + ", recordDelta=" + RecordDelta + ", layoutChanged=" + (LayoutChanged ? "yes" : "no") + ")"; } private static int CountMissingUnits(IReadOnlyList<ItemRecord>? expected, IReadOnlyList<ItemRecord> actual) { if (expected == null || expected.Count == 0) { return 0; } List<ItemRecord> list = new List<ItemRecord>(); List<int> list2 = new List<int>(); for (int i = 0; i < expected.Count; i++) { int num = IndexOfClass(list, expected[i]); if (num < 0) { list.Add(expected[i]); list2.Add(expected[i].Stack); } else { list2[num] += expected[i].Stack; } } int[] array = new int[list.Count]; for (int j = 0; j < actual.Count; j++) { int num2 = IndexOfClass(list, actual[j]); if (num2 >= 0) { array[num2] += actual[j].Stack; } } int num3 = 0; for (int k = 0; k < list.Count; k++) { if (array[k] < list2[k]) { num3 += list2[k] - array[k]; } } return num3; } private static int IndexOfClass(List<ItemRecord> samples, ItemRecord? record) { if (record == null) { return -1; } for (int i = 0; i < samples.Count; i++) { if (samples[i].RawEqualsIgnoringGridPositionAndStack(record)) { return i; } } return -1; } } public sealed class ChestSnapshot { public ChestId Id { get; } public int PrefabHash { get; } public AcsVector3 Position { get; } public string Name { get; } public uint DataRevision { get; } public BlockReason Block { get; } public InventoryPackage? Inventory { get; } public float Distance { get; } public bool IsUsable { get { if (Block == BlockReason.None && Inventory != null) { return Inventory.HasGrid; } return false; } } public bool IsInput => ChestNameNormalizer.IsInput(Name); public bool IsMisc => ChestNameNormalizer.IsMisc(Name); public bool IsGroupMember { get { if (Name.Length > 0) { return !ChestNameNormalizer.IsReserved(Name); } return false; } } public ChestSnapshot(ChestId id, int prefabHash, AcsVector3 position, string? name, uint dataRevision, BlockReason block, InventoryPackage? inventory, float distance) { Id = id; PrefabHash = prefabHash; Position = position; Name = ChestNameNormalizer.Normalize(name); DataRevision = dataRevision; Block = block; Inventory = inventory; Distance = distance; } public int CountOf(int prefabHash) { InventoryPackage inventory = Inventory; if (inventory == null) { return 0; } int num = 0; for (int i = 0; i < inventory.Count; i++) { ItemRecord itemRecord = inventory.Items[i]; if (itemRecord.PrefabHash == prefabHash) { num += itemRecord.Stack; } } return num; } public override string ToString() { return Describe(null); } public string Describe(ChestCatalog? catalog) { return (catalog?.NameOf(PrefabHash) ?? PrefabHash.ToString()) + " " + Id.ToString() + " \"" + Name + "\" rev=" + DataRevision + " block=" + Block.ToString() + " items=" + ((Inventory == null) ? "?" : Inventory.Count.ToString()); } } public sealed class ChestStabilityTracker { private sealed class Entry { internal uint DataRevision; internal ushort OwnerRevision; internal double LastChange; internal double CooldownUntil; internal double ClosedAt; internal bool InUse; internal int OpenSignals; internal int OwnWrites; } private readonly Dictionary<ChestId, Entry> _entries = new Dictionary<ChestId, Entry>(); private bool _closeSeen; public int Count => _entries.Count; public bool CloseSeen => _closeSeen; public void Observe(ChestId chest, uint dataRevision, ushort ownerRevision, bool inUse, double now, double cooldownSeconds) { if (!_entries.TryGetValue(chest, out Entry value)) { value = new Entry { DataRevision = dataRevision, OwnerRevision = ownerRevision, LastChange = now, InUse = inUse }; _entries[chest] = value; return; } if (value.DataRevision != dataRevision) { value.DataRevision = dataRevision; value.LastChange = now; } if (value.OwnerRevision != ownerRevision) { value.OwnerRevision = ownerRevision; value.LastChange = now; double num = now + cooldownSeconds; if (num > value.CooldownUntil) { value.CooldownUntil = num; } } if (value.InUse && !inUse) { _closeSeen = true; value.ClosedAt = now; double num2 = now + cooldownSeconds; if (num2 > value.CooldownUntil) { value.CooldownUntil = num2; } } value.InUse = inUse; } public void MarkOpened(ChestId chest, double now, double cooldownSeconds) { if (!_entries.TryGetValue(chest, out Entry value)) { value = new Entry { LastChange = now }; _entries[chest] = value; } double num = now + cooldownSeconds; if (num > value.CooldownUntil) { value.CooldownUntil = num; } value.OpenSignals++; } public void NoteOwnWrite(ChestId chest, uint dataRevision, ushort ownerRevision) { if (_entries.TryGetValue(chest, out Entry value)) { value.DataRevision = dataRevision; value.OwnerRevision = ownerRevision; value.OwnWrites++; } } public BlockReason Evaluate(ChestId chest, bool inUse, double now, double cooldownSeconds) { if (inUse) { return BlockReason.InUse; } if (!_entries.TryGetValue(chest, out Entry value)) { return BlockReason.RevisionUnstable; } if (now < value.CooldownUntil) { return BlockReason.Cooldown; } if (now - value.LastChange < cooldownSeconds) { return BlockReason.RevisionUnstable; } return BlockReason.None; } public bool TakeCloseSeen() { bool closeSeen = _closeSeen; _closeSeen = false; return closeSeen; } public int OpenSignalsOf(ChestId chest) { if (!_entries.TryGetValue(chest, out Entry value)) { return 0; } return value.OpenSignals; } public int OwnWritesOf(ChestId chest) { if (!_entries.TryGetValue(chest, out Entry value)) { return 0; } return value.OwnWrites; } public double SecondsSinceChange(ChestId chest, double now) { if (!_entries.TryGetValue(chest, out Entry value)) { return -1.0; } return now - value.LastChange; } public double SecondsSinceClose(ChestId chest, double now) { if (!_entries.TryGetValue(chest, out Entry value) || !(value.ClosedAt > 0.0)) { return -1.0; } return now - value.ClosedAt; } public bool Forget(ChestId chest) { return _entries.Remove(chest); } public int PruneExcept(ICollection<ChestId> keep) { if (keep == null || _entries.Count == 0) { return 0; } List<ChestId> list = null; foreach (ChestId key in _entries.Keys) { if (!keep.Contains(key)) { list = list ?? new List<ChestId>(); list.Add(key); } } if (list == null) { return 0; } for (int i = 0; i < list.Count; i++) { _entries.Remove(list[i]); } return list.Count; } public void Clear() { _entries.Clear(); _closeSeen = false; } } public sealed class ChestTidyMemo { private readonly Dictionary<ChestId, uint> _tidyAt = new Dictionary<ChestId, uint>(); public int Count => _tidyAt.Count; public bool IsTidy(ChestId chest, uint revision) { if (_tidyAt.TryGetValue(chest, out var value)) { return value == revision; } return false; } public void Note(ChestId chest, uint revision) { _tidyAt[chest] = revision; } public bool Forget(ChestId chest) { return _tidyAt.Remove(chest); } public int PruneExcept(ICollection<ChestId>? keep) { if (keep == null || _tidyAt.Count == 0) { return 0; } List<ChestId> list = null; foreach (ChestId key in _tidyAt.Keys) { if (!keep.Contains(key)) { if (list == null) { list = new List<ChestId>(); } list.Add(key); } } if (list == null) { return 0; } for (int i = 0; i < list.Count; i++) { _tidyAt.Remove(list[i]); } return list.Count; } public void Clear() { _tidyAt.Clear(); } } public enum ConflictKind { Discarded, Waited, NotDiscardable, RevisionConflict, ConservationMismatch, LostAfterWrite, Duplicate } public sealed class ConflictCounters { public const double WindowSeconds = 600.0; public const int KindCount = 7; private readonly Queue<double> _window = new Queue<double>(); private readonly long[] _totals = new long[7]; public long Moves { get; private set; } public long PartialMoves { get; private set; } public long Unsortable { get; private set; } public long Ticks { get; private set; } public long Attempts { get; private set; } public long Relocated { get; private set; } public long Beautified { get; private set; } public long Distributed { get; private set; } public long Buffered { get; private set; } public long BudgetSkips { get; private set; } public long Pauses { get; private set; } public long Warnings { get; private set; } public int WindowCount => _window.Count; public bool HadActivity { get; private set; } public long TotalOf(ConflictKind kind) { return _totals[(int)kind]; } public static bool CountsAsConflict(ConflictKind kind) { return kind != ConflictKind.Waited; } public void NoteTick() { Ticks++; } public void NoteAttempt() { Attempts++; } public void NoteBudgetSkip() { BudgetSkips++; } public void NoteMove(bool partial) { NoteMove(partial, MoveKindTag.Input); } public void NoteMove(bool partial, MoveKindTag kind) { Moves++; if (partial) { PartialMoves++; } switch (kind) { case MoveKindTag.Relocation: Relocated++; break; case MoveKindTag.Beautify: Beautified++; break; } } public void NoteDistribution(bool buffer) { Distributed++; if (buffer) { Buffered++; } } public void NoteUnsortable() { Unsortable++; } public void NotePause() { Pauses++; } public void Add(ConflictKind kind, double now) { _totals[(int)kind]++; if (CountsAsConflict(kind)) { _window.Enqueue(now); Prune(now); } } public void Prune(double now) { while (_window.Count > 0 && now - _window.Peek() > 600.0) { _window.Dequeue(); } } public bool ShouldWarn(int threshold, double now) { if (threshold <= 0) { return false; } Prune(now); if (_window.Count < threshold) { return false; } _window.Clear(); Warnings++; return true; } public string Describe() { if (Ticks == 0L) { return string.Empty; } return "ticks=" + Ticks + " attempts=" + Attempts + " moves=" + Moves + " (partial=" + PartialMoves + ", relocated=" + Relocated + ", distributed=" + Distributed + ", buffered=" + Buffered + ", beautified=" + Beautified + ") waited=" + TotalOf(ConflictKind.Waited) + " unsortable=" + Unsortable + " discarded=" + TotalOf(ConflictKind.Discarded) + " notDiscardable=" + TotalOf(ConflictKind.NotDiscardable) + " revisionConflicts=" + TotalOf(ConflictKind.RevisionConflict) + " conservationMismatch=" + TotalOf(ConflictKind.ConservationMismatch) + " lostAfterWrite=" + TotalOf(ConflictKind.LostAfterWrite) + " duplicates=" + TotalOf(ConflictKind.Duplicate) + " budgetSkips=" + BudgetSkips + " pauses=" + Pauses + " warnings=" + Warnings + " conflictsInWindow=" + _window.Count; } public void NoteActivity() { HadActivity = true; } public void ResetActivity() { HadActivity = false; } public void Clear() { Array.Clear(_totals, 0, _totals.Length); _window.Clear(); Moves = 0L; PartialMoves = 0L; Relocated = 0L; Distributed = 0L; Buffered = 0L; Beautified = 0L; Unsortable = 0L; Ticks = 0L; Attempts = 0L; BudgetSkips = 0L; Pauses = 0L; Warnings = 0L; HadActivity = false; } } public sealed class InPlaceMergeAudit { private readonly List<ItemRecord> _untouchedBefore; private readonly int _fromIndex; private readonly int _toIndex; private readonly ItemRecord? _fromBefore; private readonly ItemRecord? _toBefore; private readonly bool _amountKnown; private readonly string _precondition; public int PrefabHash { get; } public int UnitsBefore { get; } public int UnitsAfter { get; private set; } public int RecordsBefore { get; } public int RecordsAfter { get; private set; } public int Amount { get; } public int MaxStack { get; } public bool IsTopUp { get; } public bool IsFullMerge => !IsTopUp; public bool CanMerge => _precondition.Length == 0; public bool IsComplete { get; private set; } public string Violation { get; private set; } public bool IsValid { get { if (IsComplete) { return Violation.Length == 0; } return false; } } private InPlaceMergeAudit(int prefabHash, int fromIndex, int toIndex, int unitsBefore, int recordsBefore, List<ItemRecord> untouchedBefore, ItemRecord? fromBefore = null, ItemRecord? toBefore = null, bool amountKnown = false, int amount = 0, int maxStack = 0, bool isTopUp = false, string precondition = "") { PrefabHash = prefabHash; _fromIndex = fromIndex; _toIndex = toIndex; UnitsBefore = unitsBefore; RecordsBefore = recordsBefore; _untouchedBefore = untouchedBefore; _fromBefore = fromBefore; _toBefore = toBefore; _amountKnown = amountKnown; _precondition = precondition; Amount = amount; MaxStack = maxStack; IsTopUp = isTopUp; UnitsAfter = unitsBefore; RecordsAfter = recordsBefore; Violation = string.Empty; } public static InPlaceMergeAudit Before(InventoryPackage? package, int prefabHash, int fromIndex, int toIndex) { List<ItemRecord> list = new List<ItemRecord>(); if (package == null) { return new InPlaceMergeAudit(prefabHash, fromIndex, toIndex, 0, 0, list); } CollectUntouched(package, fromIndex, toIndex, list); return new InPlaceMergeAudit(prefabHash, fromIndex, toIndex, MoveAudit.CountOf(package, prefabHash), package.Count, list); } public static InPlaceMergeAudit Before(InventoryPackage? package, int prefabHash, int fromIndex, int toIndex, int amount, int maxStack) { List<ItemRecord> list = new List<ItemRecord>(); if (package == null) { return new InPlaceMergeAudit(prefabHash, fromIndex, toIndex, 0, 0, list, null, null, amountKnown: false, 0, 0, isTopUp: false, "the inventory is gone"); } string text = Refuse(package, prefabHash, fromIndex, toIndex, amount, maxStack); ItemRecord itemRecord = null; ItemRecord toBefore = null; bool isTopUp = false; if (text.Length == 0) { itemRecord = package.Items[fromIndex]; toBefore = package.Items[toIndex]; isTopUp = amount < itemRecord.Stack; CollectUntouched(package, fromIndex, toIndex, list); } return new InPlaceMergeAudit(prefabHash, fromIndex, toIndex, MoveAudit.CountOf(package, prefabHash), package.Count, list, itemRecord, toBefore, amountKnown: true, amount, maxStack, isTopUp, text); } public void After(InventoryPackage? package) { IsComplete = true; UnitsAfter = MoveAudit.CountOf(package, PrefabHash); RecordsAfter = package?.Count ?? 0; if (_precondition.Length > 0) { Violation = _precondition; return; } if (package == null) { Violation = "the inventory is gone"; return; } if (UnitsAfter != UnitsBefore) { Violation = "the units of the item type changed from " + UnitsBefore + " to " + UnitsAfter; return; } int num = (IsTopUp ? RecordsBefore : (RecordsBefore - 1)); if (RecordsAfter != num) { Violation = "the chest holds " + RecordsAfter + " record(s) instead of " + num; return; } int num2 = ((!IsTopUp && _toIndex > _fromIndex) ? (_toIndex - 1) : _toIndex); if (num2 < 0 || num2 >= package.Count) { Violation = "the merged record is not in the chest any more"; return; } if (_amountKnown) { string text = CheckSurgery(package, num2); if (text.Length > 0) { Violation = text; return; } } List<ItemRecord> list = new List<ItemRecord>(RecordsAfter); for (int i = 0; i < package.Count; i++) { if (i != num2 && (!IsTopUp || i != _fromIndex)) { list.Add(package.Items[i]); } } if (list.Count != _untouchedBefore.Count) { Violation = "the chest holds " + list.Count + " untouched record(s) instead of " + _untouchedBefore.Count; return; } for (int j = 0; j < list.Count; j++) { if (!list[j].RawEquals(_untouchedBefore[j])) { Violation = "the untouched record " + j + " changed its bytes (" + _untouchedBefore[j]?.ToString() + " -> " + list[j]?.ToString() + ")"; return; } } Violation = string.Empty; } public string Describe() { return "units " + UnitsBefore + "->" + UnitsAfter + ", records " + RecordsBefore + "->" + RecordsAfter + (_amountKnown ? (", " + (IsTopUp ? "top up " : "merge ") + Amount) : string.Empty); } public override string ToString() { return Describe() + ((!IsComplete) ? " (incomplete)" : ((Violation.Length == 0) ? string.Empty : (" INVALID: " + Violation))); } private static void CollectUntouched(InventoryPackage package, int fromIndex, int toIndex, List<ItemRecord> untouched) { for (int i = 0; i < package.Count; i++) { if (i != fromIndex && i != toIndex) { untouched.Add(package.Items[i]); } } } private static string Refuse(InventoryPackage package, int prefabHash, int fromIndex, int toIndex, int amount, int maxStack) { if (fromIndex < 0 || fromIndex >= package.Count || toIndex < 0 || toIndex >= package.Count) { return "slot " + fromIndex + " or " + toIndex + " is not in a chest of " + package.Count + " record(s)"; } if (fromIndex == toIndex) { return "an in place merge needs two different records, both are " + fromIndex; } if (maxStack <= 0) { return "the maximum stack size of the item type is unknown"; } ItemRecord itemRecord = package.Items[fromIndex]; ItemRecord itemRecord2 = package.Items[toIndex]; if (itemRecord.PrefabHash != prefabHash || itemRecord2.PrefabHash != prefabHash) { return "the records are not of the item type the merge is about"; } if (!itemRecord2.RawEqualsIgnoringGridPositionAndStack(itemRecord)) { return "the records " + fromIndex + " and " + toIndex + " are not the same merge class"; } if (amount <= 0) { return "a merge of " + amount + " unit(s) is no merge"; } if (amount > itemRecord.Stack) { return "the merge moves " + amount + " unit(s) but the record holds " + itemRecord.Stack; } if (itemRecord2.Stack + amount > maxStack) { return "the merge would leave " + (itemRecord2.Stack + amount) + " unit(s) in one slot, above the maximum of " + maxStack; } return string.Empty; } private string CheckSurgery(InventoryPackage package, int mergedIndex) { ItemRecord itemRecord = package.Items[mergedIndex]; int num = _toBefore.Stack + Amount; if (itemRecord.Stack != num) { return "the record that took the units holds " + itemRecord.Stack + " unit(s) instead of " + num; } if (!itemRecord.RawEqualsIgnoringGridPositionAndStack(_toBefore)) { return "the record that took the units changed more than its stack"; } if (!IsTopUp) { return string.Empty; } if (_fromIndex < 0 || _fromIndex >= package.Count) { return "the record that gave the units is gone although this was a top up"; } ItemRecord itemRecord2 = package.Items[_fromIndex]; int num2 = _fromBefore.Stack - Amount; if (itemRecord2.Stack != num2) { return "the record that gave the units holds " + itemRecord2.Stack + " unit(s) instead of " + num2; } if (!itemRecord2.RawEqualsIgnoringGridPositionAndStack(_fromBefore)) { return "the record that gave the units changed more than its stack"; } return string.Empty; } } public enum InPlaceMergeVerdict { Ok, Undone, PossibleLoss, Unverifiable } public readonly struct InPlaceMergeVerification { public InPlaceMergeVerdict Verdict { get; } public int MissingUnits { get; } public int ExtraRecords { get; } public bool IsLoss => Verdict == InPlaceMergeVerdict.PossibleLoss; private InPlaceMergeVerification(InPlaceMergeVerdict verdict, int missingUnits, int extraRecords) { Verdict = verdict; MissingUnits = missingUnits; ExtraRecords = extraRecords; } public static InPlaceMergeVerification Check(int expectedUnits, int unitsNow, int expectedRecords, int recordsNow, bool chestHeld, bool readable = true) { int num = expectedUnits - unitsNow; int num2 = recordsNow - expectedRecords; if (num > 0) { if (!(!readable || chestHeld)) { return new InPlaceMergeVerification(InPlaceMergeVerdict.PossibleLoss, num, (num2 > 0) ? num2 : 0); } return new InPlaceMergeVerification(InPlaceMergeVerdict.Unverifiable, num, (num2 > 0) ? num2 : 0); } if (!readable) { return new InPlaceMergeVerification(InPlaceMergeVerdict.Unverifiable, 0, 0); } if (num2 > 0) { return new InPlaceMergeVerification(InPlaceMergeVerdict.Undone, 0, num2); } return new InPlaceMergeVerification(InPlaceMergeVerdict.Ok, 0, 0); } public override string ToString() { return Verdict.ToString() + " (missingUnits=" + MissingUnits + ", extraRecords=" + ExtraRecords + ")"; } } public sealed class InventoryCache { private sealed class Entry { internal object InstanceKey { get; } internal uint Revision { get; } internal InventoryPackage Package { get; } internal Entry(object instanceKey, uint revision, InventoryPackage package) { InstanceKey = instanceKey; Revision = revision; Package = package; } } private readonly Dictionary<ChestId, Entry> _entries = new Dictionary<ChestId, Entry>(); public int Count => _entries.Count; public bool TryGet(ChestId chest, object? instanceKey, uint revision, out InventoryPackage? package) { package = Match(chest, instanceKey, revision); return package != null; } public bool TryGetCopy(ChestId chest, object? instanceKey, uint revision, out InventoryPackage? package) { package = Match(chest, instanceKey, revision)?.Clone(); return package != null; } public void Store(ChestId chest, object instanceKey, uint revision, InventoryPackage package) { if (instanceKey == null) { throw new ArgumentNullException("instanceKey"); } if (package == null) { throw new ArgumentNullException("package"); } _entries[chest] = new Entry(instanceKey, revision, package.Clone()); } public bool Invalidate(ChestId chest) { return _entries.Remove(chest); } public int PruneExcept(ICollection<ChestId> keep) { if (keep == null || _entries.Count == 0) { return 0; } List<ChestId> list = null; foreach (ChestId key in _entries.Keys) { if (!keep.Contains(key)) { list = list ?? new List<ChestId>(); list.Add(key); } } if (list == null) { return 0; } for (int i = 0; i < list.Count; i++) { _entries.Remove(list[i]); } return list.Count; } public void Clear() { _entries.Clear(); } private InventoryPackage? Match(ChestId chest, object? instanceKey, uint revision) { if (!_entries.TryGetValue(chest, out Entry value)) { return null; } if (instanceKey != null && value.InstanceKey == instanceKey && value.Revision == revision) { return value.Package; } if (instanceKey != null) { _entries.Remove(chest); } return null; } } public static class InventoryMove { public static MovePlan Plan(InventoryPackage? target, ItemRecord? item, int maxStack) { if (target == null || item == null || item.Stack <= 0 || !target.HasGrid) { return MovePlan.None; } int num = -1; int num2 = 0; if (maxStack > 1) { for (int i = 0; i < target.Count; i++) { ItemRecord itemRecord = target.Items[i]; if (itemRecord.PrefabHash == item.PrefabHash && itemRecord.Quality == item.Quality && itemRecord.Variant == item.Variant && itemRecord.WorldLevel == item.WorldLevel) { int num3 = maxStack - itemRecord.Stack; if (num3 > num2) { num2 = num3; num = i; }