From 5622e3b319cf7a312537f2a555fee9e67628b1cd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 24 Aug 2017 20:44:44 -0400 Subject: fix map tilesheet load not handling seasonal variations (#352) --- .../Framework/ModHelpers/ContentHelper.cs | 92 +++++++++++++++++----- 1 file changed, 72 insertions(+), 20 deletions(-) (limited to 'src/StardewModdingAPI/Framework/ModHelpers') diff --git a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs index ffa78ff6..21201970 100644 --- a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs @@ -216,47 +216,99 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The map tilesheets could not be loaded. private void FixLocalMapTilesheets(Map map, string mapKey) { + // check map info if (!map.TileSheets.Any()) return; - string relativeMapFolder = Path.GetDirectoryName(mapKey) ?? ""; // folder path containing the map, relative to the mod folder + + // fix tilesheets foreach (TileSheet tilesheet in map.TileSheets) { - // check for tilesheet relative to map + string imageSource = tilesheet.ImageSource; + + // get seasonal name (if applicable) + string seasonalImageSource = null; + if(Game1.currentSeason != null && Game1.currentSeason != "spring") + { + string filename = Path.GetFileName(imageSource); + string dirPath = imageSource.Substring(0, imageSource.LastIndexOf(filename)); + if (filename.StartsWith("spring_")) + seasonalImageSource = dirPath + Game1.currentSeason + "_" + filename.Substring("spring_".Length); + } + + // load best match + try { - string localKey = Path.Combine(relativeMapFolder, tilesheet.ImageSource); - FileInfo localFile = this.GetModFile(localKey); - if (localFile.Exists) + string key = + this.TryLoadTilesheetImageSource(relativeMapFolder, seasonalImageSource) + ?? this.TryLoadTilesheetImageSource(relativeMapFolder, imageSource); + if (key != null) { - try - { - this.Load(localKey); - } - catch (Exception ex) - { - throw new ContentLoadException($"The local '{tilesheet.ImageSource}' tilesheet couldn't be loaded.", ex); - } - tilesheet.ImageSource = this.GetActualAssetKey(localKey); + tilesheet.ImageSource = key; continue; } } + catch (Exception ex) + { + throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.", ex); + } + + // none found + throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder."); + } + } + + /// Load a tilesheet image source if the file exists. + /// The folder path containing the map, relative to the mod folder. + /// The tilesheet image source to load. + /// Returns the loaded asset key (if it was loaded successfully). + private string TryLoadTilesheetImageSource(string relativeMapFolder, string imageSource) + { + if (imageSource == null) + return null; + + // check for tilesheet relative to map + { + string localKey = Path.Combine(relativeMapFolder, imageSource); + FileInfo localFile = this.GetModFile(localKey); + if (localFile.Exists) + { + try + { + this.Load(localKey); + } + catch (Exception ex) + { + throw new ContentLoadException($"The local '{imageSource}' tilesheet couldn't be loaded.", ex); + } + + return this.GetActualAssetKey(localKey); + } + } + + // fallback to game content + { + string contentKey = imageSource.EndsWith(".png") + ? imageSource.Substring(0, imageSource.Length - 4) + : imageSource; - // fallback to game content + FileInfo file = new FileInfo(Path.Combine(this.ContentManager.FullRootDirectory, contentKey + ".xnb")); + if (file.Exists) { - string contentKey = tilesheet.ImageSource; - if (contentKey.EndsWith(".png")) - contentKey = contentKey.Substring(0, contentKey.Length - 4); try { this.ContentManager.Load(contentKey); } catch (Exception ex) { - throw new ContentLoadException($"The '{tilesheet.ImageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.", ex); + throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.", ex); } - tilesheet.ImageSource = contentKey; + return contentKey; } } + + // not found + return null; } /// Assert that the given key has a valid format. -- cgit From dac21226d275fca4ce4880ab9e15104f7987c133 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 1 Sep 2017 21:05:08 -0400 Subject: fix IAssetLoader instances not able to load a map tilesheet if it doesn't also exist in the content folder (#352) --- .../Framework/ModHelpers/ContentHelper.cs | 39 ++++++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) (limited to 'src/StardewModdingAPI/Framework/ModHelpers') diff --git a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs index 21201970..d24124e0 100644 --- a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs @@ -228,7 +228,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // get seasonal name (if applicable) string seasonalImageSource = null; - if(Game1.currentSeason != null && Game1.currentSeason != "spring") + if (Game1.currentSeason != null && Game1.currentSeason != "spring") { string filename = Path.GetFileName(imageSource); string dirPath = imageSource.Substring(0, imageSource.LastIndexOf(filename)); @@ -292,19 +292,23 @@ namespace StardewModdingAPI.Framework.ModHelpers ? imageSource.Substring(0, imageSource.Length - 4) : imageSource; - FileInfo file = new FileInfo(Path.Combine(this.ContentManager.FullRootDirectory, contentKey + ".xnb")); - if (file.Exists) + try { - try - { - this.ContentManager.Load(contentKey); - } - catch (Exception ex) - { - throw new ContentLoadException($"The '{imageSource}' tilesheet couldn't be loaded relative to either map file or the game's content folder.", ex); - } + this.Load(contentKey, ContentSource.GameContent); return contentKey; } + catch + { + // ignore file-not-found errors + // TODO: while it's useful to suppress a asset-not-found error here to avoid + // confusion, this is a pretty naive approach. Even if the file doesn't exist, + // the file may have been loaded through an IAssetLoader which failed. So even + // if the content file doesn't exist, that doesn't mean the error here is a + // content-not-found error. Unfortunately XNA doesn't provide a good way to + // detect the error type. + if (this.GetContentFolderFile(contentKey).Exists) + throw; + } } // not found @@ -342,6 +346,19 @@ namespace StardewModdingAPI.Framework.ModHelpers return file; } + /// Get a file from the game's content folder. + /// The asset key. + private FileInfo GetContentFolderFile(string key) + { + // get file path + string path = Path.Combine(this.ContentManager.FullRootDirectory, key); + if (!path.EndsWith(".xnb")) + path += ".xnb"; + + // get file + return new FileInfo(path); + } + /// Get the asset path which loads a mod folder through a content manager. /// The file path relative to the mod's folder. /// The absolute file path. -- cgit From 3e820b82bccf7cab390bc43d9156fdeb4ab08cd9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 2 Sep 2017 23:58:43 -0400 Subject: account for game loading tilesheets from either Content or Content\Maps (#352) --- .../Framework/ModHelpers/ContentHelper.cs | 58 ++++++++++++++-------- 1 file changed, 37 insertions(+), 21 deletions(-) (limited to 'src/StardewModdingAPI/Framework/ModHelpers') diff --git a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs index d24124e0..a3beaedf 100644 --- a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs @@ -214,6 +214,18 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The map whose tilesheets to fix. /// The map asset key within the mod folder. /// The map tilesheets could not be loaded. + /// + /// The game's logic for tilesheets in is a bit specialised. It boils down to this: + /// * If the location is indoors or the desert, or the image source contains 'path' or 'object', it's loaded as-is relative to the Content folder. + /// * Else it's loaded from Content\Maps with a seasonal prefix. + /// + /// That logic doesn't work well in our case, mainly because we have no location metadata at this point. + /// Instead we use a more heuristic approach: check relative to the map file first, then relative to + /// Content\Maps, then Content. If the image source filename contains a seasonal prefix, we try + /// for a seasonal variation and then an exact match. + /// + /// While that doesn't exactly match the game logic, it's close enough that it's unlikely to make a difference. + /// private void FixLocalMapTilesheets(Map map, string mapKey) { // check map info @@ -262,12 +274,13 @@ namespace StardewModdingAPI.Framework.ModHelpers /// The folder path containing the map, relative to the mod folder. /// The tilesheet image source to load. /// Returns the loaded asset key (if it was loaded successfully). + /// See remarks on . private string TryLoadTilesheetImageSource(string relativeMapFolder, string imageSource) { if (imageSource == null) return null; - // check for tilesheet relative to map + // check relative to map file { string localKey = Path.Combine(relativeMapFolder, imageSource); FileInfo localFile = this.GetModFile(localKey); @@ -286,28 +299,31 @@ namespace StardewModdingAPI.Framework.ModHelpers } } - // fallback to game content + // check relative to content folder { - string contentKey = imageSource.EndsWith(".png") - ? imageSource.Substring(0, imageSource.Length - 4) - : imageSource; - - try + foreach (string candidateKey in new[] { imageSource, $@"Maps\{imageSource}" }) { - this.Load(contentKey, ContentSource.GameContent); - return contentKey; - } - catch - { - // ignore file-not-found errors - // TODO: while it's useful to suppress a asset-not-found error here to avoid - // confusion, this is a pretty naive approach. Even if the file doesn't exist, - // the file may have been loaded through an IAssetLoader which failed. So even - // if the content file doesn't exist, that doesn't mean the error here is a - // content-not-found error. Unfortunately XNA doesn't provide a good way to - // detect the error type. - if (this.GetContentFolderFile(contentKey).Exists) - throw; + string contentKey = candidateKey.EndsWith(".png") + ? candidateKey.Substring(0, imageSource.Length - 4) + : candidateKey; + + try + { + this.Load(contentKey, ContentSource.GameContent); + return contentKey; + } + catch + { + // ignore file-not-found errors + // TODO: while it's useful to suppress a asset-not-found error here to avoid + // confusion, this is a pretty naive approach. Even if the file doesn't exist, + // the file may have been loaded through an IAssetLoader which failed. So even + // if the content file doesn't exist, that doesn't mean the error here is a + // content-not-found error. Unfortunately XNA doesn't provide a good way to + // detect the error type. + if (this.GetContentFolderFile(contentKey).Exists) + throw; + } } } -- cgit From b86d9f7c0ebdd3bc1200836137f92eaa3c9910ec Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 3 Sep 2017 00:00:39 -0400 Subject: handle maps referencing a non-spring seasonal variation (#352) --- .../Framework/ModHelpers/ContentHelper.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src/StardewModdingAPI/Framework/ModHelpers') diff --git a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs index a3beaedf..9f236823 100644 --- a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs @@ -240,12 +240,19 @@ namespace StardewModdingAPI.Framework.ModHelpers // get seasonal name (if applicable) string seasonalImageSource = null; - if (Game1.currentSeason != null && Game1.currentSeason != "spring") + if (Game1.currentSeason != null) { string filename = Path.GetFileName(imageSource); - string dirPath = imageSource.Substring(0, imageSource.LastIndexOf(filename)); - if (filename.StartsWith("spring_")) - seasonalImageSource = dirPath + Game1.currentSeason + "_" + filename.Substring("spring_".Length); + bool hasSeasonalPrefix = + filename.StartsWith("spring_", StringComparison.CurrentCultureIgnoreCase) + || filename.StartsWith("summer_", StringComparison.CurrentCultureIgnoreCase) + || filename.StartsWith("fall_", StringComparison.CurrentCultureIgnoreCase) + || filename.StartsWith("winter_", StringComparison.CurrentCultureIgnoreCase); + if (hasSeasonalPrefix && !filename.StartsWith(Game1.currentSeason + "_")) + { + string dirPath = imageSource.Substring(0, imageSource.LastIndexOf(filename, StringComparison.CurrentCultureIgnoreCase)); + seasonalImageSource = $"{dirPath}{Game1.currentSeason}_{filename.Substring(filename.IndexOf("_", StringComparison.CurrentCultureIgnoreCase) + 1)}"; + } } // load best match -- cgit From 23951220ae84f3132832c942b61a8e81aee1fbfe Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 8 Sep 2017 13:18:43 -0400 Subject: fix errors loading some custom map tilesheets on Linux/Mac --- release-notes.md | 4 ++-- src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src/StardewModdingAPI/Framework/ModHelpers') diff --git a/release-notes.md b/release-notes.md index 16ebae2c..5e01a264 100644 --- a/release-notes.md +++ b/release-notes.md @@ -28,8 +28,8 @@ For power users: ## 1.15.4 For players: -* Fixed errors when loading some custom maps via Entoarox Framework or XNB Loader. -* Fixed errors with in-game date calculation in some mods. +* Fixed errors when loading some custom maps on Linux/Mac or using XNB Loader. +* Fixed errors in rare cases when a mod calculates an in-game date. For modders: * Added UTC timestamp to log file. diff --git a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs index 9f236823..4440ae40 100644 --- a/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs +++ b/src/StardewModdingAPI/Framework/ModHelpers/ContentHelper.cs @@ -231,6 +231,7 @@ namespace StardewModdingAPI.Framework.ModHelpers // check map info if (!map.TileSheets.Any()) return; + mapKey = this.ContentManager.NormaliseAssetName(mapKey); // Mono's Path.GetDirectoryName doesn't handle Windows dir separators string relativeMapFolder = Path.GetDirectoryName(mapKey) ?? ""; // folder path containing the map, relative to the mod folder // fix tilesheets -- cgit