diff --git a/Source/VABOrganizer/AdvancedSorting.cs b/Source/VABOrganizer/AdvancedSorting.cs index 68d4a3c..4acde39 100644 --- a/Source/VABOrganizer/AdvancedSorting.cs +++ b/Source/VABOrganizer/AdvancedSorting.cs @@ -174,7 +174,7 @@ public static void ChangeAdvancedSortMode(AdvancedSortType newType) if (curMode != uiSorterBase.sortingButtonStates[5]) { Utils.Log($"[Advanced Sorting] In a non-custom mode, clicking button 5"); - // Just fake click the button + // ClickButton invokes SortingCallback synchronously, so select the requested sorter before entering custom mode. uiSorterBase.ClickButton(5); CurrentAdvancedSort = newType; return; diff --git a/Source/VABOrganizer/AvailablePartData.cs b/Source/VABOrganizer/AvailablePartData.cs index b6f3e1f..c03d2fc 100644 --- a/Source/VABOrganizer/AvailablePartData.cs +++ b/Source/VABOrganizer/AvailablePartData.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using UnityEngine; namespace VABOrganizer @@ -20,28 +21,31 @@ void Awake() void Start() { ConfigNode[] variableRoot = GameDatabase.Instance.GetConfigNodes(Settings.ORGANIZER_VARIABLE_ROOT_NODE_NAME); - if (variableRoot.Length > 0) + List variableNodes = new List(); + foreach (ConfigNode rootNode in variableRoot) + { + variableNodes.AddRange(rootNode.GetNodes(Settings.ORGANIZER_VARIABLE_NODE_NAME)); + } + variableNodes.AddRange(GameDatabase.Instance.GetConfigNodes(Settings.ORGANIZER_VARIABLE_NODE_NAME)); + + if (variableNodes.Count > 0) { ConfigVariables = new List(); Utils.Log($"[AdvancedSortingDataStore]: Loading variable definitions"); - foreach (ConfigNode rootNode in variableRoot) + foreach (ConfigNode varNode in variableNodes) { - ConfigNode[] variableNodes = rootNode.GetNodes(Settings.ORGANIZER_VARIABLE_NODE_NAME); - foreach (ConfigNode varNode in variableNodes) + CustomSortVariable data = new CustomSortVariable(varNode); + if (!ConfigVariables.Contains(data)) { - CustomSortVariable data = new CustomSortVariable(varNode); - if (!ConfigVariables.Contains(data)) - { - ConfigVariables.Add(data); - } - else - { - Utils.LogWarning($"[AdvancedSortingDataStore]: Multiple {Settings.ORGANIZER_VARIABLE_NODE_NAME} with the same name ({data.Name}) found, skipping others"); - } + ConfigVariables.Add(data); + } + else + { + Utils.LogWarning($"[AdvancedSortingDataStore]: Multiple {Settings.ORGANIZER_VARIABLE_NODE_NAME} with the same name ({data.Name}) found, skipping others"); } - Utils.Log($"[AdvancedSortingDataStore]: Loaded {ConfigVariables.Count} sorting variable definitions"); } + Utils.Log($"[AdvancedSortingDataStore]: Loaded {ConfigVariables.Count} sorting variable definitions"); } @@ -60,7 +64,26 @@ public void OnPartLoaderLoaded() Utils.Log($"[AdvancedSortingDataStore]: Starting part data parse"); foreach (AvailablePart p in PartLoader.Instance.loadedParts) { - PartData.Add(p.name, new AvailablePartData(p)); + if (p == null || string.IsNullOrEmpty(p.name)) + { + Utils.LogWarning("[AdvancedSortingDataStore]: Loaded part has no name, skipping"); + continue; + } + + try + { + if (PartData.ContainsKey(p.name)) + { + Utils.LogWarning($"[AdvancedSortingDataStore]: Duplicate loaded part name '{p.name}' ({p.title}); keeping the first entry"); + continue; + } + + PartData.Add(p.name, new AvailablePartData(p)); + } + catch (Exception e) + { + Utils.LogError($"[AdvancedSortingDataStore]: Failed to parse part '{p.name}' ({p.title}): {e}"); + } } watch.Stop(); /// TODO: 7 ms for stock + NFT on Chris' garbage laptop is fine, may need to be optimized later @@ -358,4 +381,4 @@ void ProcessModule_Engine(ConfigNode node) } } } -} \ No newline at end of file +} diff --git a/Source/VABOrganizer/Harmony/EditorPartList.cs b/Source/VABOrganizer/Harmony/EditorPartList.cs index 2cdaa0a..47a4c08 100644 --- a/Source/VABOrganizer/Harmony/EditorPartList.cs +++ b/Source/VABOrganizer/Harmony/EditorPartList.cs @@ -30,13 +30,32 @@ internal static bool PatchSorting(EditorPartList __instance, int button, bool as { if (AdvancedSorting.CurrentAdvancedSort != null) { + string sortKey = AdvancedSorting.CurrentAdvancedSort.Sorter; partSortProperty.SetValue(__instance, new RUIutils.FuncComparer((AvailablePart r1, AvailablePart r2) => - RUIutils.SortAscDescPrimarySecondary(asc, AdvancedSortingDataStore.Instance.PartData[r1.name].GetData(AdvancedSorting.CurrentAdvancedSort.Sorter).CompareTo(AdvancedSortingDataStore.Instance.PartData[r2.name].GetData(AdvancedSorting.CurrentAdvancedSort.Sorter)), r1.title.CompareTo(r2.title)))); + RUIutils.SortAscDescPrimarySecondary(asc, CompareAdvancedSortValues(r1, r2, sortKey), r1.title.CompareTo(r2.title)))); } } return true; } + + /// + /// Compare parsed advanced-sort values without allowing an unparseable or + /// dynamically added part to break sorting for the entire editor list. + /// + private static int CompareAdvancedSortValues(AvailablePart first, AvailablePart second, string sortKey) + { + var partData = AdvancedSortingDataStore.Instance.PartData; + bool hasFirst = partData.TryGetValue(first.name, out AvailablePartData firstData); + bool hasSecond = partData.TryGetValue(second.name, out AvailablePartData secondData); + + if (hasFirst && hasSecond) + { + return firstData.GetData(sortKey).CompareTo(secondData.GetData(sortKey)); + } + + return hasFirst == hasSecond ? 0 : hasFirst ? 1 : -1; + } /// /// Patch the part icon update to assign icons to the right categories /// @@ -108,4 +127,4 @@ internal static bool PatchStart(EditorPartList __instance) return true; } } -} \ No newline at end of file +} diff --git a/Source/VABOrganizer/Utils.cs b/Source/VABOrganizer/Utils.cs index ca9aa4a..bb4edda 100644 --- a/Source/VABOrganizer/Utils.cs +++ b/Source/VABOrganizer/Utils.cs @@ -38,7 +38,7 @@ public static T FindChildOfType(string name, Transform parent) } catch (NullReferenceException e) { - Debug.LogError($"Couldn't find {name} in children of {parent.name}"); + Debug.LogError($"Couldn't find {name} in children of {parent.name}: {e}"); } return result; }