diff --git a/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs b/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs index 3b11b857..e06c1800 100644 --- a/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/EffectsBuilder.cs @@ -1,4 +1,5 @@ using NewHorizons.External.Configs; +using NewHorizons.External.Modules; using NewHorizons.Utility; using UnityEngine; namespace NewHorizons.Builder.Atmosphere @@ -7,6 +8,21 @@ namespace NewHorizons.Builder.Atmosphere { private static GameObject _rainEmitterPrefab; private static GameObject _snowEmitterPrefab; + private static GameObject _snowLightEmitterPrefab; + private static GameObject _emberEmitterPrefab; + private static GameObject _leafEmitterPrefab; + private static GameObject _planktonEmitterPrefab; + private static GameObject _bubbleEmitterPrefab; + private static GameObject _currentEmitterPrefab; + private static GameObject _cloudEmitterPrefab; + private static GameObject _crawliesEmitterPrefab; + private static GameObject _firefliesEmitterPrefab; + private static GameObject _pollenEmitterPrefab; + private static GameObject _iceMoteEmitterPrefab; + private static GameObject _rockMoteEmitterPrefab; + private static GameObject _crystalMoteEmitterPrefab; + private static GameObject _sandMoteEmitterPrefab; + private static GameObject _fogEmitterPrefab; private static bool _isInit; @@ -18,9 +34,24 @@ namespace NewHorizons.Builder.Atmosphere if (_rainEmitterPrefab == null) _rainEmitterPrefab = SearchUtilities.Find("GiantsDeep_Body/Sector_GD/Sector_GDInterior/Effects_GDInterior/Effects_GD_Rain").InstantiateInactive().Rename("Prefab_Effects_Rain").DontDestroyOnLoad(); if (_snowEmitterPrefab == null) _snowEmitterPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Effects_BH/Effects_BH_Snowflakes").InstantiateInactive().Rename("Prefab_Effects_Snowflakes").DontDestroyOnLoad(); + if (_snowLightEmitterPrefab == null) _snowLightEmitterPrefab = SearchUtilities.Find("TimberHearth_Body/Sector_TH/Effects_TH/Effects_TH_Snowflakes").InstantiateInactive().Rename("Prefab_Effects_SnowflakesLight").DontDestroyOnLoad(); + if (_emberEmitterPrefab == null) _emberEmitterPrefab = SearchUtilities.Find("VolcanicMoon_Body/Sector_VM/Effects_VM/Effects_VM_Embers").InstantiateInactive().Rename("Prefab_Effects_Embers").DontDestroyOnLoad(); + if (_leafEmitterPrefab == null) _leafEmitterPrefab = SearchUtilities.Find("GiantsDeep_Body/Sector_GD/Sector_GDInterior/Effects_GDInterior/Effects_GD_Leaves").InstantiateInactive().Rename("Prefab_Effects_Leaves").DontDestroyOnLoad(); + if (_planktonEmitterPrefab == null) _planktonEmitterPrefab = SearchUtilities.Find("GiantsDeep_Body/Sector_GD/Sector_GDInterior/Effects_GDInterior/Effects_GD_Plankton").InstantiateInactive().Rename("Prefab_Effects_Plankton").DontDestroyOnLoad(); + if (_bubbleEmitterPrefab == null) _bubbleEmitterPrefab = SearchUtilities.Find("GiantsDeep_Body/Sector_GD/Sector_GDInterior/Effects_GDInterior/Effects_GD_Bubbles").InstantiateInactive().Rename("Prefab_Effects_Bubbles").DontDestroyOnLoad(); + if (_currentEmitterPrefab == null) _currentEmitterPrefab = SearchUtilities.Find("GiantsDeep_Body/Sector_GD/Sector_GDInterior/Effects_GDInterior/Effects_GD_RadialCurrent").InstantiateInactive().Rename("Prefab_Effects_Current").DontDestroyOnLoad(); + if (_cloudEmitterPrefab == null) _cloudEmitterPrefab = SearchUtilities.Find("GiantsDeep_Body/Sector_GD/Effects_GD/Effects_GD_Clouds").InstantiateInactive().Rename("Prefab_Effects_Clouds").DontDestroyOnLoad(); + if (_crawliesEmitterPrefab == null) _crawliesEmitterPrefab = SearchUtilities.Find("DB_EscapePodDimension_Body/Sector_EscapePodDimension/Effects_EscapePodDimension/Effects_DB_Crawlies (1)").InstantiateInactive().Rename("Prefab_Effects_Crawlies").DontDestroyOnLoad(); + if (_firefliesEmitterPrefab == null) _firefliesEmitterPrefab = SearchUtilities.Find("TimberHearth_Body/Sector_TH/Effects_TH/Effects_TH_Fireflies").InstantiateInactive().Rename("Prefab_Effects_Fireflies").DontDestroyOnLoad(); + if (_pollenEmitterPrefab == null) _pollenEmitterPrefab = SearchUtilities.Find("TimberHearth_Body/Sector_TH/Effects_TH/Effects_TH_SurfacePollen").InstantiateInactive().Rename("Prefab_Effects_Pollen").DontDestroyOnLoad(); + if (_iceMoteEmitterPrefab == null) _iceMoteEmitterPrefab = SearchUtilities.Find("DarkBramble_Body/Effects_DB/Effects_DB_IceMotes").InstantiateInactive().Rename("Prefab_Effects_IceMotes").DontDestroyOnLoad(); + if (_rockMoteEmitterPrefab == null) _rockMoteEmitterPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Effects_BH/Effects_BH_RockMotes").InstantiateInactive().Rename("Prefab_Effects_RockMotes").DontDestroyOnLoad(); + if (_crystalMoteEmitterPrefab == null) _crystalMoteEmitterPrefab = SearchUtilities.Find("BrittleHollow_Body/Sector_BH/Effects_BH/Effects_BH_CrystalMotes").InstantiateInactive().Rename("Prefab_Effects_CrystalMotes").DontDestroyOnLoad(); + if (_sandMoteEmitterPrefab == null) _sandMoteEmitterPrefab = SearchUtilities.Find("CaveTwin_Body/Sector_CaveTwin/Effects_CaveTwin/Effects_HGT_SandMotes").InstantiateInactive().Rename("Prefab_Effects_SandMotes").DontDestroyOnLoad(); + if (_fogEmitterPrefab == null) _fogEmitterPrefab = SearchUtilities.Find("DB_EscapePodDimension_Body/Sector_EscapePodDimension/Effects_EscapePodDimension/Effects_DB_Fog (1)").InstantiateInactive().Rename("Prefab_Effects_Fog").DontDestroyOnLoad(); } - public static void Make(GameObject planetGO, Sector sector, PlanetConfig config, float surfaceSize) + public static void Make(GameObject planetGO, Sector sector, PlanetConfig config) { InitPrefabs(); @@ -36,7 +67,7 @@ namespace NewHorizons.Builder.Atmosphere SCG._dynamicCullingBounds = false; SCG._waitForStreaming = false; - var minHeight = surfaceSize; + var minHeight = config.Base.surfaceSize; if (config.HeightMap?.minHeight != null) { if (config.Water?.size >= config.HeightMap.minHeight) minHeight = config.Water.size; // use sea level if its higher @@ -48,52 +79,56 @@ namespace NewHorizons.Builder.Atmosphere var maxHeight = config.Atmosphere.size; if (config.Atmosphere.clouds?.outerCloudRadius != null) maxHeight = config.Atmosphere.clouds.outerCloudRadius; - if (config.Atmosphere.hasRain) + foreach (var particleField in config.ParticleFields) { - var rainGO = Object.Instantiate(_rainEmitterPrefab, effectsGO.transform); - rainGO.name = "RainEmitter"; - rainGO.transform.position = planetGO.transform.position; + var prefab = GetPrefabByType(particleField.type); + var emitter = Object.Instantiate(prefab, effectsGO.transform); + emitter.name = !string.IsNullOrWhiteSpace(particleField.rename) ? particleField.rename : prefab.name.Replace("Prefab_", ""); + emitter.transform.position = planetGO.transform.position; - var pvc = rainGO.GetComponent(); - pvc._densityByHeight = new AnimationCurve(new Keyframe[] + var vfe = emitter.GetComponent(); + var pvc = emitter.GetComponent(); + pvc._vectionFieldEmitter = vfe; + pvc._densityByHeight = particleField.densityByHeightCurve != null ? particleField.densityByHeightCurve.ToAnimationCurve() : new AnimationCurve(new Keyframe[] { new Keyframe(minHeight - 0.5f, 0), new Keyframe(minHeight, 10f), new Keyframe(maxHeight, 0f) }); + pvc._followTarget = particleField.followTarget == ParticleFieldModule.FollowTarget.Probe ? PlanetaryVectionController.FollowTarget.Probe : PlanetaryVectionController.FollowTarget.Player; + pvc._activeInSector = sector; + pvc._exclusionSectors = new Sector[] { }; - rainGO.GetComponent()._activeInSector = sector; - rainGO.GetComponent()._exclusionSectors = new Sector[] { }; - rainGO.SetActive(true); - } - - if (config.Atmosphere.hasSnow) - { - var snowGO = new GameObject("SnowEffects"); - snowGO.transform.parent = effectsGO.transform; - snowGO.transform.position = planetGO.transform.position; - for (int i = 0; i < 5; i++) - { - var snowEmitter = Object.Instantiate(_snowEmitterPrefab, snowGO.transform); - snowEmitter.name = "SnowEmitter"; - snowEmitter.transform.position = planetGO.transform.position; - - var pvc = snowEmitter.GetComponent(); - pvc._densityByHeight = new AnimationCurve(new Keyframe[] - { - new Keyframe(minHeight - 0.5f, 0), - new Keyframe(minHeight, 10f), - new Keyframe(maxHeight, 0f) - }); - - snowEmitter.GetComponent()._activeInSector = sector; - snowEmitter.GetComponent()._exclusionSectors = new Sector[] { }; - snowEmitter.SetActive(true); - } + emitter.SetActive(true); } effectsGO.transform.position = planetGO.transform.position; effectsGO.SetActive(true); } + + public static GameObject GetPrefabByType(ParticleFieldModule.ParticleFieldType type) + { + return type switch + { + ParticleFieldModule.ParticleFieldType.Rain => _rainEmitterPrefab, + ParticleFieldModule.ParticleFieldType.SnowflakesHeavy => _snowEmitterPrefab, + ParticleFieldModule.ParticleFieldType.SnowflakesLight => _snowLightEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Embers => _emberEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Clouds => _cloudEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Leaves => _leafEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Bubbles => _bubbleEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Fog => _fogEmitterPrefab, + ParticleFieldModule.ParticleFieldType.CrystalMotes => _crystalMoteEmitterPrefab, + ParticleFieldModule.ParticleFieldType.RockMotes => _rockMoteEmitterPrefab, + ParticleFieldModule.ParticleFieldType.IceMotes => _iceMoteEmitterPrefab, + ParticleFieldModule.ParticleFieldType.SandMotes => _sandMoteEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Crawlies => _crawliesEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Fireflies => _firefliesEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Plankton => _planktonEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Pollen => _pollenEmitterPrefab, + ParticleFieldModule.ParticleFieldType.Current => _currentEmitterPrefab, + _ => null, + }; + } } } diff --git a/NewHorizons/External/Configs/PlanetConfig.cs b/NewHorizons/External/Configs/PlanetConfig.cs index e76225c4..d1248900 100644 --- a/NewHorizons/External/Configs/PlanetConfig.cs +++ b/NewHorizons/External/Configs/PlanetConfig.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; +using UnityEngine; namespace NewHorizons.External.Configs { @@ -167,6 +168,12 @@ namespace NewHorizons.External.Configs /// public WaterModule Water; + /// + /// Add particle effects in a field around the planet. + /// Also known as Vection Fields. + /// + public ParticleFieldModule[] ParticleFields; + /// /// Add various volumes on this body /// @@ -341,6 +348,29 @@ namespace NewHorizons.External.Configs // useBasicCloudShader is obsolete if (Atmosphere.clouds != null && Atmosphere.clouds.useBasicCloudShader) Atmosphere.clouds.cloudsPrefab = CloudPrefabType.Basic; + + if (Atmosphere.hasRain) + { + if (ParticleFields == null) ParticleFields = new ParticleFieldModule[0]; + ParticleFields = ParticleFields.Append(new ParticleFieldModule + { + type = ParticleFieldModule.ParticleFieldType.Rain, + rename = "RainEmitter" + }).ToArray(); + } + + if (Atmosphere.hasSnow) + { + if (ParticleFields == null) ParticleFields = new ParticleFieldModule[0]; + for (int i = 0; i < 5; i++) + { + ParticleFields = ParticleFields.Append(new ParticleFieldModule + { + type = ParticleFieldModule.ParticleFieldType.SnowflakesHeavy, + rename = "SnowEmitter" + }).ToArray(); + } + } } if (Props?.tornados != null) diff --git a/NewHorizons/External/Modules/AtmosphereModule.cs b/NewHorizons/External/Modules/AtmosphereModule.cs index 675ed837..e318f372 100644 --- a/NewHorizons/External/Modules/AtmosphereModule.cs +++ b/NewHorizons/External/Modules/AtmosphereModule.cs @@ -57,7 +57,7 @@ namespace NewHorizons.External.Modules /// Colour of fog on the planet, if you put fog. /// public MColor fogTint; - + /// /// Relative filepath to the fog color ramp texture, if you put fog. /// x axis is angle to sun (left at midnight, right at noon), y axis is distance to camera (close at bottom, far at top). @@ -75,15 +75,12 @@ namespace NewHorizons.External.Modules public bool hasTrees; /// - /// Does this planet have rain? + /// Does this planet have rain? + /// This is equivalent to effects of setting a rain particle/vection field, rain audio volume, and visor effect volume, combined for convenience. + /// For more control over the rain, use those individual components. /// public bool hasRain; - /// - /// Does this planet have snow? - /// - public bool hasSnow; - /// /// Scale height of the atmosphere /// @@ -174,7 +171,6 @@ namespace NewHorizons.External.Modules /// [DefaultValue(0f)] public float rotationSpeed = 0f; - #region Obsolete /// @@ -189,6 +185,8 @@ namespace NewHorizons.External.Modules #region Obsolete + [Obsolete("HasSnow is deprecated, please use ParticleFields instead")] + public bool hasSnow; [Obsolete("CloudTint is deprecated, please use CloudInfo instead")] public MColor cloudTint; @@ -208,7 +206,8 @@ namespace NewHorizons.External.Modules [Obsolete("UseBasicCloudShader is deprecated, please use CloudInfo instead")] public bool useBasicCloudShader; - [DefaultValue(true)] [Obsolete("ShadowsOnClouds is deprecated, please use CloudInfo instead")] + [DefaultValue(true)] + [Obsolete("ShadowsOnClouds is deprecated, please use CloudInfo instead")] public bool shadowsOnClouds = true; [Obsolete("HasAtmosphere is deprecated, please use UseAtmosphereShader instead")] diff --git a/NewHorizons/External/Modules/VectionFieldModule.cs b/NewHorizons/External/Modules/VectionFieldModule.cs new file mode 100644 index 00000000..19586544 --- /dev/null +++ b/NewHorizons/External/Modules/VectionFieldModule.cs @@ -0,0 +1,79 @@ +using NewHorizons.External.Modules.VariableSize; +using NewHorizons.External.SerializableData; +using NewHorizons.External.SerializableEnums; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.ComponentModel; +using System.Runtime.Serialization; + +namespace NewHorizons.External.Modules +{ + [JsonObject] + public class ParticleFieldModule + { + /// + /// Particle type for this vection field. + /// + public ParticleFieldType type; + + /// + /// What the particle field activates based on. + /// + [DefaultValue("player")] public FollowTarget followTarget = FollowTarget.Player; + + /// + /// Density by height curve. Determines how many particles are emitted at different heights. + /// Defaults to a curve based on minimum and maximum heights of various other modules. + /// + public HeightDensityPair[] densityByHeightCurve; + + /// + /// An optional rename of this object + /// + public string rename; + + [JsonObject] + public class HeightDensityPair + { + /// + /// A specific radius + /// + public float height; + + /// + /// The particle count for this radius. + /// + public float density; + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum ParticleFieldType + { + [EnumMember(Value = @"rain")] Rain, + [EnumMember(Value = @"snowflakesHeavy")] SnowflakesHeavy, + [EnumMember(Value = @"snowflakesLight")] SnowflakesLight, + [EnumMember(Value = @"embers")] Embers, + [EnumMember(Value = @"clouds")] Clouds, + [EnumMember(Value = @"leaves")] Leaves, + [EnumMember(Value = @"bubbles")] Bubbles, + [EnumMember(Value = @"fog")] Fog, + [EnumMember(Value = @"crystalMotes")] CrystalMotes, + [EnumMember(Value = @"rockMotes")] RockMotes, + [EnumMember(Value = @"iceMotes")] IceMotes, + [EnumMember(Value = @"sandMotes")] SandMotes, + [EnumMember(Value = @"crawlies")] Crawlies, + [EnumMember(Value = @"fireflies")] Fireflies, + [EnumMember(Value = @"plankton")] Plankton, + [EnumMember(Value = @"pollen")] Pollen, + [EnumMember(Value = @"current")] Current + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum FollowTarget + { + [EnumMember(Value = @"player")] Player, + [EnumMember(Value = @"probe")] Probe + } + } +} diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index 85e8dd3c..90fc4a8f 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -654,9 +654,6 @@ namespace NewHorizons.Handlers } } - if (body.Config.Atmosphere.hasRain || body.Config.Atmosphere.hasSnow) - EffectsBuilder.Make(go, sector, body.Config, surfaceSize); - if (body.Config.Atmosphere.fogSize != 0) { fog = FogBuilder.Make(go, sector, body.Config.Atmosphere, body.Mod); @@ -665,6 +662,11 @@ namespace NewHorizons.Handlers atmosphere = AtmosphereBuilder.Make(go, sector, body.Config.Atmosphere, surfaceSize).GetComponentInChildren(); } + if (body.Config.ParticleFields != null) + { + EffectsBuilder.Make(go, sector, body.Config); + } + if (body.Config.Props != null) { PropBuildManager.Make(go, sector, rb, body); diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index c34f9431..f02aafbb 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -132,6 +132,13 @@ "description": "Add water to this planet", "$ref": "#/definitions/WaterModule" }, + "ParticleFields": { + "type": "array", + "description": "Add particle effects in a field around the planet.\nAlso known as Vection Fields.", + "items": { + "$ref": "#/definitions/ParticleFieldModule" + } + }, "Volumes": { "description": "Add various volumes on this body", "$ref": "#/definitions/VolumesModule" @@ -371,11 +378,7 @@ }, "hasRain": { "type": "boolean", - "description": "Does this planet have rain?" - }, - "hasSnow": { - "type": "boolean", - "description": "Does this planet have snow?" + "description": "Does this planet have rain? \nThis is equivalent to effects of setting a rain particle/vection field, rain audio volume, and visor effect volume, combined for convenience.\nFor more control over the rain, use those individual components." }, "size": { "type": "number", @@ -3583,6 +3586,102 @@ } } }, + "ParticleFieldModule": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "description": "Particle type for this vection field.", + "$ref": "#/definitions/ParticleFieldType" + }, + "followTarget": { + "description": "What the particle field activates based on.", + "default": "player", + "$ref": "#/definitions/FollowTarget" + }, + "densityByHeightCurve": { + "type": "array", + "description": "Density by height curve. Determines how many particles are emitted at different heights.\nDefaults to a curve based on minimum and maximum heights of various other modules.", + "items": { + "$ref": "#/definitions/HeightDensityPair" + } + }, + "rename": { + "type": "string", + "description": "An optional rename of this object" + } + } + }, + "ParticleFieldType": { + "type": "string", + "description": "", + "x-enumNames": [ + "Rain", + "SnowflakesHeavy", + "SnowflakesLight", + "Embers", + "Clouds", + "Leaves", + "Bubbles", + "Fog", + "CrystalMotes", + "RockMotes", + "IceMotes", + "SandMotes", + "Crawlies", + "Fireflies", + "Plankton", + "Pollen", + "Current" + ], + "enum": [ + "rain", + "snowflakesHeavy", + "snowflakesLight", + "embers", + "clouds", + "leaves", + "bubbles", + "fog", + "crystalMotes", + "rockMotes", + "iceMotes", + "sandMotes", + "crawlies", + "fireflies", + "plankton", + "pollen", + "current" + ] + }, + "FollowTarget": { + "type": "string", + "description": "", + "x-enumNames": [ + "Player", + "Probe" + ], + "enum": [ + "player", + "probe" + ] + }, + "HeightDensityPair": { + "type": "object", + "additionalProperties": false, + "properties": { + "height": { + "type": "number", + "description": "A specific radius", + "format": "float" + }, + "density": { + "type": "number", + "description": "The particle count for this radius.", + "format": "float" + } + } + }, "VolumesModule": { "type": "object", "additionalProperties": false, diff --git a/NewHorizons/Utility/NewHorizonExtensions.cs b/NewHorizons/Utility/NewHorizonExtensions.cs index e8e2222c..b292b06b 100644 --- a/NewHorizons/Utility/NewHorizonExtensions.cs +++ b/NewHorizons/Utility/NewHorizonExtensions.cs @@ -1,4 +1,5 @@ using NewHorizons.External.Configs; +using NewHorizons.External.Modules.VariableSize; using NewHorizons.External.SerializableData; using NewHorizons.External.SerializableEnums; using NewHorizons.Utility.OWML; @@ -12,6 +13,7 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; using UnityEngine; +using static NewHorizons.External.Modules.ParticleFieldModule; using NomaiCoordinates = NewHorizons.External.Configs.StarSystemConfig.NomaiCoordinates; namespace NewHorizons.Utility @@ -277,5 +279,31 @@ namespace NewHorizons.Utility { go.transform.rotation = Quaternion.FromToRotation(Vector3.forward, direction); } + + public static AnimationCurve ToAnimationCurve(this TimeValuePair[] pairs) + { + var curve = new AnimationCurve(); + if (pairs != null) + { + foreach (var pair in pairs) + { + curve.AddKey(new Keyframe(pair.time, pair.value)); + } + } + return curve; + } + + public static AnimationCurve ToAnimationCurve(this HeightDensityPair[] pairs) + { + var curve = new AnimationCurve(); + if (pairs != null) + { + foreach (var pair in pairs) + { + curve.AddKey(new Keyframe(pair.height, pair.density)); + } + } + return curve; + } } }