From a1aa65dda8cfdcf7ce3767f9ce86dcc84a9bc78a Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Fri, 26 Aug 2022 23:31:41 -0400 Subject: [PATCH 01/51] Uh oh --- .../Components/SizeControllers/StarEvolutionController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/Components/SizeControllers/StarEvolutionController.cs b/NewHorizons/Components/SizeControllers/StarEvolutionController.cs index 8398cd97..7412c07e 100644 --- a/NewHorizons/Components/SizeControllers/StarEvolutionController.cs +++ b/NewHorizons/Components/SizeControllers/StarEvolutionController.cs @@ -194,7 +194,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) From f2dd6b04d2ab57b6a6256de376b11b0207d9311d Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Fri, 26 Aug 2022 23:31:46 -0400 Subject: [PATCH 02/51] Don't even try to supernova if it is null --- .../Components/SizeControllers/StarEvolutionController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Components/SizeControllers/StarEvolutionController.cs b/NewHorizons/Components/SizeControllers/StarEvolutionController.cs index 7412c07e..0f67989e 100644 --- a/NewHorizons/Components/SizeControllers/StarEvolutionController.cs +++ b/NewHorizons/Components/SizeControllers/StarEvolutionController.cs @@ -370,6 +370,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 +394,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) From c958ec6f44f42b9eebe3ebb3c2f37de9e37b206e Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Sat, 27 Aug 2022 00:05:08 -0400 Subject: [PATCH 03/51] Don't error at eye of universe --- .../Components/SizeControllers/StarEvolutionController.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NewHorizons/Components/SizeControllers/StarEvolutionController.cs b/NewHorizons/Components/SizeControllers/StarEvolutionController.cs index 0f67989e..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); From fa0d8d6689b14fd4491bc510f92ac44e71fc1577 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Fri, 26 Aug 2022 21:51:54 -0400 Subject: [PATCH 04/51] Use correct path --- NewHorizons/Builder/Props/ProjectionBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() { From cb1abb351606bccde93eb5580d00febd369a1f32 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Fri, 26 Aug 2022 21:37:33 -0400 Subject: [PATCH 05/51] Update packages to latest version --- NewHorizons/NewHorizons.csproj | 4 ++-- SchemaExporter/SchemaExporter.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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/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 @@ - + From bca0e2ffed9c251275d69d8a827214bccddd442d Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 27 Aug 2022 21:30:07 -0400 Subject: [PATCH 06/51] Update manifest.json --- NewHorizons/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" ], From e92c6b6404bbbdd1a16935f2795854c6e6cc91f1 Mon Sep 17 00:00:00 2001 From: JohnCorby Date: Sat, 27 Aug 2022 21:26:38 -0700 Subject: [PATCH 07/51] next update?????? --- NewHorizons/Builder/Body/BrambleDimensionBuilder.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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); From 313267427b96da3ac3c69d0c9b219ce603421521 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Sun, 28 Aug 2022 15:03:35 -0400 Subject: [PATCH 08/51] Better backup --- NewHorizons/Utility/DebugMenu/DebugMenu.cs | 31 +++++++++++++--------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/NewHorizons/Utility/DebugMenu/DebugMenu.cs b/NewHorizons/Utility/DebugMenu/DebugMenu.cs index 0d25ebfe..aa58001d 100644 --- a/NewHorizons/Utility/DebugMenu/DebugMenu.cs +++ b/NewHorizons/Utility/DebugMenu/DebugMenu.cs @@ -227,6 +227,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 +258,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}"); - } } } From 57c145c048dd1d3e696ac86a7212e1a16571972a Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Sun, 28 Aug 2022 15:35:05 -0400 Subject: [PATCH 09/51] Only autosave if save button is unlocked --- NewHorizons/Utility/DebugMenu/DebugMenu.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Utility/DebugMenu/DebugMenu.cs b/NewHorizons/Utility/DebugMenu/DebugMenu.cs index aa58001d..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 { From 135aae99744784408a2e23da19cefafad8d1a73d Mon Sep 17 00:00:00 2001 From: Ben C Date: Sun, 28 Aug 2022 21:02:34 -0400 Subject: [PATCH 10/51] Add Support For Extra Modules --- NewHorizons/Handlers/PlanetCreationHandler.cs | 1 + NewHorizons/INewHorizons.cs | 11 +++++ NewHorizons/Main.cs | 2 + NewHorizons/NewHorizonsApi.cs | 40 ++++++++++++------- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index 73df7daa..fcb82a02 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -248,6 +248,7 @@ namespace NewHorizons.Handlers } } } + Main.Instance.OnPlanetLoaded?.Invoke(body.Config.name); return true; } diff --git a/NewHorizons/INewHorizons.cs b/NewHorizons/INewHorizons.cs index 7c647003..7b9936ba 100644 --- a/NewHorizons/INewHorizons.cs +++ b/NewHorizons/INewHorizons.cs @@ -43,6 +43,17 @@ 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(); + + /// + /// Gets an object in the `extras` object of a config, returns null if the key doesn't exist + /// + object GetExtraModule(Type moduleType, string extrasModuleName, string planetName); + /// /// 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 cb64f092..000eb46b 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -68,6 +68,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; @@ -170,6 +171,7 @@ namespace NewHorizons OnChangeStarSystem = new StarSystemEvent(); OnStarSystemLoaded = new StarSystemEvent(); + OnPlanetLoaded = new StarSystemEvent(); SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; diff --git a/NewHorizons/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs index 0ddb9f14..13a0fae1 100644 --- a/NewHorizons/NewHorizonsApi.cs +++ b/NewHorizons/NewHorizonsApi.cs @@ -5,14 +5,17 @@ using OWML.Common; using OWML.Utils; using System; using System.Collections.Generic; +using System.Data.SqlTypes; using System.IO; using System.Linq; +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,25 @@ namespace NewHorizons } } + public object GetExtraModule(Type moduleType, string extraModuleKey, string planetName) + { + var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == planetName); + if (planet == null) + { + // Uh idk if we need this but ye it do be here etc. + Logger.LogVerbose($"Attempting To Get Extras On Planet That Doesn't Exist! ({planetName})"); + return null; + } + var jsonText = File.ReadAllText(planet.Mod.ModHelper.Manifest.ModFolderPath + planet.RelativePath); + var jsonData = JObject.Parse(jsonText); + var possibleExtras = jsonData.Property("extras")?.Value; + if (possibleExtras is JObject extras) + { + return extras.Property(extraModuleKey)?.Value.ToObject(moduleType); + } + return null; + } + public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignWithNormal) { From ffc12dde6d0714582c257dd329200f9d908486d2 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Mon, 29 Aug 2022 01:12:08 -0400 Subject: [PATCH 11/51] Resize volume layers --- NewHorizons/Patches/ShapePatches.cs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 NewHorizons/Patches/ShapePatches.cs 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; + } + } +} From 60d26822298f5fcb4bdd90af8828f937ac0f988e Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 08:04:09 -0400 Subject: [PATCH 12/51] Add To Body Schema --- SchemaExporter/SchemaExporter.cs | 191 ++++++++++++++++--------------- 1 file changed, 96 insertions(+), 95 deletions(-) diff --git a/SchemaExporter/SchemaExporter.cs b/SchemaExporter/SchemaExporter.cs index f0c802f5..450d6833 100644 --- a/SchemaExporter/SchemaExporter.cs +++ b/SchemaExporter/SchemaExporter.cs @@ -1,96 +1,97 @@ -using System; -using System.Collections.Generic; -using System.IO; -using NewHorizons.External.Configs; -using NJsonSchema; -using NJsonSchema.Generation; - -namespace SchemaExporter; - -public static class SchemaExporter -{ - public static void Main(string[] args) - { - const string folderName = "NewHorizons/Schemas"; - - Directory.CreateDirectory(folderName); - Console.WriteLine("Schema Generator: We're winning!"); - var settings = new JsonSchemaGeneratorSettings - { - IgnoreObsoleteProperties = true, - DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull, - FlattenInheritanceHierarchy = true, - AllowReferencesWithProperties = true - }; - var bodySchema = new Schema("Celestial Body Schema", "Schema for a celestial body in New Horizons", $"{folderName}/body_schema", settings); - bodySchema.Output(); - var systemSchema = - new Schema("Star System Schema", "Schema for a star system in New Horizons", $"{folderName}/star_system_schema", settings); - systemSchema.Output(); - var addonSchema = new Schema("Addon Manifest Schema", - "Schema for an addon manifest in New Horizons", $"{folderName}/addon_manifest_schema", settings); - addonSchema.Output(); - var translationSchema = - new Schema("Translation Schema", "Schema for a translation file in New Horizons", $"{folderName}/translation_schema", settings); - translationSchema.Output(); - Console.WriteLine("Done!"); - } - - private readonly struct Schema - { - private readonly JsonSchemaGeneratorSettings _generatorSettings; - private readonly string _title, _description; - private readonly string _outFileName; - - public Schema(string schemaTitle, string schemaDescription, string fileName, JsonSchemaGeneratorSettings settings) - { - _title = schemaTitle; - _description = schemaDescription; - _outFileName = fileName; - _generatorSettings = settings; - } - - public void Output() - { - Console.WriteLine($"Outputting {_title}"); - File.WriteAllText($"{_outFileName}.json", ToString()); - } - - public override string ToString() - { - return GetJsonSchema().ToJson(); - } - - private JsonSchema GetJsonSchema() - { - var schema = JsonSchema.FromType(_generatorSettings); - schema.Title = _title; - var schemaLinkProp = new JsonSchemaProperty - { - Type = JsonObjectType.String, - Description = "The schema to validate with" - }; - schema.Properties.Add("$schema", schemaLinkProp); - schema.ExtensionData ??= new Dictionary(); - schema.ExtensionData.Add("$docs", new Dictionary - { - {"title", _title}, - {"description", _description} - }); - - if (_title == "Celestial Body Schema") - { - schema.Definitions["OrbitModule"].Properties["semiMajorAxis"].Default = 5000f; - } - - if (_title == "Star System Schema") - { - schema.Definitions["NomaiCoordinates"].Properties["x"].UniqueItems = true; - schema.Definitions["NomaiCoordinates"].Properties["y"].UniqueItems = true; - schema.Definitions["NomaiCoordinates"].Properties["z"].UniqueItems = true; - } - - return schema; - } - } +using System; +using System.Collections.Generic; +using System.IO; +using NewHorizons.External.Configs; +using NJsonSchema; +using NJsonSchema.Generation; + +namespace SchemaExporter; + +public static class SchemaExporter +{ + public static void Main(string[] args) + { + const string folderName = "NewHorizons/Schemas"; + + Directory.CreateDirectory(folderName); + Console.WriteLine("Schema Generator: We're winning!"); + var settings = new JsonSchemaGeneratorSettings + { + IgnoreObsoleteProperties = true, + DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull, + FlattenInheritanceHierarchy = true, + AllowReferencesWithProperties = true + }; + var bodySchema = new Schema("Celestial Body Schema", "Schema for a celestial body in New Horizons", $"{folderName}/body_schema", settings); + bodySchema.Output(); + var systemSchema = + new Schema("Star System Schema", "Schema for a star system in New Horizons", $"{folderName}/star_system_schema", settings); + systemSchema.Output(); + var addonSchema = new Schema("Addon Manifest Schema", + "Schema for an addon manifest in New Horizons", $"{folderName}/addon_manifest_schema", settings); + addonSchema.Output(); + var translationSchema = + new Schema("Translation Schema", "Schema for a translation file in New Horizons", $"{folderName}/translation_schema", settings); + translationSchema.Output(); + Console.WriteLine("Done!"); + } + + private readonly struct Schema + { + private readonly JsonSchemaGeneratorSettings _generatorSettings; + private readonly string _title, _description; + private readonly string _outFileName; + + public Schema(string schemaTitle, string schemaDescription, string fileName, JsonSchemaGeneratorSettings settings) + { + _title = schemaTitle; + _description = schemaDescription; + _outFileName = fileName; + _generatorSettings = settings; + } + + public void Output() + { + Console.WriteLine($"Outputting {_title}"); + File.WriteAllText($"{_outFileName}.json", ToString()); + } + + public override string ToString() + { + return GetJsonSchema().ToJson(); + } + + private JsonSchema GetJsonSchema() + { + var schema = JsonSchema.FromType(_generatorSettings); + schema.Title = _title; + var schemaLinkProp = new JsonSchemaProperty + { + Type = JsonObjectType.String, + Description = "The schema to validate with" + }; + schema.Properties.Add("$schema", schemaLinkProp); + schema.ExtensionData ??= new Dictionary(); + schema.ExtensionData.Add("$docs", new Dictionary + { + {"title", _title}, + {"description", _description} + }); + + if (_title == "Celestial Body Schema") + { + schema.Definitions["OrbitModule"].Properties["semiMajorAxis"].Default = 5000f; + schema.Properties.Add("extras", new Dictionary()); + } + + if (_title == "Star System Schema") + { + schema.Definitions["NomaiCoordinates"].Properties["x"].UniqueItems = true; + schema.Definitions["NomaiCoordinates"].Properties["y"].UniqueItems = true; + schema.Definitions["NomaiCoordinates"].Properties["z"].UniqueItems = true; + } + + return schema; + } + } } \ No newline at end of file From 549bea956713a5dfa49932e924ac941d91bb0bd8 Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 08:08:36 -0400 Subject: [PATCH 13/51] Ok try again --- SchemaExporter/SchemaExporter.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/SchemaExporter/SchemaExporter.cs b/SchemaExporter/SchemaExporter.cs index 450d6833..2dba6386 100644 --- a/SchemaExporter/SchemaExporter.cs +++ b/SchemaExporter/SchemaExporter.cs @@ -81,7 +81,10 @@ public static class SchemaExporter if (_title == "Celestial Body Schema") { schema.Definitions["OrbitModule"].Properties["semiMajorAxis"].Default = 5000f; - schema.Properties.Add("extras", new Dictionary()); + schema.Properties.Add("extras", new JsonSchemaProperty { + Type = JsonObjectType.Object, + Description = "Extra data that may be used by extension mods" + }); } if (_title == "Star System Schema") @@ -89,6 +92,10 @@ public static class SchemaExporter schema.Definitions["NomaiCoordinates"].Properties["x"].UniqueItems = true; schema.Definitions["NomaiCoordinates"].Properties["y"].UniqueItems = true; schema.Definitions["NomaiCoordinates"].Properties["z"].UniqueItems = true; + schema.Properties.Add("extras", new JsonSchemaProperty { + Type = JsonObjectType.Object, + Description = "Extra data that may be used by extension mods" + }); } return schema; From a8ca79896b519a82036b2b195d15477f40545fb4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 29 Aug 2022 12:13:22 +0000 Subject: [PATCH 14/51] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 4 ++++ NewHorizons/Schemas/star_system_schema.json | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index bc88b9f7..c4fe13eb 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -131,6 +131,10 @@ "$schema": { "type": "string", "description": "The schema to validate with" + }, + "extras": { + "type": "object", + "description": "Extra data that may be used by extension mods" } }, "definitions": { diff --git a/NewHorizons/Schemas/star_system_schema.json b/NewHorizons/Schemas/star_system_schema.json index b1f5e2cc..54770b00 100644 --- a/NewHorizons/Schemas/star_system_schema.json +++ b/NewHorizons/Schemas/star_system_schema.json @@ -74,6 +74,10 @@ "$schema": { "type": "string", "description": "The schema to validate with" + }, + "extras": { + "type": "object", + "description": "Extra data that may be used by extension mods" } }, "definitions": { From 21dc7fc8dd5769221855de124e1afffa2bf1957a Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 08:33:20 -0400 Subject: [PATCH 15/51] Updated Docs --- docs/content/pages/tutorials/api.md | 95 ++++++++++++++++++++--- docs/content/pages/tutorials/extending.md | 65 ++++++++++++++++ 2 files changed, 149 insertions(+), 11 deletions(-) create mode 100644 docs/content/pages/tutorials/extending.md diff --git a/docs/content/pages/tutorials/api.md b/docs/content/pages/tutorials/api.md index 8689943c..114cbe68 100644 --- a/docs/content/pages/tutorials/api.md +++ b/docs/content/pages/tutorials/api.md @@ -9,21 +9,94 @@ 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(); + + /// + /// Gets an object in the `extras` object of a config, returns null if the key doesn't exist + /// + object GetExtraModule(Type moduleType, string extrasModuleName, string planetName); + + /// + /// 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: diff --git a/docs/content/pages/tutorials/extending.md b/docs/content/pages/tutorials/extending.md new file mode 100644 index 00000000..649c2473 --- /dev/null +++ b/docs/content/pages/tutorials/extending.md @@ -0,0 +1,65 @@ +--- +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 `GetExtraModule` method to obtain the `myCoolExtensionData` object. + +## Extending Planets + +You can extend all planets by hooking into the `OnBodyLoaded` event of the API: + +```cs +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: + +```cs +public class MyCoolExtensionData { + int myCoolExtensionProperty; +} +``` + +Then, use the `GetExtraModule` method: + +```cs +var api = ModHelper.Interactions.TryGetModApi("xen.NewHorizons"); +api.GetBodyLoadedEvent().AddListener((name) => { + ModHelper.Console.WriteLine($"Body: {name} Loaded!"); + var potentialData = api.GetExtraModule(typeof(MyCoolExtensionData), "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 + + From 44984bde2729c58cfe29a697b9edc24cd17bef8c Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 08:38:31 -0400 Subject: [PATCH 16/51] Update API docs to use TryGetModApi --- docs/content/pages/tutorials/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/pages/tutorials/api.md b/docs/content/pages/tutorials/api.md index 114cbe68..817c89ba 100644 --- a/docs/content/pages/tutorials/api.md +++ b/docs/content/pages/tutorials/api.md @@ -106,7 +106,7 @@ public class MyMod : ModBehaviour { void Start() { - INewHorizons NewHorizonsAPI = ModHelper.Interaction.GetModApi("xen.NewHorizons"); + INewHorizons NewHorizonsAPI = ModHelper.Interaction.TryGetModApi("xen.NewHorizons"); } } ``` From 202eb808aa28addffc79610ca9e7df9ed7c31f50 Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 08:52:26 -0400 Subject: [PATCH 17/51] Add Mod Dep Warning --- docs/content/pages/tutorials/extending.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/pages/tutorials/extending.md b/docs/content/pages/tutorials/extending.md index 649c2473..67e7e5ca 100644 --- a/docs/content/pages/tutorials/extending.md +++ b/docs/content/pages/tutorials/extending.md @@ -25,6 +25,8 @@ Addon developers will add a key to the `extras` object in the root of the config } ``` +**It's up to the addon dev to list your mod as a dependency!** + Your mod will then use the API's `GetExtraModule` method to obtain the `myCoolExtensionData` object. ## Extending Planets From 6722ff7ad40fce6a84804e326514f9786fdfea09 Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 17:25:42 -0400 Subject: [PATCH 18/51] Added Support for Systems --- NewHorizons/INewHorizons.cs | 9 +- NewHorizons/Main.cs | 8 +- NewHorizons/NewHorizonsApi.cs | 41 +++-- NewHorizons/Utility/NewHorizonsSystem.cs | 4 +- SchemaExporter/SchemaExporter.cs | 211 ++++++++++++----------- 5 files changed, 152 insertions(+), 121 deletions(-) diff --git a/NewHorizons/INewHorizons.cs b/NewHorizons/INewHorizons.cs index 7b9936ba..aa014ea6 100644 --- a/NewHorizons/INewHorizons.cs +++ b/NewHorizons/INewHorizons.cs @@ -50,9 +50,14 @@ namespace NewHorizons UnityEvent GetBodyLoadedEvent(); /// - /// Gets an object in the `extras` object of a config, returns null if the key doesn't exist + /// Gets an object in the `extras` object of a body config, returns null if the key doesn't exist /// - object GetExtraModule(Type moduleType, string extrasModuleName, string planetName); + object GetExtraModuleForBody(Type moduleType, string extrasModuleName, string planetName); + + /// + /// Gets an object in the `extras` object of a system config, returns null if the key doesn't exist + /// + object GetExtraModuleForSystem(Type moduleType, string extrasModuleName, string systemName); /// /// 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 000eb46b..97ba6124 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -127,7 +127,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 +143,7 @@ namespace NewHorizons } } }; - SystemDict["EyeOfTheUniverse"] = new NewHorizonsSystem("EyeOfTheUniverse", new StarSystemConfig(), Instance) + SystemDict["EyeOfTheUniverse"] = new NewHorizonsSystem("EyeOfTheUniverse", new StarSystemConfig(), "", Instance) { Config = { @@ -517,7 +517,7 @@ namespace NewHorizons } else { - SystemDict[name] = new NewHorizonsSystem(name, starSystemConfig, mod); + SystemDict[name] = new NewHorizonsSystem(name, starSystemConfig, relativePath, mod); } } } @@ -618,7 +618,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); diff --git a/NewHorizons/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs index 13a0fae1..e8e36a24 100644 --- a/NewHorizons/NewHorizonsApi.cs +++ b/NewHorizons/NewHorizonsApi.cs @@ -101,23 +101,42 @@ namespace NewHorizons } } - public object GetExtraModule(Type moduleType, string extraModuleKey, string planetName) + private object GetExtraModule(Type moduleType, string key, string path) { - var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == planetName); - if (planet == null) + if (path == "") return null; + try { - // Uh idk if we need this but ye it do be here etc. - Logger.LogVerbose($"Attempting To Get Extras On Planet That Doesn't Exist! ({planetName})"); + var jsonText = File.ReadAllText(path); + var jsonData = JObject.Parse(jsonText); + var possibleExtras = jsonData.Property("extras")?.Value; + if (possibleExtras is JObject extras) + { + return extras.Property(key)?.Value.ToObject(moduleType); + } return null; } - var jsonText = File.ReadAllText(planet.Mod.ModHelper.Manifest.ModFolderPath + planet.RelativePath); - var jsonData = JObject.Parse(jsonText); - var possibleExtras = jsonData.Property("extras")?.Value; - if (possibleExtras is JObject extras) + catch (FileNotFoundException) { - return extras.Property(extraModuleKey)?.Value.ToObject(moduleType); + return null; } - return null; + } + + public object GetExtraModuleForBody(Type moduleType, string extraModuleKey, string planetName) + { + var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == planetName); + return planet == null + ? null + : GetExtraModule(moduleType, extraModuleKey, + planet.Mod.ModHelper.Manifest.ModFolderPath + planet.RelativePath); + } + + public object GetExtraModuleForSystem(Type moduleType, string extraModuleKey, string systemName) + { + var system = Main.SystemDict[Main.Instance.CurrentStarSystem]; + return system == null + ? null + : GetExtraModule(moduleType, extraModuleKey, + system.Mod.ModHelper.Manifest.ModFolderPath + system.RelativePath); } public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, 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/SchemaExporter/SchemaExporter.cs b/SchemaExporter/SchemaExporter.cs index 2dba6386..33c12666 100644 --- a/SchemaExporter/SchemaExporter.cs +++ b/SchemaExporter/SchemaExporter.cs @@ -1,104 +1,109 @@ -using System; -using System.Collections.Generic; -using System.IO; -using NewHorizons.External.Configs; -using NJsonSchema; -using NJsonSchema.Generation; - -namespace SchemaExporter; - -public static class SchemaExporter -{ - public static void Main(string[] args) - { - const string folderName = "NewHorizons/Schemas"; - - Directory.CreateDirectory(folderName); - Console.WriteLine("Schema Generator: We're winning!"); - var settings = new JsonSchemaGeneratorSettings - { - IgnoreObsoleteProperties = true, - DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull, - FlattenInheritanceHierarchy = true, - AllowReferencesWithProperties = true - }; - var bodySchema = new Schema("Celestial Body Schema", "Schema for a celestial body in New Horizons", $"{folderName}/body_schema", settings); - bodySchema.Output(); - var systemSchema = - new Schema("Star System Schema", "Schema for a star system in New Horizons", $"{folderName}/star_system_schema", settings); - systemSchema.Output(); - var addonSchema = new Schema("Addon Manifest Schema", - "Schema for an addon manifest in New Horizons", $"{folderName}/addon_manifest_schema", settings); - addonSchema.Output(); - var translationSchema = - new Schema("Translation Schema", "Schema for a translation file in New Horizons", $"{folderName}/translation_schema", settings); - translationSchema.Output(); - Console.WriteLine("Done!"); - } - - private readonly struct Schema - { - private readonly JsonSchemaGeneratorSettings _generatorSettings; - private readonly string _title, _description; - private readonly string _outFileName; - - public Schema(string schemaTitle, string schemaDescription, string fileName, JsonSchemaGeneratorSettings settings) - { - _title = schemaTitle; - _description = schemaDescription; - _outFileName = fileName; - _generatorSettings = settings; - } - - public void Output() - { - Console.WriteLine($"Outputting {_title}"); - File.WriteAllText($"{_outFileName}.json", ToString()); - } - - public override string ToString() - { - return GetJsonSchema().ToJson(); - } - - private JsonSchema GetJsonSchema() - { - var schema = JsonSchema.FromType(_generatorSettings); - schema.Title = _title; - var schemaLinkProp = new JsonSchemaProperty - { - Type = JsonObjectType.String, - Description = "The schema to validate with" - }; - schema.Properties.Add("$schema", schemaLinkProp); - schema.ExtensionData ??= new Dictionary(); - schema.ExtensionData.Add("$docs", new Dictionary - { - {"title", _title}, - {"description", _description} - }); - - if (_title == "Celestial Body Schema") - { - schema.Definitions["OrbitModule"].Properties["semiMajorAxis"].Default = 5000f; - schema.Properties.Add("extras", new JsonSchemaProperty { - Type = JsonObjectType.Object, - Description = "Extra data that may be used by extension mods" - }); - } - - if (_title == "Star System Schema") - { - schema.Definitions["NomaiCoordinates"].Properties["x"].UniqueItems = true; - schema.Definitions["NomaiCoordinates"].Properties["y"].UniqueItems = true; - schema.Definitions["NomaiCoordinates"].Properties["z"].UniqueItems = true; - schema.Properties.Add("extras", new JsonSchemaProperty { - Type = JsonObjectType.Object, - Description = "Extra data that may be used by extension mods" - }); - } - - return schema; - } - } +using System; +using System.Collections.Generic; +using System.IO; +using NewHorizons.External.Configs; +using NJsonSchema; +using NJsonSchema.Generation; + +namespace SchemaExporter; + +public static class SchemaExporter +{ + public static void Main(string[] args) + { + const string folderName = "NewHorizons/Schemas"; + + Directory.CreateDirectory(folderName); + Console.WriteLine("Schema Generator: We're winning!"); + var settings = new JsonSchemaGeneratorSettings + { + IgnoreObsoleteProperties = true, + DefaultReferenceTypeNullHandling = ReferenceTypeNullHandling.NotNull, + FlattenInheritanceHierarchy = true, + AllowReferencesWithProperties = true + }; + var bodySchema = new Schema("Celestial Body Schema", "Schema for a celestial body in New Horizons", $"{folderName}/body_schema", settings); + bodySchema.Output(); + var systemSchema = + new Schema("Star System Schema", "Schema for a star system in New Horizons", $"{folderName}/star_system_schema", settings); + systemSchema.Output(); + var addonSchema = new Schema("Addon Manifest Schema", + "Schema for an addon manifest in New Horizons", $"{folderName}/addon_manifest_schema", settings); + addonSchema.Output(); + var translationSchema = + new Schema("Translation Schema", "Schema for a translation file in New Horizons", $"{folderName}/translation_schema", settings); + translationSchema.Output(); + Console.WriteLine("Done!"); + } + + private readonly struct Schema + { + private readonly JsonSchemaGeneratorSettings _generatorSettings; + private readonly string _title, _description; + private readonly string _outFileName; + + public Schema(string schemaTitle, string schemaDescription, string fileName, JsonSchemaGeneratorSettings settings) + { + _title = schemaTitle; + _description = schemaDescription; + _outFileName = fileName; + _generatorSettings = settings; + } + + public void Output() + { + Console.WriteLine($"Outputting {_title}"); + File.WriteAllText($"{_outFileName}.json", ToString()); + } + + public override string ToString() + { + return GetJsonSchema().ToJson(); + } + + private JsonSchema GetJsonSchema() + { + var schema = JsonSchema.FromType(_generatorSettings); + schema.Title = _title; + var schemaLinkProp = new JsonSchemaProperty + { + Type = JsonObjectType.String, + Description = "The schema to validate with" + }; + schema.Properties.Add("$schema", schemaLinkProp); + schema.ExtensionData ??= new Dictionary(); + schema.ExtensionData.Add("$docs", new Dictionary + { + {"title", _title}, + {"description", _description} + }); + + switch (_title) + { + 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 is "Star System Schema" or "Celestial Body Schema") + { + schema.Properties.Add("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; + } + } } \ No newline at end of file From 6aad0edd33b40b7980a9f422d06a2c63e2d26f85 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 29 Aug 2022 21:29:01 +0000 Subject: [PATCH 19/51] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 5 ++++- NewHorizons/Schemas/star_system_schema.json | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index c4fe13eb..02771c9b 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -134,7 +134,10 @@ }, "extras": { "type": "object", - "description": "Extra data that may be used by extension mods" + "description": "Extra data that may be used by extension mods", + "additionalProperties": { + "type": "object" + } } }, "definitions": { diff --git a/NewHorizons/Schemas/star_system_schema.json b/NewHorizons/Schemas/star_system_schema.json index 54770b00..d8073db0 100644 --- a/NewHorizons/Schemas/star_system_schema.json +++ b/NewHorizons/Schemas/star_system_schema.json @@ -77,7 +77,10 @@ }, "extras": { "type": "object", - "description": "Extra data that may be used by extension mods" + "description": "Extra data that may be used by extension mods", + "additionalProperties": { + "type": "object" + } } }, "definitions": { From 1c99f6c3d43e9d576b572a1fa87e6d5d52482f1a Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 17:31:01 -0400 Subject: [PATCH 20/51] Update Docs Again --- docs/content/pages/tutorials/api.md | 9 +++++++-- docs/content/pages/tutorials/extending.md | 12 +++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/content/pages/tutorials/api.md b/docs/content/pages/tutorials/api.md index 817c89ba..0400351f 100644 --- a/docs/content/pages/tutorials/api.md +++ b/docs/content/pages/tutorials/api.md @@ -52,9 +52,14 @@ public interface INewHorizons UnityEvent GetBodyLoadedEvent(); /// - /// Gets an object in the `extras` object of a config, returns null if the key doesn't exist + /// Gets an object in the `extras` object of a body config, returns null if the key doesn't exist /// - object GetExtraModule(Type moduleType, string extrasModuleName, string planetName); + object GetExtraModuleForBody(Type moduleType, string extrasModuleName, string planetName); + + /// + /// Gets an object in the `extras` object of a system config, returns null if the key doesn't exist + /// + object GetExtraModuleForSystem(Type moduleType, string extrasModuleName, string systemName); /// /// Allows you to overwrite the default system. This is where the player is respawned after dying. diff --git a/docs/content/pages/tutorials/extending.md b/docs/content/pages/tutorials/extending.md index 67e7e5ca..406b5e0a 100644 --- a/docs/content/pages/tutorials/extending.md +++ b/docs/content/pages/tutorials/extending.md @@ -4,8 +4,6 @@ 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. @@ -25,9 +23,9 @@ Addon developers will add a key to the `extras` object in the root of the config } ``` -**It's up to the addon dev to list your mod as a dependency!** +Your mod will then use the API's `GetExtraModuleForBody` method to obtain the `myCoolExtensionData` object. -Your mod will then use the API's `GetExtraModule` method to obtain the `myCoolExtensionData` object. +**It's up to the addon dev to list your mod as a dependency!** ## Extending Planets @@ -48,13 +46,13 @@ public class MyCoolExtensionData { } ``` -Then, use the `GetExtraModule` method: +Then, use the `GetExtraModuleForBody` method: ```cs var api = ModHelper.Interactions.TryGetModApi("xen.NewHorizons"); api.GetBodyLoadedEvent().AddListener((name) => { ModHelper.Console.WriteLine($"Body: {name} Loaded!"); - var potentialData = api.GetExtraModule(typeof(MyCoolExtensionData), "myCoolExtensionData", name); + var potentialData = api.GetExtraModuleForBody(typeof(MyCoolExtensionData), "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}!"); @@ -64,4 +62,4 @@ api.GetBodyLoadedEvent().AddListener((name) => { ## Extending Systems - +Extending systems is the exact same as extending planets, except you use the `GetExtraModuleForSystem` method instead. From 7cc26d66d972638044f7a7cd7d338859ffe8f4d7 Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 17:34:55 -0400 Subject: [PATCH 21/51] Add path for funny --- NewHorizons/Main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 97ba6124..8c9a7b34 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -618,7 +618,7 @@ namespace NewHorizons starSystemConfig.Migrate(); starSystemConfig.FixCoordinates(); - var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, "", mod); + var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, $"systems/{config.starSystem}.json", mod); SystemDict.Add(config.starSystem, system); From 1fcf21ff0e8134c4ae22c3339d6f420a89a01280 Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 17:38:00 -0400 Subject: [PATCH 22/51] Do da stanky leg 2 --- NewHorizons/Main.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 8c9a7b34..a975b0ba 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -618,7 +618,7 @@ namespace NewHorizons starSystemConfig.Migrate(); starSystemConfig.FixCoordinates(); - var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, $"systems/{config.starSystem}.json", mod); + var system = new NewHorizonsSystem(config.starSystem, starSystemConfig, $"", mod); SystemDict.Add(config.starSystem, system); From c31064c68ed7a1b7346e12bb0cea38f7601cdbfa Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 18:47:53 -0400 Subject: [PATCH 23/51] What if I like changed it LOL!!! --- NewHorizons/Handlers/PlanetCreationHandler.cs | 11 ++++++- NewHorizons/INewHorizons.cs | 8 ++--- NewHorizons/NewHorizonsApi.cs | 32 +++++++++---------- docs/content/pages/tutorials/api.md | 8 ++--- docs/content/pages/tutorials/extending.md | 23 +++++++++---- 5 files changed, 50 insertions(+), 32 deletions(-) diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index fcb82a02..4147fdb9 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -248,7 +248,16 @@ namespace NewHorizons.Handlers } } } - Main.Instance.OnPlanetLoaded?.Invoke(body.Config.name); + + 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.Message} : {e.StackTrace}"); + } + return true; } diff --git a/NewHorizons/INewHorizons.cs b/NewHorizons/INewHorizons.cs index aa014ea6..8f132ee7 100644 --- a/NewHorizons/INewHorizons.cs +++ b/NewHorizons/INewHorizons.cs @@ -50,14 +50,14 @@ namespace NewHorizons UnityEvent GetBodyLoadedEvent(); /// - /// Gets an object in the `extras` object of a body config, returns null if the key doesn't exist + /// Uses JSONPath to query a body /// - object GetExtraModuleForBody(Type moduleType, string extrasModuleName, string planetName); + object QueryBody(Type outType, string bodyName, string path); /// - /// Gets an object in the `extras` object of a system config, returns null if the key doesn't exist + /// Uses JSONPath to query a system /// - object GetExtraModuleForSystem(Type moduleType, string extrasModuleName, string systemName); + 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/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs index e8e36a24..0ac47b68 100644 --- a/NewHorizons/NewHorizonsApi.cs +++ b/NewHorizons/NewHorizonsApi.cs @@ -8,6 +8,8 @@ using System.Collections.Generic; using System.Data.SqlTypes; using System.IO; using System.Linq; +using System.Reflection; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; using UnityEngine.Events; @@ -101,42 +103,40 @@ namespace NewHorizons } } - private object GetExtraModule(Type moduleType, string key, string path) + private static object QueryJson(Type outType, string filePath, string jsonPath) { - if (path == "") return null; + if (filePath == "") return null; try { - var jsonText = File.ReadAllText(path); + var jsonText = File.ReadAllText(filePath); var jsonData = JObject.Parse(jsonText); - var possibleExtras = jsonData.Property("extras")?.Value; - if (possibleExtras is JObject extras) - { - return extras.Property(key)?.Value.ToObject(moduleType); - } - return null; + return jsonData.SelectToken(jsonPath)?.ToObject(outType); } catch (FileNotFoundException) { return null; } + catch (JsonException e) + { + Logger.LogError($"{e.Message} : {e.StackTrace}"); + return null; + } } - public object GetExtraModuleForBody(Type moduleType, string extraModuleKey, string planetName) + public object QueryBody(Type outType, string bodyName, string jsonPath) { - var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == planetName); + var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == bodyName); return planet == null ? null - : GetExtraModule(moduleType, extraModuleKey, - planet.Mod.ModHelper.Manifest.ModFolderPath + planet.RelativePath); + : QueryJson(outType, planet.Mod.ModHelper.Manifest.ModFolderPath + planet.RelativePath, jsonPath); } - public object GetExtraModuleForSystem(Type moduleType, string extraModuleKey, string systemName) + public object QuerySystem(Type outType, string jsonPath) { var system = Main.SystemDict[Main.Instance.CurrentStarSystem]; return system == null ? null - : GetExtraModule(moduleType, extraModuleKey, - system.Mod.ModHelper.Manifest.ModFolderPath + system.RelativePath); + : QueryJson(outType, system.Mod.ModHelper.Manifest.ModFolderPath + system.RelativePath, jsonPath); } public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, diff --git a/docs/content/pages/tutorials/api.md b/docs/content/pages/tutorials/api.md index 0400351f..5c7a8996 100644 --- a/docs/content/pages/tutorials/api.md +++ b/docs/content/pages/tutorials/api.md @@ -52,14 +52,14 @@ public interface INewHorizons UnityEvent GetBodyLoadedEvent(); /// - /// Gets an object in the `extras` object of a body config, returns null if the key doesn't exist + /// Uses JSONPath to query a body /// - object GetExtraModuleForBody(Type moduleType, string extrasModuleName, string planetName); + object QueryBody(Type outType, string bodyName, string path); /// - /// Gets an object in the `extras` object of a system config, returns null if the key doesn't exist + /// Uses JSONPath to query a system /// - object GetExtraModuleForSystem(Type moduleType, string extrasModuleName, string systemName); + 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/docs/content/pages/tutorials/extending.md b/docs/content/pages/tutorials/extending.md index 406b5e0a..7c3b4ec4 100644 --- a/docs/content/pages/tutorials/extending.md +++ b/docs/content/pages/tutorials/extending.md @@ -23,7 +23,7 @@ Addon developers will add a key to the `extras` object in the root of the config } ``` -Your mod will then use the API's `GetExtraModuleForBody` method to obtain the `myCoolExtensionData` object. +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!** @@ -31,7 +31,7 @@ Your mod will then use the API's `GetExtraModuleForBody` method to obtain the `m You can extend all planets by hooking into the `OnBodyLoaded` event of the API: -```cs +```csharp var api = ModHelper.Interactions.TryGetModApi("xen.NewHorizons"); api.GetBodyLoadedEvent().AddListener((name) => { ModHelper.Console.WriteLine($"Body: {name} Loaded!"); @@ -40,19 +40,19 @@ api.GetBodyLoadedEvent().AddListener((name) => { In order to get your extra module, first define the module as a class: -```cs +```csharp public class MyCoolExtensionData { int myCoolExtensionProperty; } ``` -Then, use the `GetExtraModuleForBody` method: +Then, use the `QueryBody` method: -```cs +```csharp var api = ModHelper.Interactions.TryGetModApi("xen.NewHorizons"); api.GetBodyLoadedEvent().AddListener((name) => { ModHelper.Console.WriteLine($"Body: {name} Loaded!"); - var potentialData = api.GetExtraModuleForBody(typeof(MyCoolExtensionData), "myCoolExtensionData", name); + 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}!"); @@ -62,4 +62,13 @@ api.GetBodyLoadedEvent().AddListener((name) => { ## Extending Systems -Extending systems is the exact same as extending planets, except you use the `GetExtraModuleForSystem` method instead. +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 = NHAPI.QueryBody(typeof(string), "Wetrock", "$.Orbit.primaryBody"); + ModHelper.Console.WriteLine($"Primary of {bodyName} is {primaryBody ?? "NULL"}!"); +``` From a9a9b20e31ae35e54717bbb2f536580c6f86723b Mon Sep 17 00:00:00 2001 From: Ben C Date: Mon, 29 Aug 2022 18:55:58 -0400 Subject: [PATCH 24/51] Fix name inconsistency in docs --- docs/content/pages/tutorials/extending.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/pages/tutorials/extending.md b/docs/content/pages/tutorials/extending.md index 7c3b4ec4..0e9b30bc 100644 --- a/docs/content/pages/tutorials/extending.md +++ b/docs/content/pages/tutorials/extending.md @@ -69,6 +69,6 @@ Extending systems is the exact same as extending planets, except you use the `Qu You can also use the `QueryBody` method to get values of the config outside of your extension object ```csharp -var primaryBody = NHAPI.QueryBody(typeof(string), "Wetrock", "$.Orbit.primaryBody"); +var primaryBody = api.QueryBody(typeof(string), "Wetrock", "$.Orbit.primaryBody"); ModHelper.Console.WriteLine($"Primary of {bodyName} is {primaryBody ?? "NULL"}!"); ``` From 5c04f456b6af51d3fc6bfa19ff3c658ffc48a8b5 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Mon, 29 Aug 2022 20:51:41 -0400 Subject: [PATCH 25/51] Just log the exception entirely so that it includes inner exceptions --- NewHorizons/Handlers/PlanetCreationHandler.cs | 2 +- NewHorizons/NewHorizonsApi.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index 4147fdb9..ba0ea8f8 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -255,7 +255,7 @@ namespace NewHorizons.Handlers } catch (Exception e) { - Logger.LogError($"Error in event handler for OnPlanetLoaded on body {body.Config.name}: {e.Message} : {e.StackTrace}"); + Logger.LogError($"Error in event handler for OnPlanetLoaded on body {body.Config.name}: {e}"); } return true; diff --git a/NewHorizons/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs index 0ac47b68..2c551f59 100644 --- a/NewHorizons/NewHorizonsApi.cs +++ b/NewHorizons/NewHorizonsApi.cs @@ -118,7 +118,7 @@ namespace NewHorizons } catch (JsonException e) { - Logger.LogError($"{e.Message} : {e.StackTrace}"); + Logger.LogError(e.ToString()); return null; } } From 11dc7551d8b369f5d56b0ca268bd7d6f0a156277 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Mon, 29 Aug 2022 21:14:20 -0400 Subject: [PATCH 26/51] Remove unused namespaces --- NewHorizons/NewHorizonsApi.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/NewHorizons/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs index 2c551f59..4924834f 100644 --- a/NewHorizons/NewHorizonsApi.cs +++ b/NewHorizons/NewHorizonsApi.cs @@ -5,10 +5,8 @@ using OWML.Common; using OWML.Utils; using System; using System.Collections.Generic; -using System.Data.SqlTypes; using System.IO; using System.Linq; -using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEngine; From 192c22733c919d5c3134fb4da0a70fc12580f285 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 29 Aug 2022 22:22:37 -0400 Subject: [PATCH 27/51] GetProfileName method (QSB compat) --- NewHorizons/External/NewHorizonsData.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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?"); From 0b1325ab85f5b5613aee33926286f343dc725d55 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 29 Aug 2022 22:37:12 -0400 Subject: [PATCH 28/51] Instead of killing player just freeze --- NewHorizons/Main.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index cb64f092..09d20227 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -647,6 +647,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; @@ -658,9 +665,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") @@ -678,12 +682,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 From 83c562c4a4037c9e262503970aa7ac6f5fcab877 Mon Sep 17 00:00:00 2001 From: TerrificTrifid <99054745+TerrificTrifid@users.noreply.github.com> Date: Mon, 29 Aug 2022 21:38:12 -0500 Subject: [PATCH 29/51] Make rain/snow heights more robust --- .../Builder/Atmosphere/EffectsBuilder.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) 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; From 2d03fe04a42efd2ec90903c957beb1730bbb557c Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 30 Aug 2022 00:48:39 -0400 Subject: [PATCH 30/51] No - for image key --- NewHorizons/OtherMods/OWRichPresence/RichPresenceHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 12726c97bb536c6486bd277133f021a4d1e9b3eb Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 30 Aug 2022 02:44:40 -0400 Subject: [PATCH 31/51] Add star atmosphere and fog to proxy --- NewHorizons/Builder/Body/ProxyBuilder.cs | 20 +++++-- NewHorizons/Builder/Body/StarBuilder.cs | 57 +++++++++++++++---- .../Builder/Body/StellarRemnantBuilder.cs | 4 +- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/NewHorizons/Builder/Body/ProxyBuilder.cs b/NewHorizons/Builder/Body/ProxyBuilder.cs index 6c823c20..4e028acf 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); From 3ecd8e745a7255337c05611f25c5a74701daff55 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 30 Aug 2022 03:14:08 -0400 Subject: [PATCH 32/51] Loop de Loop --- NewHorizons/Builder/Props/AudioVolumeBuilder.cs | 2 +- NewHorizons/External/Modules/PropModule.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/NewHorizons/Builder/Props/AudioVolumeBuilder.cs b/NewHorizons/Builder/Props/AudioVolumeBuilder.cs index 8e45efc8..9c5d0acc 100644 --- a/NewHorizons/Builder/Props/AudioVolumeBuilder.cs +++ b/NewHorizons/Builder/Props/AudioVolumeBuilder.cs @@ -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/External/Modules/PropModule.cs b/NewHorizons/External/Modules/PropModule.cs index 6ed70204..163040a0 100644 --- a/NewHorizons/External/Modules/PropModule.cs +++ b/NewHorizons/External/Modules/PropModule.cs @@ -840,6 +840,11 @@ namespace NewHorizons.External.Modules /// The audio track of this audio volume /// [DefaultValue("environment")] public AudioMixerTrackName track = AudioMixerTrackName.Environment; + + /// + /// Whether to loop this audio while in this audio volume + /// + [DefaultValue(true)] public bool loop = true; } [JsonObject] From e64ad0f9efcf8bea31c0d0c0fb2485865e4fa6ed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 30 Aug 2022 07:17:30 +0000 Subject: [PATCH 33/51] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index bc88b9f7..5f638234 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -1822,6 +1822,11 @@ "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", + "default": true } } }, From 11b3381ac291ded01dfc55f403d842f1fab43309 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 30 Aug 2022 03:13:45 -0400 Subject: [PATCH 34/51] Stop dev tools from deleting extras --- NewHorizons/External/Configs/PlanetConfig.cs | 5 +++++ NewHorizons/External/Configs/StarSystemConfig.cs | 5 +++++ SchemaExporter/SchemaExporter.cs | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/NewHorizons/External/Configs/PlanetConfig.cs b/NewHorizons/External/Configs/PlanetConfig.cs index 4a353011..2b071244 100644 --- a/NewHorizons/External/Configs/PlanetConfig.cs +++ b/NewHorizons/External/Configs/PlanetConfig.cs @@ -169,6 +169,11 @@ namespace NewHorizons.External.Configs /// public WaterModule Water; + /// + /// Extra data that may be used by extension mods + /// + public object extras; + public PlanetConfig() { // Always have to have a base module 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/SchemaExporter/SchemaExporter.cs b/SchemaExporter/SchemaExporter.cs index 33c12666..c2e94e25 100644 --- a/SchemaExporter/SchemaExporter.cs +++ b/SchemaExporter/SchemaExporter.cs @@ -92,7 +92,7 @@ public static class SchemaExporter if (_title is "Star System Schema" or "Celestial Body Schema") { - schema.Properties.Add("extras", new JsonSchemaProperty { + schema.Properties["extras"] = new JsonSchemaProperty { Type = JsonObjectType.Object, Description = "Extra data that may be used by extension mods", AllowAdditionalProperties = true, @@ -100,7 +100,7 @@ public static class SchemaExporter { Type = JsonObjectType.Object } - }); + }; } return schema; From 5f2beb7aee7e5c949562717d7a3783f1e29175b7 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Tue, 30 Aug 2022 03:21:03 -0400 Subject: [PATCH 35/51] or just play it once --- NewHorizons/External/Modules/PropModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/External/Modules/PropModule.cs b/NewHorizons/External/Modules/PropModule.cs index 163040a0..f4275a6e 100644 --- a/NewHorizons/External/Modules/PropModule.cs +++ b/NewHorizons/External/Modules/PropModule.cs @@ -842,7 +842,7 @@ namespace NewHorizons.External.Modules [DefaultValue("environment")] public AudioMixerTrackName track = AudioMixerTrackName.Environment; /// - /// Whether to loop this audio while in this audio volume + /// Whether to loop this audio while in this audio volume or just play it once /// [DefaultValue(true)] public bool loop = true; } From 9709601048decd8c64acb56d17873458b8fe97b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 30 Aug 2022 07:22:48 +0000 Subject: [PATCH 36/51] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 8 ++++---- NewHorizons/Schemas/star_system_schema.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 02771c9b..3783d7e7 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -128,16 +128,16 @@ "description": "Add water to this planet", "$ref": "#/definitions/WaterModule" }, - "$schema": { - "type": "string", - "description": "The schema to validate with" - }, "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" } }, "definitions": { diff --git a/NewHorizons/Schemas/star_system_schema.json b/NewHorizons/Schemas/star_system_schema.json index d8073db0..032fb8f1 100644 --- a/NewHorizons/Schemas/star_system_schema.json +++ b/NewHorizons/Schemas/star_system_schema.json @@ -71,16 +71,16 @@ "$ref": "#/definitions/CuriosityColorInfo" } }, - "$schema": { - "type": "string", - "description": "The schema to validate with" - }, "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" } }, "definitions": { From 4f15d21592c3d51a771ec332294a7f376a927dc9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 30 Aug 2022 07:23:24 +0000 Subject: [PATCH 37/51] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 5f638234..9b3db160 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -1825,7 +1825,7 @@ }, "loop": { "type": "boolean", - "description": "Whether to loop this audio while in this audio volume", + "description": "Whether to loop this audio while in this audio volume or just play it once", "default": true } } From 890b496e7ab5f88a8dd0695a4a085b0fc96c729a Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Wed, 31 Aug 2022 03:09:13 -0400 Subject: [PATCH 38/51] Add notification volumes --- .../Props/NotificationVolumeBuilder.cs | 43 ++++++ NewHorizons/Builder/Props/PropBuildManager.cs | 7 + NewHorizons/Components/NotificationVolume.cs | 129 ++++++++++++++++++ NewHorizons/External/Modules/PropModule.cs | 57 ++++++++ NewHorizons/Schemas/body_schema.json | 65 +++++++++ 5 files changed, 301 insertions(+) create mode 100644 NewHorizons/Builder/Props/NotificationVolumeBuilder.cs create mode 100644 NewHorizons/Components/NotificationVolume.cs diff --git a/NewHorizons/Builder/Props/NotificationVolumeBuilder.cs b/NewHorizons/Builder/Props/NotificationVolumeBuilder.cs new file mode 100644 index 00000000..eabb1519 --- /dev/null +++ b/NewHorizons/Builder/Props/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.Props +{ + public static class NotificationVolumeBuilder + { + public static NHNotificationVolume Make(GameObject planetGO, Sector sector, PropModule.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/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs index 8f442041..1c5afe4b 100644 --- a/NewHorizons/Builder/Props/PropBuildManager.cs +++ b/NewHorizons/Builder/Props/PropBuildManager.cs @@ -235,6 +235,13 @@ namespace NewHorizons.Builder.Props } } } + if (config.Props.notificationVolumes != null) + { + foreach (var notificationVolume in config.Props.notificationVolumes) + { + NotificationVolumeBuilder.Make(go, sector, notificationVolume, mod); + } + } } } } diff --git a/NewHorizons/Components/NotificationVolume.cs b/NewHorizons/Components/NotificationVolume.cs new file mode 100644 index 00000000..99a35705 --- /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(NewHorizons.External.Modules.PropModule.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/External/Modules/PropModule.cs b/NewHorizons/External/Modules/PropModule.cs index f4275a6e..9471df4c 100644 --- a/NewHorizons/External/Modules/PropModule.cs +++ b/NewHorizons/External/Modules/PropModule.cs @@ -98,6 +98,11 @@ namespace NewHorizons.External.Modules /// public RemoteInfo[] remotes; + /// + /// Add notification volumes to this planet + /// + public NotificationVolumeInfo[] notificationVolumes; + [JsonObject] public class ScatterInfo { @@ -1006,6 +1011,58 @@ namespace NewHorizons.External.Modules public string rename; } } + + [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, + } + } } [JsonConverter(typeof(StringEnumConverter))] diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 4e56aedb..9b9bbb0d 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -1001,6 +1001,13 @@ "items": { "$ref": "#/definitions/RemoteInfo" } + }, + "notificationVolumes": { + "type": "array", + "description": "Add notification volumes to this planet", + "items": { + "$ref": "#/definitions/NotificationVolumeInfo" + } } } }, @@ -2090,6 +2097,64 @@ } } }, + "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 + } + } + }, "ReferenceFrameModule": { "type": "object", "additionalProperties": false, From 94a304ffb54abf015e134f150c8af11bb661d01d Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Wed, 31 Aug 2022 04:43:25 -0400 Subject: [PATCH 39/51] Allow there to be no shock layer for atmospheres. --- NewHorizons/Builder/Atmosphere/AirBuilder.cs | 19 +++++++++++-------- .../External/Modules/AtmosphereModule.cs | 5 +++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/NewHorizons/Builder/Atmosphere/AirBuilder.cs b/NewHorizons/Builder/Atmosphere/AirBuilder.cs index 7b5ab84d..7b961d14 100644 --- a/NewHorizons/Builder/Atmosphere/AirBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/AirBuilder.cs @@ -23,15 +23,18 @@ 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; + if (config.Atmosphere.hasShockLayer) + { + // 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; + 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) { 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. /// From 66a08c7468a2b81fccd9dcf4d66fca4cd94a26f7 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Wed, 31 Aug 2022 09:51:38 -0400 Subject: [PATCH 40/51] Forgot to move this --- NewHorizons/Builder/Atmosphere/AirBuilder.cs | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/NewHorizons/Builder/Atmosphere/AirBuilder.cs b/NewHorizons/Builder/Atmosphere/AirBuilder.cs index 7b961d14..209a2b26 100644 --- a/NewHorizons/Builder/Atmosphere/AirBuilder.cs +++ b/NewHorizons/Builder/Atmosphere/AirBuilder.cs @@ -34,20 +34,20 @@ namespace NewHorizons.Builder.Atmosphere 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; + 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; + shockLayerRuleset._innerRadius = (bottom + top) / 2f; + shockLayerRuleset._outerRadius = top; + } } if (config.Atmosphere.hasOxygen) From ef5e08445511d8fa5f298b02cfaf16393733725d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 Aug 2022 13:54:20 +0000 Subject: [PATCH 41/51] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 4e56aedb..c0be4ee4 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -309,6 +309,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.", From 4ae90dcc25ddf5161e113717ef74653adbaebe9b Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Wed, 31 Aug 2022 13:10:20 -0400 Subject: [PATCH 42/51] Reorganize to a volume module --- NewHorizons/Builder/Props/PropBuildManager.cs | 28 --- NewHorizons/Builder/ShipLog/RevealBuilder.cs | 18 +- .../{Props => Volumes}/AudioVolumeBuilder.cs | 4 +- .../NotificationVolumeBuilder.cs | 4 +- .../Builder/Volumes/VolumesBuildManager.cs | 47 +++++ NewHorizons/Components/NotificationVolume.cs | 2 +- NewHorizons/External/Configs/PlanetConfig.cs | 19 ++ NewHorizons/External/Modules/PropModule.cs | 168 +--------------- NewHorizons/External/Modules/VolumesModule.cs | 183 ++++++++++++++++++ NewHorizons/Handlers/PlanetCreationHandler.cs | 6 + 10 files changed, 272 insertions(+), 207 deletions(-) rename NewHorizons/Builder/{Props => Volumes}/AudioVolumeBuilder.cs (94%) rename NewHorizons/Builder/{Props => Volumes}/NotificationVolumeBuilder.cs (93%) create mode 100644 NewHorizons/Builder/Volumes/VolumesBuildManager.cs create mode 100644 NewHorizons/External/Modules/VolumesModule.cs diff --git a/NewHorizons/Builder/Props/PropBuildManager.cs b/NewHorizons/Builder/Props/PropBuildManager.cs index 1c5afe4b..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) @@ -235,13 +214,6 @@ namespace NewHorizons.Builder.Props } } } - if (config.Props.notificationVolumes != null) - { - foreach (var notificationVolume in config.Props.notificationVolumes) - { - NotificationVolumeBuilder.Make(go, sector, notificationVolume, mod); - } - } } } } 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 94% rename from NewHorizons/Builder/Props/AudioVolumeBuilder.cs rename to NewHorizons/Builder/Volumes/AudioVolumeBuilder.cs index 9c5d0acc..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); diff --git a/NewHorizons/Builder/Props/NotificationVolumeBuilder.cs b/NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs similarity index 93% rename from NewHorizons/Builder/Props/NotificationVolumeBuilder.cs rename to NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs index eabb1519..c91e4161 100644 --- a/NewHorizons/Builder/Props/NotificationVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/NotificationVolumeBuilder.cs @@ -11,11 +11,11 @@ using UnityEngine; using Logger = NewHorizons.Utility.Logger; using NHNotificationVolume = NewHorizons.Components.NotificationVolume; -namespace NewHorizons.Builder.Props +namespace NewHorizons.Builder.Volumes { public static class NotificationVolumeBuilder { - public static NHNotificationVolume Make(GameObject planetGO, Sector sector, PropModule.NotificationVolumeInfo info, IModBehaviour mod) + public static NHNotificationVolume Make(GameObject planetGO, Sector sector, VolumesModule.NotificationVolumeInfo info, IModBehaviour mod) { var go = new GameObject("NotificationVolume"); go.SetActive(false); diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs new file mode 100644 index 00000000..5197aaf4 --- /dev/null +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -0,0 +1,47 @@ +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, 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); + } + } + } + } +} diff --git a/NewHorizons/Components/NotificationVolume.cs b/NewHorizons/Components/NotificationVolume.cs index 99a35705..2dc81ed1 100644 --- a/NewHorizons/Components/NotificationVolume.cs +++ b/NewHorizons/Components/NotificationVolume.cs @@ -32,7 +32,7 @@ namespace NewHorizons.Components public void SetPinned(bool pin) => _pin = pin; - public void SetTarget(NewHorizons.External.Modules.PropModule.NotificationVolumeInfo.NotificationTarget target) => SetTarget(EnumUtils.Parse(target.ToString(), NotificationTarget.All)); + public void SetTarget(External.Modules.VolumesModule.NotificationVolumeInfo.NotificationTarget target) => SetTarget(EnumUtils.Parse(target.ToString(), NotificationTarget.All)); public void SetTarget(NotificationTarget target) => _target = target; diff --git a/NewHorizons/External/Configs/PlanetConfig.cs b/NewHorizons/External/Configs/PlanetConfig.cs index 2b071244..4ea8a886 100644 --- a/NewHorizons/External/Configs/PlanetConfig.cs +++ b/NewHorizons/External/Configs/PlanetConfig.cs @@ -169,6 +169,11 @@ 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 /// @@ -317,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/Modules/PropModule.cs b/NewHorizons/External/Modules/PropModule.cs index 9471df4c..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,10 +88,9 @@ namespace NewHorizons.External.Modules /// public RemoteInfo[] remotes; - /// - /// Add notification volumes to this planet - /// - public NotificationVolumeInfo[] notificationVolumes; + [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 @@ -438,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 { @@ -823,35 +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; - - /// - /// Whether to loop this audio while in this audio volume or just play it once - /// - [DefaultValue(true)] public bool loop = true; - } - [JsonObject] public class RemoteInfo { @@ -1011,78 +922,5 @@ namespace NewHorizons.External.Modules public string rename; } } - - [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, - } - } - } - - [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..25bba3a5 --- /dev/null +++ b/NewHorizons/External/Modules/VolumesModule.cs @@ -0,0 +1,183 @@ +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 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, + } + } + } + + [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/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index ba0ea8f8..f30c4778 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; @@ -608,6 +609,11 @@ namespace NewHorizons.Handlers PropBuildManager.Make(go, sector, rb, body.Config, body.Mod); } + if (body.Config.Volumes != null) + { + VolumesBuildManager.Make(go, sector, body.Config, body.Mod); + } + if (body.Config.Funnel != null) { FunnelBuilder.Make(go, go.GetComponentInChildren(), rb, body.Config.Funnel); From 0921b23f8fec1013a1c1b0a00b011d7a7c1a4810 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 Aug 2022 17:28:38 +0000 Subject: [PATCH 43/51] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 416 ++++++++++++++------------- 1 file changed, 213 insertions(+), 203 deletions(-) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 9b9bbb0d..a032ac40 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -128,6 +128,10 @@ "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", @@ -932,13 +936,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", @@ -981,13 +978,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", @@ -1001,13 +991,6 @@ "items": { "$ref": "#/definitions/RemoteInfo" } - }, - "notificationVolumes": { - "type": "array", - "description": "Add notification volumes to this planet", - "items": { - "$ref": "#/definitions/NotificationVolumeInfo" - } } } }, @@ -1351,61 +1334,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, @@ -1815,75 +1743,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" - }, - "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" - ] - }, "SignalInfo": { "type": "object", "additionalProperties": false, @@ -2097,64 +1956,6 @@ } } }, - "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 - } - } - }, "ReferenceFrameModule": { "type": "object", "additionalProperties": false, @@ -2580,6 +2381,215 @@ "$ref": "#/definitions/MColor" } } + }, + "VolumesModule": { + "type": "object", + "additionalProperties": false, + "properties": { + "audioVolumes": { + "type": "array", + "description": "Add audio volumes to this planet", + "items": { + "$ref": "#/definitions/AudioVolumeInfo" + } + }, + "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" + ] + }, + "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": { From 2f7eb0c6957dc1b263b1d662ab64a8207a02b9b5 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Wed, 31 Aug 2022 13:44:35 -0400 Subject: [PATCH 44/51] Add hazard volumes --- .../Builder/Volumes/HazardVolumeBuilder.cs | 40 +++++++++++++ .../Builder/Volumes/VolumesBuildManager.cs | 9 ++- NewHorizons/External/Modules/VolumesModule.cs | 60 +++++++++++++++++++ NewHorizons/Handlers/PlanetCreationHandler.cs | 2 +- 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs diff --git a/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs b/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs new file mode 100644 index 00000000..2f35c6ab --- /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.firstContactDamage.ToString(), InstantDamageType.Impact); + hazardVolume._firstContactDamage = info.firstContactDamage; + + go.SetActive(true); + + return hazardVolume; + } + } +} diff --git a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs index 5197aaf4..f7a664b9 100644 --- a/NewHorizons/Builder/Volumes/VolumesBuildManager.cs +++ b/NewHorizons/Builder/Volumes/VolumesBuildManager.cs @@ -12,7 +12,7 @@ namespace NewHorizons.Builder.Volumes { public static class VolumesBuildManager { - public static void Make(GameObject go, Sector sector, PlanetConfig config, IModBehaviour mod) + public static void Make(GameObject go, Sector sector, OWRigidbody planetBody, PlanetConfig config, IModBehaviour mod) { if (config.Volumes.revealVolumes != null) { @@ -42,6 +42,13 @@ namespace NewHorizons.Builder.Volumes 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/External/Modules/VolumesModule.cs b/NewHorizons/External/Modules/VolumesModule.cs index 25bba3a5..fe8d29fb 100644 --- a/NewHorizons/External/Modules/VolumesModule.cs +++ b/NewHorizons/External/Modules/VolumesModule.cs @@ -19,6 +19,11 @@ namespace NewHorizons.External.Modules /// public AudioVolumeInfo[] audioVolumes; + /// + /// Add hazard volumes to this planet + /// + public HazardVolumeInfo[] hazardVolumes; + /// /// Add notification volumes to this planet /// @@ -158,6 +163,61 @@ namespace NewHorizons.External.Modules [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))] diff --git a/NewHorizons/Handlers/PlanetCreationHandler.cs b/NewHorizons/Handlers/PlanetCreationHandler.cs index f30c4778..67700c36 100644 --- a/NewHorizons/Handlers/PlanetCreationHandler.cs +++ b/NewHorizons/Handlers/PlanetCreationHandler.cs @@ -611,7 +611,7 @@ namespace NewHorizons.Handlers if (body.Config.Volumes != null) { - VolumesBuildManager.Make(go, sector, body.Config, body.Mod); + VolumesBuildManager.Make(go, sector, rb, body.Config, body.Mod); } if (body.Config.Funnel != null) From 1158863bb4ab36283d95fafcb81498544bbb8bc6 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Wed, 31 Aug 2022 13:47:17 -0400 Subject: [PATCH 45/51] Oops --- NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs | 2 +- NewHorizons/External/Modules/VolumesModule.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs b/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs index 2f35c6ab..67c6f22c 100644 --- a/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs +++ b/NewHorizons/Builder/Volumes/HazardVolumeBuilder.cs @@ -29,7 +29,7 @@ namespace NewHorizons.Builder.Volumes hazardVolume._attachedBody = owrb; hazardVolume._type = EnumUtils.Parse(info.type.ToString(), HazardVolume.HazardType.GENERAL); hazardVolume._damagePerSecond = info.damagePerSecond; - hazardVolume._firstContactDamageType = EnumUtils.Parse(info.firstContactDamage.ToString(), InstantDamageType.Impact); + hazardVolume._firstContactDamageType = EnumUtils.Parse(info.firstContactDamageType.ToString(), InstantDamageType.Impact); hazardVolume._firstContactDamage = info.firstContactDamage; go.SetActive(true); diff --git a/NewHorizons/External/Modules/VolumesModule.cs b/NewHorizons/External/Modules/VolumesModule.cs index fe8d29fb..43059fbf 100644 --- a/NewHorizons/External/Modules/VolumesModule.cs +++ b/NewHorizons/External/Modules/VolumesModule.cs @@ -190,7 +190,7 @@ namespace NewHorizons.External.Modules /// /// The type of damage you will take when you first touch this volume. /// - [DefaultValue("impact")] public InstantDamageType _firstContactDamageType = InstantDamageType.Impact; + [DefaultValue("impact")] public InstantDamageType firstContactDamageType = InstantDamageType.Impact; /// /// The amount of damage you will take when you first touch this volume. From 31014ee2220867bdc6aba000103cc0c40ec799dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 31 Aug 2022 17:49:51 +0000 Subject: [PATCH 46/51] Updated Schemas --- NewHorizons/Schemas/body_schema.json | 81 ++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index a032ac40..85fabcd8 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -2393,6 +2393,13 @@ "$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", @@ -2478,6 +2485,80 @@ "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, From 3528141900c16d82cf0894b2217ff7a05f3dedef Mon Sep 17 00:00:00 2001 From: TerrificTrifid <99054745+TerrificTrifid@users.noreply.github.com> Date: Wed, 31 Aug 2022 16:06:55 -0500 Subject: [PATCH 47/51] Use standard shader for basic clouds --- NewHorizons/Builder/Atmosphere/CloudsBuilder.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) 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; } From fe6577c79e38cdfa13a794f44540df96eb6c5d21 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 31 Aug 2022 21:13:01 -0400 Subject: [PATCH 48/51] Updated NH unity project --- .gitignore | 5 +++++ NewHorizons/Assets/xen.newhorizons | Bin 39856 -> 56871 bytes NewHorizons/Assets/xen.newhorizons.manifest | 23 ++++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 NewHorizons/Assets/xen.newhorizons.manifest 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 84e681f3c4fe832e136c2e1b00c3259e44b31030..1a754842360b55eeb0b2280c696eff4354db9e5d 100644 GIT binary patch literal 56871 zcmV(=K-s@lZfSIRMpFO)000LyE_g0@05UK!IW9CVGB;*1000000002qCjbBdK>z>% zTL1t6LjV8(00000000000000U009880RRLHqyPZjrT{?>00;p90U7}C7ytxAK|(Dz zH8nXmGiG6CGB+|eH8(ghGBPw`W;r-BH8L|dG&48=T>tpuLDBHie8+BotN$-b3U5hd_Cc}>)Uq@2SeAHuJydxg!G9f)_^tN z)qB`%S;Qh39EaAP!h$$x8yFpy)tC0#Zb<<-Y!z+My|J_P&$ObBYGv|)P;z&9bTL00_KXw}OabaG~^ni z2#Ih%mM+;2poejYJUtk8`jlO9Sa>f@0nR+R|4f=*n%PW0@V?K2a0;T zW6vY!EC0HF`;--ksAGTDoP{{^N=gk=NA7M{LP9JYAN91-0_ewQe=p;4a-?ki2vd@=YFWLwNw#o?5>9T z!@)3ZNR=Br$dRX^=hoR8uv-0ext@GVAvpL8HW(sC%z8NUYO7kmwO#|D>?xlKO}FoZ z96f3W{O7!1=MRe)I`5Uct8^I3Vl&RdX=^1GEB>zp_?<1MuDUJoeswPc=vf3bu?vafJ9&Y2g#H_~AdC zZGzgU4O*yS6^X1mS}3N<<-Go4ZMbT6%H=(X?|Ve8<_?C16znNS`Tfjhmrt+`MHHmD zvS!6C8wc1C3Km-Ti<7`y4x!xiYfY=_-a2yfUX{pBjiOiX@*3>tqK=g+ZoOGKbkk_O z6|#W=HDcSu?+YM6{ddm(e<^Gs;^6{qT13KC+VTphu?=E&-0^+ZaE*U$w794<_lJQx z|GF+7Y-UPIkS>1}yh8I5;%TML-38f02)`QVC21K5#?!4u&+Lf&jXf)I6PYNxUap77 z5~JSEXH{%n1+RDdKo0&+v&?OUTvKy<5MGker3s;GzBRPiFYg=9!W!>gwqmp%^$^Aw zP#h_<=kl2i~XUeIXg_E@S{LI&U~<6&g`LJAQ%!UZ{{dZ zWa(TBbJDXn1(u?VWa(lE+*H4-jT}xva~azzf4_4T9s4mk9UlaQQZk>cf{EPWiT0^k zemSZkkdO-{_oU(c0>sN8bm=)Hf#0WRq(_4g)A2p1u#FDlBKWz)u0ILF52#a|8ODDW zUnlMtpB$|I#zzTNz!RO<8wYAo2oX3?*s#XV`i;WGoBT8rW!~!|Zd6WXFAGXvPPLa5 zI44Gye|)UV%iV_{AdO*!sX&IPzaC`};2#@-Tb>DRXqcJF=bRm}(F%iKA=<0e=&}Cy+~(tqk51__-Hz(F4|)byEHBC?cnz$Zc+A?tDO=SN#Su-!g~$jcoUAlS6@y_p z6Qc{kJ9B%`bI1f`h}?Zw^xn;hl!Uxx-K!gs*yl3$C7{pmGbmS*EME9H5qL)TXEG){v^}Kwj z8C4*KtFQDDE#DiEf3O?^2DjjU!vR-Z3v&y|+_{0rh?@in#>?eAq!Te{-zaJt9fnI; z0AR`Ogs$B%z_FA-T0FDyOPWp}-tM%8@-8oQdLf#sW}Fb%CRWrBmKnu0sKCUErLJOG zC(31g+GTP1R`)?lotA`P7|BOfeZy8+j3z!-J!zt>_nY~yX`+JhAsnv*rGASerf?no z@YlGluSRX;$^D;7&W9gV3V93ALy>4qzNz%el3}kCP*>ws4f3VYv(J^)%+&PO=}n;w za{3322b0Vv8tM#*^vf6^%BC#McNR4-umoaV0KTj`wt$&Dgra-pr>9=@oQC*HCz*%) zc!<<}(bu~gm_tmD>M=pzy6zmHMCXSV1Ns(KE*a06;OKB zc&YpT2v@m`r&?mkC+a$*M{5wqXqM+lsX+Dv4MSJfOQ)|DAlp13e6mgg!O6uL<IDn-G29x*CTRJvrDfaq z*fh+iXI+e1w9Q$6p?A&pf6+~$nrtihrO8??y6Z_|-?)jRgzM_4OYb!`;`pf{3$QowjWDm4oGn~^fmaROaNBZ;O6<%-;Yt=fokA1hGS1;~J~ zZUE*qE+=$|ktomr+g>^}1xs^C(H)p_{U*9qn62BzqOoR#6XwjEsy3zoY1<%Y0# z^d#cXgSc>Ru*I}uH z?w}sU;?s}u2_YC`*tjQuV3#|H9{3`Bttuc*zW8Wi)W`0BM~yGe3GD#%Qm?>*2p3Ip z+o>YT*ai*Ns_+6@Oh5I|v<{KCCE9k}&l&I=51T zubx)6i)=XspYS)PgaqF5srW~eYFg!MnF|T$A>L;6j?vIwJ+r&xq8=?u*vT?c3jV_~481BWeB6r%SS;>MrnKOSyHuMnRz5pdkmaAQCULU5? z=YOi9a+lkkZO*%!DR?eC7#Z*Y=+O#Ve#z4D%VBFi@V`Y~G~Z>ub7mlJ`(B+MTF1rW zNr*EwLKW`}1MMvt3g188Hn;aFq78a$k<+5Z{qBJVfG;CX>7mZ;pe`Px&MjqB6isBC zIFKoJ>KRF>K9wF@)8%0a2Bf&ncX*kcQ6Tk{BQjb4>MF-)s^p>S7keC-iVwKb*1Q~O zLXIk09dQ~Gm5I1UI9>bUe$ck|`q1r_B$2>p)am{0Pr<~qt6nR4(C zz{7~bI(%-8xq`q`oCs4uN+Zxfw};8D0bkx#sXOeFk#m{dAoS8`i-F$US@dOS$IEtq z&N7k>x0TbWPOTucd-;v#q9PxJK#># z$2N*`JXbEP)U)v@9Ml%kMRP{VT8*_3ba>W=iXD(MgxP7m0`r2)LlQ6p)m+p!H)0f% zuuhT1bkZ3HGB2H>#+zHo^DBQzu6@GYx1&%*q42;7MLYaic^T%;O?Gsd&Eq3wzhz`u z7P8(wf}Bb|sG(U8%0xzg3f}|oh)1rx3aop51@br#@ulLvZ-TG$sTy8IC34>Ml9Y!p z`0$C=d`jSh10vt>$Ti! z?4B<`h502n@ObyHaq}C+xmM%TnW+>;pg#H}n5Rmkm*b3Bbkf25FbdTGU@6XCI*?D6 ze8?;v!DS4d=l4Skg5Q)ffYJJRxWT(U9hS z;ZbtM(tLGuh1(|BS62Bt7(&o%>3MSQY9$q64T?-=Gg=pd&-V)*d$dWDp2su;>xrIH zLQCXgnXxsBz&?%S&+$Z6tOKw5f%bKmw?8a|!!}XhuJ(c3iBX>~L}s!Nzqj{6q{m@1 zsYjia`fAwWE0bDfmhTXFpVBSw-lv@ZuDS}=OYqrd>pry8V+B}8DBo?eEB@3fSz8*UHxQo(hyoTw1llgq2#bc9q}{x zs~6iiZvOYTDj63x`!5tIg3G)mjfQ-ykM~5#Xy)nK^}98uSAkmrO5{w!Z{%5mA(&Z?K5aEhqH4;C#sTjv zV_OzZdxH(UZKAz|ODh`QtDvro>f-ZwB?~1u=JZ5I=h{*6C#HzqPV% zNL;{Fw>cxZ@pZ=5!R4&NO#)3 z5nI{+vO%ZGxWtd>KM%V0);@|@3hS{UM^(M=glYmR(B8s@D^JX6&b(0PIIGIm5t3-> zVB{^w)wRFNKmf}@FChNalX4EB#q~&#k`-rew{ODBoLvI&kqVWfSxqw)D#^RAv|Not zT}y(1a8&z&4Cy`CQQLK*xYn$+YSmpNyS#OGM5so?rm%;H@PkcLn}M%dY2Va^A&bJJ znw7j@-i@#{8eFYoth{|+Pie)I%a*ngu1YhjRxS@R@?2xyX5eG9PEGC$wz{4|ZOjmx zwPjJPbI$Q_vss`b%n|F>*0BZC;culc23T`vDkNT;JwJ)u5Wq-W$QE=jxQ*od|EVO; zuMm(g5|O{AtSlQyW~s0%LKeSgOL0od{1=#`e@a-5oSU9-$K3CFX&Ze7Hw|IaiO85B z*{t=eR9M&F@WqJg8QnIj{KkYv^&Quj4h`Zih}lux|KWSJma9%n4f zV|5min|gjX_Z6=8u{2->oBM05Z%k=zucvJb z9^Tq=!)ys@cc_jXo|^e@W5GrAC3nBY2+HC_p0}o^EeBpUYV+E-c!F50Gt=;G0Z*h4 zsLODWb%N-7viGC{EbWW;j&1ObT7=CDE+^0S-%g=O_W>fpYPQoulnE^h8B;Z51~*cV zp@|LtGGjerdQ&k;G|ZPSCh>HiuM9;FmgTHRM-9n5vU%6E}@6c}1@7v6Yr&8=7*Sw`Uuc7Zc;e-5=b@ z$X|7tvOrmH?n`$tM>S?5aWEFKWC?J6f@%g5SK8fD7zw<07ff<6L4+F3ZUGgStGhA* z&S(*dn)kwe1`>-S=;B93{fczcs%L`{>py<*sgc@yH%@sJn0|hgVH2zqD>4gIkv<)^ zA<08uDSc~RX6Tmkis9-J$Bl5-tG7c!g;u$}vw)IjP!#mdGSyqBJR-Kpb_Jb-3x3X9 zG$?GE>y*!V(2};vk*O}S-?%H!elOY@#`<2|CDox&aoXcXsOOT7knyyY-K59rjS|%= z?|}9#{z4KRBbH9J38Q73Z4lyojR*56J-0!Mx$zULF$`M`A5CkSVT}~^HC-O%gf%|97S2YKi=LyF~4U-Z(2 zAAwTydp$+!Hgb0>F2WW0K zW~K#;a~pj^>FBxwrwBi2>%JZ|Ck~g1Oxd2UU6fa}cK2sW82rGE`II9-dJ|MeMv>y^ zf36@?)p~RLHKv*#_O~sP%c9Nx>QRO@{IN}*BFdi&OR{-@V_jM|Um4>mK_)p1!B>-W z^+>4p#qtKbe{E(s(F`_p9twQ2@DM{%|A22ge&m9tz2A|}ikAM3o*RF>>#2D_SJe;Z zq5)mXCavy0AiY+bI4;`9i1MVG6PMmLX{MC7k6^2*I}FjmNS0$OqEbtg6aa{9_m6Iz z4dfRTvukE5MThh!L`(Ea71$_d<7b z3IHI>)bNP#K}PRIsEhG!3JxiQ6o9eb57P~q)2}_1=i-%0+%BQShK{pxWsyBH^|FJ% zu2*+6i@{7DXLHrdR2VJE9n?z^(8f?FwJ7liEIeRwUd+!giYwO479=m40GIx5q$S7f z7@1jd$wBA_`%R${5`@oCS^sDLJc|Vl$d*h)!2sA0n290M_D_em1*h&T-QI#|LVWeDy;mz(7c!h+e0LLdD$%_GhqTTxGyNRWFId zTo$3g_PhrVWnLYnpO{DHOLnZ2g2B@*Dr*{_WkDbhPo&>>pHiGJwQB`AG z9EN%$U5`SX)?myc>SdhGlRNTeH3I@Lb3AM@)at7%$ppG8(3|KzhZkzM7h(rxTLFbB zP==Ssl&S97kOO+~T0>LN_HgAeA0*lYMA)$1?DETgVCd@{;a)v2*vpvBXZ; z6H1uK=8;RfOha-U5JhSizwii1Esa!Oa0!~~yD;bK%=!mmS3VU*YoP;1?7#4%h`}fR zt=6FKT&x&P9WL_BFmmJG@dA)3^tSMHns(h0XmN5Vq%o?rVoWi;DLq~z++iyB5bsyS zE7(=5Z*d%<>cZ+8+$C@7-q*C*7UYw{xKoAP4AwfaBat;-F#X;YtCdlyGWWEHfB~?4 z5|~YfA%12TCC{o?+k(zyVNnpCP;4GXp6L)CyXIWW8OE9^2A?QJ@B@uWpX(sZZJSq< z@yaWv(@#$_p;Jjk*e~r)mt;$ip7QnKe(APx%b=n$$mMcgm;5k92iUqd(5J>C%h6lm zLxXPV&_(147PjR$%nPC-#G5X4eE}qBC=^a zp?C?SMgqUyEkl}B!DHHGl%_m{Xy%j=1Nl}e_t&lk&Ocjw__I}sTtl2nK9$eJ zR79;i){Lx(NC@`!{@njw!z!dE!VMkhine7L$2bQch#5L$Avm!(f8mSV|im}bW?V-9{e`4?G1t6C{oIj${GobL1(_bxd9rVto@<`Q5B zXe~+OgyNXI=GvP6kQXq6%f5VigL^aqG3!FAZbU=hZD>}GK3b33_6#(ALmH4p``m-2FEQt`Ff+MTdJQY|6UWH3u6nWb> z@mb|M-^fg(Q{pPBVyWr8 z>)j2HI3=H0 z8Z-!}`XP+eF~0?$+a%)YhTj>+sNxsfNroT?CM+yEe@o%+o@z;|86^RxjwkbUN^^;jXg4}L>bn4^wtg)eWx{rh9?c0|HiSn%`1Hl+tFO+>AXFb|x? z?*GRb1FklB)DO25tZk5}^<03)K^rk*=}u<_F#yIDq4&_c1E$O{UeX-g^|AMHgc%Md zSsYl@fgX}u^SffiN#%vZFt+x}dK4VoOK_6HOO?8y zQv&Y%>S|eR_djEq&-$#-PK)M@QH!)GEjiwOZoAERp2`g?v?9sSL$%(Kh|IBm`=c6Z z!k97?np6=8(&M2;V8nmMhrOilpC};qZabzK6{*+;&@@!IsK$aMG@idD!MHPuhzV`Ap`?@=K2@vKh>YF$WRrJxLW51(Q50fiO+#opmoLr%v?2C_X4RiZmh;yobKBL6pR0vgT zA$p_!Udg9|K#hbpo0R}`=|JPajpstKkop)ugR_Rk%(%5DX6t2+mf_Ih)C(~VY2-pv zFXw-E+%jEuM!5q?*$EHfC`j$9CV;p%L~kyRM>DA zH^`<*!w0NquAdnht#!Z&V=qTs$RyG|4eb;?01}-NsgM>>P|<~W<(nrMX!A7K*5EHj zxVd&r{ieJK$og5|LOM-Wdk&C@sy-QmC0GeS{0>+cWonxly$Lw;WExV9M)12Nx0du&F{E=jHa?VsQ z^cLVX(P3kwY<_U-b098#vYPD8uWPm5NN{({O(PMb2iY^-Y!`QXhf@agpU!qfkR~GR z(Zg|*;m#Nu#w{yi$F&{tdlcU>mfom=d*T4bi^U;ZXp+q1#h;ITRy`Reuwnwou{nO5 zktKs(aa8b135g*n(E)Z^v{$|wPzC4eQ!XgYuuLSXz})?t#I8%eZ!d$qX4=Iozj1$% z|4YZST`t8C(N#J%&Cyx%5ndC}uv&QGjXQ_fo)jx1`4P=w?%s(ENu0OYkQm!jx0Y(Wxpxdkz(?M$| z*V~|QnSv0}wII_Fvxa6Zi|^;6#e$cv(35{>T6C|n`<@6i*l8fJ1km!9fJpk>lCm&T z^i*MU*0p^uxATVr(AF#d9*aB0IBp-QnyDq_fl`r0aiwZs+`aCV%WQ@V8_8F)a)7o7F~MHL_k=Nw7It^HA~=jqwhTn@Kt#5rxa7D6tTu z=`rm7PzXKGx*gm7QQbf!zC9{BQ^{noKh+C`nSQ!lC;VqsNw;MFYzzgv4VhyD%ahG| z-garucBQB!p*%a-jDRQ}iHRKgAlieFjW}AAPMWSr6}=uCeaGnbH2%6CBV;abtS+;RTE;hC~S82V~2*lTi2YR*9wacK@L9blH_%8gK;7h>Ej|dfM;w(vP zmfz2tiV6*QL$=Kcnd7c35_s%rP_2240}wj!s;`IUQ}Ztzn+51966BJVvf6hjAr43M z*m-Ur_j#duh^?L4eH!H#la?JdouhNU@>Tz$c95a-4rtBi8Vs&iXP1Psjj4m5omu- zH3XloH5a?iTN|2Zp4!{npTZc66qq{;KYWzU9_z5s`J21)bJB^Iew~VDE~Z4H1S(Y3 zPtD5(;@+}C1kXK8Y9YO7+Rv^8NOy0dPVeVst+nnWlEZd_i9TB9s-zu zXJwSSX^YV{ZsKK?S)+HW>qDM^3AiSD-l&X5^*KLm9CpuEp?W^_|3pj6F1m|=wAOTw zvPMgfy^Rj|jcTH`qbR{2qcSoLtDufOVOytF-EiS+)-G2Ro)o5XRAwjRQ;9r_ppzvd zx`oDS(T=+UZ#ybL1|Lg>Mxp|O_|44Ssl4ZdEk^7vTk^>)pU;f&ucbc!H(I^oIiVVz z)ED>~ujLRRo~sj#Ig7kBwco`3aS$M<7n zZn){VJUZ)7h|j<}vezjA>1)>-cOwitkBE&GuFnVoj2Z=e_1WHcX@Pp~l@Zpbt!fy4 zxU|+spsHRM6-F4GGTHJyc!s;cJ9aMke;c5ujIP%~31_{~jLBr`9B&Sx?_6zu0pD%C}oM4F|~k6^+0##x4o|v%r_OI zOOByO-~M71zd3_`2!?y^53*IoRzckk7x*?$|9|df#p^k=I_Y7KtwjS>8`$&Gk{u*K zc3&*}e1gx@QZsrR;`kejI2Gy|QeREj3e(HvszcbrPABr`c~L{t&eXNc=JVw)KYqGd zDvGC84)`H2q(6A}{dgjQi1hDaOeFQ&zspRW!GR9H#l^!f99aoj@)gk#z%0cS1w#U+ z7E3`bDSz!)RIp_!Qu0-9V)0ojMyOJB6;X{eqHSIJJ(b%%-gpuWjDXWq&t5t(>}t=` zyU3TbXA-bmRs&8X^2uZH0@iWnQsOG_=YW_X$HFrsrr3QI7V`srffDfCrt`q}R!!e1LKzmt#t?QiGm(-i zjRSsMk*C|0_KV!b(N(b<`W8@Tx=UpLSA|=g2yP*NgR9eKUueixo8;lcAP=adec#Y) z(s6qOe+PrR?talwb$muc*lp)SXje<~o;DsSW8IZ~-MEf>^_Xo{s$a{h{cfnMy&UDc z?;8!6Ow~)gCJxGx?~1>jHyYa1uXymI19&wCd-iS;OA}TFoK`_+smY+QzS#B0?sn`^lA~Z47m^#v^5s zm6NpmKT98!9SoF%(k-`SHzeJOH)GxELOkn2xM_G&1MU~ho5bLL)e&K~?ZJ zvy9i|T3X>h?_%!yVaeS-#Hf8PNq`W|QgyZnkT#;}cMv!mR6pZ>uaiMla1W`ndkC?V z=kAbN|Dgi5;{MPf&J=>AY#p z+-Sxe;mUZHdeB#YWs|Nb2*SjwKn`g3nd>msizkAuu|O3&>`DK48RC9$w|DYkdK?vRJQnH=WCjFv zj%;OJ(uR#;^nwxMKgIkB2t~$`1y{CYze>3~5ci(F+hpIFZvVifK6FcsBO2=#^99pl z#4WZXiw8bUxf#J=6k!wn2DZN0f$#o7`dG1>G{C)8d?&l=aX#Eus!~-C8ML1&8;*KH ziH0on2$xYED`tachn)V9O2fJTt2q)N_|&@-eYj%ZB*ZBPW}EQisztq2xIayMWE+3A z@Y~P!M=YqAL&o`E3Lu9Uyy7Mm-V1k@B~sP2_}DX2 zryb&PCA$_cCHymoXtp#Kp5XRGnY=M4o|63ICcWUOu{s3b)(hLn2W#5;CDM%iea3qK zJep>GdWEAsQ;bP@-Q-l56(Ls^;R#*@3y?+zhnHIOJb=%U1T8oUUL-WWbx+B$+od5q z9ldpP6KGod1^GDo=4)b>?2dS4+2~)w;C-u>0Ms0Hq(sda@`X=LrC|>#(=isgag>%F z#pK#6zj~+kH))cLROpC}VZOKUA@WwW?8TA55SgjZP?tWk>=6UuG9&B*T=HT+&w z%B)BtjIOGMzogDntVe&Vz^o}y_AW}wekM=3q4pSmM;fAJ6(vm`|2GmOF3t%f*x;+f6jOZgP+kYx_*=XpM#*@-=pL_P@cA6ffV3p2 z#Y_#0rMS+x*`kuJjXcS9FGw1wZdig`P{m&MpuAfrO*4A^z6Hn##=OnT(VBm6@W;St zD6!+RotTU1rokSGyX7pmTM;6ro}j#DjHj*eLud~jr?nyyb!*;|2?giT{wi1(Tq?{T zT9});Z->x?W8y=xuOY_MIcOy!)SCFOUt!i5c4hLG`C8K(Bg#G}p;0?^MAhdyI6D(& z(!XAi06j2OL_Ctno8_{tJcdJ9TQS~N9U0eG6eGzb1rSHdrlqM$tLwgz&FXV6xaMMIGA3p6xrx-C3Ezx)aN6U(;*1UH@ovJ0{GXDC>#A^w+Hz z88LZ<0Zj?)p$S-W!8w~-1(qFr!ciI{43tb-aaaS3s@wNslzijC>kKY)eRQUw!`;LL zAbrz6M*3_hVW60Kg^lVlnCSzkP<)p1zU4!R)&;!5Ah>Ilh%{cv#-*tfy{Y!iD+R!V ziq@0Jc)-@!XlymVwYx_N5+*7L8^Oh_>@LiF#P?X?UVtd_Pot~CUHUI4e5f0k&P=KT z{0fm_ehtw$C(+iM40IsCAtD>pGv?(!oTCE^jQ^NhbkMi2_>(>bs*YxDrNthTaW?fy zGZKJs6%zp4v=z1wE@f>$u806)n|f~=#zqC^vBUSm*EVRh8n28|%F+E1ukt6`a^of` zvobL~8I4H>Mk%M$|{b5#bkGv!vtPD_BEzF;# zA>~7yy{I!G_oktE?pD5Gj@KF63b7}A)*V_@ypF)&0QFXfT@Xp;^bN=bKoupaj0J6x z=BBe3HQCp~Ihg%{pdV`62)*ofmq^h|RoKxwddFtgty4e19{Ua<>A7MO2V&nJv`#Pa zJW~JQ%;{s^w1b>^`;hJ4%XyKjDY4c6f=M$U8e2#4sApT4f2xuCg5t+n4P%}rDZWak zgk%vgmaYQh^AP&TX6URiAL2L1?ABmt3`a~tQv zNzsN^HR{L!68i4#c?@yG7%~9)yNaq}K{igQAfRD8LPk5N>h4$IX2Loca|zVgUx5jPoNkzEAfvWOZKIyd`}+r8v<7f9^#m@^2H85ku-c!0p~KCkAA zqP{c}E@61xa4V~00sk&+M)*ANECN53BI%*rqTn}7eOMUW&lXZ+W5+t5fpexVAbf%_ zD_H9eH`xFK*SX4M1`9a%n8{j~qX`Wu^-vf6SV1{d4xMe1<7L32d=R{hVTvjAE~?+N zKWveaM*0(*c6q5|LGa{_%*^liKs3w`6%sGQSO#F~uFrs!Z!D)}3b!vxz9*?uu9cC} z3!1x9gtHBmv&$U=>HKb*w~60n(V07F%>-27o9#Fu(|rF8;phGCfB{AsBES-GA?FO( z$TmjtY~fDEK*Phtv$z0Jh%|yv)S~M0@&=zgI@_{_$kgN99pHkEi%UPxSJgq8UyiT* z*%1Bg8=ojz-svkamp{mqyzClv^#7pD0hLn8LS`h@vKqzS(sr?p9^;|(Qqk+s`K$}M zywLlX+{7p7lNkp-ZuS-XL|r>Pu-DQVH@=v8d-dHw|NjD>B-mYVM3UzEwy?|zMevkd zlW!0%2`#V!fo`Dg(RTrYVbyK6U#B^RJfr^1$uN!(CiMzEQrRZ1O-sfoE*i^SnUIE z4QQp1Fwlx$^M=<}c!$2&1a@yv%~^mls6}QNhh?A0D}(| z#IVKa1&V)xTS1$CMW<1z_w-B9P}BT$RZq)y3))|d^jC8b`!4>U#;%(C)XL)1vpi)M zb+qhqbPxz1V)RzN+MbMOz$st>ehU&-IOr)Ukv4aeHHrI5sr7(2!E-^5H8nG>UOrtI zX1B>H2+PKXq&^S1=1|uc>Y!su~@MnI~5E0gKMvkOcmqjx}N(u zV?DS5sr~YeiLBB_w}~?yTC&fmVi>-CGQ3tRv;w%FxWXBP#F6&R{_v#&YPul85t}1%Ke}M($`C-(sK1En{MmHlP%s zv*s?rxUqvHEWBN~fFp*$B9kN;cT#ueJ(IR?d{F+`G9p%}mLPMb{(Ggi-OscfdAlQmXU4Yqh3ci15YrR@#s7~x*>iq0 z6prP|$)#yyvH3cdI51;-`X^{r`7f$Q0TM{>4&_QqjkGq{F4xA1$V-Y8WrgKYYeMGi z=XV~xRJ+Afx?}9?iu%gEUiXB?A2-Ssff8LDH@MCo)kPAvR7HhSEm_~B5dC-C(3E9f z{3T8ky^w-p9G!REP$=Y?^?eNA3yu68M2A&bNMfDV()gt!Qc+cEo7sevvI&DO8BuPy+c<*^IAqBW0IapRNR%NV@7(HQnA)>d zeZ9F4%4^hsry{D(;^MHT5T9*phFan(G-&@z*n5iN%p4&6cZid-)T}XxmY{IM=m7u0!n}6we3O!ik^@)NwPL2 zsKS1CgZ(5&k3&Nn`yFc}B036+Whckbl7F7DzGWGGc{^~q&pT%Ion77i^F0Re5`O-C z_^?0Po}n0aa<}PRrW0N`QJpLZH;HDkR1Z$Vxo%R?F zZa2^Js?i_WBE~S-n2BBieym467@)rU+$Xky2CLwviVpXmpKDhFw+QF1yJ@Ln|K`!Kw>GR0PP!T0)QKt$S}ov^^ADn2k(Hz2$p78+-qMB zNFyct=KWe5?`jIz2vhc{2H!nBYX*s{?QgJ=sMbWarFxT&mp~<8Bs(yA+abgNyrKAb z_ITU72-vfvD*vP1mw&oQD_n3Vh>U8Ot5+o4R7wQC+UNoLdcqf!Eb?ONsOo*ZtJdQ6 zS$pziPZ5|{eT91t42ZBYUG{C=Frmmrk*O{1zT0q^^#{f%vZckFzQXt@*F8skpnOO; zz2jlQXPmRT-25;(h$=KASl`|NnXd4lkvfMx9oayC5&D%6CxGtu1%ddRFg13((Tq!4 z+k)b*L!18d-0T{+Eu9g&)+YFyl^U>je}pLOo+RV>G39z{T)1VS zK~G8A1Nk+rF?{KT+=cE(OGrNHW5OiT#v>RwG*X-*lx}QKPHRxHIo`C^K(Yk+&mC<= ziu)hk;LwL7A22wl9(uY_>?~F&w0CR{=t@S1;@MY#cr3td<+=YXnKwb7%KL0I*4xle zJ@ZsedFH86al4FGJ5o~`#*mQN-BHYDSE^CIN!F1@-6M!t(kzFAbQREg(mf|N7m8G^{VA0!Pp=oySdoF>aECC z9`h-$%E|)^G_q}uCdo#qFK~wO{z>El;wS8ix;@lw-$0R1)TW5$@kV+#Ffd#=mnpnL zWa1`82rxTQ?No~@_BFSV*udAsI)Jb$MBke%Thokdb>52lB>5=&CYN1`V@q8>4BHu| zK|C#~=(E9k)^`71*XO;ZTRlwF5^WTJp5!V~7&_WA3->K}a)AvZ(0&J5dJNE8qKos3 zprLTGOfTWwytNW} z8lC>3DbLH#I$v$B7P31P`WWN!FqJ?&nAIN5q$3U?0^czv2`Vd!_xqQElZNkDxqAre zlK3`AUCoK6Y~z_<1Sq!CKGXi3=-A(dF5osS(jm`u`(leR(pP9N%fh5tpdK1W7RBNR zU&=`PAYst36%3a{%B>Ac7r>$q+6g><8t)%ZuYU=XbCwl+ni_;849Tb5m_}}%CffzY zH8aFQrh&x?aVszx+iji8{njsPn_ z)W4SlXWrJM)1Csh5W&C<8jMsN0{sYr5tzs*A^;G(rz>BVH1yb<2?Gp2jl!q{@Y?7P z&-_3Rk2V{k%vN>iFSi%iH-+WHdnhmE{9bUq-FkJkQ>>Baosw%Ua&~iiF&|hIS zn%oSGjSaEv2Z~kM=bIfK6}ibg+|h|^!;C^VlU2FlFFqn%%QXgVgJEJ?5cTg|CC5uM zL(Wzf6Idz*kS5!Z(@&lhFo!)VC>#4h{$?L`z)Vs#lMhlt2Z7Ar-bw-H;_8GrN%Ye$ zq1d`N{@?qq7hpM?Wu1=Zp(R@1pTZHD-9LqYghyZ&?_*kJ1_u-wL+_BI>}RkNc)G{N!Pqlj*8M-~siab=1HK?GgP27*n6{fpw$08II=Dn7aW zg^-L*-N>n5FVspGtCPTYyb~g`5m)t#aAD{vX868UH5GCqR*2}jNAet2jcm=MF3xw8 zYn}?vJmUR4RXUvqqBr`k5($m*W_r}ZfNYphS20$-!ddmnr$Uf;OjwCsNCuL6g9oN?1NCV46wdfQ{XAIZZ+f8 z<&?z!=~m@t!w7f4P)5M?G<{LTJ9*v)TGx`8Wq?Gjf_tXq`M4W3fAi=uKkIbU|3QX& z_FZn{BsTaLoyu2DE*}%H9h^$WBoS2iTjL}faCb#WZ^0Ph_I$Uh4`pgJto!UEG_+Dh z>iY$JiIlDgyGSP@!f||YSOT{{W-VS?r-nz3Z#YhvQE%)EL6Vnlc>0+q~E@`(JK?MwD9y)nsc9@x!Ul>C8()XiDBD zg>_eprYlO+9KaDPBgW=*sxDN9Jgtxf`JcKDPp;08V~p*np|LJ1eHv)=1hXGf?BBh_ z;=Ck~^0nj*RmloVvzv$HJ-LhZP|z;ZD?`vOSWmOh1B=pu6@+f~=Tn+h0vMpMkg1eM ziPT5^Q(IG{-&nO`F?6@a%_&*ytBGwBSCcD%m*O^Cxg%rlln11uxZzFsP<5$XQX$>j zq}NT+8F^cJ^SDyj9Q)U6P;Nn6O`JF}?UI2dQ9ynP5`#|*p%sDegm-Poy0 z5=SQNj~bEK!t+Ygm+JJ^C1-F5aao%#WjQ22`&=%zS-r)yhBoI;AP*BuNgzUhUEwY; z-ChRng7E_$poD#9j1QqJqhk1jv6>mdc|^bKP+GB}MHvk~_D)p}%kVY0!ZD|9LuTNt zcfT}hg9Mg&+KK-7Y-`?E^|mf~`iHDgS5z#yd>Qy762Nn+ElGTnGzVLWHUC2JF8qQ9 zdL%xm<80CRr}reZ1Vmk-F(ud-r({@7XMNQ40iM{OUSxi$$RH7}^{%Rmju zdZG0kU&tcd7j;`7Mle!`hJj3EL!Xr`6}U`yq#QdJ@ujwLczljtZc;!A(@6GP_*No|O)#ppaOSJn0(EmjPNi9lRU+bUkU3FMAy zd%;C0T*wPH-Ux*0)&N3~A}OOEp8L}8MU&KfI?H{aj?xS+#NG2)CSGk*hPPUZ zF-Cz;l>Y=H#XGl+ScfMXF;&x7zzyO{5+=9|n7nMdzzci!gp z-=|;1wz*7bbD`EjbI8F35fi06*z(?3>l!Ud8C><9?L0Pp>b1!b$ZKNn`g~WOjWB|Y zGpHWHEe=5D{Qy4gT87tx1~qIWrN4}nw}OhnJMM|NO#lo&IN_Ep5`IP*avgDeY5X>E zI1@Bj}8LybOm&CnpQs$-*Kx3>L6q zIn4Rt_eJXS+px*49kFWig4rbkt`@Va5{Zap<@JNDsm9QTQuf2N(RhfCe0I*N4PxIp zRX%IREr7x{cLc!QEMADZrQCgI#wQN6$8O2IQ=MZ?bz>LC%z1(vM&GK~_f9ctGOhTY zrAnr!=jEv!-z24Vn0TXh)8HflRytLhZ%=-8li46c+Uy%e=)vxVB&E1{t0hl7c2tUS zPi*S{as$URbH4PXQ1;r^QJ$(bWVr3`nkNSo*Z2hX4F%daI$@X)_LI3wO z%#R)=Xz@ZA`VbYZG$+CEq2C_XIM#!vYE40NqM38n(RwHVGP36)xhI!s0K;_TNHZ;R zirC?*cL+^PlV!_Xo<=0e zo#Yqb7)ka_mj`hD4vldzM25{{$N9^>U9(T4_~K!T6;+n+egWYWnY3JMdAXNxwb1@N zIL`O~*UM{d_mWT@Q>v@GQ-tVXWt~Y`K7IXi1mPj zveXOi|I^hn&MsC9efN_&V8AZ}xr0coE9^EiYfXBLM>WhgC@WC;c$2HFmfZ{js2cBJ zDrr)bc%xmlIJ))qREuU(%paXu)1{MfV;$TOUL$W_Reh> z|EZNa?LnTh4oeR~OvcKqxc8w40J=LE=Ce#mve4wFJz+$3JL@wvV? zCOvOK?m?s5W(f7G20rPI>}=+w8LmOUk7bpD&r%lF+0`{*Bn2aU_z_?YtQ?HYO5B{V zAsg(Y(^wC=Q2-ln%}+gZF>vyc6r z*=7Bh?Nra<7%=V$Vo;W30J0#)5#mUR60Nqq!_39q@|D$afUT7Ypf{K@2qHnT#<{#k zib--Uelyue4-u7g_KRJh9fe^&D}BO_`7@OpCWeuR1HrY&xaW%l^JU*SPi5%3ssiD^KNb}w#FF+f%wou- zGOutKnK-eTqwV<=q95_fBRe+fTJ|2vucZd3I|`y!Mj@Ky4WvTEjJ)PvOwZ+@U}7GH zZKqu>6J1NnCtk+QrN)Gz9<<0KhCTB>$L6Cia<)QYVx)j*5!pff`x-3T3|)2+R`|-b z6hgC#mcKO47LfTn2^#W~q*cy;pzpGXLxtpwgHOs&g@65mX=>8?t?wnn3353^)_N0l z;mOJUwu~LDDxwIbrAN-n-ftjpXlH%-r@qF6jW32%B z$8b+h6dps?WpK`JYbj5?n%|8X58e9N;FDS67Sy~7941_18i_dXH9WXRI>dy}81~+U03rN1 zCJGYyw{R`RglvmkBj9ylMm7nsgbjD`dPUpTZbrm_I*b~DSl@LW*w>sqUS($L6y652D6!g+N0lXv0OkPb<3Ap@ z%Cf=QgeYdRN?%DGQQa#*9uH*!?~c5A+smvc|B?i6K%3ZGG)RfF4Y-Z(3!NAD?V1~+{H2Rx#Q7ZJ zsp0l38_Fh}<`QU$;;Ulaw9HAnLlN3t)#^Y4Q3jYLtKJ&34zJb4$r{&xl-E*p;nR`7i4_ zk_!*eJc3eU7><7~$I{i$)#WTH)y=@%djBBSQJ*zB&&`Ot80gRET-Jg!T3n_+)UXlS z^vOMW^isN#F{vKW?+3|XaObo+Z^Mfx58`o{ZYqDN80MaBv2&q#43nR$Vf$U&hL{yae2p(z-mm&E>&7ic zdfyGlkK(Py+b_sSh(vO_&fp^w9v8P+N`fRmwwTw(%UN$CVtM~zg&COf&n|q(b2S|J7(V|&3G%8{tTAs;FQDeVa|ir1>nSzrwc;pzB@kM zMhdKV=VzV5eS~#r_Ao)9S^-uIoM}TmB{hekOWHlowfo!M3ll6aJ3viB2nz?^I1S(- zuewUpJ4%%ENq>etDGJfj-w#R9#LBUMVn6R@?F~5tGLNf+y-f5Eb^Q9N`e4J{i%RoM z=f7!~zD0xT-FF~WFvE#(-p9M6lz;2V?LLMd$QsF}Q?U<0H!`g?WKfWv4&|7|JJQPU&1D7f(o`C3u7 zlPwZt!JsBu1>vY)GGlECANgGlS2FG`&ynABU2AiZG!f~_OL9$FMC}$AQ!CoImObyl z2h^ZLJ|@@7bsx ziDT-%>(;R&!i*&DB$1;Jog0nJVWF%Ch+N2%A| zdUHed)}LrRNV?m`vpd0M9*F>%-Ry*x$Kp2kZ!byx1>EB36&Wun7$vH#yk3&nM@u^~ z&m>$H_CO&L1*a75Un3A~~^d%djugjevuXuod+3W-y8m3cm+- zGX>dBaISUPm4ObDxVAOr%XY#=Mpu!`K4DE$mgXmA5h8WpM-=60+(eA%G9M6+yx*+a zUb0TPQFlo^4v%iz)GEs|<$zsq%TY+R$LMVGZNL z5`j!W!_NWh!tmU&yacav>g^dIxuXYQyJQU!ITpoX3QgK#|M6xDoc9*EH8@JIkQdsO z5ND?j{Q1;(G;d*IyWBek|NGygKvqWJ%5LBAZSSDtsx= zEFsPDDh|EPKJ_ZP7^ng|x|Q1BD-(iA3YG4Og2*jGUX)vi{4}m!i|i2_)90mR{fv!E z{=CpZLSrfVry91~zX*Q25I<1DGDMUjw&$z2che5;Kp>8wa_I<~H-%%y2 zTVQn2j19ZiVm=Gi;dG7vV66?VKh4Crp8AtNkm|TGT?$zw1M?yV$Z8f;V!BVvs>I5R>F*Y%+5K+bj~;3&>o9g*vHW#)IRC&S_Ww& z@ICB6LGkJ05%`}qfgPnV3}Ps(^l_U(cxPdvc?xIh(1u0;W=^U(i{5hMTINZUlgH-& zvUyX9N?1nup@#v@)0O$2v+MwP*7sUN{MkVd3FBzcJUs={6-m`GYN(BSOm()PRK|Y; z9FKp_6KYTw$hPbXw=WGD8ayiCiCg>%+m4VN1Vp5wJLkfbpNGK=iil(NkLyDwBw}3p zxJd(hI!;A1TwEyE6R(nPhBfy<4AVXm{cSDLxQtvxX2wDST`Gi%?|rt`(8K5bh_?EO z_rwY8Q`@nZZGE))E=V}WZ*$ha<5)=&*0W?KFLUD^c6qXnM`I{i+*vGTzAH;^TXaF= zI%i48*L3bCAbzY`;2J9bCv$HtTNUHn_v*XZPBscD=!?2tVBbvZLLIJ-7qu>x zUF`!e(d54ABxVa}tj3O1;0tD%M4cC&#?r`vjGTTr;ljO3jB$dWb@GW+@)rGS(yie5 zPc&c5t+$yUs8b%zm#4ZFcQPMX@+E`MM?Ds6CB(cuy8^fF5GSj-S(cS{>~CGo1s>p5 zYqz;vM^fn7Nlw@)#+bDrj2sU(q(Vi^j3nKTE&lvESL~ z3v<4O=1vIo>r+_1TxY`s*bj^jCiuq9gl!)&(v#0Nv%iq)uo1tNIx3XlZRCN~Rug(t z1t!4yizaC&Jna@vf~S))rJN(&I_Rcr+`jV{>0Fq}9IwuVXb5^2U#=QxF`-K$ZN_Mt zZoa2qolwu=vyZ(*MIWau4s63_?Xv>-tS$LD8-vXgGw?X*X98tfuR9HMMjKlWS_f=# zF~%rxi1>NPgr4xxbVzQ%{C{u7E*kO5zwElk z_a*3U^n6`Q|KAGq@QMM;k2c3-?|6aSs-xMuz9JJIU&)#X@?|7l_WX&eR=DyDz5fyo zSUNt+%W&p%{0iqZyCPjd0(|;8N0z0N*-vqtUr5 z?shZ(a+um5SJlgu|9-w47P4uS-v1o!SUoDFR=H3ivJ1F9_Q0xVDQED|^F9*jArU`7 zB>wc6mE;4K8Fc-TWdi&)1{^k<3#Grx8dKFk*v%i+0;}YE7gY60Um#zcoiv_M9PeR2 zG7)&s5~0hU^YimlM!!~p0^_CP1z#^7<(4*6hsK3st%Z0sFpE82IPW2R^CG|(W0c+YNqfw2Jg=cKgM_yyTYJU91y;ew!87O64?;O?~Ys2WV^7f&b zU;9(0ociELQ)zEh(opc~Tl)KAQ)xbcc?zzA6{-3v&dM;~@U0B`1);$3UELw%FL{}o zrSgzK7{iu2$LNTBICax;XIQY}ERI8__E&be22-w?!A33n!_yj8AiNCWDq`jUp)cl4 z?Aar|7tbb<-q>a405Hq2m(S2QA=;MM5-NsXMg$ zB*24Mz<+GG!-qG@=)j68vrri?%v|GG^OpD;MA8o>;!gPs;P3)*)Y;S)9(A)0%Hj@~ zNnXK9_+pET|5kO0H2ZG)W_mR z{pZSOP$@MyBU7&BOArmvzq{H#?PvVikfTXRbP^Tu-g92q0N8t7s9nT*SmB^1IH;#xo4gTvRS}{HFQw~4DUBO^RrZb;vv% z%i*3`nK3VEjCmt9w9$sBZ%f>l)7)jo@U2nlMdHFcy3q9wcL+)~>lhwG?W&>82a8l8ab>PD;~ zk5@O3YzIp^#5`(WI%}OofRzcETQzaK-lTNczCS<7)DCh<{BtoctbFErsRvUKW!k$i zL6>lwj@rB+f}FZ+ctcAg2oZ=(Jd&BgILU~%kH@ox93;dlT;z>3Mqx&tghESXEFbdW zO+4xaq3M`(^V}k`@rwiYG9TF|!4o^48Gc?Xsz)pLY6rigPEk&`iZ+9aU@#N1jNctN z5fK>Er@WavU1A`YZvvpt_UaDUz~!hB6UR6&oRJPee3%wiw=H1c*|#;Q8^7lgI~@s> zyV+~uX2TuWS4h~7XG4pwGT~4Z+Z~4lc`;vOld8{OvhO;4^mV0bDMu%VyutU~k?;qx zv3KB{u@;W=K1OkOTS2D1MS?$s$18&&&34v2>=d4d7;=9i=74!waYutC7`5D#I&0q@6W z8puR|FTv!=a0>a|c|{3Ci5M*?LPwHZ>C6ZrfIkKMJBU8GJ6O|nbq=f>H)KYUJdqd(S z(sHX!iPEy@!U`*@u=B(Xoz|F-==Z)_Pcv`Vdv=%QLtzS83Q1uHGZ)5q; zRwcZv)o25=ldXVDcV8oHBcYMr|1E$~{fWa*!cW+TS|PascvH~N;E!1N4qJ=Sphum*5yoTe^7H`$iftKJBJZ)+1{L&?+L z`$;o$is*ZTrRl;v{#HF(H7zM)z_Cg=7eY2-4;SF~QKPGuDTfkTO8w3j56@>D&|_rFzX|i2l>lfvUzhg zF`?ksl`Xh`nH!-oan112V+cB*6J8f~_CNX#Ufv_vv0nHGZtM0d*dI}Q_$ImpOf{VJ zLVf84*0;@-oytoM8-ENvZkogg^#I2@@B@i$VfkVO&!u z4j&?kK!}Knb6jrThliIS*BpfogNQi^ei&zq&7}@22>_&YtKhzE%5TRcb(oT0;plrN zki0r0u|O*2vA-fT99eOW!O$No#djiNxT-K0)JXmvBaOoE7wwaKQ#5n!weFKF$G+n{ zpb@z4HQUGeM3C^AfPObfgV-}mLuEK~JCUd){WiVjC=2Y%)oRPB+dfL3y@NW_p@&`t zX-M6s+vp*H>l#gy{!$A!2jUQc^{b{3%c8CC^W4t2&kx;l52(immqjbf>1YA;IHwqE zIoNl0a8R*c{|(pYOd;OLHIPx)ZnNd=BUu>OOm|8MRIUF(8TbU(r zCXr)nkMKn}jha|nS2eMa+VP@EyfKmOBiy^{UJj%9mx+c7RK+xtrV}8^NVOMvVGroP zJ_nn4J%8L7xmghv2VAW(tu_?<9+)i#qpHcoY=AkCA73|Khy2);7Z{$HR5WRm_t|;- zcoRgPr_!#@6m2LV&eR|3Cy0i(;#dmoo2mH#aw+QT-#$`4AF@m$UN{dQ@jk*tE!O%Z zKtNW8ie7UAlTpZb$sQpSGAx0>B74_66LhUJ5vf`3MoNcz77xM_^RGj zX0p(Fod$Q#gwTyG-9N+*ao3ztGk7gS=9cG2sqpHy+f|!<6@0d;!DLlo8i1s4gh+0f z^IpdxOt05}5=axW4u8fO78(dk(flpe)Rl5#PGpuk^m||)rlkV$m|4M5KedDKMa%&} zb$s%3EzXD6zM8oXymhw<^r!Bc@_3CtOORz0STvH;NjNG`&c`~Pp9zWUnsHFcVYIv} ztNSwOA-rfY4mt_bpFp8^m{_+=K`vreIx2LZc&rX|N#RTuxSd9h(z%?FWAp7i+b}3KhrzhA`X&sbE^cs#dZXk2%|Ee%vcZtgw}QwQ?l-~JTH?n4d^fBAvzagND(F8`Hs|a~d!Co&MmIb(4%V(vi+=(D2Rw2U? z#v_Du5)Rdaa-ZkulyWh=<8)40Q1DDS)r5$(#AD(8o4u7)8%NN(4urX-wr2)1*qKL_ zg>^)?Roewp_TWwzq~UhdulA)y%lJnc@Xwor2lkL*vwrodTG#77nxGViVV znI2@$KD&$?-@*g($UKiRTq3Vy~L~**gSm#TvUIMWIQ=(8f`!* zs>^bw0k6aFJF54EeL^JN2epFjW`4^{#X*_$@YVnM`$B}Krd84R1K7nZf<%D*;#PRnQ%05SjyR(VcJ z)yzL(pgZH`eZnW(B~jb9+V1-pcSt7MgO(Fyl-p9RXi7p>BB)0M#b{8Tsr{J`>T3}& zqmnD%$#%pn8NKk(Fc;8CW)KeljdfK*aad6oQ##U14}`(nEvUc0NIENwq6-M%gMgjeVi{(_C+Zz+K+~gHM(A~Vghq$L@Iqv^t0_w! zD&2506CfB1sJWk>+4ojr;hD-33vQ|at`bx7*hd)=J3D-}?{jWg-R(51MYF;D;Vt`G z5rw)-pOk2NX)*7K2nf1Q;`?b*ReYfv{vn83OI$Ki6RZ1!zGE|?%8*p*)d#JS&fQl6+av`mI+ZX z+?))y1#)6Aa`vL1<<$%cr_hBZL@&fmf7A%K6)1LB(6T=D;3%7$%B3sfOReD#gS~8l z6LX3g>kt6P8PMzmpD#{YB*Y@aw-WV^h^)IyjL37|ELgGoK`$8pvbhtasw)1cQ$ARG z;+%fqpU21p(Tc7uJtvuza5dR#{|HLgJwwj@RQ{p|zBCdw8M&r~^uph&&lAxBOKSpF z7CMq|5^eyCO5mPR97r#y$OJToQ*P*%E~UqA*5XjjL1B6Ui?sSNOCa%WN~*Rk)3TAUWZknTr8OUdWnV*aWasJ+)`6}kBm z`N$#+owk#)riX`Eot3Vyw+tA4v)1U<+FzbpX>#xW{oT@cfT-Y4(3k`YleIWnWSj*~ zM}hKVq*mf>JM$j!xZkSl->{L$`I9*pqU)}YtCl|$e*LIHxjwrb?AaT{5P8bv>z+9;yLaBnLeq&E^PwY%nzcYRjvmZ#@f)jnUv2ieNf<`Kz&prBm#!O9IyN7u<%su>kq0t2y&MB)u0*!e* zNj6uot}=!J>*B8U>J&qXkbX5COaMb<=m&GsS_#aNxm|g6DJa`%)9f2AFmtE3eLADM zo%(F*YAKi#N7Jp2$TrNozPqwBDI%hqEIbX9xVtrU;duzPE#m4F3^rT`v=ke=qI_HL z4)D*ITmIwP@8rU`Nw*v1+kjBa;tlzYv32MLild70j?So0a<$fyyRRD%$`zIc zna^458O;cn`43@WG6QOcU-7&m9OB52-&Q78k{70fCg3Dw?w$9IgtS$P*V=ytKf=dex50NKuJZm4X~@)fNV`yA#U)7`m_LIZk$LCwzi~5ib@%ocnk;cM?bQB zA9_AT^e%R?8N@nY9YqmEgSfkF@Lqm?-Z36aAoYidt9+-ch6r?*fE5%1U|wCN(yI1c zwx+BOz5%n;7g4729iiOO-&ceKhVBwca2LNcpWY4Z_Vnj*+pigE?i8KW=*XA7FY?h ztwph+FoSpN-6f(upzM#d1z=YoGl3c)na{1;{0FR4RB6EmHNqPZ0x{dCNq@J*bW{i< z!ksqF2X!LfzvL8OSd*?C)Jn2x955~hMW;zDwjoI%sDJWR z{K$+vr(yf&&(66e9`n3AK95Xr=9n0p)EO3N)b^~t(M3=QFluSd@w?&FP*)y0Hwl{j z2cpPh@|lgw--Xt{csWv2=iqQb5+cP@ z{$>ciT=lxwouO@&SV2H0ZQqA*Dg@-tu*ZW-{^6+Uj%nQki?y$8 zvtV^TGHnB49$cL77Hn;iRqJQZ(}aYB*O)mbh5t?j$(~`1tJNhg0aqpzbPJ|<`5;}#gQy{$8_P|bXL7bDcMJ1P_FsKm!R+iHdC<+}&m0UK z2nhVVtN1o185`qI0frs$J4=4c^jBKV5TtAWD9HjS9r|knQrIyxreuI@-rJ>|B(UCQ zdZ3Db0G-1T{ZEA1geoDZ=GPr3+}`o7f#= zcJ`^!1ja%oyt&4NJiSSjrl$APs9()&rCX5@!6pDKI(3{Kr5vyfk5`;2>&o!DshLN3 zu5Q+?s#_`(JDkSx5OQO_pNtd{xbR3~M|D36!F=&b<(SD{7BJ*wG^Hoh6dXZCywN(+ zzu(kK8y!gJJw!EyWf|s(jGQ+AZHs6_e*yYcNSWK}zPmSB%~*VjWci+iJbhnGS*VHD zi>sBHLdk?(NXCTz9VtA%kqjo`0KrvJ5*{|`?5$l#mway$bTJMxaXz)IGMo8Favi-- zBB+n@iB?Txu)Cd6Dzw~%^Hpi;QE-UBM9JM=3-P8(4I4Wfv869;X&|lK8P&0|Oq~^u zy%U2_gTog#%;f}JmvkF~qj9d}Pg`A-wb?+?ZqqPN`wkfYrxJsljR5%lt(ujens*vI z_98L`??C=+p{C967UdXIe(Kq6KILYhG4_V7+vdaYUoBAb%HZOl!y*jG{~KvjR$gT%Mnz@oG}4&p9RjJE!O;V%JlcY?-Zv zPQ^TFA1FBus*rF77DY~L84sgE^X@HV_)VC?lCLw@)@*mLzm2NSDUs8Z3M12k^O&9hQgOaIec zc%8h99uu8P3|42~b$$GgEc)3A>qyB-^yDvj6HWy^2P<0l9zk%SgER@C&t3v7$$jaSMa``?>*xM2lAaB3b z*1b%GMr)M*`&aYxrJY`IKuwZLLY`Ybt_JNnf8;`G(!w7Tm zM#=uU0poW}BkJFAV?NKIvRJS~N)~ySKItz_b_{iXLu#6kJznys)%e2Q`N-JN&&KBA zoiT)WF@oz6+iPsO)_$6uC9;C4>HAg1XSnc`BOy~|#r=xrZH~tMTgu#(N>#BmpyvW2 zUON2%8WX3p`NW&!w+$}js!qx4X_2*KesDvl9Ss1^9f2W4N;<4k_9PyIqUe0OMzPWzh3;4Wd9`c|(psr&oikf(to#1s#)dbaD4K>HM)X<0 zshVDGK$R}J<*>F>45LYpeqo^woM&<;jZcEzRM4krhh+iDH_n=6(~#s#SO@X2ceax?uZew}JG}qooxi zh-$&lf^t?onIfKuor^fZB9sdWt`!z_5R%!zFOnsZz#?)ISCnGS3`Tz;YLp@UtDX&_ zcp9S+bNLw7>&rotuU{OMvA#28HB>h!1N!|?iRn?rXJl;77@43S^1@j4jTBiIzd5{s z3aOOxFPCp8^z|wD!f~ohdAtcK&2T#x{DT7RVIOe9i+zRHe8>D4GzO%{-8U?#(3&V1 z;^%KDZjzX>l8(rk+ZL;;=>?N2&trXdt<4$GfuEmPU~K%@kt)0@xnvW>nu_7;;JtdJ zr2q*xth@jgBSp}!FdW1n($@eH8tWtY^WjBz*oK*ZSEPGg^Sq%6;yYjudz=u0|0L6# z`7?J9zi}ncq zf_|=bhl@&+oYm7xDe{ZibSU+>lUOz3Jx8`jovv3T?7a_g;rZ)FL7bT}OOR-0iJK4F z(TBt#`Ms!3+PsQ%kMa)DQjH-d2Lz44w4WDGv@pkTEH;)=S^iP%1?dhbpZUl5RDOpO z*;|uAA8X|=eNqj1F%wQ&>U2w#0g>c+d^5Yc?nnN1Yr_ql^(joxvJPbYZjyLP`a$*j z(~}VOY`uqd=@CjI!iu-YJT}3NTfIId)1F!saJh!56U-noDZn*S@kqxrz$$`e*(6K{ ztL0AXMRWpGFA2sD{pVuU7>FTb{0>CXr$sC*d%@i_fH9`}Je#+;xWXtrtc+?mN#CeRiuNGVQUGBqT0pN~yFvVW9WtUfm9x0OV>#srv zp6zcI^m@X(4d{28(4wm!=)n^Pv4rZO`K{tU!&U5{sBFd73MpuEMa7C#|%ArB@!C&vM|yL zIm=+)D_R+-cAlc-l+QfCl?$*4ww=3sO-b-#5f5<#kRBjEJ^5;XVFN1aVej8|U7g3@N^y*E{UaY*Q3>T)mrl z#?x4Tt*CLu+SKi!{*b^zIk6~X;pYX(;iN9=yw)e_K)C2YxgMQ|$V1n|9$H*RW)!t;;|rj(aRfphTD$HogJU!UhD0{4F=$T=z$>+a zmz{d!dCW;EE5Gq6$N|BVX6Ty`c$#r)D)SHzc|B_nkbgwK;$m=l={=9t#EYnID*!n_ z#=ixZ+V#$jdj|wPsF{9_kh@mC>ud>}*k3-^F8h{BM{@xK0H1K>I@cst}{P{DVM4qbC04p(?D%2Brg}EFg@=5D*tS4%GGJ9ha=m zwXfnFzHfQNXJnvQKb`N3h=zyx-#jOf@AhL~IRacs+OU13IUcd``@p{s2<1 zG2qR6`M-x5G`W?}J0fg4J0si_ipAIp@@RQ7IRyh9yzHocMQIl@+)G1hLeO2$jUd2o zJQTCinrPXSWp2*C(SU0FM7ex-Wf@|kd)s7iKeG*!rG#a`W(0%*Yg~Of)jQ_k+3#G4 zs>D&X1&t5*eOP&35<5c$pj@kQQcoiuThGBhyh6tdetj6$r>Q5W;7Fx}#<)j^RuE9vD&ZT-n==hX3N7moF%MRu5A>>#u+^tw*Lmv zNm9)zjFTxI}#`OS=*f!nDPWoUdqqHS~1Ut}jEA5P| zbhK9H6ogxq?rW(5JSB?&zMa+|pvJAG+mdPH zyM`rExBcE>6S}hM%SMpL2Kc*cb#)Uzy@T`nLV`IY-_!NaRi(JWylnNfcZrbp>lH_lcY1SFiRJmqgTGu8?&eJw{a!OJz^< zQ#+kjaYJ~`h9@jYD(2xez?3r7ln#g9R2t3(9p}BFtD-un`3o>{Lr!zKE*T^ZDrD!2 zUwU1It!J0 zaEV!KX7vhhxmvu%b=Z;Rnb9TGy`x$h9^%{`^3jg4V+rgPBDEJ>z=~if63$YuVG)M|w34^74uZFnKc|)PBV-zpnAK@OLB~ZHU2YbW4DVTWuTCq% zjcs`npy5fLBdAkVIGHss@IiDwaN7|S$vv{vcSH+ax1=tR45>5W zd|i2|%M=YiH1I#JqkqZQmZJ1r67##gk~2l{yud`DknM3&0WGm6FB(X&v1oK)8M_4` zd(Zm>w=q7%fFS3s9_#s7UZ|Hci%^Ts1+z+(+HLo@&(-g->_ln^{UYndiFiUn@q@A3(^R;?QQ=ulB0iJ2B`>CSU$vN1ViXwu&L{=lv6;?>HR0K1mw& zJ;t(NpYXVtH5j3YpYZUzfWf!PalNs}ABrhgs3ll6RlwF$u-rAVXX*bHPbpRo3Ftx9 zwj0@cS=B&E2n`THR*OiMy0ovF-#de5YF;iQT8^LcEvt_~>R)d>_@R(*%0qas!)O=V z%rlC9S}BIOg#)zu*G?-}5&7@ypO6~J=oOTE4I%zZRH^gnC2YmFO<R^)u-evhfSlUL(cjrekGV`i>f&VJ$k^YJ-JdFZAZs1XTyN7uHM&O!gbvEQOM`d+P zPmckLNTWVy4roh}Oy8NKIkI%d;i9HELb)sYRz0m1zRcq{f^dri$)%$9p5(IAxXcJ7 z{k%ZMs;oIxJJ9SpRsP1hWW^On7treZ`p)q^k0ON~5|$W8QQIrDm3PtI;xa@OsnKrs zsm~g;NNYHmo0%xc_S%nfjdq3-(Af?#M(9>|$B8eDzz19^L^{{nqptf9gWZJiK<&jcK8Z{6_5|8-UPnqXNU{yZ2fw zZCsC^CizhF{S|+)meX#ue6_v+kF_WgOxXGz3YV7Cuc#` zngloJLyK#6(ugtzmcf!b!$%i46%zR=)!9oX7ZKouF74hV)%a%q#PGSDgT5<%%C6_x zYCDSw`!FpE*PZ>*FC(jtVTRE{L_f2&>s!b0PWZ^EG>XdO!zmVGV0=F6?hVud&eKmh zcw4;z;y9eNlF;!_itmFpDO`S z6B68I8%YPnqnulJHzDniN&*Nsh_mRj8S7>6S!A7#h?o53@K61H6mlPXQ0+oDWkV!W z2LuMi|M#=f(zm*V#aJc=^Unn@^Ci_X<-vRRGZ#tf_3F#K2{OaPf}DmY!4xew2TI?IRUWxbC8Bc{rzDyx7mrYMP++($Gl+e}5I@Xs_a&aPV@2 znA1`Tp6>fiw0|cAu{A~_Tja9s50F>>c-eNCES;veF|FP;qFiQSHgjAzk}STWo9@LM zWGaNFYdgXpWA8gEwy4Mt5Bx#au8M4cZn$`<9F(!0#M6sbx9Id$v@N?*0!4;{U|+orcSgGO9H%t!_F&l0cuRU){1_E~+w) zUGLD`0Y0>}J#^Le&PMt@i7=EL?`((+{Pj4T{rmAFhYnuvC~yPRI~_<}*i@o&g=REY zf&E!yyT;Td6lE!P$SPFef3#3~G6xJWg-j76iD8I@2k(1U(VkUSqNjm%++DOnBNw=e z1Fe`za>t9suLp58k#(;hqsF9;D_$H{B@L_6Ix;>b^PmUU_GB9e2K!yy|guZY*)ln*JlmAvW>_Nqz1|_jnU)-%LogFR9 zcUORPEc{_W~a$8jZDv*}s&9=#bgd8;(+dvDY#M zwvB(C_)sesSXct?CZn_1{8SQ9g}$wMEu+re#bCsm3g|Y5ucB?b?6h#dALapIm{-ME zxQJAQT}V6|@MA!WgZGjCoPt+0TDs_B0^r4$sF_00(&US5{4il_(O&%pV7p>^JvgA+ zZeJFO0~?|=prOX~=O2{8p9;o^5{nyldSRbsO~3bx&yIY$-itFWd_L|UUB~?<9q2hd zClMs}cf`dSXauY#y~0sgW7=#C_U{sdK;VmKd_}*&F6w^yK@Sg3 zmH40w5lm-{Jat}fm;0u^e({2$8Av8bYuN}su+>4?(UCK^GB8C{p?l+1knYTf{^o~H zsAOuBpn$w{kLTm`d=vTz*ECjcfCF|pAUG%+<^QI!oqg+}F07kQGS4pxiYlGu_DoNu zGnW#@nZBy@Ei{NNM9@@zoJEi(+{cFW8fePU7Cb2-L{jt$H(%XWZq$QfpXf*->!mY4 zg4^S`abL$*>UQs{8N;H#>IoAx#~Kp)6k%ox=B5bt)mJl6TEo#}r?O*N&}{g?vyslC z(T;~!abltBiMu^j8cuYe`=?B#et7)e&nAZRzIio<<7kFKGe$Xwl*ruTkO`XdrMlyXxM7I^7Z6AYJS%5$q-9ZjuXC;&PB7}v6XPTY zHX63kMxe1d!td4d%!JXkBemd#2L;X-n_V@BEb6q#c1CRK&gp1y7%tU@FcibK;|iM^ zzg#^6(?E#&e48-tGwvn4=|2cRK)1aKWIm8ye%+>zJHS>#ms9%>U>l;m z_z9@y`9WDMU3p%X&rKz>h`FIR3`>$Xe_K17Tu9E3i!<6(H0g=?B=IhfFBY9vNNkiX zqrmd+(33B|ioxfcRnMem{f7BB4kHnfqVkIMf$RV zx{gH{&hnd{#r+9Lp=&fxGhNtcS`9gDBlTnc-%(9HvTF>4)2MNAK6kH62>`A9`D~!? z6>I(imN>5!rgKBe`Pd5X?6!RV#-6Z#PskKO6*MlzV%4PUcf1kXBhIzD zMghbmLz>s#V!9d|nY5La7s##ZI2BoprJ}*+!PxKVgmYCf0W6iX#C$%&@o&^I9VGrc z=Fa+N8tBqEvNpxMN?eQeP^nb?v5x?YV_Z%STP~AVkaCHpixXu5&hH16FUM%3??AQ8 zmJsD3I6l7CRs?}GVwet2jMpQBQzrSa)y0q#Ict|*bj_{s3XEA^R=%X{Pa>+xcgFCkPVw{YgcOHO6q;*cS*Oj<9c zXliRNV#sj|mKXV`2j-wM0D;vFzEj6s-Zn2Op{+S%fk=1HqO7DcLMas~5798evAzGh zu}&8>?H#%0b4kQ7*b}zUAs3QsL8TeB`9-6oR{D?4hp%Drz(X@f0aVztm5_xV6nken3gKPxYI84e$y1{vg{J(7 zB5`sd=pdt8@f~_y;jd3;9_^(8RWVOaQ7OHXR;a6DHlX?qB>4x9+@-y|U1u@tgOkF` zoEY0~rNLpySeDM}Zu+&wHH{gJxZ*@WC_rEQ~>+6 zPn0A9pCdn0czvnkzlP=0)N%hN`o|j&AOn7NlcP8GmV8))ofs-wyKexISkq7vfZ-*K zl}j|kN&URj{uyl&xrO1KIpc-9XI~KI4HEN|%{v`-71;wAsn5lfWhHR`v>z~&qBh@V zd;{1*NiV^NjH=Q&F{-(ib|Z&JMF3=x z8K`Xd$tc|~=H2wf=p@G}=a8y}&ohE4@SlgVcV1!6j2Jm~9hP#}Ef4QlVc*W=`#zld zk%(Fq^VCBntRfht+BuB!vj@VoA;twgbAYN+Gp<`pKt?Uph-xda6h4SidhvsZig0cbG6J#Mt{ zHy*L?o&29WEl_4>>jx(~yl(ulHaTl6cl%J-lJe;X{GW>vdZaOAFxWWGg~?HlmG)CW zg@tNE<7Lr{{gNXLSf;S+8Jt|yMOdVUkcL<%jX0YE6zH|{R(8Ju^DC(4y8?jF?gEh< zsAiKi_pkA5j75;n2&|O~ldH}xqSsqzy@;QTA7;>hw`i!V*C291l?zOe0(A-|!-o+g zxbS%((qi;U;{M;xk^;z;VST7|@Fzgm${qvZM@!v-v((vef4fcBpc5r|=0 zfb&vQ^2a{~R9CT#d|*nJ7pz-#8m!9gh#yQFpPxSeOIgaVGyAsFe`Nn(LwQn9GP4KP zox()bmXVg78%9fds&JwR61g|F9fb-H~1x(F{cI0#6$d74v>si z@@{d%05@hm?YqrNTWabynN{9Y@(R9`gL7_?=$%mb0#) zYbfvtt)5hulLtFiCJCT*NjI@je9^f0Xhf}J8gcD><0R#3I&6CyTM9}8q$-SME4vhjAG`q&WZRk#Lg@gYGBK%s?YT_K{u*wiNlaueige!C z#@!MnA%*ZTo7+m@)X}9L6302oD6Ag+gpU5@MKdfPpGr#Go;;e!=DcsS@F_w z#;$jevazv+ofcZbHw|cr1e>;1JZ^ku=>nUKT*tmkfymz| z|Djy7p(Kclk#m$Y))&%?0A*S;4R(lNN8}|O>wi>HCQb|Pl%o1~1y|k2>8fAjrfe5N zqPq2H>|ZcRatPlQ@|_3g%*}a#uF?1ZwLN;EDAGt_L?q|EEj0vyLS|+|AFgTI!Tj#9 z=W8~@a#9U^H#O@sE)|WXb(hwwH*o5qxPS-+#m#qO(Nj=J9^y(=EbjoL45up2q14{{ zdT9~;uM-Xf=LtO$cs|__ z`*n};*{2XCOoMq~e@_*UvR$d2a3oBW1`5ZoQRFWSVdN>WYAQ4&dR2Fn^qH3Ydh=|0 zKB}J^cuSQzPb*G9c#@ZO%smx=CMYRp+zx_^xDD=8ed{ROvI3?R7o@7$myeM$&h#O# z{oj81J}vh4e*ELQZ?{bnzt^6(W(wAn$&6qPY2OKiYk;fz~z6rg?vMSIDnJ?we;dRne`j4^HuIz-48a#(u{ zKa%pcvsS&MDPqoBMmIBpik$f!zc;M6CC^dG#S zbeS~H1xFV}HM9n}1Y8ym#EyMy^`spn1!bi)XD1M+2jKJI5uCzv`Gt2)PAAzfw}g;I z&Z`AsTJ0G%f>gheLh9gdzQdhM@qG-LGq+2d13d7Wd7?@##OVFSfFKONtI^#JG%Rlq zO^T0_UNP94aniun?!=S*nB{}=UJ%~SjIqi+zeofNB=ay{8Yem;IX14V1{tUd>JCq;q@6i_7p#&!kI?+Z3;hTl}{Wc}JalXfb=L8$Zvz11t+HEcn z2FPweW~o33eK+>*@T+t7wVG3mEUV6T9gPn>j8Mg@bgomjVH36NJ5xUK>I&O)Ku6?5 z-Hh|+=HkfqJn>lIT#_o(ZnKth?R!n!n9Zy;n$2AX%YS z4+Msg8mAJ#1JWNw5n|LRX-#^(Uny_;g^l#a*d<_s4}n`;=r9iFyoR6Is3MFmK{Q<7 zLT7geYA3QT@p@kK2NoGups~(=iFR}{=6uQC<>n}WUfg(pXUZ;f5?*7h0yd~eCGc}d zI-u9|8rjqqA?~eBK4OXNz!y+Z3XxYmRR@d4k^EFkTp5JDIvK%44x;o@O~@j_MPF>9 z$MfiY<^wtB8}bIqj7q)wRV{n8TF-VY{ibTV?XI|4*HRxqxJO@lHvT@FQj|5yvMwvr zvXY+22H@q_Kc>F&?Q|j!B;0)-3yLHzN2rHR{tZW) zpQ1})mZ|DH({3s@x_E_vsNorg(EqK+n&sqsl)eV)MuH2TMzT=>f{NKa{;P;vV7Ws= zAz!QfHr0w(v->uG1LGGm;q0GZJ%Lzs`)b>ARX9u3`Q3(!!K44wi*~wR)Es2(unQLnM`N1`MmXh8GaS@JZ)eu=g81#{t zU7h(!9KAnEvB_fDql+$}w3C8i6P9{u7+gS);?IO9bNOZp*t6-IzQwe9vj$p^ZSmE9 zL5(UMPET}yNDx35jDlsy!vWybgfkU}l^;7mhglnRi3h1 zTF~4lq)L1UB*+T_8`TTJ7CIF|jz>gHAaE}-u}88`j#%JvLb_}G6*u?dMt4*vyryKgZ| zG2d-7rPuLihZ6Yp;I2y-$!#M&?!uv_M#rXGB={v6lBD2amX3p+mq2$BAreJk>!8A) z<|qUWF4mlOPutY%<|w{SH7+b`8GIRM^+ucVz~WlZrHi`07WZ)b%Sd&>WP&E-u2Re+ zeCcWj;d1cC1ypdyKo5$OA38yHvHj@4VZNEx#kfVzP#lP&@&(BT7x!e6*!#yX;l}MR zlecEMgS!di#+tO*)G1$yQV-bs7y8$!e#4QCLV^}(4N_D7`%C zz`A{jOr#ye=(w=C`muP=Mp~e+0;kRs!)1h|P+#t5_JNaRWF8+ayCoj>aRXsWH#z^8 zeJ@voLnmP9U)_>J$??k#nW@gC)$i4|A&74@Sy(HA0kFK9EKVO6UzZV4f*N-VCKXGP zzHJV*amg)rO|nG7{##cQfAJPrdY4)>0){{N)>L7zA}pmZ_U7*xWv{7!Dt{XukSW@T zfh`pc7=;JQ`G%{4%&ircF|anvC&SxK@lX#~EL*qwhXVZQb%RzTDs5(&bGl2z(6As} zd@5Q=6*R+5p-+IP8``&#;cH1cs8V|Oi0$%x$OnnhSPNdu8W{poCNN1Us$5o(;uEx0o!G;$A%0fv%$bl4ZEKUA-<4-2L?2zk zOwzrn^T2WS78P^f=lJ9Y=BPHm{6l$XS~u7fz~5DG{TbpF_4>%Stn#_X@sEw@e__4P zZ?N~#;LSW9u|H}O&Is^W^ZYxnmJ29T<6Xpzr!A$FMEdkp{H5i3d1W0L?b)(jdvRo`5BOA(!D7 zvML0rPgNs;TfUVSii;o5jbqlVR4qmQn?D>Dk1EvqLmZ`OKhpujxyG~BWt7-V9Y$&P zLn(yDUp=$~%dX2VB>P3fLSG>YsJN%^8c-$UaGjXm;vdhtca>sgV^h*B4!)Js;&}~h z%~6?Tyk`L}>Q1G1BJ>JJK>(5CdC+-Fu7T7)I>gM5ICKz2>KSAcR3Mow)bv2PP))kA zBrv0+6((rQ$@=)$R#yK6AAju;1#K)?#^AkgLZ)kmu3fFL-u$HowR=sO;B0q&augb! z*38YUUGZnHYw%0_?DiJahq_IC-PQ4 zt)!7Gt7tesU=3bCYS`vF-VFHOaubj*?oy29RFFZIidjL-@X?!nGG=67ocTn6L48km z3Ga3;qG+m|e;v}f8;&bes1cZ{qH{dn-in7*f1WoBO?UrMEz;c|sFvqIt$}D7mH6#`@u(HhZuD{^zN75~Lq3^M;ODNCy6U)JRj> zl*OF6TBDdFf?M=M(>SxHr{vVAR|;Pp_T;wc0k~fX?3UY6L951piCsl{>ax&+d1J6a zn&eV@3^XZ(6>P_{&x&I_oGz=^5ZJC^cJQdI0$ z{G7MqrrJX)4pR(s6BgBTR#~$v`FMQtu$u=&o|AB0BmxbUsR`3Cw`BF@6cC=Xy3sx$ z@L(5)v`y5~cZ=HjVZ$0;yf_d=x0P4S1k~k3|K@OAVaMVVA{vlqaPEf9=yl%8o}*Hu z^}%i58)j;&;G26K>6N76;h1L_beQfJi*@flJ*YkTO^E)X53qkQ&2pgS zA*145>|ADc7q@Muv`0km|2z4L%EK1j0Gx_M1}Zx_p8(p?=448WG}2k|Wuqvp6ojML zEkU$>{5#_i-hejfFvS&n{Qh6IH5^U zDU3GMveehSv^zpFTBJLx$$AkIN3Ag#X>O}NOVv+`D9-tq=p90!;ffzia!5?&uF?P7 zi%gozW;+_1?X@{p8F=ko9tq&zGv~!V{7Cq?W5$M0^ftkYb1t;ES(Ybb;_P$QfQW65 z9%m0{o6liRoTh5+aXvFH)i{}Vyn+ZnBseQ$#cKn2%0DP4$=QHV09F}7GPsg|LM|g> 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

1KB(i7@a;=)8TE&$lT(ZAt!mUn$IM$jV7?~^*? z+}bmAM%Lv(oJ=J==xNQLLH)=71JkvrD1U8F_NSfR<%PEV8QZrIDG-(tYcm&$7kUF` z>0UR0qzd%&ro454!g&_9nWD0EYL17DZ{H$R!t9`Oe2$>G7vvoRxrd^G>ClmP&FpOo zz8)k|Mv1*})(}2jppmV=!qbSg~4B11+#Xj0{c3%jw*vnU^uU@U7Rntlqw6WxlInMI6mUUAacqsLwI>U(tYN$tm5^~2s!b#QSb{!RcOR>{} z+Oifs#ju$y{GQuvQFA`#N3GCK*Y4Z+a3#>PHN(8oZF0lrX;pF3a|Adh!;G{a{B40O z=}Xn8tWWzAW!YhA8QI#-;ytB;DOcxbW)Ub6Ba~ALvk~tm^JNIEDQFx)c0gHLy5XaeqaPBiVH~=gZ*-jQ4!1sxzqK7Mw5fo_ zW60rdwS`a~)Y**NA?rqSTbZ7We5CNp0Nw?VSb6@Ye~IA7+>60NvX4Ko?NA~$Yo>*$ z%Doy?^cgJu528t;eeF^spT|Iw{&*Jm?Jl(&>fnd00GAN#M(1w@=ptVZ_0uJ7*lBq- z+P8^jL<9Dv*!z(C7U8mLWIS!z<7SM;{4FYE^I$@5Q2kBz*U2>VpppU|ioEZEOT+-S z9qr96Fge?nF$f;-Qmz>DT^Y@dbZl(d=#yM%#3vy;x_s26+jx6J>gzsIM@|Ui9&~Gy z1mE^TIWuu}V&3%=DAqwtDC1QzI{(-ySfdESfb;FS-Ob&Lsxb>K5(pOS@y=raQSmjk zrRrdGnU1v2#YiHds$088gB2O=m$z7)pVjOfmwBFti=FfIf*nY39JQdleh I{}3ev0C4;nasU7T literal 39856 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 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: [] From a1ba3a6beeae22c340f63cb613150e7bc986ded1 Mon Sep 17 00:00:00 2001 From: JohnCorby Date: Thu, 1 Sep 2022 21:17:31 -0700 Subject: [PATCH 49/51] improve SearchUtilities.Find --- NewHorizons/Utility/SearchUtilities.cs | 59 ++++++++++++++------------ 1 file changed, 31 insertions(+), 28 deletions(-) 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) From e1136b39b19531e08260c577b8d824f86f233579 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Fri, 2 Sep 2022 10:30:57 -0400 Subject: [PATCH 50/51] Make entry locations work in dimensions --- NewHorizons/Builder/ShipLog/EntryLocationBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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); From fdfe477508723a358a5aa550a94ba3d625c30846 Mon Sep 17 00:00:00 2001 From: Noah Pilarski Date: Fri, 2 Sep 2022 12:57:30 -0400 Subject: [PATCH 51/51] Replace < > and CDATA in value as well --- NewHorizons/Handlers/TranslationHandler.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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; } } }