using NewHorizons.External; using NewHorizons.Handlers; using NewHorizons.Utility; 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; using Random = UnityEngine.Random; namespace NewHorizons.Builder.Props { public static class NomaiTextBuilder { private static List _arcPrefabs; private static List _childArcPrefabs; private static List _ghostArcPrefabs; private static GameObject _scrollPrefab; private static GameObject _computerPrefab; private static GameObject _cairnPrefab; private static GameObject _recorderPrefab; private static void InitPrefabs() { // Just take every scroll and get the first arc var existingArcs = GameObject.FindObjectsOfType().Select(x => x?._nomaiWallText?.gameObject?.transform?.Find("Arc 1")?.gameObject).Where(x => x != null).ToArray(); _arcPrefabs = new List(); _childArcPrefabs = new List(); foreach (var existingArc in existingArcs) { if (existingArc.GetComponent().material.name.Contains("Child")) { var arc = existingArc.InstantiateInactive(); arc.name = "Arc (Child)"; _childArcPrefabs.Add(arc); } else { var arc = existingArc.InstantiateInactive(); arc.name = "Arc"; _arcPrefabs.Add(arc); } } var existingGhostArcs = GameObject.FindObjectsOfType().Select(x => x?._textLine?.gameObject).Where(x => x != null).ToArray(); _ghostArcPrefabs = new List(); foreach (var existingArc in existingGhostArcs) { var arc = existingArc.InstantiateInactive(); arc.name = "Arc"; _ghostArcPrefabs.Add(arc); } _scrollPrefab = GameObject.Find("BrittleHollow_Body/Sector_BH/Sector_NorthHemisphere/Sector_NorthPole/Sector_HangingCity/Sector_HangingCity_District2/Interactables_HangingCity_District2/Prefab_NOM_Scroll").InstantiateInactive(); _scrollPrefab.name = "Prefab_NOM_Scroll"; _computerPrefab = GameObject.Find("VolcanicMoon_Body/Sector_VM/Interactables_VM/Prefab_NOM_Computer").InstantiateInactive(); _computerPrefab.name = "Prefab_NOM_Computer"; _computerPrefab.transform.rotation = Quaternion.identity; _cairnPrefab = GameObject.Find("BrittleHollow_Body/Sector_BH/Sector_Crossroads/Interactables_Crossroads/Trailmarkers/Prefab_NOM_BH_Cairn_Arc (1)").InstantiateInactive(); _cairnPrefab.name = "Prefab_NOM_Cairn"; _cairnPrefab.transform.rotation = Quaternion.identity; _recorderPrefab = GameObject.Find("Comet_Body/Prefab_NOM_Shuttle/Sector_NomaiShuttleInterior/Interactibles_NomaiShuttleInterior/Prefab_NOM_Recorder").InstantiateInactive(); _recorderPrefab.name = "Prefab_NOM_Recorder"; _recorderPrefab.transform.rotation = Quaternion.identity; } public static void Make(GameObject go, Sector sector, PropModule.NomaiTextInfo info, IModBehaviour mod) { if (_scrollPrefab == null) InitPrefabs(); var xmlPath = System.IO.File.ReadAllText(mod.ModHelper.Manifest.ModFolderPath + info.xmlFile); if (info.type == "wall") { var nomaiWallTextObj = MakeWallText(go, sector, info, xmlPath).gameObject; nomaiWallTextObj.transform.parent = sector?.transform ?? go.transform; nomaiWallTextObj.transform.localPosition = info.position; if (info.normal != null) { // In global coordinates (normal was in local coordinates) var up = (nomaiWallTextObj.transform.position - go.transform.position).normalized; var forward = go.transform.TransformDirection(info.normal).normalized; nomaiWallTextObj.transform.up = up; nomaiWallTextObj.transform.forward = forward; } if(info.rotation != null) { nomaiWallTextObj.transform.localRotation = Quaternion.Euler(info.rotation); } nomaiWallTextObj.SetActive(true); } else if (info.type == "scroll") { var customScroll = _scrollPrefab.InstantiateInactive(); var nomaiWallText = MakeWallText(go, sector, info, xmlPath); nomaiWallText.transform.parent = customScroll.transform; nomaiWallText.transform.localPosition = Vector3.zero; nomaiWallText.transform.localRotation = Quaternion.identity; nomaiWallText._showTextOnStart = false; // Don't want to be able to translate until its in a socket nomaiWallText.GetComponent().enabled = false; nomaiWallText.gameObject.SetActive(true); var scrollItem = customScroll.GetComponent(); // Idk why this thing is always around GameObject.Destroy(customScroll.transform.Find("Arc_BH_City_Forum_2").gameObject); // This variable is the bane of my existence i dont get it scrollItem._nomaiWallText = nomaiWallText; // Because the scroll was already awake it does weird shit in Awake and makes some of the entries in this array be null scrollItem._colliders = new OWCollider[] { scrollItem.GetComponent() }; // Else when you put them down you can't pick them back up customScroll.GetComponent()._physicsRemoved = false; // Place scroll customScroll.transform.parent = sector?.transform ?? go.transform; customScroll.transform.localPosition = info.position ?? Vector3.zero; var up = customScroll.transform.localPosition.normalized; customScroll.transform.rotation = Quaternion.FromToRotation(customScroll.transform.up, up) * customScroll.transform.rotation; customScroll.SetActive(true); // Enable the collider and renderer Main.Instance.ModHelper.Events.Unity.RunWhen( () => Main.IsSystemReady, () => { Logger.Log("Fixing scroll!"); scrollItem._nomaiWallText = nomaiWallText; scrollItem.SetSector(sector); customScroll.transform.Find("Props_NOM_Scroll/Props_NOM_Scroll_Geo").GetComponent().enabled = true; customScroll.transform.Find("Props_NOM_Scroll/Props_NOM_Scroll_Collider").gameObject.SetActive(true); nomaiWallText.gameObject.GetComponent().enabled = false; customScroll.GetComponent().enabled = true; } ); } else if (info.type == "computer") { var computerObject = _computerPrefab.InstantiateInactive(); computerObject.transform.parent = sector?.transform ?? go.transform; computerObject.transform.localPosition = info?.position ?? Vector3.zero; var up = computerObject.transform.position - go.transform.position; computerObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, up) * computerObject.transform.rotation; var computer = computerObject.GetComponent(); computer.SetSector(sector); computer._dictNomaiTextData = MakeNomaiTextDict(xmlPath); computer._nomaiTextAsset = new TextAsset(xmlPath); AddTranslation(xmlPath); // Make sure the computer model is loaded OWAssetHandler.LoadObject(computerObject); sector.OnOccupantEnterSector.AddListener((x) => OWAssetHandler.LoadObject(computerObject)); computerObject.SetActive(true); } else if (info.type == "cairn") { var cairnObject = _cairnPrefab.InstantiateInactive(); cairnObject.transform.parent = sector?.transform ?? go.transform; cairnObject.transform.localPosition = info?.position ?? Vector3.zero; if (info.rotation != null) { cairnObject.transform.localRotation = Quaternion.Euler(info.rotation); } else { // By default align it to normal var up = (cairnObject.transform.position - go.transform.position).normalized; cairnObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, up) * cairnObject.transform.rotation; } // Idk do we have to set it active before finding things? cairnObject.SetActive(true); // Make it do the thing when it finishes being knocked over foreach (var rock in cairnObject.GetComponent()._rocks) { rock._returning = false; rock._owCollider.SetActivation(true); rock.enabled = false; } // So we can actually knock it over cairnObject.GetComponent().enabled = true; var nomaiWallText = cairnObject.transform.Find("Props_TH_ClutterSmall/Arc_Short").GetComponent(); nomaiWallText.SetSector(sector); nomaiWallText._dictNomaiTextData = MakeNomaiTextDict(xmlPath); nomaiWallText._nomaiTextAsset = new TextAsset(xmlPath); AddTranslation(xmlPath); // Make sure the computer model is loaded OWAssetHandler.LoadObject(cairnObject); sector.OnOccupantEnterSector.AddListener((x) => OWAssetHandler.LoadObject(cairnObject)); } else if (info.type == "recorder") { var recorderObject = _recorderPrefab.InstantiateInactive(); recorderObject.transform.parent = sector?.transform ?? go.transform; recorderObject.transform.localPosition = info?.position ?? Vector3.zero; if (info.rotation != null) { recorderObject.transform.localRotation = Quaternion.Euler(info.rotation); } else { var up = recorderObject.transform.position - go.transform.position; recorderObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, up) * recorderObject.transform.rotation; } var nomaiText = recorderObject.GetComponentInChildren(); nomaiText.SetSector(sector); nomaiText._dictNomaiTextData = MakeNomaiTextDict(xmlPath); nomaiText._nomaiTextAsset = new TextAsset(xmlPath); AddTranslation(xmlPath); // Make sure the recorder model is loaded OWAssetHandler.LoadObject(recorderObject); sector.OnOccupantEnterSector.AddListener((x) => OWAssetHandler.LoadObject(recorderObject)); recorderObject.SetActive(true); recorderObject.transform.Find("InteractSphere").gameObject.GetComponent().enabled = true; } else { Logger.LogError($"Unsupported NomaiText type {info.type}"); } } private static NomaiWallText MakeWallText(GameObject go, Sector sector, PropModule.NomaiTextInfo info, string xmlPath) { GameObject nomaiWallTextObj = new GameObject("NomaiWallText"); nomaiWallTextObj.SetActive(false); var box = nomaiWallTextObj.AddComponent(); box.center = new Vector3(-0.0643f, 1.1254f, 0f); box.size = new Vector3(6.1424f, 5.2508f, 0.5f); box.isTrigger = true; nomaiWallTextObj.AddComponent(); var nomaiWallText = nomaiWallTextObj.AddComponent(); var text = new TextAsset(xmlPath); BuildArcs(xmlPath, nomaiWallText, nomaiWallTextObj, info); AddTranslation(xmlPath); nomaiWallText._nomaiTextAsset = text; nomaiWallText.SetTextAsset(text); return nomaiWallText; } private static void BuildArcs(string xml, NomaiWallText nomaiWallText, GameObject conversationZone, PropModule.NomaiTextInfo info) { var dict = MakeNomaiTextDict(xml); nomaiWallText._dictNomaiTextData = dict; Random.InitState(info.seed); var arcsByID = new Dictionary(); if (info.arcInfo != null && info.arcInfo.Count() != dict.Values.Count()) { Logger.LogError($"Can't make NomaiWallText, arcInfo length [{info.arcInfo.Count()}] doesn't equal text entries [{dict.Values.Count()}]"); return; } var i = 0; foreach (var textData in dict.Values) { var textEntryID = textData.ID; var parentID = textData.ParentID; var parent = parentID == -1 ? null : arcsByID[parentID]; GameObject arc; var type = info.arcInfo != null ? info.arcInfo[i].type : "adult"; if (type == "child") { arc = _childArcPrefabs[Random.Range(0, _childArcPrefabs.Count())].InstantiateInactive(); } else if(type == "stranger" && _ghostArcPrefabs.Count() > 0) // It could be empty if they dont have the DLC { arc = _ghostArcPrefabs[Random.Range(0, _ghostArcPrefabs.Count())].InstantiateInactive(); } else { arc = _arcPrefabs[Random.Range(0, _arcPrefabs.Count())].InstantiateInactive(); } arc.transform.parent = conversationZone.transform; arc.GetComponent()._prebuilt = false; if (info.arcInfo != null) { var a = info.arcInfo[i]; if (a.position == null) arc.transform.localPosition = Vector3.zero; else arc.transform.localPosition = new Vector3(a.position.X, a.position.Y, 0); arc.transform.localRotation = Quaternion.Euler(0, 0, a.zRotation); } // Try auto I guess else { if (parent == null) { arc.transform.localPosition = Vector3.zero; } else { var points = parent.GetComponent().GetPoints(); var point = points[points.Count() / 2]; arc.transform.localPosition = point; arc.transform.localRotation = Quaternion.Euler(0, 0, Random.Range(0, 360)); } } arc.GetComponent().SetEntryID(textEntryID); arc.GetComponent().enabled = false; arc.name = $"Arc {++i}"; arc.SetActive(true); arcsByID.Add(textEntryID, arc); } } private static Dictionary MakeNomaiTextDict(string xmlPath) { var dict = new Dictionary(); XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(xmlPath); XmlNode rootNode = xmlDocument.SelectSingleNode("NomaiObject"); foreach (object obj in rootNode.SelectNodes("TextBlock")) { XmlNode xmlNode = (XmlNode)obj; int textEntryID = -1; int parentID = -1; XmlNode textNode = xmlNode.SelectSingleNode("Text"); XmlNode entryIDNode = xmlNode.SelectSingleNode("ID"); XmlNode parentIDNode = xmlNode.SelectSingleNode("ParentID"); if (entryIDNode != null && !int.TryParse(entryIDNode.InnerText, out textEntryID)) { Logger.LogError($"Couldn't parse int ID in [{entryIDNode?.InnerText}] for [{xmlPath}]"); textEntryID = -1; } if (parentIDNode != null && !int.TryParse(parentIDNode.InnerText, out parentID)) { Logger.LogError($"Couldn't parse int ParentID in [{parentIDNode?.InnerText}] for [{xmlPath}]"); parentID = -1; } NomaiText.NomaiTextData value = new NomaiText.NomaiTextData(textEntryID, parentID, textNode, false, NomaiText.Location.UNSPECIFIED); dict.Add(textEntryID, value); } return dict; } private static void AddTranslation(string xmlPath) { XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(xmlPath); XmlNode xmlNode = xmlDocument.SelectSingleNode("NomaiObject"); XmlNodeList xmlNodeList = xmlNode.SelectNodes("TextBlock"); foreach (object obj in xmlNodeList) { XmlNode xmlNode2 = (XmlNode)obj; var text = xmlNode2.SelectSingleNode("Text").InnerText; TranslationHandler.AddDialogue(text); } } } }