From 1a56fe1ab25e1273433c7ae1897ff5973bbd14ad Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 3 Feb 2024 16:09:50 -0500 Subject: [PATCH 1/4] Preloading bundles and setting subtitle path --- NewHorizons/External/Configs/AddonConfig.cs | 12 + NewHorizons/Handlers/SubtitlesHandler.cs | 12 +- NewHorizons/Main.cs | 16 +- NewHorizons/NewHorizonsApi.cs | 564 +++++++++--------- .../Utility/Files/AssetBundleUtilities.cs | 222 +++---- 5 files changed, 438 insertions(+), 388 deletions(-) diff --git a/NewHorizons/External/Configs/AddonConfig.cs b/NewHorizons/External/Configs/AddonConfig.cs index 54d8cf0e..cfcc731b 100644 --- a/NewHorizons/External/Configs/AddonConfig.cs +++ b/NewHorizons/External/Configs/AddonConfig.cs @@ -31,5 +31,17 @@ namespace NewHorizons.External.Configs /// If popupMessage is set, should it repeat every time the game starts or only once /// public bool repeatPopup; + + /// + /// These asset bundles will be loaded on the title screen and stay loaded. Will improve initial load time at the cost of increased memory use. + /// The path is the relative directory of the asset bundle in the mod folder. + /// + public string[] preloadAssetBundles; + + /// + /// The path to the addons subtitle for the main menu. + /// Defaults to "subtitle.png". + /// + public string subtitlePath = "subtitle.png"; } } diff --git a/NewHorizons/Handlers/SubtitlesHandler.cs b/NewHorizons/Handlers/SubtitlesHandler.cs index fc4bfe94..de283b54 100644 --- a/NewHorizons/Handlers/SubtitlesHandler.cs +++ b/NewHorizons/Handlers/SubtitlesHandler.cs @@ -61,9 +61,17 @@ namespace NewHorizons.Handlers private void AddSubtitles() { - foreach (var mod in Main.MountedAddons.Where(mod => File.Exists($"{mod.ModHelper.Manifest.ModFolderPath}subtitle.png"))) + foreach (var mod in Main.MountedAddons) { - AddSubtitle(mod, "subtitle.png"); + if (Main.AddonConfigs.TryGetValue(mod, out var addonConfig) && File.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, addonConfig.subtitlePath))) + { + AddSubtitle(mod, addonConfig.subtitlePath); + } + // Else default to subtitle.png + else if (File.Exists(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, "subtitle.png"))) + { + AddSubtitle(mod, "subtitle.png"); + } } } diff --git a/NewHorizons/Main.cs b/NewHorizons/Main.cs index 92509469..ee07514f 100644 --- a/NewHorizons/Main.cs +++ b/NewHorizons/Main.cs @@ -50,9 +50,10 @@ namespace NewHorizons private static bool _wasConfigured = false; private static string _defaultSystemOverride; - public static Dictionary SystemDict = new Dictionary(); - public static Dictionary> BodyDict = new Dictionary>(); - public static List MountedAddons = new List(); + public static Dictionary SystemDict = new(); + public static Dictionary> BodyDict = new(); + public static List MountedAddons = new(); + public static Dictionary AddonConfigs = new(); public static float SecondsElapsedInLoop = -1; @@ -747,6 +748,15 @@ namespace NewHorizons { MenuHandler.RegisterOneTimePopup(mod, TranslationHandler.GetTranslation(addonConfig.popupMessage, TranslationHandler.TextType.UI), addonConfig.repeatPopup); } + if (addonConfig.preloadAssetBundles != null) + { + foreach (var bundle in addonConfig.preloadAssetBundles) + { + AssetBundleUtilities.PreloadBundle(bundle, mod); + } + } + + AddonConfigs[mod] = addonConfig; } private void LoadTranslations(string folder, IModBehaviour mod) diff --git a/NewHorizons/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs index e5507abc..d6d78ed9 100644 --- a/NewHorizons/NewHorizonsApi.cs +++ b/NewHorizons/NewHorizonsApi.cs @@ -26,316 +26,314 @@ using UnityEngine; using UnityEngine.Events; using static NewHorizons.External.Modules.ShipLogModule; -namespace NewHorizons +namespace NewHorizons; + +public class NewHorizonsApi : INewHorizons { - - public class NewHorizonsApi : INewHorizons + [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] + public void Create(Dictionary config) { - [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] - public void Create(Dictionary config) - { - Create(config, null); - } + Create(config, null); + } - [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] - public void Create(Dictionary config, IModBehaviour mod) + [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] + public void Create(Dictionary config, IModBehaviour mod) + { + try { - try + var name = (string)config["Name"]; + + NHLogger.LogWarning($"Recieved API request to create planet [{name}]"); + + if (name == null) return; + + var relativePath = $"temp/{name}.json"; + var fullPath = Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, relativePath); + if (!Directory.Exists(Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "temp"))) { - var name = (string)config["Name"]; - - NHLogger.LogWarning($"Recieved API request to create planet [{name}]"); - - if (name == null) return; - - var relativePath = $"temp/{name}.json"; - var fullPath = Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, relativePath); - if (!Directory.Exists(Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "temp"))) - { - Directory.CreateDirectory(Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "temp")); - } - JsonHelper.SaveJsonObject(fullPath, config); - var body = Main.Instance.LoadConfig(Main.Instance, relativePath); - File.Delete(fullPath); - - // Update it to point to their mod for textures and stuff - body.Mod = mod ?? Main.Instance; - - if (!Main.BodyDict.ContainsKey(body.Config.starSystem)) Main.BodyDict.Add(body.Config.starSystem, new List()); - Main.BodyDict[body.Config.starSystem].Add(body); + Directory.CreateDirectory(Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "temp")); } - catch (Exception ex) + JsonHelper.SaveJsonObject(fullPath, config); + var body = Main.Instance.LoadConfig(Main.Instance, relativePath); + File.Delete(fullPath); + + // Update it to point to their mod for textures and stuff + body.Mod = mod ?? Main.Instance; + + if (!Main.BodyDict.ContainsKey(body.Config.starSystem)) Main.BodyDict.Add(body.Config.starSystem, new List()); + Main.BodyDict[body.Config.starSystem].Add(body); + } + catch (Exception ex) + { + NHLogger.LogError($"Error in Create API:\n{ex}"); + } + } + + [Obsolete("SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) is deprecated, please use SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) instead")] + public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, + float scale, bool alignRadial) + { + return SpawnObject(null, planet, sector, propToCopyPath, position, eulerAngles, scale, alignRadial); + } + + public void LoadConfigs(IModBehaviour mod) + { + Main.Instance.LoadConfigs(mod); + } + + public GameObject GetPlanet(string name) + { + return Main.BodyDict.Values.SelectMany(x => x)?.ToList()?.FirstOrDefault(x => x.Config.name == name)?.Object; + } + + 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) + { + if (!Main.SystemDict.ContainsKey(name)) return false; + + Main.Instance.SetDefaultSystem(name); + return true; + } + + public bool ChangeCurrentStarSystem(string name) + { + if (!Main.SystemDict.ContainsKey(name)) return false; + + Main.Instance.ChangeCurrentStarSystem(name); + return true; + } + + public string[] GetInstalledAddons() + { + try + { + return Main.MountedAddons.Select(x => x?.ModHelper?.Manifest?.UniqueName).ToArray(); + } + catch (Exception ex) + { + NHLogger.LogError($"Couldn't get installed addons:\n{ex}"); + return new string[] { }; + } + } + + private static object QueryJson(Type outType, string filePath, string jsonPath) + { + if (filePath == "") return null; + try + { + var jsonText = File.ReadAllText(filePath); + var jsonData = JObject.Parse(jsonText); + return jsonData.SelectToken(jsonPath)?.ToObject(outType); + } + catch (FileNotFoundException) + { + return null; + } + catch (JsonException e) + { + NHLogger.LogError(e.ToString()); + return null; + } + } + + public object QueryBody(Type outType, string bodyName, string jsonPath) + { + var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == bodyName); + return planet == null + ? null + : QueryJson(outType, Path.Combine(planet.Mod.ModHelper.Manifest.ModFolderPath, planet.RelativePath), jsonPath); + } + + public T QueryBody(string bodyName, string jsonPath) + { + var data = QueryBody(typeof(T), bodyName, jsonPath); + if (data is T result) + { + return result; + } + return default; + } + + public object QuerySystem(Type outType, string jsonPath) + { + var system = Main.SystemDict[Main.Instance.CurrentStarSystem]; + return system == null + ? null + : QueryJson(outType, Path.Combine(system.Mod.ModHelper.Manifest.ModFolderPath, system.RelativePath), jsonPath); + } + + public T QuerySystem(string jsonPath) + { + var data = QuerySystem(typeof(T), jsonPath); + if (data is T result) + { + return result; + } + return default; + } + + public GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, + float scale, bool alignRadial) + { + var prefab = SearchUtilities.Find(propToCopyPath); + var detailInfo = new DetailInfo() + { + position = position, + rotation = eulerAngles, + scale = scale, + alignRadial = alignRadial + }; + return DetailBuilder.Make(planet, sector, mod, prefab, detailInfo); + } + + public 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 = "") + { + var info = new SignalInfo() + { + audio = audio, + detectionRadius = detectionRadius, + frequency = frequency, + identificationRadius = identificationRadius, + insideCloak = insideCloak, + name = name, + onlyAudibleToScope = onlyAudibleToScope, + position = Vector3.zero, + reveals = reveals, + sourceRadius = sourceRadius + }; + + return SignalBuilder.Make(root, null, info, mod).GetComponent(); + } + + public (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) + { + var info = new DialogueInfo() + { + blockAfterPersistentCondition = blockAfterPersistentCondition, + lookAtRadius = lookAtRadius, + pathToAnimController = pathToAnimController, + position = Vector3.zero, + radius = radius, + range = range, + xmlFile = xmlFile, + remoteTrigger = remoteTriggerRadius > 0f ? new RemoteTriggerInfo() { - NHLogger.LogError($"Error in Create API:\n{ex}"); + position = null, + radius = remoteTriggerRadius, + } : null, + }; + + return DialogueBuilder.Make(root, null, info, mod); + } + + public void CreatePlanet(string config, IModBehaviour mod) + { + try + { + var planet = JsonConvert.DeserializeObject(config); + if (planet == null) + { + NHLogger.LogError($"Couldn't load planet via API. Is your Json formatted correctly? {config}"); + return; } + + var body = Main.Instance.RegisterPlanetConfig(planet, mod, null); + + if (!Main.BodyDict.ContainsKey(body.Config.starSystem)) Main.BodyDict.Add(body.Config.starSystem, new List()); + Main.BodyDict[body.Config.starSystem].Add(body); } - - [Obsolete("SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) is deprecated, please use SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) instead")] - public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, - float scale, bool alignRadial) + catch (Exception ex) { - return SpawnObject(null, planet, sector, propToCopyPath, position, eulerAngles, scale, alignRadial); + NHLogger.LogError($"Error in CreatePlanet API:\n{ex}"); } + } - public void LoadConfigs(IModBehaviour mod) + public void DefineStarSystem(string name, string config, IModBehaviour mod) + { + var starSystemConfig = JsonConvert.DeserializeObject(config); + Main.Instance.LoadStarSystemConfig(name, starSystemConfig, null, mod); + } + + public (CharacterDialogueTree, RemoteDialogueTrigger) CreateDialogueFromXML(string textAssetID, string xml, string dialogueInfo, GameObject planetGO) + { + var info = JsonConvert.DeserializeObject(dialogueInfo); + return DialogueBuilder.Make(planetGO, null, info, xml, textAssetID); + } + + public GameObject CreateNomaiText(string xml, string textInfo, GameObject planetGO) + { + var info = JsonConvert.DeserializeObject(textInfo); + return TranslatorTextBuilder.Make(planetGO, null, info, null, xml); + } + + public void AddShipLogXML(IModBehaviour mod, XElement xml, string planetName, string imageFolder, Dictionary entryPositions, Dictionary curiousityColours) + { + // This method has to be called each time the ship log manager is created, i.e. each time a system loads so it will only ever be relevant to the current one. + var starSystem = Main.Instance.CurrentStarSystem; + + var body = new NewHorizonsBody(new PlanetConfig() { - Main.Instance.LoadConfigs(mod); - } - - public GameObject GetPlanet(string name) - { - return Main.BodyDict.Values.SelectMany(x => x)?.ToList()?.FirstOrDefault(x => x.Config.name == name)?.Object; - } - - 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) - { - if (!Main.SystemDict.ContainsKey(name)) return false; - - Main.Instance.SetDefaultSystem(name); - return true; - } - - public bool ChangeCurrentStarSystem(string name) - { - if (!Main.SystemDict.ContainsKey(name)) return false; - - Main.Instance.ChangeCurrentStarSystem(name); - return true; - } - - public string[] GetInstalledAddons() - { - try + name = planetName, + starSystem = starSystem, + ShipLog = new ShipLogModule() { - return Main.MountedAddons.Select(x => x?.ModHelper?.Manifest?.UniqueName).ToArray(); + spriteFolder = imageFolder } - catch (Exception ex) - { - NHLogger.LogError($"Couldn't get installed addons:\n{ex}"); - return new string[] { }; - } - } + }, mod); - private static object QueryJson(Type outType, string filePath, string jsonPath) + if (!Main.BodyDict.ContainsKey(starSystem)) { - if (filePath == "") return null; - try - { - var jsonText = File.ReadAllText(filePath); - var jsonData = JObject.Parse(jsonText); - return jsonData.SelectToken(jsonPath)?.ToObject(outType); - } - catch (FileNotFoundException) - { - return null; - } - catch (JsonException e) - { - NHLogger.LogError(e.ToString()); - return null; - } + Main.BodyDict.Add(starSystem, new List()); + Main.BodyDict[starSystem].Add(body); } - - public object QueryBody(Type outType, string bodyName, string jsonPath) + else { - var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == bodyName); - return planet == null - ? null - : QueryJson(outType, Path.Combine(planet.Mod.ModHelper.Manifest.ModFolderPath, planet.RelativePath), jsonPath); - } - - public T QueryBody(string bodyName, string jsonPath) - { - var data = QueryBody(typeof(T), bodyName, jsonPath); - if (data is T result) + var existingBody = Main.BodyDict[starSystem] + .FirstOrDefault(x => x.Config.name == planetName && x.Mod.ModHelper.Manifest.UniqueName == mod.ModHelper.Manifest.UniqueName); + if (existingBody != null) { - return result; - } - return default; - } - - public object QuerySystem(Type outType, string jsonPath) - { - var system = Main.SystemDict[Main.Instance.CurrentStarSystem]; - return system == null - ? null - : QueryJson(outType, Path.Combine(system.Mod.ModHelper.Manifest.ModFolderPath, system.RelativePath), jsonPath); - } - - public T QuerySystem(string jsonPath) - { - var data = QuerySystem(typeof(T), jsonPath); - if (data is T result) - { - return result; - } - return default; - } - - public GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, - float scale, bool alignRadial) - { - var prefab = SearchUtilities.Find(propToCopyPath); - var detailInfo = new DetailInfo() - { - position = position, - rotation = eulerAngles, - scale = scale, - alignRadial = alignRadial - }; - return DetailBuilder.Make(planet, sector, mod, prefab, detailInfo); - } - - public 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 = "") - { - var info = new SignalInfo() - { - audio = audio, - detectionRadius = detectionRadius, - frequency = frequency, - identificationRadius = identificationRadius, - insideCloak = insideCloak, - name = name, - onlyAudibleToScope = onlyAudibleToScope, - position = Vector3.zero, - reveals = reveals, - sourceRadius = sourceRadius - }; - - return SignalBuilder.Make(root, null, info, mod).GetComponent(); - } - - public (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) - { - var info = new DialogueInfo() - { - blockAfterPersistentCondition = blockAfterPersistentCondition, - lookAtRadius = lookAtRadius, - pathToAnimController = pathToAnimController, - position = Vector3.zero, - radius = radius, - range = range, - xmlFile = xmlFile, - remoteTrigger = remoteTriggerRadius > 0f ? new RemoteTriggerInfo() - { - position = null, - radius = remoteTriggerRadius, - } : null, - }; - - return DialogueBuilder.Make(root, null, info, mod); - } - - public void CreatePlanet(string config, IModBehaviour mod) - { - try - { - var planet = JsonConvert.DeserializeObject(config); - if (planet == null) - { - NHLogger.LogError($"Couldn't load planet via API. Is your Json formatted correctly? {config}"); - return; - } - - var body = Main.Instance.RegisterPlanetConfig(planet, mod, null); - - if (!Main.BodyDict.ContainsKey(body.Config.starSystem)) Main.BodyDict.Add(body.Config.starSystem, new List()); - Main.BodyDict[body.Config.starSystem].Add(body); - } - catch (Exception ex) - { - NHLogger.LogError($"Error in CreatePlanet API:\n{ex}"); - } - } - - public void DefineStarSystem(string name, string config, IModBehaviour mod) - { - var starSystemConfig = JsonConvert.DeserializeObject(config); - Main.Instance.LoadStarSystemConfig(name, starSystemConfig, null, mod); - } - - public (CharacterDialogueTree, RemoteDialogueTrigger) CreateDialogueFromXML(string textAssetID, string xml, string dialogueInfo, GameObject planetGO) - { - var info = JsonConvert.DeserializeObject(dialogueInfo); - return DialogueBuilder.Make(planetGO, null, info, xml, textAssetID); - } - - public GameObject CreateNomaiText(string xml, string textInfo, GameObject planetGO) - { - var info = JsonConvert.DeserializeObject(textInfo); - return TranslatorTextBuilder.Make(planetGO, null, info, null, xml); - } - - public void AddShipLogXML(IModBehaviour mod, XElement xml, string planetName, string imageFolder, Dictionary entryPositions, Dictionary curiousityColours) - { - // This method has to be called each time the ship log manager is created, i.e. each time a system loads so it will only ever be relevant to the current one. - var starSystem = Main.Instance.CurrentStarSystem; - - var body = new NewHorizonsBody(new PlanetConfig() - { - name = planetName, - starSystem = starSystem, - ShipLog = new ShipLogModule() - { - spriteFolder = imageFolder - } - }, mod); - - if (!Main.BodyDict.ContainsKey(starSystem)) - { - Main.BodyDict.Add(starSystem, new List()); - Main.BodyDict[starSystem].Add(body); + body = existingBody; } else { - var existingBody = Main.BodyDict[starSystem] - .FirstOrDefault(x => x.Config.name == planetName && x.Mod.ModHelper.Manifest.UniqueName == mod.ModHelper.Manifest.UniqueName); - if (existingBody != null) - { - body = existingBody; - } - else - { - Main.BodyDict[starSystem].Add(body); - } + Main.BodyDict[starSystem].Add(body); } - - var system = new StarSystemConfig() - { - entryPositions = entryPositions? - .Select((pair) => new EntryPositionInfo() { id = pair.Key, position = pair.Value }) - .ToArray(), - curiosities = curiousityColours? - .Select((pair) => new CuriosityColorInfo() { id = pair.Key, color = MColor.FromColor(pair.Value.colour), highlightColor = MColor.FromColor(pair.Value.highlight) }) - .ToArray() - }; - - Main.Instance.LoadStarSystemConfig(starSystem, system, null, mod); - - RumorModeBuilder.AddShipLogXML(GameObject.FindObjectOfType(), xml, body); } - /// - /// Register your own builder that will act on the given GameObject by reading its raw json string - /// - /// - public void RegisterCustomBuilder(Action builder) => PlanetCreationHandler.CustomBuilders.Add(builder); + var system = new StarSystemConfig() + { + entryPositions = entryPositions? + .Select((pair) => new EntryPositionInfo() { id = pair.Key, position = pair.Value }) + .ToArray(), + curiosities = curiousityColours? + .Select((pair) => new CuriosityColorInfo() { id = pair.Key, color = MColor.FromColor(pair.Value.colour), highlightColor = MColor.FromColor(pair.Value.highlight) }) + .ToArray() + }; - public string GetTranslationForShipLog(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.SHIPLOG); + Main.Instance.LoadStarSystemConfig(starSystem, system, null, mod); - public string GetTranslationForDialogue(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.DIALOGUE); - - public string GetTranslationForUI(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.UI); - - public string GetTranslationForOtherText(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.OTHER); + RumorModeBuilder.AddShipLogXML(GameObject.FindObjectOfType(), xml, body); } + + /// + /// Register your own builder that will act on the given GameObject by reading its raw json string + /// + /// + public void RegisterCustomBuilder(Action builder) => PlanetCreationHandler.CustomBuilders.Add(builder); + + public string GetTranslationForShipLog(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.SHIPLOG); + + public string GetTranslationForDialogue(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.DIALOGUE); + + public string GetTranslationForUI(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.UI); + + public string GetTranslationForOtherText(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.OTHER); } diff --git a/NewHorizons/Utility/Files/AssetBundleUtilities.cs b/NewHorizons/Utility/Files/AssetBundleUtilities.cs index 182826ba..83bf1461 100644 --- a/NewHorizons/Utility/Files/AssetBundleUtilities.cs +++ b/NewHorizons/Utility/Files/AssetBundleUtilities.cs @@ -3,123 +3,145 @@ using OWML.Common; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using UnityEngine; -namespace NewHorizons.Utility.Files +namespace NewHorizons.Utility.Files; + +public static class AssetBundleUtilities { - public static class AssetBundleUtilities + public static Dictionary AssetBundles = new Dictionary(); + + public static void ClearCache() { - public static Dictionary AssetBundles = new Dictionary(); - - public static void ClearCache() + foreach (var pair in AssetBundles) { - foreach (var pair in AssetBundles) + if (!pair.Value.keepLoaded) { - if (pair.Value == null) NHLogger.LogError($"The asset bundle for {pair.Key} was null when trying to unload"); - else pair.Value.Unload(true); - } - AssetBundles.Clear(); - } - - public static T Load(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) where T : UnityEngine.Object - { - string key = Path.GetFileName(assetBundleRelativeDir); - T obj; - - try - { - AssetBundle bundle; - - if (AssetBundles.ContainsKey(key)) + if (pair.Value.bundle == null) { - bundle = AssetBundles[key]; + NHLogger.LogError($"The asset bundle for {pair.Key} was null when trying to unload"); } else { - var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir); - bundle = AssetBundle.LoadFromFile(completePath); - if (bundle == null) - { - NHLogger.LogError($"Couldn't load AssetBundle at [{completePath}] for [{mod.ModHelper.Manifest.Name}]"); - return null; - } - - AssetBundles[key] = bundle; - } - - obj = bundle.LoadAsset(pathInBundle); - } - catch (Exception e) - { - NHLogger.LogError($"Couldn't load asset {pathInBundle} from AssetBundle {assetBundleRelativeDir}:\n{e}"); - return null; - } - - return obj; - } - - public static GameObject LoadPrefab(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) - { - var prefab = Load(assetBundleRelativeDir, pathInBundle, mod); - - prefab.SetActive(false); - - ReplaceShaders(prefab); - - return prefab; - } - - public static void ReplaceShaders(GameObject prefab) - { - foreach (var renderer in prefab.GetComponentsInChildren(true)) - { - foreach (var material in renderer.sharedMaterials) - { - if (material == null) continue; - - var replacementShader = Shader.Find(material.shader.name); - if (replacementShader == null) continue; - - // preserve override tag and render queue (for Standard shader) - // keywords and properties are already preserved - if (material.renderQueue != material.shader.renderQueue) - { - var renderType = material.GetTag("RenderType", false); - var renderQueue = material.renderQueue; - material.shader = replacementShader; - material.SetOverrideTag("RenderType", renderType); - material.renderQueue = renderQueue; - } - else - { - material.shader = replacementShader; - } + pair.Value.bundle.Unload(true); } } - foreach (var trenderer in prefab.GetComponentsInChildren(true)) + } + AssetBundles = AssetBundles.Where(x => x.Value.keepLoaded).ToDictionary(x => x.Key, x => x.Value); + } + + public static void PreloadBundle(string assetBundleRelativeDir, IModBehaviour mod) + { + string key = Path.GetFileName(assetBundleRelativeDir); + var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir); + var request = AssetBundle.LoadFromFileAsync(completePath); + request.completed += _ => + { + NHLogger.Log($"Finished loading async bundle {assetBundleRelativeDir}"); + AssetBundles[key] = (request.assetBundle, true); + }; + } + + public static T Load(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) where T : UnityEngine.Object + { + string key = Path.GetFileName(assetBundleRelativeDir); + T obj; + + try + { + AssetBundle bundle; + + if (AssetBundles.ContainsKey(key)) { - foreach (var material in trenderer.sharedMaterials) + bundle = AssetBundles[key].bundle; + } + else + { + var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir); + bundle = AssetBundle.LoadFromFile(completePath); + if (bundle == null) { - if (material == null) continue; + NHLogger.LogError($"Couldn't load AssetBundle at [{completePath}] for [{mod.ModHelper.Manifest.Name}]"); + return null; + } - var replacementShader = Shader.Find(material.shader.name); - if (replacementShader == null) continue; + AssetBundles[key] = (bundle, false); + } - // preserve override tag and render queue (for Standard shader) - // keywords and properties are already preserved - if (material.renderQueue != material.shader.renderQueue) - { - var renderType = material.GetTag("RenderType", false); - var renderQueue = material.renderQueue; - material.shader = replacementShader; - material.SetOverrideTag("RenderType", renderType); - material.renderQueue = renderQueue; - } - else - { - material.shader = replacementShader; - } + obj = bundle.LoadAsset(pathInBundle); + } + catch (Exception e) + { + NHLogger.LogError($"Couldn't load asset {pathInBundle} from AssetBundle {assetBundleRelativeDir}:\n{e}"); + return null; + } + + return obj; + } + + public static GameObject LoadPrefab(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) + { + var prefab = Load(assetBundleRelativeDir, pathInBundle, mod); + + prefab.SetActive(false); + + ReplaceShaders(prefab); + + return prefab; + } + + public static void ReplaceShaders(GameObject prefab) + { + foreach (var renderer in prefab.GetComponentsInChildren(true)) + { + foreach (var material in renderer.sharedMaterials) + { + if (material == null) continue; + + var replacementShader = Shader.Find(material.shader.name); + if (replacementShader == null) continue; + + // preserve override tag and render queue (for Standard shader) + // keywords and properties are already preserved + if (material.renderQueue != material.shader.renderQueue) + { + var renderType = material.GetTag("RenderType", false); + var renderQueue = material.renderQueue; + material.shader = replacementShader; + material.SetOverrideTag("RenderType", renderType); + material.renderQueue = renderQueue; + } + else + { + material.shader = replacementShader; + } + } + } + + foreach (var trenderer in prefab.GetComponentsInChildren(true)) + { + foreach (var material in trenderer.sharedMaterials) + { + if (material == null) continue; + + var replacementShader = Shader.Find(material.shader.name); + if (replacementShader == null) continue; + + // preserve override tag and render queue (for Standard shader) + // keywords and properties are already preserved + if (material.renderQueue != material.shader.renderQueue) + { + var renderType = material.GetTag("RenderType", false); + var renderQueue = material.renderQueue; + material.shader = replacementShader; + material.SetOverrideTag("RenderType", renderType); + material.renderQueue = renderQueue; + } + else + { + material.shader = replacementShader; } } } From 73b818d0b46f3a901c30c963d09d3822bfd83077 Mon Sep 17 00:00:00 2001 From: Ben C Date: Sat, 3 Feb 2024 21:11:25 +0000 Subject: [PATCH 2/4] Updated Schemas --- NewHorizons/Schemas/addon_manifest_schema.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/NewHorizons/Schemas/addon_manifest_schema.json b/NewHorizons/Schemas/addon_manifest_schema.json index aada816a..dec9022f 100644 --- a/NewHorizons/Schemas/addon_manifest_schema.json +++ b/NewHorizons/Schemas/addon_manifest_schema.json @@ -27,6 +27,17 @@ "type": "boolean", "description": "If popupMessage is set, should it repeat every time the game starts or only once" }, + "preloadAssetBundles": { + "type": "array", + "description": "These asset bundles will be loaded on the title screen and stay loaded. Will improve initial load time at the cost of increased memory use.\nThe path is the relative directory of the asset bundle in the mod folder.", + "items": { + "type": "string" + } + }, + "subtitlePath": { + "type": "string", + "description": "The path to the addons subtitle for the main menu.\nDefaults to \"subtitle.png\"." + }, "$schema": { "type": "string", "description": "The schema to validate with" From f11099d62f48119ab5ead9b6257e822092bd44f8 Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 3 Feb 2024 16:17:20 -0500 Subject: [PATCH 3/4] Don't impulsively change namespace brackets --- NewHorizons/NewHorizonsApi.cs | 571 +++++++++--------- .../Utility/Files/AssetBundleUtilities.cs | 229 +++---- 2 files changed, 401 insertions(+), 399 deletions(-) diff --git a/NewHorizons/NewHorizonsApi.cs b/NewHorizons/NewHorizonsApi.cs index d6d78ed9..a96673d5 100644 --- a/NewHorizons/NewHorizonsApi.cs +++ b/NewHorizons/NewHorizonsApi.cs @@ -26,314 +26,315 @@ using UnityEngine; using UnityEngine.Events; using static NewHorizons.External.Modules.ShipLogModule; -namespace NewHorizons; - -public class NewHorizonsApi : INewHorizons +namespace NewHorizons { - [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] - public void Create(Dictionary config) + public class NewHorizonsApi : INewHorizons { - Create(config, null); - } - - [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] - public void Create(Dictionary config, IModBehaviour mod) - { - try + [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] + public void Create(Dictionary config) { - var name = (string)config["Name"]; + Create(config, null); + } - NHLogger.LogWarning($"Recieved API request to create planet [{name}]"); - - if (name == null) return; - - var relativePath = $"temp/{name}.json"; - var fullPath = Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, relativePath); - if (!Directory.Exists(Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "temp"))) + [Obsolete("Create(Dictionary config) is deprecated, please use LoadConfigs(IModBehaviour mod) instead")] + public void Create(Dictionary config, IModBehaviour mod) + { + try { - Directory.CreateDirectory(Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "temp")); + var name = (string)config["Name"]; + + NHLogger.LogWarning($"Recieved API request to create planet [{name}]"); + + if (name == null) return; + + var relativePath = $"temp/{name}.json"; + var fullPath = Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, relativePath); + if (!Directory.Exists(Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "temp"))) + { + Directory.CreateDirectory(Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "temp")); + } + JsonHelper.SaveJsonObject(fullPath, config); + var body = Main.Instance.LoadConfig(Main.Instance, relativePath); + File.Delete(fullPath); + + // Update it to point to their mod for textures and stuff + body.Mod = mod ?? Main.Instance; + + if (!Main.BodyDict.ContainsKey(body.Config.starSystem)) Main.BodyDict.Add(body.Config.starSystem, new List()); + Main.BodyDict[body.Config.starSystem].Add(body); } - JsonHelper.SaveJsonObject(fullPath, config); - var body = Main.Instance.LoadConfig(Main.Instance, relativePath); - File.Delete(fullPath); - - // Update it to point to their mod for textures and stuff - body.Mod = mod ?? Main.Instance; - - if (!Main.BodyDict.ContainsKey(body.Config.starSystem)) Main.BodyDict.Add(body.Config.starSystem, new List()); - Main.BodyDict[body.Config.starSystem].Add(body); - } - catch (Exception ex) - { - NHLogger.LogError($"Error in Create API:\n{ex}"); - } - } - - [Obsolete("SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) is deprecated, please use SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) instead")] - public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, - float scale, bool alignRadial) - { - return SpawnObject(null, planet, sector, propToCopyPath, position, eulerAngles, scale, alignRadial); - } - - public void LoadConfigs(IModBehaviour mod) - { - Main.Instance.LoadConfigs(mod); - } - - public GameObject GetPlanet(string name) - { - return Main.BodyDict.Values.SelectMany(x => x)?.ToList()?.FirstOrDefault(x => x.Config.name == name)?.Object; - } - - 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) - { - if (!Main.SystemDict.ContainsKey(name)) return false; - - Main.Instance.SetDefaultSystem(name); - return true; - } - - public bool ChangeCurrentStarSystem(string name) - { - if (!Main.SystemDict.ContainsKey(name)) return false; - - Main.Instance.ChangeCurrentStarSystem(name); - return true; - } - - public string[] GetInstalledAddons() - { - try - { - return Main.MountedAddons.Select(x => x?.ModHelper?.Manifest?.UniqueName).ToArray(); - } - catch (Exception ex) - { - NHLogger.LogError($"Couldn't get installed addons:\n{ex}"); - return new string[] { }; - } - } - - private static object QueryJson(Type outType, string filePath, string jsonPath) - { - if (filePath == "") return null; - try - { - var jsonText = File.ReadAllText(filePath); - var jsonData = JObject.Parse(jsonText); - return jsonData.SelectToken(jsonPath)?.ToObject(outType); - } - catch (FileNotFoundException) - { - return null; - } - catch (JsonException e) - { - NHLogger.LogError(e.ToString()); - return null; - } - } - - public object QueryBody(Type outType, string bodyName, string jsonPath) - { - var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == bodyName); - return planet == null - ? null - : QueryJson(outType, Path.Combine(planet.Mod.ModHelper.Manifest.ModFolderPath, planet.RelativePath), jsonPath); - } - - public T QueryBody(string bodyName, string jsonPath) - { - var data = QueryBody(typeof(T), bodyName, jsonPath); - if (data is T result) - { - return result; - } - return default; - } - - public object QuerySystem(Type outType, string jsonPath) - { - var system = Main.SystemDict[Main.Instance.CurrentStarSystem]; - return system == null - ? null - : QueryJson(outType, Path.Combine(system.Mod.ModHelper.Manifest.ModFolderPath, system.RelativePath), jsonPath); - } - - public T QuerySystem(string jsonPath) - { - var data = QuerySystem(typeof(T), jsonPath); - if (data is T result) - { - return result; - } - return default; - } - - public GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, - float scale, bool alignRadial) - { - var prefab = SearchUtilities.Find(propToCopyPath); - var detailInfo = new DetailInfo() - { - position = position, - rotation = eulerAngles, - scale = scale, - alignRadial = alignRadial - }; - return DetailBuilder.Make(planet, sector, mod, prefab, detailInfo); - } - - public 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 = "") - { - var info = new SignalInfo() - { - audio = audio, - detectionRadius = detectionRadius, - frequency = frequency, - identificationRadius = identificationRadius, - insideCloak = insideCloak, - name = name, - onlyAudibleToScope = onlyAudibleToScope, - position = Vector3.zero, - reveals = reveals, - sourceRadius = sourceRadius - }; - - return SignalBuilder.Make(root, null, info, mod).GetComponent(); - } - - public (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) - { - var info = new DialogueInfo() - { - blockAfterPersistentCondition = blockAfterPersistentCondition, - lookAtRadius = lookAtRadius, - pathToAnimController = pathToAnimController, - position = Vector3.zero, - radius = radius, - range = range, - xmlFile = xmlFile, - remoteTrigger = remoteTriggerRadius > 0f ? new RemoteTriggerInfo() + catch (Exception ex) { - position = null, - radius = remoteTriggerRadius, - } : null, - }; - - return DialogueBuilder.Make(root, null, info, mod); - } - - public void CreatePlanet(string config, IModBehaviour mod) - { - try - { - var planet = JsonConvert.DeserializeObject(config); - if (planet == null) - { - NHLogger.LogError($"Couldn't load planet via API. Is your Json formatted correctly? {config}"); - return; + NHLogger.LogError($"Error in Create API:\n{ex}"); } - - var body = Main.Instance.RegisterPlanetConfig(planet, mod, null); - - if (!Main.BodyDict.ContainsKey(body.Config.starSystem)) Main.BodyDict.Add(body.Config.starSystem, new List()); - Main.BodyDict[body.Config.starSystem].Add(body); } - catch (Exception ex) + + [Obsolete("SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) is deprecated, please use SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, float scale, bool alignRadial) instead")] + public GameObject SpawnObject(GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, + float scale, bool alignRadial) { - NHLogger.LogError($"Error in CreatePlanet API:\n{ex}"); + return SpawnObject(null, planet, sector, propToCopyPath, position, eulerAngles, scale, alignRadial); } - } - public void DefineStarSystem(string name, string config, IModBehaviour mod) - { - var starSystemConfig = JsonConvert.DeserializeObject(config); - Main.Instance.LoadStarSystemConfig(name, starSystemConfig, null, mod); - } - - public (CharacterDialogueTree, RemoteDialogueTrigger) CreateDialogueFromXML(string textAssetID, string xml, string dialogueInfo, GameObject planetGO) - { - var info = JsonConvert.DeserializeObject(dialogueInfo); - return DialogueBuilder.Make(planetGO, null, info, xml, textAssetID); - } - - public GameObject CreateNomaiText(string xml, string textInfo, GameObject planetGO) - { - var info = JsonConvert.DeserializeObject(textInfo); - return TranslatorTextBuilder.Make(planetGO, null, info, null, xml); - } - - public void AddShipLogXML(IModBehaviour mod, XElement xml, string planetName, string imageFolder, Dictionary entryPositions, Dictionary curiousityColours) - { - // This method has to be called each time the ship log manager is created, i.e. each time a system loads so it will only ever be relevant to the current one. - var starSystem = Main.Instance.CurrentStarSystem; - - var body = new NewHorizonsBody(new PlanetConfig() + public void LoadConfigs(IModBehaviour mod) { - name = planetName, - starSystem = starSystem, - ShipLog = new ShipLogModule() + Main.Instance.LoadConfigs(mod); + } + + public GameObject GetPlanet(string name) + { + return Main.BodyDict.Values.SelectMany(x => x)?.ToList()?.FirstOrDefault(x => x.Config.name == name)?.Object; + } + + 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) + { + if (!Main.SystemDict.ContainsKey(name)) return false; + + Main.Instance.SetDefaultSystem(name); + return true; + } + + public bool ChangeCurrentStarSystem(string name) + { + if (!Main.SystemDict.ContainsKey(name)) return false; + + Main.Instance.ChangeCurrentStarSystem(name); + return true; + } + + public string[] GetInstalledAddons() + { + try { - spriteFolder = imageFolder + return Main.MountedAddons.Select(x => x?.ModHelper?.Manifest?.UniqueName).ToArray(); } - }, mod); - - if (!Main.BodyDict.ContainsKey(starSystem)) - { - Main.BodyDict.Add(starSystem, new List()); - Main.BodyDict[starSystem].Add(body); - } - else - { - var existingBody = Main.BodyDict[starSystem] - .FirstOrDefault(x => x.Config.name == planetName && x.Mod.ModHelper.Manifest.UniqueName == mod.ModHelper.Manifest.UniqueName); - if (existingBody != null) + catch (Exception ex) { - body = existingBody; + NHLogger.LogError($"Couldn't get installed addons:\n{ex}"); + return new string[] { }; + } + } + + private static object QueryJson(Type outType, string filePath, string jsonPath) + { + if (filePath == "") return null; + try + { + var jsonText = File.ReadAllText(filePath); + var jsonData = JObject.Parse(jsonText); + return jsonData.SelectToken(jsonPath)?.ToObject(outType); + } + catch (FileNotFoundException) + { + return null; + } + catch (JsonException e) + { + NHLogger.LogError(e.ToString()); + return null; + } + } + + public object QueryBody(Type outType, string bodyName, string jsonPath) + { + var planet = Main.BodyDict[Main.Instance.CurrentStarSystem].Find((b) => b.Config.name == bodyName); + return planet == null + ? null + : QueryJson(outType, Path.Combine(planet.Mod.ModHelper.Manifest.ModFolderPath, planet.RelativePath), jsonPath); + } + + public T QueryBody(string bodyName, string jsonPath) + { + var data = QueryBody(typeof(T), bodyName, jsonPath); + if (data is T result) + { + return result; + } + return default; + } + + public object QuerySystem(Type outType, string jsonPath) + { + var system = Main.SystemDict[Main.Instance.CurrentStarSystem]; + return system == null + ? null + : QueryJson(outType, Path.Combine(system.Mod.ModHelper.Manifest.ModFolderPath, system.RelativePath), jsonPath); + } + + public T QuerySystem(string jsonPath) + { + var data = QuerySystem(typeof(T), jsonPath); + if (data is T result) + { + return result; + } + return default; + } + + public GameObject SpawnObject(IModBehaviour mod, GameObject planet, Sector sector, string propToCopyPath, Vector3 position, Vector3 eulerAngles, + float scale, bool alignRadial) + { + var prefab = SearchUtilities.Find(propToCopyPath); + var detailInfo = new DetailInfo() + { + position = position, + rotation = eulerAngles, + scale = scale, + alignRadial = alignRadial + }; + return DetailBuilder.Make(planet, sector, mod, prefab, detailInfo); + } + + public 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 = "") + { + var info = new SignalInfo() + { + audio = audio, + detectionRadius = detectionRadius, + frequency = frequency, + identificationRadius = identificationRadius, + insideCloak = insideCloak, + name = name, + onlyAudibleToScope = onlyAudibleToScope, + position = Vector3.zero, + reveals = reveals, + sourceRadius = sourceRadius + }; + + return SignalBuilder.Make(root, null, info, mod).GetComponent(); + } + + public (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) + { + var info = new DialogueInfo() + { + blockAfterPersistentCondition = blockAfterPersistentCondition, + lookAtRadius = lookAtRadius, + pathToAnimController = pathToAnimController, + position = Vector3.zero, + radius = radius, + range = range, + xmlFile = xmlFile, + remoteTrigger = remoteTriggerRadius > 0f ? new RemoteTriggerInfo() + { + position = null, + radius = remoteTriggerRadius, + } : null, + }; + + return DialogueBuilder.Make(root, null, info, mod); + } + + public void CreatePlanet(string config, IModBehaviour mod) + { + try + { + var planet = JsonConvert.DeserializeObject(config); + if (planet == null) + { + NHLogger.LogError($"Couldn't load planet via API. Is your Json formatted correctly? {config}"); + return; + } + + var body = Main.Instance.RegisterPlanetConfig(planet, mod, null); + + if (!Main.BodyDict.ContainsKey(body.Config.starSystem)) Main.BodyDict.Add(body.Config.starSystem, new List()); + Main.BodyDict[body.Config.starSystem].Add(body); + } + catch (Exception ex) + { + NHLogger.LogError($"Error in CreatePlanet API:\n{ex}"); + } + } + + public void DefineStarSystem(string name, string config, IModBehaviour mod) + { + var starSystemConfig = JsonConvert.DeserializeObject(config); + Main.Instance.LoadStarSystemConfig(name, starSystemConfig, null, mod); + } + + public (CharacterDialogueTree, RemoteDialogueTrigger) CreateDialogueFromXML(string textAssetID, string xml, string dialogueInfo, GameObject planetGO) + { + var info = JsonConvert.DeserializeObject(dialogueInfo); + return DialogueBuilder.Make(planetGO, null, info, xml, textAssetID); + } + + public GameObject CreateNomaiText(string xml, string textInfo, GameObject planetGO) + { + var info = JsonConvert.DeserializeObject(textInfo); + return TranslatorTextBuilder.Make(planetGO, null, info, null, xml); + } + + public void AddShipLogXML(IModBehaviour mod, XElement xml, string planetName, string imageFolder, Dictionary entryPositions, Dictionary curiousityColours) + { + // This method has to be called each time the ship log manager is created, i.e. each time a system loads so it will only ever be relevant to the current one. + var starSystem = Main.Instance.CurrentStarSystem; + + var body = new NewHorizonsBody(new PlanetConfig() + { + name = planetName, + starSystem = starSystem, + ShipLog = new ShipLogModule() + { + spriteFolder = imageFolder + } + }, mod); + + if (!Main.BodyDict.ContainsKey(starSystem)) + { + Main.BodyDict.Add(starSystem, new List()); + Main.BodyDict[starSystem].Add(body); } else { - Main.BodyDict[starSystem].Add(body); + var existingBody = Main.BodyDict[starSystem] + .FirstOrDefault(x => x.Config.name == planetName && x.Mod.ModHelper.Manifest.UniqueName == mod.ModHelper.Manifest.UniqueName); + if (existingBody != null) + { + body = existingBody; + } + else + { + Main.BodyDict[starSystem].Add(body); + } } + + var system = new StarSystemConfig() + { + entryPositions = entryPositions? + .Select((pair) => new EntryPositionInfo() { id = pair.Key, position = pair.Value }) + .ToArray(), + curiosities = curiousityColours? + .Select((pair) => new CuriosityColorInfo() { id = pair.Key, color = MColor.FromColor(pair.Value.colour), highlightColor = MColor.FromColor(pair.Value.highlight) }) + .ToArray() + }; + + Main.Instance.LoadStarSystemConfig(starSystem, system, null, mod); + + RumorModeBuilder.AddShipLogXML(GameObject.FindObjectOfType(), xml, body); } - var system = new StarSystemConfig() - { - entryPositions = entryPositions? - .Select((pair) => new EntryPositionInfo() { id = pair.Key, position = pair.Value }) - .ToArray(), - curiosities = curiousityColours? - .Select((pair) => new CuriosityColorInfo() { id = pair.Key, color = MColor.FromColor(pair.Value.colour), highlightColor = MColor.FromColor(pair.Value.highlight) }) - .ToArray() - }; + /// + /// Register your own builder that will act on the given GameObject by reading its raw json string + /// + /// + public void RegisterCustomBuilder(Action builder) => PlanetCreationHandler.CustomBuilders.Add(builder); - Main.Instance.LoadStarSystemConfig(starSystem, system, null, mod); + public string GetTranslationForShipLog(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.SHIPLOG); - RumorModeBuilder.AddShipLogXML(GameObject.FindObjectOfType(), xml, body); + public string GetTranslationForDialogue(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.DIALOGUE); + + public string GetTranslationForUI(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.UI); + + public string GetTranslationForOtherText(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.OTHER); } - - /// - /// Register your own builder that will act on the given GameObject by reading its raw json string - /// - /// - public void RegisterCustomBuilder(Action builder) => PlanetCreationHandler.CustomBuilders.Add(builder); - - public string GetTranslationForShipLog(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.SHIPLOG); - - public string GetTranslationForDialogue(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.DIALOGUE); - - public string GetTranslationForUI(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.UI); - - public string GetTranslationForOtherText(string text) => TranslationHandler.GetTranslation(text, TranslationHandler.TextType.OTHER); } diff --git a/NewHorizons/Utility/Files/AssetBundleUtilities.cs b/NewHorizons/Utility/Files/AssetBundleUtilities.cs index 83bf1461..72bb05c2 100644 --- a/NewHorizons/Utility/Files/AssetBundleUtilities.cs +++ b/NewHorizons/Utility/Files/AssetBundleUtilities.cs @@ -6,144 +6,145 @@ using System.IO; using System.Linq; using UnityEngine; -namespace NewHorizons.Utility.Files; - -public static class AssetBundleUtilities +namespace NewHorizons.Utility.Files { - public static Dictionary AssetBundles = new Dictionary(); - - public static void ClearCache() + public static class AssetBundleUtilities { - foreach (var pair in AssetBundles) + public static Dictionary AssetBundles = new Dictionary(); + + public static void ClearCache() { - if (!pair.Value.keepLoaded) + foreach (var pair in AssetBundles) { - if (pair.Value.bundle == null) + if (!pair.Value.keepLoaded) { - NHLogger.LogError($"The asset bundle for {pair.Key} was null when trying to unload"); + if (pair.Value.bundle == null) + { + NHLogger.LogError($"The asset bundle for {pair.Key} was null when trying to unload"); + } + else + { + pair.Value.bundle.Unload(true); + } + } + + } + AssetBundles = AssetBundles.Where(x => x.Value.keepLoaded).ToDictionary(x => x.Key, x => x.Value); + } + + public static void PreloadBundle(string assetBundleRelativeDir, IModBehaviour mod) + { + string key = Path.GetFileName(assetBundleRelativeDir); + var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir); + var request = AssetBundle.LoadFromFileAsync(completePath); + request.completed += _ => + { + NHLogger.Log($"Finished loading async bundle {assetBundleRelativeDir}"); + AssetBundles[key] = (request.assetBundle, true); + }; + } + + public static T Load(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) where T : UnityEngine.Object + { + string key = Path.GetFileName(assetBundleRelativeDir); + T obj; + + try + { + AssetBundle bundle; + + if (AssetBundles.ContainsKey(key)) + { + bundle = AssetBundles[key].bundle; } else { - pair.Value.bundle.Unload(true); + var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir); + bundle = AssetBundle.LoadFromFile(completePath); + if (bundle == null) + { + NHLogger.LogError($"Couldn't load AssetBundle at [{completePath}] for [{mod.ModHelper.Manifest.Name}]"); + return null; + } + + AssetBundles[key] = (bundle, false); } + + obj = bundle.LoadAsset(pathInBundle); } - - } - AssetBundles = AssetBundles.Where(x => x.Value.keepLoaded).ToDictionary(x => x.Key, x => x.Value); - } - - public static void PreloadBundle(string assetBundleRelativeDir, IModBehaviour mod) - { - string key = Path.GetFileName(assetBundleRelativeDir); - var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir); - var request = AssetBundle.LoadFromFileAsync(completePath); - request.completed += _ => - { - NHLogger.Log($"Finished loading async bundle {assetBundleRelativeDir}"); - AssetBundles[key] = (request.assetBundle, true); - }; - } - - public static T Load(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) where T : UnityEngine.Object - { - string key = Path.GetFileName(assetBundleRelativeDir); - T obj; - - try - { - AssetBundle bundle; - - if (AssetBundles.ContainsKey(key)) + catch (Exception e) { - bundle = AssetBundles[key].bundle; - } - else - { - var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir); - bundle = AssetBundle.LoadFromFile(completePath); - if (bundle == null) - { - NHLogger.LogError($"Couldn't load AssetBundle at [{completePath}] for [{mod.ModHelper.Manifest.Name}]"); - return null; - } - - AssetBundles[key] = (bundle, false); + NHLogger.LogError($"Couldn't load asset {pathInBundle} from AssetBundle {assetBundleRelativeDir}:\n{e}"); + return null; } - obj = bundle.LoadAsset(pathInBundle); - } - catch (Exception e) - { - NHLogger.LogError($"Couldn't load asset {pathInBundle} from AssetBundle {assetBundleRelativeDir}:\n{e}"); - return null; + return obj; } - return obj; - } - - public static GameObject LoadPrefab(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) - { - var prefab = Load(assetBundleRelativeDir, pathInBundle, mod); - - prefab.SetActive(false); - - ReplaceShaders(prefab); - - return prefab; - } - - public static void ReplaceShaders(GameObject prefab) - { - foreach (var renderer in prefab.GetComponentsInChildren(true)) + public static GameObject LoadPrefab(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) { - foreach (var material in renderer.sharedMaterials) + var prefab = Load(assetBundleRelativeDir, pathInBundle, mod); + + prefab.SetActive(false); + + ReplaceShaders(prefab); + + return prefab; + } + + public static void ReplaceShaders(GameObject prefab) + { + foreach (var renderer in prefab.GetComponentsInChildren(true)) { - if (material == null) continue; - - var replacementShader = Shader.Find(material.shader.name); - if (replacementShader == null) continue; - - // preserve override tag and render queue (for Standard shader) - // keywords and properties are already preserved - if (material.renderQueue != material.shader.renderQueue) + foreach (var material in renderer.sharedMaterials) { - var renderType = material.GetTag("RenderType", false); - var renderQueue = material.renderQueue; - material.shader = replacementShader; - material.SetOverrideTag("RenderType", renderType); - material.renderQueue = renderQueue; - } - else - { - material.shader = replacementShader; + if (material == null) continue; + + var replacementShader = Shader.Find(material.shader.name); + if (replacementShader == null) continue; + + // preserve override tag and render queue (for Standard shader) + // keywords and properties are already preserved + if (material.renderQueue != material.shader.renderQueue) + { + var renderType = material.GetTag("RenderType", false); + var renderQueue = material.renderQueue; + material.shader = replacementShader; + material.SetOverrideTag("RenderType", renderType); + material.renderQueue = renderQueue; + } + else + { + material.shader = replacementShader; + } } } - } - foreach (var trenderer in prefab.GetComponentsInChildren(true)) - { - foreach (var material in trenderer.sharedMaterials) + foreach (var trenderer in prefab.GetComponentsInChildren(true)) { - if (material == null) continue; - - var replacementShader = Shader.Find(material.shader.name); - if (replacementShader == null) continue; - - // preserve override tag and render queue (for Standard shader) - // keywords and properties are already preserved - if (material.renderQueue != material.shader.renderQueue) + foreach (var material in trenderer.sharedMaterials) { - var renderType = material.GetTag("RenderType", false); - var renderQueue = material.renderQueue; - material.shader = replacementShader; - material.SetOverrideTag("RenderType", renderType); - material.renderQueue = renderQueue; - } - else - { - material.shader = replacementShader; + if (material == null) continue; + + var replacementShader = Shader.Find(material.shader.name); + if (replacementShader == null) continue; + + // preserve override tag and render queue (for Standard shader) + // keywords and properties are already preserved + if (material.renderQueue != material.shader.renderQueue) + { + var renderType = material.GetTag("RenderType", false); + var renderQueue = material.renderQueue; + material.shader = replacementShader; + material.SetOverrideTag("RenderType", renderType); + material.renderQueue = renderQueue; + } + else + { + material.shader = replacementShader; + } } } } } -} +} \ No newline at end of file From e73afbebc713ccae33f4e224a47f275a6195c99d Mon Sep 17 00:00:00 2001 From: JohnCorby Date: Sat, 3 Feb 2024 15:20:51 -0800 Subject: [PATCH 4/4] wait until preload bundles are loaded before loading scene --- .../SubmitActionLoadScenePatches.cs | 49 ++++++++++++++++++- .../Utility/Files/AssetBundleUtilities.cs | 14 +++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/NewHorizons/Patches/EyeScenePatches/SubmitActionLoadScenePatches.cs b/NewHorizons/Patches/EyeScenePatches/SubmitActionLoadScenePatches.cs index 9feba169..891f439c 100644 --- a/NewHorizons/Patches/EyeScenePatches/SubmitActionLoadScenePatches.cs +++ b/NewHorizons/Patches/EyeScenePatches/SubmitActionLoadScenePatches.cs @@ -1,4 +1,5 @@ using HarmonyLib; +using NewHorizons.Utility.Files; using NewHorizons.Utility.OWML; namespace NewHorizons.Patches.EyeScenePatches @@ -6,10 +7,18 @@ namespace NewHorizons.Patches.EyeScenePatches [HarmonyPatch(typeof(SubmitActionLoadScene))] public static class SubmitActionLoadScenePatches { + // To call the base method + [HarmonyReversePatch] + [HarmonyPatch(typeof(SubmitActionConfirm), nameof(SubmitActionConfirm.ConfirmSubmit))] + public static void SubmitActionConfirm_ConfirmSubmit(SubmitActionConfirm instance) { } + + [HarmonyPrefix] [HarmonyPatch(nameof(SubmitActionLoadScene.ConfirmSubmit))] - public static void SubmitActionLoadScene_ConfirmSubmit(SubmitActionLoadScene __instance) + public static bool SubmitActionLoadScene_ConfirmSubmit(SubmitActionLoadScene __instance) { + if (__instance._receivedSubmitAction) return false; + // Title screen can warp you to eye and cause problems. if (__instance._sceneToLoad == SubmitActionLoadScene.LoadableScenes.EYE) { @@ -17,6 +26,44 @@ namespace NewHorizons.Patches.EyeScenePatches Main.Instance.IsWarpingBackToEye = true; __instance._sceneToLoad = SubmitActionLoadScene.LoadableScenes.GAME; } + + // modified from patched function + SubmitActionConfirm_ConfirmSubmit(__instance); + __instance._receivedSubmitAction = true; + Locator.GetMenuInputModule().DisableInputs(); + + Delay.RunWhen(() => + { + // update text. just use 0% + __instance.ResetStringBuilder(); + __instance._nowLoadingSB.Append(UITextLibrary.GetString(UITextType.LoadingMessage)); + __instance._nowLoadingSB.Append(0.ToString("P0")); + __instance._loadingText.text = __instance._nowLoadingSB.ToString(); + + return AssetBundleUtilities.AreRequiredAssetsLoaded(); + }, () => + { + switch (__instance._sceneToLoad) + { + case SubmitActionLoadScene.LoadableScenes.GAME: + LoadManager.LoadSceneAsync(OWScene.SolarSystem, false, LoadManager.FadeType.ToBlack, 1f, false); + __instance.ResetStringBuilder(); + __instance._waitingOnStreaming = true; + break; + case SubmitActionLoadScene.LoadableScenes.EYE: + LoadManager.LoadSceneAsync(OWScene.EyeOfTheUniverse, true, LoadManager.FadeType.ToBlack, 1f, false); + __instance.ResetStringBuilder(); + break; + case SubmitActionLoadScene.LoadableScenes.TITLE: + LoadManager.LoadScene(OWScene.TitleScreen, LoadManager.FadeType.ToBlack, 2f, true); + break; + case SubmitActionLoadScene.LoadableScenes.CREDITS: + LoadManager.LoadScene(OWScene.Credits_Fast, LoadManager.FadeType.ToBlack, 1f, false); + break; + } + }); + + return false; } } } diff --git a/NewHorizons/Utility/Files/AssetBundleUtilities.cs b/NewHorizons/Utility/Files/AssetBundleUtilities.cs index 72bb05c2..104d7393 100644 --- a/NewHorizons/Utility/Files/AssetBundleUtilities.cs +++ b/NewHorizons/Utility/Files/AssetBundleUtilities.cs @@ -10,7 +10,9 @@ namespace NewHorizons.Utility.Files { public static class AssetBundleUtilities { - public static Dictionary AssetBundles = new Dictionary(); + public static Dictionary AssetBundles = new(); + + private static readonly List _loadingBundles = new(); public static void ClearCache() { @@ -37,13 +39,21 @@ namespace NewHorizons.Utility.Files string key = Path.GetFileName(assetBundleRelativeDir); var completePath = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, assetBundleRelativeDir); var request = AssetBundle.LoadFromFileAsync(completePath); + _loadingBundles.Add(request); + NHLogger.Log($"Preloading bundle {assetBundleRelativeDir} - {_loadingBundles.Count} left"); request.completed += _ => { - NHLogger.Log($"Finished loading async bundle {assetBundleRelativeDir}"); + _loadingBundles.Remove(request); + NHLogger.Log($"Finshed preloading bundle {assetBundleRelativeDir} - {_loadingBundles.Count} left"); AssetBundles[key] = (request.assetBundle, true); }; } + /// + /// are preloaded bundles done loading? + /// + public static bool AreRequiredAssetsLoaded() => _loadingBundles.Count == 0; + public static T Load(string assetBundleRelativeDir, string pathInBundle, IModBehaviour mod) where T : UnityEngine.Object { string key = Path.GetFileName(assetBundleRelativeDir);