summaryrefslogtreecommitdiff
path: root/src/SMAPI/Metadata/CoreAssetPropagator.cs
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-01 18:16:09 -0400
committerJesse Plamondon-Willard <Pathoschild@users.noreply.github.com>2022-05-01 18:16:09 -0400
commitc8ad50dad1d706a1901798f9396f6becfea36c0e (patch)
tree28bd818a5db39ec5ece1bd141a28de955950463b /src/SMAPI/Metadata/CoreAssetPropagator.cs
parent451b70953ff4c0b1b27ae0de203ad99379b45b2a (diff)
parentf78093bdb58d477b400cde3f19b70ffd6ddf833d (diff)
downloadSMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.tar.gz
SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.tar.bz2
SMAPI-c8ad50dad1d706a1901798f9396f6becfea36c0e.zip
Merge branch 'develop' into stable
Diffstat (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs')
-rw-r--r--src/SMAPI/Metadata/CoreAssetPropagator.cs545
1 files changed, 272 insertions, 273 deletions
diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs
index 552bc000..5dee2c4d 100644
--- a/src/SMAPI/Metadata/CoreAssetPropagator.cs
+++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs
@@ -45,8 +45,8 @@ namespace StardewModdingAPI.Metadata
/// <summary>Whether to enable more aggressive memory optimizations.</summary>
private readonly bool AggressiveMemoryOptimizations;
- /// <summary>Normalizes an asset key to match the cache key and assert that it's valid.</summary>
- private readonly Func<string, string> AssertAndNormalizeAssetName;
+ /// <summary>Parse a raw asset name.</summary>
+ private readonly Func<string, IAssetName> ParseAssetName;
/// <summary>Optimized bucket categories for batch reloading assets.</summary>
private enum AssetBucket
@@ -71,15 +71,15 @@ namespace StardewModdingAPI.Metadata
/// <param name="monitor">Writes messages to the console.</param>
/// <param name="reflection">Simplifies access to private code.</param>
/// <param name="aggressiveMemoryOptimizations">Whether to enable more aggressive memory optimizations.</param>
- public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, IMonitor monitor, Reflector reflection, bool aggressiveMemoryOptimizations)
+ /// <param name="parseAssetName">Parse a raw asset name.</param>
+ public CoreAssetPropagator(LocalizedContentManager mainContent, GameContentManagerForAssetPropagation disposableContent, IMonitor monitor, Reflector reflection, bool aggressiveMemoryOptimizations, Func<string, IAssetName> parseAssetName)
{
this.MainContentManager = mainContent;
this.DisposableContentManager = disposableContent;
this.Monitor = monitor;
this.Reflection = reflection;
this.AggressiveMemoryOptimizations = aggressiveMemoryOptimizations;
-
- this.AssertAndNormalizeAssetName = disposableContent.AssertAndNormalizeAssetName;
+ this.ParseAssetName = parseAssetName;
}
/// <summary>Reload one of the game's core assets (if applicable).</summary>
@@ -87,22 +87,27 @@ namespace StardewModdingAPI.Metadata
/// <param name="ignoreWorld">Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world.</param>
/// <param name="propagatedAssets">A lookup of asset names to whether they've been propagated.</param>
/// <param name="updatedNpcWarps">Whether the NPC pathfinding cache was reloaded.</param>
- public void Propagate(IDictionary<string, Type> assets, bool ignoreWorld, out IDictionary<string, bool> propagatedAssets, out bool updatedNpcWarps)
+ public void Propagate(IDictionary<IAssetName, Type> assets, bool ignoreWorld, out IDictionary<IAssetName, bool> propagatedAssets, out bool updatedNpcWarps)
{
+ // get base name lookup
+ propagatedAssets = assets
+ .Select(asset => asset.Key.GetBaseAssetName())
+ .Distinct()
+ .ToDictionary(name => name, _ => false);
+
// group into optimized lists
var buckets = assets.GroupBy(p =>
{
- if (this.IsInFolder(p.Key, "Characters") || this.IsInFolder(p.Key, "Characters\\Monsters"))
+ if (p.Key.IsDirectlyUnderPath("Characters") || p.Key.IsDirectlyUnderPath("Characters/Monsters"))
return AssetBucket.Sprite;
- if (this.IsInFolder(p.Key, "Portraits"))
+ if (p.Key.IsDirectlyUnderPath("Portraits"))
return AssetBucket.Portrait;
return AssetBucket.Other;
});
// reload assets
- propagatedAssets = assets.ToDictionary(p => p.Key, _ => false, StringComparer.OrdinalIgnoreCase);
updatedNpcWarps = false;
foreach (var bucket in buckets)
{
@@ -110,12 +115,12 @@ namespace StardewModdingAPI.Metadata
{
case AssetBucket.Sprite:
if (!ignoreWorld)
- this.ReloadNpcSprites(bucket.Select(p => p.Key), propagatedAssets);
+ this.ReloadNpcSprites(propagatedAssets);
break;
case AssetBucket.Portrait:
if (!ignoreWorld)
- this.ReloadNpcPortraits(bucket.Select(p => p.Key), propagatedAssets);
+ this.ReloadNpcPortraits(propagatedAssets);
break;
default:
@@ -149,16 +154,16 @@ namespace StardewModdingAPI.Metadata
** Private methods
*********/
/// <summary>Reload one of the game's core assets (if applicable).</summary>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <param name="type">The asset type to reload.</param>
/// <param name="ignoreWorld">Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world.</param>
/// <param name="changedWarps">Whether any map warps were changed as part of this propagation.</param>
/// <returns>Returns whether an asset was loaded. The return value may be true or false, or a non-null value for true.</returns>
[SuppressMessage("ReSharper", "StringLiteralTypo", Justification = "These deliberately match the asset names.")]
- private bool PropagateOther(string key, Type type, bool ignoreWorld, out bool changedWarps)
+ private bool PropagateOther(IAssetName assetName, Type type, bool ignoreWorld, out bool changedWarps)
{
var content = this.MainContentManager;
- key = this.AssertAndNormalizeAssetName(key);
+ string key = assetName.BaseName;
changedWarps = false;
/****
@@ -170,7 +175,7 @@ namespace StardewModdingAPI.Metadata
{
foreach (TileSheet tilesheet in Game1.currentLocation.map.TileSheets)
{
- if (this.NormalizeAssetNameIgnoringEmpty(tilesheet.ImageSource) == key)
+ if (this.IsSameBaseName(assetName, tilesheet.ImageSource))
Game1.mapDisplayDevice.LoadTileSheet(tilesheet);
}
}
@@ -188,7 +193,7 @@ namespace StardewModdingAPI.Metadata
{
GameLocation location = info.Location;
- if (!string.IsNullOrWhiteSpace(location.mapPath.Value) && this.NormalizeAssetNameIgnoringEmpty(location.mapPath.Value) == key)
+ if (this.IsSameBaseName(assetName, location.mapPath.Value))
{
static ISet<string> GetWarpSet(GameLocation location)
{
@@ -213,25 +218,24 @@ namespace StardewModdingAPI.Metadata
/****
** Propagate by key
****/
- Reflector reflection = this.Reflection;
- switch (key.ToLower().Replace("/", "\\")) // normalized key so we can compare statically
+ switch (assetName.BaseName.ToLower().Replace("\\", "/")) // normalized key so we can compare statically
{
/****
** Animals
****/
- case "animals\\horse":
- return !ignoreWorld && this.ReloadPetOrHorseSprites<Horse>(content, key);
+ case "animals/horse":
+ return !ignoreWorld && this.ReloadPetOrHorseSprites<Horse>(content, assetName);
/****
** Buildings
****/
- case "buildings\\houses": // Farm
+ case "buildings/houses": // Farm
Farm.houseTextures = this.LoadAndDisposeIfNeeded(Farm.houseTextures, key);
return true;
- case "buildings\\houses_paintmask": // Farm
+ case "buildings/houses_paintmask": // Farm
{
- bool removedFromCache = this.RemoveFromPaintMaskCache(key);
+ bool removedFromCache = this.RemoveFromPaintMaskCache(assetName);
Farm farm = Game1.getFarm();
farm?.ApplyHousePaint();
@@ -242,149 +246,149 @@ namespace StardewModdingAPI.Metadata
/****
** Content\Characters\Farmer
****/
- case "characters\\farmer\\accessories": // Game1.LoadContent
+ case "characters/farmer/accessories": // Game1.LoadContent
FarmerRenderer.accessoriesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.accessoriesTexture, key);
return true;
- case "characters\\farmer\\farmer_base": // Farmer
- case "characters\\farmer\\farmer_base_bald":
- case "characters\\farmer\\farmer_girl_base":
- case "characters\\farmer\\farmer_girl_base_bald":
- return !ignoreWorld && this.ReloadPlayerSprites(key);
+ case "characters/farmer/farmer_base": // Farmer
+ case "characters/farmer/farmer_base_bald":
+ case "characters/farmer/farmer_girl_base":
+ case "characters/farmer/farmer_girl_base_bald":
+ return !ignoreWorld && this.ReloadPlayerSprites(assetName);
- case "characters\\farmer\\hairstyles": // Game1.LoadContent
+ case "characters/farmer/hairstyles": // Game1.LoadContent
FarmerRenderer.hairStylesTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hairStylesTexture, key);
return true;
- case "characters\\farmer\\hats": // Game1.LoadContent
+ case "characters/farmer/hats": // Game1.LoadContent
FarmerRenderer.hatsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.hatsTexture, key);
return true;
- case "characters\\farmer\\pants": // Game1.LoadContent
+ case "characters/farmer/pants": // Game1.LoadContent
FarmerRenderer.pantsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.pantsTexture, key);
return true;
- case "characters\\farmer\\shirts": // Game1.LoadContent
+ case "characters/farmer/shirts": // Game1.LoadContent
FarmerRenderer.shirtsTexture = this.LoadAndDisposeIfNeeded(FarmerRenderer.shirtsTexture, key);
return true;
/****
** Content\Data
****/
- case "data\\achievements": // Game1.LoadContent
+ case "data/achievements": // Game1.LoadContent
Game1.achievements = content.Load<Dictionary<int, string>>(key);
return true;
- case "data\\bigcraftablesinformation": // Game1.LoadContent
+ case "data/bigcraftablesinformation": // Game1.LoadContent
Game1.bigCraftablesInformation = content.Load<Dictionary<int, string>>(key);
return true;
- case "data\\clothinginformation": // Game1.LoadContent
+ case "data/clothinginformation": // Game1.LoadContent
Game1.clothingInformation = content.Load<Dictionary<int, string>>(key);
return true;
- case "data\\concessions": // MovieTheater.GetConcessions
+ case "data/concessions": // MovieTheater.GetConcessions
MovieTheater.ClearCachedLocalizedData();
return true;
- case "data\\concessiontastes": // MovieTheater.GetConcessionTasteForCharacter
+ case "data/concessiontastes": // MovieTheater.GetConcessionTasteForCharacter
this.Reflection
.GetField<List<ConcessionTaste>>(typeof(MovieTheater), "_concessionTastes")
.SetValue(content.Load<List<ConcessionTaste>>(key));
return true;
- case "data\\cookingrecipes": // CraftingRecipe.InitShared
+ case "data/cookingrecipes": // CraftingRecipe.InitShared
CraftingRecipe.cookingRecipes = content.Load<Dictionary<string, string>>(key);
return true;
- case "data\\craftingrecipes": // CraftingRecipe.InitShared
+ case "data/craftingrecipes": // CraftingRecipe.InitShared
CraftingRecipe.craftingRecipes = content.Load<Dictionary<string, string>>(key);
return true;
- case "data\\farmanimals": // FarmAnimal constructor
+ case "data/farmanimals": // FarmAnimal constructor
return !ignoreWorld && this.ReloadFarmAnimalData();
- case "data\\hairdata": // Farmer.GetHairStyleMetadataFile
+ case "data/hairdata": // Farmer.GetHairStyleMetadataFile
return this.ReloadHairData();
- case "data\\movies": // MovieTheater.GetMovieData
- case "data\\moviesreactions": // MovieTheater.GetMovieReactions
+ case "data/movies": // MovieTheater.GetMovieData
+ case "data/moviesreactions": // MovieTheater.GetMovieReactions
MovieTheater.ClearCachedLocalizedData();
return true;
- case "data\\npcdispositions": // NPC constructor
- return !ignoreWorld && this.ReloadNpcDispositions(content, key);
+ case "data/npcdispositions": // NPC constructor
+ return !ignoreWorld && this.ReloadNpcDispositions(content, assetName);
- case "data\\npcgifttastes": // Game1.LoadContent
+ case "data/npcgifttastes": // Game1.LoadContent
Game1.NPCGiftTastes = content.Load<Dictionary<string, string>>(key);
return true;
- case "data\\objectcontexttags": // Game1.LoadContent
+ case "data/objectcontexttags": // Game1.LoadContent
Game1.objectContextTags = content.Load<Dictionary<string, string>>(key);
return true;
- case "data\\objectinformation": // Game1.LoadContent
+ case "data/objectinformation": // Game1.LoadContent
Game1.objectInformation = content.Load<Dictionary<int, string>>(key);
return true;
/****
** Content\Fonts
****/
- case "fonts\\spritefont1": // Game1.LoadContent
+ case "fonts/spritefont1": // Game1.LoadContent
Game1.dialogueFont = content.Load<SpriteFont>(key);
return true;
- case "fonts\\smallfont": // Game1.LoadContent
+ case "fonts/smallfont": // Game1.LoadContent
Game1.smallFont = content.Load<SpriteFont>(key);
return true;
- case "fonts\\tinyfont": // Game1.LoadContent
+ case "fonts/tinyfont": // Game1.LoadContent
Game1.tinyFont = content.Load<SpriteFont>(key);
return true;
- case "fonts\\tinyfontborder": // Game1.LoadContent
+ case "fonts/tinyfontborder": // Game1.LoadContent
Game1.tinyFontBorder = content.Load<SpriteFont>(key);
return true;
/****
** Content\LooseSprites\Lighting
****/
- case "loosesprites\\lighting\\greenlight": // Game1.LoadContent
+ case "loosesprites/lighting/greenlight": // Game1.LoadContent
Game1.cauldronLight = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\lighting\\indoorwindowlight": // Game1.LoadContent
+ case "loosesprites/lighting/indoorwindowlight": // Game1.LoadContent
Game1.indoorWindowLight = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\lighting\\lantern": // Game1.LoadContent
+ case "loosesprites/lighting/lantern": // Game1.LoadContent
Game1.lantern = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\lighting\\sconcelight": // Game1.LoadContent
+ case "loosesprites/lighting/sconcelight": // Game1.LoadContent
Game1.sconceLight = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\lighting\\windowlight": // Game1.LoadContent
+ case "loosesprites/lighting/windowlight": // Game1.LoadContent
Game1.windowLight = content.Load<Texture2D>(key);
return true;
/****
** Content\LooseSprites
****/
- case "loosesprites\\birds": // Game1.LoadContent
+ case "loosesprites/birds": // Game1.LoadContent
Game1.birdsSpriteSheet = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\concessions": // Game1.LoadContent
+ case "loosesprites/concessions": // Game1.LoadContent
Game1.concessionsSpriteSheet = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\controllermaps": // Game1.LoadContent
+ case "loosesprites/controllermaps": // Game1.LoadContent
Game1.controllerMaps = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\cursors": // Game1.LoadContent
+ case "loosesprites/cursors": // Game1.LoadContent
Game1.mouseCursors = content.Load<Texture2D>(key);
foreach (DayTimeMoneyBox menu in Game1.onScreenMenus.OfType<DayTimeMoneyBox>())
{
@@ -393,59 +397,59 @@ namespace StardewModdingAPI.Metadata
}
if (!ignoreWorld)
- this.ReloadDoorSprites(content, key);
+ this.ReloadDoorSprites(content, assetName);
return true;
- case "loosesprites\\cursors2": // Game1.LoadContent
+ case "loosesprites/cursors2": // Game1.LoadContent
Game1.mouseCursors2 = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\daybg": // Game1.LoadContent
+ case "loosesprites/daybg": // Game1.LoadContent
Game1.daybg = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\font_bold": // Game1.LoadContent
+ case "loosesprites/font_bold": // Game1.LoadContent
SpriteText.spriteTexture = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\font_colored": // Game1.LoadContent
+ case "loosesprites/font_colored": // Game1.LoadContent
SpriteText.coloredTexture = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\giftbox": // Game1.LoadContent
+ case "loosesprites/giftbox": // Game1.LoadContent
Game1.giftboxTexture = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\nightbg": // Game1.LoadContent
+ case "loosesprites/nightbg": // Game1.LoadContent
Game1.nightbg = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\shadow": // Game1.LoadContent
+ case "loosesprites/shadow": // Game1.LoadContent
Game1.shadowTexture = content.Load<Texture2D>(key);
return true;
- case "loosesprites\\suspensionbridge": // SuspensionBridge constructor
- return !ignoreWorld && this.ReloadSuspensionBridges(content, key);
+ case "loosesprites/suspensionbridge": // SuspensionBridge constructor
+ return !ignoreWorld && this.ReloadSuspensionBridges(content, assetName);
/****
** Content\Maps
****/
- case "maps\\menutiles": // Game1.LoadContent
+ case "maps/menutiles": // Game1.LoadContent
Game1.menuTexture = content.Load<Texture2D>(key);
return true;
- case "maps\\menutilesuncolored": // Game1.LoadContent
+ case "maps/menutilesuncolored": // Game1.LoadContent
Game1.uncoloredMenuTexture = content.Load<Texture2D>(key);
return true;
- case "maps\\springobjects": // Game1.LoadContent
+ case "maps/springobjects": // Game1.LoadContent
Game1.objectSpriteSheet = content.Load<Texture2D>(key);
return true;
/****
** Content\Minigames
****/
- case "minigames\\clouds": // TitleMenu
+ case "minigames/clouds": // TitleMenu
{
if (Game1.activeClickableMenu is TitleMenu titleMenu)
{
@@ -455,128 +459,128 @@ namespace StardewModdingAPI.Metadata
}
return false;
- case "minigames\\titlebuttons": // TitleMenu
- return this.ReloadTitleButtons(content, key);
+ case "minigames/titlebuttons": // TitleMenu
+ return this.ReloadTitleButtons(content, assetName);
/****
** Content\Strings
****/
- case "strings\\stringsfromcsfiles":
+ case "strings/stringsfromcsfiles":
return this.ReloadStringsFromCsFiles(content);
/****
** Content\TileSheets
****/
- case "tilesheets\\animations": // Game1.LoadContent
+ case "tilesheets/animations": // Game1.LoadContent
Game1.animations = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\buffsicons": // Game1.LoadContent
+ case "tilesheets/buffsicons": // Game1.LoadContent
Game1.buffsIcons = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\bushes": // new Bush()
+ case "tilesheets/bushes": // new Bush()
Bush.texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(key));
return true;
- case "tilesheets\\chairtiles": // Game1.LoadContent
- return this.ReloadChairTiles(content, key, ignoreWorld);
+ case "tilesheets/chairtiles": // Game1.LoadContent
+ return this.ReloadChairTiles(content, assetName, ignoreWorld);
- case "tilesheets\\craftables": // Game1.LoadContent
+ case "tilesheets/craftables": // Game1.LoadContent
Game1.bigCraftableSpriteSheet = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\critters": // Critter constructor
- return !ignoreWorld && this.ReloadCritterTextures(content, key) > 0;
+ case "tilesheets/critters": // Critter constructor
+ return !ignoreWorld && this.ReloadCritterTextures(content, assetName) > 0;
- case "tilesheets\\crops": // Game1.LoadContent
+ case "tilesheets/crops": // Game1.LoadContent
Game1.cropSpriteSheet = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\debris": // Game1.LoadContent
+ case "tilesheets/debris": // Game1.LoadContent
Game1.debrisSpriteSheet = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\emotes": // Game1.LoadContent
+ case "tilesheets/emotes": // Game1.LoadContent
Game1.emoteSpriteSheet = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\fruittrees": // FruitTree
+ case "tilesheets/fruittrees": // FruitTree
FruitTree.texture = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\furniture": // Game1.LoadContent
+ case "tilesheets/furniture": // Game1.LoadContent
Furniture.furnitureTexture = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\furniturefront": // Game1.LoadContent
+ case "tilesheets/furniturefront": // Game1.LoadContent
Furniture.furnitureFrontTexture = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\projectiles": // Game1.LoadContent
+ case "tilesheets/projectiles": // Game1.LoadContent
Projectile.projectileSheet = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\rain": // Game1.LoadContent
+ case "tilesheets/rain": // Game1.LoadContent
Game1.rainTexture = content.Load<Texture2D>(key);
return true;
- case "tilesheets\\tools": // Game1.ResetToolSpriteSheet
+ case "tilesheets/tools": // Game1.ResetToolSpriteSheet
Game1.ResetToolSpriteSheet();
return true;
- case "tilesheets\\weapons": // Game1.LoadContent
+ case "tilesheets/weapons": // Game1.LoadContent
Tool.weaponsTexture = content.Load<Texture2D>(key);
return true;
/****
** Content\TerrainFeatures
****/
- case "terrainfeatures\\flooring": // from Flooring
+ case "terrainfeatures/flooring": // from Flooring
Flooring.floorsTexture = content.Load<Texture2D>(key);
return true;
- case "terrainfeatures\\flooring_winter": // from Flooring
+ case "terrainfeatures/flooring_winter": // from Flooring
Flooring.floorsTextureWinter = content.Load<Texture2D>(key);
return true;
- case "terrainfeatures\\grass": // from Grass
- return !ignoreWorld && this.ReloadGrassTextures(content, key);
+ case "terrainfeatures/grass": // from Grass
+ return !ignoreWorld && this.ReloadGrassTextures(content, assetName);
- case "terrainfeatures\\hoedirt": // from HoeDirt
+ case "terrainfeatures/hoedirt": // from HoeDirt
HoeDirt.lightTexture = content.Load<Texture2D>(key);
return true;
- case "terrainfeatures\\hoedirtdark": // from HoeDirt
+ case "terrainfeatures/hoedirtdark": // from HoeDirt
HoeDirt.darkTexture = content.Load<Texture2D>(key);
return true;
- case "terrainfeatures\\hoedirtsnow": // from HoeDirt
+ case "terrainfeatures/hoedirtsnow": // from HoeDirt
HoeDirt.snowTexture = content.Load<Texture2D>(key);
return true;
- case "terrainfeatures\\mushroom_tree": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.mushroomTree);
+ case "terrainfeatures/mushroom_tree": // from Tree
+ return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.mushroomTree);
- case "terrainfeatures\\tree_palm": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.palmTree);
+ case "terrainfeatures/tree_palm": // from Tree
+ return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.palmTree);
- case "terrainfeatures\\tree1_fall": // from Tree
- case "terrainfeatures\\tree1_spring": // from Tree
- case "terrainfeatures\\tree1_summer": // from Tree
- case "terrainfeatures\\tree1_winter": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.bushyTree);
+ case "terrainfeatures/tree1_fall": // from Tree
+ case "terrainfeatures/tree1_spring": // from Tree
+ case "terrainfeatures/tree1_summer": // from Tree
+ case "terrainfeatures/tree1_winter": // from Tree
+ return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.bushyTree);
- case "terrainfeatures\\tree2_fall": // from Tree
- case "terrainfeatures\\tree2_spring": // from Tree
- case "terrainfeatures\\tree2_summer": // from Tree
- case "terrainfeatures\\tree2_winter": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.leafyTree);
+ case "terrainfeatures/tree2_fall": // from Tree
+ case "terrainfeatures/tree2_spring": // from Tree
+ case "terrainfeatures/tree2_summer": // from Tree
+ case "terrainfeatures/tree2_winter": // from Tree
+ return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.leafyTree);
- case "terrainfeatures\\tree3_fall": // from Tree
- case "terrainfeatures\\tree3_spring": // from Tree
- case "terrainfeatures\\tree3_winter": // from Tree
- return !ignoreWorld && this.ReloadTreeTextures(content, key, Tree.pineTree);
+ case "terrainfeatures/tree3_fall": // from Tree
+ case "terrainfeatures/tree3_spring": // from Tree
+ case "terrainfeatures/tree3_winter": // from Tree
+ return !ignoreWorld && this.ReloadTreeTextures(content, assetName, Tree.pineTree);
}
/****
@@ -585,25 +589,25 @@ namespace StardewModdingAPI.Metadata
if (!ignoreWorld)
{
// dynamic textures
- if (this.KeyStartsWith(key, "animals\\cat"))
- return this.ReloadPetOrHorseSprites<Cat>(content, key);
- if (this.KeyStartsWith(key, "animals\\dog"))
- return this.ReloadPetOrHorseSprites<Dog>(content, key);
- if (this.IsInFolder(key, "Animals"))
- return this.ReloadFarmAnimalSprites(content, key);
+ if (assetName.StartsWith("animals/cat"))
+ return this.ReloadPetOrHorseSprites<Cat>(content, assetName);
+ if (assetName.StartsWith("animals/dog"))
+ return this.ReloadPetOrHorseSprites<Dog>(content, assetName);
+ if (assetName.IsDirectlyUnderPath("Animals"))
+ return this.ReloadFarmAnimalSprites(content, assetName);
- if (this.IsInFolder(key, "Buildings"))
- return this.ReloadBuildings(key);
+ if (assetName.IsDirectlyUnderPath("Buildings"))
+ return this.ReloadBuildings(assetName);
- if (this.KeyStartsWith(key, "LooseSprites\\Fence"))
- return this.ReloadFenceTextures(key);
+ if (assetName.StartsWith("LooseSprites/Fence"))
+ return this.ReloadFenceTextures(assetName);
// dynamic data
- if (this.IsInFolder(key, "Characters\\Dialogue"))
- return this.ReloadNpcDialogue(key);
+ if (assetName.IsDirectlyUnderPath("Characters/Dialogue"))
+ return this.ReloadNpcDialogue(assetName);
- if (this.IsInFolder(key, "Characters\\schedules"))
- return this.ReloadNpcSchedules(key);
+ if (assetName.IsDirectlyUnderPath("Characters/schedules"))
+ return this.ReloadNpcSchedules(assetName);
}
return false;
@@ -618,14 +622,14 @@ namespace StardewModdingAPI.Metadata
****/
/// <summary>Reload buttons on the title screen.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any textures were reloaded.</returns>
/// <remarks>Derived from the <see cref="TitleMenu"/> constructor and <see cref="TitleMenu.setUpIcons"/>.</remarks>
- private bool ReloadTitleButtons(LocalizedContentManager content, string key)
+ private bool ReloadTitleButtons(LocalizedContentManager content, IAssetName assetName)
{
if (Game1.activeClickableMenu is TitleMenu titleMenu)
{
- Texture2D texture = content.Load<Texture2D>(key);
+ Texture2D texture = content.Load<Texture2D>(assetName.BaseName);
titleMenu.titleButtonsTexture = texture;
titleMenu.backButton.texture = texture;
@@ -645,21 +649,21 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload the sprites for matching pets or horses.</summary>
/// <typeparam name="TAnimal">The animal type.</typeparam>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadPetOrHorseSprites<TAnimal>(LocalizedContentManager content, string key)
+ private bool ReloadPetOrHorseSprites<TAnimal>(LocalizedContentManager content, IAssetName assetName)
where TAnimal : NPC
{
// find matches
TAnimal[] animals = this.GetCharacters()
.OfType<TAnimal>()
- .Where(p => key == this.NormalizeAssetNameIgnoringEmpty(p.Sprite?.Texture?.Name))
+ .Where(p => this.IsSameBaseName(assetName, p.Sprite?.Texture?.Name))
.ToArray();
if (!animals.Any())
return false;
// update sprites
- Texture2D texture = content.Load<Texture2D>(key);
+ Texture2D texture = content.Load<Texture2D>(assetName.BaseName);
foreach (TAnimal animal in animals)
animal.Sprite.spriteTexture = texture;
return true;
@@ -667,10 +671,10 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload the sprites for matching farm animals.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any textures were reloaded.</returns>
/// <remarks>Derived from <see cref="FarmAnimal.reload"/>.</remarks>
- private bool ReloadFarmAnimalSprites(LocalizedContentManager content, string key)
+ private bool ReloadFarmAnimalSprites(LocalizedContentManager content, IAssetName assetName)
{
// find matches
FarmAnimal[] animals = this.GetFarmAnimals().ToArray();
@@ -678,7 +682,7 @@ namespace StardewModdingAPI.Metadata
return false;
// update sprites
- Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(key));
+ Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName));
foreach (FarmAnimal animal in animals)
{
// get expected key
@@ -687,26 +691,26 @@ namespace StardewModdingAPI.Metadata
: animal.type.Value;
if (animal.showDifferentTextureWhenReadyForHarvest.Value && animal.currentProduce.Value <= 0)
expectedKey = $"Sheared{expectedKey}";
- expectedKey = $"Animals\\{expectedKey}";
+ expectedKey = $"Animals/{expectedKey}";
// reload asset
- if (expectedKey == key)
+ if (this.IsSameBaseName(assetName, expectedKey))
animal.Sprite.spriteTexture = texture.Value;
}
return texture.IsValueCreated;
}
/// <summary>Reload building textures.</summary>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadBuildings(string key)
+ private bool ReloadBuildings(IAssetName assetName)
{
// get paint mask info
const string paintMaskSuffix = "_PaintMask";
- bool isPaintMask = key.EndsWith(paintMaskSuffix, StringComparison.OrdinalIgnoreCase);
+ bool isPaintMask = assetName.BaseName.EndsWith(paintMaskSuffix, StringComparison.OrdinalIgnoreCase);
// get building type
- string type = Path.GetFileName(key);
+ string type = Path.GetFileName(assetName.BaseName);
if (isPaintMask)
type = type.Substring(0, type.Length - paintMaskSuffix.Length);
@@ -718,7 +722,7 @@ namespace StardewModdingAPI.Metadata
.ToArray();
// remove from paint mask cache
- bool removedFromCache = this.RemoveFromPaintMaskCache(key);
+ bool removedFromCache = this.RemoveFromPaintMaskCache(assetName);
// reload textures
if (buildings.Any())
@@ -734,22 +738,20 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload map seat textures.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <param name="ignoreWorld">Whether the in-game world is fully unloaded (e.g. on the title screen), so there's no need to propagate changes into the world.</param>
/// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadChairTiles(LocalizedContentManager content, string key, bool ignoreWorld)
+ private bool ReloadChairTiles(LocalizedContentManager content, IAssetName assetName, bool ignoreWorld)
{
- MapSeat.mapChairTexture = content.Load<Texture2D>(key);
+ MapSeat.mapChairTexture = content.Load<Texture2D>(assetName.BaseName);
if (!ignoreWorld)
{
- foreach (var location in this.GetLocations())
+ foreach (GameLocation location in this.GetLocations())
{
foreach (MapSeat seat in location.mapSeats.Where(p => p != null))
{
- string curKey = this.NormalizeAssetNameIgnoringEmpty(seat._loadedTextureFile);
-
- if (curKey == null || key.Equals(curKey, StringComparison.OrdinalIgnoreCase))
+ if (this.IsSameBaseName(assetName, seat._loadedTextureFile))
seat.overlayTexture = MapSeat.mapChairTexture;
}
}
@@ -760,9 +762,9 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload critter textures.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns the number of reloaded assets.</returns>
- private int ReloadCritterTextures(LocalizedContentManager content, string key)
+ private int ReloadCritterTextures(LocalizedContentManager content, IAssetName assetName)
{
// get critters
Critter[] critters =
@@ -770,7 +772,7 @@ namespace StardewModdingAPI.Metadata
from location in this.GetLocations()
where location.critters != null
from Critter critter in location.critters
- where this.NormalizeAssetNameIgnoringEmpty(critter.sprite?.Texture?.Name) == key
+ where this.IsSameBaseName(assetName, critter.sprite?.Texture?.Name)
select critter
)
.ToArray();
@@ -778,8 +780,8 @@ namespace StardewModdingAPI.Metadata
return 0;
// update sprites
- Texture2D texture = content.Load<Texture2D>(key);
- foreach (var entry in critters)
+ Texture2D texture = content.Load<Texture2D>(assetName.BaseName);
+ foreach (Critter entry in critters)
entry.sprite.spriteTexture = texture;
return critters.Length;
@@ -787,28 +789,26 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload the sprites for interior doors.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any doors were affected.</returns>
- private bool ReloadDoorSprites(LocalizedContentManager content, string key)
+ private bool ReloadDoorSprites(LocalizedContentManager content, IAssetName assetName)
{
- Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(key));
+ Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName));
foreach (GameLocation location in this.GetLocations())
{
- IEnumerable<InteriorDoor> doors = location.interiorDoors?.Doors;
+ IEnumerable<InteriorDoor?>? doors = location.interiorDoors?.Doors;
if (doors == null)
continue;
- foreach (InteriorDoor door in doors)
+ foreach (InteriorDoor? door in doors)
{
if (door?.Sprite == null)
continue;
- string textureName = this.NormalizeAssetNameIgnoringEmpty(this.Reflection.GetField<string>(door.Sprite, "textureName").GetValue());
- if (textureName != key)
- continue;
-
- door.Sprite.texture = texture.Value;
+ string? curKey = this.Reflection.GetField<string?>(door.Sprite, "textureName").GetValue();
+ if (this.IsSameBaseName(assetName, curKey))
+ door.Sprite.texture = texture.Value;
}
}
@@ -831,12 +831,12 @@ namespace StardewModdingAPI.Metadata
}
/// <summary>Reload the sprites for a fence type.</summary>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadFenceTextures(string key)
+ private bool ReloadFenceTextures(IAssetName assetName)
{
- // get fence type
- if (!int.TryParse(this.GetSegments(key)[1].Substring("Fence".Length), out int fenceType))
+ // get fence type (e.g. LooseSprites/Fence3 => 3)
+ if (!int.TryParse(this.GetSegments(assetName.BaseName)[1].Substring("Fence".Length), out int fenceType))
return false;
// get fences
@@ -859,22 +859,22 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload tree textures.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadGrassTextures(LocalizedContentManager content, string key)
+ private bool ReloadGrassTextures(LocalizedContentManager content, IAssetName assetName)
{
Grass[] grasses =
(
from location in this.GetLocations()
from grass in location.terrainFeatures.Values.OfType<Grass>()
- where this.NormalizeAssetNameIgnoringEmpty(grass.textureName()) == key
+ where this.IsSameBaseName(assetName, grass.textureName())
select grass
)
.ToArray();
if (grasses.Any())
{
- Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(key));
+ Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName));
foreach (Grass grass in grasses)
grass.texture = texture;
return true;
@@ -931,16 +931,16 @@ namespace StardewModdingAPI.Metadata
// warping onto the wrong tile (or even off-screen) if a patch changes the farmhouse
// map on location change.
if (playerPos.HasValue)
- Game1.player.Position = playerPos.Value;
+ Game1.player!.Position = playerPos.Value;
}
/// <summary>Reload the disposition data for matching NPCs.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any NPCs were affected.</returns>
- private bool ReloadNpcDispositions(LocalizedContentManager content, string key)
+ private bool ReloadNpcDispositions(LocalizedContentManager content, IAssetName assetName)
{
- IDictionary<string, string> data = content.Load<Dictionary<string, string>>(key);
+ IDictionary<string, string> data = content.Load<Dictionary<string, string>>(assetName.BaseName);
bool changed = false;
foreach (NPC npc in this.GetCharacters())
{
@@ -955,18 +955,16 @@ namespace StardewModdingAPI.Metadata
}
/// <summary>Reload the sprites for matching NPCs.</summary>
- /// <param name="keys">The asset keys to reload.</param>
- /// <param name="propagated">The asset keys which have been propagated.</param>
- private void ReloadNpcSprites(IEnumerable<string> keys, IDictionary<string, bool> propagated)
+ /// <param name="propagated">The asset keys which are being propagated.</param>
+ private void ReloadNpcSprites(IDictionary<IAssetName, bool> propagated)
{
// get NPCs
- HashSet<string> lookup = new HashSet<string>(keys, StringComparer.OrdinalIgnoreCase);
var characters =
(
from npc in this.GetCharacters()
- let key = this.NormalizeAssetNameIgnoringEmpty(npc.Sprite?.Texture?.Name)
- where key != null && lookup.Contains(key)
- select new { Npc = npc, Key = key }
+ let key = this.ParseAssetNameOrNull(npc.Sprite?.Texture?.Name)?.GetBaseAssetName()
+ where key != null && propagated.ContainsKey(key)
+ select new { Npc = npc, AssetName = key }
)
.ToArray();
if (!characters.Any())
@@ -975,63 +973,65 @@ namespace StardewModdingAPI.Metadata
// update sprite
foreach (var target in characters)
{
- target.Npc.Sprite.spriteTexture = this.LoadAndDisposeIfNeeded(target.Npc.Sprite.spriteTexture, target.Key);
- propagated[target.Key] = true;
+ target.Npc.Sprite.spriteTexture = this.LoadAndDisposeIfNeeded(target.Npc.Sprite.spriteTexture, target.AssetName.BaseName);
+ propagated[target.AssetName] = true;
}
}
/// <summary>Reload the portraits for matching NPCs.</summary>
- /// <param name="keys">The asset key to reload.</param>
- /// <param name="propagated">The asset keys which have been propagated.</param>
- private void ReloadNpcPortraits(IEnumerable<string> keys, IDictionary<string, bool> propagated)
+ /// <param name="propagated">The asset keys which are being propagated.</param>
+ private void ReloadNpcPortraits(IDictionary<IAssetName, bool> propagated)
{
// get NPCs
- HashSet<string> lookup = new HashSet<string>(keys, StringComparer.OrdinalIgnoreCase);
var characters =
(
from npc in this.GetCharacters()
where npc.isVillager()
- let key = this.NormalizeAssetNameIgnoringEmpty(npc.Portrait?.Name)
- where key != null && lookup.Contains(key)
- select new { Npc = npc, Key = key }
+ let key = this.ParseAssetNameOrNull(npc.Portrait?.Name)?.GetBaseAssetName()
+ where key != null && propagated.ContainsKey(key)
+ select new { Npc = npc, AssetName = key }
)
.ToList();
// special case: Gil is a private NPC field on the AdventureGuild class (only used for the portrait)
{
- string gilKey = this.NormalizeAssetNameIgnoringEmpty("Portraits/Gil");
- if (lookup.Contains(gilKey))
+ IAssetName gilKey = this.ParseAssetName("Portraits/Gil");
+ if (propagated.ContainsKey(gilKey))
{
GameLocation adventureGuild = Game1.getLocationFromName("AdventureGuild");
if (adventureGuild != null)
- characters.Add(new { Npc = this.Reflection.GetField<NPC>(adventureGuild, "Gil").GetValue(), Key = gilKey });
+ {
+ NPC? gil = this.Reflection.GetField<NPC?>(adventureGuild, "Gil").GetValue();
+ if (gil != null)
+ characters.Add(new { Npc = gil, AssetName = gilKey });
+ }
}
}
// update portrait
foreach (var target in characters)
{
- target.Npc.Portrait = this.LoadAndDisposeIfNeeded(target.Npc.Portrait, target.Key);
- propagated[target.Key] = true;
+ target.Npc.Portrait = this.LoadAndDisposeIfNeeded(target.Npc.Portrait, target.AssetName.BaseName);
+ propagated[target.AssetName] = true;
}
}
/// <summary>Reload the sprites for matching players.</summary>
- /// <param name="key">The asset key to reload.</param>
- private bool ReloadPlayerSprites(string key)
+ /// <param name="assetName">The asset name to reload.</param>
+ private bool ReloadPlayerSprites(IAssetName assetName)
{
Farmer[] players =
(
from player in Game1.getOnlineFarmers()
- where key == this.NormalizeAssetNameIgnoringEmpty(player.getTexture())
+ where this.IsSameBaseName(assetName, player.getTexture())
select player
)
.ToArray();
foreach (Farmer player in players)
{
- this.Reflection.GetField<Dictionary<string, Dictionary<int, List<int>>>>(typeof(FarmerRenderer), "_recolorOffsets").GetValue().Remove(player.getTexture());
+ this.Reflection.GetField<Dictionary<string, Dictionary<int, List<int>>>?>(typeof(FarmerRenderer), "_recolorOffsets").GetValue()?.Remove(player.getTexture());
player.FarmerRenderer.MarkSpriteDirty();
}
@@ -1040,22 +1040,27 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload suspension bridge textures.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadSuspensionBridges(LocalizedContentManager content, string key)
+ private bool ReloadSuspensionBridges(LocalizedContentManager content, IAssetName assetName)
{
- Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(key));
+ Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName));
foreach (GameLocation location in this.GetLocations(buildingInteriors: false))
{
// get suspension bridges field
- var field = this.Reflection.GetField<IEnumerable<SuspensionBridge>>(location, nameof(IslandNorth.suspensionBridges), required: false);
+ var field = this.Reflection.GetField<IEnumerable<SuspensionBridge>?>(location, nameof(IslandNorth.suspensionBridges), required: false);
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalse -- field is nullable when required: false
if (field == null || !typeof(IEnumerable<SuspensionBridge>).IsAssignableFrom(field.FieldInfo.FieldType))
continue;
// update textures
- foreach (SuspensionBridge bridge in field.GetValue())
- this.Reflection.GetField<Texture2D>(bridge, "_texture").SetValue(texture.Value);
+ IEnumerable<SuspensionBridge>? bridges = field.GetValue();
+ if (bridges != null)
+ {
+ foreach (SuspensionBridge bridge in bridges)
+ this.Reflection.GetField<Texture2D>(bridge, "_texture").SetValue(texture.Value);
+ }
}
return texture.IsValueCreated;
@@ -1063,10 +1068,10 @@ namespace StardewModdingAPI.Metadata
/// <summary>Reload tree textures.</summary>
/// <param name="content">The content manager through which to reload the asset.</param>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <param name="type">The type to reload.</param>
/// <returns>Returns whether any textures were reloaded.</returns>
- private bool ReloadTreeTextures(LocalizedContentManager content, string key, int type)
+ private bool ReloadTreeTextures(LocalizedContentManager content, IAssetName assetName, int type)
{
Tree[] trees = this.GetLocations()
.SelectMany(p => p.terrainFeatures.Values.OfType<Tree>())
@@ -1075,7 +1080,7 @@ namespace StardewModdingAPI.Metadata
if (trees.Any())
{
- Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(key));
+ Lazy<Texture2D> texture = new Lazy<Texture2D>(() => content.Load<Texture2D>(assetName.BaseName));
foreach (Tree tree in trees)
tree.texture = texture;
return true;
@@ -1088,12 +1093,12 @@ namespace StardewModdingAPI.Metadata
** Reload data methods
****/
/// <summary>Reload the dialogue data for matching NPCs.</summary>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any assets were reloaded.</returns>
- private bool ReloadNpcDialogue(string key)
+ private bool ReloadNpcDialogue(IAssetName assetName)
{
// get NPCs
- string name = Path.GetFileName(key);
+ string name = Path.GetFileName(assetName.BaseName);
NPC[] villagers = this.GetCharacters().Where(npc => npc.Name == name && npc.isVillager()).ToArray();
if (!villagers.Any())
return false;
@@ -1118,12 +1123,12 @@ namespace StardewModdingAPI.Metadata
}
/// <summary>Reload the schedules for matching NPCs.</summary>
- /// <param name="key">The asset key to reload.</param>
+ /// <param name="assetName">The asset name to reload.</param>
/// <returns>Returns whether any assets were reloaded.</returns>
- private bool ReloadNpcSchedules(string key)
+ private bool ReloadNpcSchedules(IAssetName assetName)
{
// get NPCs
- string name = Path.GetFileName(key);
+ string name = Path.GetFileName(assetName.BaseName);
NPC[] villagers = this.GetCharacters().Where(npc => npc.Name == name && npc.isVillager()).ToArray();
if (!villagers.Any())
return false;
@@ -1133,7 +1138,7 @@ namespace StardewModdingAPI.Metadata
{
// reload schedule
this.Reflection.GetField<bool>(villager, "_hasLoadedMasterScheduleData").SetValue(false);
- this.Reflection.GetField<Dictionary<string, string>>(villager, "_masterScheduleData").SetValue(null);
+ this.Reflection.GetField<Dictionary<string, string>?>(villager, "_masterScheduleData").SetValue(null);
villager.Schedule = villager.getSchedule(Game1.dayOfMonth);
// switch to new schedule if needed
@@ -1157,17 +1162,17 @@ namespace StardewModdingAPI.Metadata
/// <remarks>Derived from the <see cref="Game1.TranslateFields"/>.</remarks>
private bool ReloadStringsFromCsFiles(LocalizedContentManager content)
{
- Game1.samBandName = content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.2156");
- Game1.elliottBookName = content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.2157");
+ Game1.samBandName = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.2156");
+ Game1.elliottBookName = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.2157");
string[] dayNames = this.Reflection.GetField<string[]>(typeof(Game1), "_shortDayDisplayName").GetValue();
- dayNames[0] = content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3042");
- dayNames[1] = content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3043");
- dayNames[2] = content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3044");
- dayNames[3] = content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3045");
- dayNames[4] = content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3046");
- dayNames[5] = content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3047");
- dayNames[6] = content.LoadString("Strings\\StringsFromCSFiles:Game1.cs.3048");
+ dayNames[0] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3042");
+ dayNames[1] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3043");
+ dayNames[2] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3044");
+ dayNames[3] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3045");
+ dayNames[4] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3046");
+ dayNames[5] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3047");
+ dayNames[6] = content.LoadString("Strings/StringsFromCSFiles:Game1.cs.3048");
return true;
}
@@ -1229,7 +1234,7 @@ namespace StardewModdingAPI.Metadata
{
foreach (Building building in buildableLocation.buildings)
{
- GameLocation indoors = building.indoors.Value;
+ GameLocation? indoors = building.indoors.Value;
if (indoors != null)
yield return new LocationInfo(indoors, building);
}
@@ -1237,58 +1242,52 @@ namespace StardewModdingAPI.Metadata
}
}
- /// <summary>Normalize an asset key to match the cache key and assert that it's valid, but don't raise an error for null or empty values.</summary>
- /// <param name="path">The asset key to normalize.</param>
- private string NormalizeAssetNameIgnoringEmpty(string path)
+ /// <summary>Get whether two asset names are equivalent if you ignore the locale code.</summary>
+ /// <param name="left">The first value to compare.</param>
+ /// <param name="right">The second value to compare.</param>
+ private bool IsSameBaseName(IAssetName? left, string? right)
{
- if (string.IsNullOrWhiteSpace(path))
- return null;
+ if (left is null || right is null)
+ return false;
- return this.AssertAndNormalizeAssetName(path);
+ IAssetName? parsedB = this.ParseAssetNameOrNull(right);
+ return this.IsSameBaseName(left, parsedB);
}
- /// <summary>Get whether a key starts with a substring after the substring is normalized.</summary>
- /// <param name="key">The key to check.</param>
- /// <param name="rawSubstring">The substring to normalize and find.</param>
- private bool KeyStartsWith(string key, string rawSubstring)
+ /// <summary>Get whether two asset names are equivalent if you ignore the locale code.</summary>
+ /// <param name="left">The first value to compare.</param>
+ /// <param name="right">The second value to compare.</param>
+ private bool IsSameBaseName(IAssetName? left, IAssetName? right)
{
- if (string.IsNullOrWhiteSpace(key) || string.IsNullOrWhiteSpace(rawSubstring))
+ if (left is null || right is null)
return false;
- return key.StartsWith(this.NormalizeAssetNameIgnoringEmpty(rawSubstring), StringComparison.OrdinalIgnoreCase);
+ return left.IsEquivalentTo(right.BaseName, useBaseName: true);
}
- /// <summary>Get whether a normalized asset key is in the given folder.</summary>
- /// <param name="key">The normalized asset key (like <c>Animals/cat</c>).</param>
- /// <param name="folder">The key folder (like <c>Animals</c>); doesn't need to be normalized.</param>
- /// <param name="allowSubfolders">Whether to return true if the key is inside a subfolder of the <paramref name="folder"/>.</param>
- private bool IsInFolder(string key, string folder, bool allowSubfolders = false)
+ /// <summary>Normalize an asset key to match the cache key and assert that it's valid, but don't raise an error for null or empty values.</summary>
+ /// <param name="path">The asset key to normalize.</param>
+ private IAssetName? ParseAssetNameOrNull(string? path)
{
- return
- this.KeyStartsWith(key, $"{folder}\\")
- && (allowSubfolders || this.CountSegments(key) == this.CountSegments(folder) + 1);
+ if (string.IsNullOrWhiteSpace(path))
+ return null;
+
+ return this.ParseAssetName(path);
}
/// <summary>Get the segments in a path (e.g. 'a/b' is 'a' and 'b').</summary>
/// <param name="path">The path to check.</param>
- private string[] GetSegments(string path)
+ private string[] GetSegments(string? path)
{
return path != null
? PathUtilities.GetSegments(path)
- : new string[0];
- }
-
- /// <summary>Count the number of segments in a path (e.g. 'a/b' is 2).</summary>
- /// <param name="path">The path to check.</param>
- private int CountSegments(string path)
- {
- return this.GetSegments(path).Length;
+ : Array.Empty<string>();
}
/// <summary>Load a texture, and dispose the old one if <see cref="AggressiveMemoryOptimizations"/> is enabled and it's different from the new instance.</summary>
/// <param name="oldTexture">The previous texture to dispose.</param>
/// <param name="key">The asset key to load.</param>
- private Texture2D LoadAndDisposeIfNeeded(Texture2D oldTexture, string key)
+ private Texture2D LoadAndDisposeIfNeeded(Texture2D? oldTexture, string key)
{
// if aggressive memory optimizations are enabled, load the asset from the disposable
// content manager and dispose the old instance if needed.
@@ -1308,8 +1307,8 @@ namespace StardewModdingAPI.Metadata
}
/// <summary>Remove a case-insensitive key from the paint mask cache.</summary>
- /// <param name="key">The paint mask asset key.</param>
- private bool RemoveFromPaintMaskCache(string key)
+ /// <param name="assetName">The paint mask asset name.</param>
+ private bool RemoveFromPaintMaskCache(IAssetName assetName)
{
// make cache case-insensitive
// This is needed for cache invalidation since mods may specify keys with a different capitalization
@@ -1317,7 +1316,7 @@ namespace StardewModdingAPI.Metadata
BuildingPainter.paintMaskLookup = new Dictionary<string, List<List<int>>>(BuildingPainter.paintMaskLookup, StringComparer.OrdinalIgnoreCase);
// remove key from cache
- return BuildingPainter.paintMaskLookup.Remove(key);
+ return BuildingPainter.paintMaskLookup.Remove(assetName.BaseName);
}
/// <summary>Metadata about a location used in asset propagation.</summary>
@@ -1330,7 +1329,7 @@ namespace StardewModdingAPI.Metadata
public GameLocation Location { get; }
/// <summary>The building which contains the location, if any.</summary>
- public Building ParentBuilding { get; }
+ public Building? ParentBuilding { get; }
/*********
@@ -1339,7 +1338,7 @@ namespace StardewModdingAPI.Metadata
/// <summary>Construct an instance.</summary>
/// <param name="location">The location instance.</param>
/// <param name="parentBuilding">The building which contains the location, if any.</param>
- public LocationInfo(GameLocation location, Building parentBuilding)
+ public LocationInfo(GameLocation location, Building? parentBuilding)
{
this.Location = location;
this.ParentBuilding = parentBuilding;