Centralise content loading to fix map tilesheet edge case
Pathoschild opened this issue · 1 comments
Centralise content loading so the game can correctly handle map tilesheets in the mod folder when those tilesheets aren't loaded in advance.
Current design
The content logic is split into two main parts: the single lower-level SContentManager
and its shims, and the higher-level ContentHelper
instances. When a mod loads content through the ContentHelper
, some extra logic is applied to support reading files from the mod folder. This doesn't happen when the game loads an asset directly through SContentManager
.
Problem
That distinction causes problems with custom tilesheets if they're not loaded in advance. Consider this code:
// load texture
helper.Content.Load<Texture2D>("tilesheet.png");
// create tilesheet
string tilesheetPath = helper.Content.GetActualAssetKey("tilesheet.png");
TileSheet tilesheet = new TileSheet("custom-tilesheet", location.map, tilesheetPath, new Size(16, 32), Size(16, 16));
// add & load tilesheet
location.map.AddTileSheet(tilesheet);
location.map.LoadTileSheets(Game1.mapDisplayDevice);
That code works fine, because Load<Texture2D>
adds ..\Mods\ModName\tilesheet.png
to the cache so it's available when the game loads the tilesheet. However, removing the Load<Texture2D>
line makes the code crash — since SContentManager
doesn't recognise mod paths, it ends up looking for Mods\ModName\tilesheet.png.xnb
in the game's content folder.
Sample error
ContentLoadException: Error loading "..\Mods\ModName\tilesheet.png". File not found. ---> System.IO.FileNotFoundException: Error loading "Mods\ModName\tilesheet.png.xnb". File not found.
at Microsoft.Xna.Framework.TitleContainer.OpenStream(String name)
at Microsoft.Xna.Framework.Content.ContentManager.OpenStream(String assetName)
--- End of inner exception stack trace ---
at Microsoft.Xna.Framework.Content.ContentManager.OpenStream(String assetName)
at Microsoft.Xna.Framework.Content.ContentManager.ReadAsset[T](String assetName, Action`1 recordDisposableObject)
at Microsoft.Xna.Framework.Content.ContentManager.Load[T](String assetName)
at StardewValley.LocalizedContentManager.Load[T](String assetName)
at StardewModdingAPI.Framework.SContentManager.<>n__0[T](String assetName)
at StardewModdingAPI.Framework.SContentManager.<>c__DisplayClass24_0`1.<LoadFor>b__0() in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\SContentManager.cs:line 179
at StardewModdingAPI.Framework.Utilities.ContextHash`1.Track[TResult](T key, Func`1 action) in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\Utilities\ContextHash.cs:line 53
at StardewModdingAPI.Framework.SContentManager.LoadFor[T](String assetName, ContentManager instance) in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\SContentManager.cs:line 176
at StardewModdingAPI.Framework.ContentManagerShim.Load[T](String assetName) in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\ContentManagerShim.cs:line 40
at xTile.Display.XnaDisplayDevice.LoadTileSheet(TileSheet tileSheet)
at xTile.Map.LoadTileSheets(IDisplayDevice displayDevice)
at StardewCrossing.ModEntry.SaveEvents_AfterLoad(Object sender, EventArgs e) in C:\Users\Jesse\Downloads\StardewCrossing\StardewCrossing\ModEntry.cs:line 35
at StardewModdingAPI.Framework.InternalExtensions.SafelyRaisePlainEvent(IMonitor monitor, String name, IEnumerable`1 handlers, Object sender, EventArgs args) in C:\source\_Stardew\SMAPI\src\SMAPI\Framework\InternalExtensions.cs:line 40