Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workbench Organization. #486

Merged
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
205 changes: 129 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,81 @@ 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()
{
Enum.GetValues(typeof(CraftTree.Type)).Cast<CraftTree.Type>().ToList().ForEach(x =>
MrPurple6411 marked this conversation as resolved.
Show resolved Hide resolved
{
switch (x)
{
case CraftTree.Type.Workbench:
CreateVanillaTabNode(x, "Modification Station", TechType.Workbench, CraftTree.WorkbenchScheme().root);
CraftTreeHandler.AddTabNode(x, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.Workbench));
break;
case CraftTree.Type.Fabricator:
CraftTreeHandler.AddTabNode(x, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.Fabricator));
break;
case CraftTree.Type.Constructor:
CraftTreeHandler.AddTabNode(x, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.Constructor));
break;
case CraftTree.Type.SeamothUpgrades:
CraftTreeHandler.AddTabNode(x, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.BaseUpgradeConsole));
break;
case CraftTree.Type.MapRoom:
CreateVanillaTabNode(x, "Scanner Upgrades", TechType.BaseMapRoom, CraftTree.MapRoomSheme().root);
CraftTreeHandler.AddTabNode(x, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.BaseMapRoom));
break;
#if SUBNAUTICA
case CraftTree.Type.CyclopsFabricator:
CreateVanillaTabNode(x, "Cyclops Fabricator", TechType.Cyclops, CraftTree.CyclopsFabricatorScheme().root);
CraftTreeHandler.AddTabNode(x, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.Cyclops));
break;
#elif BELOWZERO
case CraftTree.Type.SeaTruckFabricator:
CraftTreeHandler.AddTabNode(x, FallbackTabNode, "Unsorted Mod Items", SpriteManager.Get(TechType.SeaTruckFabricator));
break;
#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.Cast<CraftNode>())
{
if (node.action == TreeAction.Expand)
continue;

CraftTreeHandler.RemoveNode(treeType, new[] { node.id });
removedNodes.Add(node);
}

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 +180,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 +228,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];

if (currentNode != null)
{
node = currentNode;
}
else
{
break;
}
}
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(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 +269,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
}