mirror of
https://github.com/Outer-Wilds-New-Horizons/new-horizons.git
synced 2025-12-11 20:15:44 +01:00
207 lines
8.6 KiB
C#
207 lines
8.6 KiB
C#
using NewHorizons.External;
|
|
using NewHorizons.Handlers;
|
|
using OWML.Common;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Xml;
|
|
using UnityEngine;
|
|
using Logger = NewHorizons.Utility.Logger;
|
|
|
|
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 go, Sector sector, PropModule.DialogueInfo info, CharacterDialogueTree dialogue)
|
|
{
|
|
GameObject conversationTrigger = new GameObject("ConversationTrigger");
|
|
conversationTrigger.SetActive(false);
|
|
|
|
var remoteDialogueTrigger = conversationTrigger.AddComponent<RemoteDialogueTrigger>();
|
|
var sphereCollider = conversationTrigger.AddComponent<SphereCollider>();
|
|
conversationTrigger.AddComponent<OWCollider>();
|
|
|
|
remoteDialogueTrigger._listDialogues = new RemoteDialogueTrigger.RemoteDialogueCondition[]
|
|
{
|
|
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 ?? go.transform;
|
|
conversationTrigger.transform.localPosition = info.remoteTriggerPosition ?? info.position;
|
|
conversationTrigger.SetActive(true);
|
|
}
|
|
|
|
public static CharacterDialogueTree MakeConversationZone(GameObject go, Sector sector, PropModule.DialogueInfo info, IModHelper mod)
|
|
{
|
|
GameObject conversationZone = new GameObject("ConversationZone");
|
|
conversationZone.SetActive(false);
|
|
|
|
conversationZone.layer = LayerMask.NameToLayer("Interactible");
|
|
|
|
var sphere = conversationZone.AddComponent<SphereCollider>();
|
|
sphere.radius = info.radius;
|
|
sphere.isTrigger = true;
|
|
|
|
var owCollider = conversationZone.AddComponent<OWCollider>();
|
|
var interact = conversationZone.AddComponent<InteractReceiver>();
|
|
|
|
if(info.radius <= 0)
|
|
{
|
|
sphere.enabled = false;
|
|
owCollider.enabled = false;
|
|
interact.enabled = false;
|
|
}
|
|
|
|
var dialogueTree = conversationZone.AddComponent<CharacterDialogueTree>();
|
|
|
|
var xml = System.IO.File.ReadAllText(mod.Manifest.ModFolderPath + info.xmlFile);
|
|
var text = new TextAsset(xml);
|
|
|
|
dialogueTree.SetTextXml(text);
|
|
AddTranslation(xml);
|
|
|
|
conversationZone.transform.parent = sector?.transform ?? go.transform;
|
|
conversationZone.transform.localPosition = 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<SolanumAnimController>();
|
|
var controller = character.GetComponent<CharacterAnimController>();
|
|
|
|
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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: make a custom controller for basic characters to just turn them to face you
|
|
}
|
|
|
|
if (info.lookAtRadius > 0)
|
|
{
|
|
GameObject playerTrackingZone = new GameObject("PlayerTrackingZone");
|
|
playerTrackingZone.SetActive(false);
|
|
|
|
playerTrackingZone.layer = LayerMask.NameToLayer("BasicEffectVolume");
|
|
playerTrackingZone.SetActive(false);
|
|
|
|
var sphereCollider = playerTrackingZone.AddComponent<SphereCollider>();
|
|
sphereCollider.radius = info.lookAtRadius;
|
|
sphereCollider.isTrigger = true;
|
|
|
|
playerTrackingZone.AddComponent<OWCollider>();
|
|
|
|
var triggerVolume = playerTrackingZone.AddComponent<OWTriggerVolume>();
|
|
|
|
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
|
|
else
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
playerTrackingZone.transform.parent = dialogue.gameObject.transform;
|
|
playerTrackingZone.transform.localPosition = Vector3.zero;
|
|
|
|
playerTrackingZone.SetActive(true);
|
|
}
|
|
}
|
|
|
|
private static void AddTranslation(string xml)
|
|
{
|
|
XmlDocument xmlDocument = new XmlDocument();
|
|
xmlDocument.LoadXml(xml);
|
|
XmlNode xmlNode = xmlDocument.SelectSingleNode("DialogueTree");
|
|
XmlNodeList xmlNodeList = xmlNode.SelectNodes("DialogueNode");
|
|
string characterName = xmlNode.SelectSingleNode("NameField").InnerText;
|
|
TranslationHandler.AddDialogue(characterName);
|
|
|
|
foreach (object obj in xmlNodeList)
|
|
{
|
|
XmlNode xmlNode2 = (XmlNode)obj;
|
|
var name = xmlNode2.SelectSingleNode("Name").InnerText;
|
|
|
|
XmlNodeList xmlText = xmlNode2.SelectNodes("Dialogue/Page");
|
|
foreach (object Page in xmlText)
|
|
{
|
|
XmlNode pageData = (XmlNode)Page;
|
|
var text = pageData.InnerText;
|
|
TranslationHandler.AddDialogue(text, name);
|
|
}
|
|
|
|
xmlText = xmlNode2.SelectNodes("DialogueOptionsList/DialogueOption/Text");
|
|
foreach (object Page in xmlText)
|
|
{
|
|
XmlNode pageData = (XmlNode)Page;
|
|
var text = pageData.InnerText;
|
|
TranslationHandler.AddDialogue(text, characterName, name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|