diff --git a/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs b/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs new file mode 100644 index 00000000..a65849da --- /dev/null +++ b/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs @@ -0,0 +1,124 @@ +using NewHorizons.Builder.Props; +using NewHorizons.Components.Orbital; +using NewHorizons.Handlers; +using NewHorizons.Utility; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace NewHorizons.Builder.Body +{ + public static class BrambleDimensionBuilder + { + public static readonly float BASE_DIMENSION_RADIUS = 1705f; + + // location of all vanilla bramble dimensions + //-9116.795 -19873.44 2480.327 + //-8460.688 -19873.44 6706.444 + //-5015.165 -19873.44 4142.816 + //-8993.414 -17059.44 4521.747 + //-7044.813 -17135.44 3272.149 + //-6904.48 -17048.44 5574.479 + //-11096.95 -22786.44 4657.534 + //-8716.807 -22786.44 4496.394 + + + // keys are all node names that have been referenced as an exit by at least one dimension but do not (yet) exist + // values are all dimensions' warp controllers that link to a given dimension + // unpairedNodes[name of node that doesn't exist yet] => List{warp controller for dimension that exits to that node, ...} + private static Dictionary> _unpairedDimensions = new(); + + public static void Init() + { + // Just in case something went wrong and a dimension never got paired last time + _unpairedDimensions.Clear(); + } + + public static GameObject Make(NewHorizonsBody body) + { + var config = body.Config.Bramble.dimension; + + // spawn the dimension body + var dimensionPrefab = SearchUtilities.Find("DB_HubDimension_Body"); + var dimension = dimensionPrefab.InstantiateInactive(); + + PlanetCreationHandler.UpdateBodyOrbit(body, dimension); + + // fix name + var ao = dimension.GetComponent(); + ao.IsDimension = true; + var name = body.Config.name ?? "Custom Bramble Dimension"; + ao._customName = name; + ao._name = AstroObject.Name.CustomString; + dimension.name = name.Replace(" ", "").Replace("'", "") + "_Body"; + + // set position + ao.transform.position = body.Config.Orbit.staticPosition; + + // fix children's names and remove base game props (mostly just bramble nodes that are children to Interactibles) and set up the OuterWarp child + var dimensionSector = dimension.FindChild("Sector_HubDimension"); + dimensionSector.name = "Sector"; + var atmo = dimensionSector.FindChild("Atmosphere_HubDimension"); + var geom = dimensionSector.FindChild("Geometry_HubDimension"); + var vols = dimensionSector.FindChild("Volumes_HubDimension"); + var efxs = dimensionSector.FindChild("Effects_HubDimension"); + var intr = dimensionSector.FindChild("Interactables_HubDimension"); + var exitWarps = intr.FindChild("OuterWarp_Hub"); + + exitWarps.name = "OuterWarp"; + exitWarps.transform.parent = dimensionSector.transform; + atmo.name = "Atmosphere"; + geom.name = "Geometry"; // disable this? + vols.name = "Volumes"; + efxs.name = "Effects"; + intr.name = "Interactibles"; + GameObject.Destroy(intr); + + // set up warps + var outerFogWarpVolume = exitWarps.GetComponent(); + outerFogWarpVolume._senderWarps.Clear(); + outerFogWarpVolume._linkedInnerWarpVolume = null; + outerFogWarpVolume._name = OuterFogWarpVolume.Name.None; + + PairExit(config.linksTo, outerFogWarpVolume); + + // change fog color + if (body.Config.Bramble.dimension.fogTint != null) + { + var fogGO = atmo.FindChild("FogSphere_Hub"); + var fog = fogGO.GetComponent(); + fog.fogTint = body.Config.Bramble.dimension.fogTint.ToColor(); + } + + dimension.SetActive(true); + + return dimension; + } + + public static void PairExit(string exitName, OuterFogWarpVolume warpController) + { + if (!BrambleNodeBuilder.namedNodes.ContainsKey(exitName)) + { + if (!_unpairedDimensions.ContainsKey(exitName)) _unpairedDimensions[exitName] = new(); + _unpairedDimensions[exitName].Add(warpController); + return; + } + + warpController._linkedInnerWarpVolume = BrambleNodeBuilder.namedNodes[exitName]; + } + + public static void FinishPairingDimensionsForExitNode(string nodeName) + { + if (!_unpairedDimensions.ContainsKey(nodeName)) return; + + var warpControllers = _unpairedDimensions[nodeName].ToList(); + foreach (var dimensionWarpController in warpControllers) + { + PairExit(nodeName, dimensionWarpController); + } + + //unpairedDimensions.Remove(nodeName); + } + + } +} diff --git a/NewHorizons/Builder/Props/BrambleNodeBuilder.cs b/NewHorizons/Builder/Props/BrambleNodeBuilder.cs new file mode 100644 index 00000000..e1da4ad6 --- /dev/null +++ b/NewHorizons/Builder/Props/BrambleNodeBuilder.cs @@ -0,0 +1,277 @@ +using NewHorizons.Builder.Body; +using NewHorizons.Handlers; +using NewHorizons.Utility; +using OWML.Common; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static NewHorizons.External.Modules.BrambleModule; +using static NewHorizons.External.Modules.SignalModule; + +namespace NewHorizons.Builder.Props +{ + + // TODO + //3) support for existing dimensions? + //5) test whether nodes can lead to vanilla dimensions + + public static class BrambleNodeBuilder + { + // keys are all dimension names that have been referenced by at least one node but do not (yet) exist + // values are all nodes' warp controllers that link to a given dimension + // unpairedNodes[name of dimension that doesn't exist yet] => List{warp controller for node that links to that dimension, ...} + private static Dictionary> _unpairedNodes = new(); + private static Dictionary> _propogatedSignals = null; + + public static readonly Dictionary namedNodes = new(); + public static readonly Dictionary builtBrambleNodes = new(); + + public static void Init() + { + _unpairedNodes.Clear(); + _propogatedSignals.Clear(); + namedNodes.Clear(); + builtBrambleNodes.Clear(); + } + + public static void FinishPairingNodesForDimension(string dimensionName, AstroObject dimensionAO = null) + { + if (!_unpairedNodes.ContainsKey(dimensionName)) return; + + foreach (var nodeWarpController in _unpairedNodes[dimensionName]) + { + PairEntrance(nodeWarpController, dimensionName, dimensionAO); + } + + _unpairedNodes.Remove(dimensionName); + } + + private static void RecordUnpairedNode(InnerFogWarpVolume warpVolume, string linksTo) + { + if (!_unpairedNodes.ContainsKey(linksTo)) _unpairedNodes[linksTo] = new(); + + _unpairedNodes[linksTo].Add(warpVolume); + } + + private static OuterFogWarpVolume GetOuterFogWarpVolumeFromAstroObject(GameObject go) + { + var outerWarpGO = go.FindChild("Sector/OuterWarp"); + if (outerWarpGO == null) return null; + + var outerFogWarpVolume = outerWarpGO.GetComponent(); + return outerFogWarpVolume; + } + + private static void PropogateSignals() + { + // The purpose of this function is to determine which signals any given node should play, based on which dimension it links to + // you know how the main dark bramble node, the one that forms the core of the planet, plays Feldspar's harmonica signal, even though Feldspar isn't in the dimension that the node links directly to? + // that's what this function is for. it would determine that the main node should play Feldspar's signal + + // New Strategy (thanks Damian): + // 1) Run Floyd-Warshall on the dimensions (where each dimension is a vertex and each node is an edge) + // 2) For each dimension A, if it's possible to reach dimension B, add dimension B's signals to the list propogatedSignals[A] + + var allDimensions = PlanetCreationHandler.allBodies.Where(body => body?.Config?.Bramble?.dimension != null).Select(body => body.Config).ToList(); + + // + // Floyd Warshall + // + + // access will be our final answer - if access[i, j], then nodes linking to dimension i should display all of dimension j's signals + var access = new bool[allDimensions.Count(), allDimensions.Count()]; + + var dimensionNameToIndex = new Dictionary(); + for (int dimensionIndex = 0; dimensionIndex < allDimensions.Count(); dimensionIndex++) dimensionNameToIndex[allDimensions[dimensionIndex].name] = dimensionIndex; + + // set up the direct links (ie, if dimension 0 contains a node that links to dimension 3, set access[0, 3] = true) + for (int dimensionIndex = 0; dimensionIndex < allDimensions.Count(); dimensionIndex++) + { + var dimension = allDimensions[dimensionIndex]; + if (dimension.Bramble.nodes == null) continue; + foreach (var node in dimension.Bramble.nodes) + { + var destinationDimensionIndex = dimensionNameToIndex[node.linksTo]; + access[dimensionIndex, destinationDimensionIndex] = true; + } + } + + // a node that links to dimension A should display all of dimension A's signals, so for the purposes of our function, we need to say that dimension A links to dimension A + for (int dimensionIndex = 0; dimensionIndex < allDimensions.Count(); dimensionIndex++) access[dimensionIndex, dimensionIndex] = true; + + // The actual Floyd-Warshall - determine whether each pair of dimensions link indirectly (eg if A->B->C, then after this step, access[A, C] = true) + for (int k = 0; k < allDimensions.Count(); k++) + for (int i = 0; i < allDimensions.Count(); i++) + for (int j = 0; j < allDimensions.Count(); j++) + if (access[i, k] && access[k, j]) + access[i, j] = true; + + // + // Build the list of dimensionName -> List + // + + // this dictionary lists all the signals a given node should have, depending on the dimension it links to + // ie, if a node links to "dimension1", then that node should spawn all of the signals in the list propogatedSignals["dimension1"] + _propogatedSignals = new Dictionary>(); + foreach (var dimension in allDimensions) + { + _propogatedSignals[dimension.name] = new(); + var dimensionIndex = dimensionNameToIndex[dimension.name]; + + foreach (var destinationDimension in allDimensions) + { + if (destinationDimension.Signal?.signals == null) continue; + + var destinationIndex = dimensionNameToIndex[destinationDimension.name]; + if (access[dimensionIndex, destinationIndex]) + { + _propogatedSignals[dimension.name].AddRange(destinationDimension.Signal.signals); + } + } + } + } + + private static bool PairEntrance(InnerFogWarpVolume nodeWarp, string destinationName, AstroObject dimensionAO = null) + { + var destinationAO = dimensionAO ?? AstroObjectLocator.GetAstroObject(destinationName); // find child "Sector/OuterWarp" + if (destinationAO == null) return false; + + // link the node's warp volume to the destination's + var destination = GetOuterFogWarpVolumeFromAstroObject(destinationAO.gameObject); + if (destination == null) return false; + + nodeWarp._linkedOuterWarpVolume = destination; + destination.RegisterSenderWarp(nodeWarp); + return true; + } + + + // DB_EscapePodDimension_Body/Sector_EscapePodDimension/Interactables_EscapePodDimension/InnerWarp_ToAnglerNest // need to change the light shaft color + // DB_ExitOnlyDimension_Body/Sector_ExitOnlyDimension/Interactables_ExitOnlyDimension/InnerWarp_ToExitOnly // need to change the colors + // DB_HubDimension_Body/Sector_HubDimension/Interactables_HubDimension/InnerWarp_ToCluster // need to delete the child "Signal_Harmonica" + + public static void Make(GameObject go, Sector sector, BrambleNodeInfo[] configs, IModBehaviour mod) + { + foreach(var config in configs) + { + Make(go, sector, config, mod); + } + } + + public static GameObject Make(GameObject go, Sector sector, BrambleNodeInfo config, IModBehaviour mod) + { + // + // spawn the bramble node + // + + var brambleSeedPrefabPath = "DB_PioneerDimension_Body/Sector_PioneerDimension/Interactables_PioneerDimension/SeedWarp_ToPioneer (1)"; + var brambleNodePrefabPath = "DB_HubDimension_Body/Sector_HubDimension/Interactables_HubDimension/InnerWarp_ToCluster"; + + var path = config.isSeed ? brambleSeedPrefabPath : brambleNodePrefabPath; + var brambleNode = DetailBuilder.MakeDetail(go, sector, path, config.position, config.rotation, 1, false); + brambleNode.name = "Bramble Node to " + config.linksTo; + var warpController = brambleNode.GetComponent(); + + // this node comes with Feldspar's signal, we don't want that though + GameObject.Destroy(brambleNode.FindChild("Signal_Harmonica")); + + // + // change the colors + // + + if (config.isSeed) SetSeedColors(brambleNode, config.fogTint.ToColor(), config.lightTint.ToColor()); + else SetNodeColors(brambleNode, config.fogTint.ToColor(), config.lightTint.ToColor()); + + // + // set up warps + // + + warpController._sector = sector; + warpController._attachedBody = go.GetComponent(); // I don't think this is necessary, it seems to be set correctly on its own + warpController._containerWarpVolume = GetOuterFogWarpVolumeFromAstroObject(go); // the OuterFogWarpVolume of the dimension this node is inside of (null if this node is not inside of a bramble dimension (eg it's sitting on a planet or something)) + var success = PairEntrance(warpController, config.linksTo); + if (!success) RecordUnpairedNode(warpController, config.linksTo); + + warpController.Awake(); // I can't spawn this game object disabled, but Awake needs to run after _sector is set. That means I need to call Awake myself + + // + // Cleanup for dimension exits + // + if (config.name != null) + { + namedNodes[config.name] = warpController; + BrambleDimensionBuilder.FinishPairingDimensionsForExitNode(config.name); + } + + // + // Make signals + // + if (_propogatedSignals == null) PropogateSignals(); + foreach (var signalConfig in _propogatedSignals[config.linksTo]) + { + var signalGO = SignalBuilder.Make(go, sector, signalConfig, mod); + signalGO.GetComponent()._identificationDistance = 0; + signalGO.GetComponent()._sourceRadius = 1; + signalGO.transform.position = brambleNode.transform.position; + signalGO.transform.parent = brambleNode.transform; + } + + // Done! + return brambleNode; + } + + public static void SetNodeColors(GameObject brambleNode, Color fogTint, Color lightTint) + { + if (fogTint != null) + { + var fogRenderer = brambleNode.GetComponent(); + + fogRenderer._fogColor = fogTint; + fogRenderer._useFarFogColor = false; + } + + if (lightTint != null) + { + var lightShafts = brambleNode.FindChild("Effects/DB_BrambleLightShafts"); + + var lightShaft1 = lightShafts.FindChild("BrambleLightShaft1"); + var mat = lightShaft1.GetComponent().material; + mat.color = lightTint; + + for (int i = 1; i <= 6; i++) + { + var lightShaft = lightShafts.FindChild($"BrambleLightShaft{i}"); + lightShaft.GetComponent().sharedMaterial = mat; + } + } + } + + public static void SetSeedColors(GameObject brambleSeed, Color fogTint, Color lightTint) + { + if (fogTint != null) + { + var fogRenderer = brambleSeed.FindChild("VolumetricFogSphere (2)"); + + var fogMeshRenderer = fogRenderer.GetComponent(); + var mat = fogMeshRenderer.material; + mat.color = fogTint; + fogMeshRenderer.sharedMaterial = mat; + } + + if (lightTint != null) + { + var lightShafts = brambleSeed.FindChild("Terrain_DB_BrambleSphere_Seed_V2 (2)/DB_SeedLightShafts"); + + var lightShaft1 = lightShafts.FindChild("DB_SeedLightShafts1"); + var mat = lightShaft1.GetComponent().material; + mat.color = lightTint; + + for (int i = 1; i <= 6; i++) + { + var lightShaft = lightShafts.FindChild($"DB_SeedLightShafts{i}"); + lightShaft.GetComponent().sharedMaterial = mat; + } + } + } + } +} diff --git a/NewHorizons/Builder/Props/SignalBuilder.cs b/NewHorizons/Builder/Props/SignalBuilder.cs index a245958b..87b2e7c3 100644 --- a/NewHorizons/Builder/Props/SignalBuilder.cs +++ b/NewHorizons/Builder/Props/SignalBuilder.cs @@ -133,7 +133,7 @@ namespace NewHorizons.Builder.Props } } - public static void Make(GameObject planetGO, Sector sector, SignalModule.SignalInfo info, IModBehaviour mod) + public static GameObject Make(GameObject planetGO, Sector sector, SignalModule.SignalInfo info, IModBehaviour mod) { var signalGO = new GameObject($"Signal_{info.name}"); signalGO.SetActive(false); @@ -167,7 +167,7 @@ namespace NewHorizons.Builder.Props if (clip == null) { Logger.LogError($"Couldn't find AudioClip {info.audioClip} or AudioFile {info.audioFilePath}"); - return; + return null; } audioSignal.SetSector(sector); @@ -180,7 +180,8 @@ namespace NewHorizons.Builder.Props audioSignal._revealFactID = info.reveals; audioSignal._onlyAudibleToScope = info.onlyAudibleToScope; audioSignal._identificationDistance = info.identificationRadius; - audioSignal._canBePickedUpByScope = true; + audioSignal._canBePickedUpByScope = true; + audioSignal._outerFogWarpVolume = planetGO.GetComponentInChildren(); // shouldn't break non-bramble signals source.clip = clip; source.loop = true; @@ -218,7 +219,9 @@ namespace NewHorizons.Builder.Props audioSignalDetectionTrigger._trigger = owTriggerVolume; signalGO.SetActive(true); - signalDetectionGO.SetActive(true); + signalDetectionGO.SetActive(true); + + return signalGO; } private static SignalFrequency StringToFrequency(string str) diff --git a/NewHorizons/Components/Orbital/NHAstroObject.cs b/NewHorizons/Components/Orbital/NHAstroObject.cs index 29c79208..9fe7e7f2 100644 --- a/NewHorizons/Components/Orbital/NHAstroObject.cs +++ b/NewHorizons/Components/Orbital/NHAstroObject.cs @@ -1,4 +1,4 @@ -using NewHorizons.External.Modules; +using NewHorizons.External.Modules; namespace NewHorizons.Components.Orbital { public class NHAstroObject : AstroObject, IOrbitalParameters @@ -10,6 +10,7 @@ namespace NewHorizons.Components.Orbital public float argumentOfPeriapsis { get; set; } public float trueAnomaly { get; set; } public bool HideDisplayName { get; set; } + public bool IsDimension { get; set; } public void SetOrbitalParametersFromConfig(OrbitModule orbit) { diff --git a/NewHorizons/External/Configs/PlanetConfig.cs b/NewHorizons/External/Configs/PlanetConfig.cs index 2f64b1b9..e571e431 100644 --- a/NewHorizons/External/Configs/PlanetConfig.cs +++ b/NewHorizons/External/Configs/PlanetConfig.cs @@ -7,6 +7,7 @@ using NewHorizons.Builder.Orbital; using NewHorizons.External.Modules; using NewHorizons.External.Modules.VariableSize; using Newtonsoft.Json; +using Logger = NewHorizons.Utility.Logger; namespace NewHorizons.External.Configs { @@ -31,6 +32,11 @@ namespace NewHorizons.External.Configs /// public BaseModule Base; + /// + /// Add bramble nodes to this planet and/or make this planet a bramble dimension + /// + public BrambleModule Bramble; + /// /// Set to a higher number if you wish for this body to be built sooner /// @@ -171,12 +177,18 @@ namespace NewHorizons.External.Configs if (ReferenceFrame == null) ReferenceFrame = new ReferenceFrameModule(); } - public void MigrateAndValidate() + public void Validate() { - // Validate + // If we can correct a part of the config, do it + // If it cannot be solved, throw an exception if (Base.centerOfSolarSystem) Orbit.isStatic = true; if (Atmosphere?.clouds?.lightningGradient != null) Atmosphere.clouds.hasLightning = true; + if (Bramble?.dimension != null && Orbit?.staticPosition == null) throw new Exception($"Dimension {name} must have Orbit.staticPosition defined."); + if (Orbit?.staticPosition != null) Orbit.isStatic = true; + } + public void Migrate() + { // Backwards compatability // Should be the only place that obsolete things are referenced #pragma warning disable 612, 618 diff --git a/NewHorizons/External/Modules/BrambleModule.cs b/NewHorizons/External/Modules/BrambleModule.cs new file mode 100644 index 00000000..db1856eb --- /dev/null +++ b/NewHorizons/External/Modules/BrambleModule.cs @@ -0,0 +1,81 @@ +using NewHorizons.Utility; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules +{ + + [JsonObject] + public class BrambleModule + { + /// + /// Defining this value will make this body a bramble dimension. Leave it null to not do that. + /// + public BrambleDimensionInfo dimension; + + /// + /// Place nodes/seeds that take you to other bramble dimensions + /// + public BrambleNodeInfo[] nodes; + + + [JsonObject] + public class BrambleDimensionInfo + { + /// + /// The color of the fog inside this dimension. Leave blank for the default yellowish color + /// + public MColor fogTint; + + /// + /// The name of the *node* that the player is taken to when exiting this dimension. + /// + public string linksTo; + } + + + [JsonObject] + public class BrambleNodeInfo + { + /// + /// The physical position of the node + /// + public MVector3 position; + + /// + /// The physical rotation of the node + /// + public MVector3 rotation; + + /// + /// The name of the planet that hosts the dimension this node links to + /// + public string linksTo; + + /// + /// The name of this node. Only required if this node should serve as an exit. + /// + public string name; + + /// + /// Set this to true to make this node a seed instead of a node the player can enter + /// + [DefaultValue(false)] public bool isSeed = false; + + /// + /// The color of the fog inside the node. Leave blank for the default yellowish color (default: 131, 124, 105, 255) + /// + public MColor fogTint; + + /// + /// The color of the shafts of light coming from the entrances to the node. Leave blank for the default yellowish color + /// + public MColor lightTint; + } + } +} diff --git a/NewHorizons/External/Modules/OrbitModule.cs b/NewHorizons/External/Modules/OrbitModule.cs index 7438b841..c56ed87c 100644 --- a/NewHorizons/External/Modules/OrbitModule.cs +++ b/NewHorizons/External/Modules/OrbitModule.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using NewHorizons.Components.Orbital; using NewHorizons.Utility; @@ -8,7 +8,12 @@ namespace NewHorizons.External.Modules { [JsonObject] public class OrbitModule : IOrbitalParameters - { + { + /// + /// Specify this if you want the body to remain stationary at a given location (ie not orbit its parent). Required for Bramble dimensions + /// + public MVector3 staticPosition; + /// /// The name of the body this one will orbit around /// diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index a2a36aab..f8716fbb 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -23,6 +23,21 @@ namespace NewHorizons.Handlers private static Dictionary ExistingAOConfigs; private static Dictionary _dict; + private static Dictionary _dimensions; + + public static List allBodies; + + public static NewHorizonsBody GetNewHorizonsBody(AstroObject ao) + { + if (ao is NHAstroObject nhAO) + { + if (_dict.ContainsKey(nhAO)) return _dict[nhAO]; + } + + if (!_dimensions.ContainsKey(ao)) return null; + + return _dimensions[ao]; + } public static void Init(List bodies) { @@ -30,6 +45,8 @@ namespace NewHorizons.Handlers ExistingAOConfigs = new Dictionary(); _dict = new Dictionary(); + _dimensions = new Dictionary(); + allBodies = bodies; // Set up stars // Need to manage this when there are multiple stars @@ -223,7 +240,11 @@ namespace NewHorizons.Handlers var planetObject = GenerateBody(body, defaultPrimaryToSun); if (planetObject == null) return false; planetObject.SetActive(true); - _dict.Add(planetObject.GetComponent(), body); + + var ao = planetObject.GetComponent(); + + if (!ao.IsDimension) _dict.Add(ao, body); + else _dimensions.Add(ao, body); } catch (Exception e) { @@ -268,6 +289,44 @@ namespace NewHorizons.Handlers // Only called when making new planets public static GameObject GenerateBody(NewHorizonsBody body, bool defaultPrimaryToSun = false) { + if (body.Config?.Bramble?.dimension != null) + { + if (body.Config?.Orbit?.staticPosition == null) + { + Logger.LogError($"Unable to build bramble dimension {body.Config?.name} because it does not have Orbit.staticPosition defined."); + return null; + } + + return GenerateBrambleDimensionBody(body); + } + else + { + return GenerateStandardBody(body, defaultPrimaryToSun); + } + } + + public static GameObject GenerateBrambleDimensionBody(NewHorizonsBody body) + { + var go = BrambleDimensionBuilder.Make(body); + var ao = go.GetComponent(); + var sector = go.FindChild("Sector").GetComponent(); + var owRigidBody = go.GetComponent(); + + go = SharedGenerateBody(body, go, sector, owRigidBody); + body.Object = go; + + if (ao.GetAstroObjectName() == AstroObject.Name.CustomString) + { + AstroObjectLocator.RegisterCustomAstroObject(ao); + } + + Logger.Log($"returning GO named {go.name}"); + + return go; + } + + public static GameObject GenerateStandardBody(NewHorizonsBody body, bool defaultPrimaryToSun = false) + { // Focal points are weird if (body.Config.FocalPoint != null) FocalPointBuilder.ValidateConfig(body.Config); @@ -351,6 +410,10 @@ namespace NewHorizons.Handlers { DetectorBuilder.Make(go, owRigidBody, primaryBody, ao, body.Config); } + else if (body.Config.Orbit.staticPosition != null) + { + ao.transform.position = body.Config.Orbit.staticPosition; + } if (ao.GetAstroObjectName() == AstroObject.Name.CustomString) { @@ -406,6 +469,19 @@ namespace NewHorizons.Handlers StarLightController.AddStar(StarBuilder.Make(go, sector, body.Config.Star, body.Mod)); } + if (body.Config?.Bramble != null) + { + if (body.Config.Bramble.nodes != null) + { + BrambleNodeBuilder.Make(go, sector, body.Config.Bramble.nodes, body.Mod); + } + + if (body.Config.Bramble.dimension != null) + { + BrambleNodeBuilder.FinishPairingNodesForDimension(body.Config.name, go.GetComponent()); + } + } + if (body.Config.Ring != null) { RingBuilder.Make(go, sector, body.Config.Ring, body.Mod); diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index e0d67abf..ac72d5d3 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -1,5 +1,6 @@ using HarmonyLib; using NewHorizons.AchievementsPlus; +using NewHorizons.Builder.Body; using NewHorizons.Builder.Props; using NewHorizons.Components; using NewHorizons.External; @@ -268,6 +269,8 @@ namespace NewHorizons NewHorizonsData.Load(); SignalBuilder.Init(); + BrambleDimensionBuilder.Init(); + BrambleNodeBuilder.Init(); AstroObjectLocator.Init(); OWAssetHandler.Init(); PlanetCreationHandler.Init(BodyDict[CurrentStarSystem]); @@ -485,7 +488,8 @@ namespace NewHorizons } // Has to happen after we make sure theres a system config - config.MigrateAndValidate(); + config.Validate(); + config.Migrate(); body = new NewHorizonsBody(config, mod, relativePath); } diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index bb04938c..e4e8db3e 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -20,6 +20,10 @@ "description": "Base Properties of this Body", "$ref": "#/definitions/BaseModule" }, + "Bramble": { + "description": "Add bramble nodes to this planet and/or make this planet a bramble dimension", + "$ref": "#/definitions/BrambleModule" + }, "buildPriority": { "type": "integer", "description": "Set to a higher number if you wish for this body to be built sooner", @@ -484,6 +488,72 @@ "inverseSquared" ] }, + "BrambleModule": { + "type": "object", + "additionalProperties": false, + "properties": { + "dimension": { + "description": "Defining this value will make this body a bramble dimension. Leave it null to not do that.", + "$ref": "#/definitions/BrambleDimensionInfo" + }, + "nodes": { + "type": "array", + "description": "Place nodes/seeds that take you to other bramble dimensions", + "items": { + "$ref": "#/definitions/BrambleNodeInfo" + } + } + } + }, + "BrambleDimensionInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "fogTint": { + "description": "The color of the fog inside this dimension. Leave blank for the default yellowish color", + "$ref": "#/definitions/MColor" + }, + "linksTo": { + "type": "string", + "description": "The name of the *node* that the player is taken to when exiting this dimension." + } + } + }, + "BrambleNodeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "position": { + "description": "The physical position of the node", + "$ref": "#/definitions/MVector3" + }, + "rotation": { + "description": "The physical rotation of the node", + "$ref": "#/definitions/MVector3" + }, + "linksTo": { + "type": "string", + "description": "The name of the planet that hosts the dimension this node links to" + }, + "name": { + "type": "string", + "description": "The name of this node. Only required if this node should serve as an exit." + }, + "isSeed": { + "type": "boolean", + "description": "Set this to true to make this node a seed instead of a node the player can enter", + "default": false + }, + "fogTint": { + "description": "The color of the fog inside the node. Leave blank for the default yellowish color (default: 131, 124, 105, 255)", + "$ref": "#/definitions/MColor" + }, + "lightTint": { + "description": "The color of the shafts of light coming from the entrances to the node. Leave blank for the default yellowish color", + "$ref": "#/definitions/MColor" + } + } + }, "CloakModule": { "type": "object", "additionalProperties": false, @@ -630,6 +700,10 @@ "type": "object", "additionalProperties": false, "properties": { + "staticPosition": { + "description": "Specify this if you want the body to remain stationary at a given location (ie not orbit its parent). Required for Bramble dimensions", + "$ref": "#/definitions/MVector3" + }, "primaryBody": { "type": "string", "description": "The name of the body this one will orbit around" diff --git a/NewHorizons/Utility/DebugMenu/DebugMenuPropPlacer.cs b/NewHorizons/Utility/DebugMenu/DebugMenuPropPlacer.cs index 76d3462d..09785e52 100644 --- a/NewHorizons/Utility/DebugMenu/DebugMenuPropPlacer.cs +++ b/NewHorizons/Utility/DebugMenu/DebugMenuPropPlacer.cs @@ -142,7 +142,7 @@ namespace NewHorizons.Utility.DebugMenu Vector3 latestPropSphericalPosDelta = VectorInput(mostRecentlyPlacedPropSphericalPos, propSphericalPosDelta, out propSphericalPosDelta, "lat ", "lon ", "height"); if (latestPropSphericalPosDelta != Vector3.zero) { - DeltaSphericalPosition(mostRecentlyPlacedProp, latestPropSphericalPosDelta); + SetSphericalPosition(mostRecentlyPlacedProp, mostRecentlyPlacedPropSphericalPos + latestPropSphericalPosDelta); mostRecentlyPlacedPropSphericalPos = mostRecentlyPlacedPropSphericalPos + latestPropSphericalPosDelta; } @@ -209,6 +209,40 @@ namespace NewHorizons.Utility.DebugMenu return newSpherical; } + // DB_EscapePodDimension_Body/Sector_EscapePodDimension/Interactables_EscapePodDimension/InnerWarp_ToAnglerNest + // DB_ExitOnlyDimension_Body/Sector_ExitOnlyDimension/Interactables_ExitOnlyDimension/InnerWarp_ToExitOnly // need to change the colors + // DB_HubDimension_Body/Sector_HubDimension/Interactables_HubDimension/InnerWarp_ToCluster // need to delete the child "Signal_Harmonica" + + private Vector3 SetSphericalPosition(GameObject prop, Vector3 newSpherical) + { + Transform astroObject = prop.transform.parent.parent; + Transform sector = prop.transform.parent; + Vector3 originalLocalPos = astroObject.InverseTransformPoint(prop.transform.position); // parent is the sector, this gives localPos relative to the astroobject (what the DetailBuilder asks for) + Vector3 sphericalPos = CoordinateUtilities.CartesianToSpherical(originalLocalPos); + + if (newSpherical == sphericalPos) return sphericalPos; + + Vector3 finalLocalPosition = CoordinateUtilities.SphericalToCartesian(newSpherical); + Vector3 finalAbsolutePosition = astroObject.TransformPoint(finalLocalPosition); + prop.transform.localPosition = prop.transform.parent.InverseTransformPoint(finalAbsolutePosition); + Logger.Log("new position: " + prop.transform.localPosition); + + var onlyChangingRAndRIsNegative = false; + + // first, rotate the object by the astroObject's rotation, that means anything afterwards is relative to this rotation (ie, we can pretend the astroObject has 0 rotation) + // then, rotate by the difference in position, basically accounting for the curvature of a sphere + // then re-apply the local rotations of the hierarchy down to the prop (apply the sector local rotation, then the prop local rotation) + + // since we're doing all rotation relative to the astro object, we start with its absolute rotation + // then we apply the rotation about the astroobject using FromTooRotation + // then we reapply the local rotations down through the hierarchy + Vector3 originalLocalPos_ForcedPositiveR = CoordinateUtilities.SphericalToCartesian(new Vector3(sphericalPos.x, sphericalPos.y, Mathf.Abs(sphericalPos.z))); + Vector3 finalLocalPos_ForcedPositiveR = CoordinateUtilities.SphericalToCartesian(new Vector3(newSpherical.x, newSpherical.y, Mathf.Abs(newSpherical.z))); + if (!onlyChangingRAndRIsNegative) prop.transform.rotation = astroObject.rotation * Quaternion.FromToRotation(originalLocalPos_ForcedPositiveR.normalized, finalLocalPos_ForcedPositiveR.normalized) * sector.localRotation * prop.transform.localRotation; + + return newSpherical; + } + private Vector3 VectorInput(Vector3 input, Vector3 deltaControls, out Vector3 deltaControlsOut, string labelX, string labelY, string labelZ) { var dx = deltaControls.x;