From 8c1032e67a14885e0d2236206baf54c2d6dd9919 Mon Sep 17 00:00:00 2001 From: FreezeDriedMangoes Date: Sat, 4 Feb 2023 14:47:58 -0500 Subject: [PATCH] reintroduced legacy NomaiTextBuilder and put new code under the name (and module) of TranslatorTextBuilder --- .../Props/{NomaiText => }/NomaiTextBuilder.cs | 252 ++---- NewHorizons/Builder/Props/PropBuildManager.cs | 17 +- NewHorizons/Builder/Props/RemoteBuilder.cs | 2 +- .../NomaiTextArcArranger.cs | 0 .../NomaiTextArcBuilder.cs | 0 .../TranslatorText/TranslatorTextBuilder.cs | 845 ++++++++++++++++++ NewHorizons/External/Modules/PropModule.cs | 8 +- NewHorizons/Main.cs | 1 + .../Utility/DebugMenu/DebugMenuNomaiText.cs | 6 +- 9 files changed, 971 insertions(+), 160 deletions(-) rename NewHorizons/Builder/Props/{NomaiText => }/NomaiTextBuilder.cs (82%) rename NewHorizons/Builder/Props/{NomaiText => TranslatorText}/NomaiTextArcArranger.cs (100%) rename NewHorizons/Builder/Props/{NomaiText => TranslatorText}/NomaiTextArcBuilder.cs (100%) create mode 100644 NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs diff --git a/NewHorizons/Builder/Props/NomaiText/NomaiTextBuilder.cs b/NewHorizons/Builder/Props/NomaiTextBuilder.cs similarity index 82% rename from NewHorizons/Builder/Props/NomaiText/NomaiTextBuilder.cs rename to NewHorizons/Builder/Props/NomaiTextBuilder.cs index d8f8e4ce..b76b04ce 100644 --- a/NewHorizons/Builder/Props/NomaiText/NomaiTextBuilder.cs +++ b/NewHorizons/Builder/Props/NomaiTextBuilder.cs @@ -11,16 +11,17 @@ using Enum = System.Enum; using Logger = NewHorizons.Utility.Logger; using Random = UnityEngine.Random; using OWML.Utils; -using Newtonsoft.Json; -using System; namespace NewHorizons.Builder.Props { + /// + /// Legacy - this class is used with the deprecated "nomaiText" module (deprecated on release of autospirals) + /// public static class NomaiTextBuilder { - private static Material _ghostArcMaterial; - private static Material _adultArcMaterial; - private static Material _childArcMaterial; + private static List _arcPrefabs; + private static List _childArcPrefabs; + private static List _ghostArcPrefabs; private static GameObject _scrollPrefab; private static GameObject _computerPrefab; private static GameObject _preCrashComputerPrefab; @@ -46,6 +47,10 @@ namespace NewHorizons.Builder.Props return conversationInfoToCorrespondingSpawnedGameObject[convo]; } + public static List GetArcPrefabs() { return _arcPrefabs; } + public static List GetChildArcPrefabs() { return _childArcPrefabs; } + public static List GetGhostArcPrefabs() { return _ghostArcPrefabs; } + private static bool _isInit; internal static void InitPrefabs() @@ -53,26 +58,42 @@ namespace NewHorizons.Builder.Props if (_isInit) return; _isInit = true; - - if (_adultArcMaterial == null) + + if (_arcPrefabs == null || _childArcPrefabs == null) { - _adultArcMaterial = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_Crossroads/Interactables_Crossroads/Trailmarkers/Prefab_NOM_BH_Cairn_Arc (2)/Props_TH_ClutterSmall/Arc_Short/Arc") - .GetComponent() - .sharedMaterial; + // 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) + .OrderBy(x => x.transform.GetPath()) // order by path so game updates dont break things + .ToArray(); + _arcPrefabs = new List(); + _childArcPrefabs = new List(); + foreach (var existingArc in existingArcs) + { + if (existingArc.GetComponent().material.name.Contains("Child")) + { + _childArcPrefabs.Add(existingArc.InstantiateInactive().Rename("Arc (Child)").DontDestroyOnLoad()); + } + else + { + _arcPrefabs.Add(existingArc.InstantiateInactive().Rename("Arc").DontDestroyOnLoad()); + } + } } - if (_childArcMaterial == null) + if (_ghostArcPrefabs == null) { - _childArcMaterial = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_OldSettlement/Fragment OldSettlement 5/Core_OldSettlement 5/Interactables_Core_OldSettlement5/Arc_BH_OldSettlement_ChildrensRhyme/Arc 1") - .GetComponent() - .sharedMaterial; - } - - if (_ghostArcMaterial == null) - { - _ghostArcMaterial = SearchUtilities.Find("RingWorld_Body/Sector_RingInterior/Sector_Zone1/Interactables_Zone1/Props_IP_ZoneSign_1/Arc_TestAlienWriting/Arc 1") - .GetComponent() - .sharedMaterial; + var existingGhostArcs = GameObject.FindObjectsOfType() + .Select(x => x?._textLine?.gameObject) + .Where(x => x != null) + .OrderBy(x => x.transform.GetPath()) // order by path so game updates dont break things + .ToArray(); + _ghostArcPrefabs = new List(); + foreach (var existingArc in existingGhostArcs) + { + _ghostArcPrefabs.Add(existingArc.InstantiateInactive().Rename("Arc (Ghost)").DontDestroyOnLoad()); + } } if (_scrollPrefab == null) _scrollPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_NorthHemisphere/Sector_NorthPole/Sector_HangingCity/Sector_HangingCity_District2/Interactables_HangingCity_District2/Prefab_NOM_Scroll").InstantiateInactive().Rename("Prefab_NOM_Scroll").DontDestroyOnLoad(); @@ -120,17 +141,17 @@ namespace NewHorizons.Builder.Props } } - public static GameObject Make(GameObject planetGO, Sector sector, PropModule.NomaiTextInfo info, NewHorizonsBody nhBody) + public static GameObject Make(GameObject planetGO, Sector sector, PropModule.NomaiTextInfo info, IModBehaviour mod) { InitPrefabs(); - var xmlPath = File.ReadAllText(Path.Combine(nhBody.Mod.ModHelper.Manifest.ModFolderPath, info.xmlFile)); + var xmlPath = File.ReadAllText(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, info.xmlFile)); switch (info.type) { case PropModule.NomaiTextInfo.NomaiTextType.Wall: { - var nomaiWallTextObj = MakeWallText(planetGO, sector, info, xmlPath, nhBody).gameObject; + var nomaiWallTextObj = MakeWallText(planetGO, sector, info, xmlPath).gameObject; if (!string.IsNullOrEmpty(info.rename)) { @@ -178,12 +199,9 @@ namespace NewHorizons.Builder.Props // In global coordinates (normal was in local coordinates) var up = (nomaiWallTextObj.transform.position - planetGO.transform.position).normalized; var forward = planetGO.transform.TransformDirection(info.normal).normalized; - - nomaiWallTextObj.transform.forward = forward; - var desiredUp = Vector3.ProjectOnPlane(up, forward); - var zRotation = Vector3.SignedAngle(nomaiWallTextObj.transform.up, desiredUp, forward); - nomaiWallTextObj.transform.RotateAround(nomaiWallTextObj.transform.position, forward, zRotation); + nomaiWallTextObj.transform.up = up; + nomaiWallTextObj.transform.forward = forward; } if (info.rotation != null) { @@ -191,8 +209,6 @@ namespace NewHorizons.Builder.Props } } - // nomaiWallTextObj.GetComponent().DrawBoundsWithDebugSpheres(); - nomaiWallTextObj.SetActive(true); conversationInfoToCorrespondingSpawnedGameObject[info] = nomaiWallTextObj; @@ -211,7 +227,7 @@ namespace NewHorizons.Builder.Props customScroll.name = _scrollPrefab.name; } - var nomaiWallText = MakeWallText(planetGO, sector, info, xmlPath, nhBody); + var nomaiWallText = MakeWallText(planetGO, sector, info, xmlPath); nomaiWallText.transform.parent = customScroll.transform; nomaiWallText.transform.localPosition = Vector3.zero; nomaiWallText.transform.localRotation = Quaternion.identity; @@ -572,7 +588,7 @@ namespace NewHorizons.Builder.Props } } - private static NomaiWallText MakeWallText(GameObject go, Sector sector, PropModule.NomaiTextInfo info, string xmlPath, NewHorizonsBody nhBody) + private static NomaiWallText MakeWallText(GameObject go, Sector sector, PropModule.NomaiTextInfo info, string xmlPath) { GameObject nomaiWallTextObj = new GameObject("NomaiWallText"); nomaiWallTextObj.SetActive(false); @@ -594,7 +610,7 @@ namespace NewHorizons.Builder.Props // Text assets need a name to be used with VoiceMod text.name = Path.GetFileNameWithoutExtension(info.xmlFile); - BuildArcs(xmlPath, nomaiWallText, nomaiWallTextObj, info, nhBody); + BuildArcs(xmlPath, nomaiWallText, nomaiWallTextObj, info); AddTranslation(xmlPath); nomaiWallText._nomaiTextAsset = text; @@ -609,30 +625,19 @@ namespace NewHorizons.Builder.Props return nomaiWallText; } - internal static void BuildArcs(string xml, NomaiWallText nomaiWallText, GameObject conversationZone, PropModule.NomaiTextInfo info, NewHorizonsBody nhBody) + internal static void BuildArcs(string xml, NomaiWallText nomaiWallText, GameObject conversationZone, PropModule.NomaiTextInfo info) { var dict = MakeNomaiTextDict(xml); nomaiWallText._dictNomaiTextData = dict; - var cacheKey = xml.GetHashCode() + " " + JsonConvert.SerializeObject(info).GetHashCode(); - RefreshArcs(nomaiWallText, conversationZone, info, nhBody, cacheKey); + RefreshArcs(nomaiWallText, conversationZone, info); } - [Serializable] - private struct ArcCacheData - { - public MMesh mesh; - public MVector3[] skeletonPoints; - public MVector3 position; - public float zRotation; - public bool mirrored; - } - - internal static void RefreshArcs(NomaiWallText nomaiWallText, GameObject conversationZone, PropModule.NomaiTextInfo info, NewHorizonsBody nhBody, string cacheKey) + internal static void RefreshArcs(NomaiWallText nomaiWallText, GameObject conversationZone, PropModule.NomaiTextInfo info) { var dict = nomaiWallText._dictNomaiTextData; - Random.InitState(info.seed == 0 ? info.xmlFile.GetHashCode() : info.seed); + Random.InitState(info.seed); var arcsByID = new Dictionary(); @@ -642,14 +647,6 @@ namespace NewHorizons.Builder.Props return; } - ArcCacheData[] cachedData = null; - if (nhBody.Cache?.ContainsKey(cacheKey) ?? false) - cachedData = nhBody.Cache.Get(cacheKey); - - var arranger = nomaiWallText.gameObject.AddComponent(); - - // Generate spiral meshes/GOs - var i = 0; foreach (var textData in dict.Values) { @@ -659,129 +656,76 @@ namespace NewHorizons.Builder.Props var parent = parentID == -1 ? null : arcsByID[parentID]; - GameObject arcReadFromCache = null; - if (cachedData != null) - { - var skeletonPoints = cachedData[i].skeletonPoints.Select(mv => (Vector3)mv).ToArray(); - arcReadFromCache = NomaiTextArcBuilder.BuildSpiralGameObject(skeletonPoints, cachedData[i].mesh); - arcReadFromCache.transform.parent = arranger.transform; - arcReadFromCache.transform.localScale = new Vector3(cachedData[i].mirrored? -1 : 1, 1, 1); - arcReadFromCache.transform.localPosition = cachedData[i].position; - arcReadFromCache.transform.localEulerAngles = new Vector3(0, 0, cachedData[i].zRotation); - } - - GameObject arc = MakeArc(arcInfo, conversationZone, parent, textEntryID, arcReadFromCache); - arc.name = $"Arc {textEntryID} - Child of {parentID}"; + GameObject arc = MakeArc(arcInfo, conversationZone, parent, textEntryID); + arc.name = $"Arc {i} - Child of {parentID}"; arcsByID.Add(textEntryID, arc); i++; } - - // no need to arrange if the cache exists - if (cachedData == null) - { - Logger.LogVerbose("Cache and/or cache entry was null, proceding with wall text arc arrangment."); - - // auto placement - - var overlapFound = true; - for (var k = 0; k < arranger.spirals.Count*2; k++) - { - overlapFound = arranger.AttemptOverlapResolution(); - if (!overlapFound) break; - for(var a = 0; a < 10; a++) arranger.FDGSimulationStep(); - } - - if (overlapFound) Logger.LogVerbose("Overlap resolution failed!"); - - // manual placement - - for (var j = 0; j < info.arcInfo?.Length; j++) - { - var arcInfo = info.arcInfo[j]; - var arc = arranger.spirals[j]; - - if (arcInfo.keepAutoPlacement) continue; - - if (arcInfo.position == null) arc.transform.localPosition = Vector3.zero; - else arc.transform.localPosition = new Vector3(arcInfo.position.x, arcInfo.position.y, 0); - - arc.transform.localRotation = Quaternion.Euler(0, 0, arcInfo.zRotation); - - if (arcInfo.mirror) arc.transform.localScale = new Vector3(-1, 1, 1); - else arc.transform.localScale = new Vector3( 1, 1, 1); - } - - // make an entry in the cache for all these spirals - - if (nhBody.Cache != null) - { - var cacheData = arranger.spirals.Select(spiralManipulator => new ArcCacheData() - { - mesh = spiralManipulator.GetComponent().sharedMesh, // TODO: create a serializable version of Mesh and pass this: spiralManipulator.GetComponent().mesh - skeletonPoints = spiralManipulator.NomaiTextLine._points.Select(v => (MVector3)v).ToArray(), - position = spiralManipulator.transform.localPosition, - zRotation = spiralManipulator.transform.localEulerAngles.z, - mirrored = spiralManipulator.transform.localScale.x < 0 - }).ToArray(); - - nhBody.Cache.Set(cacheKey, cacheData); - } - } } - internal static GameObject MakeArc(PropModule.NomaiTextArcInfo arcInfo, GameObject conversationZone, GameObject parent, int textEntryID, GameObject prebuiltArc = null) + internal static GameObject MakeArc(PropModule.NomaiTextArcInfo arcInfo, GameObject conversationZone, GameObject parent, int textEntryID) { GameObject arc; var type = arcInfo != null ? arcInfo.type : PropModule.NomaiTextArcInfo.NomaiTextArcType.Adult; - NomaiTextArcBuilder.SpiralProfile profile; - Material mat; - Mesh overrideMesh = null; - Color? overrideColor = null; + var variation = arcInfo != null ? arcInfo.variation : -1; switch (type) { case PropModule.NomaiTextArcInfo.NomaiTextArcType.Child: - profile = NomaiTextArcBuilder.childSpiralProfile; - mat = _childArcMaterial; + variation = variation < 0 + ? Random.Range(0, _childArcPrefabs.Count()) + : (variation % _childArcPrefabs.Count()); + arc = _childArcPrefabs[variation].InstantiateInactive(); break; - case PropModule.NomaiTextArcInfo.NomaiTextArcType.Stranger when _ghostArcMaterial != null: - profile = NomaiTextArcBuilder.strangerSpiralProfile; - mat = _ghostArcMaterial; - overrideMesh = MeshUtilities.RectangleMeshFromCorners(new Vector3[]{ new Vector3(-0.9f, 0.0f, 0.0f), new Vector3(0.9f, 0.0f, 0.0f), new Vector3(-0.9f, 2.0f, 0.0f), new Vector3(0.9f, 2.0f, 0.0f) }); - overrideColor = new Color(0.0158f, 1.0f, 0.5601f, 1f); + case PropModule.NomaiTextArcInfo.NomaiTextArcType.Stranger when _ghostArcPrefabs.Any(): + variation = variation < 0 + ? Random.Range(0, _ghostArcPrefabs.Count()) + : (variation % _ghostArcPrefabs.Count()); + arc = _ghostArcPrefabs[variation].InstantiateInactive(); break; case PropModule.NomaiTextArcInfo.NomaiTextArcType.Adult: default: - profile = NomaiTextArcBuilder.adultSpiralProfile; - mat = _adultArcMaterial; + variation = variation < 0 + ? Random.Range(0, _arcPrefabs.Count()) + : (variation % _arcPrefabs.Count()); + arc = _arcPrefabs[variation].InstantiateInactive(); break; } - - if (prebuiltArc != null) - { - arc = prebuiltArc; - } - else - { - if (parent != null) arc = parent.GetComponent().AddChild(profile).gameObject; - else arc = NomaiTextArcArranger.CreateSpiral(profile, conversationZone).gameObject; - } - - if (mat != null) arc.GetComponent().sharedMaterial = mat; arc.transform.parent = conversationZone.transform; arc.GetComponent()._prebuilt = false; + if (arcInfo != null) + { + arcInfo.variation = variation; + if (arcInfo.position == null) arc.transform.localPosition = Vector3.zero; + else arc.transform.localPosition = new Vector3(arcInfo.position.x, arcInfo.position.y, 0); + + arc.transform.localRotation = Quaternion.Euler(0, 0, arcInfo.zRotation); + + if (arcInfo.mirror) arc.transform.localScale = new Vector3(-1, 1, 1); + } + // 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; - if (overrideMesh != null) - arc.GetComponent().sharedMesh = overrideMesh; - - if (overrideColor != null) - arc.GetComponent()._targetColor = (Color)overrideColor; - arc.SetActive(true); if (arcInfo != null) arcInfoToCorrespondingSpawnedGameObject[arcInfo] = arc; diff --git a/NewHorizons/Builder/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs index b1018554..10536aaa 100644 --- a/NewHorizons/Builder/Props/PropBuildManager.cs +++ b/NewHorizons/Builder/Props/PropBuildManager.cs @@ -132,7 +132,22 @@ namespace NewHorizons.Builder.Props { try { - NomaiTextBuilder.Make(go, sector, nomaiTextInfo, nhBody); + NomaiTextBuilder.Make(go, sector, nomaiTextInfo, nhBody.Mod); + } + catch (Exception ex) + { + Logger.LogError($"Couldn't make text [{nomaiTextInfo.xmlFile}] for [{go.name}]:\n{ex}"); + } + + } + } + if (config.Props.translatorText != null) + { + foreach (var nomaiTextInfo in config.Props.translatorText) + { + try + { + TranslatorTextBuilder.Make(go, sector, nomaiTextInfo, nhBody); } catch (Exception ex) { diff --git a/NewHorizons/Builder/Props/RemoteBuilder.cs b/NewHorizons/Builder/Props/RemoteBuilder.cs index f179cdd9..4fa179f3 100644 --- a/NewHorizons/Builder/Props/RemoteBuilder.cs +++ b/NewHorizons/Builder/Props/RemoteBuilder.cs @@ -194,7 +194,7 @@ namespace NewHorizons.Builder.Props { var textInfo = info.nomaiText[i]; component._remoteIDs[i] = RemoteHandler.GetPlatformID(textInfo.id); - var wallText = NomaiTextBuilder.Make(whiteboard, sector, new PropModule.NomaiTextInfo + var wallText = TranslatorTextBuilder.Make(whiteboard, sector, new PropModule.NomaiTextInfo { arcInfo = textInfo.arcInfo, location = textInfo.location, diff --git a/NewHorizons/Builder/Props/NomaiText/NomaiTextArcArranger.cs b/NewHorizons/Builder/Props/TranslatorText/NomaiTextArcArranger.cs similarity index 100% rename from NewHorizons/Builder/Props/NomaiText/NomaiTextArcArranger.cs rename to NewHorizons/Builder/Props/TranslatorText/NomaiTextArcArranger.cs diff --git a/NewHorizons/Builder/Props/NomaiText/NomaiTextArcBuilder.cs b/NewHorizons/Builder/Props/TranslatorText/NomaiTextArcBuilder.cs similarity index 100% rename from NewHorizons/Builder/Props/NomaiText/NomaiTextArcBuilder.cs rename to NewHorizons/Builder/Props/TranslatorText/NomaiTextArcBuilder.cs diff --git a/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs b/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs new file mode 100644 index 00000000..f864361c --- /dev/null +++ b/NewHorizons/Builder/Props/TranslatorText/TranslatorTextBuilder.cs @@ -0,0 +1,845 @@ +using NewHorizons.External.Modules; +using NewHorizons.Handlers; +using NewHorizons.Utility; +using OWML.Common; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using UnityEngine; +using Enum = System.Enum; +using Logger = NewHorizons.Utility.Logger; +using Random = UnityEngine.Random; +using OWML.Utils; +using Newtonsoft.Json; +using System; + +namespace NewHorizons.Builder.Props +{ + public static class TranslatorTextBuilder + { + private static Material _ghostArcMaterial; + private static Material _adultArcMaterial; + private static Material _childArcMaterial; + private static GameObject _scrollPrefab; + private static GameObject _computerPrefab; + private static GameObject _preCrashComputerPrefab; + private static GameObject _cairnPrefab; + private static GameObject _cairnVariantPrefab; + private static GameObject _recorderPrefab; + private static GameObject _preCrashRecorderPrefab; + private static GameObject _trailmarkerPrefab; + + private static Dictionary arcInfoToCorrespondingSpawnedGameObject = new Dictionary(); + public static GameObject GetSpawnedGameObjectByNomaiTextArcInfo(PropModule.NomaiTextArcInfo arc) + { + if (!arcInfoToCorrespondingSpawnedGameObject.ContainsKey(arc)) return null; + return arcInfoToCorrespondingSpawnedGameObject[arc]; + } + + private static Dictionary conversationInfoToCorrespondingSpawnedGameObject = new Dictionary(); + + public static GameObject GetSpawnedGameObjectByNomaiTextInfo(PropModule.NomaiTextInfo convo) + { + Logger.LogVerbose("Retrieving wall text obj for " + convo); + if (!conversationInfoToCorrespondingSpawnedGameObject.ContainsKey(convo)) return null; + return conversationInfoToCorrespondingSpawnedGameObject[convo]; + } + + private static bool _isInit; + + internal static void InitPrefabs() + { + if (_isInit) return; + + _isInit = true; + + if (_adultArcMaterial == null) + { + _adultArcMaterial = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_Crossroads/Interactables_Crossroads/Trailmarkers/Prefab_NOM_BH_Cairn_Arc (2)/Props_TH_ClutterSmall/Arc_Short/Arc") + .GetComponent() + .sharedMaterial; + } + + if (_childArcMaterial == null) + { + _childArcMaterial = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_OldSettlement/Fragment OldSettlement 5/Core_OldSettlement 5/Interactables_Core_OldSettlement5/Arc_BH_OldSettlement_ChildrensRhyme/Arc 1") + .GetComponent() + .sharedMaterial; + } + + if (_ghostArcMaterial == null) + { + _ghostArcMaterial = SearchUtilities.Find("RingWorld_Body/Sector_RingInterior/Sector_Zone1/Interactables_Zone1/Props_IP_ZoneSign_1/Arc_TestAlienWriting/Arc 1") + .GetComponent() + .sharedMaterial; + } + + if (_scrollPrefab == null) _scrollPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_NorthHemisphere/Sector_NorthPole/Sector_HangingCity/Sector_HangingCity_District2/Interactables_HangingCity_District2/Prefab_NOM_Scroll").InstantiateInactive().Rename("Prefab_NOM_Scroll").DontDestroyOnLoad(); + + if (_computerPrefab == null) + { + _computerPrefab = SearchUtilities.Find("VolcanicMoon_Body/Sector_VM/Interactables_VM/Prefab_NOM_Computer").InstantiateInactive().Rename("Prefab_NOM_Computer").DontDestroyOnLoad(); + _computerPrefab.transform.rotation = Quaternion.identity; + } + + if (_preCrashComputerPrefab == null) + { + _preCrashComputerPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_EscapePodCrashSite/Sector_CrashFragment/EscapePod_Socket/Interactibles_EscapePod/Prefab_NOM_Vessel_Computer").InstantiateInactive().Rename("Prefab_NOM_Vessel_Computer").DontDestroyOnLoad(); + _preCrashComputerPrefab.transform.rotation = Quaternion.identity; + } + + if (_cairnPrefab == null) + { + _cairnPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_Crossroads/Interactables_Crossroads/Trailmarkers/Prefab_NOM_BH_Cairn_Arc (1)").InstantiateInactive().Rename("Prefab_NOM_Cairn").DontDestroyOnLoad(); + _cairnPrefab.transform.rotation = Quaternion.identity; + } + + if (_cairnVariantPrefab == null) + { + _cairnVariantPrefab = SearchUtilities.Find("TimberHearth_Body/Sector_TH/Sector_NomaiMines/Interactables_NomaiMines/Prefab_NOM_TH_Cairn_Arc").InstantiateInactive().Rename("Prefab_NOM_Cairn").DontDestroyOnLoad(); + _cairnVariantPrefab.transform.rotation = Quaternion.identity; + } + + if (_recorderPrefab == null) + { + _recorderPrefab = SearchUtilities.Find("Comet_Body/Prefab_NOM_Shuttle/Sector_NomaiShuttleInterior/Interactibles_NomaiShuttleInterior/Prefab_NOM_Recorder").InstantiateInactive().Rename("Prefab_NOM_Recorder").DontDestroyOnLoad(); + _recorderPrefab.transform.rotation = Quaternion.identity; + } + + if (_preCrashRecorderPrefab == null) + { + _preCrashRecorderPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_EscapePodCrashSite/Sector_CrashFragment/Interactables_CrashFragment/Prefab_NOM_Recorder").InstantiateInactive().Rename("Prefab_NOM_Recorder_Vessel").DontDestroyOnLoad(); + _preCrashRecorderPrefab.transform.rotation = Quaternion.identity; + } + + if (_trailmarkerPrefab == null) + { + _trailmarkerPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Sector_NorthHemisphere/Sector_NorthPole/Sector_HangingCity/Sector_HangingCity_District2/Interactables_HangingCity_District2/Prefab_NOM_Sign").InstantiateInactive().Rename("Prefab_NOM_Trailmarker").DontDestroyOnLoad(); + _trailmarkerPrefab.transform.rotation = Quaternion.identity; + } + } + + public static GameObject Make(GameObject planetGO, Sector sector, PropModule.NomaiTextInfo info, NewHorizonsBody nhBody) + { + InitPrefabs(); + + var xmlPath = File.ReadAllText(Path.Combine(nhBody.Mod.ModHelper.Manifest.ModFolderPath, info.xmlFile)); + + switch (info.type) + { + case PropModule.NomaiTextInfo.NomaiTextType.Wall: + { + var nomaiWallTextObj = MakeWallText(planetGO, sector, info, xmlPath, nhBody).gameObject; + + if (!string.IsNullOrEmpty(info.rename)) + { + nomaiWallTextObj.name = info.rename; + } + + nomaiWallTextObj.transform.parent = sector?.transform ?? planetGO.transform; + + if (!string.IsNullOrEmpty(info.parentPath)) + { + var newParent = planetGO.transform.Find(info.parentPath); + if (newParent != null) + { + nomaiWallTextObj.transform.parent = newParent; + } + else + { + Logger.LogError($"Cannot find parent object at path: {planetGO.name}/{info.parentPath}"); + } + } + + var pos = (Vector3)(info.position ?? Vector3.zero); + if (info.isRelativeToParent) + { + nomaiWallTextObj.transform.localPosition = pos; + if (info.normal != null) + { + // In global coordinates (normal was in local coordinates) + var up = (nomaiWallTextObj.transform.position - planetGO.transform.position).normalized; + var forward = planetGO.transform.TransformDirection(info.normal).normalized; + + nomaiWallTextObj.transform.up = up; + nomaiWallTextObj.transform.forward = forward; + } + if (info.rotation != null) + { + nomaiWallTextObj.transform.localRotation = Quaternion.Euler(info.rotation); + } + } + else + { + nomaiWallTextObj.transform.position = planetGO.transform.TransformPoint(pos); + if (info.normal != null) + { + // In global coordinates (normal was in local coordinates) + var up = (nomaiWallTextObj.transform.position - planetGO.transform.position).normalized; + var forward = planetGO.transform.TransformDirection(info.normal).normalized; + + nomaiWallTextObj.transform.forward = forward; + + var desiredUp = Vector3.ProjectOnPlane(up, forward); + var zRotation = Vector3.SignedAngle(nomaiWallTextObj.transform.up, desiredUp, forward); + nomaiWallTextObj.transform.RotateAround(nomaiWallTextObj.transform.position, forward, zRotation); + } + if (info.rotation != null) + { + nomaiWallTextObj.transform.rotation = planetGO.transform.TransformRotation(Quaternion.Euler(info.rotation)); + } + } + + // nomaiWallTextObj.GetComponent().DrawBoundsWithDebugSpheres(); + + nomaiWallTextObj.SetActive(true); + conversationInfoToCorrespondingSpawnedGameObject[info] = nomaiWallTextObj; + + return nomaiWallTextObj; + } + case PropModule.NomaiTextInfo.NomaiTextType.Scroll: + { + var customScroll = _scrollPrefab.InstantiateInactive(); + + if (!string.IsNullOrEmpty(info.rename)) + { + customScroll.name = info.rename; + } + else + { + customScroll.name = _scrollPrefab.name; + } + + var nomaiWallText = MakeWallText(planetGO, sector, info, xmlPath, nhBody); + 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 ?? planetGO.transform; + + if (!string.IsNullOrEmpty(info.parentPath)) + { + var newParent = planetGO.transform.Find(info.parentPath); + if (newParent != null) + { + customScroll.transform.parent = newParent; + } + else + { + Logger.LogError($"Cannot find parent object at path: {planetGO.name}/{info.parentPath}"); + } + } + + var pos = (Vector3)(info.position ?? Vector3.zero); + if (info.isRelativeToParent) customScroll.transform.localPosition = pos; + else customScroll.transform.position = planetGO.transform.TransformPoint(pos); + + var up = planetGO.transform.InverseTransformPoint(customScroll.transform.position).normalized; + if (info.rotation != null) + { + customScroll.transform.rotation = planetGO.transform.TransformRotation(Quaternion.Euler(info.rotation)); + } + else + { + customScroll.transform.rotation = Quaternion.FromToRotation(customScroll.transform.up, up) * customScroll.transform.rotation; + } + + customScroll.SetActive(true); + + // Enable the collider and renderer + Delay.RunWhen( + () => Main.IsSystemReady, + () => + { + Logger.LogVerbose("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; + } + ); + conversationInfoToCorrespondingSpawnedGameObject[info] = customScroll; + + return customScroll; + } + case PropModule.NomaiTextInfo.NomaiTextType.Computer: + { + var computerObject = _computerPrefab.InstantiateInactive(); + + if (!string.IsNullOrEmpty(info.rename)) + { + computerObject.name = info.rename; + } + else + { + computerObject.name = _computerPrefab.name; + } + + computerObject.transform.parent = sector?.transform ?? planetGO.transform; + + if (!string.IsNullOrEmpty(info.parentPath)) + { + var newParent = planetGO.transform.Find(info.parentPath); + if (newParent != null) + { + computerObject.transform.parent = newParent; + } + else + { + Logger.LogError($"Cannot find parent object at path: {planetGO.name}/{info.parentPath}"); + } + } + + var pos = (Vector3)(info.position ?? Vector3.zero); + if (info.isRelativeToParent) computerObject.transform.localPosition = pos; + else computerObject.transform.position = planetGO.transform.TransformPoint(pos); + + var up = computerObject.transform.position - planetGO.transform.position; + if (info.normal != null) up = planetGO.transform.TransformDirection(info.normal); + computerObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, up) * computerObject.transform.rotation; + + var computer = computerObject.GetComponent(); + computer.SetSector(sector); + + computer._location = EnumUtils.Parse(info.location.ToString()); + computer._dictNomaiTextData = MakeNomaiTextDict(xmlPath); + computer._nomaiTextAsset = new TextAsset(xmlPath); + computer._nomaiTextAsset.name = Path.GetFileNameWithoutExtension(info.xmlFile); + AddTranslation(xmlPath); + + // Make sure the computer model is loaded + StreamingHandler.SetUpStreaming(computerObject, sector); + + computerObject.SetActive(true); + conversationInfoToCorrespondingSpawnedGameObject[info] = computerObject; + + return computerObject; + } + case PropModule.NomaiTextInfo.NomaiTextType.PreCrashComputer: + { + var detailInfo = new PropModule.DetailInfo() + { + position = info.position, + parentPath = info.parentPath, + isRelativeToParent = info.isRelativeToParent, + rename = info.rename + }; + var computerObject = DetailBuilder.Make(planetGO, sector, _preCrashComputerPrefab, detailInfo); + computerObject.SetActive(false); + + var up = computerObject.transform.position - planetGO.transform.position; + if (info.normal != null) up = planetGO.transform.TransformDirection(info.normal); + computerObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, up) * computerObject.transform.rotation; + + var computer = computerObject.GetComponent(); + computer.SetSector(sector); + + computer._location = EnumUtils.Parse(info.location.ToString()); + computer._dictNomaiTextData = MakeNomaiTextDict(xmlPath); + computer._nomaiTextAsset = new TextAsset(xmlPath); + computer._nomaiTextAsset.name = Path.GetFileNameWithoutExtension(info.xmlFile); + AddTranslation(xmlPath); + + // Make fifth ring work + var fifthRingObject = computerObject.FindChild("Props_NOM_Vessel_Computer 1/Props_NOM_Vessel_Computer_Effects (4)"); + fifthRingObject.SetActive(true); + var fifthRing = fifthRingObject.GetComponent(); + //fifthRing._baseProjectorColor = new Color(1.4118, 1.5367, 4, 1); + //fifthRing._baseTextColor = new Color(0.8824, 0.9604, 2.5, 1); + //fifthRing._baseTextShadowColor = new Color(0.3529, 0.3843, 1, 0.25); + fifthRing._computer = computer; + + computerObject.SetActive(true); + + // All rings are rendered by detail builder so dont do that (have to wait for entries to be set) + Delay.FireOnNextUpdate(() => + { + for (var i = computer.GetNumTextBlocks(); i < 5; i++) + { + var ring = computer._computerRings[i]; + ring.gameObject.SetActive(false); + } + }); + + conversationInfoToCorrespondingSpawnedGameObject[info] = computerObject; + + return computerObject; + } + case PropModule.NomaiTextInfo.NomaiTextType.Cairn: + case PropModule.NomaiTextInfo.NomaiTextType.CairnVariant: + { + var cairnObject = (info.type == PropModule.NomaiTextInfo.NomaiTextType.CairnVariant ? _cairnVariantPrefab : _cairnPrefab).InstantiateInactive(); + + if (!string.IsNullOrEmpty(info.rename)) + { + cairnObject.name = info.rename; + } + else + { + cairnObject.name = _cairnPrefab.name; + } + + cairnObject.transform.parent = sector?.transform ?? planetGO.transform; + + if (!string.IsNullOrEmpty(info.parentPath)) + { + var newParent = planetGO.transform.Find(info.parentPath); + if (newParent != null) + { + cairnObject.transform.parent = newParent; + } + else + { + Logger.LogError($"Cannot find parent object at path: {planetGO.name}/{info.parentPath}"); + } + } + + var pos = (Vector3)(info.position ?? Vector3.zero); + if (info.isRelativeToParent) cairnObject.transform.localPosition = pos; + else cairnObject.transform.position = planetGO.transform.TransformPoint(pos); + + if (info.rotation != null) + { + var rot = Quaternion.Euler(info.rotation); + if (info.isRelativeToParent) cairnObject.transform.localRotation = rot; + else cairnObject.transform.rotation = planetGO.transform.TransformRotation(rot); + } + else + { + // By default align it to normal + var up = (cairnObject.transform.position - planetGO.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._location = EnumUtils.Parse(info.location.ToString()); + nomaiWallText._dictNomaiTextData = MakeNomaiTextDict(xmlPath); + nomaiWallText._nomaiTextAsset = new TextAsset(xmlPath); + nomaiWallText._nomaiTextAsset.name = Path.GetFileNameWithoutExtension(info.xmlFile); + AddTranslation(xmlPath); + + // Make sure the computer model is loaded + StreamingHandler.SetUpStreaming(cairnObject, sector); + conversationInfoToCorrespondingSpawnedGameObject[info] = cairnObject; + + return cairnObject; + } + case PropModule.NomaiTextInfo.NomaiTextType.PreCrashRecorder: + case PropModule.NomaiTextInfo.NomaiTextType.Recorder: + { + var prefab = (info.type == PropModule.NomaiTextInfo.NomaiTextType.PreCrashRecorder ? _preCrashRecorderPrefab : _recorderPrefab); + var detailInfo = new PropModule.DetailInfo { + parentPath = info.parentPath, + rotation = info.rotation, + position = info.position, + isRelativeToParent = info.isRelativeToParent, + rename = info.rename + }; + var recorderObject = DetailBuilder.Make(planetGO, sector, prefab, detailInfo); + recorderObject.SetActive(false); + + if (info.rotation == null) + { + var up = recorderObject.transform.position - planetGO.transform.position; + recorderObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, up) * recorderObject.transform.rotation; + } + + var nomaiText = recorderObject.GetComponentInChildren(); + nomaiText.SetSector(sector); + + nomaiText._location = EnumUtils.Parse(info.location.ToString()); + nomaiText._dictNomaiTextData = MakeNomaiTextDict(xmlPath); + nomaiText._nomaiTextAsset = new TextAsset(xmlPath); + nomaiText._nomaiTextAsset.name = Path.GetFileNameWithoutExtension(info.xmlFile); + AddTranslation(xmlPath); + + recorderObject.SetActive(true); + + recorderObject.transform.Find("InteractSphere").gameObject.GetComponent().enabled = true; + conversationInfoToCorrespondingSpawnedGameObject[info] = recorderObject; + return recorderObject; + } + case PropModule.NomaiTextInfo.NomaiTextType.Trailmarker: + { + var trailmarkerObject = _trailmarkerPrefab.InstantiateInactive(); + + if (!string.IsNullOrEmpty(info.rename)) + { + trailmarkerObject.name = info.rename; + } + else + { + trailmarkerObject.name = _trailmarkerPrefab.name; + } + + trailmarkerObject.transform.parent = sector?.transform ?? planetGO.transform; + + if (!string.IsNullOrEmpty(info.parentPath)) + { + var newParent = planetGO.transform.Find(info.parentPath); + if (newParent != null) + { + trailmarkerObject.transform.parent = newParent; + } + else + { + Logger.LogError($"Cannot find parent object at path: {planetGO.name}/{info.parentPath}"); + } + } + + var pos = (Vector3)(info.position ?? Vector3.zero); + if (info.isRelativeToParent) trailmarkerObject.transform.localPosition = pos; + else trailmarkerObject.transform.position = planetGO.transform.TransformPoint(pos); + + // shrink because that is what mobius does on all trailmarkers or else they are the size of the player + trailmarkerObject.transform.localScale = Vector3.one * 0.75f; + + if (info.rotation != null) + { + var rot = Quaternion.Euler(info.rotation); + if (info.isRelativeToParent) trailmarkerObject.transform.localRotation = rot; + else trailmarkerObject.transform.rotation = planetGO.transform.TransformRotation(rot); + } + else + { + // By default align it to normal + var up = (trailmarkerObject.transform.position - planetGO.transform.position).normalized; + trailmarkerObject.transform.rotation = Quaternion.FromToRotation(Vector3.up, up) * trailmarkerObject.transform.rotation; + } + + // Idk do we have to set it active before finding things? + trailmarkerObject.SetActive(true); + + var nomaiWallText = trailmarkerObject.transform.Find("Arc_Short").GetComponent(); + nomaiWallText.SetSector(sector); + + nomaiWallText._location = EnumUtils.Parse(info.location.ToString()); + nomaiWallText._dictNomaiTextData = MakeNomaiTextDict(xmlPath); + nomaiWallText._nomaiTextAsset = new TextAsset(xmlPath); + nomaiWallText._nomaiTextAsset.name = Path.GetFileNameWithoutExtension(info.xmlFile); + AddTranslation(xmlPath); + + // Make sure the model is loaded + StreamingHandler.SetUpStreaming(trailmarkerObject, sector); + conversationInfoToCorrespondingSpawnedGameObject[info] = trailmarkerObject; + + return trailmarkerObject; + } + default: + Logger.LogError($"Unsupported NomaiText type {info.type}"); + return null; + } + } + + private static NomaiWallText MakeWallText(GameObject go, Sector sector, PropModule.NomaiTextInfo info, string xmlPath, NewHorizonsBody nhBody) + { + 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(); + + nomaiWallText._location = EnumUtils.Parse(info.location.ToString()); + + var text = new TextAsset(xmlPath); + + // Text assets need a name to be used with VoiceMod + text.name = Path.GetFileNameWithoutExtension(info.xmlFile); + + BuildArcs(xmlPath, nomaiWallText, nomaiWallTextObj, info, nhBody); + AddTranslation(xmlPath); + nomaiWallText._nomaiTextAsset = text; + + nomaiWallText.SetTextAsset(text); + + // #433 fuzzy stranger text + if (info.arcInfo != null && info.arcInfo.Any(x => x.type == PropModule.NomaiTextArcInfo.NomaiTextArcType.Stranger)) + { + StreamingHandler.SetUpStreaming(AstroObject.Name.RingWorld, sector); + } + + return nomaiWallText; + } + + internal static void BuildArcs(string xml, NomaiWallText nomaiWallText, GameObject conversationZone, PropModule.NomaiTextInfo info, NewHorizonsBody nhBody) + { + var dict = MakeNomaiTextDict(xml); + + nomaiWallText._dictNomaiTextData = dict; + + var cacheKey = xml.GetHashCode() + " " + JsonConvert.SerializeObject(info).GetHashCode(); + RefreshArcs(nomaiWallText, conversationZone, info, nhBody, cacheKey); + } + + [Serializable] + private struct ArcCacheData + { + public MMesh mesh; + public MVector3[] skeletonPoints; + public MVector3 position; + public float zRotation; + public bool mirrored; + } + + internal static void RefreshArcs(NomaiWallText nomaiWallText, GameObject conversationZone, PropModule.NomaiTextInfo info, NewHorizonsBody nhBody, string cacheKey) + { + var dict = nomaiWallText._dictNomaiTextData; + Random.InitState(info.seed == 0 ? info.xmlFile.GetHashCode() : 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; + } + + ArcCacheData[] cachedData = null; + if (nhBody.Cache?.ContainsKey(cacheKey) ?? false) + cachedData = nhBody.Cache.Get(cacheKey); + + var arranger = nomaiWallText.gameObject.AddComponent(); + + // Generate spiral meshes/GOs + + var i = 0; + foreach (var textData in dict.Values) + { + var arcInfo = info.arcInfo?.Length > i ? info.arcInfo[i] : null; + var textEntryID = textData.ID; + var parentID = textData.ParentID; + + var parent = parentID == -1 ? null : arcsByID[parentID]; + + GameObject arcReadFromCache = null; + if (cachedData != null) + { + var skeletonPoints = cachedData[i].skeletonPoints.Select(mv => (Vector3)mv).ToArray(); + arcReadFromCache = NomaiTextArcBuilder.BuildSpiralGameObject(skeletonPoints, cachedData[i].mesh); + arcReadFromCache.transform.parent = arranger.transform; + arcReadFromCache.transform.localScale = new Vector3(cachedData[i].mirrored? -1 : 1, 1, 1); + arcReadFromCache.transform.localPosition = cachedData[i].position; + arcReadFromCache.transform.localEulerAngles = new Vector3(0, 0, cachedData[i].zRotation); + } + + GameObject arc = MakeArc(arcInfo, conversationZone, parent, textEntryID, arcReadFromCache); + arc.name = $"Arc {textEntryID} - Child of {parentID}"; + + arcsByID.Add(textEntryID, arc); + + i++; + } + + // no need to arrange if the cache exists + if (cachedData == null) + { + Logger.LogVerbose("Cache and/or cache entry was null, proceding with wall text arc arrangment."); + + // auto placement + + var overlapFound = true; + for (var k = 0; k < arranger.spirals.Count*2; k++) + { + overlapFound = arranger.AttemptOverlapResolution(); + if (!overlapFound) break; + for(var a = 0; a < 10; a++) arranger.FDGSimulationStep(); + } + + if (overlapFound) Logger.LogVerbose("Overlap resolution failed!"); + + // manual placement + + for (var j = 0; j < info.arcInfo?.Length; j++) + { + var arcInfo = info.arcInfo[j]; + var arc = arranger.spirals[j]; + + if (arcInfo.keepAutoPlacement) continue; + + if (arcInfo.position == null) arc.transform.localPosition = Vector3.zero; + else arc.transform.localPosition = new Vector3(arcInfo.position.x, arcInfo.position.y, 0); + + arc.transform.localRotation = Quaternion.Euler(0, 0, arcInfo.zRotation); + + if (arcInfo.mirror) arc.transform.localScale = new Vector3(-1, 1, 1); + else arc.transform.localScale = new Vector3( 1, 1, 1); + } + + // make an entry in the cache for all these spirals + + if (nhBody.Cache != null) + { + var cacheData = arranger.spirals.Select(spiralManipulator => new ArcCacheData() + { + mesh = spiralManipulator.GetComponent().sharedMesh, // TODO: create a serializable version of Mesh and pass this: spiralManipulator.GetComponent().mesh + skeletonPoints = spiralManipulator.NomaiTextLine._points.Select(v => (MVector3)v).ToArray(), + position = spiralManipulator.transform.localPosition, + zRotation = spiralManipulator.transform.localEulerAngles.z, + mirrored = spiralManipulator.transform.localScale.x < 0 + }).ToArray(); + + nhBody.Cache.Set(cacheKey, cacheData); + } + } + } + + internal static GameObject MakeArc(PropModule.NomaiTextArcInfo arcInfo, GameObject conversationZone, GameObject parent, int textEntryID, GameObject prebuiltArc = null) + { + GameObject arc; + var type = arcInfo != null ? arcInfo.type : PropModule.NomaiTextArcInfo.NomaiTextArcType.Adult; + NomaiTextArcBuilder.SpiralProfile profile; + Material mat; + Mesh overrideMesh = null; + Color? overrideColor = null; + switch (type) + { + case PropModule.NomaiTextArcInfo.NomaiTextArcType.Child: + profile = NomaiTextArcBuilder.childSpiralProfile; + mat = _childArcMaterial; + break; + case PropModule.NomaiTextArcInfo.NomaiTextArcType.Stranger when _ghostArcMaterial != null: + profile = NomaiTextArcBuilder.strangerSpiralProfile; + mat = _ghostArcMaterial; + overrideMesh = MeshUtilities.RectangleMeshFromCorners(new Vector3[]{ new Vector3(-0.9f, 0.0f, 0.0f), new Vector3(0.9f, 0.0f, 0.0f), new Vector3(-0.9f, 2.0f, 0.0f), new Vector3(0.9f, 2.0f, 0.0f) }); + overrideColor = new Color(0.0158f, 1.0f, 0.5601f, 1f); + break; + case PropModule.NomaiTextArcInfo.NomaiTextArcType.Adult: + default: + profile = NomaiTextArcBuilder.adultSpiralProfile; + mat = _adultArcMaterial; + break; + } + + if (prebuiltArc != null) + { + arc = prebuiltArc; + } + else + { + if (parent != null) arc = parent.GetComponent().AddChild(profile).gameObject; + else arc = NomaiTextArcArranger.CreateSpiral(profile, conversationZone).gameObject; + } + + if (mat != null) arc.GetComponent().sharedMaterial = mat; + + arc.transform.parent = conversationZone.transform; + arc.GetComponent()._prebuilt = false; + + arc.GetComponent().SetEntryID(textEntryID); + arc.GetComponent().enabled = false; + + if (overrideMesh != null) + arc.GetComponent().sharedMesh = overrideMesh; + + if (overrideColor != null) + arc.GetComponent()._targetColor = (Color)overrideColor; + + arc.SetActive(true); + + if (arcInfo != null) arcInfoToCorrespondingSpawnedGameObject[arcInfo] = arc; + + return 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); + } + } + } +} diff --git a/NewHorizons/External/Modules/PropModule.cs b/NewHorizons/External/Modules/PropModule.cs index 9cc829e6..a2d16a1e 100644 --- a/NewHorizons/External/Modules/PropModule.cs +++ b/NewHorizons/External/Modules/PropModule.cs @@ -33,10 +33,16 @@ namespace NewHorizons.External.Modules /// public GeyserInfo[] geysers; + /// + /// Add translatable text to this planet. (LEGACY - for use with pre-autospirals configs) + /// + [Obsolete("nomaiText is deprecated as of the release of auto spirals, instead please use translatorText with new configs.")] + public NomaiTextInfo[] nomaiText; + /// /// Add translatable text to this planet /// - public NomaiTextInfo[] nomaiText; + public NomaiTextInfo[] translatorText; /// /// Details which will be shown from 50km away. Meant to be lower resolution. diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 93e4ed9c..eff0ff25 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -275,6 +275,7 @@ namespace NewHorizons GeyserBuilder.InitPrefab(); LavaBuilder.InitPrefabs(); NomaiTextBuilder.InitPrefabs(); + TranslatorTextBuilder.InitPrefabs(); RemoteBuilder.InitPrefabs(); SandBuilder.InitPrefabs(); SingularityBuilder.InitPrefabs(); diff --git a/NewHorizons/Utility/DebugMenu/DebugMenuNomaiText.cs b/NewHorizons/Utility/DebugMenu/DebugMenuNomaiText.cs index 2ebcc329..07d192d8 100644 --- a/NewHorizons/Utility/DebugMenu/DebugMenuNomaiText.cs +++ b/NewHorizons/Utility/DebugMenu/DebugMenuNomaiText.cs @@ -100,7 +100,7 @@ namespace NewHorizons.Utility.DebugMenu ConversationMetadata conversationMetadata = new ConversationMetadata() { conversation = conversation, - conversationGo = NomaiTextBuilder.GetSpawnedGameObjectByNomaiTextInfo(conversation), + conversationGo = TranslatorTextBuilder.GetSpawnedGameObjectByNomaiTextInfo(conversation), planetConfig = config, spirals = new List(), collapsed = true @@ -120,7 +120,7 @@ namespace NewHorizons.Utility.DebugMenu SpiralMetadata metadata = new SpiralMetadata() { spiral = arcInfo, - spiralGo = NomaiTextBuilder.GetSpawnedGameObjectByNomaiTextArcInfo(arcInfo), + spiralGo = TranslatorTextBuilder.GetSpawnedGameObjectByNomaiTextArcInfo(arcInfo), conversation = conversation, planetConfig = config, planetName = config.name, @@ -365,7 +365,7 @@ namespace NewHorizons.Utility.DebugMenu for (indexInParent = 0; indexInParent < wallTextComponent._textLines.Length; indexInParent++) if (oldTextLineComponent == wallTextComponent._textLines[indexInParent]) break; var textEntryId = oldTextLineComponent._entryID; GameObject.Destroy(spiralMeta.spiralGo); - spiralMeta.spiralGo = NomaiTextBuilder.MakeArc(spiralMeta.spiral, conversationZone, null, textEntryId); + spiralMeta.spiralGo = TranslatorTextBuilder.MakeArc(spiralMeta.spiral, conversationZone, null, textEntryId); wallTextComponent._textLines[indexInParent] = spiralMeta.spiralGo.GetComponent(); spiralMeta.spiralGo.name = "Brandnewspiral";