From 79b1e97eb296e619f349fbc85e229cb081ed5055 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 2 Jan 2022 14:05:20 -0500 Subject: [PATCH 01/20] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b3aad111..dc50f488 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A planet creator for Outer Wilds. +You can view the addons creators have made [here](https://outerwildsmods.com/mods/newhorizons/addons/)! + Planets are created using a JSON file format structure, and placed in the `planets` folder. From 6759a4af76c89ee5132fad497ef92927916b7dfb Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Sun, 2 Jan 2022 20:28:22 -0500 Subject: [PATCH 02/20] Black hole + white hole pairing --- NewHorizons/Builder/Body/BlackHoleBuilder.cs | 36 ---- NewHorizons/Builder/Body/RingBuilder.cs | 4 +- .../Builder/Body/SingularityBuilder.cs | 203 ++++++++++++++++++ .../Components/BlackHoleDestructionVolume.cs | 29 +++ NewHorizons/External/BaseModule.cs | 4 +- NewHorizons/External/IPlanetConfig.cs | 1 + NewHorizons/External/PlanetConfig.cs | 1 + NewHorizons/External/SingularityModule.cs | 15 ++ NewHorizons/Main.cs | 6 +- NewHorizons/Utility/Patches.cs | 29 +++ 10 files changed, 286 insertions(+), 42 deletions(-) delete mode 100644 NewHorizons/Builder/Body/BlackHoleBuilder.cs create mode 100644 NewHorizons/Builder/Body/SingularityBuilder.cs create mode 100644 NewHorizons/Components/BlackHoleDestructionVolume.cs create mode 100644 NewHorizons/External/SingularityModule.cs diff --git a/NewHorizons/Builder/Body/BlackHoleBuilder.cs b/NewHorizons/Builder/Body/BlackHoleBuilder.cs deleted file mode 100644 index 850651b4..00000000 --- a/NewHorizons/Builder/Body/BlackHoleBuilder.cs +++ /dev/null @@ -1,36 +0,0 @@ -using NewHorizons.External; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using UnityEngine; - -namespace NewHorizons.Builder.Body -{ - static class BlackHoleBuilder - { - public static void Make(GameObject body, BaseModule module, Sector sector) - { - var blackHole = GameObject.Instantiate(GameObject.Find("BrittleHollow_Body/BlackHole_BH"), body.transform); - blackHole.name = "BlackHole"; - blackHole.transform.localPosition = Vector3.zero; - //blackHole.transform.localScale = Vector3.one; //* module.BlackHoleSize; - - var blackHoleRenderer = blackHole.transform.Find("BlackHoleRenderer"); - //blackHoleRenderer.transform.localScale = Vector3.one; - - var singularityLOD = blackHoleRenderer.GetComponent(); - singularityLOD.SetSector(sector); - - /* - var meshRenderer = blackHoleRenderer.GetComponent(); - meshRenderer.material.SetFloat("_Radius", module.BlackHoleSize * 0.4f); - - var owRenderer = blackHoleRenderer.gameObject.AddComponent(); - var propID_Radius = Shader.PropertyToID("_Radius"); - owRenderer.SetMaterialProperty(propID_Radius, module.BlackHoleSize * 0.4f); - */ - } - } -} diff --git a/NewHorizons/Builder/Body/RingBuilder.cs b/NewHorizons/Builder/Body/RingBuilder.cs index 03df9906..673dcb42 100644 --- a/NewHorizons/Builder/Body/RingBuilder.cs +++ b/NewHorizons/Builder/Body/RingBuilder.cs @@ -43,11 +43,11 @@ namespace NewHorizons.Builder.Body var mat = new Material(RingShader); mat.mainTexture = texture; - mat.renderQueue = 3000; + mat.renderQueue = 2895; ringMR.material = mat; // Make mesh - var segments = (int)Math.Max(20, ring.OuterRadius); + var segments = (int)Mathf.Clamp(ring.OuterRadius, 20, 20000); BuildRingMesh(ringMesh, segments, ring.InnerRadius, ring.OuterRadius); } diff --git a/NewHorizons/Builder/Body/SingularityBuilder.cs b/NewHorizons/Builder/Body/SingularityBuilder.cs new file mode 100644 index 00000000..58ca2e68 --- /dev/null +++ b/NewHorizons/Builder/Body/SingularityBuilder.cs @@ -0,0 +1,203 @@ +using NewHorizons.Components; +using NewHorizons.External; +using NewHorizons.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Logger = NewHorizons.Utility.Logger; + +namespace NewHorizons.Builder.Body +{ + static class SingularityBuilder + { + enum Polarity + { + BlackHole, + WhiteHole + } + + private static Shader blackHoleShader = null; + private static Shader whiteHoleShader = null; + + public static void Make(GameObject body, Sector sector, OWRigidbody OWRB, IPlanetConfig config) + { + + var size = config.Base.BlackHoleSize; + string pairedSingularity = null; + var polarity = Polarity.BlackHole; + + if (config.Singularity != null) + { + size = config.Singularity.Size; + pairedSingularity = config.Singularity.PairedSingularity; + if(config.Singularity.Type != null && config.Singularity.Type.ToUpper().Equals("WHITEHOLE")) + { + polarity = Polarity.WhiteHole; + } + } + bool hasHazardVolume = pairedSingularity == null; + + GameObject newSingularity = null; + switch (polarity) + { + case Polarity.BlackHole: + newSingularity = MakeBlackHole(body, sector, size, hasHazardVolume); + break; + case Polarity.WhiteHole: + newSingularity = MakeWhiteHole(body, sector, OWRB, size); + break; + } + + // Try to pair them + if(pairedSingularity != null && newSingularity != null) + { + var pairedSingularityAO = AstroObjectLocator.GetAstroObject(pairedSingularity); + if(pairedSingularityAO != null) + { + Logger.Log($"Pairing singularities {pairedSingularity}, {config.Name}"); + try + { + switch (polarity) + { + case Polarity.BlackHole: + newSingularity.GetComponentInChildren()._whiteHole = pairedSingularityAO.GetComponentInChildren(); + break; + case Polarity.WhiteHole: + pairedSingularityAO.GetComponentInChildren()._whiteHole = newSingularity.GetComponentInChildren(); + break; + } + } + catch(Exception) + { + Logger.LogError($"Couldn't pair singularities {pairedSingularity}, {config.Name}"); + } + } + } + } + + private static GameObject MakeBlackHole(GameObject body, Sector sector, float size, bool hasDestructionVolume) + { + var blackHole = new GameObject("BlackHole"); + blackHole.SetActive(false); + blackHole.transform.parent = body.transform; + blackHole.transform.localPosition = Vector3.zero; + + var blackHoleRender = new GameObject("BlackHoleRender"); + blackHoleRender.transform.parent = blackHole.transform; + blackHoleRender.transform.localPosition = Vector3.zero; + blackHoleRender.transform.localScale = Vector3.one * size; + + var meshFilter = blackHoleRender.AddComponent(); + meshFilter.mesh = GameObject.Find("BrittleHollow_Body/BlackHole_BH/BlackHoleRenderer").GetComponent().mesh; + + var meshRenderer = blackHoleRender.AddComponent(); + if (blackHoleShader == null) blackHoleShader = GameObject.Find("BrittleHollow_Body/BlackHole_BH/BlackHoleRenderer").GetComponent().sharedMaterial.shader; + meshRenderer.material = new Material(blackHoleShader); + meshRenderer.material.SetFloat("_Radius", size * 0.4f); + meshRenderer.material.SetFloat("_MaxDistortRadius", size * 0.95f); + meshRenderer.material.SetFloat("_MassScale", 1); + meshRenderer.material.SetFloat("_DistortFadeDist", size * 0.55f); + + if(hasDestructionVolume) + { + var destructionVolumeGO = new GameObject("DestructionVolume"); + destructionVolumeGO.layer = LayerMask.NameToLayer("BasicEffectVolume"); + destructionVolumeGO.transform.parent = blackHole.transform; + destructionVolumeGO.transform.localScale = Vector3.one; + destructionVolumeGO.transform.localPosition = Vector3.zero; + + var sphereCollider = destructionVolumeGO.AddComponent(); + sphereCollider.radius = size * 0.4f; + sphereCollider.isTrigger = true; + + destructionVolumeGO.AddComponent(); + } + else + { + var blackHoleVolume = GameObject.Instantiate(GameObject.Find("BrittleHollow_Body/BlackHole_BH/BlackHoleVolume"), blackHole.transform); + blackHoleVolume.name = "BlackHoleVolume"; + blackHoleVolume.GetComponent().radius = size * 0.4f; + } + + blackHole.SetActive(true); + return blackHole; + } + + private static GameObject MakeWhiteHole(GameObject body, Sector sector, OWRigidbody OWRB, float size) + { + var whiteHole = new GameObject("WhiteHole"); + whiteHole.SetActive(false); + whiteHole.transform.parent = body.transform; + whiteHole.transform.localPosition = Vector3.zero; + + var whiteHoleRenderer = new GameObject("WhiteHoleRenderer"); + whiteHoleRenderer.transform.parent = whiteHole.transform; + whiteHoleRenderer.transform.localPosition = Vector3.zero; + whiteHoleRenderer.transform.localScale = Vector3.one * size * 2.8f; + + var meshFilter = whiteHoleRenderer.AddComponent(); + meshFilter.mesh = GameObject.Find("WhiteHole_Body/WhiteHoleVisuals/Singularity").GetComponent().mesh; + + var meshRenderer = whiteHoleRenderer.AddComponent(); + if (whiteHoleShader == null) whiteHoleShader = GameObject.Find("WhiteHole_Body/WhiteHoleVisuals/Singularity").GetComponent().sharedMaterial.shader; + meshRenderer.material = new Material(whiteHoleShader); + meshRenderer.sharedMaterial.SetFloat("_Radius", size * 0.4f); + meshRenderer.sharedMaterial.SetFloat("_DistortFadeDist", size); + meshRenderer.sharedMaterial.SetFloat("_MaxDistortRadius", size * 2.8f); + meshRenderer.sharedMaterial.SetColor("_Color", new Color(1.88f, 1.88f, 1.88f, 1f)); + + var ambientLight = GameObject.Instantiate(GameObject.Find("WhiteHole_Body/WhiteHoleVisuals/AmbientLight_WH")); + ambientLight.transform.parent = whiteHole.transform; + ambientLight.transform.localScale = Vector3.one; + ambientLight.transform.localPosition = Vector3.zero; + ambientLight.name = "AmbientLight"; + ambientLight.GetComponent().range = size * 7f; + + var proxyShadow = sector.gameObject.AddComponent(); + + // it's going to complain + GameObject whiteHoleVolumeGO = GameObject.Instantiate(GameObject.Find("WhiteHole_Body/WhiteHoleVolume")); + + whiteHoleVolumeGO.transform.parent = whiteHole.transform; + whiteHoleVolumeGO.transform.localPosition = Vector3.zero; + whiteHoleVolumeGO.transform.localScale = Vector3.one; + whiteHoleVolumeGO.GetComponent().radius = size; + whiteHoleVolumeGO.name = "WhiteHoleVolume"; + + var whiteHoleFluidVolume = whiteHoleVolumeGO.GetComponent(); + whiteHoleFluidVolume._innerRadius = size * 0.5f; + whiteHoleFluidVolume._outerRadius = size; + whiteHoleFluidVolume._attachedBody = OWRB; + + var whiteHoleVolume = whiteHoleVolumeGO.GetComponent(); + whiteHoleVolume._debrisDistMax = size * 6.5f; + whiteHoleVolume._debrisDistMin = size * 2f; + whiteHoleVolume._whiteHoleSector = sector; + whiteHoleVolume._fluidVolume = whiteHoleFluidVolume; + whiteHoleVolume._whiteHoleBody = OWRB; + whiteHoleVolume._whiteHoleProxyShadowSuperGroup = proxyShadow; + + whiteHoleVolumeGO.GetComponent().radius = size; + + whiteHoleVolume.enabled = true; + whiteHoleFluidVolume.enabled = true; + + var zeroGVolume = GameObject.Instantiate(GameObject.Find("WhiteHole_Body/ZeroGVolume"), whiteHole.transform); + zeroGVolume.name = "ZeroGVolume"; + zeroGVolume.GetComponent().radius = size * 10f; + zeroGVolume.GetComponent()._attachedBody = OWRB; + + var rulesetVolume = GameObject.Instantiate(GameObject.Find("WhiteHole_Body/Sector_WhiteHole/RulesetVolumes_WhiteHole"), sector.transform); + rulesetVolume.name = "RulesetVolume"; + rulesetVolume.transform.localPosition = Vector3.zero; + rulesetVolume.transform.localScale = Vector3.one * size / 100f; + rulesetVolume.GetComponent().enabled = true; + + whiteHole.SetActive(true); + return whiteHole; + } + } +} diff --git a/NewHorizons/Components/BlackHoleDestructionVolume.cs b/NewHorizons/Components/BlackHoleDestructionVolume.cs new file mode 100644 index 00000000..23663f89 --- /dev/null +++ b/NewHorizons/Components/BlackHoleDestructionVolume.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Logger = NewHorizons.Utility.Logger; + +namespace NewHorizons.Components +{ + public class BlackHoleDestructionVolume : DestructionVolume + { + public override void Awake() + { + base.Awake(); + _deathType = DeathType.BlackHole; + } + + public override void VanishProbe(OWRigidbody probeBody, RelativeLocationData entryLocation) + { + Logger.Log($"Uh oh you shot your probe into a black hole"); + SurveyorProbe requiredComponent = probeBody.GetRequiredComponent(); + if (requiredComponent.IsLaunched()) + { + UnityEngine.Object.Destroy(requiredComponent.gameObject); + } + } + } +} + diff --git a/NewHorizons/External/BaseModule.cs b/NewHorizons/External/BaseModule.cs index 8acbbd84..1ae8c966 100644 --- a/NewHorizons/External/BaseModule.cs +++ b/NewHorizons/External/BaseModule.cs @@ -16,10 +16,12 @@ namespace NewHorizons.External public float SurfaceSize { get; set; } public float WaterSize { get; set; } public float GroundSize { get; set; } - public float BlackHoleSize { get; set; } public float LavaSize { get; set; } public bool HasCometTail { get; set; } public bool HasReferenceFrame { get; set; } = true; public bool CenterOfSolarSystem { get; set; } = false; + + // Old, see SingularityModule instead + public float BlackHoleSize { get; set; } } } diff --git a/NewHorizons/External/IPlanetConfig.cs b/NewHorizons/External/IPlanetConfig.cs index 5f726a2a..98e0686e 100644 --- a/NewHorizons/External/IPlanetConfig.cs +++ b/NewHorizons/External/IPlanetConfig.cs @@ -19,5 +19,6 @@ namespace NewHorizons.External PropModule Props { get; } SpawnModule Spawn { get; } SignalModule Signal { get; } + SingularityModule Singularity { get; } } } diff --git a/NewHorizons/External/PlanetConfig.cs b/NewHorizons/External/PlanetConfig.cs index 1cefbd2f..80e9db8a 100644 --- a/NewHorizons/External/PlanetConfig.cs +++ b/NewHorizons/External/PlanetConfig.cs @@ -23,6 +23,7 @@ namespace NewHorizons.External public PropModule Props { get; set; } public SpawnModule Spawn { get; set; } public SignalModule Signal { get; set; } + public SingularityModule Singularity { get; set; } public PlanetConfig(Dictionary dict) { diff --git a/NewHorizons/External/SingularityModule.cs b/NewHorizons/External/SingularityModule.cs new file mode 100644 index 00000000..6d30d96c --- /dev/null +++ b/NewHorizons/External/SingularityModule.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External +{ + public class SingularityModule : Module + { + public float Size; + public string PairedSingularity; + public string Type; //BlackHole or WhiteHole + } +} diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index f4109066..c0a234c2 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -187,7 +187,7 @@ namespace NewHorizons existingPlanet = AstroObjectLocator.GetAstroObject(stringID).gameObject; if (existingPlanet == null) existingPlanet = AstroObjectLocator.GetAstroObject(body.Config.Name.Replace(" ", "")).gameObject; } - catch (Exception e) + catch (Exception) { existingPlanet = GameObject.Find(body.Config.Name.Replace(" ", "") + "_Body"); } @@ -319,8 +319,8 @@ namespace NewHorizons if (body.Config.ProcGen != null) ProcGenBuilder.Make(go, body.Config.ProcGen); - if (body.Config.Base.BlackHoleSize != 0) - BlackHoleBuilder.Make(go, body.Config.Base, sector); + if (body.Config.Base.BlackHoleSize != 0 || body.Config.Singularity != null) + SingularityBuilder.Make(go, sector, owRigidBody, body.Config); if (body.Config.Star != null) StarLightController.AddStar(StarBuilder.Make(go, sector, body.Config.Star)); diff --git a/NewHorizons/Utility/Patches.cs b/NewHorizons/Utility/Patches.cs index 8742b1be..d7f6b01a 100644 --- a/NewHorizons/Utility/Patches.cs +++ b/NewHorizons/Utility/Patches.cs @@ -43,6 +43,10 @@ namespace NewHorizons.Utility var playerDataResetGame = typeof(PlayerData).GetMethod("ResetGame"); Main.Instance.ModHelper.HarmonyHelper.AddPostfix(playerDataResetGame, typeof(Patches), nameof(Patches.OnPlayerDataResetGame)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("Start", typeof(Patches), nameof(Patches.OnBlackHoleVolumeStart)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("Awake", typeof(Patches), nameof(Patches.OnWhiteHoleVolumeAwake)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("UpdateOrbitalLaunchValues", typeof(Patches), nameof(Patches.OnProbeLauncherUpdateOrbitalLaunchValues)); + // Postfixes Main.Instance.ModHelper.HarmonyHelper.AddPostfix("Awake", typeof(Patches), nameof(Patches.OnMapControllerAwake)); Main.Instance.ModHelper.HarmonyHelper.AddPostfix("Awake", typeof(Patches), nameof(Patches.OnOWCameraAwake)); @@ -306,5 +310,30 @@ namespace NewHorizons.Utility NewHorizonsData.Reset(); } #endregion + + public static bool OnBlackHoleVolumeStart(BlackHoleVolume __instance) + { + return __instance._whiteHole == null; + } + + public static bool OnWhiteHoleVolumeAwake(WhiteHoleVolume __instance) + { + __instance._growQueue = new List(8); + __instance._growQueueLocationData = new List(8); + __instance._ejectedBodyList = new List(64); + try + { + __instance._whiteHoleBody = __instance.gameObject.GetAttachedOWRigidbody(false); + __instance._whiteHoleProxyShadowSuperGroup = __instance._whiteHoleBody.GetComponentInChildren(); + __instance._fluidVolume = __instance.gameObject.GetRequiredComponent(); + } + catch (Exception) { } + return false; + } + + public static bool OnProbeLauncherUpdateOrbitalLaunchValues(ProbeLauncher __instance) + { + return (Locator.GetPlayerRulesetDetector()?.GetPlanetoidRuleset()?.GetGravityVolume() != null); + } } } From 2e0d9093c6ab44c72061f75f1aae8229746053fd Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Mon, 3 Jan 2022 02:21:40 -0500 Subject: [PATCH 03/20] Some fixes + load assetbundles for props / audiofiles for signals --- .../Builder/Atmosphere/CloudsBuilder.cs | 8 +- .../Builder/Body/AsteroidBeltBuilder.cs | 4 +- NewHorizons/Builder/Body/HeightMapBuilder.cs | 3 + NewHorizons/Builder/Body/RingBuilder.cs | 2 +- NewHorizons/Builder/General/BaseBuilder.cs | 1 + NewHorizons/Builder/General/MarkerBuilder.cs | 14 ++- .../Builder/Orbital/OrbitlineBuilder.cs | 12 +- NewHorizons/Builder/Props/PropBuilder.cs | 110 +++++++++++++----- NewHorizons/Builder/Props/SignalBuilder.cs | 33 ++++-- NewHorizons/External/BaseModule.cs | 1 + NewHorizons/External/PropModule.cs | 4 + NewHorizons/External/SignalModule.cs | 3 +- NewHorizons/Main.cs | 14 ++- NewHorizons/Utility/NewHorizonBody.cs | 4 +- NewHorizons/Utility/SearchUtilities.cs | 18 ++- 15 files changed, 174 insertions(+), 57 deletions(-) diff --git a/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs b/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs index 7188b6bb..fee15518 100644 --- a/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs @@ -31,6 +31,8 @@ namespace NewHorizons.Atmosphere return; } + Color cloudTint = atmo.CloudTint == null ? Color.white : (Color)atmo.CloudTint.ToColor32(); + GameObject cloudsMainGO = new GameObject(); cloudsMainGO.SetActive(false); cloudsMainGO.transform.parent = body.transform; @@ -56,8 +58,8 @@ namespace NewHorizons.Atmosphere foreach (var material in topMR.sharedMaterials) { - material.SetColor("_Color", atmo.CloudTint.ToColor32()); - material.SetColor("_TintColor", atmo.CloudTint.ToColor32()); + material.SetColor("_Color", cloudTint); + material.SetColor("_TintColor", cloudTint); material.SetTexture("_MainTex", image); material.SetTexture("_RampTex", ramp); @@ -84,7 +86,7 @@ namespace NewHorizons.Atmosphere bottomTSR.LODRadius = 1f; // It's always more green than expected - var bottomCloudTint = atmo.CloudTint.ToColor32(); + var bottomCloudTint = cloudTint; bottomCloudTint.g = (byte)(bottomCloudTint.g * 0.8f); foreach (Material material in bottomTSR.sharedMaterials) { diff --git a/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs b/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs index 813e35d3..9e49a5d2 100644 --- a/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs +++ b/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs @@ -14,7 +14,7 @@ namespace NewHorizons.Builder.Body { static class AsteroidBeltBuilder { - public static void Make(string bodyName, AsteroidBeltModule belt, IModAssets assets) + public static void Make(string bodyName, AsteroidBeltModule belt, IModAssets assets, string uniqueName) { var minSize = 20; var maxSize = 50; @@ -59,7 +59,7 @@ namespace NewHorizons.Builder.Body } }; - var asteroid = new NewHorizonsBody(new PlanetConfig(config), assets); + var asteroid = new NewHorizonsBody(new PlanetConfig(config), assets, uniqueName); Main.NextPassBodies.Add(asteroid); } } diff --git a/NewHorizons/Builder/Body/HeightMapBuilder.cs b/NewHorizons/Builder/Body/HeightMapBuilder.cs index 58ef8d21..c542f292 100644 --- a/NewHorizons/Builder/Body/HeightMapBuilder.cs +++ b/NewHorizons/Builder/Body/HeightMapBuilder.cs @@ -33,6 +33,7 @@ namespace NewHorizons.Builder.Body } GameObject cubeSphere = new GameObject("CubeSphere"); + cubeSphere.SetActive(false); cubeSphere.transform.parent = go.transform; cubeSphere.transform.rotation = Quaternion.Euler(90, 0, 0); @@ -53,6 +54,8 @@ namespace NewHorizons.Builder.Body // Fix rotation in the end cubeSphere.transform.localRotation = Quaternion.Euler(90, 0, 0); cubeSphere.transform.localPosition = Vector3.zero; + + cubeSphere.SetActive(true); } } } diff --git a/NewHorizons/Builder/Body/RingBuilder.cs b/NewHorizons/Builder/Body/RingBuilder.cs index 673dcb42..9a0a357d 100644 --- a/NewHorizons/Builder/Body/RingBuilder.cs +++ b/NewHorizons/Builder/Body/RingBuilder.cs @@ -47,7 +47,7 @@ namespace NewHorizons.Builder.Body ringMR.material = mat; // Make mesh - var segments = (int)Mathf.Clamp(ring.OuterRadius, 20, 20000); + var segments = (int)Mathf.Clamp(ring.OuterRadius, 20, 2000); BuildRingMesh(ringMesh, segments, ring.InnerRadius, ring.OuterRadius); } diff --git a/NewHorizons/Builder/General/BaseBuilder.cs b/NewHorizons/Builder/General/BaseBuilder.cs index 11243407..ac51232e 100644 --- a/NewHorizons/Builder/General/BaseBuilder.cs +++ b/NewHorizons/Builder/General/BaseBuilder.cs @@ -38,6 +38,7 @@ namespace NewHorizons.Builder.General var type = AstroObject.Type.Planet; if (config.Orbit.IsMoon) type = AstroObject.Type.Moon; + else if (config.Base.IsSatellite) type = AstroObject.Type.Satellite; else if (config.Base.HasCometTail) type = AstroObject.Type.Comet; else if (config.Star != null) type = AstroObject.Type.Star; else if (config.FocalPoint != null) type = AstroObject.Type.None; diff --git a/NewHorizons/Builder/General/MarkerBuilder.cs b/NewHorizons/Builder/General/MarkerBuilder.cs index cb51de61..f24cbdcf 100644 --- a/NewHorizons/Builder/General/MarkerBuilder.cs +++ b/NewHorizons/Builder/General/MarkerBuilder.cs @@ -13,22 +13,26 @@ namespace NewHorizons.Builder.General MapMarker mapMarker = body.AddComponent(); mapMarker.SetValue("_labelID", (UITextType)Utility.AddToUITable.Add(name.ToUpper())); - var markerType = "Planet"; + var markerType = MapMarker.MarkerType.Planet; if (config.Orbit.IsMoon) { - markerType = "Moon"; + markerType = MapMarker.MarkerType.Moon; } else if (config.Star != null) { - markerType = "Sun"; + markerType = MapMarker.MarkerType.Sun; } else if (config.FocalPoint != null) { - markerType = "HourglassTwins"; + markerType = MapMarker.MarkerType.HourglassTwins; + } + else if(config.Base.IsSatellite) + { + markerType = MapMarker.MarkerType.Probe; } - mapMarker.SetValue("_markerType", mapMarker.GetType().GetNestedType("MarkerType", BindingFlags.NonPublic).GetField(markerType).GetValue(mapMarker)); + mapMarker._markerType = markerType; } } } diff --git a/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs b/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs index 71468c1b..c878ca52 100644 --- a/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs +++ b/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs @@ -38,10 +38,20 @@ namespace NewHorizons.Builder.Orbital else if (config.Base.BlackHoleSize != 0) color = new Color(1f, 0.5f, 1f); else if (config.Base.WaterSize != 0) color = new Color(0.5f, 0.5f, 1f); else if (config.Base.LavaSize != 0) color = new Color(1f, 0.5f, 0.5f); + + var fade = isMoon; + if (config.Base.IsSatellite) + { + if(config.Orbit.Tint != null) color = new Color(0.4082f, 0.516f, 0.4469f, 1f); + fade = true; + orbitLine._fadeEndDist = 5000; + orbitLine._fadeStartDist = 3000; + } + orbitLine.SetValue("_color", color); orbitLine.SetValue("_astroObject", astroobject); - orbitLine.SetValue("_fade", isMoon); + orbitLine.SetValue("_fade", fade); orbitLine.SetValue("_lineWidth", 2f); Main.Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => diff --git a/NewHorizons/Builder/Props/PropBuilder.cs b/NewHorizons/Builder/Props/PropBuilder.cs index 823e3d4c..bc5241bc 100644 --- a/NewHorizons/Builder/Props/PropBuilder.cs +++ b/NewHorizons/Builder/Props/PropBuilder.cs @@ -9,39 +9,86 @@ using Random = UnityEngine.Random; using Logger = NewHorizons.Utility.Logger; using System.Reflection; using NewHorizons.Utility; +using OWML.Common; namespace NewHorizons.Builder.Props { public static class PropBuilder { - public static void Make(GameObject go, Sector sector, IPlanetConfig config) + public static void Make(GameObject go, Sector sector, IPlanetConfig config, IModAssets assets, string uniqueModName) { if (config.Props.Scatter != null) PropBuilder.Scatter(go, config.Props.Scatter, config.Base.SurfaceSize, sector); if(config.Props.Details != null) { foreach(var detail in config.Props.Details) { - MakeDetail(go, sector, detail.path, detail.position, detail.rotation, detail.scale); + if(detail.assetBundle != null) + { + string key = uniqueModName + "." + detail.assetBundle; + AssetBundle bundle; + GameObject prefab; + + try + { + if (Main.AssetBundles.ContainsKey(key)) bundle = Main.AssetBundles[key]; + else + { + bundle = assets.LoadBundle(detail.assetBundle); + Main.AssetBundles[key] = bundle; + } + } + catch(Exception e) + { + Logger.Log($"Couldn't load AssetBundle {detail.assetBundle} : {e.Message}"); + return; + } + + try + { + prefab = bundle.LoadAsset(detail.path); + prefab.SetActive(false); + } + catch(Exception e) + { + Logger.Log($"Couldn't load asset {detail.path} from AssetBundle {detail.assetBundle} : {e.Message}"); + return; + } + + MakeDetail(go, sector, prefab, detail.position, detail.rotation, detail.scale, detail.alignToNormal); + } + else if(detail.objFilePath != null) + { + try + { + var prefab = assets.Get3DObject(detail.objFilePath, detail.mtlFilePath); + prefab.SetActive(false); + MakeDetail(go, sector, prefab, detail.position, detail.rotation, detail.scale, detail.alignToNormal); + } + catch(Exception e) + { + Logger.LogError($"Could not load 3d object {detail.objFilePath} with texture {detail.mtlFilePath} : {e.Message}"); + } + } + else MakeDetail(go, sector, detail.path, detail.position, detail.rotation, detail.scale, detail.alignToNormal); } } } - public static GameObject MakeDetail(GameObject go, Sector sector, string propToClone, MVector3 position, MVector3 rotation, float scale) + public static GameObject MakeDetail(GameObject go, Sector sector, string propToClone, MVector3 position, MVector3 rotation, float scale, bool alignWithNormal) { var prefab = GameObject.Find(propToClone); - return MakeDetail(go, sector, prefab, position, rotation, scale); + + //TODO: this is super costly + if (prefab == null) prefab = SearchUtilities.FindObjectOfTypeAndName(propToClone.Split(new char[] { '\\', '/' }).Last()); + if (prefab == null) Logger.LogError($"Couldn't find detail {propToClone}"); + return MakeDetail(go, sector, prefab, position, rotation, scale, alignWithNormal); } - public static GameObject MakeDetail(GameObject go, Sector sector, GameObject prefab, MVector3 position, MVector3 rotation, float scale, bool alignWithNormal = false) + public static GameObject MakeDetail(GameObject go, Sector sector, GameObject prefab, MVector3 position, MVector3 rotation, float scale, bool alignWithNormal, bool snapToSurface = false) { if (prefab == null) return null; GameObject prop = GameObject.Instantiate(prefab, sector.transform); - prop.transform.localPosition = position == null ? prefab.transform.localPosition : (Vector3)position; - Quaternion rot = rotation == null ? prefab.transform.localRotation : Quaternion.Euler((Vector3)rotation); - if (alignWithNormal) rot = Quaternion.FromToRotation(prop.transform.TransformDirection(Vector3.up), ((Vector3)position).normalized); - prop.transform.rotation = rot; - prop.transform.localScale = scale != 0 ? Vector3.one * scale : prefab.transform.localScale; prop.SetActive(false); List assetBundles = new List(); @@ -59,27 +106,34 @@ namespace NewHorizons.Builder.Props sector.OnOccupantEnterSector += ((SectorDetector sd) => StreamingManager.LoadStreamingAssets(assetBundle)); } - foreach(var component in prop.GetComponentsInChildren()) + foreach (var component in prop.GetComponentsInChildren()) { - try - { - var setSectorMethod = component.GetType().GetMethod("SetSector"); - var sectorField = component.GetType().GetField("_sector"); + // TODO: Make this work or smthng + if (component is GhostIK) (component as GhostIK).enabled = false; + if(component is GhostEffects) (component as GhostEffects).enabled = false; + - if (setSectorMethod != null) - { - Logger.Log($"Found a SetSector method in {prop}.{component}"); - setSectorMethod.Invoke(component, new object[] { sector }); - } - else if (sectorField != null) - { - Logger.Log($"Found a _sector field in {component}"); - sectorField.SetValue(component, sector); - } - } - catch (Exception e) { Logger.Log($"{e.Message}, {e.StackTrace}"); } + var enabledField = component.GetType().GetField("enabled"); + if(enabledField != null && enabledField.FieldType == typeof(bool)) enabledField.SetValue(component, true); } + prop.transform.parent = go.transform; + prop.transform.localPosition = position == null ? prefab.transform.localPosition : (Vector3)position; + + Quaternion rot = rotation == null ? prefab.transform.rotation : Quaternion.Euler((Vector3)rotation); + prop.transform.rotation = rot; + if (alignWithNormal) + { + var up = prop.transform.localPosition.normalized; + var front = Vector3.Cross(up, Vector3.left); + if (front.sqrMagnitude == 0f) front = Vector3.Cross(up, Vector3.forward); + if (front.sqrMagnitude == 0f) front = Vector3.Cross(up, Vector3.up); + + prop.transform.LookAt(prop.transform.position + front, up); + } + + prop.transform.localScale = scale != 0 ? Vector3.one * scale : prefab.transform.localScale; + prop.SetActive(true); return prop; @@ -97,7 +151,7 @@ namespace NewHorizons.Builder.Props { var randomInd = (int)Random.Range(0, points.Count); var point = points[randomInd]; - var prop = MakeDetail(go, sector, prefab, (MVector3)(point.normalized * radius), null, 0f, true); + var prop = MakeDetail(go, sector, prefab, (MVector3)(point.normalized * radius), null, 0f, true, true); if(propInfo.offset != null) prop.transform.localPosition += prop.transform.TransformVector(propInfo.offset); if(propInfo.rotation != null) prop.transform.rotation *= Quaternion.Euler(propInfo.rotation); points.RemoveAt(randomInd); diff --git a/NewHorizons/Builder/Props/SignalBuilder.cs b/NewHorizons/Builder/Props/SignalBuilder.cs index f97ea0da..a028d5fe 100644 --- a/NewHorizons/Builder/Props/SignalBuilder.cs +++ b/NewHorizons/Builder/Props/SignalBuilder.cs @@ -1,6 +1,7 @@ using NewHorizons.Components; using NewHorizons.External; using NewHorizons.Utility; +using OWML.Common; using System; using System.Collections.Generic; using System.Linq; @@ -83,20 +84,19 @@ namespace NewHorizons.Builder.Props public static string GetCustomSignalName(SignalName signalName) { - string name = null; - _customSignalNames.TryGetValue(signalName, out name); + _customSignalNames.TryGetValue(signalName, out string name); return name; } - public static void Make(GameObject body, Sector sector, SignalModule module) + public static void Make(GameObject body, Sector sector, SignalModule module, IModAssets assets) { foreach(var info in module.Signals) { - Make(body, sector, info); + Make(body, sector, info, assets); } } - public static void Make(GameObject body, Sector sector, SignalModule.SignalInfo info) + public static void Make(GameObject body, Sector sector, SignalModule.SignalInfo info, IModAssets assets) { var signalGO = new GameObject($"Signal_{info.Name}"); signalGO.SetActive(false); @@ -107,15 +107,32 @@ namespace NewHorizons.Builder.Props var source = signalGO.AddComponent(); var owAudioSource = signalGO.AddComponent(); - AudioSignal audioSignal = null; + AudioSignal audioSignal; if (info.InsideCloak) audioSignal = signalGO.AddComponent(); else audioSignal = signalGO.AddComponent(); var frequency = StringToFrequency(info.Frequency); var name = StringToSignalName(info.Name); - AudioClip clip = SearchUtilities.FindResourceOfTypeAndName(info.AudioClip); - if (clip == null) return; + AudioClip clip = null; + if(info.AudioClip != null) clip = SearchUtilities.FindResourceOfTypeAndName(info.AudioClip); + else if (info.AudioFilePath != null) + { + try + { + clip = assets.GetAudio(info.AudioFilePath); + } + catch(Exception e) + { + Logger.LogError($"Couldn't load audio file {info.AudioFilePath} : {e.Message}"); + } + } + + if (clip == null) + { + Logger.LogError($"Couldn't find AudioClip {info.AudioClip} or AudioFile {info.AudioFilePath}"); + return; + } audioSignal.SetSector(sector); audioSignal._frequency = frequency; diff --git a/NewHorizons/External/BaseModule.cs b/NewHorizons/External/BaseModule.cs index 1ae8c966..f382d9c6 100644 --- a/NewHorizons/External/BaseModule.cs +++ b/NewHorizons/External/BaseModule.cs @@ -20,6 +20,7 @@ namespace NewHorizons.External public bool HasCometTail { get; set; } public bool HasReferenceFrame { get; set; } = true; public bool CenterOfSolarSystem { get; set; } = false; + public bool IsSatellite { get; set; } // Old, see SingularityModule instead public float BlackHoleSize { get; set; } diff --git a/NewHorizons/External/PropModule.cs b/NewHorizons/External/PropModule.cs index fee29eaa..3ac6db8c 100644 --- a/NewHorizons/External/PropModule.cs +++ b/NewHorizons/External/PropModule.cs @@ -24,9 +24,13 @@ namespace NewHorizons.External public class DetailInfo { public string path; + public string objFilePath; + public string mtlFilePath; + public string assetBundle; public MVector3 position; public MVector3 rotation; public float scale; + public bool alignToNormal; } } } diff --git a/NewHorizons/External/SignalModule.cs b/NewHorizons/External/SignalModule.cs index ea4298c0..07a72252 100644 --- a/NewHorizons/External/SignalModule.cs +++ b/NewHorizons/External/SignalModule.cs @@ -16,7 +16,8 @@ namespace NewHorizons.External public MVector3 Position; public string Frequency; public string Name; - public string AudioClip; + public string AudioClip = null; + public string AudioFilePath = null; public float SourceRadius = 1f; public float DetectionRadius = 0f; public float IdentificationRadius = 10f; diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index c0a234c2..9a1de336 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -30,6 +30,8 @@ namespace NewHorizons public static List BodyList = new List(); public static List NextPassBodies = new List(); + public static Dictionary AssetBundles = new Dictionary(); + public static float FurthestOrbit { get; set; } = 50000f; public StarLightController StarLightController { get; private set; } @@ -236,7 +238,7 @@ namespace NewHorizons { var config = mod.ModHelper.Storage.Load(file.Replace(folder, "")); Logger.Log($"Loaded {config.Name}"); - BodyList.Add(new NewHorizonsBody(config, mod.ModHelper.Assets)); + BodyList.Add(new NewHorizonsBody(config, mod.ModHelper.Assets, mod.ModHelper.Manifest.UniqueName)); } catch (Exception e) { @@ -289,7 +291,7 @@ namespace NewHorizons if (body.Config.Base.GroundSize != 0) GeometryBuilder.Make(go, body.Config.Base.GroundSize); var atmoSize = body.Config.Atmosphere != null ? body.Config.Atmosphere.Size : 0f; - float sphereOfInfluence = Mathf.Max(atmoSize, body.Config.Base.SurfaceSize * 2f); + float sphereOfInfluence = Mathf.Max(Mathf.Max(atmoSize, 50), body.Config.Base.SurfaceSize * 2f); var outputTuple = BaseBuilder.Make(go, primaryBody, body.Config); var ao = (AstroObject)outputTuple.Item1; @@ -366,7 +368,7 @@ namespace NewHorizons RingBuilder.Make(go, body.Config.Ring, body.Assets); if (body.Config.AsteroidBelt != null) - AsteroidBeltBuilder.Make(body.Config.Name, body.Config.AsteroidBelt, body.Assets); + AsteroidBeltBuilder.Make(body.Config.Name, body.Config.AsteroidBelt, body.Assets, body.ModUniqueName); if (body.Config.Base.HasCometTail) CometTailBuilder.Make(go, body.Config.Base, go.GetComponent().GetPrimaryBody()); @@ -396,10 +398,10 @@ namespace NewHorizons } if (body.Config.Props != null) - PropBuilder.Make(go, sector, body.Config); + PropBuilder.Make(go, sector, body.Config, body.Assets, body.ModUniqueName); if (body.Config.Signal != null) - SignalBuilder.Make(go, sector, body.Config.Signal); + SignalBuilder.Make(go, sector, body.Config.Signal, body.Assets); return go; } @@ -418,7 +420,7 @@ namespace NewHorizons Logger.Log("Recieved API request to create planet " + (string)config["Name"], Logger.LogType.Log); var planetConfig = new PlanetConfig(config); - var body = new NewHorizonsBody(planetConfig, mod != null ? mod.ModHelper.Assets : Main.Instance.ModHelper.Assets); + var body = new NewHorizonsBody(planetConfig, mod != null ? mod.ModHelper.Assets : Main.Instance.ModHelper.Assets, mod.ModHelper.Manifest.UniqueName); Main.BodyList.Add(body); } diff --git a/NewHorizons/Utility/NewHorizonBody.cs b/NewHorizons/Utility/NewHorizonBody.cs index 39bb008b..73e494c1 100644 --- a/NewHorizons/Utility/NewHorizonBody.cs +++ b/NewHorizons/Utility/NewHorizonBody.cs @@ -6,14 +6,16 @@ namespace NewHorizons.Utility { public class NewHorizonsBody { - public NewHorizonsBody(IPlanetConfig config, IModAssets assets) + public NewHorizonsBody(IPlanetConfig config, IModAssets assets, string modUniqueName) { Config = config; Assets = assets; + ModUniqueName = modUniqueName; } public IPlanetConfig Config; public IModAssets Assets; + public string ModUniqueName; public GameObject Object; } diff --git a/NewHorizons/Utility/SearchUtilities.cs b/NewHorizons/Utility/SearchUtilities.cs index a211fba9..7eee7333 100644 --- a/NewHorizons/Utility/SearchUtilities.cs +++ b/NewHorizons/Utility/SearchUtilities.cs @@ -10,7 +10,7 @@ namespace NewHorizons.Utility { public static class SearchUtilities { - public static List FindObjectsOfTypeAndName(string name) where T : MonoBehaviour + public static List FindObjectsOfTypeAndName(string name) where T : Object { T[] firstList = GameObject.FindObjectsOfType(); List finalList = new List(); @@ -26,6 +26,22 @@ namespace NewHorizons.Utility return finalList; } + public static T FindObjectOfTypeAndName(string name) where T : Object + { + T[] firstList = GameObject.FindObjectsOfType(); + List finalList = new List(); + + for (var i = 0; i < firstList.Length; i++) + { + if (firstList[i].name == name) + { + return firstList[i]; + } + } + + return null; + } + public static List FindResourcesOfTypeAndName(string name) where T : Object { T[] firstList = Resources.FindObjectsOfTypeAll(); From b87ff06a7c7b3d214bac8c1ecfc501095e8f505b Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Mon, 3 Jan 2022 02:27:26 -0500 Subject: [PATCH 04/20] Update PropBuilder.cs --- NewHorizons/Builder/Props/PropBuilder.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Builder/Props/PropBuilder.cs b/NewHorizons/Builder/Props/PropBuilder.cs index 823e3d4c..8911d9e8 100644 --- a/NewHorizons/Builder/Props/PropBuilder.cs +++ b/NewHorizons/Builder/Props/PropBuilder.cs @@ -59,7 +59,23 @@ namespace NewHorizons.Builder.Props sector.OnOccupantEnterSector += ((SectorDetector sd) => StreamingManager.LoadStreamingAssets(assetBundle)); } - foreach(var component in prop.GetComponentsInChildren()) + foreach (var component in prop.GetComponentsInChildren()) + { + // TODO: Make this work or smthng + if (component is GhostIK) (component as GhostIK).enabled = false; + if(component is GhostEffects) (component as GhostEffects).enabled = false; + + + var enabledField = component.GetType().GetField("enabled"); + if(enabledField != null && enabledField.FieldType == typeof(bool)) enabledField.SetValue(component, true); + } + + prop.transform.parent = go.transform; + prop.transform.localPosition = position == null ? Vector3.zero : (Vector3)position; + + Quaternion rot = rotation == null ? prefab.transform.rotation : Quaternion.Euler((Vector3)rotation); + prop.transform.rotation = rot; + if (alignWithNormal) { try { From 5b5250624713300dfff805f35ef3496aa50f98b7 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Jan 2022 02:46:03 -0500 Subject: [PATCH 05/20] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dc50f488..1c471d27 100644 --- a/README.md +++ b/README.md @@ -33,14 +33,14 @@ Planets are created using a JSON file format structure, and placed in the `plane - Comets (Done) - Signalscope signals (Done) - Asteroid belts (Done) +- Support satellites (Done) +- Surface scatter: rocks, trees, etc, using in-game models (done) or custom ones (done) +- Load planet meshes from asset bundle (technically done) +- Black hole / white hole pairs (done) - Procedurally terrain generation (started) -- Support satellites (using custom models in the assets folder or in-game ones) -- Surface scatter: rocks, trees, etc, using in-game models (done) or custom ones -- Load planet meshes from asset bundle - "Quantum" planet parameters - Better terrain and water LOD - Edit existing planet orbits -- Black hole / white hole pairs - Separate solar system scenes accessible via wormhole - Implement all planet features: - Tornados From 74cf24391da8af62ddacf1421dc65b8de94b091a Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Jan 2022 11:22:34 -0500 Subject: [PATCH 06/20] Update README.md --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1c471d27..5bec6aa7 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,12 @@ Planets are created using a JSON file format structure, and placed in the `plane - [AsteroidBelt](#asteroidbelt) - [FocalPoint](#focalpoint) - [Props](#props) + - [Asset Bundles](#asset-bundles) - [Spawn](#spawn) - [Star](#star) + - [Signal](#signal) - [How to destroy existing planets](#how-to-destroy-existing-planets) + - [How to update existing planets](#how-to-update-existing-planets) - [How to use New Horizons in other mods](#how-to-use-new-horizons-in-other-mods) - [Credits](#credits) @@ -284,9 +287,9 @@ and The positions of the binaries will be based off of their masses (as determined by the "surfaceGravity" parameter). However, one of them must have a non-zero semiMajorAxis field else the mod gets confused. This example uses stars, but it will also work for planets. If you want to have other planets orbiting the center of mass, just put the focal point body as the primary body. ### Props -Lets you place items on the surface of the planet. Currently this is a very early release version. +Lets you place items on the surface of the planet. --scatter : (list) I'll just give an example. +- "scatter" : (list) I'll just give an example. ``` "scatter" : [ @@ -296,6 +299,49 @@ Lets you place items on the surface of the planet. Currently this is a very earl The path is in the hierarchy of the solar system. Use the [Unity Explorer](https://outerwildsmods.com/mods/unityexplorer) mod to find an object you want to copy onto your new body. Some objects work better than others for this. Good luck. +- "details" : (list of detail info objects) + +A detail info object can have the following parameters: +- "path" : (string) either the location of it in the scene hierarchy or in the asset bundle provided +- "assetBundle" : (string) the asset bundle containing the object +- "objFilePath" : (string) the file path to a .obj 3d model +- "mtlFilePath" : (string) the file path to the material for the .obj model +- "position" : (x, y, z) +- "rotation" : (x, y, z) the euler angle rotation from a 3d vector +- "scale" : (decimal number) +- "alignToNormal" : (true/false) If it should align with the normal vector of the surface its own (overwrites rotation) + +You have three options: Load from the scene hierarchy by setting "path", load from an asset bundle by setting "path" and "assetBundle", or load an obj file by setting "objFilePath" and "mtlFilePath". Asset bundles give much better results than .obj's. + +#### Asset Bundles +1. Start up a Unity 2017 project (I use Unity 2017.4.40f1 (64-bit), so if you use something else I can't guarantee it will work). +2. In the root directory create a folder called "Editor". In it create a file called "CreateAssetBundle.cs" with the following code in it: +``` +using UnityEditor; +using UnityEngine; +using System.IO; + +public class CreateAssetBundles +{ + [MenuItem("Assets/Build AssetBundles")] + static void BuildAllAssetBundles() + { + string assetBundleDirectory = "Assets/StreamingAssets"; + if (!Directory.Exists(Application.streamingAssetsPath)) + { + Directory.CreateDirectory(assetBundleDirectory); + } + BuildPipeline.BuildAssetBundles(assetBundleDirectory, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64); + } +} +``` +3. Create your object in the Unity scene and save it as a prefab. +4. Add all files used (models, prefabs, textures, materials, etc) to an asset bundle by selecting them and using the drop down in the bottom right. Here I am adding a rover model to my "rss" asset bundle for the Real Solar System add-on. +![setting asset bundle](https://user-images.githubusercontent.com/22628069/147954146-e1d610c0-0336-428f-8b32-bfc01090061c.png) + +5. In the top left click the "Assets" drop-down and select "Build AssetBundles". This should create your asset bundle in a folder in the root directory called "StreamingAssets". +6. Copy the asset bundle and asset bundle .manifest files from StreamingAssets into your mod's "planets" folder. If you did everything properly they should work in game. To double check everything is included, open the .manifest file in a text editor to see the files included and their paths. + ### Ring - "innerRadius" : (decimal number) - "outerRadius" : (decimal number) @@ -314,6 +360,41 @@ Use this if you are creating a star. - "tint" : (colour) - "solarFlareTint" : (colour) The flares are tinted weirdly so this won't make the actual colour of the star. You'll want to use trial and error to find something that matches. +### Signal +- "signals" : (list of signal info objects) + +Signal info objects can then have the following values set: +- "position" : (position) To find a good value for this, fly to the planet, look directly at where you want the signal to come from, and press "P" to have the game log the position you're looking at. +- "frequency" : (string) There are 7 acceptible values for this: + - "Default" : appears in game as ??? + - "Traveler" : appears in game as "Outer Wilds Ventures" + - "Quantum" : appears in game as "Quantum Fluctuations" + - "EscapePod" : appears in game as "Distress Signal" + - "Statue" : appears in game as "Nomai Statue" + - "WarpCore" : appears in game as "Anti-Graviton Flux" + - "HideAndSeek" : appears in game as "Hide and Seek" + - "Radio" : appears in game as "Deep Space Radio" +- "name" : (string) The name as it will appear in game +- "audioClip" : (string) The audio clip from the game you want to use (can find these using Unity Explorer or by datamining) +- "audioFilePath" : (string) The file path to a .wav you want to use as the audio clip +- "sourceRadius" : (decimal number) The radius of the spherical volume the signal appears to come from +- "detectionRadius" : (decimal number) How close you must be to get the "Unidentified signal detected" pop-up +- "identiicationRadius" : (decimal number) How close you must get to identify the signal +- "onlyAudibleToScope" : (true/false) +- "insideCloak" : (true/false) You have to set this to true if the signal is inside a cloaking field + +Here's an example of what all this looks like, for more check my [Signals+](https://github.com/xen-42/outer-wilds-signals-plus) add-on: +``` +"Signal" : +{ + "Signals" : + [ + { "Frequency" : "Statue", "Name" : "Museum Statue", "AudioClip" : "OW NM Flashback 082818 AP loop", "SourceRadius" : 1, "Position" : {"x": -76.35, "y": 12, "z": 214.7 } }, + { "Frequency" : "WarpCore", "Name" : "Timber Hearth Receiver", "AudioClip" : "OW_NM_WhiteHoleAmbienceL", "SourceRadius" : 0.5, "Position" : {"x": -237.8, "y": -50.8, "z": -59.2 } } + ] +} +``` + ### How to destroy existing planets You do this (but with the appropriate name) as it's own config. @@ -324,7 +405,13 @@ You do this (but with the appropriate name) as it's own config. } ``` -Remember that if you destroy Timber Hearth you better put a [Spawn](#spawn) module on another planet. I haven't tried destroying the sun. Probably don't do that, it will break everything. Probably. +Remember that if you destroy Timber Hearth you better put a [Spawn](#spawn) module on another planet. If you want to entirely replace the solar system you can restroy everything, including the sun. You can use the prefabs from my [Real Solar System](https://github.com/xen-42/outer-wilds-real-solar-system) addon, in the `planets/0 - original planets` folder. Also, deleting a planet destroys anything orbiting it, so if you want to replace the solar system you can just destroy the sun. + +### How to update existing planets + +Similar to above, make a config where "Name" is the name of the planet. The name should be able to just match their in-game english names, however if you encounter any issues with that here are the in-code names for planets that are guaranteed to work: `Sun`, `CaveTwin` (Ember Twin), `TowerTwin` (Ash Twin), `TimberHearth`, `BrittleHollow`, `GiantsDeep`, `DarkBramble`, `Comet` (Interloper), `WhiteHole`, `WhiteHoleTarget` (Whitehole station I believe), `QuantumMoon`, `ProbeCannon`, `TimberMoon` (Attlerock), `VolcanicMoon` (Hollow's Lantern), `DreamWorld`, `MapSatellite`, `RingWorld` (the Stranger). + +Only some of the above modules are supported (currently) for existing planets. Things you cannot modify for existing planets include: heightmaps, procedural generation, gravity, or their orbits. You also can't make them into stars or binary focal points (but why would you want to, just delete them and replace them entirely). However this still means there are many things you can do: completely change their atmospheres, give them rings, asteroid belts, comet tails, lava, water, prop details, or signals. ## How to use New Horizons in other mods From ed9fdd1a63d0dc4dae48e2676827f41e59920b19 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Jan 2022 11:23:15 -0500 Subject: [PATCH 07/20] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5bec6aa7..9d80679f 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Planets are created using a JSON file format structure, and placed in the `plane - [FocalPoint](#focalpoint) - [Props](#props) - [Asset Bundles](#asset-bundles) + - [Ring](#ring) - [Spawn](#spawn) - [Star](#star) - [Signal](#signal) From 2569d2654168805d2e6f343c1d8ec5318b8db989 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Jan 2022 11:27:26 -0500 Subject: [PATCH 08/20] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 9d80679f..7f2efb6d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Planets are created using a JSON file format structure, and placed in the `plane - [Spawn](#spawn) - [Star](#star) - [Signal](#signal) + - [Singularity](#singularity) - [How to destroy existing planets](#how-to-destroy-existing-planets) - [How to update existing planets](#how-to-update-existing-planets) - [How to use New Horizons in other mods](#how-to-use-new-horizons-in-other-mods) @@ -396,6 +397,12 @@ Here's an example of what all this looks like, for more check my [Signals+](http } ``` +### Singularity +This allows you to make black holes and white holes, and to pair them. +- "size" : (decimal number) +- "pairedSingularity" : (string) The singularity you want this one to pair to. Must be the opposite type. If you don't set this, the singularity will not transport you, and if it is a black hole it will kill you on entry. +- "type" : (string) Put either "BlackHole" or "WhiteHole". + ### How to destroy existing planets You do this (but with the appropriate name) as it's own config. From ec8f7c3cbdad20fa8d99d2412d878dbf0b8f97b1 Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Mon, 3 Jan 2022 11:53:57 -0500 Subject: [PATCH 09/20] Fixed bad merge Also moved black hole building to shared generation --- NewHorizons/Builder/Props/PropBuilder.cs | 16 ---------------- NewHorizons/Main.cs | 8 ++++---- NewHorizons/manifest.json | 2 +- 3 files changed, 5 insertions(+), 21 deletions(-) diff --git a/NewHorizons/Builder/Props/PropBuilder.cs b/NewHorizons/Builder/Props/PropBuilder.cs index 4482ddb3..4434af7a 100644 --- a/NewHorizons/Builder/Props/PropBuilder.cs +++ b/NewHorizons/Builder/Props/PropBuilder.cs @@ -120,22 +120,6 @@ namespace NewHorizons.Builder.Props prop.transform.parent = go.transform; prop.transform.localPosition = position == null ? Vector3.zero : (Vector3)position; - Quaternion rot = rotation == null ? prefab.transform.rotation : Quaternion.Euler((Vector3)rotation); - prop.transform.rotation = rot; - if (alignWithNormal) - { - // TODO: Make this work or smthng - if (component is GhostIK) (component as GhostIK).enabled = false; - if(component is GhostEffects) (component as GhostEffects).enabled = false; - - - var enabledField = component.GetType().GetField("enabled"); - if(enabledField != null && enabledField.FieldType == typeof(bool)) enabledField.SetValue(component, true); - } - - prop.transform.parent = go.transform; - prop.transform.localPosition = position == null ? prefab.transform.localPosition : (Vector3)position; - Quaternion rot = rotation == null ? prefab.transform.rotation : Quaternion.Euler((Vector3)rotation); prop.transform.rotation = rot; if (alignWithNormal) diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 9a1de336..d3b996df 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -99,7 +99,7 @@ namespace NewHorizons StarLightController = starLightGO.AddComponent(); StarLightController.AddStar(starController); - + starLightGO.SetActive(true); // TODO: Make this configurable probably @@ -321,9 +321,6 @@ namespace NewHorizons if (body.Config.ProcGen != null) ProcGenBuilder.Make(go, body.Config.ProcGen); - if (body.Config.Base.BlackHoleSize != 0 || body.Config.Singularity != null) - SingularityBuilder.Make(go, sector, owRigidBody, body.Config); - if (body.Config.Star != null) StarLightController.AddStar(StarBuilder.Make(go, sector, body.Config.Star)); if (body.Config.FocalPoint != null) @@ -403,6 +400,9 @@ namespace NewHorizons if (body.Config.Signal != null) SignalBuilder.Make(go, sector, body.Config.Signal, body.Assets); + if (body.Config.Base.BlackHoleSize != 0 || body.Config.Singularity != null) + SingularityBuilder.Make(go, sector, rb, body.Config); + return go; } } diff --git a/NewHorizons/manifest.json b/NewHorizons/manifest.json index 6b7dfd04..a37b854b 100644 --- a/NewHorizons/manifest.json +++ b/NewHorizons/manifest.json @@ -3,6 +3,6 @@ "author": "xen", "name": "New Horizons", "uniqueName": "xen.NewHorizons", - "version": "0.3.1", + "version": "0.4.0", "owmlVersion": "2.1.0" } From 32b6e9d75e8310bc0b2296ae5333cdfa0abd0d95 Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Mon, 3 Jan 2022 13:01:27 -0500 Subject: [PATCH 10/20] Made asteroid belts support the proc gen module --- NewHorizons/Builder/Body/AsteroidBeltBuilder.cs | 2 ++ NewHorizons/External/AsteroidBeltModule.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs b/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs index 9e49a5d2..96ea1047 100644 --- a/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs +++ b/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs @@ -59,6 +59,8 @@ namespace NewHorizons.Builder.Body } }; + var asteroidConfig = new PlanetConfig(config); + if (belt.ProcGen != null) asteroidConfig.ProcGen = belt.ProcGen; var asteroid = new NewHorizonsBody(new PlanetConfig(config), assets, uniqueName); Main.NextPassBodies.Add(asteroid); } diff --git a/NewHorizons/External/AsteroidBeltModule.cs b/NewHorizons/External/AsteroidBeltModule.cs index b2edb237..c275b376 100644 --- a/NewHorizons/External/AsteroidBeltModule.cs +++ b/NewHorizons/External/AsteroidBeltModule.cs @@ -13,5 +13,6 @@ namespace NewHorizons.External public float Inclination { get; set; } public float LongitudeOfAscendingNode { get; set; } public int RandomSeed { get; set; } + public ProcGenModule ProcGen { get; set; } } } From 981be029ef175e1631f939e52fc3351d83d1cdea Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Mon, 3 Jan 2022 14:57:34 -0500 Subject: [PATCH 11/20] Changed orbitline colour priorities --- NewHorizons/Builder/Orbital/OrbitlineBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs b/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs index c878ca52..dbc3ea8f 100644 --- a/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs +++ b/NewHorizons/Builder/Orbital/OrbitlineBuilder.cs @@ -35,9 +35,10 @@ namespace NewHorizons.Builder.Orbital if (config.Orbit.Tint != null) color = config.Orbit.Tint.ToColor32(); else if (config.Star != null) color = config.Star.Tint.ToColor32(); else if (config.Atmosphere != null && config.Atmosphere.CloudTint != null) color = config.Atmosphere.CloudTint.ToColor32(); - else if (config.Base.BlackHoleSize != 0) color = new Color(1f, 0.5f, 1f); + else if (config.Base.BlackHoleSize != 0 || config.Singularity != null) color = new Color(1f, 0.5f, 1f); else if (config.Base.WaterSize != 0) color = new Color(0.5f, 0.5f, 1f); else if (config.Base.LavaSize != 0) color = new Color(1f, 0.5f, 0.5f); + else if (config.Atmosphere != null && config.Atmosphere.FogTint != null) color = config.Atmosphere.FogTint.ToColor32(); var fade = isMoon; if (config.Base.IsSatellite) From e9b9d5982e4b3a600328c0cad34af0a846d50ffe Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Mon, 3 Jan 2022 15:21:50 -0500 Subject: [PATCH 12/20] Planets should cast shadows now --- NewHorizons/Builder/Body/HeightMapBuilder.cs | 2 ++ NewHorizons/Builder/Body/ProcGenBuilder.cs | 2 ++ NewHorizons/Builder/General/BaseBuilder.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/NewHorizons/Builder/Body/HeightMapBuilder.cs b/NewHorizons/Builder/Body/HeightMapBuilder.cs index c542f292..fb09efd8 100644 --- a/NewHorizons/Builder/Body/HeightMapBuilder.cs +++ b/NewHorizons/Builder/Body/HeightMapBuilder.cs @@ -51,6 +51,8 @@ namespace NewHorizons.Builder.Body var cubeSphereMC = cubeSphere.AddComponent(); cubeSphereMC.sharedMesh = mesh; + cubeSphere.AddComponent(); + // Fix rotation in the end cubeSphere.transform.localRotation = Quaternion.Euler(90, 0, 0); cubeSphere.transform.localPosition = Vector3.zero; diff --git a/NewHorizons/Builder/Body/ProcGenBuilder.cs b/NewHorizons/Builder/Body/ProcGenBuilder.cs index e6a75575..e53e76d7 100644 --- a/NewHorizons/Builder/Body/ProcGenBuilder.cs +++ b/NewHorizons/Builder/Body/ProcGenBuilder.cs @@ -30,6 +30,8 @@ namespace NewHorizons.Builder.Body var cubeSphereMC = icosphere.AddComponent(); cubeSphereMC.sharedMesh = mesh; icosphere.transform.localRotation = Quaternion.Euler(90, 0, 0); + + icosphere.AddComponent(); } } } diff --git a/NewHorizons/Builder/General/BaseBuilder.cs b/NewHorizons/Builder/General/BaseBuilder.cs index ac51232e..0f0870c7 100644 --- a/NewHorizons/Builder/General/BaseBuilder.cs +++ b/NewHorizons/Builder/General/BaseBuilder.cs @@ -12,6 +12,8 @@ namespace NewHorizons.Builder.General { public static Tuple Make(GameObject body, AstroObject primaryBody, IPlanetConfig config) { + body.AddComponent(); + Rigidbody rigidBody = body.AddComponent(); rigidBody.mass = 10000; rigidBody.drag = 0f; From 5eba660dfac254f0ea75f7f8a30e4d9f3eb96689 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Jan 2022 16:10:59 -0500 Subject: [PATCH 13/20] Added shields.io badges --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 7f2efb6d..8aeae538 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ ![new horizons thumbnail](https://user-images.githubusercontent.com/22628069/146680547-bd815057-9f4e-42da-a6c4-84d3ff82ff2c.png) +![Current version](https://img.shields.io/github/manifest-json/v/xen-42/outer-wilds-new-horizons?color=gree&filename=NewHorizons%2Fmanifest.json) +![Downloads](https://img.shields.io/github/downloads/xen-42/outer-wilds-new-horizons/total) +![Downloads of newest version](https://img.shields.io/github/downloads/xen-42/outer-wilds-new-horizons/latest/total) A planet creator for Outer Wilds. From 5882754f736c65766264485f249274d1285ef87e Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Jan 2022 18:50:50 -0500 Subject: [PATCH 14/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8aeae538..d71b53bf 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ Some of these I don't explain since they are just orbital parameters. If you don - "longitudeOfAscendingNode" : (decimal number) - "argumentOfPeriapsis" : (decimal number) - "eccentricity" : (decimal number from 0 to < 1) The closer to 1 it is, the more oval-shaped the orbit is. -- "trueAnomaly" : (decimal number) Where the planet should start off in its orbit in terms of the central angle. From 0 to 360. +- "trueAnomaly" : (decimal number) Where the planet should start off in its orbit in terms of the central angle. From 0 to 360. NOTE: This is currently broken for eccentric orbits which (for now) always start at periapsis. If you want to move the starting point, set argument of periapsis instead. - "axialTilt" : (decimal number) - "siderealPeriod" : (decimal number) - "isTidallyLocked" : (true/false) From 43d511dead2d1d8682c43221ad864fd34a81a352 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Jan 2022 20:33:50 -0500 Subject: [PATCH 15/20] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d71b53bf..9145c002 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ You can view the addons creators have made [here](https://outerwildsmods.com/mod Planets are created using a JSON file format structure, and placed in the `planets` folder. +NOTE: Any mod that requires PacificEngine's CommonResources is not compatible with New Horizons currently. Both mods mess with planets and orbits and end up clashing. We're currently working on a solution. + - [Roadmap](#roadmap) From d737d4af0e41bf66c45353208cb580e942363dc9 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 3 Jan 2022 21:31:36 -0500 Subject: [PATCH 16/20] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9145c002..2bd5c0dc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A planet creator for Outer Wilds. You can view the addons creators have made [here](https://outerwildsmods.com/mods/newhorizons/addons/)! -Planets are created using a JSON file format structure, and placed in the `planets` folder. +Planets are created using a JSON file format structure, and placed in the `planets` folder (or in any sub-directory of it). NOTE: Any mod that requires PacificEngine's CommonResources is not compatible with New Horizons currently. Both mods mess with planets and orbits and end up clashing. We're currently working on a solution. From a365f74760763a8c5e58b34fb58ce17f730fb4c4 Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Mon, 3 Jan 2022 23:02:17 -0500 Subject: [PATCH 17/20] Wormholes between systems + planets on title screen --- .../Builder/Body/AsteroidBeltBuilder.cs | 5 +- NewHorizons/Builder/Body/HeightMapBuilder.cs | 2 +- .../Builder/Body/SingularityBuilder.cs | 107 +++++++++++------- NewHorizons/Builder/General/ShipLogBuilder.cs | 38 +++++++ NewHorizons/Builder/Props/PropBuilder.cs | 79 +++++++------ .../Components/BlackHoleDestructionVolume.cs | 1 - .../Components/ChangeStarSystemVolume.cs | 18 +++ NewHorizons/External/IPlanetConfig.cs | 1 + NewHorizons/External/PlanetConfig.cs | 1 + NewHorizons/External/PropModule.cs | 6 +- NewHorizons/External/SingularityModule.cs | 5 +- NewHorizons/Main.cs | 92 +++++++++++++-- NewHorizons/NewHorizons.csproj | 3 + NewHorizons/Utility/DebugRaycaster.cs | 48 +++++++- NewHorizons/Utility/Patches.cs | 50 +++++++- 15 files changed, 369 insertions(+), 87 deletions(-) create mode 100644 NewHorizons/Builder/General/ShipLogBuilder.cs create mode 100644 NewHorizons/Components/ChangeStarSystemVolume.cs diff --git a/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs b/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs index 96ea1047..2543266e 100644 --- a/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs +++ b/NewHorizons/Builder/Body/AsteroidBeltBuilder.cs @@ -14,8 +14,10 @@ namespace NewHorizons.Builder.Body { static class AsteroidBeltBuilder { - public static void Make(string bodyName, AsteroidBeltModule belt, IModAssets assets, string uniqueName) + public static void Make(string bodyName, IPlanetConfig parentConfig, IModAssets assets, string uniqueName) { + var belt = parentConfig.AsteroidBelt; + var minSize = 20; var maxSize = 50; int count = (int)(2f * Mathf.PI * belt.InnerRadius / (10f * maxSize)); @@ -31,6 +33,7 @@ namespace NewHorizons.Builder.Body var config = new Dictionary() { {"Name", $"{bodyName} Asteroid {i}"}, + {"StarSystem", parentConfig.StarSystem }, {"Base", new Dictionary() { {"HasMapMarker", false }, diff --git a/NewHorizons/Builder/Body/HeightMapBuilder.cs b/NewHorizons/Builder/Body/HeightMapBuilder.cs index fb09efd8..0d29a70d 100644 --- a/NewHorizons/Builder/Body/HeightMapBuilder.cs +++ b/NewHorizons/Builder/Body/HeightMapBuilder.cs @@ -51,7 +51,7 @@ namespace NewHorizons.Builder.Body var cubeSphereMC = cubeSphere.AddComponent(); cubeSphereMC.sharedMesh = mesh; - cubeSphere.AddComponent(); + if(go.GetComponent() != null) cubeSphere.AddComponent(); // Fix rotation in the end cubeSphere.transform.localRotation = Quaternion.Euler(90, 0, 0); diff --git a/NewHorizons/Builder/Body/SingularityBuilder.cs b/NewHorizons/Builder/Body/SingularityBuilder.cs index 58ca2e68..c858ae22 100644 --- a/NewHorizons/Builder/Body/SingularityBuilder.cs +++ b/NewHorizons/Builder/Body/SingularityBuilder.cs @@ -38,16 +38,19 @@ namespace NewHorizons.Builder.Body polarity = Polarity.WhiteHole; } } - bool hasHazardVolume = pairedSingularity == null; + bool isWormHole = config.Singularity.TargetStarSystem != null; + bool hasHazardVolume = !isWormHole && (pairedSingularity == null); + + Vector3 localPosition = config.Singularity.Position == null ? Vector3.zero : (Vector3)config.Singularity.Position; GameObject newSingularity = null; switch (polarity) { case Polarity.BlackHole: - newSingularity = MakeBlackHole(body, sector, size, hasHazardVolume); + newSingularity = MakeBlackHole(body, sector, localPosition, size, hasHazardVolume, config.Singularity.TargetStarSystem); break; case Polarity.WhiteHole: - newSingularity = MakeWhiteHole(body, sector, OWRB, size); + newSingularity = MakeWhiteHole(body, sector, OWRB, localPosition, size); break; } @@ -57,33 +60,38 @@ namespace NewHorizons.Builder.Body var pairedSingularityAO = AstroObjectLocator.GetAstroObject(pairedSingularity); if(pairedSingularityAO != null) { - Logger.Log($"Pairing singularities {pairedSingularity}, {config.Name}"); - try + switch (polarity) { - switch (polarity) - { - case Polarity.BlackHole: - newSingularity.GetComponentInChildren()._whiteHole = pairedSingularityAO.GetComponentInChildren(); - break; - case Polarity.WhiteHole: - pairedSingularityAO.GetComponentInChildren()._whiteHole = newSingularity.GetComponentInChildren(); - break; - } - } - catch(Exception) - { - Logger.LogError($"Couldn't pair singularities {pairedSingularity}, {config.Name}"); + case Polarity.BlackHole: + PairSingularities(newSingularity, pairedSingularityAO.gameObject); + break; + case Polarity.WhiteHole: + PairSingularities(pairedSingularityAO.gameObject, newSingularity); + break; } } } } - private static GameObject MakeBlackHole(GameObject body, Sector sector, float size, bool hasDestructionVolume) + public static void PairSingularities(GameObject blackHole, GameObject whiteHole) + { + Logger.Log($"Pairing singularities {blackHole?.name}, {whiteHole?.name}"); + try + { + blackHole.GetComponentInChildren()._whiteHole = whiteHole.GetComponentInChildren(); + } + catch (Exception) + { + Logger.LogError($"Couldn't pair singularities"); + } + } + + public static GameObject MakeBlackHole(GameObject body, Sector sector, Vector3 localPosition, float size, bool hasDestructionVolume, string targetSolarSystem, bool makeAudio = true) { var blackHole = new GameObject("BlackHole"); blackHole.SetActive(false); blackHole.transform.parent = body.transform; - blackHole.transform.localPosition = Vector3.zero; + blackHole.transform.localPosition = localPosition; var blackHoleRender = new GameObject("BlackHoleRender"); blackHoleRender.transform.parent = blackHole.transform; @@ -101,7 +109,25 @@ namespace NewHorizons.Builder.Body meshRenderer.material.SetFloat("_MassScale", 1); meshRenderer.material.SetFloat("_DistortFadeDist", size * 0.55f); - if(hasDestructionVolume) + if(makeAudio) + { + var blackHoleAmbience = GameObject.Instantiate(GameObject.Find("BrittleHollow_Body/BlackHole_BH/BlackHoleAmbience"), blackHole.transform); + blackHoleAmbience.name = "BlackHoleAmbience"; + blackHoleAmbience.GetComponent().SetSector(sector); + + var blackHoleAudioSource = blackHoleAmbience.GetComponent(); + blackHoleAudioSource.maxDistance = size * 2.5f; + blackHoleAudioSource.minDistance = size * 0.4f; + blackHoleAmbience.transform.localPosition = Vector3.zero; + + var blackHoleOneShot = GameObject.Instantiate(GameObject.Find("BrittleHollow_Body/BlackHole_BH/BlackHoleEmissionOneShot"), blackHole.transform); + var oneShotAudioSource = blackHoleOneShot.GetComponent(); + oneShotAudioSource.maxDistance = size * 3f; + oneShotAudioSource.minDistance = size * 0.4f; + } + + + if (hasDestructionVolume || targetSolarSystem != null) { var destructionVolumeGO = new GameObject("DestructionVolume"); destructionVolumeGO.layer = LayerMask.NameToLayer("BasicEffectVolume"); @@ -113,7 +139,12 @@ namespace NewHorizons.Builder.Body sphereCollider.radius = size * 0.4f; sphereCollider.isTrigger = true; - destructionVolumeGO.AddComponent(); + if (hasDestructionVolume) destructionVolumeGO.AddComponent(); + else if (targetSolarSystem != null) + { + var wormholeVolume = destructionVolumeGO.AddComponent(); + wormholeVolume.TargetSolarSystem = targetSolarSystem; + } } else { @@ -126,12 +157,12 @@ namespace NewHorizons.Builder.Body return blackHole; } - private static GameObject MakeWhiteHole(GameObject body, Sector sector, OWRigidbody OWRB, float size) + public static GameObject MakeWhiteHole(GameObject body, Sector sector, OWRigidbody OWRB, Vector3 localPosition, float size, bool makeZeroGVolume = true) { var whiteHole = new GameObject("WhiteHole"); whiteHole.SetActive(false); whiteHole.transform.parent = body.transform; - whiteHole.transform.localPosition = Vector3.zero; + whiteHole.transform.localPosition = localPosition; var whiteHoleRenderer = new GameObject("WhiteHoleRenderer"); whiteHoleRenderer.transform.parent = whiteHole.transform; @@ -156,11 +187,7 @@ namespace NewHorizons.Builder.Body ambientLight.name = "AmbientLight"; ambientLight.GetComponent().range = size * 7f; - var proxyShadow = sector.gameObject.AddComponent(); - - // it's going to complain GameObject whiteHoleVolumeGO = GameObject.Instantiate(GameObject.Find("WhiteHole_Body/WhiteHoleVolume")); - whiteHoleVolumeGO.transform.parent = whiteHole.transform; whiteHoleVolumeGO.transform.localPosition = Vector3.zero; whiteHoleVolumeGO.transform.localScale = Vector3.one; @@ -178,23 +205,27 @@ namespace NewHorizons.Builder.Body whiteHoleVolume._whiteHoleSector = sector; whiteHoleVolume._fluidVolume = whiteHoleFluidVolume; whiteHoleVolume._whiteHoleBody = OWRB; - whiteHoleVolume._whiteHoleProxyShadowSuperGroup = proxyShadow; + whiteHoleVolume._whiteHoleProxyShadowSuperGroup = body.GetComponent(); whiteHoleVolumeGO.GetComponent().radius = size; whiteHoleVolume.enabled = true; whiteHoleFluidVolume.enabled = true; - var zeroGVolume = GameObject.Instantiate(GameObject.Find("WhiteHole_Body/ZeroGVolume"), whiteHole.transform); - zeroGVolume.name = "ZeroGVolume"; - zeroGVolume.GetComponent().radius = size * 10f; - zeroGVolume.GetComponent()._attachedBody = OWRB; + if(makeZeroGVolume) + { + var zeroGVolume = GameObject.Instantiate(GameObject.Find("WhiteHole_Body/ZeroGVolume"), whiteHole.transform); + zeroGVolume.name = "ZeroGVolume"; + zeroGVolume.transform.localPosition = Vector3.zero; + zeroGVolume.GetComponent().radius = size * 10f; + zeroGVolume.GetComponent()._attachedBody = OWRB; - var rulesetVolume = GameObject.Instantiate(GameObject.Find("WhiteHole_Body/Sector_WhiteHole/RulesetVolumes_WhiteHole"), sector.transform); - rulesetVolume.name = "RulesetVolume"; - rulesetVolume.transform.localPosition = Vector3.zero; - rulesetVolume.transform.localScale = Vector3.one * size / 100f; - rulesetVolume.GetComponent().enabled = true; + var rulesetVolume = GameObject.Instantiate(GameObject.Find("WhiteHole_Body/Sector_WhiteHole/RulesetVolumes_WhiteHole"), body.transform); + rulesetVolume.name = "RulesetVolume"; + rulesetVolume.transform.localPosition = Vector3.zero; + rulesetVolume.transform.localScale = Vector3.one * size / 100f; + rulesetVolume.GetComponent().enabled = true; + } whiteHole.SetActive(true); return whiteHole; diff --git a/NewHorizons/Builder/General/ShipLogBuilder.cs b/NewHorizons/Builder/General/ShipLogBuilder.cs new file mode 100644 index 00000000..7e0f654f --- /dev/null +++ b/NewHorizons/Builder/General/ShipLogBuilder.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace NewHorizons.Builder.General +{ + public static class ShipLogBuilder + { + public static ShipLogDetectiveMode StarChartMode; + + public static void Init() + { + var shipLogRoot = GameObject.Find("Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas"); + var starChartLog = GameObject.Instantiate(shipLogRoot.transform.Find("DetectiveMode"), shipLogRoot.transform); + starChartLog.transform.name = "StarChartMode"; + + var cardRoot = starChartLog.transform.Find("ScaleRoot").Find("PanRoot"); + foreach(Transform child in cardRoot) + { + GameObject.Destroy(child.gameObject); + } + + var cardPrefab = GameObject.Find("Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas/DetectiveMode/ScaleRoot/PanRoot/TH_VILLAGE"); + + var detectiveMode = GameObject.Find("Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas/DetectiveMode/"); + var mapMode = GameObject.Find("Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas/MapMode/"); + + StarChartMode = starChartLog.GetComponent(); + + StarChartMode._cardDict = new Dictionary(); + StarChartMode._cardList = new List(); + StarChartMode._centerPromptList = detectiveMode.GetComponent()._centerPromptList; + } + } +} diff --git a/NewHorizons/Builder/Props/PropBuilder.cs b/NewHorizons/Builder/Props/PropBuilder.cs index 4434af7a..c3707beb 100644 --- a/NewHorizons/Builder/Props/PropBuilder.cs +++ b/NewHorizons/Builder/Props/PropBuilder.cs @@ -17,43 +17,17 @@ namespace NewHorizons.Builder.Props { public static void Make(GameObject go, Sector sector, IPlanetConfig config, IModAssets assets, string uniqueModName) { - if (config.Props.Scatter != null) PropBuilder.Scatter(go, config.Props.Scatter, config.Base.SurfaceSize, sector); + if (config.Props.Scatter != null) + { + PropBuilder.MakeScatter(go, config.Props.Scatter, config.Base.SurfaceSize, sector, assets, uniqueModName); + } if(config.Props.Details != null) { foreach(var detail in config.Props.Details) { if(detail.assetBundle != null) { - string key = uniqueModName + "." + detail.assetBundle; - AssetBundle bundle; - GameObject prefab; - - try - { - if (Main.AssetBundles.ContainsKey(key)) bundle = Main.AssetBundles[key]; - else - { - bundle = assets.LoadBundle(detail.assetBundle); - Main.AssetBundles[key] = bundle; - } - } - catch(Exception e) - { - Logger.Log($"Couldn't load AssetBundle {detail.assetBundle} : {e.Message}"); - return; - } - - try - { - prefab = bundle.LoadAsset(detail.path); - prefab.SetActive(false); - } - catch(Exception e) - { - Logger.Log($"Couldn't load asset {detail.path} from AssetBundle {detail.assetBundle} : {e.Message}"); - return; - } - + var prefab = LoadPrefab(detail.assetBundle, detail.path, uniqueModName, assets); MakeDetail(go, sector, prefab, detail.position, detail.rotation, detail.scale, detail.alignToNormal); } else if(detail.objFilePath != null) @@ -139,19 +113,21 @@ namespace NewHorizons.Builder.Props return prop; } - private static void Scatter(GameObject go, PropModule.ScatterInfo[] scatterInfo, float radius, Sector sector) + private static void MakeScatter(GameObject go, PropModule.ScatterInfo[] scatterInfo, float radius, Sector sector, IModAssets assets, string uniqueModName) { var area = 4f * Mathf.PI * radius * radius; var points = FibonacciSphere((int)area); foreach (var propInfo in scatterInfo) { - var prefab = GameObject.Find(propInfo.path); + GameObject prefab; + if (propInfo.assetBundle != null) prefab = LoadPrefab(propInfo.assetBundle, propInfo.path, uniqueModName, assets); + else prefab = GameObject.Find(propInfo.path); for(int i = 0; i < propInfo.count; i++) { var randomInd = (int)Random.Range(0, points.Count); var point = points[randomInd]; - var prop = MakeDetail(go, sector, prefab, (MVector3)(point.normalized * radius), null, 0f, true, true); + var prop = MakeDetail(go, sector, prefab, (MVector3)(point.normalized * radius), null, propInfo.scale, true, true); if(propInfo.offset != null) prop.transform.localPosition += prop.transform.TransformVector(propInfo.offset); if(propInfo.rotation != null) prop.transform.rotation *= Quaternion.Euler(propInfo.rotation); points.RemoveAt(randomInd); @@ -180,5 +156,40 @@ namespace NewHorizons.Builder.Props } return points; } + + private static GameObject LoadPrefab(string assetBundle, string path, string uniqueModName, IModAssets assets) + { + string key = uniqueModName + "." + assetBundle; + AssetBundle bundle; + GameObject prefab; + + try + { + if (Main.AssetBundles.ContainsKey(key)) bundle = Main.AssetBundles[key]; + else + { + bundle = assets.LoadBundle(assetBundle); + Main.AssetBundles[key] = bundle; + } + } + catch (Exception e) + { + Logger.LogError($"Couldn't load AssetBundle {assetBundle} : {e.Message}"); + return null; + } + + try + { + prefab = bundle.LoadAsset(path); + prefab.SetActive(false); + } + catch (Exception e) + { + Logger.Log($"Couldn't load asset {path} from AssetBundle {assetBundle} : {e.Message}"); + return null; + } + + return prefab; + } } } diff --git a/NewHorizons/Components/BlackHoleDestructionVolume.cs b/NewHorizons/Components/BlackHoleDestructionVolume.cs index 23663f89..8c2d9ffc 100644 --- a/NewHorizons/Components/BlackHoleDestructionVolume.cs +++ b/NewHorizons/Components/BlackHoleDestructionVolume.cs @@ -17,7 +17,6 @@ namespace NewHorizons.Components public override void VanishProbe(OWRigidbody probeBody, RelativeLocationData entryLocation) { - Logger.Log($"Uh oh you shot your probe into a black hole"); SurveyorProbe requiredComponent = probeBody.GetRequiredComponent(); if (requiredComponent.IsLaunched()) { diff --git a/NewHorizons/Components/ChangeStarSystemVolume.cs b/NewHorizons/Components/ChangeStarSystemVolume.cs new file mode 100644 index 00000000..3a754057 --- /dev/null +++ b/NewHorizons/Components/ChangeStarSystemVolume.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.Components +{ + public class ChangeStarSystemVolume : BlackHoleDestructionVolume + { + public string TargetSolarSystem { get; set; } + + public override void VanishPlayer(OWRigidbody playerBody, RelativeLocationData entryLocation) + { + Main.Instance.ChangeCurrentStarSystem(TargetSolarSystem); + } + } +} diff --git a/NewHorizons/External/IPlanetConfig.cs b/NewHorizons/External/IPlanetConfig.cs index 98e0686e..db8f9af4 100644 --- a/NewHorizons/External/IPlanetConfig.cs +++ b/NewHorizons/External/IPlanetConfig.cs @@ -5,6 +5,7 @@ namespace NewHorizons.External public interface IPlanetConfig { string Name { get; } + string StarSystem { get; } bool Destroy { get; } int BuildPriority { get; } BaseModule Base {get;} diff --git a/NewHorizons/External/PlanetConfig.cs b/NewHorizons/External/PlanetConfig.cs index 80e9db8a..357db378 100644 --- a/NewHorizons/External/PlanetConfig.cs +++ b/NewHorizons/External/PlanetConfig.cs @@ -8,6 +8,7 @@ namespace NewHorizons.External public class PlanetConfig : IPlanetConfig { public string Name { get; set; } + public string StarSystem { get; set; } = "SolarSystem"; public bool Destroy { get; set; } public int BuildPriority { get; set; } = -1; public MVector3 SpawnPoint { get; set; } diff --git a/NewHorizons/External/PropModule.cs b/NewHorizons/External/PropModule.cs index 3ac6db8c..7edc9cb9 100644 --- a/NewHorizons/External/PropModule.cs +++ b/NewHorizons/External/PropModule.cs @@ -15,10 +15,12 @@ namespace NewHorizons.External public class ScatterInfo { - public string path; public int count; + public string path; + public string assetBundle; public MVector3 offset; public MVector3 rotation; + public float scale { get; set; } = 1f; } public class DetailInfo @@ -29,7 +31,7 @@ namespace NewHorizons.External public string assetBundle; public MVector3 position; public MVector3 rotation; - public float scale; + public float scale { get; set; } = 1f; public bool alignToNormal; } } diff --git a/NewHorizons/External/SingularityModule.cs b/NewHorizons/External/SingularityModule.cs index 6d30d96c..42fae1aa 100644 --- a/NewHorizons/External/SingularityModule.cs +++ b/NewHorizons/External/SingularityModule.cs @@ -1,4 +1,5 @@ -using System; +using NewHorizons.Utility; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,6 +11,8 @@ namespace NewHorizons.External { public float Size; public string PairedSingularity; + public string TargetStarSystem; public string Type; //BlackHole or WhiteHole + public MVector3 Position; } } diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index d3b996df..4e65cd3d 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -17,6 +17,7 @@ using System.Linq; using System.Reflection; using UnityEngine; using UnityEngine.SceneManagement; +using UnityEngine.UI; using Logger = NewHorizons.Utility.Logger; namespace NewHorizons @@ -29,13 +30,12 @@ namespace NewHorizons public static List BodyList = new List(); public static List NextPassBodies = new List(); - public static Dictionary AssetBundles = new Dictionary(); - public static float FurthestOrbit { get; set; } = 50000f; - public StarLightController StarLightController { get; private set; } + private static string _currentStarSystem = "SolarSystem"; + public override object GetApi() { return new NewHorizonsApi(); @@ -59,6 +59,9 @@ namespace NewHorizons { Logger.LogWarning("Couldn't find planets folder"); } + + //UnityEngine.Random.InitState(); + Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single)); } public void OnDestroy() @@ -71,10 +74,14 @@ namespace NewHorizons { Logger.Log($"Scene Loaded: {scene.name} {mode}"); - if (scene.name != "SolarSystem") { return; } + if (scene.name.Equals("TitleScreen")) DisplayBodyOnTitleScreen(); + + if (scene.name != "SolarSystem") return; NewHorizonsData.Load(); + Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => ShipLogBuilder.Init()); + // Need to manage this when there are multiple stars var sun = GameObject.Find("Sun_Body"); var starController = sun.AddComponent(); @@ -115,8 +122,10 @@ namespace NewHorizons AstroObjectLocator.AddAstroObject(ao); } - // Stars then planets then moons (not necessary but probably speeds things up, maybe) - var toLoad = BodyList.OrderBy(b => + // Order by stars then planets then moons (not necessary but probably speeds things up, maybe) ALSO only include current star system + var toLoad = BodyList + .Where(b => b.Config.StarSystem.Equals(_currentStarSystem)) + .OrderBy(b => (b.Config.BuildPriority != -1 ? b.Config.BuildPriority : (b.Config.FocalPoint != null ? 0 : (b.Config.Star != null) ? 0 : @@ -174,6 +183,69 @@ namespace NewHorizons */ } + public void DisplayBodyOnTitleScreen() + { + //Try loading one planet why not + GameObject titleScreenGO = new GameObject("TitleScreenPlanet"); + var eligible = BodyList.Where(b => b.Config.Ring != null && (b.Config.HeightMap != null || (b.Config.Atmosphere?.Cloud != null))).ToArray(); + var body = eligible[UnityEngine.Random.Range(0, eligible.Count())]; + + Logger.Log($"Displaying {body.Config.Name} on the title screen"); + + var flag = false; + HeightMapModule heightMap = new HeightMapModule(); + var minSize = 20; + var maxSize = 35; + if (body.Config.HeightMap != null) + { + var size = Mathf.Clamp(body.Config.HeightMap.MaxHeight / 10, minSize, maxSize); + heightMap.TextureMap = body.Config.HeightMap.TextureMap; + heightMap.HeightMap = body.Config.HeightMap.HeightMap; + heightMap.MaxHeight = size; + heightMap.MinHeight = body.Config.HeightMap.MinHeight * size / body.Config.HeightMap.MaxHeight; + flag = true; + } + if (body.Config.Atmosphere != null && body.Config.Atmosphere.Cloud != null) + { + // Hacky but whatever I just want a sphere + var size = Mathf.Clamp(body.Config.Atmosphere.Size / 10, minSize, maxSize); + heightMap.MaxHeight = heightMap.MinHeight = size+1; + heightMap.TextureMap = body.Config.Atmosphere.Cloud; + flag = true; + } + + if (flag) + { + HeightMapBuilder.Make(titleScreenGO, heightMap, body.Assets); + if (body.Config.Ring != null) + { + RingModule newRing = new RingModule(); + newRing.InnerRadius = maxSize * 1.2f; + newRing.OuterRadius = maxSize * 2f; + newRing.Texture = body.Config.Ring.Texture; + RingBuilder.Make(titleScreenGO, newRing, body.Assets); + titleScreenGO.transform.localScale = Vector3.one * 0.8f; + } + GameObject.Find("Scene/Background/PlanetPivot/Prefab_HEA_Campfire").SetActive(false); + GameObject.Find("Scene/Background/PlanetPivot/PlanetRoot").SetActive(false); + titleScreenGO.transform.parent = GameObject.Find("Scene/Background/PlanetPivot/").transform; + titleScreenGO.transform.localPosition = Vector3.zero; + + var lightGO = new GameObject("Light"); + lightGO.transform.parent = titleScreenGO.transform.parent.parent; + lightGO.transform.localPosition = new Vector3(-47.9203f, 145.7596f, 43.1802f); + var light = lightGO.AddComponent(); + light.color = new Color(1f, 1f, 1f, 1f); + light.range = 100; + light.intensity = 0.8f; + } + + var img = GameObject.Instantiate(GameObject.Find("TitleMenu/TitleCanvas/TitleLayoutGroup/Logo_EchoesOfTheEye").GetComponent()); + var texture = Main.Instance.ModHelper.Assets.GetTexture("AssetBundle/logo.png"); + img.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(texture.width / 2f, texture.height / 2f)); + img.transform.parent = GameObject.Find("TitleMenu").transform; + } + private bool LoadBody(NewHorizonsBody body, bool defaultPrimaryToSun = false) { var stringID = body.Config.Name.ToUpper().Replace(" ", "_").Replace("'", ""); @@ -365,7 +437,7 @@ namespace NewHorizons RingBuilder.Make(go, body.Config.Ring, body.Assets); if (body.Config.AsteroidBelt != null) - AsteroidBeltBuilder.Make(body.Config.Name, body.Config.AsteroidBelt, body.Assets, body.ModUniqueName); + AsteroidBeltBuilder.Make(body.Config.Name, body.Config, body.Assets, body.ModUniqueName); if (body.Config.Base.HasCometTail) CometTailBuilder.Make(go, body.Config.Base, go.GetComponent().GetPrimaryBody()); @@ -405,6 +477,12 @@ namespace NewHorizons return go; } + + public void ChangeCurrentStarSystem(string newStarSystem) + { + _currentStarSystem = newStarSystem; + LoadManager.LoadSceneAsync(OWScene.SolarSystem, true, LoadManager.FadeType.ToBlack, 0.1f, true); + } } public class NewHorizonsApi diff --git a/NewHorizons/NewHorizons.csproj b/NewHorizons/NewHorizons.csproj index 486ddf83..bc2fab05 100644 --- a/NewHorizons/NewHorizons.csproj +++ b/NewHorizons/NewHorizons.csproj @@ -25,6 +25,9 @@ Always + + + Always diff --git a/NewHorizons/Utility/DebugRaycaster.cs b/NewHorizons/Utility/DebugRaycaster.cs index 387d78f0..39537d07 100644 --- a/NewHorizons/Utility/DebugRaycaster.cs +++ b/NewHorizons/Utility/DebugRaycaster.cs @@ -1,4 +1,5 @@ -using System; +using NewHorizons.Builder.Body; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -13,6 +14,9 @@ namespace NewHorizons.Utility { private OWRigidbody _rb; + private GameObject blackHole; + private GameObject whiteHole; + private void Awake() { _rb = this.GetRequiredComponent(); @@ -34,6 +38,48 @@ namespace NewHorizons.Utility } _rb.EnableCollisionDetection(); } + + /* + // Portal Gun: + if (Keyboard.current == null) return; + var fireBlackHole = Keyboard.current[Key.B].wasReleasedThisFrame; + var fireWhiteHole = Keyboard.current[Key.N].wasReleasedThisFrame; + if (fireBlackHole || fireWhiteHole) + { + // Raycast + _rb.DisableCollisionDetection(); + int layerMask = OWLayerMask.physicalMask; + var origin = Locator.GetActiveCamera().transform.position; + var direction = Locator.GetActiveCamera().transform.TransformDirection(Vector3.forward); + if (Physics.Raycast(origin, direction, out RaycastHit hitInfo, Mathf.Infinity, OWLayerMask.physicalMask)) + { + var pos = hitInfo.transform.InverseTransformPoint(hitInfo.point + hitInfo.normal); + var hitBody = hitInfo.transform.gameObject; + var sector = hitBody.GetComponent()?.GetRootSector(); + + if (hitBody == null || sector == null) return; + Logger.Log($"{hitBody}"); + if (fireBlackHole) + { + if (blackHole != null) GameObject.Destroy(blackHole); + blackHole = SingularityBuilder.MakeBlackHole(hitBody, sector, pos, 2, false, null, false); + Logger.Log("Make black hole"); + } + else + { + if (whiteHole != null) GameObject.Destroy(whiteHole); + whiteHole = SingularityBuilder.MakeWhiteHole(hitBody, sector, hitBody.GetAttachedOWRigidbody(), pos, 2, false); + Logger.Log("Make white hole"); + } + + if(blackHole && whiteHole) + { + SingularityBuilder.PairSingularities(blackHole, whiteHole); + } + } + _rb.EnableCollisionDetection(); + } + */ } } } diff --git a/NewHorizons/Utility/Patches.cs b/NewHorizons/Utility/Patches.cs index d7f6b01a..a8e9b08a 100644 --- a/NewHorizons/Utility/Patches.cs +++ b/NewHorizons/Utility/Patches.cs @@ -1,4 +1,5 @@ -using NewHorizons.Builder.Props; +using NewHorizons.Builder.General; +using NewHorizons.Builder.Props; using NewHorizons.Components; using NewHorizons.External; using OWML.Common; @@ -47,6 +48,8 @@ namespace NewHorizons.Utility Main.Instance.ModHelper.HarmonyHelper.AddPrefix("Awake", typeof(Patches), nameof(Patches.OnWhiteHoleVolumeAwake)); Main.Instance.ModHelper.HarmonyHelper.AddPrefix("UpdateOrbitalLaunchValues", typeof(Patches), nameof(Patches.OnProbeLauncherUpdateOrbitalLaunchValues)); + Main.Instance.ModHelper.HarmonyHelper.AddPrefix("Update", typeof(Patches), nameof(Patches.OnShipLogControllerUpdate)); + // Postfixes Main.Instance.ModHelper.HarmonyHelper.AddPostfix("Awake", typeof(Patches), nameof(Patches.OnMapControllerAwake)); Main.Instance.ModHelper.HarmonyHelper.AddPostfix("Awake", typeof(Patches), nameof(Patches.OnOWCameraAwake)); @@ -335,5 +338,50 @@ namespace NewHorizons.Utility { return (Locator.GetPlayerRulesetDetector()?.GetPlanetoidRuleset()?.GetGravityVolume() != null); } + + // Replacing the entire method + public static bool OnShipLogControllerUpdate(ShipLogController __instance) + { + if (__instance._exiting) + { + if (__instance._canvasAnimator.IsComplete()) + { + __instance.enabled = false; + __instance._shipLogCanvas.gameObject.SetActive(false); + } + return false; + } + if (OWInput.GetInputMode() != InputMode.ShipComputer) + { + return false; + } + __instance._exitPrompt.SetVisibility(__instance._currentMode.AllowCancelInput()); + if (__instance._currentMode.AllowCancelInput() && OWInput.IsNewlyPressed(InputLibrary.cancel, InputMode.All)) + { + __instance.ExitShipComputer(); + return false; + } + __instance._currentMode.UpdateMode(); + if (__instance._currentMode.AllowModeSwap() && OWInput.IsNewlyPressed(InputLibrary.swapShipLogMode, InputMode.All)) + { + ShipLogMode currentMode = __instance._currentMode; + string focusedEntryID = currentMode.GetFocusedEntryID(); + bool flag = currentMode.Equals(__instance._mapMode); + __instance._currentMode = (flag ? __instance._detectiveMode : __instance._mapMode); + + if (currentMode.Equals(__instance._mapMode)) + __instance._currentMode = ShipLogBuilder.StarChartMode; + else if (currentMode.Equals(ShipLogBuilder.StarChartMode)) + __instance._currentMode = __instance._detectiveMode; + else + __instance._currentMode = __instance._mapMode; + + currentMode.ExitMode(); + __instance._currentMode.EnterMode(focusedEntryID, null); + __instance._oneShotSource.PlayOneShot(flag ? global::AudioType.ShipLogEnterDetectiveMode : global::AudioType.ShipLogEnterMapMode, 1f); + } + + return false; + } } } From 8334c67206532fa8df855caa31ab389e7cdabbe2 Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Mon, 3 Jan 2022 23:16:32 -0500 Subject: [PATCH 18/20] Fix title scene body --- NewHorizons/Builder/Atmosphere/CloudsBuilder.cs | 2 -- NewHorizons/Main.cs | 14 +++++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs b/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs index fee15518..c948ff6e 100644 --- a/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs @@ -20,8 +20,6 @@ namespace NewHorizons.Atmosphere if (atmo.CloudCap == null) cap = ImageUtilities.ClearTexture(128, 128); else cap = assets.GetTexture(atmo.CloudCap); - - //if(atmo.CloudRamp == null) ramp = ImageUtilities.Scaled(image, 1, 128); if(atmo.CloudRamp == null) ramp = ImageUtilities.CanvasScaled(image, 1, image.height); else ramp = assets.GetTexture(atmo.CloudRamp); } diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 4e65cd3d..301130a3 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -196,9 +196,10 @@ namespace NewHorizons HeightMapModule heightMap = new HeightMapModule(); var minSize = 20; var maxSize = 35; + float size = minSize; if (body.Config.HeightMap != null) { - var size = Mathf.Clamp(body.Config.HeightMap.MaxHeight / 10, minSize, maxSize); + size = Mathf.Clamp(body.Config.HeightMap.MaxHeight / 10, minSize, maxSize); heightMap.TextureMap = body.Config.HeightMap.TextureMap; heightMap.HeightMap = body.Config.HeightMap.HeightMap; heightMap.MaxHeight = size; @@ -208,7 +209,7 @@ namespace NewHorizons if (body.Config.Atmosphere != null && body.Config.Atmosphere.Cloud != null) { // Hacky but whatever I just want a sphere - var size = Mathf.Clamp(body.Config.Atmosphere.Size / 10, minSize, maxSize); + size = Mathf.Clamp(body.Config.Atmosphere.Size / 10, minSize, maxSize); heightMap.MaxHeight = heightMap.MinHeight = size+1; heightMap.TextureMap = body.Config.Atmosphere.Cloud; flag = true; @@ -220,8 +221,8 @@ namespace NewHorizons if (body.Config.Ring != null) { RingModule newRing = new RingModule(); - newRing.InnerRadius = maxSize * 1.2f; - newRing.OuterRadius = maxSize * 2f; + newRing.InnerRadius = size * 1.2f; + newRing.OuterRadius = size * 2f; newRing.Texture = body.Config.Ring.Texture; RingBuilder.Make(titleScreenGO, newRing, body.Assets); titleScreenGO.transform.localScale = Vector3.one * 0.8f; @@ -239,11 +240,6 @@ namespace NewHorizons light.range = 100; light.intensity = 0.8f; } - - var img = GameObject.Instantiate(GameObject.Find("TitleMenu/TitleCanvas/TitleLayoutGroup/Logo_EchoesOfTheEye").GetComponent()); - var texture = Main.Instance.ModHelper.Assets.GetTexture("AssetBundle/logo.png"); - img.sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(texture.width / 2f, texture.height / 2f)); - img.transform.parent = GameObject.Find("TitleMenu").transform; } private bool LoadBody(NewHorizonsBody body, bool defaultPrimaryToSun = false) From e3015b51e9ba7d0f7f7385b0de21846eaf405abe Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Tue, 4 Jan 2022 02:22:09 -0500 Subject: [PATCH 19/20] Fixed heightmap shader seam --- NewHorizons/AssetBundle/shader | Bin 18768 -> 28653 bytes NewHorizons/AssetBundle/shader.manifest | 9 +++++---- .../Builder/Body/Geometry/CubeSphere.cs | 4 ++-- NewHorizons/Builder/Body/HeightMapBuilder.cs | 2 +- NewHorizons/Builder/Body/RingBuilder.cs | 6 ++++-- NewHorizons/External/RingModule.cs | 1 + 6 files changed, 13 insertions(+), 9 deletions(-) diff --git a/NewHorizons/AssetBundle/shader b/NewHorizons/AssetBundle/shader index a6cf02462fe92a16a8dbf3ca8465ece8becc9894..148eca35bfb86bb521405608730df269f2ee95fc 100644 GIT binary patch literal 28653 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 gU{ZvKQt3&n9kO*&J1!nFhZ-L`HkoyrG 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 L#An<8<8)_w7z|CT diff --git a/NewHorizons/AssetBundle/shader.manifest b/NewHorizons/AssetBundle/shader.manifest index d8a544b8..cb6d8e25 100644 --- a/NewHorizons/AssetBundle/shader.manifest +++ b/NewHorizons/AssetBundle/shader.manifest @@ -1,9 +1,9 @@ ManifestFileVersion: 0 -CRC: 3840241390 +CRC: 3597037522 Hashes: AssetFileHash: serializedVersion: 2 - Hash: 1c2c90716d1ed7c9cbf5f785e41eace6 + Hash: 33678ea445c06b269454371064cc3a5c TypeTreeHash: serializedVersion: 2 Hash: 6370d3f9de9eca57f523bce048404a46 @@ -12,6 +12,7 @@ ClassTypes: - Class: 48 Script: {instanceID: 0} Assets: -- Assets/SphereTextureWrapper.shader -- Assets/UnlitTransparent.shader +- Assets/Shaders/SphereTextureWrapper.shader +- Assets/Shaders/Ring.shader +- Assets/Shaders/UnlitTransparent.shader Dependencies: [] diff --git a/NewHorizons/Builder/Body/Geometry/CubeSphere.cs b/NewHorizons/Builder/Body/Geometry/CubeSphere.cs index 01653ee6..ebe11193 100644 --- a/NewHorizons/Builder/Body/Geometry/CubeSphere.cs +++ b/NewHorizons/Builder/Body/Geometry/CubeSphere.cs @@ -97,8 +97,8 @@ namespace NewHorizons.Body float latitude = (Mathf.Rad2Deg * Mathf.Acos(v.z / Mathf.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z))); float longitude = 180f; - if(v.x > 0) longitude = Mathf.Rad2Deg * Mathf.Atan(v.y / v.x) + 90f; - if(v.x < 0) longitude = Mathf.Rad2Deg * (Mathf.Atan(v.y / v.x) + Mathf.PI) + 90f; + if(v.x > 0) longitude = Mathf.Rad2Deg * Mathf.Atan(v.y / v.x); + if(v.x < 0) longitude = Mathf.Rad2Deg * (Mathf.Atan(v.y / v.x) + Mathf.PI); float sampleX = heightMap.width * longitude / 360f; float sampleY = heightMap.height * latitude / 180f; diff --git a/NewHorizons/Builder/Body/HeightMapBuilder.cs b/NewHorizons/Builder/Body/HeightMapBuilder.cs index 0d29a70d..cf47994a 100644 --- a/NewHorizons/Builder/Body/HeightMapBuilder.cs +++ b/NewHorizons/Builder/Body/HeightMapBuilder.cs @@ -42,7 +42,7 @@ namespace NewHorizons.Builder.Body cubeSphere.AddComponent(); cubeSphere.GetComponent().mesh = mesh; - if(PlanetShader == null) PlanetShader = Main.ShaderBundle.LoadAsset("Assets/SphereTextureWrapper.shader"); + if(PlanetShader == null) PlanetShader = Main.ShaderBundle.LoadAsset("Assets/Shaders/SphereTextureWrapper.shader"); var cubeSphereMR = cubeSphere.AddComponent(); cubeSphereMR.material = new Material(PlanetShader); diff --git a/NewHorizons/Builder/Body/RingBuilder.cs b/NewHorizons/Builder/Body/RingBuilder.cs index 9a0a357d..4356a955 100644 --- a/NewHorizons/Builder/Body/RingBuilder.cs +++ b/NewHorizons/Builder/Body/RingBuilder.cs @@ -13,6 +13,7 @@ namespace NewHorizons.Builder.Body static class RingBuilder { public static Shader RingShader; + public static Shader UnlitShader; public static void Make(GameObject body, RingModule ring, IModAssets assets) { @@ -39,9 +40,10 @@ namespace NewHorizons.Builder.Body var ringMR = ringGO.AddComponent(); var texture = ringTexture; - if (RingShader == null) RingShader = Main.ShaderBundle.LoadAsset("Assets/UnlitTransparent.shader"); + if (RingShader == null) RingShader = Main.ShaderBundle.LoadAsset("Assets/Shaders/Ring.shader"); + if (UnlitShader == null) UnlitShader = Main.ShaderBundle.LoadAsset("Assets/Shaders/UnlitTransparent.shader"); - var mat = new Material(RingShader); + var mat = new Material(ring.Unlit ? UnlitShader : RingShader); mat.mainTexture = texture; mat.renderQueue = 2895; ringMR.material = mat; diff --git a/NewHorizons/External/RingModule.cs b/NewHorizons/External/RingModule.cs index 1be64325..bcb35531 100644 --- a/NewHorizons/External/RingModule.cs +++ b/NewHorizons/External/RingModule.cs @@ -13,5 +13,6 @@ namespace NewHorizons.External public float Inclination { get; set; } public float LongitudeOfAscendingNode { get; set; } public string Texture { get; set; } + public bool Unlit { get; set; } = true; } } From 085ddbf3d3082c7dcc2ea2a4269bec818b4d1b73 Mon Sep 17 00:00:00 2001 From: "Nick J. Connors" Date: Tue, 4 Jan 2022 12:42:53 -0500 Subject: [PATCH 20/20] Disable ship log builder for now --- NewHorizons/Builder/General/ShipLogBuilder.cs | 2 ++ NewHorizons/Utility/Patches.cs | 26 +++++-------------- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/NewHorizons/Builder/General/ShipLogBuilder.cs b/NewHorizons/Builder/General/ShipLogBuilder.cs index 7e0f654f..1502b5e6 100644 --- a/NewHorizons/Builder/General/ShipLogBuilder.cs +++ b/NewHorizons/Builder/General/ShipLogBuilder.cs @@ -13,6 +13,7 @@ namespace NewHorizons.Builder.General public static void Init() { + /* var shipLogRoot = GameObject.Find("Ship_Body/Module_Cabin/Systems_Cabin/ShipLogPivot/ShipLog/ShipLogPivot/ShipLogCanvas"); var starChartLog = GameObject.Instantiate(shipLogRoot.transform.Find("DetectiveMode"), shipLogRoot.transform); starChartLog.transform.name = "StarChartMode"; @@ -33,6 +34,7 @@ namespace NewHorizons.Builder.General StarChartMode._cardDict = new Dictionary(); StarChartMode._cardList = new List(); StarChartMode._centerPromptList = detectiveMode.GetComponent()._centerPromptList; + */ } } } diff --git a/NewHorizons/Utility/Patches.cs b/NewHorizons/Utility/Patches.cs index a8e9b08a..3f3a6532 100644 --- a/NewHorizons/Utility/Patches.cs +++ b/NewHorizons/Utility/Patches.cs @@ -339,28 +339,15 @@ namespace NewHorizons.Utility return (Locator.GetPlayerRulesetDetector()?.GetPlanetoidRuleset()?.GetGravityVolume() != null); } - // Replacing the entire method public static bool OnShipLogControllerUpdate(ShipLogController __instance) { - if (__instance._exiting) - { - if (__instance._canvasAnimator.IsComplete()) - { - __instance.enabled = false; - __instance._shipLogCanvas.gameObject.SetActive(false); - } - return false; - } - if (OWInput.GetInputMode() != InputMode.ShipComputer) - { - return false; - } + if (__instance._exiting + || OWInput.GetInputMode() != InputMode.ShipComputer + || __instance._currentMode.AllowCancelInput() && OWInput.IsNewlyPressed(InputLibrary.cancel, InputMode.All) + || ShipLogBuilder.StarChartMode == null) + return true; + __instance._exitPrompt.SetVisibility(__instance._currentMode.AllowCancelInput()); - if (__instance._currentMode.AllowCancelInput() && OWInput.IsNewlyPressed(InputLibrary.cancel, InputMode.All)) - { - __instance.ExitShipComputer(); - return false; - } __instance._currentMode.UpdateMode(); if (__instance._currentMode.AllowModeSwap() && OWInput.IsNewlyPressed(InputLibrary.swapShipLogMode, InputMode.All)) { @@ -380,7 +367,6 @@ namespace NewHorizons.Utility __instance._currentMode.EnterMode(focusedEntryID, null); __instance._oneShotSource.PlayOneShot(flag ? global::AudioType.ShipLogEnterDetectiveMode : global::AudioType.ShipLogEnterMapMode, 1f); } - return false; } }