diff --git a/NewHorizons/Builder/Props/DetailBuilder.cs b/NewHorizons/Builder/Props/DetailBuilder.cs
index 6a05cb090..65a37584c 100644
--- a/NewHorizons/Builder/Props/DetailBuilder.cs
+++ b/NewHorizons/Builder/Props/DetailBuilder.cs
@@ -156,7 +156,7 @@ public static GameObject Make(GameObject go, Sector sector, IModBehaviour mod, G
// If they're adding dialogue we have to manually register the xml text
if (isFromAssetBundle && component is CharacterDialogueTree dialogue)
{
- DialogueBuilder.AddTranslation(dialogue._xmlCharacterDialogueAsset.text, null);
+ DialogueBuilder.HandleUnityCreatedDialogue(dialogue);
}
FixComponent(component, go, detail.ignoreSun);
diff --git a/NewHorizons/Builder/Props/DialogueBuilder.cs b/NewHorizons/Builder/Props/DialogueBuilder.cs
index 429626e55..89d70da7a 100644
--- a/NewHorizons/Builder/Props/DialogueBuilder.cs
+++ b/NewHorizons/Builder/Props/DialogueBuilder.cs
@@ -5,10 +5,14 @@
using NewHorizons.Utility.OuterWilds;
using NewHorizons.Utility.OWML;
using OWML.Common;
+using System;
using System.Collections.Generic;
+using System.Data.SqlTypes;
using System.IO;
using System.Xml;
+using System.Xml.Linq;
using UnityEngine;
+using UnityEngine.XR;
namespace NewHorizons.Builder.Props
{
@@ -102,7 +106,11 @@ private static CharacterDialogueTree AddToExistingDialogue(DialogueInfo info, st
// We just have to merge the dialogue options
var dialogueOptions = newDialogueNode.GetChildNode("DialogueOptionsList").GetChildNodes("DialogueOption");
var existingDialogueOptionsList = existingNode.GetChildNode("DialogueOptionsList");
- var firstNode = existingDialogueOptionsList.ChildNodes[0];
+ if (existingDialogueOptionsList == null)
+ {
+ existingDialogueOptionsList = existingDialogueDoc.CreateElement("DialogueOptionsList");
+ existingNode.AppendChild(existingDialogueOptionsList);
+ }
foreach (XmlNode node in dialogueOptions)
{
var importedNode = existingDialogueOptionsList.OwnerDocument.ImportNode(node, true);
@@ -118,6 +126,10 @@ private static CharacterDialogueTree AddToExistingDialogue(DialogueInfo info, st
}
}
+ // Character name is required for adding translations, something to do with how OW prefixes its dialogue
+ var characterName = existingDialogueTree.SelectSingleNode("NameField").InnerText;
+ AddTranslation(additionalDialogueDoc.GetChildNode("DialogueTree"), characterName);
+
var newTextAsset = new TextAsset(existingDialogueDoc.OuterXml)
{
name = existingDialogue._xmlCharacterDialogueAsset.name
@@ -125,13 +137,84 @@ private static CharacterDialogueTree AddToExistingDialogue(DialogueInfo info, st
existingDialogue.SetTextXml(newTextAsset);
- // Chracter name is required for adding translations, something to do with how OW prefixes its dialogue
- var characterName = existingDialogueTree.SelectSingleNode("NameField").InnerText;
- AddTranslation(xml, characterName);
+ FixDialogueNextFrame(existingDialogue);
return existingDialogue;
}
+ private static void FixDialogueNextFrame(CharacterDialogueTree characterDialogueTree)
+ {
+ Delay.FireOnNextUpdate(() =>
+ {
+ var rawText = characterDialogueTree._xmlCharacterDialogueAsset.text;
+
+ var doc = new XmlDocument();
+ doc.LoadXml(rawText);
+ var dialogueTree = doc.DocumentElement.SelectSingleNode("//DialogueTree");
+
+ DoDialogueOptionsListReplacement(dialogueTree);
+
+ var newTextAsset = new TextAsset(doc.OuterXml)
+ {
+ name = characterDialogueTree._xmlCharacterDialogueAsset.name
+ };
+
+ characterDialogueTree.SetTextXml(newTextAsset);
+ });
+ }
+
+ ///
+ /// Always call this after adding translations, else it won't update them properly
+ ///
+ ///
+ private static void DoDialogueOptionsListReplacement(XmlNode dialogueTree)
+ {
+ var optionsListsByName = new Dictionary();
+ var dialogueNodes = dialogueTree.GetChildNodes("DialogueNode");
+ foreach (XmlNode dialogueNode in dialogueNodes)
+ {
+ var optionsList = dialogueNode.GetChildNode("DialogueOptionsList");
+ if (optionsList != null)
+ {
+ var name = dialogueNode.GetChildNode("Name").InnerText;
+ optionsListsByName[name] = optionsList;
+ }
+ }
+ foreach (var (name, optionsList) in optionsListsByName)
+ {
+ var replacement = optionsList.GetChildNode("ReuseDialogueOptionsListFrom");
+ if (replacement != null)
+ {
+ var replacementName = replacement.InnerText;
+ if (optionsListsByName.TryGetValue(replacementName, out var replacementOptionsList))
+ {
+ if (replacementOptionsList.GetChildNode("ReuseDialogueOptionsListFrom") != null)
+ {
+ NHLogger.LogError($"Can not target a node with ReuseDialogueOptionsListFrom that also reuses options when making dialogue. Node {name} cannot reuse the list from {replacement.InnerText}");
+ }
+ var dialogueNode = optionsList.ParentNode;
+ dialogueNode.RemoveChild(optionsList);
+ dialogueNode.AppendChild(replacementOptionsList.Clone());
+
+ // Have to manually fix the translations here
+ var characterName = dialogueTree.SelectSingleNode("NameField").InnerText;
+
+ var xmlText = replacementOptionsList.SelectNodes("DialogueOption/Text");
+ foreach (object option in xmlText)
+ {
+ var optionData = (XmlNode)option;
+ var text = optionData.InnerText.Trim();
+ TranslationHandler.ReuseDialogueTranslation(text, new string[] { characterName, replacementName }, new string[] { characterName, name });
+ }
+ }
+ else
+ {
+ NHLogger.LogError($"Could not reuse dialogue options list from node with Name {replacement.InnerText} to node with Name {name}");
+ }
+ }
+ }
+ }
+
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);
@@ -187,13 +270,19 @@ private static CharacterDialogueTree MakeConversationZone(GameObject planetGO, S
var dialogueTree = conversationZone.AddComponent();
+ var dialogueDoc = new XmlDocument();
+ dialogueDoc.LoadXml(xml);
+ var xmlNode = dialogueDoc.SelectSingleNode("DialogueTree");
+ AddTranslation(xmlNode);
+
+ xml = xmlNode.OuterXml;
+
var text = new TextAsset(xml)
{
// Text assets need a name to be used with VoiceMod
name = dialogueName
};
dialogueTree.SetTextXml(text);
- AddTranslation(xml);
switch (info.flashlightToggle)
{
@@ -214,6 +303,8 @@ private static CharacterDialogueTree MakeConversationZone(GameObject planetGO, S
conversationZone.SetActive(true);
+ FixDialogueNextFrame(dialogueTree);
+
return dialogueTree;
}
@@ -378,11 +469,17 @@ private static void MakePlayerTrackingZone(GameObject go, CharacterDialogueTree
}
}
+ [Obsolete("Pass in the DialogueTree XmlNode instead, this is still here because Pikpik was using it in EOTP")]
public static void AddTranslation(string xml, string characterName = null)
{
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
var xmlNode = xmlDocument.SelectSingleNode("DialogueTree");
+ AddTranslation(xmlNode, characterName);
+ }
+
+ public static void AddTranslation(XmlNode xmlNode, string characterName = null)
+ {
var xmlNodeList = xmlNode.SelectNodes("DialogueNode");
// When adding dialogue to existing stuff, we have to pass in the character name
@@ -417,5 +514,21 @@ public static void AddTranslation(string xml, string characterName = null)
}
}
}
+
+ public static void HandleUnityCreatedDialogue(CharacterDialogueTree dialogue)
+ {
+ var text = dialogue._xmlCharacterDialogueAsset.text;
+ var dialogueDoc = new XmlDocument();
+ dialogueDoc.LoadXml(text);
+ var xmlNode = dialogueDoc.SelectSingleNode("DialogueTree");
+ AddTranslation(xmlNode, null);
+ var newTextAsset = new TextAsset(dialogueDoc.OuterXml)
+ {
+ name = dialogue._xmlCharacterDialogueAsset.name
+ };
+ dialogue.SetTextXml(newTextAsset);
+
+ FixDialogueNextFrame(dialogue);
+ }
}
}
diff --git a/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs b/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs
index 25d135c16..1eb440d9a 100644
--- a/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs
+++ b/NewHorizons/Components/Fixers/PlayerShipAtmosphereDetectorFix.cs
@@ -14,7 +14,11 @@ internal class PlayerShipAtmosphereDetectorFix : MonoBehaviour
public void Start()
{
_fluidDetector = Locator.GetPlayerCameraDetector().GetComponent();
- _shipAtmosphereVolume = Locator.GetShipBody().transform.Find("Volumes/ShipAtmosphereVolume").GetComponent();
+ _shipAtmosphereVolume = Locator.GetShipBody()?.transform?.Find("Volumes/ShipAtmosphereVolume")?.GetComponent();
+ if (_shipAtmosphereVolume == null)
+ {
+ Destroy(this);
+ }
}
public void Update()
diff --git a/NewHorizons/Components/ShipLog/ShipLogStarChartMode.cs b/NewHorizons/Components/ShipLog/ShipLogStarChartMode.cs
index a1d466eb5..57d48c35d 100644
--- a/NewHorizons/Components/ShipLog/ShipLogStarChartMode.cs
+++ b/NewHorizons/Components/ShipLog/ShipLogStarChartMode.cs
@@ -282,7 +282,7 @@ private void SetWarpTarget(ShipLogEntryCard shipLogEntryCard)
var name = UniqueIDToName(shipLogEntryCard.name);
- var warpNotificationDataText = TranslationHandler.GetTranslation("WARP_LOCKED", TranslationHandler.TextType.UI).Replace("{0}", name.ToUpper());
+ var warpNotificationDataText = TranslationHandler.GetTranslation("WARP_LOCKED", TranslationHandler.TextType.UI).Replace("{0}", name.ToUpperFixed());
_warpNotificationData = new NotificationData(warpNotificationDataText);
NotificationManager.SharedInstance.PostNotification(_warpNotificationData, true);
diff --git a/NewHorizons/Handlers/TranslationHandler.cs b/NewHorizons/Handlers/TranslationHandler.cs
index c2f49ebf8..43f787a05 100644
--- a/NewHorizons/Handlers/TranslationHandler.cs
+++ b/NewHorizons/Handlers/TranslationHandler.cs
@@ -173,6 +173,26 @@ public static void AddDialogue(string rawText, bool trimRawTextForKey = false, p
TextTranslation.Get().m_table.theTable[key] = value;
}
+ ///
+ /// Two dialogue nodes might share indentical text but they will have different prefixes. Still, we want to reuse that old text.
+ ///
+ ///
+ ///
+ ///
+ public static void ReuseDialogueTranslation(string rawText, string[] oldPrefixes, string[] newPrefixes)
+ {
+ var key = string.Join(string.Empty, newPrefixes) + rawText;
+ var existingKey = string.Join(string.Empty, oldPrefixes) + rawText;
+ if (TextTranslation.Get().m_table.theTable.TryGetValue(existingKey, out var existingTranslation))
+ {
+ TextTranslation.Get().m_table.theTable[key] = existingTranslation;
+ }
+ else
+ {
+ NHLogger.LogWarning($"Couldn't find translation key {existingKey}");
+ }
+ }
+
public static void AddShipLog(string rawText, params string[] rawPreText)
{
var key = string.Join(string.Empty, rawPreText) + rawText;
@@ -189,7 +209,7 @@ public static int AddUI(string rawText)
{
var uiTable = TextTranslation.Get().m_table.theUITable;
- var text = GetTranslation(rawText, TextType.UI).ToUpper();
+ var text = GetTranslation(rawText, TextType.UI).ToUpperFixed();
var key = uiTable.Keys.Max() + 1;
try
diff --git a/NewHorizons/Patches/SignalPatches/AudioSignalPatches.cs b/NewHorizons/Patches/SignalPatches/AudioSignalPatches.cs
index 9f1aacfe3..58beba598 100644
--- a/NewHorizons/Patches/SignalPatches/AudioSignalPatches.cs
+++ b/NewHorizons/Patches/SignalPatches/AudioSignalPatches.cs
@@ -2,6 +2,7 @@
using NewHorizons.Builder.Props.Audio;
using NewHorizons.External;
using NewHorizons.Handlers;
+using NewHorizons.Utility;
using System;
using UnityEngine;
@@ -17,7 +18,7 @@ public static bool AudioSignal_SignalNameToString(SignalName name, ref string __
var customSignalName = SignalBuilder.GetCustomSignalName(name);
if (!string.IsNullOrEmpty(customSignalName))
{
- __result = TranslationHandler.GetTranslation(customSignalName, TranslationHandler.TextType.UI, false).ToUpper();
+ __result = TranslationHandler.GetTranslation(customSignalName, TranslationHandler.TextType.UI, false).ToUpperFixed();
return false;
}
return true;
@@ -68,7 +69,7 @@ public static bool AudioSignal_FrequencyToString(SignalFrequency frequency, ref
var customName = SignalBuilder.GetCustomFrequencyName(frequency);
if (!string.IsNullOrEmpty(customName))
{
- if (NewHorizonsData.KnowsFrequency(customName)) __result = TranslationHandler.GetTranslation(customName, TranslationHandler.TextType.UI, false).ToUpper();
+ if (NewHorizonsData.KnowsFrequency(customName)) __result = TranslationHandler.GetTranslation(customName, TranslationHandler.TextType.UI, false).ToUpperFixed();
else __result = UITextLibrary.GetString(UITextType.SignalFreqUnidentified);
return false;
}
diff --git a/NewHorizons/Utility/DebugTools/DebugReload.cs b/NewHorizons/Utility/DebugTools/DebugReload.cs
index cd067d986..c40d76b8a 100644
--- a/NewHorizons/Utility/DebugTools/DebugReload.cs
+++ b/NewHorizons/Utility/DebugTools/DebugReload.cs
@@ -15,7 +15,7 @@ public static class DebugReload
public static void InitializePauseMenu(IPauseMenuManager pauseMenu)
{
- _reloadButton = pauseMenu.MakeSimpleButton(TranslationHandler.GetTranslation("Reload Configs", TranslationHandler.TextType.UI).ToUpper(), 3, true);
+ _reloadButton = pauseMenu.MakeSimpleButton(TranslationHandler.GetTranslation("Reload Configs", TranslationHandler.TextType.UI).ToUpperFixed(), 3, true);
_reloadButton.OnSubmitAction += ReloadConfigs;
UpdateReloadButton();
}
diff --git a/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs b/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs
index 92e77893c..882b9cee5 100644
--- a/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs
+++ b/NewHorizons/Utility/DebugTools/Menu/DebugMenu.cs
@@ -89,7 +89,7 @@ private void OnChangeStarSystem(string _)
public static void InitializePauseMenu(IPauseMenuManager pauseMenu)
{
- pauseMenuButton = pauseMenu.MakeSimpleButton(TranslationHandler.GetTranslation("Toggle Dev Tools Menu", TranslationHandler.TextType.UI).ToUpper(), 3, true);
+ pauseMenuButton = pauseMenu.MakeSimpleButton(TranslationHandler.GetTranslation("Toggle Dev Tools Menu", TranslationHandler.TextType.UI).ToUpperFixed(), 3, true);
_instance?.InitMenu();
}
diff --git a/NewHorizons/Utility/NewHorizonExtensions.cs b/NewHorizons/Utility/NewHorizonExtensions.cs
index c34182e18..3ba29885c 100644
--- a/NewHorizons/Utility/NewHorizonExtensions.cs
+++ b/NewHorizons/Utility/NewHorizonExtensions.cs
@@ -7,6 +7,7 @@
using OWML.Utils;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -69,6 +70,70 @@ public static float GetFalloffExponent(this GravityVolume gv)
return 0;
}
+ public static string ToLanguageName(this TextTranslation.Language language)
+ {
+ switch (language)
+ {
+ case TextTranslation.Language.UNKNOWN:
+ case TextTranslation.Language.TOTAL:
+ case TextTranslation.Language.ENGLISH:
+ return "English";
+ case TextTranslation.Language.SPANISH_LA:
+ return "Spanish";
+ case TextTranslation.Language.GERMAN:
+ return "German";
+ case TextTranslation.Language.FRENCH:
+ return "French";
+ case TextTranslation.Language.ITALIAN:
+ return "Italian";
+ case TextTranslation.Language.POLISH:
+ return "Polish";
+ case TextTranslation.Language.PORTUGUESE_BR:
+ return "Portuguese (Brazil)";
+ case TextTranslation.Language.JAPANESE:
+ return "Japanese";
+ case TextTranslation.Language.RUSSIAN:
+ return "Russian";
+ case TextTranslation.Language.CHINESE_SIMPLE:
+ return "Chinese (Simplified)";
+ case TextTranslation.Language.KOREAN:
+ return "Korean";
+ case TextTranslation.Language.TURKISH:
+ return "Turkish";
+ default:
+ return language.ToString().Replace("_", " ").ToTitleCase();
+ }
+ }
+
+ public static CultureInfo ToCultureInfo(this TextTranslation.Language language)
+ {
+ return CultureInfo.GetCultures(CultureTypes.AllCultures).FirstOrDefault(culture =>
+ {
+ var name = language.ToLanguageName();
+ return culture.EnglishName == name || culture.NativeName.ToTitleCase() == name;
+ }) ?? CultureInfo.CurrentCulture;
+ }
+
+ public static string ToUpperFixed(this string str)
+ {
+ return str.ToUpper(TextTranslation.Get().m_language);
+ }
+
+ public static string ToLowerFixed(this string str)
+ {
+ return str.ToLower(TextTranslation.Get().m_language);
+ }
+
+ public static string ToUpper(this string str, TextTranslation.Language language)
+ {
+ return str.ToUpper(language.ToCultureInfo());
+ }
+
+ public static string ToLower(this string str, TextTranslation.Language language)
+ {
+ return str.ToLower(language.ToCultureInfo());
+ }
+
public static string ToCamelCase(this string str)
{
StringBuilder strBuilder = new StringBuilder(str);
@@ -78,7 +143,7 @@ public static string ToCamelCase(this string str)
public static string ToTitleCase(this string str)
{
- StringBuilder strBuilder = new StringBuilder(str);
+ StringBuilder strBuilder = new StringBuilder(str.ToLowerInvariant());
strBuilder[0] = strBuilder[0].ToString().ToUpperInvariant().ToCharArray()[0];
return strBuilder.ToString();
}
@@ -348,7 +413,7 @@ public static List GetChildNodes(this XmlNode parentNode, string tagNam
public static XmlNode GetChildNode(this XmlNode parentNode, string tagName)
{
- return parentNode.ChildNodes.Cast().First(node => node.LocalName == tagName);
+ return parentNode.ChildNodes.Cast().FirstOrDefault(node => node.LocalName == tagName);
}
public static string TruncateWhitespaceAndToLower(this string text)
diff --git a/NewHorizons/manifest.json b/NewHorizons/manifest.json
index 74ce1e6ee..c57ae22d4 100644
--- a/NewHorizons/manifest.json
+++ b/NewHorizons/manifest.json
@@ -4,7 +4,7 @@
"author": "xen, Bwc9876, JohnCorby, MegaPiggy, Clay, Trifid, and friends",
"name": "New Horizons",
"uniqueName": "xen.NewHorizons",
- "version": "1.20.1",
+ "version": "1.20.2",
"owmlVersion": "2.10.3",
"dependencies": [ "JohnCorby.VanillaFix", "xen.CommonCameraUtility", "dgarro.CustomShipLogModes" ],
"conflicts": [ "PacificEngine.OW_CommonResources" ],