diff --git a/.gitignore b/.gitignore index 94675367..4e3a3cec 100644 --- a/.gitignore +++ b/.gitignore @@ -417,4 +417,9 @@ docs/.m_cache/** # Unity project new-horizons-unity new-horizons-unity/** +nh-unity +nh-unity/** +NewHorizons/Assets/Assets +NewHorizons/Assets/Assets.manifest + docs/.env diff --git a/NewHorizons/Assets/xen.newhorizons b/NewHorizons/Assets/xen.newhorizons index 84e681f3..1a754842 100644 Binary files a/NewHorizons/Assets/xen.newhorizons and b/NewHorizons/Assets/xen.newhorizons differ diff --git a/NewHorizons/Assets/xen.newhorizons.manifest b/NewHorizons/Assets/xen.newhorizons.manifest new file mode 100644 index 00000000..d45ddec5 --- /dev/null +++ b/NewHorizons/Assets/xen.newhorizons.manifest @@ -0,0 +1,23 @@ +ManifestFileVersion: 0 +CRC: 1014555239 +Hashes: + AssetFileHash: + serializedVersion: 2 + Hash: 45fa3430ee7bea1e8384e57927fc0f76 + TypeTreeHash: + serializedVersion: 2 + Hash: 55d48f4ad9c3b13330b9eb5ee5686477 +HashAppended: 0 +ClassTypes: +- Class: 48 + Script: {instanceID: 0} +SerializeReferenceClassIdentifiers: [] +Assets: +- Assets/Shaders/SphereTextureWrapper.shader +- Assets/Shaders/Ring.shader +- Assets/Shaders/SphereTextureWrapperNormal.shader +- Assets/Shaders/UnlitRing1Pixel.shader +- Assets/Shaders/UnlitTransparent.shader +- Assets/Shaders/StandardCullOFF.shader +- Assets/Shaders/Ring1Pixel.shader +Dependencies: [] diff --git a/NewHorizons/Builder/Atmosphere/AirBuilder.cs b/NewHorizons/Builder/Atmosphere/AirBuilder.cs index 7b5ab84d..209a2b26 100644 --- a/NewHorizons/Builder/Atmosphere/AirBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/AirBuilder.cs @@ -23,28 +23,31 @@ namespace NewHorizons.Builder.Atmosphere sfv._allowShipAutoroll = true; sfv._disableOnStart = false; - // Try to parent it to the same as other rulesets to match vanilla but if its null put it on the root object - var ruleSetGO = planetGO.GetComponentInChildren()?.gameObject; - if (ruleSetGO == null) ruleSetGO = planetGO; - - var shockLayerRuleset = ruleSetGO.AddComponent(); - shockLayerRuleset._type = ShockLayerRuleset.ShockType.Atmospheric; - shockLayerRuleset._radialCenter = airGO.transform; - shockLayerRuleset._minShockSpeed = config.Atmosphere.minShockSpeed; - shockLayerRuleset._maxShockSpeed = config.Atmosphere.maxShockSpeed; - - if (config.Atmosphere.clouds != null) + if (config.Atmosphere.hasShockLayer) { - shockLayerRuleset._innerRadius = config.Atmosphere.clouds.innerCloudRadius; - shockLayerRuleset._outerRadius = config.Atmosphere.clouds.outerCloudRadius; - } - else - { - var bottom = config.Base.surfaceSize; - var top = config.Atmosphere.size; + // Try to parent it to the same as other rulesets to match vanilla but if its null put it on the root object + var ruleSetGO = planetGO.GetComponentInChildren()?.gameObject; + if (ruleSetGO == null) ruleSetGO = planetGO; - shockLayerRuleset._innerRadius = (bottom + top) / 2f; - shockLayerRuleset._outerRadius = top; + var shockLayerRuleset = ruleSetGO.AddComponent(); + shockLayerRuleset._type = ShockLayerRuleset.ShockType.Atmospheric; + shockLayerRuleset._radialCenter = airGO.transform; + shockLayerRuleset._minShockSpeed = config.Atmosphere.minShockSpeed; + shockLayerRuleset._maxShockSpeed = config.Atmosphere.maxShockSpeed; + + if (config.Atmosphere.clouds != null) + { + shockLayerRuleset._innerRadius = config.Atmosphere.clouds.innerCloudRadius; + shockLayerRuleset._outerRadius = config.Atmosphere.clouds.outerCloudRadius; + } + else + { + var bottom = config.Base.surfaceSize; + var top = config.Atmosphere.size; + + shockLayerRuleset._innerRadius = (bottom + top) / 2f; + shockLayerRuleset._outerRadius = top; + } } if (config.Atmosphere.hasOxygen) diff --git a/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs b/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs index 65039e3a..6732b18f 100644 --- a/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/CloudsBuilder.cs @@ -9,7 +9,6 @@ namespace NewHorizons.Builder.Atmosphere { public static class CloudsBuilder { - private static Shader _sphereShader = null; private static Material[] _gdCloudMaterials; private static Material[] _qmCloudMaterials; private static GameObject _lightningPrefab; @@ -91,8 +90,6 @@ namespace NewHorizons.Builder.Atmosphere // Fix the rotations once the rest is done cloudsMainGO.transform.rotation = planetGO.transform.TransformRotation(Quaternion.Euler(0, 0, 0)); - // For the base shader it has to be rotated idk - if (atmo.clouds.cloudsPrefab == CloudPrefabType.Basic) cloudsMainGO.transform.rotation = planetGO.transform.TransformRotation(Quaternion.Euler(90, 0, 0)); // Lightning if (atmo.clouds.hasLightning) @@ -170,7 +167,6 @@ namespace NewHorizons.Builder.Atmosphere MeshRenderer topMR = cloudsTopGO.AddComponent(); - if (_sphereShader == null) _sphereShader = Main.NHAssetBundle.LoadAsset("Assets/Shaders/SphereTextureWrapper.shader"); if (_gdCloudMaterials == null) _gdCloudMaterials = SearchUtilities.Find("CloudsTopLayer_GD").GetComponent().sharedMaterials; if (_qmCloudMaterials == null) _qmCloudMaterials = SearchUtilities.Find("CloudsTopLayer_QM").GetComponent().sharedMaterials; Material[] prefabMaterials = atmo.clouds.cloudsPrefab == CloudPrefabType.GiantsDeep ? _gdCloudMaterials : _qmCloudMaterials; @@ -178,10 +174,10 @@ namespace NewHorizons.Builder.Atmosphere if (atmo.clouds.cloudsPrefab == CloudPrefabType.Basic) { - var material = new Material(_sphereShader); + var material = new Material(Shader.Find("Standard")); if (atmo.clouds.unlit) material.renderQueue = 3000; material.name = atmo.clouds.unlit ? "BasicCloud" : "BasicShadowCloud"; - + material.SetFloat(279, 0f); // smoothness tempArray[0] = material; } else @@ -211,8 +207,7 @@ namespace NewHorizons.Builder.Atmosphere if (atmo.clouds.rotationSpeed != 0f) { var topRT = cloudsTopGO.AddComponent(); - // Idk why but the axis is weird - topRT._localAxis = atmo.clouds.cloudsPrefab == CloudPrefabType.Basic ? Vector3.forward : Vector3.up; + topRT._localAxis = Vector3.up; topRT._degreesPerSecond = atmo.clouds.rotationSpeed; topRT._randomizeRotationRate = false; } diff --git a/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs b/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs index fb405705..c177a9ed 100644 --- a/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs @@ -20,6 +20,16 @@ namespace NewHorizons.Builder.Atmosphere SCG._dynamicCullingBounds = false; SCG._waitForStreaming = false; + var minHeight = surfaceSize; + var maxHeight = config.Atmosphere.size; + if (config.HeightMap?.minHeight != null) + { + if (config.Water?.size >= config.HeightMap.minHeight) minHeight = config.Water.size; // use sea level if its higher + else minHeight = config.HeightMap.minHeight; + } + else if (config.Water?.size != null) minHeight = config.Water.size; + else if (config.Lava?.size != null) minHeight = config.Lava.size; + if (config.Atmosphere.hasRain) { var rainGO = GameObject.Instantiate(SearchUtilities.Find("GiantsDeep_Body/Sector_GD/Sector_GDInterior/Effects_GDInterior/Effects_GD_Rain"), effectsGO.transform); @@ -29,9 +39,9 @@ namespace NewHorizons.Builder.Atmosphere var pvc = rainGO.GetComponent(); pvc._densityByHeight = new AnimationCurve(new Keyframe[] { - new Keyframe(surfaceSize - 0.5f, 0), - new Keyframe(surfaceSize, 10f), - new Keyframe(config.Atmosphere.size, 0f) + new Keyframe(minHeight - 0.5f, 0), + new Keyframe(minHeight, 10f), + new Keyframe(maxHeight, 0f) }); rainGO.GetComponent()._activeInSector = sector; @@ -53,9 +63,9 @@ namespace NewHorizons.Builder.Atmosphere var pvc = snowEmitter.GetComponent(); pvc._densityByHeight = new AnimationCurve(new Keyframe[] { - new Keyframe(surfaceSize - 0.5f, 0), - new Keyframe(surfaceSize, 10f), - new Keyframe(config.Atmosphere.size, 0f) + new Keyframe(minHeight - 0.5f, 0), + new Keyframe(minHeight, 10f), + new Keyframe(maxHeight, 0f) }); snowEmitter.GetComponent()._activeInSector = sector; diff --git a/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs b/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs index 65fa1fcf..18927c1f 100644 --- a/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs +++ b/NewHorizons/Builder/Body/BrambleDimensionBuilder.cs @@ -192,8 +192,12 @@ namespace NewHorizons.Builder.Body cloak.GetComponent().enabled = true; // Cull stuff - var cullController = go.AddComponent(); - cullController.SetSector(sector); + // Do next update so other nodes can be built first + Delay.FireOnNextUpdate(() => + { + var cullController = go.AddComponent(); + cullController.SetSector(sector); + }); // finalize atmo.SetActive(true); diff --git a/NewHorizons/Builder/Body/ProxyBuilder.cs b/NewHorizons/Builder/Body/ProxyBuilder.cs index 0b95236e..53681a33 100644 --- a/NewHorizons/Builder/Body/ProxyBuilder.cs +++ b/NewHorizons/Builder/Body/ProxyBuilder.cs @@ -54,7 +54,7 @@ namespace NewHorizons.Builder.Body remnantGO.transform.parent = proxy.transform; remnantGO.transform.localPosition = Vector3.zero; - SharedMake(planetGO, remnantGO, proxyController, remnant); + SharedMake(planetGO, remnantGO, null, remnant); proxyController.stellarRemnantGO = remnantGO; } @@ -125,9 +125,11 @@ namespace NewHorizons.Builder.Body if (realSize < body.Config.Ring.outerRadius) realSize = body.Config.Ring.outerRadius; } + Renderer starAtmosphere = null; + Renderer starFog = null; if (body.Config.Star != null) { - StarBuilder.MakeStarProxy(planetGO, proxy, body.Config.Star, body.Mod, body.Config.isStellarRemnant); + (_, starAtmosphere, starFog) = StarBuilder.MakeStarProxy(planetGO, proxy, body.Config.Star, body.Mod, body.Config.isStellarRemnant); if (realSize < body.Config.Star.size) realSize = body.Config.Star.size; } @@ -217,9 +219,17 @@ namespace NewHorizons.Builder.Body if (proxyController != null) { - proxyController._atmosphere = atmosphere; - proxyController._fog = fog; - proxyController._fogCurveMaxVal = fogCurveMaxVal; + proxyController._atmosphere = atmosphere ?? starAtmosphere; + if (fog != null) + { + proxyController._fog = fog; + proxyController._fogCurveMaxVal = fogCurveMaxVal; + } + else if (starFog != null) + { + proxyController._fog = starFog; + proxyController._fogCurveMaxVal = 0.05f; + } proxyController.topClouds = topClouds; proxyController.lightningGenerator = lightningGenerator; proxyController.supernovaPlanetEffectController = supernovaPlanetEffect; diff --git a/NewHorizons/Builder/Body/StarBuilder.cs b/NewHorizons/Builder/Body/StarBuilder.cs index e976f800..7e32c3d3 100644 --- a/NewHorizons/Builder/Body/StarBuilder.cs +++ b/NewHorizons/Builder/Body/StarBuilder.cs @@ -46,23 +46,29 @@ namespace NewHorizons.Builder.Body sunAtmosphere.transform.position = planetGO.transform.position; sunAtmosphere.transform.localScale = Vector3.one * OuterRadiusRatio; sunAtmosphere.name = "Atmosphere_Star"; + + var atmospheres = sunAtmosphere.transform.Find("AtmoSphere"); + atmospheres.transform.localScale = Vector3.one; + var lods = atmospheres.GetComponentsInChildren(); + foreach (var lod in lods) + { + lod.material.SetFloat(InnerRadius, starModule.size); + lod.material.SetFloat(OuterRadius, starModule.size * OuterRadiusRatio); + } + var fog = sunAtmosphere.transform.Find("FogSphere").GetComponent(); + fog.transform.localScale = Vector3.one; + fog.fogRadius = starModule.size * OuterRadiusRatio; + fog.lodFadeDistance = fog.fogRadius * (StarBuilder.OuterRadiusRatio - 1f); + + fog.fogImpostor.material.SetFloat(Radius, starModule.size * OuterRadiusRatio); if (starModule.tint != null) { fog.fogTint = starModule.tint.ToColor(); fog.fogImpostor.material.SetColor(Tint, starModule.tint.ToColor()); - sunAtmosphere.transform.Find("AtmoSphere").transform.localScale = Vector3.one; - foreach (var lod in sunAtmosphere.transform.Find("AtmoSphere").GetComponentsInChildren()) - { + foreach (var lod in lods) lod.material.SetColor(SkyColor, starModule.tint.ToColor()); - lod.material.SetFloat(InnerRadius, starModule.size); - lod.material.SetFloat(OuterRadius, starModule.size * OuterRadiusRatio); - } } - fog.transform.localScale = Vector3.one; - fog.fogRadius = starModule.size * OuterRadiusRatio; - fog.lodFadeDistance = fog.fogRadius * (StarBuilder.OuterRadiusRatio - 1f); - fog.fogImpostor.material.SetFloat(Radius, starModule.size * OuterRadiusRatio); } var ambientLightGO = Object.Instantiate(SearchUtilities.Find("Sun_Body/AmbientLight_SUN"), starGO.transform); @@ -179,10 +185,37 @@ namespace NewHorizons.Builder.Body return (starGO, starController, starEvolutionController); } - public static GameObject MakeStarProxy(GameObject planet, GameObject proxyGO, StarModule starModule, IModBehaviour mod, bool isStellarRemnant) + public static (GameObject, Renderer, Renderer) MakeStarProxy(GameObject planet, GameObject proxyGO, StarModule starModule, IModBehaviour mod, bool isStellarRemnant) { var (starGO, controller, supernova) = SharedStarGeneration(proxyGO, null, mod, starModule, isStellarRemnant); + Renderer atmosphere = null; + Renderer fog = null; + if (starModule.hasAtmosphere) + { + GameObject sunAtmosphere = Object.Instantiate(SearchUtilities.Find("SunProxy/Sun_Proxy_Body/Atmosphere_SUN", false) ?? SearchUtilities.Find("SunProxy(Clone)/Sun_Proxy_Body/Atmosphere_SUN"), starGO.transform); + sunAtmosphere.transform.position = proxyGO.transform.position; + sunAtmosphere.transform.localScale = Vector3.one * OuterRadiusRatio; + sunAtmosphere.name = "Atmosphere_Star"; + + atmosphere = sunAtmosphere.transform.Find("Atmosphere_LOD2").GetComponent(); + atmosphere.transform.localScale = Vector3.one; + atmosphere.material.SetFloat(InnerRadius, starModule.size); + atmosphere.material.SetFloat(OuterRadius, starModule.size * OuterRadiusRatio); + + fog = sunAtmosphere.transform.Find("FogSphere").GetComponent(); + fog.transform.localScale = Vector3.one; + fog.material.SetFloat(Radius, starModule.size * OuterRadiusRatio); + + if (starModule.tint != null) + { + fog.material.SetColor(Tint, starModule.tint.ToColor()); + atmosphere.material.SetColor(SkyColor, starModule.tint.ToColor()); + } + + controller.atmosphere = sunAtmosphere; + } + controller.isProxy = true; // Planet can have multiple stars on them, so find the one that is also a remnant / not a remnant @@ -198,7 +231,7 @@ namespace NewHorizons.Builder.Body supernova.mainStellerDeathController = mainController.supernova; } - return starGO; + return (starGO, atmosphere, fog); } private static (GameObject, StarEvolutionController, StellarDeathController) SharedStarGeneration(GameObject planetGO, Sector sector, IModBehaviour mod, StarModule starModule, bool isStellarRemnant) diff --git a/NewHorizons/Builder/Body/StellarRemnantBuilder.cs b/NewHorizons/Builder/Body/StellarRemnantBuilder.cs index 31ea505a..6e06c5dc 100644 --- a/NewHorizons/Builder/Body/StellarRemnantBuilder.cs +++ b/NewHorizons/Builder/Body/StellarRemnantBuilder.cs @@ -88,7 +88,7 @@ namespace NewHorizons.Builder.Body lightRadius = 10000, solarLuminosity = 0.5f }; - if (proxy != null) return StarBuilder.MakeStarProxy(planetGO, proxy, whiteDwarfModule, mod, true); + if (proxy != null) return StarBuilder.MakeStarProxy(planetGO, proxy, whiteDwarfModule, mod, true).Item1; else return StarBuilder.Make(planetGO, sector, whiteDwarfModule, mod, true).Item1; } @@ -107,7 +107,7 @@ namespace NewHorizons.Builder.Body // Instead of showing the typical star surface we use a tinted singularity GameObject neutronStar; - if (proxy != null) neutronStar = StarBuilder.MakeStarProxy(planetGO, proxy, neutronStarModule, mod, true); + if (proxy != null) neutronStar = StarBuilder.MakeStarProxy(planetGO, proxy, neutronStarModule, mod, true).Item1; else (neutronStar, _, _) = StarBuilder.Make(planetGO, sector, neutronStarModule, mod, true); neutronStar.FindChild("Surface").SetActive(false); diff --git a/NewHorizons/Builder/Props/ProjectionBuilder.cs b/NewHorizons/Builder/Props/ProjectionBuilder.cs index 19e45099..f6cb399d 100644 --- a/NewHorizons/Builder/Props/ProjectionBuilder.cs +++ b/NewHorizons/Builder/Props/ProjectionBuilder.cs @@ -221,7 +221,7 @@ namespace NewHorizons.Builder.Props public static GameObject MakeMindSlidesTarget(GameObject planetGO, Sector sector, PropModule.ProjectionInfo info, IModBehaviour mod) { // spawn a trigger for the vision torch - var path = "DreamWorld_Body/Sector_DreamWorld/Sector_Underground/Sector_PrisonCell/Ghosts_PrisonCell/GhostNodeMap_PrisonCell_Lower/Prefab_IP_GhostBird_Prisoner/Ghostbird_IP_ANIM/Ghostbird_Skin_01:Ghostbird_Rig_V01:Base/Ghostbird_Skin_01:Ghostbird_Rig_V01:Root/Ghostbird_Skin_01:Ghostbird_Rig_V01:Spine01/Ghostbird_Skin_01:Ghostbird_Rig_V01:Spine02/Ghostbird_Skin_01:Ghostbird_Rig_V01:Spine03/Ghostbird_Skin_01:Ghostbird_Rig_V01:Spine04/Ghostbird_Skin_01:Ghostbird_Rig_V01:Neck01/Ghostbird_Skin_01:Ghostbird_Rig_V01:Neck02/Ghostbird_Skin_01:Ghostbird_Rig_V01:Head/PrisonerHeadDetector"; + var path = "DreamWorld_Body/Sector_DreamWorld/Sector_Underground/Sector_PrisonCell/Ghosts_PrisonCell/GhostDirector_Prisoner/Prefab_IP_GhostBird_Prisoner/Ghostbird_IP_ANIM/Ghostbird_Skin_01:Ghostbird_Rig_V01:Base/Ghostbird_Skin_01:Ghostbird_Rig_V01:Root/Ghostbird_Skin_01:Ghostbird_Rig_V01:Spine01/Ghostbird_Skin_01:Ghostbird_Rig_V01:Spine02/Ghostbird_Skin_01:Ghostbird_Rig_V01:Spine03/Ghostbird_Skin_01:Ghostbird_Rig_V01:Spine04/Ghostbird_Skin_01:Ghostbird_Rig_V01:Neck01/Ghostbird_Skin_01:Ghostbird_Rig_V01:Neck02/Ghostbird_Skin_01:Ghostbird_Rig_V01:Head/PrisonerHeadDetector"; var prefab = SearchUtilities.Find(path); var detailInfo = new PropModule.DetailInfo() { diff --git a/NewHorizons/Builder/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs index 8f442041..bda9684d 100644 --- a/NewHorizons/Builder/Props/PropBuildManager.cs +++ b/NewHorizons/Builder/Props/PropBuildManager.cs @@ -109,20 +109,6 @@ namespace NewHorizons.Builder.Props } } } - if (config.Props.reveal != null) - { - foreach (var revealInfo in config.Props.reveal) - { - try - { - RevealBuilder.Make(go, sector, revealInfo, mod); - } - catch (Exception ex) - { - Logger.LogError($"Couldn't make reveal location [{revealInfo.reveals}] for [{go.name}]:\n{ex}"); - } - } - } if (config.Props.entryLocation != null) { foreach (var entryLocationInfo in config.Props.entryLocation) @@ -207,13 +193,6 @@ namespace NewHorizons.Builder.Props } } } - if (config.Props.audioVolumes != null) - { - foreach (var audioVolume in config.Props.audioVolumes) - { - AudioVolumeBuilder.Make(go, sector, audioVolume, mod); - } - } if (config.Props.signals != null) { foreach (var signal in config.Props.signals) diff --git a/NewHorizons/Builder/ShipLog/EntryLocationBuilder.cs b/NewHorizons/Builder/ShipLog/EntryLocationBuilder.cs index 4efd3ca1..b35ec450 100644 --- a/NewHorizons/Builder/ShipLog/EntryLocationBuilder.cs +++ b/NewHorizons/Builder/ShipLog/EntryLocationBuilder.cs @@ -1,4 +1,4 @@ -using NewHorizons.External.Modules; +using NewHorizons.External.Modules; using OWML.Common; using System.Collections.Generic; using UnityEngine; @@ -15,6 +15,7 @@ namespace NewHorizons.Builder.ShipLog entryLocationGameObject.transform.position = go.transform.TransformPoint(info.position ?? Vector3.zero); ShipLogEntryLocation newLocation = entryLocationGameObject.AddComponent(); newLocation._entryID = info.id; + newLocation._outerFogWarpVolume = go.GetComponentInChildren(); newLocation._isWithinCloakField = info.cloaked; _locationsToInitialize.Add(newLocation); entryLocationGameObject.SetActive(true); diff --git a/NewHorizons/Builder/ShipLog/RevealBuilder.cs b/NewHorizons/Builder/ShipLog/RevealBuilder.cs index 43133771..95f118b6 100644 --- a/NewHorizons/Builder/ShipLog/RevealBuilder.cs +++ b/NewHorizons/Builder/ShipLog/RevealBuilder.cs @@ -7,18 +7,18 @@ namespace NewHorizons.Builder.ShipLog { public static class RevealBuilder { - public static void Make(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) + public static void Make(GameObject go, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod) { var newRevealGO = MakeGameObject(go, sector, info, mod); switch (info.revealOn) { - case PropModule.RevealInfo.RevealVolumeType.Enter: + case VolumesModule.RevealVolumeInfo.RevealVolumeType.Enter: MakeTrigger(newRevealGO, sector, info, mod); break; - case PropModule.RevealInfo.RevealVolumeType.Observe: + case VolumesModule.RevealVolumeInfo.RevealVolumeType.Observe: MakeObservable(newRevealGO, sector, info, mod); break; - case PropModule.RevealInfo.RevealVolumeType.Snapshot: + case VolumesModule.RevealVolumeInfo.RevealVolumeType.Snapshot: MakeSnapshot(newRevealGO, sector, info, mod); break; default: @@ -28,7 +28,7 @@ namespace NewHorizons.Builder.ShipLog newRevealGO.SetActive(true); } - private static SphereShape MakeShape(GameObject go, PropModule.RevealInfo info, Shape.CollisionMode collisionMode) + private static SphereShape MakeShape(GameObject go, VolumesModule.RevealVolumeInfo info, Shape.CollisionMode collisionMode) { SphereShape newShape = go.AddComponent(); newShape.radius = info.radius; @@ -36,7 +36,7 @@ namespace NewHorizons.Builder.ShipLog return newShape; } - private static GameObject MakeGameObject(GameObject planetGO, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) + private static GameObject MakeGameObject(GameObject planetGO, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod) { GameObject revealTriggerVolume = new GameObject("Reveal Volume (" + info.revealOn + ")"); revealTriggerVolume.SetActive(false); @@ -45,7 +45,7 @@ namespace NewHorizons.Builder.ShipLog return revealTriggerVolume; } - private static void MakeTrigger(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) + private static void MakeTrigger(GameObject go, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod) { var shape = MakeShape(go, info, Shape.CollisionMode.Volume); @@ -65,7 +65,7 @@ namespace NewHorizons.Builder.ShipLog } } - private static void MakeObservable(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) + private static void MakeObservable(GameObject go, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod) { go.layer = LayerMask.NameToLayer("Interactible"); @@ -96,7 +96,7 @@ namespace NewHorizons.Builder.ShipLog } } - private static void MakeSnapshot(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) + private static void MakeSnapshot(GameObject go, Sector sector, VolumesModule.RevealVolumeInfo info, IModBehaviour mod) { var shape = MakeShape(go, info, Shape.CollisionMode.Manual); diff --git a/NewHorizons/Builder/Props/AudioVolumeBuilder.cs b/NewHorizons/Builder/Volumes/AudioVolumeBuilder.cs similarity index 91% rename from NewHorizons/Builder/Props/AudioVolumeBuilder.cs rename to NewHorizons/Builder/Volumes/AudioVolumeBuilder.cs index 8e45efc8..b487670a 100644 --- a/NewHorizons/Builder/Props/AudioVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/AudioVolumeBuilder.cs @@ -9,11 +9,11 @@ using System.Threading.Tasks; using UnityEngine; using Logger = NewHorizons.Utility.Logger; -namespace NewHorizons.Builder.Props +namespace NewHorizons.Builder.Volumes { public static class AudioVolumeBuilder { - public static AudioVolume Make(GameObject planetGO, Sector sector, PropModule.AudioVolumeInfo info, IModBehaviour mod) + public static AudioVolume Make(GameObject planetGO, Sector sector, VolumesModule.AudioVolumeInfo info, IModBehaviour mod) { var go = new GameObject("AudioVolume"); go.SetActive(false); @@ -26,7 +26,7 @@ namespace NewHorizons.Builder.Props var owAudioSource = go.AddComponent(); owAudioSource._audioSource = audioSource; - owAudioSource.loop = true; + owAudioSource.loop = info.loop; owAudioSource.SetTrack((OWAudioMixer.TrackName)Enum.Parse(typeof(OWAudioMixer.TrackName), Enum.GetName(typeof(AudioMixerTrackName), info.track))); AudioUtilities.SetAudioClip(owAudioSource, info.audio, mod); diff --git a/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs b/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs new file mode 100644 index 00000000..67c6f22c --- /dev/null +++ b/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs @@ -0,0 +1,40 @@ +using NewHorizons.External.Modules; +using OWML.Common; +using OWML.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace NewHorizons.Builder.Volumes +{ + public static class HazardVolumeBuilder + { + public static HazardVolume Make(GameObject planetGO, Sector sector, OWRigidbody owrb, VolumesModule.HazardVolumeInfo info, IModBehaviour mod) + { + var go = new GameObject("HazardVolume"); + go.SetActive(false); + + go.transform.parent = sector?.transform ?? planetGO.transform; + go.transform.position = planetGO.transform.TransformPoint(info.position != null ? (Vector3)info.position : Vector3.zero); + go.layer = LayerMask.NameToLayer("BasicEffectVolume"); + + var shape = go.AddComponent(); + shape.radius = info.radius; + + var owTriggerVolume = go.AddComponent(); + owTriggerVolume._shape = shape; + + var hazardVolume = go.AddComponent(); + hazardVolume._attachedBody = owrb; + hazardVolume._type = EnumUtils.Parse(info.type.ToString(), HazardVolume.HazardType.GENERAL); + hazardVolume._damagePerSecond = info.damagePerSecond; + hazardVolume._firstContactDamageType = EnumUtils.Parse(info.firstContactDamageType.ToString(), InstantDamageType.Impact); + hazardVolume._firstContactDamage = info.firstContactDamage; + + go.SetActive(true); + + return hazardVolume; + } + } +} diff --git a/NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs b/NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs new file mode 100644 index 00000000..c91e4161 --- /dev/null +++ b/NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs @@ -0,0 +1,43 @@ +using NewHorizons.Components; +using NewHorizons.External.Modules; +using NewHorizons.Utility; +using OWML.Common; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Logger = NewHorizons.Utility.Logger; +using NHNotificationVolume = NewHorizons.Components.NotificationVolume; + +namespace NewHorizons.Builder.Volumes +{ + public static class NotificationVolumeBuilder + { + public static NHNotificationVolume Make(GameObject planetGO, Sector sector, VolumesModule.NotificationVolumeInfo info, IModBehaviour mod) + { + var go = new GameObject("NotificationVolume"); + go.SetActive(false); + + go.transform.parent = sector?.transform ?? planetGO.transform; + go.transform.position = planetGO.transform.TransformPoint(info.position != null ? (Vector3)info.position : Vector3.zero); + go.layer = LayerMask.NameToLayer("BasicEffectVolume"); + + var shape = go.AddComponent(); + shape.radius = info.radius; + + var owTriggerVolume = go.AddComponent(); + owTriggerVolume._shape = shape; + + var notificationVolume = go.AddComponent(); + notificationVolume.SetTarget(info.target); + if (info.entryNotification != null) notificationVolume.SetEntryNotification(info.entryNotification.displayMessage, info.entryNotification.duration); + if (info.exitNotification != null) notificationVolume.SetExitNotification(info.exitNotification.displayMessage, info.exitNotification.duration); + + go.SetActive(true); + + return notificationVolume; + } + } +} diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs new file mode 100644 index 00000000..f7a664b9 --- /dev/null +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -0,0 +1,54 @@ +using NewHorizons.Builder.Body; +using NewHorizons.Builder.ShipLog; +using NewHorizons.Builder.Volumes; +using NewHorizons.External.Configs; +using OWML.Common; +using System; +using System.Collections.Generic; +using UnityEngine; +using Logger = NewHorizons.Utility.Logger; + +namespace NewHorizons.Builder.Volumes +{ + public static class VolumesBuildManager + { + public static void Make(GameObject go, Sector sector, OWRigidbody planetBody, PlanetConfig config, IModBehaviour mod) + { + if (config.Volumes.revealVolumes != null) + { + foreach (var revealInfo in config.Volumes.revealVolumes) + { + try + { + RevealBuilder.Make(go, sector, revealInfo, mod); + } + catch (Exception ex) + { + Logger.LogError($"Couldn't make reveal location [{revealInfo.reveals}] for [{go.name}]:\n{ex}"); + } + } + } + if (config.Volumes.audioVolumes != null) + { + foreach (var audioVolume in config.Volumes.audioVolumes) + { + AudioVolumeBuilder.Make(go, sector, audioVolume, mod); + } + } + if (config.Volumes.notificationVolumes != null) + { + foreach (var notificationVolume in config.Volumes.notificationVolumes) + { + NotificationVolumeBuilder.Make(go, sector, notificationVolume, mod); + } + } + if (config.Volumes.hazardVolumes != null) + { + foreach (var hazardVolume in config.Volumes.hazardVolumes) + { + HazardVolumeBuilder.Make(go, sector, planetBody, hazardVolume, mod); + } + } + } + } +} diff --git a/NewHorizons/Components/NotificationVolume.cs b/NewHorizons/Components/NotificationVolume.cs new file mode 100644 index 00000000..2dc81ed1 --- /dev/null +++ b/NewHorizons/Components/NotificationVolume.cs @@ -0,0 +1,129 @@ +using NewHorizons.Handlers; +using OWML.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace NewHorizons.Components +{ + [RequireComponent(typeof(OWTriggerVolume))] + public class NotificationVolume : MonoBehaviour + { + private NotificationTarget _target = NotificationTarget.All; + private bool _pin = false; + private OWTriggerVolume _triggerVolume; + private NotificationData _entryNotification; + private NotificationData _exitNotification; + + public void Awake() + { + _triggerVolume = this.GetRequiredComponent(); + _triggerVolume.OnEntry += OnTriggerVolumeEntry; + _triggerVolume.OnExit += OnTriggerVolumeExit; + } + + public void OnDestroy() + { + if (_triggerVolume == null) return; + _triggerVolume.OnEntry -= OnTriggerVolumeEntry; + _triggerVolume.OnExit -= OnTriggerVolumeExit; + } + + public void SetPinned(bool pin) => _pin = pin; + + public void SetTarget(External.Modules.VolumesModule.NotificationVolumeInfo.NotificationTarget target) => SetTarget(EnumUtils.Parse(target.ToString(), NotificationTarget.All)); + + public void SetTarget(NotificationTarget target) => _target = target; + + public void SetEntryNotification(string displayMessage, float duration = 5) + { + _entryNotification = new NotificationData(_target, TranslationHandler.GetTranslation(displayMessage, TranslationHandler.TextType.UI), duration); + } + + public void SetExitNotification(string displayMessage, float duration = 5) + { + _exitNotification = new NotificationData(_target, TranslationHandler.GetTranslation(displayMessage, TranslationHandler.TextType.UI), duration); + } + + public void OnTriggerVolumeEntry(GameObject hitObj) + { + if (_target == NotificationTarget.All) + { + if (hitObj.CompareTag("PlayerDetector") || hitObj.CompareTag("ShipDetector")) + { + PostEntryNotification(); + } + } + else if (_target == NotificationTarget.Player) + { + if (hitObj.CompareTag("PlayerDetector")) + { + PostEntryNotification(); + } + } + else if (_target == NotificationTarget.Ship) + { + if (hitObj.CompareTag("ShipDetector")) + { + PostEntryNotification(); + } + } + } + + public void OnTriggerVolumeExit(GameObject hitObj) + { + if (_target == NotificationTarget.All) + { + if (hitObj.CompareTag("PlayerDetector") || hitObj.CompareTag("ShipDetector")) + { + PostExitNotification(); + } + } + else if (_target == NotificationTarget.Player) + { + if (hitObj.CompareTag("PlayerDetector")) + { + PostExitNotification(); + } + } + else if (_target == NotificationTarget.Ship) + { + if (hitObj.CompareTag("ShipDetector")) + { + PostExitNotification(); + } + } + } + + public void PostEntryNotification() + { + if (_entryNotification == null) return; + NotificationManager.SharedInstance.PostNotification(_entryNotification, _pin); + } + + public void PostExitNotification() + { + if (_exitNotification == null) return; + NotificationManager.SharedInstance.PostNotification(_exitNotification, _pin); + } + + public void UnpinEntryNotification() + { + if (_entryNotification == null) return; + if (NotificationManager.SharedInstance.IsPinnedNotification(_entryNotification)) + { + NotificationManager.SharedInstance.UnpinNotification(_entryNotification); + } + } + + public void UnpinExitNotification() + { + if (_exitNotification == null) return; + if (NotificationManager.SharedInstance.IsPinnedNotification(_exitNotification)) + { + NotificationManager.SharedInstance.UnpinNotification(_exitNotification); + } + } + } +} diff --git a/NewHorizons/Components/SizeControllers/StarEvolutionController.cs b/NewHorizons/Components/SizeControllers/StarEvolutionController.cs index 8398cd97..ce904cca 100644 --- a/NewHorizons/Components/SizeControllers/StarEvolutionController.cs +++ b/NewHorizons/Components/SizeControllers/StarEvolutionController.cs @@ -98,6 +98,8 @@ namespace NewHorizons.Components.SizeControllers { var sun = GameObject.FindObjectOfType(); + if (sun == null) return; + // Need to grab all this early bc the star might only Start after the solar system was made (remnants) _defaultCollapseStartSurfaceMaterial = new Material(sun._collapseStartSurfaceMaterial); _defaultCollapseEndSurfaceMaterial = new Material(sun._collapseEndSurfaceMaterial); @@ -194,7 +196,7 @@ namespace NewHorizons.Components.SizeControllers var secondsElapsed = TimeLoop.GetSecondsElapsed(); var lifespanInSeconds = lifespan * 60; - if (secondsElapsed >= lifespanInSeconds) + if (willExplode && secondsElapsed >= lifespanInSeconds) { var timeAfter = secondsElapsed - lifespanInSeconds; if (timeAfter <= collapseTime) @@ -370,6 +372,7 @@ namespace NewHorizons.Components.SizeControllers public void StartSupernova() { + if (supernova == null) return; if (_isSupernova) return; Logger.LogVerbose($"{gameObject.transform.root.name} started supernova"); @@ -393,7 +396,7 @@ namespace NewHorizons.Components.SizeControllers Logger.LogVerbose($"{gameObject.transform.root.name} stopped supernova"); SupernovaStop.Invoke(); - supernova.Deactivate(); + if (supernova != null) supernova.Deactivate(); _isSupernova = false; if (atmosphere != null) atmosphere.SetActive(true); if (destructionVolume != null) diff --git a/NewHorizons/External/Configs/PlanetConfig.cs b/NewHorizons/External/Configs/PlanetConfig.cs index 4a353011..4ea8a886 100644 --- a/NewHorizons/External/Configs/PlanetConfig.cs +++ b/NewHorizons/External/Configs/PlanetConfig.cs @@ -169,6 +169,16 @@ namespace NewHorizons.External.Configs /// public WaterModule Water; + /// + /// Add various volumes on this body + /// + public VolumesModule Volumes; + + /// + /// Extra data that may be used by extension mods + /// + public object extras; + public PlanetConfig() { // Always have to have a base module @@ -312,6 +322,20 @@ namespace NewHorizons.External.Configs if (tornado.downwards) tornado.type = PropModule.TornadoInfo.TornadoType.Downwards; + if (Props?.audioVolumes != null) + { + if (Volumes == null) Volumes = new VolumesModule(); + if (Volumes.audioVolumes == null) Volumes.audioVolumes = new VolumesModule.AudioVolumeInfo[0]; + Volumes.audioVolumes = Volumes.audioVolumes.Concat(Props.audioVolumes).ToArray(); + } + + if (Props?.reveal != null) + { + if (Volumes == null) Volumes = new VolumesModule(); + if (Volumes.revealVolumes == null) Volumes.revealVolumes = new VolumesModule.RevealVolumeInfo[0]; + Volumes.revealVolumes = Volumes.revealVolumes.Concat(Props.reveal).ToArray(); + } + if (Base.sphereOfInfluence != 0f) Base.soiOverride = Base.sphereOfInfluence; // Moved a bunch of stuff off of shiplog module to star system module because it didnt exist when we made this diff --git a/NewHorizons/External/Configs/StarSystemConfig.cs b/NewHorizons/External/Configs/StarSystemConfig.cs index 2df0dc8c..ce311174 100644 --- a/NewHorizons/External/Configs/StarSystemConfig.cs +++ b/NewHorizons/External/Configs/StarSystemConfig.cs @@ -102,6 +102,11 @@ namespace NewHorizons.External.Configs /// public CuriosityColorInfo[] curiosities; + /// + /// Extra data that may be used by extension mods + /// + public object extras; + public class NomaiCoordinates { [MinLength(2)] diff --git a/NewHorizons/External/Modules/AtmosphereModule.cs b/NewHorizons/External/Modules/AtmosphereModule.cs index f03f3ceb..d2b835ee 100644 --- a/NewHorizons/External/Modules/AtmosphereModule.cs +++ b/NewHorizons/External/Modules/AtmosphereModule.cs @@ -100,6 +100,11 @@ namespace NewHorizons.External.Modules /// public bool useAtmosphereShader; + /// + /// Whether this atmosphere will have flames appear when your ship goes a certain speed. + /// + [DefaultValue(true)] public bool hasShockLayer = true; + /// /// Minimum speed that your ship can go in the atmosphere where flames will appear. /// diff --git a/NewHorizons/External/Modules/PropModule.cs b/NewHorizons/External/Modules/PropModule.cs index 6ed70204..932b1bc0 100644 --- a/NewHorizons/External/Modules/PropModule.cs +++ b/NewHorizons/External/Modules/PropModule.cs @@ -48,11 +48,6 @@ namespace NewHorizons.External.Modules /// public RaftInfo[] rafts; - /// - /// Add triggers that reveal parts of the ship log on this planet - /// - public RevealInfo[] reveal; - /// /// Scatter props around this planet's surface /// @@ -83,11 +78,6 @@ namespace NewHorizons.External.Modules /// public SingularityModule[] singularities; - /// - /// Add audio volumes to this planet - /// - public AudioVolumeInfo[] audioVolumes; - /// /// Add signalscope signals to this planet /// @@ -98,6 +88,10 @@ namespace NewHorizons.External.Modules /// public RemoteInfo[] remotes; + [Obsolete("reveal is deprecated. Use Volumes->revealVolumes instead.")] public VolumesModule.RevealVolumeInfo[] reveal; + + [Obsolete("audioVolumes is deprecated. Use Volumes->audioVolumes instead.")] public VolumesModule.AudioVolumeInfo[] audioVolumes; + [JsonObject] public class ScatterInfo { @@ -433,55 +427,6 @@ namespace NewHorizons.External.Modules public string xmlFile; } - [JsonObject] - public class RevealInfo - { - [JsonConverter(typeof(StringEnumConverter))] - public enum RevealVolumeType - { - [EnumMember(Value = @"enter")] Enter = 0, - - [EnumMember(Value = @"observe")] Observe = 1, - - [EnumMember(Value = @"snapshot")] Snapshot = 2 - } - - /// - /// The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only) - /// - public float maxAngle = 180f; // Observe Only - - /// - /// The max distance the user can be away from the volume to reveal the fact (`snapshot` and `observe` only) - /// - public float maxDistance = -1f; // Snapshot & Observe Only - - /// - /// The position to place this volume at - /// - public MVector3 position; - - /// - /// The radius of this reveal volume - /// - public float radius = 1f; - - /// - /// What needs to be done to the volume to unlock the facts - /// - [DefaultValue("enter")] public RevealVolumeType revealOn = RevealVolumeType.Enter; - - /// - /// A list of facts to reveal - /// - public string[] reveals; - - /// - /// An achievement to unlock. Optional. - /// - public string achievementID; - } - [JsonObject] public class EntryLocationInfo { @@ -818,30 +763,6 @@ namespace NewHorizons.External.Modules [DefaultValue(1f)] public float probability = 1f; } - [JsonObject] - public class AudioVolumeInfo - { - /// - /// The location of this audio volume. Optional (will default to 0,0,0). - /// - public MVector3 position; - - /// - /// The radius of this audio volume - /// - public float radius; - - /// - /// The audio to use. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. - /// - public string audio; - - /// - /// The audio track of this audio volume - /// - [DefaultValue("environment")] public AudioMixerTrackName track = AudioMixerTrackName.Environment; - } - [JsonObject] public class RemoteInfo { @@ -1002,25 +923,4 @@ namespace NewHorizons.External.Modules } } } - - [JsonConverter(typeof(StringEnumConverter))] - public enum AudioMixerTrackName - { - [EnumMember(Value = @"undefined")] Undefined = 0, - [EnumMember(Value = @"menu")] Menu = 1, - [EnumMember(Value = @"music")] Music = 2, - [EnumMember(Value = @"environment")] Environment = 4, - [EnumMember(Value = @"environmentUnfiltered")] Environment_Unfiltered = 5, - [EnumMember(Value = @"endTimesSfx")] EndTimes_SFX = 8, - [EnumMember(Value = @"signal")] Signal = 16, - [EnumMember(Value = @"death")] Death = 32, - [EnumMember(Value = @"player")] Player = 64, - [EnumMember(Value = @"playerExternal")] Player_External = 65, - [EnumMember(Value = @"ship")] Ship = 128, - [EnumMember(Value = @"map")] Map = 256, - [EnumMember(Value = @"endTimesMusic")] EndTimes_Music = 512, - [EnumMember(Value = @"muffleWhileRafting")] MuffleWhileRafting = 1024, - [EnumMember(Value = @"muffleIndoors")] MuffleIndoors = 2048, - [EnumMember(Value = @"slideReelMusic")] SlideReelMusic = 4096, - } } \ No newline at end of file diff --git a/NewHorizons/External/Modules/VolumesModule.cs b/NewHorizons/External/Modules/VolumesModule.cs new file mode 100644 index 00000000..43059fbf --- /dev/null +++ b/NewHorizons/External/Modules/VolumesModule.cs @@ -0,0 +1,243 @@ +using NewHorizons.Utility; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace NewHorizons.External.Modules +{ + [JsonObject] + public class VolumesModule + { + /// + /// Add audio volumes to this planet + /// + public AudioVolumeInfo[] audioVolumes; + + /// + /// Add hazard volumes to this planet + /// + public HazardVolumeInfo[] hazardVolumes; + + /// + /// Add notification volumes to this planet + /// + public NotificationVolumeInfo[] notificationVolumes; + + /// + /// Add triggers that reveal parts of the ship log on this planet + /// + public RevealVolumeInfo[] revealVolumes; + + [JsonObject] + public class RevealVolumeInfo + { + [JsonConverter(typeof(StringEnumConverter))] + public enum RevealVolumeType + { + [EnumMember(Value = @"enter")] Enter = 0, + + [EnumMember(Value = @"observe")] Observe = 1, + + [EnumMember(Value = @"snapshot")] Snapshot = 2 + } + + /// + /// The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only) + /// + public float maxAngle = 180f; // Observe Only + + /// + /// The max distance the user can be away from the volume to reveal the fact (`snapshot` and `observe` only) + /// + public float maxDistance = -1f; // Snapshot & Observe Only + + /// + /// The position to place this volume at + /// + public MVector3 position; + + /// + /// The radius of this reveal volume + /// + public float radius = 1f; + + /// + /// What needs to be done to the volume to unlock the facts + /// + [DefaultValue("enter")] public RevealVolumeType revealOn = RevealVolumeType.Enter; + + /// + /// A list of facts to reveal + /// + public string[] reveals; + + /// + /// An achievement to unlock. Optional. + /// + public string achievementID; + } + + [JsonObject] + public class AudioVolumeInfo + { + /// + /// The location of this audio volume. Optional (will default to 0,0,0). + /// + public MVector3 position; + + /// + /// The radius of this audio volume + /// + public float radius; + + /// + /// The audio to use. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list. + /// + public string audio; + + /// + /// The audio track of this audio volume + /// + [DefaultValue("environment")] public AudioMixerTrackName track = AudioMixerTrackName.Environment; + + /// + /// Whether to loop this audio while in this audio volume or just play it once + /// + [DefaultValue(true)] public bool loop = true; + } + + [JsonObject] + public class NotificationVolumeInfo + { + /// + /// What the notification will show for. + /// + [DefaultValue("all")] public NotificationTarget target = NotificationTarget.All; + + /// + /// The location of this notification volume. Optional (will default to 0,0,0). + /// + public MVector3 position; + + /// + /// The radius of this notification volume. + /// + public float radius; + + /// + /// The notification that will play when you enter this volume. + /// + public NotificationInfo entryNotification; + + /// + /// The notification that will play when you exit this volume. + /// + public NotificationInfo exitNotification; + + + [JsonObject] + public class NotificationInfo + { + /// + /// The message that will be displayed. + /// + public string displayMessage; + + /// + /// The duration this notification will be displayed. + /// + [DefaultValue(5f)] public float duration = 5f; + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum NotificationTarget + { + [EnumMember(Value = @"all")] All = 0, + [EnumMember(Value = @"ship")] Ship = 1, + [EnumMember(Value = @"player")] Player = 2, + } + } + + [JsonObject] + public class HazardVolumeInfo + { + /// + /// The location of this hazard volume. Optional (will default to 0,0,0). + /// + public MVector3 position; + + /// + /// The radius of this hazard volume. + /// + public float radius; + + /// + /// The type of hazard for this volume. + /// + [DefaultValue("general")] public HazardType type = HazardType.GENERAL; + + /// + /// The amount of damage you will take per second while inside this volume. + /// + [DefaultValue(10f)] public float damagePerSecond = 10f; + + /// + /// The type of damage you will take when you first touch this volume. + /// + [DefaultValue("impact")] public InstantDamageType firstContactDamageType = InstantDamageType.Impact; + + /// + /// The amount of damage you will take when you first touch this volume. + /// + public float firstContactDamage; + + [JsonConverter(typeof(StringEnumConverter))] + public enum HazardType + { + [EnumMember(Value = @"none")] NONE = 0, + [EnumMember(Value = @"general")] GENERAL = 1, + [EnumMember(Value = @"darkMatter")] DARKMATTER = 2, + [EnumMember(Value = @"heat")] HEAT = 4, + [EnumMember(Value = @"fire")] FIRE = 8, + [EnumMember(Value = @"sandfall")] SANDFALL = 16, + [EnumMember(Value = @"electricity")] ELECTRICITY = 32, + [EnumMember(Value = @"rapids")] RAPIDS = 64 + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum InstantDamageType + { + [EnumMember(Value = @"impact")] Impact, + [EnumMember(Value = @"puncture")] Puncture, + [EnumMember(Value = @"electrical")] Electrical + } + } + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum AudioMixerTrackName + { + [EnumMember(Value = @"undefined")] Undefined = 0, + [EnumMember(Value = @"menu")] Menu = 1, + [EnumMember(Value = @"music")] Music = 2, + [EnumMember(Value = @"environment")] Environment = 4, + [EnumMember(Value = @"environmentUnfiltered")] Environment_Unfiltered = 5, + [EnumMember(Value = @"endTimesSfx")] EndTimes_SFX = 8, + [EnumMember(Value = @"signal")] Signal = 16, + [EnumMember(Value = @"death")] Death = 32, + [EnumMember(Value = @"player")] Player = 64, + [EnumMember(Value = @"playerExternal")] Player_External = 65, + [EnumMember(Value = @"ship")] Ship = 128, + [EnumMember(Value = @"map")] Map = 256, + [EnumMember(Value = @"endTimesMusic")] EndTimes_Music = 512, + [EnumMember(Value = @"muffleWhileRafting")] MuffleWhileRafting = 1024, + [EnumMember(Value = @"muffleIndoors")] MuffleIndoors = 2048, + [EnumMember(Value = @"slideReelMusic")] SlideReelMusic = 4096, + } +} diff --git a/NewHorizons/External/NewHorizonsData.cs b/NewHorizons/External/NewHorizonsData.cs index a6273dd7..f90cb479 100644 --- a/NewHorizons/External/NewHorizonsData.cs +++ b/NewHorizons/External/NewHorizonsData.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using NewHorizons.Utility; @@ -11,9 +11,12 @@ namespace NewHorizons.External private static string _activeProfileName; private static readonly string FileName = "save.json"; + // This is its own method so it can be patched by NH-QSB compat + public static string GetProfileName() => StandaloneProfileManager.SharedInstance?.currentProfile?.profileName; + public static void Load() { - _activeProfileName = StandaloneProfileManager.SharedInstance?.currentProfile?.profileName; + _activeProfileName = GetProfileName(); if (_activeProfileName == null) { Logger.LogError("Couldn't find active profile, are you on Gamepass?"); diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index 003850a7..0595aae3 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -3,6 +3,7 @@ using NewHorizons.Builder.Body; using NewHorizons.Builder.General; using NewHorizons.Builder.Orbital; using NewHorizons.Builder.Props; +using NewHorizons.Builder.Volumes; using NewHorizons.Components; using NewHorizons.Components.Orbital; using NewHorizons.OtherMods.OWRichPresence; @@ -251,6 +252,16 @@ namespace NewHorizons.Handlers } } } + + try + { + Main.Instance.OnPlanetLoaded?.Invoke(body.Config.name); + } + catch (Exception e) + { + Logger.LogError($"Error in event handler for OnPlanetLoaded on body {body.Config.name}: {e}"); + } + return true; } @@ -613,6 +624,11 @@ namespace NewHorizons.Handlers PropBuildManager.Make(go, sector, rb, body.Config, body.Mod); } + if (body.Config.Volumes != null) + { + VolumesBuildManager.Make(go, sector, rb, body.Config, body.Mod); + } + if (body.Config.Funnel != null) { FunnelBuilder.Make(go, go.GetComponentInChildren(), rb, body.Config.Funnel); diff --git a/NewHorizons/Handlers/TranslationHandler.cs b/NewHorizons/Handlers/TranslationHandler.cs index aa253c3e..03910e2b 100644 --- a/NewHorizons/Handlers/TranslationHandler.cs +++ b/NewHorizons/Handlers/TranslationHandler.cs @@ -66,9 +66,10 @@ namespace NewHorizons.Handlers foreach (var originalKey in config.ShipLogDictionary.Keys) { var key = originalKey.Replace("<", "<").Replace(">", ">").Replace("", ""); + var value = config.ShipLogDictionary[originalKey].Replace("<", "<").Replace(">", ">").Replace("", ""); - if (!_shipLogTranslationDictionary[language].ContainsKey(key)) _shipLogTranslationDictionary[language].Add(key, config.ShipLogDictionary[originalKey]); - else _shipLogTranslationDictionary[language][key] = config.ShipLogDictionary[originalKey]; + if (!_shipLogTranslationDictionary[language].ContainsKey(key)) _shipLogTranslationDictionary[language].Add(key, value); + else _shipLogTranslationDictionary[language][key] = value; } } @@ -78,9 +79,10 @@ namespace NewHorizons.Handlers foreach (var originalKey in config.DialogueDictionary.Keys) { var key = originalKey.Replace("<", "<").Replace(">", ">").Replace("", ""); + var value = config.DialogueDictionary[originalKey].Replace("<", "<").Replace(">", ">").Replace("", ""); - if (!_dialogueTranslationDictionary[language].ContainsKey(key)) _dialogueTranslationDictionary[language].Add(key, config.DialogueDictionary[originalKey]); - else _dialogueTranslationDictionary[language][key] = config.DialogueDictionary[originalKey]; + if (!_dialogueTranslationDictionary[language].ContainsKey(key)) _dialogueTranslationDictionary[language].Add(key, value); + else _dialogueTranslationDictionary[language][key] = value; } } @@ -90,9 +92,10 @@ namespace NewHorizons.Handlers foreach (var originalKey in config.UIDictionary.Keys) { var key = originalKey.Replace("<", "<").Replace(">", ">").Replace("", ""); + var value = config.UIDictionary[originalKey].Replace("<", "<").Replace(">", ">").Replace("", ""); - if (!_uiTranslationDictionary[language].ContainsKey(key)) _uiTranslationDictionary[language].Add(key, config.UIDictionary[originalKey]); - else _uiTranslationDictionary[language][key] = config.UIDictionary[originalKey]; + if (!_uiTranslationDictionary[language].ContainsKey(key)) _uiTranslationDictionary[language].Add(key, value); + else _uiTranslationDictionary[language][key] = value; } } } diff --git a/NewHorizons/INewHorizons.cs b/NewHorizons/INewHorizons.cs index 7c647003..8f132ee7 100644 --- a/NewHorizons/INewHorizons.cs +++ b/NewHorizons/INewHorizons.cs @@ -43,6 +43,22 @@ namespace NewHorizons /// UnityEvent GetStarSystemLoadedEvent(); + /// + /// An event invoked when NH has finished a planet for a star system. + /// Gives the name of the planet that was just loaded. + /// + UnityEvent GetBodyLoadedEvent(); + + /// + /// Uses JSONPath to query a body + /// + object QueryBody(Type outType, string bodyName, string path); + + /// + /// Uses JSONPath to query a system + /// + object QuerySystem(Type outType, string path); + /// /// Allows you to overwrite the default system. This is where the player is respawned after dying. /// diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 711e43fc..290347e3 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -69,6 +69,7 @@ namespace NewHorizons public class StarSystemEvent : UnityEvent { } public StarSystemEvent OnChangeStarSystem; public StarSystemEvent OnStarSystemLoaded; + public StarSystemEvent OnPlanetLoaded; // For warping to the eye system private GameObject _ship; @@ -127,7 +128,7 @@ namespace NewHorizons BodyDict["SolarSystem"] = new List(); BodyDict["EyeOfTheUniverse"] = new List(); // Keep this empty tho fr - SystemDict["SolarSystem"] = new NewHorizonsSystem("SolarSystem", new StarSystemConfig(), Instance) + SystemDict["SolarSystem"] = new NewHorizonsSystem("SolarSystem", new StarSystemConfig(), "", Instance) { Config = { @@ -143,7 +144,7 @@ namespace NewHorizons } } }; - SystemDict["EyeOfTheUniverse"] = new NewHorizonsSystem("EyeOfTheUniverse", new StarSystemConfig(), Instance) + SystemDict["EyeOfTheUniverse"] = new NewHorizonsSystem("EyeOfTheUniverse", new StarSystemConfig(), "", Instance) { Config = { @@ -173,6 +174,7 @@ namespace NewHorizons OnChangeStarSystem = new StarSystemEvent(); OnStarSystemLoaded = new StarSystemEvent(); + OnPlanetLoaded = new StarSystemEvent(); SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; @@ -604,7 +606,7 @@ namespace NewHorizons } else { - SystemDict[name] = new NewHorizonsSystem(name, starSystemConfig, mod); + SystemDict[name] = new NewHorizonsSystem(name, starSystemConfig, relativePath, mod); } } } @@ -705,7 +707,7 @@ namespace NewHorizons starSystemConfig.Migrate(); starSystemConfig.FixCoordinates(); - var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, mod); + var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, $"", mod); SystemDict.Add(config.starSystem, system); @@ -736,6 +738,13 @@ namespace NewHorizons #region Change star system public void ChangeCurrentStarSystem(string newStarSystem, bool warp = false, bool vessel = false) { + // If we're just on the title screen set the system for later + if (LoadManager.GetCurrentScene() == OWScene.TitleScreen) + { + _currentStarSystem = newStarSystem; + return; + } + if (IsChangingStarSystem) return; IsWarpingFromShip = warp; @@ -747,9 +756,6 @@ namespace NewHorizons IsChangingStarSystem = true; WearingSuit = PlayerState.IsWearingSuit(); - // We kill them so they don't move as much - Locator.GetDeathManager().KillPlayer(DeathType.Meditation); - OWScene sceneToLoad; if (newStarSystem == "EyeOfTheUniverse") @@ -769,12 +775,15 @@ namespace NewHorizons _currentStarSystem = newStarSystem; + // Freeze player inputs + OWInput.ChangeInputMode(InputMode.None); + LoadManager.LoadSceneAsync(sceneToLoad, !vessel, LoadManager.FadeType.ToBlack, 0.1f, true); } void OnDeath(DeathType _) { - // We reset the solar system on death (unless we just killed the player) + // We reset the solar system on death if (!IsChangingStarSystem) { // If the override is a valid system then we go there diff --git a/NewHorizons/NewHorizons.csproj b/NewHorizons/NewHorizons.csproj index 72b21de4..bb43cdbf 100644 --- a/NewHorizons/NewHorizons.csproj +++ b/NewHorizons/NewHorizons.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/NewHorizons/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs index 0ddb9f14..4924834f 100644 --- a/NewHorizons/NewHorizonsApi.cs +++ b/NewHorizons/NewHorizonsApi.cs @@ -7,12 +7,15 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using UnityEngine; using UnityEngine.Events; using Logger = NewHorizons.Utility.Logger; namespace NewHorizons { + public class NewHorizonsApi : INewHorizons { [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] @@ -64,20 +67,10 @@ namespace NewHorizons return Main.BodyDict.Values.SelectMany(x => x)?.ToList()?.FirstOrDefault(x => x.Config.name == name)?.Object; } - public string GetCurrentStarSystem() - { - return Main.Instance.CurrentStarSystem; - } - - public UnityEvent GetChangeStarSystemEvent() - { - return Main.Instance.OnChangeStarSystem; - } - - public UnityEvent GetStarSystemLoadedEvent() - { - return Main.Instance.OnStarSystemLoaded; - } + public string GetCurrentStarSystem() => Main.Instance.CurrentStarSystem; + public UnityEvent GetChangeStarSystemEvent() => Main.Instance.OnChangeStarSystem; + public UnityEvent GetStarSystemLoadedEvent() => Main.Instance.OnStarSystemLoaded; + public UnityEvent GetBodyLoadedEvent() => Main.Instance.OnPlanetLoaded; public bool SetDefaultSystem(string name) { @@ -108,6 +101,42 @@ namespace NewHorizons } } + private static object QueryJson(Type outType, string filePath, string jsonPath) + { + if (filePath == "") return null; + try + { + var jsonText = File.ReadAllText(filePath); + var jsonData = JObject.Parse(jsonText); + return jsonData.SelectToken(jsonPath)?.ToObject(outType); + } + catch (FileNotFoundException) + { + return null; + } + catch (JsonException e) + { + Logger.LogError(e.ToString()); + return null; + } + } + + public object QueryBody(Type outType, string bodyName, string jsonPath) + { + var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == bodyName); + return planet == null + ? null + : QueryJson(outType, planet.Mod.ModHelper.Manifest.ModFolderPath + planet.RelativePath, jsonPath); + } + + public object QuerySystem(Type outType, string jsonPath) + { + var system = Main.SystemDict[Main.Instance.CurrentStarSystem]; + return system == null + ? null + : QueryJson(outType, system.Mod.ModHelper.Manifest.ModFolderPath + system.RelativePath, jsonPath); + } + public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignWithNormal) { diff --git a/NewHorizons/OtherMods/OWRichPresence/RichPresenceHandler.cs b/NewHorizons/OtherMods/OWRichPresence/RichPresenceHandler.cs index a1e4da27..7b94ca6c 100644 --- a/NewHorizons/OtherMods/OWRichPresence/RichPresenceHandler.cs +++ b/NewHorizons/OtherMods/OWRichPresence/RichPresenceHandler.cs @@ -47,7 +47,7 @@ namespace NewHorizons.OtherMods.OWRichPresence var localizedName = TranslationHandler.GetTranslation(name, TranslationHandler.TextType.UI); var message = TranslationHandler.GetTranslation("RICH_PRESENCE_EXPLORING", TranslationHandler.TextType.UI).Replace("{0}", localizedName); - API.CreateTrigger(go, sector, message, name.Replace(" ", "").Replace("'", "").ToLowerInvariant()); + API.CreateTrigger(go, sector, message, name.Replace(" ", "").Replace("'", "").Replace("-", "").ToLowerInvariant()); } public static void OnStarSystemLoaded(string name) diff --git a/NewHorizons/Patches/ShapePatches.cs b/NewHorizons/Patches/ShapePatches.cs new file mode 100644 index 00000000..cf4d19d0 --- /dev/null +++ b/NewHorizons/Patches/ShapePatches.cs @@ -0,0 +1,29 @@ +using HarmonyLib; +using System.Collections.Generic; + +namespace NewHorizons.Patches +{ + [HarmonyPatch] + public class ShapePatches + { + [HarmonyPrefix] + [HarmonyPatch(typeof(ShapeManager), nameof(ShapeManager.Initialize))] + public static bool ShapeManager_Initialize() + { + ShapeManager._exists = true; + + ShapeManager._detectors = new ShapeManager.Layer(256); + for (int index = 0; index < 256; ++index) + ShapeManager._detectors[index].contacts = new List(64); + + ShapeManager._volumes = new ShapeManager.Layer[4]; + for (int index = 0; index < 4; ++index) + ShapeManager._volumes[index] = new ShapeManager.Layer(2048); + + ShapeManager._locked = false; + ShapeManager._frameFlag = false; + + return false; + } + } +} diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index bc88b9f7..7ea40e04 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -128,6 +128,17 @@ "description": "Add water to this planet", "$ref": "#/definitions/WaterModule" }, + "Volumes": { + "description": "Add various volumes on this body", + "$ref": "#/definitions/VolumesModule" + }, + "extras": { + "type": "object", + "description": "Extra data that may be used by extension mods", + "additionalProperties": { + "type": "object" + } + }, "$schema": { "type": "string", "description": "The schema to validate with" @@ -302,6 +313,11 @@ "type": "boolean", "description": "Whether we use an atmospheric shader on the planet. Doesn't affect clouds, fog, rain, snow, oxygen, etc. Purely\nvisual." }, + "hasShockLayer": { + "type": "boolean", + "description": "Whether this atmosphere will have flames appear when your ship goes a certain speed.", + "default": true + }, "minShockSpeed": { "type": "number", "description": "Minimum speed that your ship can go in the atmosphere where flames will appear.", @@ -925,13 +941,6 @@ "$ref": "#/definitions/RaftInfo" } }, - "reveal": { - "type": "array", - "description": "Add triggers that reveal parts of the ship log on this planet", - "items": { - "$ref": "#/definitions/RevealInfo" - } - }, "scatter": { "type": "array", "description": "Scatter props around this planet's surface", @@ -974,13 +983,6 @@ "$ref": "#/definitions/SingularityModule" } }, - "audioVolumes": { - "type": "array", - "description": "Add audio volumes to this planet", - "items": { - "$ref": "#/definitions/AudioVolumeInfo" - } - }, "signals": { "type": "array", "description": "Add signalscope signals to this planet", @@ -1337,61 +1339,6 @@ } } }, - "RevealInfo": { - "type": "object", - "additionalProperties": false, - "properties": { - "maxAngle": { - "type": "number", - "description": "The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only)", - "format": "float" - }, - "maxDistance": { - "type": "number", - "description": "The max distance the user can be away from the volume to reveal the fact (`snapshot` and `observe` only)", - "format": "float" - }, - "position": { - "description": "The position to place this volume at", - "$ref": "#/definitions/MVector3" - }, - "radius": { - "type": "number", - "description": "The radius of this reveal volume", - "format": "float" - }, - "revealOn": { - "description": "What needs to be done to the volume to unlock the facts", - "default": "enter", - "$ref": "#/definitions/RevealVolumeType" - }, - "reveals": { - "type": "array", - "description": "A list of facts to reveal", - "items": { - "type": "string" - } - }, - "achievementID": { - "type": "string", - "description": "An achievement to unlock. Optional." - } - } - }, - "RevealVolumeType": { - "type": "string", - "description": "", - "x-enumNames": [ - "Enter", - "Observe", - "Snapshot" - ], - "enum": [ - "enter", - "observe", - "snapshot" - ] - }, "ScatterInfo": { "type": "object", "additionalProperties": false, @@ -1801,70 +1748,6 @@ "whiteHole" ] }, - "AudioVolumeInfo": { - "type": "object", - "additionalProperties": false, - "properties": { - "position": { - "description": "The location of this audio volume. Optional (will default to 0,0,0).", - "$ref": "#/definitions/MVector3" - }, - "radius": { - "type": "number", - "description": "The radius of this audio volume", - "format": "float" - }, - "audio": { - "type": "string", - "description": "The audio to use. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list." - }, - "track": { - "description": "The audio track of this audio volume", - "default": "environment", - "$ref": "#/definitions/AudioMixerTrackName" - } - } - }, - "AudioMixerTrackName": { - "type": "string", - "description": "", - "x-enumNames": [ - "Undefined", - "Menu", - "Music", - "Environment", - "Environment_Unfiltered", - "EndTimes_SFX", - "Signal", - "Death", - "Player", - "Player_External", - "Ship", - "Map", - "EndTimes_Music", - "MuffleWhileRafting", - "MuffleIndoors", - "SlideReelMusic" - ], - "enum": [ - "undefined", - "menu", - "music", - "environment", - "environmentUnfiltered", - "endTimesSfx", - "signal", - "death", - "player", - "playerExternal", - "ship", - "map", - "endTimesMusic", - "muffleWhileRafting", - "muffleIndoors", - "slideReelMusic" - ] - }, "SignalInfo": { "type": "object", "additionalProperties": false, @@ -2503,6 +2386,296 @@ "$ref": "#/definitions/MColor" } } + }, + "VolumesModule": { + "type": "object", + "additionalProperties": false, + "properties": { + "audioVolumes": { + "type": "array", + "description": "Add audio volumes to this planet", + "items": { + "$ref": "#/definitions/AudioVolumeInfo" + } + }, + "hazardVolumes": { + "type": "array", + "description": "Add hazard volumes to this planet", + "items": { + "$ref": "#/definitions/HazardVolumeInfo" + } + }, + "notificationVolumes": { + "type": "array", + "description": "Add notification volumes to this planet", + "items": { + "$ref": "#/definitions/NotificationVolumeInfo" + } + }, + "revealVolumes": { + "type": "array", + "description": "Add triggers that reveal parts of the ship log on this planet", + "items": { + "$ref": "#/definitions/RevealVolumeInfo" + } + } + } + }, + "AudioVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "position": { + "description": "The location of this audio volume. Optional (will default to 0,0,0).", + "$ref": "#/definitions/MVector3" + }, + "radius": { + "type": "number", + "description": "The radius of this audio volume", + "format": "float" + }, + "audio": { + "type": "string", + "description": "The audio to use. Can be a path to a .wav/.ogg/.mp3 file, or taken from the AudioClip list." + }, + "track": { + "description": "The audio track of this audio volume", + "default": "environment", + "$ref": "#/definitions/AudioMixerTrackName" + }, + "loop": { + "type": "boolean", + "description": "Whether to loop this audio while in this audio volume or just play it once", + "default": true + } + } + }, + "AudioMixerTrackName": { + "type": "string", + "description": "", + "x-enumNames": [ + "Undefined", + "Menu", + "Music", + "Environment", + "Environment_Unfiltered", + "EndTimes_SFX", + "Signal", + "Death", + "Player", + "Player_External", + "Ship", + "Map", + "EndTimes_Music", + "MuffleWhileRafting", + "MuffleIndoors", + "SlideReelMusic" + ], + "enum": [ + "undefined", + "menu", + "music", + "environment", + "environmentUnfiltered", + "endTimesSfx", + "signal", + "death", + "player", + "playerExternal", + "ship", + "map", + "endTimesMusic", + "muffleWhileRafting", + "muffleIndoors", + "slideReelMusic" + ] + }, + "HazardVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "position": { + "description": "The location of this hazard volume. Optional (will default to 0,0,0).", + "$ref": "#/definitions/MVector3" + }, + "radius": { + "type": "number", + "description": "The radius of this hazard volume.", + "format": "float" + }, + "type": { + "description": "The type of hazard for this volume.", + "default": "general", + "$ref": "#/definitions/HazardType" + }, + "damagePerSecond": { + "type": "number", + "description": "The amount of damage you will take per second while inside this volume.", + "format": "float", + "default": 10.0 + }, + "firstContactDamageType": { + "description": "The type of damage you will take when you first touch this volume.", + "default": "impact", + "$ref": "#/definitions/InstantDamageType" + }, + "firstContactDamage": { + "type": "number", + "description": "The amount of damage you will take when you first touch this volume.", + "format": "float" + } + } + }, + "HazardType": { + "type": "string", + "description": "", + "x-enumNames": [ + "NONE", + "GENERAL", + "DARKMATTER", + "HEAT", + "FIRE", + "SANDFALL", + "ELECTRICITY", + "RAPIDS" + ], + "enum": [ + "none", + "general", + "darkMatter", + "heat", + "fire", + "sandfall", + "electricity", + "rapids" + ] + }, + "InstantDamageType": { + "type": "string", + "description": "", + "x-enumNames": [ + "Impact", + "Puncture", + "Electrical" + ], + "enum": [ + "impact", + "puncture", + "electrical" + ] + }, + "NotificationVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "target": { + "description": "What the notification will show for.", + "default": "all", + "$ref": "#/definitions/NotificationTarget" + }, + "position": { + "description": "The location of this notification volume. Optional (will default to 0,0,0).", + "$ref": "#/definitions/MVector3" + }, + "radius": { + "type": "number", + "description": "The radius of this notification volume.", + "format": "float" + }, + "entryNotification": { + "description": "The notification that will play when you enter this volume.", + "$ref": "#/definitions/NotificationInfo" + }, + "exitNotification": { + "description": "The notification that will play when you exit this volume.", + "$ref": "#/definitions/NotificationInfo" + } + } + }, + "NotificationTarget": { + "type": "string", + "description": "", + "x-enumNames": [ + "All", + "Ship", + "Player" + ], + "enum": [ + "all", + "ship", + "player" + ] + }, + "NotificationInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "displayMessage": { + "type": "string", + "description": "The message that will be displayed." + }, + "duration": { + "type": "number", + "description": "The duration this notification will be displayed.", + "format": "float", + "default": 5.0 + } + } + }, + "RevealVolumeInfo": { + "type": "object", + "additionalProperties": false, + "properties": { + "maxAngle": { + "type": "number", + "description": "The max view angle (in degrees) the player can see the volume with to unlock the fact (`observe` only)", + "format": "float" + }, + "maxDistance": { + "type": "number", + "description": "The max distance the user can be away from the volume to reveal the fact (`snapshot` and `observe` only)", + "format": "float" + }, + "position": { + "description": "The position to place this volume at", + "$ref": "#/definitions/MVector3" + }, + "radius": { + "type": "number", + "description": "The radius of this reveal volume", + "format": "float" + }, + "revealOn": { + "description": "What needs to be done to the volume to unlock the facts", + "default": "enter", + "$ref": "#/definitions/RevealVolumeType" + }, + "reveals": { + "type": "array", + "description": "A list of facts to reveal", + "items": { + "type": "string" + } + }, + "achievementID": { + "type": "string", + "description": "An achievement to unlock. Optional." + } + } + }, + "RevealVolumeType": { + "type": "string", + "description": "", + "x-enumNames": [ + "Enter", + "Observe", + "Snapshot" + ], + "enum": [ + "enter", + "observe", + "snapshot" + ] } }, "$docs": { diff --git a/NewHorizons/Schemas/star_system_schema.json b/NewHorizons/Schemas/star_system_schema.json index b1f5e2cc..032fb8f1 100644 --- a/NewHorizons/Schemas/star_system_schema.json +++ b/NewHorizons/Schemas/star_system_schema.json @@ -71,6 +71,13 @@ "$ref": "#/definitions/CuriosityColorInfo" } }, + "extras": { + "type": "object", + "description": "Extra data that may be used by extension mods", + "additionalProperties": { + "type": "object" + } + }, "$schema": { "type": "string", "description": "The schema to validate with" diff --git a/NewHorizons/Utility/DebugMenu/DebugMenu.cs b/NewHorizons/Utility/DebugMenu/DebugMenu.cs index 0d25ebfe..be19c867 100644 --- a/NewHorizons/Utility/DebugMenu/DebugMenu.cs +++ b/NewHorizons/Utility/DebugMenu/DebugMenu.cs @@ -69,7 +69,13 @@ namespace NewHorizons.Utility.DebugMenu PauseMenuInitHook(); - Main.Instance.OnChangeStarSystem.AddListener((string s) => SaveLoadedConfigsForRecentSystem()); + Main.Instance.OnChangeStarSystem.AddListener((string s) => { + if (saveButtonUnlocked) + { + SaveLoadedConfigsForRecentSystem(); + saveButtonUnlocked = false; + } + }); } else { @@ -227,6 +233,24 @@ namespace NewHorizons.Utility.DebugMenu var json = loadedConfigFiles[filePath].ToSerializedJson(); + try + { + var path = loadedMod.ModHelper.Manifest.ModFolderPath + backupFolderName + relativePath; + Logger.LogVerbose($"Backing up... {relativePath} to {path}"); + var oldPath = loadedMod.ModHelper.Manifest.ModFolderPath + relativePath; + var directoryName = Path.GetDirectoryName(path); + Directory.CreateDirectory(directoryName); + + if (File.Exists(oldPath)) + File.WriteAllBytes(path, File.ReadAllBytes(oldPath)); + else + File.WriteAllText(path, json); + } + catch (Exception e) + { + Logger.LogError($"Failed to save backup file {backupFolderName}{relativePath}:\n{e}"); + } + try { Logger.Log($"Saving... {relativePath} to {filePath}"); @@ -240,19 +264,6 @@ namespace NewHorizons.Utility.DebugMenu { Logger.LogError($"Failed to save file {relativePath}:\n{e}"); } - - try - { - var path = Main.Instance.ModHelper.Manifest.ModFolderPath + backupFolderName + relativePath; - var directoryName = Path.GetDirectoryName(path); - Directory.CreateDirectory(directoryName); - - File.WriteAllText(path, json); - } - catch (Exception e) - { - Logger.LogError($"Failed to save backup file {backupFolderName}{relativePath}:\n{e}"); - } } } diff --git a/NewHorizons/Utility/NewHorizonsSystem.cs b/NewHorizons/Utility/NewHorizonsSystem.cs index ae6bafa3..be9a539f 100644 --- a/NewHorizons/Utility/NewHorizonsSystem.cs +++ b/NewHorizons/Utility/NewHorizonsSystem.cs @@ -11,15 +11,17 @@ namespace NewHorizons.Utility public class NewHorizonsSystem { public string UniqueID; + public string RelativePath; public SpawnModule Spawn = null; public SpawnPoint SpawnPoint = null; public StarSystemConfig Config; public IModBehaviour Mod; - public NewHorizonsSystem(string uniqueID, StarSystemConfig config, IModBehaviour mod) + public NewHorizonsSystem(string uniqueID, StarSystemConfig config, string relativePath, IModBehaviour mod) { UniqueID = uniqueID; Config = config; + RelativePath = relativePath; Mod = mod; } } diff --git a/NewHorizons/Utility/SearchUtilities.cs b/NewHorizons/Utility/SearchUtilities.cs index 970c33d4..6f4d1be5 100644 --- a/NewHorizons/Utility/SearchUtilities.cs +++ b/NewHorizons/Utility/SearchUtilities.cs @@ -95,38 +95,41 @@ namespace NewHorizons.Utility { if (CachedGameObjects.TryGetValue(path, out var go)) return go; + // 1: normal find go = GameObject.Find(path); - if (go == null) + if (go) { - // find inactive use root + transform.find - var names = path.Split('/'); - var rootName = names[0]; - var root = SceneManager.GetActiveScene().GetRootGameObjects().FirstOrDefault(x => x.name == rootName); - if (root == null) - { - if (warn) Logger.LogWarning($"Couldn't find root object in path {path}"); - return null; - } - - var childPath = string.Join("/", names.Skip(1)); - go = root.FindChild(childPath); - if (go == null) - { - var name = names.Last(); - if (warn) Logger.LogWarning($"Couldn't find object in path {path}, will look for potential matches for name {name}"); - // find resource to include inactive objects - // also includes prefabs but hopefully thats okay - go = FindResourceOfTypeAndName(name); - if (go == null) - { - if (warn) Logger.LogWarning($"Couldn't find object with name {name}"); - return null; - } - } + CachedGameObjects.Add(path, go); + return go; } - CachedGameObjects.Add(path, go); - return go; + // 2: find inactive using root + transform.find + var names = path.Split('/'); + + var rootName = names[0]; + var root = SceneManager.GetActiveScene().GetRootGameObjects().FirstOrDefault(x => x.name == rootName); + + var childPath = string.Join("/", names.Skip(1)); + go = root ? root.FindChild(childPath) : null; + if (go) + { + CachedGameObjects.Add(path, go); + return go; + } + + var name = names.Last(); + if (warn) Logger.LogWarning($"Couldn't find object in path {path}, will look for potential matches for name {name}"); + // 3: find resource to include inactive objects + // also includes prefabs but hopefully thats okay + go = FindResourceOfTypeAndName(name); + if (go) + { + CachedGameObjects.Add(path, go); + return go; + } + + if (warn) Logger.LogWarning($"Couldn't find object with name {name}"); + return null; } public static List GetAllChildren(this GameObject parent) diff --git a/NewHorizons/manifest.json b/NewHorizons/manifest.json index 38fd21ab..5696ae38 100644 --- a/NewHorizons/manifest.json +++ b/NewHorizons/manifest.json @@ -4,7 +4,7 @@ "author": "xen, Bwc9876, clay, MegaPiggy, John, Hawkbar, Trifid, Book", "name": "New Horizons", "uniqueName": "xen.NewHorizons", - "version": "1.5.0", + "version": "1.5.1", "owmlVersion": "2.6.0", "dependencies": [ "JohnCorby.VanillaFix" ], "conflicts": [ "Raicuparta.QuantumSpaceBuddies", "PacificEngine.OW_Randomizer" ], diff --git a/SchemaExporter/SchemaExporter.cs b/SchemaExporter/SchemaExporter.cs index f0c802f5..c2e94e25 100644 --- a/SchemaExporter/SchemaExporter.cs +++ b/SchemaExporter/SchemaExporter.cs @@ -78,16 +78,29 @@ public static class SchemaExporter {"description", _description} }); - if (_title == "Celestial Body Schema") + switch (_title) { - schema.Definitions["OrbitModule"].Properties["semiMajorAxis"].Default = 5000f; + case "Celestial Body Schema": + schema.Definitions["OrbitModule"].Properties["semiMajorAxis"].Default = 5000f; + break; + case "Star System Schema": + schema.Definitions["NomaiCoordinates"].Properties["x"].UniqueItems = true; + schema.Definitions["NomaiCoordinates"].Properties["y"].UniqueItems = true; + schema.Definitions["NomaiCoordinates"].Properties["z"].UniqueItems = true; + break; } - if (_title == "Star System Schema") + if (_title is "Star System Schema" or "Celestial Body Schema") { - schema.Definitions["NomaiCoordinates"].Properties["x"].UniqueItems = true; - schema.Definitions["NomaiCoordinates"].Properties["y"].UniqueItems = true; - schema.Definitions["NomaiCoordinates"].Properties["z"].UniqueItems = true; + schema.Properties["extras"] = new JsonSchemaProperty { + Type = JsonObjectType.Object, + Description = "Extra data that may be used by extension mods", + AllowAdditionalProperties = true, + AdditionalPropertiesSchema = new JsonSchema + { + Type = JsonObjectType.Object + } + }; } return schema; diff --git a/SchemaExporter/SchemaExporter.csproj b/SchemaExporter/SchemaExporter.csproj index af626a17..2c55f3ad 100644 --- a/SchemaExporter/SchemaExporter.csproj +++ b/SchemaExporter/SchemaExporter.csproj @@ -1,4 +1,4 @@ - + Exe net48 @@ -20,7 +20,7 @@ - + diff --git a/docs/content/pages/tutorials/api.md b/docs/content/pages/tutorials/api.md index 8689943c..5c7a8996 100644 --- a/docs/content/pages/tutorials/api.md +++ b/docs/content/pages/tutorials/api.md @@ -9,21 +9,99 @@ First create the following interface in your mod: ```cs public interface INewHorizons -{ - void LoadConfigs(IModBehaviour mod); + { + [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] + void Create(Dictionary config); - GameObject GetPlanet(string name); - - string GetCurrentStarSystem(); + [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] + void Create(Dictionary config, IModBehaviour mod); - UnityEvent GetChangeStarSystemEvent(); + /// + /// Will load all configs in the regular folders (planets, systems, translations, etc) for this mod. + /// The NH addon config template is just a single call to this API method. + /// + void LoadConfigs(IModBehaviour mod); - UnityEvent GetStarSystemLoadedEvent(); - - GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignWithNormal); + /// + /// Retrieve the root GameObject of a custom planet made by creating configs. + /// Will only work if the planet has been created (see GetStarSystemLoadedEvent) + /// + GameObject GetPlanet(string name); - string[] GetInstalledAddons(); -} + /// + /// The name of the current star system loaded. + /// + string GetCurrentStarSystem(); + + /// + /// An event invoked when the player begins loading the new star system, before the scene starts to load. + /// Gives the name of the star system being switched to. + /// + UnityEvent GetChangeStarSystemEvent(); + + /// + /// An event invoked when NH has finished generating all planets for a new star system. + /// Gives the name of the star system that was just loaded. + /// + UnityEvent GetStarSystemLoadedEvent(); + + /// + /// An event invoked when NH has finished a planet for a star system. + /// Gives the name of the planet that was just loaded. + /// + UnityEvent GetBodyLoadedEvent(); + + /// + /// Uses JSONPath to query a body + /// + object QueryBody(Type outType, string bodyName, string path); + + /// + /// Uses JSONPath to query a system + /// + object QuerySystem(Type outType, string path); + + /// + /// Allows you to overwrite the default system. This is where the player is respawned after dying. + /// + bool SetDefaultSystem(string name); + + /// + /// Allows you to instantly begin a warp to a new star system. + /// Will return false if that system does not exist (cannot be warped to). + /// + bool ChangeCurrentStarSystem(string name); + + /// + /// Returns the uniqueIDs of each installed NH addon. + /// + string[] GetInstalledAddons(); + + /// + /// Allows you to spawn a copy of a prop by specifying its path. + /// This is the same as using Props->details in a config, but also returns the spawned gameObject to you. + /// + GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, + float scale, bool alignWithNormal); + + /// + /// Allows you to spawn an AudioSignal on a planet. + /// This is the same as using Props->signals in a config, but also returns the spawned AudioSignal to you. + /// This method will not set its position. You will have to do that with the returned object. + /// + AudioSignal SpawnSignal(IModBehaviour mod, GameObject root, string audio, string name, string frequency, + float sourceRadius = 1f, float detectionRadius = 20f, float identificationRadius = 10f, bool insideCloak = false, + bool onlyAudibleToScope = true, string reveals = ""); + + /// + /// Allows you to spawn character dialogue on a planet. Also returns the RemoteDialogueTrigger if remoteTriggerRadius is specified. + /// This is the same as using Props->dialogue in a config, but also returns the spawned game objects to you. + /// This method will not set the position of the dialogue or remote trigger. You will have to do that with the returned objects. + /// + (CharacterDialogueTree, RemoteDialogueTrigger) SpawnDialogue(IModBehaviour mod, GameObject root, string xmlFile, float radius = 1f, + float range = 1f, string blockAfterPersistentCondition = null, float lookAtRadius = 1f, string pathToAnimController = null, + float remoteTriggerRadius = 0f); + } ``` In your main `ModBehaviour` class you can get the NewHorizons API like so: @@ -33,7 +111,7 @@ public class MyMod : ModBehaviour { void Start() { - INewHorizons NewHorizonsAPI = ModHelper.Interaction.GetModApi("xen.NewHorizons"); + INewHorizons NewHorizonsAPI = ModHelper.Interaction.TryGetModApi("xen.NewHorizons"); } } ``` diff --git a/docs/content/pages/tutorials/extending.md b/docs/content/pages/tutorials/extending.md new file mode 100644 index 00000000..0e9b30bc --- /dev/null +++ b/docs/content/pages/tutorials/extending.md @@ -0,0 +1,74 @@ +--- +Title: Extending Configs +Description: A guide on extending config files with the New Horizons API +Sort_Priority: 5 +--- + +# Extending Configs + +This guide will explain how to use the API to add new features to New Horizons. + +## How Extending Works + +Addon developers will add a key to the `extras` object in the root of the config + +```json +{ + "name": "Wetrock", + "extras": { + "myCoolExtensionData": { + "myCoolExtensionProperty": 2 + } + } +} +``` + +Your mod will then use the API's `QueryBody` method to obtain the `myCoolExtensionData` object. + +**It's up to the addon dev to list your mod as a dependency!** + +## Extending Planets + +You can extend all planets by hooking into the `OnBodyLoaded` event of the API: + +```csharp +var api = ModHelper.Interactions.TryGetModApi("xen.NewHorizons"); +api.GetBodyLoadedEvent().AddListener((name) => { + ModHelper.Console.WriteLine($"Body: {name} Loaded!"); +}); +``` + +In order to get your extra module, first define the module as a class: + +```csharp +public class MyCoolExtensionData { + int myCoolExtensionProperty; +} +``` + +Then, use the `QueryBody` method: + +```csharp +var api = ModHelper.Interactions.TryGetModApi("xen.NewHorizons"); +api.GetBodyLoadedEvent().AddListener((name) => { + ModHelper.Console.WriteLine($"Body: {name} Loaded!"); + var potentialData = api.QueryBody(typeof(MyCoolExtensionData), "$.extras.myCoolExtensionData", name); + // Makes sure the module is valid and not null + if (potentialData is MyCoolExtensionData data) { + ModHelper.Console.WriteLine($"myCoolExtensionProperty for {name} is {data.myCoolExtensionProperty}!"); + } +}); +``` + +## Extending Systems + +Extending systems is the exact same as extending planets, except you use the `QuerySystem` method instead. + +## Accessing Other Values + +You can also use the `QueryBody` method to get values of the config outside of your extension object + +```csharp +var primaryBody = api.QueryBody(typeof(string), "Wetrock", "$.Orbit.primaryBody"); + ModHelper.Console.WriteLine($"Primary of {bodyName} is {primaryBody ?? "NULL"}!"); +```