#region using System.IO; using System.Xml; using NewHorizons.External.Modules; using NewHorizons.Handlers; using OWML.Common; using UnityEngine; #endregion namespace NewHorizons.Builder.Props { public static class DialogueBuilder { public static void Make(GameObject go, Sector sector, PropModule.DialogueInfo info, IModBehaviour mod) { // In stock I think they disable dialogue stuff with conditions // Here we just don't make it at all if (info.blockAfterPersistentCondition != null && PlayerData._currentGameSave.GetPersistentCondition(info.blockAfterPersistentCondition)) return; var dialogue = MakeConversationZone(go, sector, info, mod.ModHelper); if (info.remoteTriggerPosition != null || info.remoteTriggerRadius != 0) MakeRemoteDialogueTrigger(go, sector, info, dialogue); // Make the character look at the player // Useful for dialogue replacement if (!string.IsNullOrEmpty(info.pathToAnimController)) MakePlayerTrackingZone(go, dialogue, info); } public static void MakeRemoteDialogueTrigger(GameObject planetGO, Sector sector, PropModule.DialogueInfo info, CharacterDialogueTree dialogue) { var conversationTrigger = new GameObject("ConversationTrigger"); conversationTrigger.SetActive(false); var remoteDialogueTrigger = conversationTrigger.AddComponent(); var sphereCollider = conversationTrigger.AddComponent(); conversationTrigger.AddComponent(); remoteDialogueTrigger._listDialogues = new[] { new RemoteDialogueTrigger.RemoteDialogueCondition { priority = 1, dialogue = dialogue, prereqConditionType = RemoteDialogueTrigger.MultiConditionType.AND, prereqConditions = new string[] { }, onTriggerEnterConditions = new string[] { } } }; remoteDialogueTrigger._activatedDialogues = new bool[1]; remoteDialogueTrigger._deactivateTriggerPostConversation = true; sphereCollider.radius = info.remoteTriggerRadius == 0 ? info.radius : info.remoteTriggerRadius; conversationTrigger.transform.parent = sector?.transform ?? planetGO.transform; conversationTrigger.transform.position = planetGO.transform.TransformPoint(info.remoteTriggerPosition ?? info.position); conversationTrigger.SetActive(true); } public static CharacterDialogueTree MakeConversationZone(GameObject planetGO, Sector sector, PropModule.DialogueInfo info, IModHelper mod) { var conversationZone = new GameObject("ConversationZone"); conversationZone.SetActive(false); conversationZone.layer = LayerMask.NameToLayer("Interactible"); var sphere = conversationZone.AddComponent(); sphere.radius = info.radius; sphere.isTrigger = true; var owCollider = conversationZone.AddComponent(); var interact = conversationZone.AddComponent(); if (info.radius <= 0) { sphere.enabled = false; owCollider.enabled = false; interact.enabled = false; } var dialogueTree = conversationZone.AddComponent(); var xml = File.ReadAllText(mod.Manifest.ModFolderPath + info.xmlFile); var text = new TextAsset(xml); dialogueTree.SetTextXml(text); AddTranslation(xml); conversationZone.transform.parent = sector?.transform ?? planetGO.transform; conversationZone.transform.position = planetGO.transform.TransformPoint(info.position); conversationZone.SetActive(true); return dialogueTree; } public static void MakePlayerTrackingZone(GameObject go, CharacterDialogueTree dialogue, PropModule.DialogueInfo info) { var character = go.transform.Find(info.pathToAnimController); // At most one of these should ever not be null var nomaiController = character.GetComponent(); var controller = character.GetComponent(); var lookOnlyWhenTalking = info.lookAtRadius <= 0; // To have them look when you start talking if (controller != null) { controller._dialogueTree = dialogue; controller.lookOnlyWhenTalking = lookOnlyWhenTalking; } else if (nomaiController != null) { if (lookOnlyWhenTalking) { dialogue.OnStartConversation += nomaiController.StartWatchingPlayer; dialogue.OnEndConversation += nomaiController.StopWatchingPlayer; } } if (info.lookAtRadius > 0) { var playerTrackingZone = new GameObject("PlayerTrackingZone"); playerTrackingZone.SetActive(false); playerTrackingZone.layer = LayerMask.NameToLayer("BasicEffectVolume"); playerTrackingZone.SetActive(false); var sphereCollider = playerTrackingZone.AddComponent(); sphereCollider.radius = info.lookAtRadius; sphereCollider.isTrigger = true; playerTrackingZone.AddComponent(); var triggerVolume = playerTrackingZone.AddComponent(); if (controller) { // Since the Awake method is CharacterAnimController was already called if (controller.playerTrackingZone) { controller.playerTrackingZone.OnEntry -= controller.OnZoneEntry; controller.playerTrackingZone.OnExit -= controller.OnZoneExit; } // Set it to use the new zone controller.playerTrackingZone = triggerVolume; triggerVolume.OnEntry += controller.OnZoneEntry; triggerVolume.OnExit += controller.OnZoneExit; } // Simpler for the Nomai else if (nomaiController) { triggerVolume.OnEntry += _ => nomaiController.StartWatchingPlayer(); triggerVolume.OnExit += _ => nomaiController.StopWatchingPlayer(); } // No controller playerTrackingZone.transform.parent = dialogue.gameObject.transform; playerTrackingZone.transform.localPosition = Vector3.zero; playerTrackingZone.SetActive(true); } } private static void AddTranslation(string xml) { var xmlDocument = new XmlDocument(); xmlDocument.LoadXml(xml); var xmlNode = xmlDocument.SelectSingleNode("DialogueTree"); var xmlNodeList = xmlNode.SelectNodes("DialogueNode"); var characterName = xmlNode.SelectSingleNode("NameField").InnerText; TranslationHandler.AddDialogue(characterName); foreach (var obj in xmlNodeList) { var xmlNode2 = (XmlNode) obj; var name = xmlNode2.SelectSingleNode("Name").InnerText; var xmlText = xmlNode2.SelectNodes("Dialogue/Page"); foreach (var Page in xmlText) { var pageData = (XmlNode) Page; var text = pageData.InnerText; TranslationHandler.AddDialogue(text, name); } xmlText = xmlNode2.SelectNodes("DialogueOptionsList/DialogueOption/Text"); foreach (var Page in xmlText) { var pageData = (XmlNode) Page; var text = pageData.InnerText; TranslationHandler.AddDialogue(text, characterName, name); } } } } }