diff --git a/NewHorizons/Builder/Props/DialogueBuilder.cs b/NewHorizons/Builder/Props/DialogueBuilder.cs index c2068b1c..7afae72f 100644 --- a/NewHorizons/Builder/Props/DialogueBuilder.cs +++ b/NewHorizons/Builder/Props/DialogueBuilder.cs @@ -1,3 +1,4 @@ +using Delaunay; using NewHorizons.Components.Props; using NewHorizons.External.Modules.Props.Dialogue; using NewHorizons.Handlers; @@ -5,7 +6,9 @@ using NewHorizons.Utility; using NewHorizons.Utility.OuterWilds; using NewHorizons.Utility.OWML; using OWML.Common; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Xml; using System.Xml.Linq; using UnityEngine; @@ -25,6 +28,18 @@ namespace NewHorizons.Builder.Props // Create dialogue directly from xml string instead of loading it from a file public static (CharacterDialogueTree, RemoteDialogueTrigger) Make(GameObject go, Sector sector, DialogueInfo info, string xml, string dialogueName) { + if (string.IsNullOrEmpty(info.pathToExistingDialogue)) + { + return MakeNewDialogue(go, sector, info, xml, dialogueName); + } + else + { + return (AddToExistingDialogue(info, xml), null); + } + } + + private static (CharacterDialogueTree, RemoteDialogueTrigger) MakeNewDialogue(GameObject go, Sector sector, DialogueInfo info, string xml, string dialogueName) + { NHLogger.LogVerbose($"[DIALOGUE] Created a new character dialogue [{info.rename}] on [{info.parentPath}]"); // In stock I think they disable dialogue stuff with conditions @@ -54,6 +69,68 @@ namespace NewHorizons.Builder.Props return (dialogue, remoteTrigger); } + private static CharacterDialogueTree AddToExistingDialogue(DialogueInfo info, string xml) + { + AddTranslation(xml); + + var existingDialogue = SearchUtilities.Find(info.pathToExistingDialogue)?.GetComponent(); + + if (existingDialogue == null) + { + NHLogger.LogError($"Couldn't find dialogue at {info.pathToExistingDialogue}!"); + return null; + } + + var existingText = existingDialogue._xmlCharacterDialogueAsset.text; + + var existingDialogueDoc = new XmlDocument(); + existingDialogueDoc.LoadXml(existingText); + var existingDialogueTree = existingDialogueDoc.DocumentElement.SelectSingleNode("//DialogueTree"); + + var existingDialogueNodesByName = new Dictionary(); + foreach (XmlNode existingDialogueNode in existingDialogueTree.GetChildNodes("DialogueNode")) + { + var name = existingDialogueNode.GetChildNode("Name").InnerText; + existingDialogueNodesByName[name] = existingDialogueNode; + } + + var additionalDialogueDoc = new XmlDocument(); + additionalDialogueDoc.LoadXml(xml); + var newDialogueNodes = additionalDialogueDoc.DocumentElement.SelectSingleNode("//DialogueTree").GetChildNodes("DialogueNode"); + + foreach (XmlNode newDialogueNode in newDialogueNodes) + { + var name = newDialogueNode.GetChildNode("Name").InnerText; + + if (existingDialogueNodesByName.TryGetValue(name, out var existingNode)) + { + // We just have to merge the dialogue options + var dialogueOptions = newDialogueNode.GetChildNode("DialogueOptionsList").GetChildNodes("DialogueOption"); + var existingDialogueOptionsList = existingNode.GetChildNode("DialogueOptionsList"); + foreach (XmlNode node in dialogueOptions) + { + var importedNode = existingDialogueOptionsList.OwnerDocument.ImportNode(node, true); + existingDialogueOptionsList.AppendChild(importedNode); + } + } + else + { + // We add the new dialogue node to the existing dialogue + var importedNode = existingDialogueTree.OwnerDocument.ImportNode(newDialogueNode, true); + existingDialogueTree.AppendChild(importedNode); + } + } + + var newTextAsset = new TextAsset(existingDialogueDoc.OuterXml) + { + name = existingDialogue._xmlCharacterDialogueAsset.name + }; + + existingDialogue.SetTextXml(newTextAsset); + + return existingDialogue; + } + private static RemoteDialogueTrigger MakeRemoteDialogueTrigger(GameObject planetGO, Sector sector, DialogueInfo info, CharacterDialogueTree dialogue) { var conversationTrigger = GeneralPropBuilder.MakeNew("ConversationTrigger", planetGO, sector, info.remoteTrigger, defaultPosition: info.position, defaultParentPath: info.pathToAnimController); diff --git a/NewHorizons/External/Modules/Props/Dialogue/DialogueInfo.cs b/NewHorizons/External/Modules/Props/Dialogue/DialogueInfo.cs index 06207150..b2687c3c 100644 --- a/NewHorizons/External/Modules/Props/Dialogue/DialogueInfo.cs +++ b/NewHorizons/External/Modules/Props/Dialogue/DialogueInfo.cs @@ -31,6 +31,11 @@ namespace NewHorizons.External.Modules.Props.Dialogue /// public string pathToAnimController; + /// + /// If this dialogue is adding to existing character dialogue, put a path to the game object with the dialogue on it here + /// + public string pathToExistingDialogue; + /// /// Radius of the spherical collision volume where you get the "talk to" prompt when looking at. If you use a /// remoteTrigger, you can set this to 0 to make the dialogue only trigger remotely. diff --git a/NewHorizons/Utility/NewHorizonExtensions.cs b/NewHorizons/Utility/NewHorizonExtensions.cs index dd907c45..6e349e5d 100644 --- a/NewHorizons/Utility/NewHorizonExtensions.cs +++ b/NewHorizons/Utility/NewHorizonExtensions.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.Xml; using UnityEngine; using static NewHorizons.External.Modules.ParticleFieldModule; using NomaiCoordinates = NewHorizons.External.Configs.StarSystemConfig.NomaiCoordinates; @@ -24,7 +25,7 @@ namespace NewHorizons.Utility { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore, - Formatting = Formatting.Indented, + Formatting = Newtonsoft.Json.Formatting.Indented, }; private static StringBuilder stringBuilder = new StringBuilder(); @@ -36,7 +37,7 @@ namespace NewHorizons.Utility { using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter) { - Formatting = Formatting.Indented, + Formatting = Newtonsoft.Json.Formatting.Indented, IndentChar = '\t', Indentation = 1 }) @@ -339,5 +340,15 @@ namespace NewHorizons.Utility } } } + + public static List GetChildNodes(this XmlNode parentNode, string tagName) + { + return parentNode.ChildNodes.Cast().Where(node => node.LocalName == tagName).ToList(); + } + + public static XmlNode GetChildNode(this XmlNode parentNode, string tagName) + { + return parentNode.ChildNodes.Cast().First(node => node.LocalName == tagName); + } } }