mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
Dialogue addition (#788)
## Minor features - New `pathToExistingDialogue` field for adding new DialogueNodes and DialogueOptions to existing character dialogues. Just add new responses to Slate's dialogue instead of replacing it! Will improve mod compat and simplify translating.
This commit is contained in:
commit
4a2a7636e4
@ -5,9 +5,9 @@ using NewHorizons.Utility;
|
|||||||
using NewHorizons.Utility.OuterWilds;
|
using NewHorizons.Utility.OuterWilds;
|
||||||
using NewHorizons.Utility.OWML;
|
using NewHorizons.Utility.OWML;
|
||||||
using OWML.Common;
|
using OWML.Common;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace NewHorizons.Builder.Props
|
namespace NewHorizons.Builder.Props
|
||||||
@ -24,6 +24,18 @@ namespace NewHorizons.Builder.Props
|
|||||||
|
|
||||||
// Create dialogue directly from xml string instead of loading it from a file
|
// 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)
|
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}]");
|
NHLogger.LogVerbose($"[DIALOGUE] Created a new character dialogue [{info.rename}] on [{info.parentPath}]");
|
||||||
|
|
||||||
@ -54,6 +66,68 @@ namespace NewHorizons.Builder.Props
|
|||||||
return (dialogue, remoteTrigger);
|
return (dialogue, remoteTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CharacterDialogueTree AddToExistingDialogue(DialogueInfo info, string xml)
|
||||||
|
{
|
||||||
|
AddTranslation(xml);
|
||||||
|
|
||||||
|
var existingDialogue = SearchUtilities.Find(info.pathToExistingDialogue)?.GetComponent<CharacterDialogueTree>();
|
||||||
|
|
||||||
|
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<string, XmlNode>();
|
||||||
|
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)
|
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);
|
var conversationTrigger = GeneralPropBuilder.MakeNew("ConversationTrigger", planetGO, sector, info.remoteTrigger, defaultPosition: info.position, defaultParentPath: info.pathToAnimController);
|
||||||
|
|||||||
@ -31,6 +31,11 @@ namespace NewHorizons.External.Modules.Props.Dialogue
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string pathToAnimController;
|
public string pathToAnimController;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If this dialogue is adding to existing character dialogue, put a path to the game object with the dialogue on it here
|
||||||
|
/// </summary>
|
||||||
|
public string pathToExistingDialogue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Radius of the spherical collision volume where you get the "talk to" prompt when looking at. If you use a
|
/// 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.
|
/// remoteTrigger, you can set this to 0 to make the dialogue only trigger remotely.
|
||||||
|
|||||||
@ -121,22 +121,36 @@ namespace NewHorizons.Handlers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static (string, string) FixKeyValue(string key, string value)
|
||||||
|
{
|
||||||
|
key = key.Replace("<", "<").Replace(">", ">").Replace("<![CDATA[", "").Replace("]]>", "");
|
||||||
|
value = value.Replace("<", "<").Replace(">", ">").Replace("<![CDATA[", "").Replace("]]>", "");
|
||||||
|
|
||||||
|
return (key, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static void AddDialogue(string rawText, bool trimRawTextForKey = false, params string[] rawPreText)
|
public static void AddDialogue(string rawText, bool trimRawTextForKey = false, params string[] rawPreText)
|
||||||
{
|
{
|
||||||
var key = string.Join(string.Empty, rawPreText) + (trimRawTextForKey? rawText.Trim() : rawText);
|
var key = string.Join(string.Empty, rawPreText) + (trimRawTextForKey? rawText.Trim() : rawText);
|
||||||
|
|
||||||
var text = GetTranslation(rawText, TextType.DIALOGUE);
|
var value = GetTranslation(rawText, TextType.DIALOGUE);
|
||||||
|
|
||||||
TextTranslation.Get().m_table.Insert(key, text);
|
// Manually insert directly into the dictionary, otherwise it logs errors about duplicates but we want to allow replacing
|
||||||
|
(key, value) = FixKeyValue(key, value);
|
||||||
|
|
||||||
|
TextTranslation.Get().m_table.theTable[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddShipLog(string rawText, params string[] rawPreText)
|
public static void AddShipLog(string rawText, params string[] rawPreText)
|
||||||
{
|
{
|
||||||
var key = string.Join(string.Empty, rawPreText) + rawText;
|
var key = string.Join(string.Empty, rawPreText) + rawText;
|
||||||
|
|
||||||
string text = GetTranslation(rawText, TextType.SHIPLOG);
|
string value = GetTranslation(rawText, TextType.SHIPLOG);
|
||||||
|
|
||||||
TextTranslation.Get().m_table.InsertShipLog(key, text);
|
// Manually insert directly into the dictionary, otherwise it logs errors about duplicates but we want to allow replacing
|
||||||
|
(key, value) = FixKeyValue(key, value);
|
||||||
|
|
||||||
|
TextTranslation.Get().m_table.theShipLogTable[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int AddUI(string rawText)
|
public static int AddUI(string rawText)
|
||||||
@ -154,7 +168,7 @@ namespace NewHorizons.Handlers
|
|||||||
}
|
}
|
||||||
catch (Exception) { }
|
catch (Exception) { }
|
||||||
|
|
||||||
TextTranslation.Get().m_table.Insert_UI(key, text);
|
TextTranslation.Get().m_table.theUITable[key] = text;
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1568,6 +1568,10 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "If this dialogue is meant for a character, this is the relative path from the planet to that character's\nCharacterAnimController, TravelerController, TravelerEyeController (eye of the universe), FacePlayerWhenTalking, \nHearthianRecorderEffects or SolanumAnimController.\n\nIf it's a Recorder this will also delete the existing dialogue already attached to that prop.\n\nIf none of those components are present it will add a FacePlayerWhenTalking component."
|
"description": "If this dialogue is meant for a character, this is the relative path from the planet to that character's\nCharacterAnimController, TravelerController, TravelerEyeController (eye of the universe), FacePlayerWhenTalking, \nHearthianRecorderEffects or SolanumAnimController.\n\nIf it's a Recorder this will also delete the existing dialogue already attached to that prop.\n\nIf none of those components are present it will add a FacePlayerWhenTalking component."
|
||||||
},
|
},
|
||||||
|
"pathToExistingDialogue": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "If this dialogue is adding to existing character dialogue, put a path to the game object with the dialogue on it here"
|
||||||
|
},
|
||||||
"radius": {
|
"radius": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "Radius of the spherical collision volume where you get the \"talk to\" prompt when looking at. If you use a\nremoteTrigger, you can set this to 0 to make the dialogue only trigger remotely.",
|
"description": "Radius of the spherical collision volume where you get the \"talk to\" prompt when looking at. If you use a\nremoteTrigger, you can set this to 0 to make the dialogue only trigger remotely.",
|
||||||
|
|||||||
@ -12,6 +12,7 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Xml;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using static NewHorizons.External.Modules.ParticleFieldModule;
|
using static NewHorizons.External.Modules.ParticleFieldModule;
|
||||||
using NomaiCoordinates = NewHorizons.External.Configs.StarSystemConfig.NomaiCoordinates;
|
using NomaiCoordinates = NewHorizons.External.Configs.StarSystemConfig.NomaiCoordinates;
|
||||||
@ -24,7 +25,7 @@ namespace NewHorizons.Utility
|
|||||||
{
|
{
|
||||||
NullValueHandling = NullValueHandling.Ignore,
|
NullValueHandling = NullValueHandling.Ignore,
|
||||||
DefaultValueHandling = DefaultValueHandling.Ignore,
|
DefaultValueHandling = DefaultValueHandling.Ignore,
|
||||||
Formatting = Formatting.Indented,
|
Formatting = Newtonsoft.Json.Formatting.Indented,
|
||||||
};
|
};
|
||||||
|
|
||||||
private static StringBuilder stringBuilder = new StringBuilder();
|
private static StringBuilder stringBuilder = new StringBuilder();
|
||||||
@ -36,7 +37,7 @@ namespace NewHorizons.Utility
|
|||||||
{
|
{
|
||||||
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter)
|
using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter)
|
||||||
{
|
{
|
||||||
Formatting = Formatting.Indented,
|
Formatting = Newtonsoft.Json.Formatting.Indented,
|
||||||
IndentChar = '\t',
|
IndentChar = '\t',
|
||||||
Indentation = 1
|
Indentation = 1
|
||||||
})
|
})
|
||||||
@ -339,5 +340,15 @@ namespace NewHorizons.Utility
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<XmlNode> GetChildNodes(this XmlNode parentNode, string tagName)
|
||||||
|
{
|
||||||
|
return parentNode.ChildNodes.Cast<XmlNode>().Where(node => node.LocalName == tagName).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XmlNode GetChildNode(this XmlNode parentNode, string tagName)
|
||||||
|
{
|
||||||
|
return parentNode.ChildNodes.Cast<XmlNode>().First(node => node.LocalName == tagName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user