diff --git a/NewHorizons/Builder/Props/NomaiTextBuilder.cs b/NewHorizons/Builder/Props/NomaiTextBuilder.cs
index a9b9e607..b76b04ce 100644
--- a/NewHorizons/Builder/Props/NomaiTextBuilder.cs
+++ b/NewHorizons/Builder/Props/NomaiTextBuilder.cs
@@ -14,6 +14,9 @@ using OWML.Utils;
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 List _arcPrefabs;
diff --git a/NewHorizons/Builder/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs
index d3dcbf4f..10536aaa 100644
--- a/NewHorizons/Builder/Props/PropBuildManager.cs
+++ b/NewHorizons/Builder/Props/PropBuildManager.cs
@@ -1,6 +1,7 @@
using NewHorizons.Builder.Body;
using NewHorizons.Builder.ShipLog;
using NewHorizons.External.Configs;
+using NewHorizons.Utility;
using OWML.Common;
using System;
using System.Collections.Generic;
@@ -10,8 +11,11 @@ namespace NewHorizons.Builder.Props
{
public static class PropBuildManager
{
- public static void Make(GameObject go, Sector sector, OWRigidbody planetBody, PlanetConfig config, IModBehaviour mod)
+ public static void Make(GameObject go, Sector sector, OWRigidbody planetBody, NewHorizonsBody nhBody)
{
+ PlanetConfig config = nhBody.Config;
+ IModBehaviour mod = nhBody.Mod;
+
if (config.Props.scatter != null)
{
try
@@ -128,7 +132,22 @@ namespace NewHorizons.Builder.Props
{
try
{
- NomaiTextBuilder.Make(go, sector, nomaiTextInfo, mod);
+ 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)
{
@@ -205,7 +224,7 @@ namespace NewHorizons.Builder.Props
{
try
{
- RemoteBuilder.Make(go, sector, remoteInfo, mod);
+ RemoteBuilder.Make(go, sector, remoteInfo, nhBody);
}
catch (Exception ex)
{
diff --git a/NewHorizons/Builder/Props/RemoteBuilder.cs b/NewHorizons/Builder/Props/RemoteBuilder.cs
index c8cd3334..4fa179f3 100644
--- a/NewHorizons/Builder/Props/RemoteBuilder.cs
+++ b/NewHorizons/Builder/Props/RemoteBuilder.cs
@@ -116,10 +116,11 @@ namespace NewHorizons.Builder.Props
}
}
- public static void Make(GameObject go, Sector sector, PropModule.RemoteInfo info, IModBehaviour mod)
+ public static void Make(GameObject go, Sector sector, PropModule.RemoteInfo info, NewHorizonsBody nhBody)
{
InitPrefabs();
+ var mod = nhBody.Mod;
var id = RemoteHandler.GetPlatformID(info.id);
Texture2D decal = Texture2D.whiteTexture;
@@ -142,7 +143,7 @@ namespace NewHorizons.Builder.Props
{
try
{
- RemoteBuilder.MakeWhiteboard(go, sector, id, decal, info.whiteboard, mod);
+ RemoteBuilder.MakeWhiteboard(go, sector, id, decal, info.whiteboard, nhBody);
}
catch (Exception ex)
{
@@ -166,7 +167,7 @@ namespace NewHorizons.Builder.Props
}
}
- public static void MakeWhiteboard(GameObject go, Sector sector, NomaiRemoteCameraPlatform.ID id, Texture2D decal, PropModule.RemoteInfo.WhiteboardInfo info, IModBehaviour mod)
+ public static void MakeWhiteboard(GameObject go, Sector sector, NomaiRemoteCameraPlatform.ID id, Texture2D decal, PropModule.RemoteInfo.WhiteboardInfo info, NewHorizonsBody nhBody)
{
var detailInfo = new PropModule.DetailInfo()
{
@@ -193,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,
@@ -204,7 +205,7 @@ namespace NewHorizons.Builder.Props
seed = textInfo.seed,
type = PropModule.NomaiTextInfo.NomaiTextType.Wall,
xmlFile = textInfo.xmlFile
- }, mod).GetComponent();
+ }, nhBody).GetComponent();
wallText._showTextOnStart = false;
component._nomaiTexts[i] = wallText;
}
diff --git a/NewHorizons/Builder/Props/TranslatorText/NomaiTextArcArranger.cs b/NewHorizons/Builder/Props/TranslatorText/NomaiTextArcArranger.cs
new file mode 100644
index 00000000..d05bda58
--- /dev/null
+++ b/NewHorizons/Builder/Props/TranslatorText/NomaiTextArcArranger.cs
@@ -0,0 +1,393 @@
+using NewHorizons.Utility;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.Profiling;
+using Logger = NewHorizons.Utility.Logger;
+
+namespace NewHorizons.Builder.Props
+{
+ public class NomaiTextArcArranger : MonoBehaviour {
+ private static int MAX_MOVE_DISTANCE = 2;
+
+ public List spirals = new List();
+ public List reverseToposortedSpirals = null;
+ private bool updateToposortOnNextStep = true;
+ private Dictionary sprialOverlapResolutionPriority = new Dictionary();
+
+ public SpiralManipulator root { get; private set; }
+
+ public float maxX = 2.7f;
+ public float minX = -2.7f;
+ public float maxY = 2.6f;
+ public float minY = -1f;
+
+ public static SpiralManipulator CreateSpiral(NomaiTextArcBuilder.SpiralProfile profile, GameObject spiralMeshHolder)
+ {
+ var rootArc = NomaiTextArcBuilder.BuildSpiralGameObject(profile);
+ rootArc.transform.parent = spiralMeshHolder.transform;
+ rootArc.transform.localEulerAngles = new Vector3(0, 0, Random.Range(-60, 60));
+
+ var manip = rootArc.AddComponent();
+ if (Random.value < 0.5) manip.transform.localScale = new Vector3(-1, 1, 1); // randomly mirror
+
+ // add to arranger
+ var arranger = spiralMeshHolder.GetAddComponent();
+ if (arranger.root == null) arranger.root = manip;
+ arranger.spirals.Add(manip);
+ arranger.updateToposortOnNextStep = true;
+
+ return manip;
+ }
+
+ public void FDGSimulationStep()
+ {
+ if (updateToposortOnNextStep)
+ {
+ updateToposortOnNextStep = false;
+ GenerateReverseToposort();
+ }
+
+ Dictionary childForces = new Dictionary();
+
+ foreach (var s1 in reverseToposortedSpirals) // treating the conversation like a tree datastructure, move "leaf" spirals first so that we can propogate their force up to the parents
+ {
+ Vector2 force = Vector2.zero;
+
+ // accumulate the force the children feel
+ if (childForces.ContainsKey(s1))
+ {
+ force += 0.9f * childForces[s1];
+ }
+
+ // push away from fellow spirals
+ foreach (var s2 in spirals)
+ {
+ if (s1 == s2) continue;
+ if (s1.parent == s2) continue;
+ if (s1 == s2.parent) continue;
+
+ var f = (s2.center - s1.center);
+ force -= f / Mathf.Pow(f.magnitude, 6);
+
+ var f2 = (s2.localPosition - s1.localPosition);
+ force -= f2 / Mathf.Pow(f2.magnitude, 6);
+ }
+
+ // push away from the edges
+ var MAX_EDGE_PUSH_FORCE = 1;
+ force += new Vector2(0, -1) * Mathf.Max(0, (s1.transform.localPosition.y + maxY)*(MAX_EDGE_PUSH_FORCE / maxY) - MAX_EDGE_PUSH_FORCE);
+ force += new Vector2(0, 1) * Mathf.Max(0, (s1.transform.localPosition.y + minY)*(MAX_EDGE_PUSH_FORCE / minY) - MAX_EDGE_PUSH_FORCE);
+ force += new Vector2(-1, 0) * Mathf.Max(0, (s1.transform.localPosition.x + maxX)*(MAX_EDGE_PUSH_FORCE / maxX) - MAX_EDGE_PUSH_FORCE);
+ force += new Vector2(1, 0) * Mathf.Max(0, (s1.transform.localPosition.x + minX)*(MAX_EDGE_PUSH_FORCE / minX) - MAX_EDGE_PUSH_FORCE);
+
+ // push up just to make everything a little more pretty (this is not neccessary to get an arrangement that simply has no overlap/spirals exiting the bounds)
+ force += new Vector2(0, 1) * 1;
+
+ // renormalize the force magnitude (keeps force sizes reasonable, and improves stability in the case of small forces)
+ var avg = 1; // the size of vector required to get a medium push
+ var scale = 0.75f;
+ force = force.normalized * scale * (1 / (1 + Mathf.Exp(avg-force.magnitude)) - 1 / (1 + Mathf.Exp(avg))); // apply a sigmoid-ish smoothing operation, so only giant forces actually move the spirals
+
+ // if this is the root spiral, then rotate it instead of trying to move it
+ if (s1.parent == null)
+ {
+ // this is the root spiral, so rotate instead of moving
+ var finalAngle = Mathf.Atan2(force.y, force.x); // root spiral is always at 0, 0
+ var currentAngle = Mathf.Atan2(s1.center.y, s1.center.x); // root spiral is always at 0, 0
+ s1.transform.localEulerAngles = new Vector3(0, 0, finalAngle-currentAngle);
+ s1.UpdateChildren();
+
+ continue;
+ }
+
+ // pick the parent point that's closest to center+force, and move to there
+ var spiral = s1;
+ var parentPoints = spiral.parent.GetComponent().GetPoints();
+
+ var idealPoint = spiral.position + force;
+ var bestPointIndex = 0;
+ var bestPointDistance = 99999999f;
+ for (var j = SpiralManipulator.MIN_PARENT_POINT; j < SpiralManipulator.MAX_PARENT_POINT && j < parentPoints.Length; j++)
+ {
+ // don't put this spiral on a point already occupied by a sibling
+ if (j != spiral._parentPointIndex && spiral.parent.pointsOccupiedByChildren.Contains(j)) continue;
+
+ var point = parentPoints[j];
+ point = spiral.parent.transform.TransformPoint(point);
+
+ var dist = Vector2.Distance(point, idealPoint);
+ if (dist < bestPointDistance) {
+ bestPointDistance = dist;
+ bestPointIndex = j;
+ }
+ }
+
+ // limit the distance a spiral can move in a single step
+ bestPointIndex = spiral._parentPointIndex + Mathf.Min(MAX_MOVE_DISTANCE, Mathf.Max(-MAX_MOVE_DISTANCE, bestPointIndex - spiral._parentPointIndex)); // minimize step size to help stability
+
+ // actually move the spiral
+ spiral.PlaceOnParentPoint(bestPointIndex);
+
+ // Enforce bounds
+ if (OutsideBounds(s1))
+ {
+ var start = s1._parentPointIndex;
+ var originalMirror = s1.Mirrored;
+
+ var success = AttemptToPushSpiralInBounds(s1, start);
+ if (!success)
+ {
+ // try flipping it if nothing worked with original mirror state
+ s1.Mirror();
+ success = AttemptToPushSpiralInBounds(s1, start);
+ }
+
+ if (!success)
+ {
+ // if we couldn't put it inside the bounds, put it back how we found it (this increases stability of the rest of the spirals)
+ if (s1.Mirrored != originalMirror) s1.Mirror();
+ s1.PlaceOnParentPoint(start);
+ Logger.LogVerbose("Unable to place spiral " + s1.gameObject.name + " within bounds.");
+ }
+ }
+
+ // record force for parents
+ if (!childForces.ContainsKey(s1.parent)) childForces[s1.parent] = Vector2.zero;
+ childForces[s1.parent] += force;
+ }
+ }
+
+ public void GenerateReverseToposort()
+ {
+ reverseToposortedSpirals = new List();
+ Queue frontierQueue = new Queue();
+ frontierQueue.Enqueue(root);
+
+ while(frontierQueue.Count > 0)
+ {
+ var spiral = frontierQueue.Dequeue();
+ reverseToposortedSpirals.Add(spiral);
+
+ foreach(var child in spiral.children) frontierQueue.Enqueue(child);
+ }
+
+ reverseToposortedSpirals.Reverse();
+ }
+
+ #region overlap handling
+
+ // returns whether there was overlap or not
+ public bool AttemptOverlapResolution()
+ {
+ var overlappingSpirals = FindOverlap();
+ if (overlappingSpirals.x < 0) return false;
+
+ if (!sprialOverlapResolutionPriority.ContainsKey(overlappingSpirals.x)) sprialOverlapResolutionPriority[overlappingSpirals.x] = 0;
+ if (!sprialOverlapResolutionPriority.ContainsKey(overlappingSpirals.y)) sprialOverlapResolutionPriority[overlappingSpirals.y] = 0;
+
+ int mirrorIndex = overlappingSpirals.x;
+ if (sprialOverlapResolutionPriority[overlappingSpirals.y] > sprialOverlapResolutionPriority[overlappingSpirals.x]) mirrorIndex = overlappingSpirals.y;
+
+ this.spirals[mirrorIndex].Mirror();
+ sprialOverlapResolutionPriority[mirrorIndex]--;
+
+ return true;
+ }
+
+ public Vector2Int FindOverlap()
+ {
+ var index = -1;
+ foreach (var s1 in spirals)
+ {
+ index++;
+ if (s1.parent == null) continue;
+
+ var jndex = -1;
+ foreach (var s2 in spirals)
+ {
+ jndex++;
+ if (SpiralsOverlap(s1, s2)) return new Vector2Int(index, jndex);;
+ }
+ }
+
+ return new Vector2Int(-1, -1);
+ }
+
+ public bool SpiralsOverlap(SpiralManipulator s1, SpiralManipulator s2)
+ {
+ if (s1 == s2) return false;
+ if (Vector3.Distance(s1.center, s2.center) > Mathf.Max(s1.NomaiTextLine.GetWorldRadius(), s2.NomaiTextLine.GetWorldRadius())) return false; // no overlap possible - too far away
+
+ var s1Points = s1.NomaiTextLine.GetPoints().Select(p => s1.transform.TransformPoint(p)).ToList();
+ var s2Points = s2.NomaiTextLine.GetPoints().Select(p => s2.transform.TransformPoint(p)).ToList();
+ var s1ThresholdForOverlap = Vector3.Distance(s1Points[0], s1Points[1]);
+ var s2ThresholdForOverlap = Vector3.Distance(s2Points[0], s2Points[1]);
+ var thresholdForOverlap = Mathf.Pow(Mathf.Max(s1ThresholdForOverlap, s2ThresholdForOverlap), 2); // square to save on computation (we'll work in distance squared from here on)
+
+ if (s1.parent == s2) s1Points.RemoveAt(0); // don't consider the base points so that we can check if children overlap their parents
+ if (s2.parent == s1) s2Points.RemoveAt(0); // (note: the base point of a child is always exactly overlapping with one of the parent's points)
+
+ foreach(var p1 in s1Points)
+ {
+ foreach(var p2 in s2Points)
+ {
+ if (Vector3.SqrMagnitude(p1-p2) <= thresholdForOverlap) return true; // s1 and s2 overlap
+ }
+ }
+
+ return false;
+ }
+
+ #endregion overlap handling
+
+ #region bounds handling
+
+ public bool OutsideBounds(SpiralManipulator spiral)
+ {
+ var points = spiral.NomaiTextLine.GetPoints()
+ .Select(p => spiral.transform.TransformPoint(p))
+ .Select(p => spiral.transform.parent.InverseTransformPoint(p))
+ .ToList();
+
+ foreach(var point in points) {
+ if (point.x < minX || point.x > maxX ||
+ point.y < minY || point.y > maxY)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private bool AttemptToPushSpiralInBounds(SpiralManipulator s1, int start)
+ {
+ var range = Mathf.Max(start-SpiralManipulator.MIN_PARENT_POINT, SpiralManipulator.MAX_PARENT_POINT-start);
+
+ for (var i = 1; i <= range; i++)
+ {
+ if (start-i >= SpiralManipulator.MIN_PARENT_POINT)
+ {
+ s1.PlaceOnParentPoint(start-i);
+ if (!OutsideBounds(s1)) return true;
+ }
+
+ if (start+i <= SpiralManipulator.MAX_PARENT_POINT)
+ {
+ s1.PlaceOnParentPoint(start+i);
+ if (!OutsideBounds(s1)) return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void DrawBoundsWithDebugSpheres()
+ {
+ AddDebugShape.AddSphere(this.gameObject, 0.1f, Color.green).transform.localPosition = new Vector3(minX, minY, 0);
+ AddDebugShape.AddSphere(this.gameObject, 0.1f, Color.green).transform.localPosition = new Vector3(minX, maxY, 0);
+ AddDebugShape.AddSphere(this.gameObject, 0.1f, Color.green).transform.localPosition = new Vector3(maxX, maxY, 0);
+ AddDebugShape.AddSphere(this.gameObject, 0.1f, Color.green).transform.localPosition = new Vector3(maxX, minY, 0);
+ AddDebugShape.AddSphere(this.gameObject, 0.1f, Color.red).transform.localPosition = new Vector3(0, 0, 0);
+ }
+
+ #endregion bounds handling
+ }
+
+ public class SpiralManipulator : MonoBehaviour {
+ public SpiralManipulator parent;
+ public List children = new List();
+
+ public HashSet pointsOccupiedByChildren = new HashSet();
+ public int _parentPointIndex = -1;
+
+ public static int MIN_PARENT_POINT = 3;
+ public static int MAX_PARENT_POINT = 26;
+
+ #region properties
+
+ public bool Mirrored { get { return this.transform.localScale.x < 0; } }
+
+ private NomaiTextLine _NomaiTextLine;
+ public NomaiTextLine NomaiTextLine
+ {
+ get
+ {
+ if (_NomaiTextLine == null) _NomaiTextLine = GetComponent();
+ return _NomaiTextLine;
+ }
+ }
+
+ public Vector2 center
+ {
+ get { return NomaiTextLine.GetWorldCenter(); }
+ }
+
+ public Vector2 localPosition
+ {
+ get { return new Vector2(this.transform.localPosition.x, this.transform.localPosition.y); }
+ }
+ public Vector2 position
+ {
+ get { return new Vector2(this.transform.position.x, this.transform.position.y); }
+ }
+
+ #endregion properties
+
+ public SpiralManipulator AddChild(NomaiTextArcBuilder.SpiralProfile profile) {
+ var child = NomaiTextArcArranger.CreateSpiral(profile, this.transform.parent.gameObject);
+
+ var index = Random.Range(MIN_PARENT_POINT, MAX_PARENT_POINT);
+ child.transform.parent = this.transform.parent;
+ child.parent = this;
+ child.PlaceOnParentPoint(index);
+
+ this.children.Add(child);
+ return child;
+ }
+
+ public void Mirror()
+ {
+ this.transform.localScale = new Vector3(-this.transform.localScale.x, 1, 1);
+ if (this.parent != null) this.PlaceOnParentPoint(this._parentPointIndex);
+ }
+
+ public void UpdateChildren()
+ {
+ foreach(var child in this.children)
+ {
+ child.PlaceOnParentPoint(child._parentPointIndex);
+ }
+ }
+
+ public int PlaceOnParentPoint(int parentPointIndex, bool updateChildren=true)
+ {
+ // validate
+ var _points = parent.GetComponent().GetPoints();
+ parentPointIndex = Mathf.Max(0, Mathf.Min(parentPointIndex, _points.Length-1));
+
+ // track occupied points
+ if (this._parentPointIndex != -1) parent.pointsOccupiedByChildren.Remove(this._parentPointIndex);
+ this._parentPointIndex = parentPointIndex;
+ parent.pointsOccupiedByChildren.Add(parentPointIndex);
+
+ // calculate the normal
+ var normal = _points[Mathf.Min(parentPointIndex+1, _points.Length-1)] - _points[Mathf.Max(parentPointIndex-1, 0)];
+ if (parent.transform.localScale.x < 0) normal = new Vector3(normal.x, -normal.y, -normal.z);
+ float rot = Mathf.Atan2(normal.y, normal.x) * Mathf.Rad2Deg;
+
+ // get location of the point
+ var point = _points[parentPointIndex];
+ if (parent.transform.localScale.x < 0) point = new Vector3(-point.x, point.y, point.z);
+
+ // finalize
+ this.transform.localPosition = Quaternion.Euler(0, 0, parent.transform.localEulerAngles.z) * point + parent.transform.localPosition;
+ this.transform.localEulerAngles = new Vector3(0, 0, rot + parent.transform.localEulerAngles.z);
+ if (updateChildren) this.UpdateChildren();
+
+ return parentPointIndex;
+ }
+ }
+}
\ No newline at end of file
diff --git a/NewHorizons/Builder/Props/TranslatorText/NomaiTextArcBuilder.cs b/NewHorizons/Builder/Props/TranslatorText/NomaiTextArcBuilder.cs
new file mode 100644
index 00000000..cb7343fa
--- /dev/null
+++ b/NewHorizons/Builder/Props/TranslatorText/NomaiTextArcBuilder.cs
@@ -0,0 +1,473 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace NewHorizons.Builder.Props
+{
+ public static class NomaiTextArcBuilder {
+ // TODO: stranger arcs
+ // Note: building a wall text (making meshes and arranging) takes 0.1s for an example with 10 spirals
+ // TODO: caching - maybe make a "cachable" annotaion! if cache does not contain results of function, run function, write results to cache file. otherwise return results from cache file.
+ // cache file should be shipped with release but doesn't need to be. if debug mode is enabled, always regen cache, if click regen configs, reload cache
+
+ public static GameObject BuildSpiralGameObject(SpiralProfile profile, string goName="New Nomai Spiral")
+ {
+ var m = new SpiralMesh(profile);
+ m.Randomize();
+ m.updateMesh();
+
+ //
+ // rotate mesh to point up
+ //
+
+ var norm = m.skeleton[1] - m.skeleton[0];
+ float r = Mathf.Atan2(-norm.y, norm.x) * Mathf.Rad2Deg;
+ var ang = -90-r;
+
+ // using m.sharedMesh causes old meshes to disappear for some reason, idk why
+ var mesh = m.mesh;
+ var newVerts = mesh.vertices.Select(v => Quaternion.Euler(-90, 0, 0) * Quaternion.Euler(0, ang, 0) * v).ToArray();
+ mesh.vertices = newVerts;
+ mesh.RecalculateBounds();
+
+ // rotate the skeleton to point up, too
+ var _points = m.skeleton
+ .Select((point) =>
+ Quaternion.Euler(-90, 0, 0) * Quaternion.Euler(0, ang, 0) * (new Vector3(point.x, 0, point.y))
+ )
+ .ToArray();
+
+
+ return BuildSpiralGameObject(_points, mesh, goName);
+ }
+
+ public static GameObject BuildSpiralGameObject(Vector3[] _points, Mesh mesh, string goName="New Nomai Spiral")
+ {
+ var g = new GameObject(goName);
+ g.SetActive(false);
+ g.transform.localPosition = Vector3.zero;
+ g.transform.localEulerAngles = Vector3.zero;
+
+ g.AddComponent().sharedMesh = mesh;
+ g.AddComponent().sharedMaterial = new Material(Shader.Find("Sprites/Default"));
+ g.GetComponent().sharedMaterial.color = Color.magenta;
+
+ var owNomaiTextLine = g.AddComponent();
+
+ owNomaiTextLine._points = _points;
+ owNomaiTextLine._active = true;
+ owNomaiTextLine._prebuilt = false;
+
+ g.SetActive(true);
+ return g;
+ }
+
+ #region spiral shape definitions
+
+ public struct SpiralProfile {
+ // all of the Vector2 params here refer to a range of valid values
+ public string profileName;
+ public Vector2 a;
+ public Vector2 b;
+ public Vector2 startS;
+ public Vector2 endS;
+ public Vector2 skeletonScale;
+ public int numSkeletonPoints;
+ public float uvScale;
+ public float innerWidth; // width at the tip
+ public float outerWidth; // width at the base
+ public Material material;
+ }
+
+ public static SpiralProfile adultSpiralProfile = new SpiralProfile() {
+ profileName="Adult",
+ a = new Vector2(0.5f, 0.5f),
+ b = new Vector2(0.3f, 0.6f),
+ startS = new Vector2(342.8796f, 342.8796f),
+ endS = new Vector2(0, 50f),
+ skeletonScale = 0.75f * new Vector2(0.01f, 0.01f),
+ numSkeletonPoints = 51,
+
+ innerWidth = 0.001f,
+ outerWidth = 0.05f,
+ uvScale = 4.9f,
+ };
+
+ public static SpiralProfile childSpiralProfile = new SpiralProfile() {
+ profileName="Child",
+ a = new Vector2(0.9f, 0.9f),
+ b = new Vector2(0.17f, 0.4f),
+ startS = new Vector2(342.8796f, 342.8796f),
+ endS = new Vector2(35f, 25f),
+ skeletonScale = 0.8f * new Vector2(0.01f, 0.01f),
+ numSkeletonPoints = 51,
+
+ innerWidth = 0.001f/10f,
+ outerWidth = 2f*0.05f,
+ uvScale = 4.9f * 0.55f,
+ };
+
+ public static SpiralProfile strangerSpiralProfile = new SpiralProfile() {
+ profileName="Stranger",
+ a = new Vector2(0.9f, 0.9f), // this value doesn't really matter for this
+ b = new Vector2(5f, 5f),
+ startS = new Vector2(1.8505f, 1.8505f),
+ endS = new Vector2(0, 0),
+ skeletonScale = new Vector2(0.6f, 0.6f),
+ numSkeletonPoints = 17,
+
+ innerWidth = 0.75f,
+ outerWidth = 0.75f,
+ uvScale = 1f/1.8505f,
+
+
+ };
+
+
+ #endregion spiral shape definitions
+
+ #region mesh generation
+
+ public class SpiralMesh: MathematicalSpiral {
+ public List skeleton;
+ public List skeletonOutsidePoints;
+
+ public int numSkeletonPoints = 51; // seems to be Mobius' default
+
+ public float innerWidth = 0.001f; // width at the tip
+ public float outerWidth = 0.05f; // width at the base
+ public float uvScale = 4.9f;
+ private float baseUVScale = 1f / 300f;
+ public float uvOffset = 0;
+
+ public Mesh mesh;
+
+ public SpiralMesh(SpiralProfile profile): base(profile) {
+ this.numSkeletonPoints = profile.numSkeletonPoints;
+ this.innerWidth = profile.innerWidth;
+ this.outerWidth = profile.outerWidth;
+ this.uvScale = profile.uvScale;
+
+ this.uvOffset = UnityEngine.Random.value;
+ }
+
+ public override void Randomize() {
+ base.Randomize();
+ uvOffset = UnityEngine.Random.value; // this way even two spirals that are exactly the same shape will look different (this changes the starting point of the handwriting texture)
+ }
+
+ internal void updateMesh() {
+ skeleton = this.getSkeleton(numSkeletonPoints);
+ skeletonOutsidePoints = this.getSkeletonOutsidePoints(numSkeletonPoints);
+
+ List vertsSide1 = skeleton.Select((skeletonPoint, index) => {
+ Vector3 normal = new Vector3(cos(skeletonPoint.z), 0, sin(skeletonPoint.z));
+ float width = lerp(((float) index) / ((float) skeleton.Count()), outerWidth, innerWidth);
+
+ return new Vector3(skeletonPoint.x, 0, skeletonPoint.y) + width * normal;
+ }).ToList();
+
+ List vertsSide2 = skeleton.Select((skeletonPoint, index) => {
+ Vector3 normal = new Vector3(cos(skeletonPoint.z), 0, sin(skeletonPoint.z));
+ float width = lerp(((float) index) / ((float) skeleton.Count()), outerWidth, innerWidth);
+
+ return new Vector3(skeletonPoint.x, 0, skeletonPoint.y) - width * normal;
+ }).ToList();
+
+ Vector3[] newVerts = vertsSide1.Zip(vertsSide2, (f, s) => new [] {
+ f,
+ s
+ }).SelectMany(f =>f).ToArray(); // interleave vertsSide1 and vertsSide2
+
+ List triangles = new List();
+ for (int i = 0; i
+ public List getSkeleton(int numPoints) {
+ var skeleton =
+ WalkAlongSpiral(numPoints)
+ .Select(input => {
+ float inputS = input.y;
+ var skeletonPoint = getDrawnSpiralPointAndNormal(inputS);
+ return skeletonPoint;
+ })
+ .Reverse()
+ .ToList();
+
+ return skeleton;
+ }
+
+ public List getSkeletonOutsidePoints(int numPoints) {
+ var outsidePoints =
+ WalkAlongSpiral(numPoints)
+ .Select(input => {
+ float inputT = input.x;
+ float inputS = input.y;
+
+ var skeletonPoint = getDrawnSpiralPointAndNormal(inputS);
+
+ var deriv = spiralDerivative(inputT);
+ var outsidePoint = new Vector2(skeletonPoint.x, skeletonPoint.y) - (new Vector2(-deriv.y, deriv.x)).normalized * 0.1f;
+ return outsidePoint;
+ })
+ .Reverse()
+ .ToList();
+
+ return outsidePoints;
+ }
+
+ // generate a list of evenly distributed over the whole spiral. `numPoints` number of pairs are generated
+ public IEnumerable WalkAlongSpiral(int numPoints) {
+ var endT = tFromArcLen(startS);
+ var startT = tFromArcLen(endS);
+ var rangeT = endT - startT;
+
+ for (int i = 0; i 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,
+ 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 2fbf0f0a..5ff5625e 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.
@@ -638,7 +644,7 @@ namespace NewHorizons.External.Modules
/// Additional information about each arc in the text
///
public NomaiTextArcInfo[] arcInfo;
-
+
///
/// The normal vector for this object. Used for writing on walls and positioning computers.
///
@@ -703,6 +709,11 @@ namespace NewHorizons.External.Modules
[EnumMember(Value = @"stranger")] Stranger = 2
}
+
+ ///
+ /// Whether to skip modifying this spiral's placement, and instead keep the automatically determined placement.
+ ///
+ public bool keepAutoPlacement;
///
/// Whether to flip the spiral from left-curling to right-curling or vice versa.
diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs
index e0912246..ee7cbba5 100644
--- a/NewHorizons/Handlers/PlanetCreationHandler.cs
+++ b/NewHorizons/Handlers/PlanetCreationHandler.cs
@@ -136,6 +136,8 @@ namespace NewHorizons.Handlers
public static bool LoadBody(NewHorizonsBody body, bool defaultPrimaryToSun = false)
{
+ body.LoadCache();
+
// I don't remember doing this why is it exceptions what am I doing
GameObject existingPlanet = null;
try
@@ -202,6 +204,7 @@ namespace NewHorizons.Handlers
catch (Exception ex)
{
Logger.LogError($"Couldn't make quantum state for [{body.Config.name}]:\n{ex}");
+ body.UnloadCache();
return false;
}
}
@@ -217,6 +220,7 @@ namespace NewHorizons.Handlers
catch (Exception e)
{
Logger.LogError($"Couldn't update body {body.Config?.name}:\n{e}");
+ body.UnloadCache();
return false;
}
}
@@ -237,8 +241,12 @@ namespace NewHorizons.Handlers
{
Logger.Log($"Creating [{body.Config.name}]");
var planetObject = GenerateBody(body, defaultPrimaryToSun);
- if (planetObject == null) return false;
- planetObject.SetActive(true);
+ planetObject?.SetActive(true);
+ if (planetObject == null)
+ {
+ body.UnloadCache();
+ return false;
+ }
var ao = planetObject.GetComponent();
@@ -250,6 +258,7 @@ namespace NewHorizons.Handlers
catch (Exception e)
{
Logger.LogError($"Couldn't generate body {body.Config?.name}:\n{e}");
+ body.UnloadCache();
return false;
}
}
@@ -264,6 +273,7 @@ namespace NewHorizons.Handlers
Logger.LogError($"Error in event handler for OnPlanetLoaded on body {body.Config.name}: {e}");
}
+ body.UnloadCache(true);
return true;
}
@@ -628,7 +638,7 @@ namespace NewHorizons.Handlers
if (body.Config.Props != null)
{
- PropBuildManager.Make(go, sector, rb, body.Config, body.Mod);
+ PropBuildManager.Make(go, sector, rb, body);
}
if (body.Config.Volumes != null)
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/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json
index bc3099a1..92b1e581 100644
--- a/NewHorizons/Schemas/body_schema.json
+++ b/NewHorizons/Schemas/body_schema.json
@@ -947,7 +947,7 @@
"$ref": "#/definitions/GeyserInfo"
}
},
- "nomaiText": {
+ "translatorText": {
"type": "array",
"description": "Add translatable text to this planet",
"items": {
@@ -1345,6 +1345,10 @@
"type": "object",
"additionalProperties": false,
"properties": {
+ "keepAutoPlacement": {
+ "type": "boolean",
+ "description": "Whether to skip modifying this spiral's placement, and instead keep the automatically determined placement."
+ },
"mirror": {
"type": "boolean",
"description": "Whether to flip the spiral from left-curling to right-curling or vice versa."
diff --git a/NewHorizons/Utility/Cache.cs b/NewHorizons/Utility/Cache.cs
new file mode 100644
index 00000000..ba062551
--- /dev/null
+++ b/NewHorizons/Utility/Cache.cs
@@ -0,0 +1,70 @@
+using Newtonsoft.Json;
+using OWML.Common;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.Serialization;
+
+namespace NewHorizons.Utility
+{
+ public class Cache
+ {
+ string filepath;
+ IModBehaviour mod;
+ Dictionary data = new Dictionary();
+ HashSet accessedKeys = new HashSet();
+
+ public Cache(IModBehaviour mod, string cacheFilePath)
+ {
+ this.mod = mod;
+ this.filepath = cacheFilePath;
+ var fullPath = mod.ModHelper.Manifest.ModFolderPath + cacheFilePath;
+
+ if (!File.Exists(fullPath))
+ {
+ Logger.LogWarning("Cache file not found! Cache path: " + cacheFilePath);
+ data = new Dictionary();
+ return;
+ }
+
+ var json = File.ReadAllText(fullPath);
+ data = JsonConvert.DeserializeObject>(json);
+ // the code above does exactly the same thing that the code below does, but the below for some reason always returns null. no clue why
+ // data = mod.ModHelper.Storage.Load>(filepath);
+ }
+
+ public void WriteToFile()
+ {
+ mod.ModHelper.Storage.Save>(data, filepath);
+ }
+
+ public bool ContainsKey(string key)
+ {
+ return data.ContainsKey(key);
+ }
+
+ public T Get(string key)
+ {
+ accessedKeys.Add(key);
+ var json = data[key];
+ return JsonConvert.DeserializeObject(json);
+ }
+
+ public void Set(string key, T value)
+ {
+ accessedKeys.Add(key);
+ data[key] = JsonConvert.SerializeObject(value);
+ }
+
+ public void ClearUnaccessed()
+ {
+ var keys = data.Keys.ToList();
+ foreach(var key in keys)
+ {
+ if (accessedKeys.Contains(key)) continue;
+ data.Remove(key);
+ }
+ }
+ }
+}
diff --git a/NewHorizons/Utility/DebugMenu/DebugMenuNomaiText.cs b/NewHorizons/Utility/DebugMenu/DebugMenuNomaiText.cs
index 99843836..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,
@@ -266,7 +266,7 @@ namespace NewHorizons.Utility.DebugMenu
GUILayout.Label("Variation");
GUILayout.BeginHorizontal();
- var varietyCount = GetVarietyCountForType(spiralMeta.spiral.type);
+ var varietyCount = 1;
//var newVariation = int.Parse(GUILayout.TextField(spiralMeta.spiral.variation+""));
//newVariation = Mathf.Min(Mathf.Max(0, newVariation), varietyCount);
//if (newVariation != spiralMeta.spiral.variation) changed = true;
@@ -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";
@@ -469,20 +469,6 @@ namespace NewHorizons.Utility.DebugMenu
});
}
- private int GetVarietyCountForType(NomaiTextArcInfo.NomaiTextArcType type)
- {
- switch(type)
- {
- case NomaiTextArcInfo.NomaiTextArcType.Stranger:
- return NomaiTextBuilder.GetGhostArcPrefabs().Count();
- case NomaiTextArcInfo.NomaiTextArcType.Child:
- return NomaiTextBuilder.GetChildArcPrefabs().Count();
- default:
- case NomaiTextArcInfo.NomaiTextArcType.Adult:
- return NomaiTextBuilder.GetArcPrefabs().Count();
- }
- }
-
void UpdateConversationTransform(ConversationMetadata conversationMetadata, GameObject sectorParent)
{
var nomaiWallTextObj = conversationMetadata.conversationGo;
diff --git a/NewHorizons/Utility/MMesh.cs b/NewHorizons/Utility/MMesh.cs
new file mode 100644
index 00000000..6ab39722
--- /dev/null
+++ b/NewHorizons/Utility/MMesh.cs
@@ -0,0 +1,51 @@
+using System.ComponentModel;
+using System.Linq;
+using Newtonsoft.Json;
+using UnityEngine;
+namespace NewHorizons.Utility
+{
+ [JsonObject]
+ public class MMesh
+ {
+ public MMesh(MVector3[] vertices, int[] triangles, MVector3[] normals, MVector2[] uv, MVector2[] uv2)
+ {
+ this.vertices = vertices;
+ this.triangles = triangles;
+ this.normals = normals;
+ this.uv = uv;
+ this.uv2 = uv2;
+ }
+
+ public MVector3[] vertices;
+ public int[] triangles;
+ public MVector3[] normals;
+ public MVector2[] uv;
+ public MVector2[] uv2;
+
+ public static implicit operator MMesh(Mesh mesh)
+ {
+ return new MMesh
+ (
+ mesh.vertices.Select(v => (MVector3)v).ToArray(),
+ mesh.triangles,
+ mesh.normals.Select(v => (MVector3)v).ToArray(),
+ mesh.uv.Select(v => (MVector2)v).ToArray(),
+ mesh.uv2.Select(v => (MVector2)v).ToArray()
+ );
+ }
+
+ public static implicit operator Mesh(MMesh mmesh)
+ {
+ var mesh = new Mesh();
+
+ mesh.vertices = mmesh.vertices.Select(mv => (Vector3)mv).ToArray();
+ mesh.triangles = mmesh.triangles;
+ mesh.normals = mmesh.normals.Select(mv => (Vector3)mv).ToArray();
+ mesh.uv = mmesh.uv.Select(mv => (Vector2)mv).ToArray();
+ mesh.uv2 = mmesh.uv2.Select(mv => (Vector2)mv).ToArray();
+ mesh.RecalculateBounds();
+
+ return mesh;
+ }
+ }
+}
diff --git a/NewHorizons/Utility/MeshUtilities.cs b/NewHorizons/Utility/MeshUtilities.cs
new file mode 100644
index 00000000..b9f2d320
--- /dev/null
+++ b/NewHorizons/Utility/MeshUtilities.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace NewHorizons.Utility
+{
+ public class MeshUtilities
+ {
+ public static Mesh RectangleMeshFromCorners(Vector3[] corners)
+ {
+ MVector3[] verts = corners.Select(v => (MVector3)v).ToArray();
+
+ int[] triangles = new int[] {
+ 0, 1, 2,
+ 1, 3, 2,
+ };
+
+ MVector3[] normals = new MVector3[verts.Length];
+ for (int i = 0; i