From e992abeeb66ea26bfa5c5bef5d59759eadb3240e Mon Sep 17 00:00:00 2001 From: "Mr. Purple" Date: Wed, 25 Oct 2023 03:58:40 -0600 Subject: [PATCH] feat: Workbench Organization (#486) * Workbench Organization. Move all Crafting Nodes out of the Workbench Root Added a Fallback Node when adding craftnodes and they fail due to mixing tabs and crafting nodes. Created a TraverseTree method as we seem to be doing the same thing in multiple places. Added logging to ModCraftTreeRoot to show what nodes are in the way when trying to add a node fails due to cross node type. * Move Misplaced Using Directives * More Corrections Add CreateVanillaTabNode for all 3 fabricators that have no tabs. Add fallback tabs for all vanilla fabricators. Added the ability for Mods to remove and move other mods nodes by doubling down on the removals to before and after mod node creation. If a mod item fails the tab/crafting check it now gets put into the fallback unsorted tab with a warning. * Ensure nodes only moved If they are crafting nodes. * Fix: correct possible exception if someone adds just a treenode or some new modded subnode * Remove Unneeded iteration * Update CraftTreePatcher.cs --- Nautilus/Crafting/ModCraftTreeRoot.cs | 8 +- Nautilus/Patchers/CraftTreePatcher.cs | 196 ++++++++++++++++---------- 2 files changed, 124 insertions(+), 80 deletions(-) diff --git a/Nautilus/Crafting/ModCraftTreeRoot.cs b/Nautilus/Crafting/ModCraftTreeRoot.cs index 58b329d3..89feff14 100644 --- a/Nautilus/Crafting/ModCraftTreeRoot.cs +++ b/Nautilus/Crafting/ModCraftTreeRoot.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Nautilus.Handlers; @@ -98,7 +98,7 @@ public ModCraftTreeRoot AddTabNode(string tabId, string displayText, Atlas.Sprit } else { - InternalLogger.Error($"Cannot add tab: {tabId} as it is being added to a parent node that contains crafting nodes."); + InternalLogger.Error($"Cannot add tab: {tabId} as it is being added to a parent node that contains crafting nodes. {string.Join(", ", parentTab.ChildNodes.Where(node => node.Action == TreeAction.Craft).Select(x => x.Name))} "); } return this; } @@ -129,7 +129,7 @@ public ModCraftTreeRoot AddTabNode(string tabId, string displayText, UnityEngine } else { - InternalLogger.Error($"Cannot add tab: {tabId} as it is being added to a parent node that contains crafting nodes."); + InternalLogger.Error($"Cannot add tab: {tabId} as it is being added to a parent node that contains crafting nodes. {string.Join(", ", parentTab.ChildNodes.Where(node => node.Action == TreeAction.Craft).Select(x => x.Name))} "); } return this; } @@ -180,7 +180,7 @@ public ModCraftTreeRoot AddCraftNode(string moddedTechType, string parentTabId = } else { - InternalLogger.Error($"Cannot add crafting node: {techType} as it is being added to a parent node that contains tab nodes."); + InternalLogger.Error($"Cannot add crafting node: {techType} as it is being added to a parent node that contains tab nodes. {string.Join(", ", parentTab.ChildNodes.Where(node => node.Action == TreeAction.Expand).Select(x => x.Name))} "); } } else diff --git a/Nautilus/Patchers/CraftTreePatcher.cs b/Nautilus/Patchers/CraftTreePatcher.cs index a4b34e9b..df1448ae 100644 --- a/Nautilus/Patchers/CraftTreePatcher.cs +++ b/Nautilus/Patchers/CraftTreePatcher.cs @@ -1,13 +1,15 @@ -using System.Collections.Generic; +namespace Nautilus.Patchers; + +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using BepInEx.Logging; using HarmonyLib; using Nautilus.Crafting; +using Nautilus.Handlers; using Nautilus.Utility; -namespace Nautilus.Patchers; - internal class CraftTreePatcher { #region Internal Fields @@ -16,6 +18,8 @@ internal class CraftTreePatcher internal static List NodesToRemove = new(); internal static List CraftingNodes = new(); internal static List TabNodes = new(); + private const string FallbackTabNode = "Modded"; + private const string VanillaRoot = "Vanilla"; #endregion @@ -23,11 +27,63 @@ internal class CraftTreePatcher internal static void Patch(Harmony harmony) { + CreateFallbackNodes(); harmony.PatchAll(typeof(CraftTreePatcher)); - InternalLogger.Log($"CraftTreePatcher is done.", LogLevel.Debug); } + private static void CreateFallbackNodes() + { + // Workbench + CreateVanillaTabNode(CraftTree.Type.Workbench, "Modification Station", TechType.Workbench, CraftTree.WorkbenchScheme().root); + CraftTreeHandler.AddTabNode(CraftTree.Type.Workbench, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.Workbench)); + + // Fabricator + CraftTreeHandler.AddTabNode(CraftTree.Type.Fabricator, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.Fabricator)); + + // Constructor + CraftTreeHandler.AddTabNode(CraftTree.Type.Constructor, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.Constructor)); + + // Seamoth Upgrades + CraftTreeHandler.AddTabNode(CraftTree.Type.SeamothUpgrades, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.BaseUpgradeConsole)); + + // Map Room + CreateVanillaTabNode(CraftTree.Type.MapRoom, "Scanner Upgrades", TechType.BaseMapRoom, CraftTree.MapRoomSheme().root); + CraftTreeHandler.AddTabNode(CraftTree.Type.MapRoom, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.BaseMapRoom)); +#if SUBNAUTICA + // Cyclops Fabricator + CreateVanillaTabNode(CraftTree.Type.CyclopsFabricator, "Cyclops Fabricator", TechType.Cyclops, CraftTree.CyclopsFabricatorScheme().root); + CraftTreeHandler.AddTabNode(CraftTree.Type.CyclopsFabricator, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.Cyclops)); +#elif BELOWZERO + // SeaTruck Fabricator + CraftTreeHandler.AddTabNode(CraftTree.Type.SeaTruckFabricator, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.SeaTruckFabricator)); +#endif + } + + private static void CreateVanillaTabNode(CraftTree.Type treeType, string DisplayName, TechType spriteTechType, TreeNode root) + { + var removedNodes = new List(); + foreach (var node in root.nodes) + { + if (node is not CraftNode craftNode || craftNode.action == TreeAction.Expand) + continue; + + CraftTreeHandler.RemoveNode(treeType, new[] { node.id }); + removedNodes.Add(craftNode); + } + + if (removedNodes.Count == 0) + return; + + CraftTreeHandler.AddTabNode(treeType, VanillaRoot, DisplayName, SpriteManager.Get(spriteTechType)); + foreach (var node in removedNodes) + { + InternalLogger.Debug($"Moved {node.techType0} from {treeType} root into new {VanillaRoot} tab."); + CraftTreeHandler.AddCraftingNode(treeType, node.techType0, new[] { VanillaRoot }); + } + InternalLogger.Debug($"Reorganized {removedNodes.Count} {treeType} nodes into new {VanillaRoot} tab."); + } + [HarmonyPrefix] [HarmonyPatch(typeof(CraftTree), nameof(CraftTree.GetTree))] private static bool GetTreePreFix(CraftTree.Type treeType, ref CraftTree __result) @@ -115,54 +171,40 @@ private static void SeaTruckFabricatorSchemePostfix(ref CraftNode __result) private static void PatchCraftTree(ref CraftNode __result, CraftTree.Type type) { - RemoveNodes(ref __result, NodesToRemove, type); - AddCustomTabs(ref __result, TabNodes, type); - PatchNodes(ref __result, CraftingNodes, type); + var removals = NodesToRemove.Where(x => x.Scheme == type).ToList(); + RemoveNodes(ref __result, ref removals, type); + + var tabNodes = TabNodes.Where(x => x.Scheme == type).ToList(); + var craftingNodes = CraftingNodes.Where(x => x.Scheme == type).ToList(); + AddCustomTabs(ref __result, tabNodes, type); + PatchNodes(ref __result, craftingNodes, type); + + // Remove any nodes added by mods that were marked for removal by other mods. + RemoveNodes(ref __result, ref removals, type); } private static void AddCustomTabs(ref CraftNode nodes, List customTabs, CraftTree.Type scheme) { - foreach (TabNode tab in customTabs) + foreach (TabNode customNode in customTabs) { // Wrong crafter, skip. - if (tab.Scheme != scheme) + if (customNode.Scheme != scheme) { continue; } - TreeNode currentNode = default; - currentNode = nodes; + var currentNode = TraverseTree(nodes, customNode.Path); - // Patch into game's CraftTree. - for (int i = 0; i < tab.Path.Length; i++) + if (currentNode.nodes.Any(node => node is CraftNode craftNode && craftNode.action == TreeAction.Craft)) { - string currentPath = tab.Path[i]; - InternalLogger.Log("Tab Current Path: " + currentPath + " Tab: " + tab.Name + " Crafter: " + tab.Scheme.ToString(), LogLevel.Debug); - - TreeNode node = currentNode[currentPath]; - - // Reached the end of the line. - if (node != null) - { - currentNode = node; - } - else - { - break; - } - } - - if(currentNode.nodes.Any(node=> node is CraftNode craftNode && craftNode.action == TreeAction.Craft)) - { - InternalLogger.Error($"Cannot add tab: {tab.Name} as it is being added to a parent node that contains crafting nodes."); + InternalLogger.Error($"Cannot add tab: {customNode.Name} as it is being added to a parent node that contains crafting nodes. {string.Join(", ", currentNode.nodes.Where(node => node is CraftNode craftNode && craftNode.action == TreeAction.Craft).Select(x => x.id))} "); continue; } // Add the new tab node. - CraftNode newNode = new(tab.Name, TreeAction.Expand, TechType.None); currentNode.AddNode(new TreeNode[] { - newNode + new CraftNode(customNode.Name, TreeAction.Expand, TechType.None) }); } } @@ -177,47 +219,33 @@ private static void PatchNodes(ref CraftNode nodes, List customNod continue; } - // Have to do this to make sure C# shuts up. - TreeNode node = default; - node = nodes; + var currentNode = TraverseTree(nodes, customNode.Path); - // Loop through the path provided by the node. - // Get the node for the last path. - for (int i = 0; i < customNode.Path.Length; i++) + if (currentNode.nodes.Any(x => x is CraftNode craftNode && craftNode.action == TreeAction.Expand)) { - string currentPath = customNode.Path[i]; - TreeNode currentNode = node[currentPath]; + InternalLogger.Warn($"Cannot add Crafting node: {customNode.TechType.AsString()} as it is being added to {currentNode.id} that contains Tab nodes. {string.Join(", ", currentNode.nodes.Where(node => node is CraftNode craftNode && craftNode.action == TreeAction.Expand).Select(x => x.id))}"); + InternalLogger.Warn($"Adding to Fallback {FallbackTabNode} node in tree root."); - if (currentNode != null) - { - node = currentNode; - } - else - { - break; - } - } - - if(node.nodes.Any(x => x is CraftNode craftNode && craftNode.action == TreeAction.Expand)) - { - InternalLogger.Error($"Cannot Crafting node: {customNode.TechType.AsString()} as it is being added to {node.id} that contains Tab nodes."); - continue; + currentNode = TraverseTree(nodes, new[] { FallbackTabNode }); + if (currentNode.isRoot) + continue; } // Add the node. - node.AddNode(new TreeNode[] + currentNode.AddNode(new TreeNode[] { new CraftNode(customNode.TechType.AsString(false), TreeAction.Craft, customNode.TechType) }); } } - private static void RemoveNodes(ref CraftNode nodes, List nodesToRemove, CraftTree.Type scheme) + private static void RemoveNodes(ref CraftNode nodes, ref List nodesToRemove, CraftTree.Type scheme) { + var safelist = new List(nodesToRemove).OrderByDescending(x => x.Path.Length); // This method can be used to both remove single child nodes, thus removing one recipe from the tree. // Or it can remove entire tabs at once, removing the tab and all the recipes it contained in one go. - foreach (Node nodeToRemove in nodesToRemove) + foreach (Node nodeToRemove in safelist) { // Not for this fabricator. Skip. if (nodeToRemove.Scheme != scheme) @@ -232,37 +260,53 @@ private static void RemoveNodes(ref CraftNode nodes, List nodesToRemove, C } // Get the names of each node in the path to traverse tree until we reach the node we want. - TreeNode currentNode = default; - currentNode = nodes; - - // Travel the path down the tree. - string currentPath = null; - for (int step = 0; step < nodeToRemove.Path.Length; step++) - { - currentPath = nodeToRemove.Path[step]; - if (step > nodeToRemove.Path.Length) - { - break; - } - - currentNode = currentNode[currentPath]; - } + var currentNode = TraverseTree(nodes, nodeToRemove.Path); // Safty checks. - if (currentNode != null && currentNode.id == currentPath) + if (currentNode != null && currentNode.id == nodeToRemove.Path.Last()) { if (currentNode.parent == null) { - InternalLogger.Warn($"Skipped removing craft tree node in {nameof(RemoveNodes)} for '{scheme}'. Could not identify the parent node."); + InternalLogger.Warn($"Skipped removing craft tree node in {nameof(RemoveNodes)} for '{scheme}' at '{string.Join("/", nodeToRemove.Path)}'. Could not identify the parent node."); } else { currentNode.Clear(); // Remove all child nodes (if any) currentNode.parent.RemoveNode(currentNode); // Remove the node from its parent + nodesToRemove.Remove(nodeToRemove); // Remove the node from the list of nodes to remove } } } } + private static TreeNode TraverseTree(TreeNode nodes, string[] path) + { + TreeNode currentNode = nodes; + + // Loop through the path provided by the node. + // Get the node for the last path. + for (int i = 0; i < path.Length; i++) + { + var currentPath = path[i]; + InternalLogger.Debug($"Traversing path: {currentPath}"); + var lastnode = currentNode; + var node2 = currentNode[currentPath]; + + if (node2 != null) + { + currentNode = node2; + } + else + { + InternalLogger.Warn($"Could not find node at path: {currentPath} in tree {lastnode.id}"); + // log what nodes are available + InternalLogger.Warn($"Available nodes: {string.Join(", ", lastnode.nodes.Select(x => x.id))}"); + break; + } + } + + return currentNode; + } + #endregion } \ No newline at end of file