auto-fix maps broken due to missing vanilla tilesheet
This commit is contained in:
parent
95f658014e
commit
0d7d447600
|
@ -3,6 +3,7 @@
|
||||||
# Release notes
|
# Release notes
|
||||||
## Upcoming release
|
## Upcoming release
|
||||||
* For players:
|
* For players:
|
||||||
|
* SMAPI now auto-fixes maps loaded without a required tilesheet to prevent errors.
|
||||||
* Fixed outdated instructions in Steam error message.
|
* Fixed outdated instructions in Steam error message.
|
||||||
* Simplified [running without a terminal on Linux/macOS](https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting#SMAPI_doesn.27t_recognize_controller_.28Steam_only.29) when needed.
|
* Simplified [running without a terminal on Linux/macOS](https://stardewvalleywiki.com/Modding:Player_Guide/Troubleshooting#SMAPI_doesn.27t_recognize_controller_.28Steam_only.29) when needed.
|
||||||
* Updated compatibility list.
|
* Updated compatibility list.
|
||||||
|
|
|
@ -70,6 +70,7 @@
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=thumbstick/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=thumbstick/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tilesheet/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=tilesheet/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tilesheets/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=tilesheets/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=tilesheet_0027s/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=unloadable/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=unloadable/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=virally/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=virally/@EntryIndexedValue">True</s:Boolean>
|
||||||
</wpf:ResourceDictionary>
|
</wpf:ResourceDictionary>
|
|
@ -1,3 +1,6 @@
|
||||||
|
using System.Numerics;
|
||||||
|
using xTile.Dimensions;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Framework.Content
|
namespace StardewModdingAPI.Framework.Content
|
||||||
{
|
{
|
||||||
/// <summary>Basic metadata about a vanilla tilesheet.</summary>
|
/// <summary>Basic metadata about a vanilla tilesheet.</summary>
|
||||||
|
@ -15,6 +18,12 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
/// <summary>The asset path for the tilesheet texture.</summary>
|
/// <summary>The asset path for the tilesheet texture.</summary>
|
||||||
public readonly string ImageSource;
|
public readonly string ImageSource;
|
||||||
|
|
||||||
|
/// <summary>The number of tiles in the tilesheet.</summary>
|
||||||
|
public readonly Size SheetSize;
|
||||||
|
|
||||||
|
/// <summary>The size of each tile in pixels.</summary>
|
||||||
|
public readonly Size TileSize;
|
||||||
|
|
||||||
|
|
||||||
/*********
|
/*********
|
||||||
** Public methods
|
** Public methods
|
||||||
|
@ -23,11 +32,15 @@ namespace StardewModdingAPI.Framework.Content
|
||||||
/// <param name="index">The tilesheet's index in the list.</param>
|
/// <param name="index">The tilesheet's index in the list.</param>
|
||||||
/// <param name="id">The tilesheet's unique ID in the map.</param>
|
/// <param name="id">The tilesheet's unique ID in the map.</param>
|
||||||
/// <param name="imageSource">The asset path for the tilesheet texture.</param>
|
/// <param name="imageSource">The asset path for the tilesheet texture.</param>
|
||||||
public TilesheetReference(int index, string id, string imageSource)
|
/// <param name="sheetSize">The number of tiles in the tilesheet.</param>
|
||||||
|
/// <param name="tileSize">The size of each tile in pixels.</param>
|
||||||
|
public TilesheetReference(int index, string id, string imageSource, Size sheetSize, Size tileSize)
|
||||||
{
|
{
|
||||||
this.Index = index;
|
this.Index = index;
|
||||||
this.Id = id;
|
this.Id = id;
|
||||||
this.ImageSource = imageSource;
|
this.ImageSource = imageSource;
|
||||||
|
this.SheetSize = sheetSize;
|
||||||
|
this.TileSize = tileSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -406,14 +406,14 @@ namespace StardewModdingAPI.Framework
|
||||||
if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[] tilesheets))
|
if (!this.VanillaTilesheets.TryGetValue(assetName, out TilesheetReference[] tilesheets))
|
||||||
{
|
{
|
||||||
tilesheets = this.TryLoadVanillaAsset(assetName, out Map map)
|
tilesheets = this.TryLoadVanillaAsset(assetName, out Map map)
|
||||||
? map.TileSheets.Select((sheet, index) => new TilesheetReference(index, sheet.Id, sheet.ImageSource)).ToArray()
|
? map.TileSheets.Select((sheet, index) => new TilesheetReference(index, sheet.Id, sheet.ImageSource, sheet.SheetSize, sheet.TileSize)).ToArray()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
this.VanillaTilesheets[assetName] = tilesheets;
|
this.VanillaTilesheets[assetName] = tilesheets;
|
||||||
this.VanillaContentManager.Unload();
|
this.VanillaContentManager.Unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
return tilesheets ?? new TilesheetReference[0];
|
return tilesheets ?? Array.Empty<TilesheetReference>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Get the language enum which corresponds to a locale code (e.g. <see cref="LocalizedContentManager.LanguageCode.fr"/> given <c>fr-FR</c>).</summary>
|
/// <summary>Get the language enum which corresponds to a locale code (e.g. <see cref="LocalizedContentManager.LanguageCode.fr"/> given <c>fr-FR</c>).</summary>
|
||||||
|
|
|
@ -13,6 +13,7 @@ using StardewModdingAPI.Framework.Utilities;
|
||||||
using StardewModdingAPI.Internal;
|
using StardewModdingAPI.Internal;
|
||||||
using StardewValley;
|
using StardewValley;
|
||||||
using xTile;
|
using xTile;
|
||||||
|
using xTile.Tiles;
|
||||||
|
|
||||||
namespace StardewModdingAPI.Framework.ContentManagers
|
namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
{
|
{
|
||||||
|
@ -308,7 +309,7 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
}
|
}
|
||||||
|
|
||||||
// return matched asset
|
// return matched asset
|
||||||
return this.TryValidateLoadedAsset(info, data, mod)
|
return this.TryFixAndValidateLoadedAsset(info, data, mod)
|
||||||
? new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName)
|
? new AssetDataForObject(info, data, this.AssertAndNormalizeAssetName)
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
@ -381,12 +382,13 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
return asset;
|
return asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Validate that an asset loaded by a mod is valid and won't cause issues.</summary>
|
/// <summary>Validate that an asset loaded by a mod is valid and won't cause issues, and fix issues if possible.</summary>
|
||||||
/// <typeparam name="T">The asset type.</typeparam>
|
/// <typeparam name="T">The asset type.</typeparam>
|
||||||
/// <param name="info">The basic asset metadata.</param>
|
/// <param name="info">The basic asset metadata.</param>
|
||||||
/// <param name="data">The loaded asset data.</param>
|
/// <param name="data">The loaded asset data.</param>
|
||||||
/// <param name="mod">The mod which loaded the asset.</param>
|
/// <param name="mod">The mod which loaded the asset.</param>
|
||||||
private bool TryValidateLoadedAsset<T>(IAssetInfo info, T data, IModMetadata mod)
|
/// <returns>Returns whether the asset passed validation checks (after any fixes were applied).</returns>
|
||||||
|
private bool TryFixAndValidateLoadedAsset<T>(IAssetInfo info, T data, IModMetadata mod)
|
||||||
{
|
{
|
||||||
// can't load a null asset
|
// can't load a null asset
|
||||||
if (data == null)
|
if (data == null)
|
||||||
|
@ -401,20 +403,23 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
TilesheetReference[] vanillaTilesheetRefs = this.Coordinator.GetVanillaTilesheetIds(info.AssetName);
|
TilesheetReference[] vanillaTilesheetRefs = this.Coordinator.GetVanillaTilesheetIds(info.AssetName);
|
||||||
foreach (TilesheetReference vanillaSheet in vanillaTilesheetRefs)
|
foreach (TilesheetReference vanillaSheet in vanillaTilesheetRefs)
|
||||||
{
|
{
|
||||||
// skip if match
|
// add missing tilesheet
|
||||||
if (loadedMap.TileSheets.Count > vanillaSheet.Index && loadedMap.TileSheets[vanillaSheet.Index].Id == vanillaSheet.Id)
|
if (loadedMap.GetTileSheet(vanillaSheet.Id) == null)
|
||||||
continue;
|
{
|
||||||
|
mod.Monitor.LogOnce("SMAPI fixed maps loaded by this mod to prevent errors. See the log file for details.", LogLevel.Warn);
|
||||||
|
this.Monitor.Log($"Fixed broken map replacement: {mod.DisplayName} loaded '{info.AssetName}' without a required tilesheet (id: {vanillaSheet.Id}, source: {vanillaSheet.ImageSource}).");
|
||||||
|
|
||||||
|
loadedMap.AddTileSheet(new TileSheet(vanillaSheet.Id, loadedMap, vanillaSheet.ImageSource, vanillaSheet.SheetSize, vanillaSheet.TileSize));
|
||||||
|
}
|
||||||
|
|
||||||
// handle mismatch
|
// handle mismatch
|
||||||
|
if (loadedMap.TileSheets.Count <= vanillaSheet.Index || loadedMap.TileSheets[vanillaSheet.Index].Id != vanillaSheet.Id)
|
||||||
{
|
{
|
||||||
// only show warning if not farm map
|
// only show warning if not farm map
|
||||||
// This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting.
|
// This is temporary: mods shouldn't do this for any vanilla map, but these are the ones we know will crash. Showing a warning for others instead gives modders time to update their mods, while still simplifying troubleshooting.
|
||||||
bool isFarmMap = info.AssetNameEquals("Maps/Farm") || info.AssetNameEquals("Maps/Farm_Combat") || info.AssetNameEquals("Maps/Farm_Fishing") || info.AssetNameEquals("Maps/Farm_Foraging") || info.AssetNameEquals("Maps/Farm_FourCorners") || info.AssetNameEquals("Maps/Farm_Island") || info.AssetNameEquals("Maps/Farm_Mining");
|
bool isFarmMap = info.AssetNameEquals("Maps/Farm") || info.AssetNameEquals("Maps/Farm_Combat") || info.AssetNameEquals("Maps/Farm_Fishing") || info.AssetNameEquals("Maps/Farm_Foraging") || info.AssetNameEquals("Maps/Farm_FourCorners") || info.AssetNameEquals("Maps/Farm_Island") || info.AssetNameEquals("Maps/Farm_Mining");
|
||||||
|
|
||||||
int loadedIndex = this.TryFindTilesheet(loadedMap, vanillaSheet.Id);
|
string reason = $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewvalleywiki.com/Modding:Maps#Tilesheet_order for help.";
|
||||||
string reason = loadedIndex != -1
|
|
||||||
? $"mod reordered the original tilesheets, which {(isFarmMap ? "would cause a crash" : "often causes crashes")}.\nTechnical details for mod author: Expected order: {string.Join(", ", vanillaTilesheetRefs.Select(p => p.Id))}. See https://stardewvalleywiki.com/Modding:Maps#Tilesheet_order for help."
|
|
||||||
: $"mod has no tilesheet with ID '{vanillaSheet.Id}'. Map replacements must keep the original tilesheets to avoid errors or crashes.";
|
|
||||||
|
|
||||||
SCore.DeprecationManager.PlaceholderWarn("3.8.2", DeprecationLevel.PendingRemoval);
|
SCore.DeprecationManager.PlaceholderWarn("3.8.2", DeprecationLevel.PendingRemoval);
|
||||||
if (isFarmMap)
|
if (isFarmMap)
|
||||||
|
@ -429,19 +434,5 @@ namespace StardewModdingAPI.Framework.ContentManagers
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Find a map tilesheet by ID.</summary>
|
|
||||||
/// <param name="map">The map whose tilesheets to search.</param>
|
|
||||||
/// <param name="id">The tilesheet ID to match.</param>
|
|
||||||
private int TryFindTilesheet(Map map, string id)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < map.TileSheets.Count; i++)
|
|
||||||
{
|
|
||||||
if (map.TileSheets[i].Id == id)
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue