diff --git a/NewHorizons/Builder/Props/ProjectionBuilder.cs b/NewHorizons/Builder/Props/ProjectionBuilder.cs index 7df34b6c..2ebf64fc 100644 --- a/NewHorizons/Builder/Props/ProjectionBuilder.cs +++ b/NewHorizons/Builder/Props/ProjectionBuilder.cs @@ -150,28 +150,22 @@ namespace NewHorizons.Builder.Props // Now we replace the slides int slidesCount = info.slides.Length; - var slideCollection = new SlideCollection(slidesCount); + SlideCollection slideCollection = new NHSlideCollection(slidesCount, mod, info.slides.Select(x => x.imagePath).ToArray()); slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null // We can fit 16 slides max into an atlas var textures = new Texture2D[slidesCount > 16 ? 16 : slidesCount]; - var (invImageLoader, atlasImageLoader, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, true); + // Slide reels dynamically load the inverted cached images when needed. We only need to load raw images to generate the cache or atlases + var (_, atlasImageLoader, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, false, true, false); // If the cache doesn't exist it will be created here, slide reels only use the base image loader for cache creation so delete the images after to free memory - imageLoader.deleteTexturesWhenDone = !CacheExists(mod); + imageLoader.deleteTexturesWhenDone = true; var key = GetUniqueSlideReelID(mod, info.slides); - if (invImageLoader != null && atlasImageLoader != null) + if (atlasImageLoader != null) { - // Loading directly from cache - invImageLoader.imageLoadedEvent.AddListener( - (Texture2D tex, int index, string originalPath) => - { - slideCollection.slides[index]._image = tex; - } - ); atlasImageLoader.imageLoadedEvent.AddListener( (Texture2D tex, int _, string originalPath) => { @@ -202,6 +196,7 @@ namespace NewHorizons.Builder.Props { var time = DateTime.Now; + // inverted slides will be loaded for the whole loop but its fine since this is only when generating cache slideCollection.slides[index]._image = ImageUtilities.InvertSlideReel(mod, tex, originalPath); NHLogger.LogVerbose($"Slide reel make reel invert texture {(DateTime.Now - time).TotalMilliseconds}ms"); // Track the first 16 to put on the slide reel object @@ -215,8 +210,14 @@ namespace NewHorizons.Builder.Props var slidesBack = slideReelObj.GetComponentInChildren(true).transform.Find("Slides_Back").GetComponent(); var slidesFront = slideReelObj.GetComponentInChildren(true).transform.Find("Slides_Front").GetComponent(); - // Now put together the textures into a 4x4 thing for the materials - var reelTexture = ImageUtilities.MakeReelTexture(mod, textures, key); + // Now put together the textures into a 4x4 thing for the materials #888 + var displayTextures = textures; + if (info.displaySlides != null && info.displaySlides.Length > 0) + { + displayTextures = info.displaySlides.Select(x => textures[x]).ToArray(); + } + + var reelTexture = ImageUtilities.MakeReelTexture(mod, displayTextures, key); slidesBack.material.mainTexture = reelTexture; slidesBack.material.SetTexture(EmissionMap, reelTexture); slidesBack.material.name = reelTexture.name; @@ -348,15 +349,16 @@ namespace NewHorizons.Builder.Props var toDestroy = autoProjector.GetComponent(); var slideCollectionContainer = autoProjector.gameObject.AddComponent(); + slideCollectionContainer.doAsyncLoading = false; autoProjector._slideCollectionItem = slideCollectionContainer; Component.DestroyImmediate(toDestroy); // Now we replace the slides int slidesCount = info.slides.Length; - var slideCollection = new SlideCollection(slidesCount); + SlideCollection slideCollection = new NHSlideCollection(slidesCount, mod, info.slides.Select(x => x.imagePath).ToArray()); slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null - var (invImageLoader, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, false); + var (invImageLoader, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, true, false, false); // Autoprojector only uses the inverted images so the original can be destroyed if they are loaded (when creating the cached inverted images) imageLoader.deleteTexturesWhenDone = true; @@ -381,6 +383,7 @@ namespace NewHorizons.Builder.Props } slideCollectionContainer.slideCollection = slideCollection; + slideCollectionContainer._playWithShipLogFacts = Array.Empty(); // else it NREs in container initialize StreamingHandler.SetUpStreaming(projectorObj, sector); @@ -402,9 +405,9 @@ namespace NewHorizons.Builder.Props if (_visionTorchDetectorPrefab == null) return null; // spawn a trigger for the vision torch - var g = DetailBuilder.Make(planetGO, sector, mod, _visionTorchDetectorPrefab, new DetailInfo(info) { scale = 2, rename = !string.IsNullOrEmpty(info.rename) ? info.rename : "VisionStaffDetector" }); + var visionTorchTargetGO = DetailBuilder.Make(planetGO, sector, mod, _visionTorchDetectorPrefab, new DetailInfo(info) { scale = 2, rename = !string.IsNullOrEmpty(info.rename) ? info.rename : "VisionStaffDetector" }); - if (g == null) + if (visionTorchTargetGO == null) { NHLogger.LogWarning($"Tried to make a vision torch target but couldn't. Do you have the DLC installed?"); return null; @@ -416,7 +419,7 @@ namespace NewHorizons.Builder.Props var slideCollection = new SlideCollection(slidesCount); // TODO: uh I think that info.slides[i].playTimeDuration is not being read here... note to self for when I implement support for that: 0.7 is what to default to if playTimeDuration turns out to be 0 slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null - var (_, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, false, false); + var (_, _, imageLoader) = StartAsyncLoader(mod, info.slides, ref slideCollection, false, false, true); imageLoader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) => { var time = DateTime.Now; @@ -425,17 +428,18 @@ namespace NewHorizons.Builder.Props }); // attach a component to store all the data for the slides that play when a vision torch scans this target - var target = g.AddComponent(); - var slideCollectionContainer = g.AddComponent(); + var target = visionTorchTargetGO.AddComponent(); + var slideCollectionContainer = visionTorchTargetGO.AddComponent(); + slideCollectionContainer.doAsyncLoading = false; slideCollectionContainer.slideCollection = slideCollection; - target.slideCollection = g.AddComponent(); + target.slideCollection = visionTorchTargetGO.AddComponent(); target.slideCollection._slideCollectionContainer = slideCollectionContainer; LinkShipLogFacts(info, slideCollectionContainer); - g.SetActive(true); + visionTorchTargetGO.SetActive(true); - return g; + return visionTorchTargetGO; } public static GameObject MakeStandingVisionTorch(GameObject planetGO, Sector sector, ProjectionInfo info, IModBehaviour mod) @@ -469,7 +473,7 @@ namespace NewHorizons.Builder.Props var slideCollection = new SlideCollection(slidesCount); slideCollection.streamingAssetIdentifier = string.Empty; // NREs if null - var (_, _, imageLoader) = StartAsyncLoader(mod, slides, ref slideCollection, false, false); + var (_, _, imageLoader) = StartAsyncLoader(mod, slides, ref slideCollection, false, false, true); // This variable just lets us track how many of the slides have been loaded. // This way as soon as the last one is loaded (due to async loading, this may be @@ -494,6 +498,7 @@ namespace NewHorizons.Builder.Props // Set up the containers for the slides var slideCollectionContainer = standingTorch.AddComponent(); + slideCollectionContainer.doAsyncLoading = false; slideCollectionContainer.slideCollection = slideCollection; var mindSlideCollection = standingTorch.AddComponent(); @@ -512,8 +517,18 @@ namespace NewHorizons.Builder.Props return standingTorch; } + /// + /// start loading all the slide stuff we need async. + /// + /// the mod to load slides from + /// slides to load + /// where to assign the slide objects + /// should we load cached inverted images? + /// should we load cached atlas images? + /// should we load the original images? happens anyway if cache doesnt exist since atlas or inverted will need it + /// the 3 loaders (inverted, atlas, original). inverted and atlas will be null if cache doesnt exist, so check those to find out if cache exists private static (SlideReelAsyncImageLoader inverted, SlideReelAsyncImageLoader atlas, SlideReelAsyncImageLoader slides) - StartAsyncLoader(IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection, bool useInvertedCache, bool useAtlasCache) + StartAsyncLoader(IModBehaviour mod, SlideInfo[] slides, ref SlideCollection slideCollection, bool useInvertedCache, bool useAtlasCache, bool loadRawImages) { var invertedImageLoader = new SlideReelAsyncImageLoader(); var atlasImageLoader = new SlideReelAsyncImageLoader(); @@ -545,7 +560,7 @@ namespace NewHorizons.Builder.Props // Load the inverted images used when displaying slide reels to a screen invertedImageLoader.PathsToLoad.Add((i, Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/inverted_blank_slide_reel.png"))); } - else + else if (!cacheExists || loadRawImages) { // Used to then make cached stuff imageLoader.PathsToLoad.Add((i, Path.Combine(Main.Instance.ModHelper.Manifest.ModFolderPath, "Assets/textures/blank_slide_reel.png"))); @@ -553,12 +568,12 @@ namespace NewHorizons.Builder.Props } else { - if (useInvertedCache && cacheExists) + if (cacheExists && useInvertedCache) { // Load the inverted images used when displaying slide reels to a screen invertedImageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, InvertedSlideReelCacheFolder, slideInfo.imagePath))); } - else + if (!cacheExists || loadRawImages) { imageLoader.PathsToLoad.Add((i, Path.Combine(mod.ModHelper.Manifest.ModFolderPath, slideInfo.imagePath))); } @@ -577,12 +592,11 @@ namespace NewHorizons.Builder.Props { atlasImageLoader.Start(false, false); } - // When using the inverted cache we never need the regular images if (useInvertedCache) { invertedImageLoader.Start(true, false); } - else + if (loadRawImages) { imageLoader.Start(true, false); } diff --git a/NewHorizons/Components/EOTE/NHSlideCollection.cs b/NewHorizons/Components/EOTE/NHSlideCollection.cs new file mode 100644 index 00000000..6b13a833 --- /dev/null +++ b/NewHorizons/Components/EOTE/NHSlideCollection.cs @@ -0,0 +1,215 @@ +using HarmonyLib; +using NewHorizons.Builder.Props; +using NewHorizons.Utility; +using NewHorizons.Utility.Files; +using NewHorizons.Utility.OWML; +using OWML.Common; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace NewHorizons.Components.EOTE; + +[HarmonyPatch] +public class NHSlideCollection : SlideCollection +{ + public string[] slidePaths; + public IModBehaviour mod; + private HashSet _pathsBeingLoaded = new(); + /// + /// map of slide path to collections that have this path loaded. used to only unload slide when nothing else is using it + /// + public static Dictionary> _slidesRequiringPath = new(); + + private static ShipLogSlideProjector _shipLogSlideProjector; + + static NHSlideCollection() + { + SceneManager.sceneUnloaded += (_) => + { + foreach (var (slide, collections) in _slidesRequiringPath) + { + // If it has null, that means some other permanent thing loaded this texture and it will get cleared elsewhere + // Otherwise it was loaded by an NHSlideCollection and should be deleted + if (collections.Any() && !collections.Contains(null)) + { + ImageUtilities.DeleteTexture(slide); + } + } + _slidesRequiringPath.Clear(); + }; + } + + public NHSlideCollection(int startArrSize, IModBehaviour mod, string[] slidePaths) : base(startArrSize) + { + this.mod = mod; + this.slidePaths = slidePaths; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollection), nameof(SlideCollection.RequestStreamSlides))] + public static bool SlideCollection_RequestStreamSlides(SlideCollection __instance, int[] slideIndices) + { + if (__instance is NHSlideCollection collection) + { + foreach (var id in slideIndices) + { + collection.LoadSlide(id); + } + return false; + } + else + { + return true; + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollection), nameof(SlideCollection.RequestRelease))] + public static bool SlideCollection_RequestRelease(SlideCollection __instance, int[] slideIndices) + { + if (__instance is NHSlideCollection collection) + { + foreach (var id in slideIndices) + { + collection.UnloadSlide(id); + } + return false; + } + else + { + return true; + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollection), nameof(SlideCollection.IsStreamedTextureIndexLoaded))] + public static bool SlideCollection_IsStreamedTextureIndexLoaded(SlideCollection __instance, int streamIdx, ref bool __result) + { + if (__instance is NHSlideCollection collection) + { + __result = collection.IsSlideLoaded(streamIdx); + return false; + } + else + { + return true; + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollection), nameof(SlideCollection.GetStreamingTexture))] + public static bool SlideCollection_GetStreamingTexture(SlideCollection __instance, int id, ref Texture __result) + { + if (__instance is NHSlideCollection collection) + { + __result = collection.LoadSlide(id); + return false; + } + else + { + return true; + } + } + + public Texture LoadSlide(int index) + { + Texture LoadSlideInt(int index) + { + var wrappedIndex = (index + slides.Length) % slides.Length; + var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.InvertedSlideReelCacheFolder, slidePaths[wrappedIndex]); + + // We are the first slide collection container to try and load this image + var key = ImageUtilities.GetKey(mod, path); + if (!_slidesRequiringPath.ContainsKey(key)) + { + // Something else has loaded this image i.e., AutoProjector or Vision torch. We want to ensure we do not delete it + if (ImageUtilities.IsTextureLoaded(mod, path)) + { + // null is dummy value to ensure its never empty (so its not deleted) + _slidesRequiringPath[key] = new() { null }; + } + else + { + _slidesRequiringPath[key] = new(); + } + _slidesRequiringPath[key].Add(this); + } + + if (ImageUtilities.IsTextureLoaded(mod, path)) + { + // already loaded + var texture = ImageUtilities.GetTexture(mod, path); + slides[wrappedIndex]._image = texture; + return texture; + } + else if (!_pathsBeingLoaded.Contains(path)) + { + // not loaded yet, we need to load it + var loader = new SlideReelAsyncImageLoader(); + loader.PathsToLoad.Add((wrappedIndex, path)); + loader.Start(true, false); + loader.imageLoadedEvent.AddListener((Texture2D tex, int index, string originalPath) => + { + // weird: sometimes we set image, sometimes we return from GetStreamingTexture. oh well + slides[wrappedIndex]._image = tex; + _pathsBeingLoaded.Remove(path); + if (_shipLogSlideProjector == null) + { + // Object.FindObjectOfType doesnt work with inactive + _shipLogSlideProjector = Resources.FindObjectsOfTypeAll().FirstOrDefault(); + } + if (_shipLogSlideProjector != null) + { + // gotta tell ship log we updated the image + _shipLogSlideProjector._slideDirty = true; + } + else + { + NHLogger.LogVerbose("No ship log slide reel projector exists"); + } + }); + _pathsBeingLoaded.Add(path); + return null; + } + else + { + // It is being loaded so we just wait + return null; + } + } + var texture = LoadSlideInt(index); + LoadSlideInt(index - 1); + LoadSlideInt(index + 1); + + return texture; + } + + public bool IsSlideLoaded(int index) + { + var wrappedIndex = (index + slides.Length) % slides.Length; + var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.InvertedSlideReelCacheFolder, slidePaths[wrappedIndex]); + return ImageUtilities.IsTextureLoaded(mod, path); + } + + public void UnloadSlide(int index) + { + var wrappedIndex = (index + slides.Length) % slides.Length; + var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, ProjectionBuilder.InvertedSlideReelCacheFolder, slidePaths[wrappedIndex]); + + // Only unload textures that we were the ones to load in + if (ImageUtilities.IsTextureLoaded(mod, path)) + { + var key = ImageUtilities.GetKey(mod, path); + _slidesRequiringPath[key].Remove(this); + if (!_slidesRequiringPath[key].Any()) + { + NHLogger.LogVerbose($"Slide reel deleting {key} since nobody is using it anymore"); + ImageUtilities.DeleteTexture(mod, path, ImageUtilities.GetTexture(mod, path)); + slides[wrappedIndex]._image = null; + } + } + } +} diff --git a/NewHorizons/Components/EOTE/NHSlideCollectionContainer.cs b/NewHorizons/Components/EOTE/NHSlideCollectionContainer.cs index bcf979ec..030139e4 100644 --- a/NewHorizons/Components/EOTE/NHSlideCollectionContainer.cs +++ b/NewHorizons/Components/EOTE/NHSlideCollectionContainer.cs @@ -1,4 +1,7 @@ using HarmonyLib; +using System; +using System.Linq; +using UnityEngine; namespace NewHorizons.Components.EOTE; @@ -7,12 +10,14 @@ public class NHSlideCollectionContainer : SlideCollectionContainer { public string[] conditionsToSet; public string[] persistentConditionsToSet; + // at some point we'll do streaming on all slides. until then just have an off switch + public bool doAsyncLoading = true; [HarmonyPrefix] [HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.Initialize))] public static bool SlideCollectionContainer_Initialize(SlideCollectionContainer __instance) { - if (__instance is NHSlideCollectionContainer) + if (__instance is NHSlideCollectionContainer container) { if (__instance._initialized) return false; @@ -28,6 +33,7 @@ public class NHSlideCollectionContainer : SlideCollectionContainer { var fact = Locator.GetShipLogManager().GetFact(factID); fact?.RegisterSlideCollection(__instance._slideCollection); + // in original it logs. we dont want that here ig } return false; } @@ -59,4 +65,98 @@ public class NHSlideCollectionContainer : SlideCollectionContainer } } } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.NextSlideAvailable))] + public static bool SlideCollectionContainer_NextSlideAvailable(SlideCollectionContainer __instance, ref bool __result) + { + if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading) + { + __result = ((NHSlideCollection)container.slideCollection).IsSlideLoaded(container.slideIndex + 1); + return false; + } + else + { + return true; + } + } + + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.PrevSlideAvailable))] + public static bool SlideCollectionContainer_PrevSlideAvailable(SlideCollectionContainer __instance, ref bool __result) + { + if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading) + { + __result = ((NHSlideCollection)container.slideCollection).IsSlideLoaded(container.slideIndex - 1); + return false; + } + else + { + return true; + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.UnloadStreamingTextures))] + public static bool SlideCollectionContainer_UnloadStreamingTextures(SlideCollectionContainer __instance) + { + if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading) + { + for (int i = 0; i < ((NHSlideCollection)container.slideCollection).slidePaths.Length; i++) + { + ((NHSlideCollection)container.slideCollection).UnloadSlide(i); + } + return false; + } + else + { + return true; + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.GetStreamingTexture))] + public static bool SlideCollectionContainer_GetStreamingTexture(SlideCollectionContainer __instance, int id, ref Texture __result) + { + if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading) + { + __result = ((NHSlideCollection)container.slideCollection).LoadSlide(id); + return false; + } + else + { + return true; + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.RequestManualStreamSlides))] + public static bool SlideCollectionContainer_RequestManualStreamSlides(SlideCollectionContainer __instance) + { + if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading) + { + ((NHSlideCollection)container.slideCollection).LoadSlide(__instance._currentSlideIndex); + return false; + } + else + { + return true; + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(SlideCollectionContainer), nameof(SlideCollectionContainer.streamingTexturesAvailable), MethodType.Getter)] + public static bool SlideCollectionContainer_streamingTexturesAvailable(SlideCollectionContainer __instance, ref bool __result) + { + if (__instance is NHSlideCollectionContainer container && container.doAsyncLoading) + { + __result = ((NHSlideCollection)container.slideCollection).slidePaths != null && ((NHSlideCollection)container.slideCollection).slidePaths.Any(); + return false; + } + else + { + return true; + } + } } diff --git a/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs b/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs index c61ef06b..e059c310 100644 --- a/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs +++ b/NewHorizons/External/Modules/Props/EchoesOfTheEye/ProjectionInfo.cs @@ -85,6 +85,12 @@ namespace NewHorizons.External.Modules.Props.EchoesOfTheEye /// Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel. /// [DefaultValue("antique")] public SlideReelCondition reelCondition = SlideReelCondition.Antique; - } + /// + /// Set which slides appear on the slide reel model. Leave empty to default to the first few slides. + /// Takes a list of indices, i.e., to show the first 5 slides in reverse you would put [4, 3, 2, 1, 0]. + /// Index starts at 0. + /// + public int[] displaySlides; + } } diff --git a/NewHorizons/Patches/ShipLogPatches/ShipLogSlideReelPatches.cs b/NewHorizons/Patches/ShipLogPatches/ShipLogSlideReelPatches.cs new file mode 100644 index 00000000..1da6531e --- /dev/null +++ b/NewHorizons/Patches/ShipLogPatches/ShipLogSlideReelPatches.cs @@ -0,0 +1,37 @@ +using HarmonyLib; +using NewHorizons.Components.EOTE; + +namespace NewHorizons.Patches.ShipLogPatches; + +[HarmonyPatch] +public static class ShipLogSlideReelPatches +{ + [HarmonyPrefix] + [HarmonyPatch(typeof(ShipLogSlideProjector), nameof(ShipLogSlideProjector.CheckStreamingTexturesAvailable))] + public static bool ShipLogSlideProjector_CheckStreamingTexturesAvailable(ShipLogSlideProjector __instance, ref bool __result) + { + if (__instance._collectionIndex >= 0 && __instance._collectionIndex < __instance._slideCollections.Count && + __instance._slideCollections[__instance._collectionIndex] is NHSlideCollection) + { + __result = true; + return false; + } + return true; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(ShipLogSlideProjector), nameof(ShipLogSlideProjector.UnloadCurrentStreamingTextures))] + public static bool ShipLogSlideProjector_UnloadCurrentStreamingTextures(ShipLogSlideProjector __instance) + { + if (__instance._collectionIndex >= 0 && __instance._collectionIndex < __instance._slideCollections.Count && + __instance._slideCollections[__instance._collectionIndex] is NHSlideCollection collection) + { + for (int i = 0; i < collection.slides.Length; i++) + { + collection.UnloadSlide(i); + } + return false; + } + return true; + } +} diff --git a/NewHorizons/Patches/ToolPatches/ToolModeSwapperPatches.cs b/NewHorizons/Patches/ToolPatches/ToolModeSwapperPatches.cs index 63986a2a..b0a0168d 100644 --- a/NewHorizons/Patches/ToolPatches/ToolModeSwapperPatches.cs +++ b/NewHorizons/Patches/ToolPatches/ToolModeSwapperPatches.cs @@ -5,6 +5,8 @@ namespace NewHorizons.Patches.ToolPatches [HarmonyPatch(typeof(ToolModeSwapper))] public static class ToolModeSwapperPatches { + private static ShipCockpitController _shipCockpitController; + // Patches ToolModeSwapper.EquipToolMode(ToolMode mode) to deny swaps if you're holding a vision torch. // This is critical for preventing swapping to the scout launcher (causes memory slides to fail) but it // just doesn't look right when you switch to other stuff (eg the signalscope), so I'm disabling swapping tools entirely @@ -21,7 +23,9 @@ namespace NewHorizons.Patches.ToolPatches mode == ToolMode.Probe || mode == ToolMode.SignalScope || mode == ToolMode.Translator; - var isInShip = UnityEngine.Object.FindObjectOfType()?._playerAtFlightConsole ?? false; + if (_shipCockpitController == null) + _shipCockpitController = UnityEngine.Object.FindObjectOfType(); + var isInShip = _shipCockpitController != null ? _shipCockpitController._playerAtFlightConsole : false; if (!isInShip && isHoldingVisionTorch && swappingToRestrictedTool) return false; diff --git a/NewHorizons/Schemas/body_schema.json b/NewHorizons/Schemas/body_schema.json index 298e2300..1b18d953 100644 --- a/NewHorizons/Schemas/body_schema.json +++ b/NewHorizons/Schemas/body_schema.json @@ -2863,6 +2863,14 @@ "description": "Exclusive to the slide reel type. Condition/material of the reel. Antique is the Stranger, Pristine is the Dreamworld, Rusted is a burned reel.", "default": "antique", "$ref": "#/definitions/SlideReelCondition" + }, + "displaySlides": { + "type": "array", + "description": "Set which slides appear on the slide reel model. Leave empty to default to the first few slides.\nTakes a list of indices, i.e., to show the first 5 slides in reverse you would put [4, 3, 2, 1, 0].\nIndex starts at 0.", + "items": { + "type": "integer", + "format": "int32" + } } } }, diff --git a/NewHorizons/Utility/Files/ImageUtilities.cs b/NewHorizons/Utility/Files/ImageUtilities.cs index 2e660d17..09ff831f 100644 --- a/NewHorizons/Utility/Files/ImageUtilities.cs +++ b/NewHorizons/Utility/Files/ImageUtilities.cs @@ -15,6 +15,9 @@ namespace NewHorizons.Utility.Files public static bool CheckCachedTexture(string key, out Texture existingTexture) => _textureCache.TryGetValue(key, out existingTexture); public static void TrackCachedTexture(string key, Texture texture) => _textureCache.Add(key, texture); // dont reinsert cuz that causes memory leak! + public static string GetKey(IModBehaviour mod, string filename) + => GetKey(Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename)); + public static string GetKey(string path) => path.Substring(Main.Instance.ModHelper.OwmlConfig.ModsPath.Length + 1).Replace('\\', '/'); @@ -44,7 +47,7 @@ namespace NewHorizons.Utility.Files var key = GetKey(path); if (_textureCache.TryGetValue(key, out var existingTexture)) { - NHLogger.LogVerbose($"Already loaded image at path: {path}"); + //NHLogger.LogVerbose($"Already loaded image at path: {path}"); return (Texture2D)existingTexture; } @@ -68,6 +71,19 @@ namespace NewHorizons.Utility.Files } } + /// + /// Not sure why the other method takes in the texture as well + /// + /// + public static void DeleteTexture(string key) + { + if (_textureCache.ContainsKey(key)) + { + UnityEngine.Object.Destroy(_textureCache[key]); + _textureCache.Remove(key); + } + } + public static void DeleteTexture(IModBehaviour mod, string filename, Texture2D texture) { var path = Path.Combine(mod.ModHelper.Manifest.ModFolderPath, filename); @@ -191,9 +207,9 @@ namespace NewHorizons.Utility.Files { for (int j = 0; j < size; j++) { - var colour = Color.black; + var colour = Color.clear; - if (srcTexture) + if (srcTexture != null) { var srcX = i * srcTexture.width / (float)size; var srcY = j * srcTexture.height / (float)size; diff --git a/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs index e81d8298..e088e9d9 100644 --- a/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs +++ b/NewHorizons/Utility/Files/SlideReelAsyncImageLoader.cs @@ -42,6 +42,11 @@ public class SlideReelAsyncImageLoader private bool _started; private bool _clamp; + /// + /// start loading the images a frame later + /// + /// sets wrapMode + /// load all slides one at a time vs at the same time public void Start(bool clamp, bool sequential) { if (_started) return;