Skip to content

Commit

Permalink
feat: Workbench Organization (#486)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MrPurple6411 authored Oct 25, 2023
1 parent 86412ec commit e992abe
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 80 deletions.
8 changes: 4 additions & 4 deletions Nautilus/Crafting/ModCraftTreeRoot.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Nautilus.Handlers;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
196 changes: 120 additions & 76 deletions Nautilus/Patchers/CraftTreePatcher.cs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,18 +18,72 @@ internal class CraftTreePatcher
internal static List<Node> NodesToRemove = new();
internal static List<CraftingNode> CraftingNodes = new();
internal static List<TabNode> TabNodes = new();
private const string FallbackTabNode = "Modded";
private const string VanillaRoot = "Vanilla";

#endregion

#region Patches

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<CraftNode>();
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)
Expand Down Expand Up @@ -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<TabNode> 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)
});
}
}
Expand All @@ -177,47 +219,33 @@ private static void PatchNodes(ref CraftNode nodes, List<CraftingNode> 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<Node> nodesToRemove, CraftTree.Type scheme)
private static void RemoveNodes(ref CraftNode nodes, ref List<Node> nodesToRemove, CraftTree.Type scheme)
{
var safelist = new List<Node>(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)
Expand All @@ -232,37 +260,53 @@ private static void RemoveNodes(ref CraftNode nodes, List<Node> 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
}

0 comments on commit e992abe

Please sign in to comment.