From 5be3e5af5a08f9e72a969191dca07597d9c7c1f7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 20 Mar 2018 19:45:45 -0400 Subject: rename class to better match usage (#459) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 221 ++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 src/SMAPI/Metadata/CoreAssetPropagator.cs (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs new file mode 100644 index 00000000..d6a731cd --- /dev/null +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework.Graphics; +using StardewModdingAPI.Framework.Reflection; +using StardewValley; +using StardewValley.BellsAndWhistles; +using StardewValley.Buildings; +using StardewValley.Locations; +using StardewValley.Menus; +using StardewValley.Objects; +using StardewValley.Projectiles; +using StardewValley.TerrainFeatures; + +namespace StardewModdingAPI.Metadata +{ + /// Handles updating the game when a mod changes core assets. + /// This implementation only handles the core assets used by the game itself, and doesn't update any custom references to the changed textures. + internal class CoreAssetPropagator + { + /********* + ** Properties + *********/ + /// Normalises an asset key to match the cache key. + protected readonly Func GetNormalisedPath; + + /// Setters which update static or singleton texture fields indexed by normalised asset key. + private readonly IDictionary> SingletonSetters; + + + /********* + ** Public methods + *********/ + /// Initialise the core asset data. + /// Normalises an asset key to match the cache key. + /// Simplifies access to private code. + public CoreAssetPropagator(Func getNormalisedPath, Reflector reflection) + { + this.GetNormalisedPath = getNormalisedPath; + this.SingletonSetters = + new Dictionary> + { + // from CraftingRecipe.InitShared + ["Data\\CraftingRecipes"] = (content, key) => CraftingRecipe.craftingRecipes = content.Load>(key), + ["Data\\CookingRecipes"] = (content, key) => CraftingRecipe.cookingRecipes = content.Load>(key), + + // from Game1.loadContent + ["LooseSprites\\daybg"] = (content, key) => Game1.daybg = content.Load(key), + ["LooseSprites\\nightbg"] = (content, key) => Game1.nightbg = content.Load(key), + ["Maps\\MenuTiles"] = (content, key) => Game1.menuTexture = content.Load(key), + ["LooseSprites\\Lighting\\lantern"] = (content, key) => Game1.lantern = content.Load(key), + ["LooseSprites\\Lighting\\windowLight"] = (content, key) => Game1.windowLight = content.Load(key), + ["LooseSprites\\Lighting\\sconceLight"] = (content, key) => Game1.sconceLight = content.Load(key), + ["LooseSprites\\Lighting\\greenLight"] = (content, key) => Game1.cauldronLight = content.Load(key), + ["LooseSprites\\Lighting\\indoorWindowLight"] = (content, key) => Game1.indoorWindowLight = content.Load(key), + ["LooseSprites\\shadow"] = (content, key) => Game1.shadowTexture = content.Load(key), + ["LooseSprites\\Cursors"] = (content, key) => Game1.mouseCursors = content.Load(key), + ["LooseSprites\\ControllerMaps"] = (content, key) => Game1.controllerMaps = content.Load(key), + ["TileSheets\\animations"] = (content, key) => Game1.animations = content.Load(key), + ["Data\\Achievements"] = (content, key) => Game1.achievements = content.Load>(key), + ["Data\\NPCGiftTastes"] = (content, key) => Game1.NPCGiftTastes = content.Load>(key), + ["Fonts\\SpriteFont1"] = (content, key) => Game1.dialogueFont = content.Load(key), + ["Fonts\\SmallFont"] = (content, key) => Game1.smallFont = content.Load(key), + ["Fonts\\tinyFont"] = (content, key) => Game1.tinyFont = content.Load(key), + ["Fonts\\tinyFontBorder"] = (content, key) => Game1.tinyFontBorder = content.Load(key), + ["Maps\\springobjects"] = (content, key) => Game1.objectSpriteSheet = content.Load(key), + ["TileSheets\\crops"] = (content, key) => Game1.cropSpriteSheet = content.Load(key), + ["TileSheets\\emotes"] = (content, key) => Game1.emoteSpriteSheet = content.Load(key), + ["TileSheets\\debris"] = (content, key) => Game1.debrisSpriteSheet = content.Load(key), + ["TileSheets\\Craftables"] = (content, key) => Game1.bigCraftableSpriteSheet = content.Load(key), + ["TileSheets\\rain"] = (content, key) => Game1.rainTexture = content.Load(key), + ["TileSheets\\BuffsIcons"] = (content, key) => Game1.buffsIcons = content.Load(key), + ["Data\\ObjectInformation"] = (content, key) => Game1.objectInformation = content.Load>(key), + ["Data\\BigCraftablesInformation"] = (content, key) => Game1.bigCraftablesInformation = content.Load>(key), + ["Characters\\Farmer\\hairstyles"] = (content, key) => FarmerRenderer.hairStylesTexture = content.Load(key), + ["Characters\\Farmer\\shirts"] = (content, key) => FarmerRenderer.shirtsTexture = content.Load(key), + ["Characters\\Farmer\\hats"] = (content, key) => FarmerRenderer.hatsTexture = content.Load(key), + ["Characters\\Farmer\\accessories"] = (content, key) => FarmerRenderer.accessoriesTexture = content.Load(key), + ["TileSheets\\furniture"] = (content, key) => Furniture.furnitureTexture = content.Load(key), + ["LooseSprites\\font_bold"] = (content, key) => SpriteText.spriteTexture = content.Load(key), + ["LooseSprites\\font_colored"] = (content, key) => SpriteText.coloredTexture = content.Load(key), + ["TileSheets\\weapons"] = (content, key) => Tool.weaponsTexture = content.Load(key), + ["TileSheets\\Projectiles"] = (content, key) => Projectile.projectileSheet = content.Load(key), + + // from Game1.ResetToolSpriteSheet + ["TileSheets\\tools"] = (content, key) => Game1.ResetToolSpriteSheet(), + +#if STARDEW_VALLEY_1_3 + // from Bush + ["TileSheets\\bushes"] = (content, key) => reflection.GetField>(typeof(Bush), "texture").SetValue(new Lazy(() => content.Load(key))), + + // from Farm + ["Buildings\\houses"] = (content, key) => reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load(key)), + + // from Farmer + ["Characters\\Farmer\\farmer_base"] = (content, key) => + { + if (Game1.player != null && Game1.player.isMale) + Game1.player.FarmerRenderer = new FarmerRenderer(key); + }, + ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => + { + if (Game1.player != null && !Game1.player.isMale) + Game1.player.FarmerRenderer = new FarmerRenderer(key); + }, +#else + // from Bush + ["TileSheets\\bushes"] = (content, key) => Bush.texture = content.Load(key), + + // from Critter + ["TileSheets\\critters"] = (content, key) => Critter.critterTexture = content.Load(key), + + // from Farm + ["Buildings\\houses"] = (content, key) => + { + Farm farm = Game1.getFarm(); + if (farm != null) + farm.houseTextures = content.Load(key); + }, + + // from Farmer + ["Characters\\Farmer\\farmer_base"] = (content, key) => + { + if (Game1.player != null && Game1.player.isMale) + Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); + }, + ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => + { + if (Game1.player != null && !Game1.player.isMale) + Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); + }, +#endif + + // from Flooring + ["TerrainFeatures\\Flooring"] = (content, key) => Flooring.floorsTexture = content.Load(key), + + // from FruitTree + ["TileSheets\\fruitTrees"] = (content, key) => FruitTree.texture = content.Load(key), + + // from HoeDirt + ["TerrainFeatures\\hoeDirt"] = (content, key) => HoeDirt.lightTexture = content.Load(key), + ["TerrainFeatures\\hoeDirtDark"] = (content, key) => HoeDirt.darkTexture = content.Load(key), + ["TerrainFeatures\\hoeDirtSnow"] = (content, key) => HoeDirt.snowTexture = content.Load(key), + + // from TitleMenu + ["Minigames\\Clouds"] = (content, key) => + { + if (Game1.activeClickableMenu is TitleMenu) + reflection.GetField(Game1.activeClickableMenu, "cloudsTexture").SetValue(content.Load(key)); + }, + ["Minigames\\TitleButtons"] = (content, key) => + { + if (Game1.activeClickableMenu is TitleMenu titleMenu) + { + reflection.GetField(titleMenu, "titleButtonsTexture").SetValue(content.Load(key)); + foreach (TemporaryAnimatedSprite bird in reflection.GetField>(titleMenu, "birds").GetValue()) +#if STARDEW_VALLEY_1_3 + bird.texture = content.Load(key); +#else + bird.Texture = content.Load(key); +#endif + } + }, + + // from Wallpaper + ["Maps\\walls_and_floors"] = (content, key) => Wallpaper.wallpaperTexture = content.Load(key) + } + .ToDictionary(p => getNormalisedPath(p.Key), p => p.Value); + } + + /// Reload one of the game's core assets (if applicable). + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether an asset was reloaded. + public bool ReloadForKey(LocalizedContentManager content, string key) + { + // static assets + if (this.SingletonSetters.TryGetValue(key, out Action reload)) + { + reload(content, key); + return true; + } + + // building textures + if (key.StartsWith(this.GetNormalisedPath("Buildings\\"))) + { + Building[] buildings = this.GetAllBuildings().Where(p => key == this.GetNormalisedPath($"Buildings\\{p.buildingType}")).ToArray(); + if (buildings.Any()) + { +#if STARDEW_VALLEY_1_3 + foreach (Building building in buildings) + building.texture = new Lazy(() => content.Load(key)); +#else + Texture2D texture = content.Load(key); + foreach (Building building in buildings) + building.texture = texture; +#endif + + return true; + } + return false; + } + + return false; + } + + + /********* + ** Private methods + *********/ + /// Get all player-constructed buildings in the world. + private IEnumerable GetAllBuildings() + { + foreach (BuildableGameLocation location in Game1.locations.OfType()) + { + foreach (Building building in location.buildings) + yield return building; + } + } + } +} -- cgit From de5ee6f928339198d3c3ab0a91e9343863782c59 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 20 Mar 2018 21:22:19 -0400 Subject: rewrite core asset logic for extensibility (#459) --- src/SMAPI/Framework/ContentCore.cs | 2 +- src/SMAPI/Metadata/CoreAssetPropagator.cs | 379 +++++++++++++++++++----------- 2 files changed, 249 insertions(+), 132 deletions(-) (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs') diff --git a/src/SMAPI/Framework/ContentCore.cs b/src/SMAPI/Framework/ContentCore.cs index d5848d7b..3c7e7b5a 100644 --- a/src/SMAPI/Framework/ContentCore.cs +++ b/src/SMAPI/Framework/ContentCore.cs @@ -368,7 +368,7 @@ namespace StardewModdingAPI.Framework int reloaded = 0; foreach (string key in removeAssetNames) { - if (this.CoreAssets.ReloadForKey(Game1.content, key)) // use an intercepted content manager + if (this.CoreAssets.Propagate(Game1.content, key)) // use an intercepted content manager reloaded++; } diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index d6a731cd..85021727 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -14,18 +14,17 @@ using StardewValley.TerrainFeatures; namespace StardewModdingAPI.Metadata { - /// Handles updating the game when a mod changes core assets. - /// This implementation only handles the core assets used by the game itself, and doesn't update any custom references to the changed textures. + /// Propagates changes to core assets to the game state. internal class CoreAssetPropagator { /********* ** Properties *********/ /// Normalises an asset key to match the cache key. - protected readonly Func GetNormalisedPath; + private readonly Func GetNormalisedPath; - /// Setters which update static or singleton texture fields indexed by normalised asset key. - private readonly IDictionary> SingletonSetters; + /// Simplifies access to private game code. + private readonly Reflector Reflection; /********* @@ -37,154 +36,272 @@ namespace StardewModdingAPI.Metadata public CoreAssetPropagator(Func getNormalisedPath, Reflector reflection) { this.GetNormalisedPath = getNormalisedPath; - this.SingletonSetters = - new Dictionary> - { - // from CraftingRecipe.InitShared - ["Data\\CraftingRecipes"] = (content, key) => CraftingRecipe.craftingRecipes = content.Load>(key), - ["Data\\CookingRecipes"] = (content, key) => CraftingRecipe.cookingRecipes = content.Load>(key), - - // from Game1.loadContent - ["LooseSprites\\daybg"] = (content, key) => Game1.daybg = content.Load(key), - ["LooseSprites\\nightbg"] = (content, key) => Game1.nightbg = content.Load(key), - ["Maps\\MenuTiles"] = (content, key) => Game1.menuTexture = content.Load(key), - ["LooseSprites\\Lighting\\lantern"] = (content, key) => Game1.lantern = content.Load(key), - ["LooseSprites\\Lighting\\windowLight"] = (content, key) => Game1.windowLight = content.Load(key), - ["LooseSprites\\Lighting\\sconceLight"] = (content, key) => Game1.sconceLight = content.Load(key), - ["LooseSprites\\Lighting\\greenLight"] = (content, key) => Game1.cauldronLight = content.Load(key), - ["LooseSprites\\Lighting\\indoorWindowLight"] = (content, key) => Game1.indoorWindowLight = content.Load(key), - ["LooseSprites\\shadow"] = (content, key) => Game1.shadowTexture = content.Load(key), - ["LooseSprites\\Cursors"] = (content, key) => Game1.mouseCursors = content.Load(key), - ["LooseSprites\\ControllerMaps"] = (content, key) => Game1.controllerMaps = content.Load(key), - ["TileSheets\\animations"] = (content, key) => Game1.animations = content.Load(key), - ["Data\\Achievements"] = (content, key) => Game1.achievements = content.Load>(key), - ["Data\\NPCGiftTastes"] = (content, key) => Game1.NPCGiftTastes = content.Load>(key), - ["Fonts\\SpriteFont1"] = (content, key) => Game1.dialogueFont = content.Load(key), - ["Fonts\\SmallFont"] = (content, key) => Game1.smallFont = content.Load(key), - ["Fonts\\tinyFont"] = (content, key) => Game1.tinyFont = content.Load(key), - ["Fonts\\tinyFontBorder"] = (content, key) => Game1.tinyFontBorder = content.Load(key), - ["Maps\\springobjects"] = (content, key) => Game1.objectSpriteSheet = content.Load(key), - ["TileSheets\\crops"] = (content, key) => Game1.cropSpriteSheet = content.Load(key), - ["TileSheets\\emotes"] = (content, key) => Game1.emoteSpriteSheet = content.Load(key), - ["TileSheets\\debris"] = (content, key) => Game1.debrisSpriteSheet = content.Load(key), - ["TileSheets\\Craftables"] = (content, key) => Game1.bigCraftableSpriteSheet = content.Load(key), - ["TileSheets\\rain"] = (content, key) => Game1.rainTexture = content.Load(key), - ["TileSheets\\BuffsIcons"] = (content, key) => Game1.buffsIcons = content.Load(key), - ["Data\\ObjectInformation"] = (content, key) => Game1.objectInformation = content.Load>(key), - ["Data\\BigCraftablesInformation"] = (content, key) => Game1.bigCraftablesInformation = content.Load>(key), - ["Characters\\Farmer\\hairstyles"] = (content, key) => FarmerRenderer.hairStylesTexture = content.Load(key), - ["Characters\\Farmer\\shirts"] = (content, key) => FarmerRenderer.shirtsTexture = content.Load(key), - ["Characters\\Farmer\\hats"] = (content, key) => FarmerRenderer.hatsTexture = content.Load(key), - ["Characters\\Farmer\\accessories"] = (content, key) => FarmerRenderer.accessoriesTexture = content.Load(key), - ["TileSheets\\furniture"] = (content, key) => Furniture.furnitureTexture = content.Load(key), - ["LooseSprites\\font_bold"] = (content, key) => SpriteText.spriteTexture = content.Load(key), - ["LooseSprites\\font_colored"] = (content, key) => SpriteText.coloredTexture = content.Load(key), - ["TileSheets\\weapons"] = (content, key) => Tool.weaponsTexture = content.Load(key), - ["TileSheets\\Projectiles"] = (content, key) => Projectile.projectileSheet = content.Load(key), - - // from Game1.ResetToolSpriteSheet - ["TileSheets\\tools"] = (content, key) => Game1.ResetToolSpriteSheet(), + this.Reflection = reflection; + } -#if STARDEW_VALLEY_1_3 - // from Bush - ["TileSheets\\bushes"] = (content, key) => reflection.GetField>(typeof(Bush), "texture").SetValue(new Lazy(() => content.Load(key))), + /// Reload one of the game's core assets (if applicable). + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether an asset was reloaded. + public bool Propagate(LocalizedContentManager content, string key) + { + return this.PropagateImpl(content, key) != null; + } - // from Farm - ["Buildings\\houses"] = (content, key) => reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load(key)), - // from Farmer - ["Characters\\Farmer\\farmer_base"] = (content, key) => - { - if (Game1.player != null && Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(key); - }, - ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => + /********* + ** Private methods + *********/ + /// Reload one of the game's core assets (if applicable). + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns any non-null value to indicate an asset was loaded.. + private object PropagateImpl(LocalizedContentManager content, string key) + { + Reflector reflection = this.Reflection; + switch (key.ToLower().Replace("/", "\\")) // normalised key so we can compare statically + { + /**** + ** Buildings + ****/ + case "buildings\\houses": // Farm +#if STARDEW_VALLEY_1_3 + reflection.GetField(typeof(Farm), nameof(Farm.houseTextures)).SetValue(content.Load(key)); + return true; +#else { - if (Game1.player != null && !Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(key); - }, + Farm farm = Game1.getFarm(); + if (farm == null) + return null; + return farm.houseTextures = content.Load(key); + } +#endif + + /**** + ** Content\Characters\Farmer + ****/ + case "characters\\farmer\\accessories": // Game1.loadContent + return FarmerRenderer.accessoriesTexture = content.Load(key); + + case "characters\\farmer\\farmer_base": // Farmer + if (Game1.player == null || !Game1.player.isMale) + return null; +#if STARDEW_VALLEY_1_3 + return Game1.player.FarmerRenderer = new FarmerRenderer(key); #else - // from Bush - ["TileSheets\\bushes"] = (content, key) => Bush.texture = content.Load(key), + return Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); +#endif - // from Critter - ["TileSheets\\critters"] = (content, key) => Critter.critterTexture = content.Load(key), + case "characters\\farmer\\farmer_girl_base": // Farmer + if (Game1.player == null || Game1.player.isMale) + return null; +#if STARDEW_VALLEY_1_3 + return Game1.player.FarmerRenderer = new FarmerRenderer(key); +#else + return Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); +#endif - // from Farm - ["Buildings\\houses"] = (content, key) => - { - Farm farm = Game1.getFarm(); - if (farm != null) - farm.houseTextures = content.Load(key); - }, + case "characters\\farmer\\hairstyles": // Game1.loadContent + return FarmerRenderer.hairStylesTexture = content.Load(key); + + case "characters\\farmer\\hats": // Game1.loadContent + return FarmerRenderer.hatsTexture = content.Load(key); + + case "characters\\farmer\\shirts": // Game1.loadContent + return FarmerRenderer.shirtsTexture = content.Load(key); + + /**** + ** Content\Data + ****/ + case "data\\achievements": // Game1.loadContent + return Game1.achievements = content.Load>(key); + + case "data\\bigcraftablesinformation": // Game1.loadContent + return Game1.bigCraftablesInformation = content.Load>(key); + + case "data\\cookingrecipes": // CraftingRecipe.InitShared + return CraftingRecipe.cookingRecipes = content.Load>(key); + + case "data\\craftingrecipes": // CraftingRecipe.InitShared + return CraftingRecipe.craftingRecipes = content.Load>(key); + + case "data\\npcgifttastes": // Game1.loadContent + return Game1.NPCGiftTastes = content.Load>(key); + + case "data\\objectinformation": // Game1.loadContent + return Game1.objectInformation = content.Load>(key); + + /**** + ** Content\Fonts + ****/ + case "fonts\\spritefont1": // Game1.loadContent + return Game1.dialogueFont = content.Load(key); + + case "fonts\\smallfont": // Game1.loadContent + return Game1.smallFont = content.Load(key); + + case "fonts\\tinyfont": // Game1.loadContent + return Game1.tinyFont = content.Load(key); + + case "fonts\\tinyfontborder": // Game1.loadContent + return Game1.tinyFontBorder = content.Load(key); - // from Farmer - ["Characters\\Farmer\\farmer_base"] = (content, key) => + /**** + ** Content\Lighting + ****/ + case "loosesprites\\lighting\\greenlight": // Game1.loadContent + return Game1.cauldronLight = content.Load(key); + + case "loosesprites\\lighting\\indoorwindowlight": // Game1.loadContent + return Game1.indoorWindowLight = content.Load(key); + + case "loosesprites\\lighting\\lantern": // Game1.loadContent + return Game1.lantern = content.Load(key); + + case "loosesprites\\lighting\\sconcelight": // Game1.loadContent + return Game1.sconceLight = content.Load(key); + + case "loosesprites\\lighting\\windowlight": // Game1.loadContent + return Game1.windowLight = content.Load(key); + + /**** + ** Content\LooseSprites + ****/ + case "loosesprites\\controllermaps": // Game1.loadContent + return Game1.controllerMaps = content.Load(key); + + case "loosesprites\\cursors": // Game1.loadContent + return Game1.mouseCursors = content.Load(key); + + case "loosesprites\\daybg": // Game1.loadContent + return Game1.daybg = content.Load(key); + + case "loosesprites\\font_bold": // Game1.loadContent + return SpriteText.spriteTexture = content.Load(key); + + case "loosesprites\\font_colored": // Game1.loadContent + return SpriteText.coloredTexture = content.Load(key); + + case "loosesprites\\nightbg": // Game1.loadContent + return Game1.nightbg = content.Load(key); + + case "loosesprites\\shadow": // Game1.loadContent + return Game1.shadowTexture = content.Load(key); + + /**** + ** Content\Critters + ****/ + case "tilesheets\\critters": // Criter.InitShared + return Critter.critterTexture = content.Load(key); + + case "tilesheets\\crops": // Game1.loadContent + return Game1.cropSpriteSheet = content.Load(key); + + case "tilesheets\\debris": // Game1.loadContent + return Game1.debrisSpriteSheet = content.Load(key); + + case "tilesheets\\emotes": // Game1.loadContent + return Game1.emoteSpriteSheet = content.Load(key); + + case "tilesheets\\furniture": // Game1.loadContent + return Furniture.furnitureTexture = content.Load(key); + + case "tilesheets\\projectiles": // Game1.loadContent + return Projectile.projectileSheet = content.Load(key); + + case "tilesheets\\rain": // Game1.loadContent + return Game1.rainTexture = content.Load(key); + + case "tilesheets\\tools": // Game1.ResetToolSpriteSheet + Game1.ResetToolSpriteSheet(); + return true; + + case "tilesheets\\weapons": // Game1.loadContent + return Tool.weaponsTexture = content.Load(key); + + /**** + ** Content\Maps + ****/ + case "maps\\menutiles": // Game1.loadContent + return Game1.menuTexture = content.Load(key); + + case "maps\\springobjects": // Game1.loadContent + return Game1.objectSpriteSheet = content.Load(key); + + case "maps\\walls_and_floors": // Wallpaper + return Wallpaper.wallpaperTexture = content.Load(key); + + /**** + ** Content\Minigames + ****/ + case "minigames\\clouds": // TitleMenu + if (Game1.activeClickableMenu is TitleMenu) { - if (Game1.player != null && Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); - }, - ["Characters\\Farmer\\farmer_girl_base"] = (content, key) => + reflection.GetField(Game1.activeClickableMenu, "cloudsTexture").SetValue(content.Load(key)); + return true; + } + + return null; + + case "minigames\\titlebuttons": // TitleMenu + if (Game1.activeClickableMenu is TitleMenu titleMenu) { - if (Game1.player != null && !Game1.player.isMale) - Game1.player.FarmerRenderer = new FarmerRenderer(content.Load(key)); - }, + Texture2D texture = content.Load(key); + reflection.GetField(titleMenu, "titleButtonsTexture").SetValue(texture); + foreach (TemporaryAnimatedSprite bird in reflection.GetField>(titleMenu, "birds").GetValue()) +#if STARDEW_VALLEY_1_3 + bird.texture = texture; +#else + bird.Texture = texture; #endif + return true; + } - // from Flooring - ["TerrainFeatures\\Flooring"] = (content, key) => Flooring.floorsTexture = content.Load(key), + return null; - // from FruitTree - ["TileSheets\\fruitTrees"] = (content, key) => FruitTree.texture = content.Load(key), + /**** + ** Content\TileSheets + ****/ + case "tilesheets\\animations": // Game1.loadContent + return Game1.animations = content.Load(key); - // from HoeDirt - ["TerrainFeatures\\hoeDirt"] = (content, key) => HoeDirt.lightTexture = content.Load(key), - ["TerrainFeatures\\hoeDirtDark"] = (content, key) => HoeDirt.darkTexture = content.Load(key), - ["TerrainFeatures\\hoeDirtSnow"] = (content, key) => HoeDirt.snowTexture = content.Load(key), + case "tilesheets\\buffsicons": // Game1.loadContent + return Game1.buffsIcons = content.Load(key); - // from TitleMenu - ["Minigames\\Clouds"] = (content, key) => - { - if (Game1.activeClickableMenu is TitleMenu) - reflection.GetField(Game1.activeClickableMenu, "cloudsTexture").SetValue(content.Load(key)); - }, - ["Minigames\\TitleButtons"] = (content, key) => - { - if (Game1.activeClickableMenu is TitleMenu titleMenu) - { - reflection.GetField(titleMenu, "titleButtonsTexture").SetValue(content.Load(key)); - foreach (TemporaryAnimatedSprite bird in reflection.GetField>(titleMenu, "birds").GetValue()) + case "tilesheets\\bushes": // new Bush() #if STARDEW_VALLEY_1_3 - bird.texture = content.Load(key); + reflection.GetField>(typeof(Bush), "texture").SetValue(new Lazy(() => content.Load(key))); + return true; #else - bird.Texture = content.Load(key); + return Bush.texture = content.Load(key); #endif - } - }, - // from Wallpaper - ["Maps\\walls_and_floors"] = (content, key) => Wallpaper.wallpaperTexture = content.Load(key) - } - .ToDictionary(p => getNormalisedPath(p.Key), p => p.Value); - } + case "tilesheets\\craftables": // Game1.loadContent + return Game1.bigCraftableSpriteSheet = content.Load(key); - /// Reload one of the game's core assets (if applicable). - /// The content manager through which to reload the asset. - /// The asset key to reload. - /// Returns whether an asset was reloaded. - public bool ReloadForKey(LocalizedContentManager content, string key) - { - // static assets - if (this.SingletonSetters.TryGetValue(key, out Action reload)) - { - reload(content, key); - return true; + case "tilesheets\\fruittrees": // FruitTree + return FruitTree.texture = content.Load(key); + + /**** + ** Content\TerrainFeatures + ****/ + case "terrainfeatures\\flooring": // Flooring + return Flooring.floorsTexture = content.Load(key); + + case "terrainfeatures\\hoedirt": // from HoeDirt + return HoeDirt.lightTexture = content.Load(key); + + case "Terrainfeatures\\hoedirtdark": // from HoeDirt + return HoeDirt.darkTexture = content.Load(key); + + case "Terrainfeatures\\hoedirtsnow": // from HoeDirt + return HoeDirt.snowTexture = content.Load(key); } // building textures - if (key.StartsWith(this.GetNormalisedPath("Buildings\\"))) + if (key.StartsWith(this.GetNormalisedPath("Buildings\\"), StringComparison.InvariantCultureIgnoreCase)) { - Building[] buildings = this.GetAllBuildings().Where(p => key == this.GetNormalisedPath($"Buildings\\{p.buildingType}")).ToArray(); + Building[] buildings = this.GetAllBuildings().Where(p => key.Equals(this.GetNormalisedPath($"Buildings\\{p.buildingType?.ToLower()}"), StringComparison.InvariantCultureIgnoreCase)).ToArray(); if (buildings.Any()) { #if STARDEW_VALLEY_1_3 @@ -198,10 +315,10 @@ namespace StardewModdingAPI.Metadata return true; } - return false; + return null; } - return false; + return null; } -- cgit From 51368b8afb0c2064a70ed09f41570ca8fac5fdfa Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Fri, 23 Mar 2018 20:18:23 -0400 Subject: update tree textures when changeed through the content API (#459) --- docs/release-notes.md | 3 + src/SMAPI/Metadata/CoreAssetPropagator.cs | 120 ++++++++++++++++++++++-------- 2 files changed, 91 insertions(+), 32 deletions(-) (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 059105c3..05a37ea8 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,6 +16,9 @@ --> ## 2.5.4 +* For modders: + * Added automatic texture update for trees when changed through the content API. + * For the [log parser][]: * Fixed error when log text contains certain tokens. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 85021727..21aaeb6c 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI.Framework.Reflection; @@ -45,7 +46,10 @@ namespace StardewModdingAPI.Metadata /// Returns whether an asset was reloaded. public bool Propagate(LocalizedContentManager content, string key) { - return this.PropagateImpl(content, key) != null; + object result = this.PropagateImpl(content, key); + if (result is bool b) + return b; + return result != null; } @@ -55,7 +59,7 @@ namespace StardewModdingAPI.Metadata /// Reload one of the game's core assets (if applicable). /// The content manager through which to reload the asset. /// The asset key to reload. - /// Returns any non-null value to indicate an asset was loaded.. + /// Returns any non-null value to indicate an asset was loaded. private object PropagateImpl(LocalizedContentManager content, string key) { Reflector reflection = this.Reflection; @@ -72,7 +76,7 @@ namespace StardewModdingAPI.Metadata { Farm farm = Game1.getFarm(); if (farm == null) - return null; + return false; return farm.houseTextures = content.Load(key); } #endif @@ -85,7 +89,7 @@ namespace StardewModdingAPI.Metadata case "characters\\farmer\\farmer_base": // Farmer if (Game1.player == null || !Game1.player.isMale) - return null; + return false; #if STARDEW_VALLEY_1_3 return Game1.player.FarmerRenderer = new FarmerRenderer(key); #else @@ -94,7 +98,7 @@ namespace StardewModdingAPI.Metadata case "characters\\farmer\\farmer_girl_base": // Farmer if (Game1.player == null || Game1.player.isMale) - return null; + return false; #if STARDEW_VALLEY_1_3 return Game1.player.FarmerRenderer = new FarmerRenderer(key); #else @@ -240,8 +244,7 @@ namespace StardewModdingAPI.Metadata reflection.GetField(Game1.activeClickableMenu, "cloudsTexture").SetValue(content.Load(key)); return true; } - - return null; + return false; case "minigames\\titlebuttons": // TitleMenu if (Game1.activeClickableMenu is TitleMenu titleMenu) @@ -256,8 +259,7 @@ namespace StardewModdingAPI.Metadata #endif return true; } - - return null; + return false; /**** ** Content\TileSheets @@ -291,48 +293,102 @@ namespace StardewModdingAPI.Metadata case "terrainfeatures\\hoedirt": // from HoeDirt return HoeDirt.lightTexture = content.Load(key); - case "Terrainfeatures\\hoedirtdark": // from HoeDirt + case "terrainfeatures\\hoedirtdark": // from HoeDirt return HoeDirt.darkTexture = content.Load(key); - case "Terrainfeatures\\hoedirtsnow": // from HoeDirt + case "terrainfeatures\\hoedirtsnow": // from HoeDirt return HoeDirt.snowTexture = content.Load(key); + + case "terrainfeatures\\mushroom_tree": // from Tree + return this.ReloadTreeTextures(content, key, Tree.mushroomTree); + + case "terrainfeatures\\tree_palm": // from Tree + return this.ReloadTreeTextures(content, key, 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 this.ReloadTreeTextures(content, key, 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 this.ReloadTreeTextures(content, key, Tree.leafyTree); + + case "terrainfeatures\\tree3_fall": // from Tree + case "terrainfeatures\\tree3_spring": // from Tree + case "terrainfeatures\\tree3_winter": // from Tree + return this.ReloadTreeTextures(content, key, Tree.pineTree); } // building textures if (key.StartsWith(this.GetNormalisedPath("Buildings\\"), StringComparison.InvariantCultureIgnoreCase)) { - Building[] buildings = this.GetAllBuildings().Where(p => key.Equals(this.GetNormalisedPath($"Buildings\\{p.buildingType?.ToLower()}"), StringComparison.InvariantCultureIgnoreCase)).ToArray(); - if (buildings.Any()) - { -#if STARDEW_VALLEY_1_3 - foreach (Building building in buildings) - building.texture = new Lazy(() => content.Load(key)); -#else - Texture2D texture = content.Load(key); - foreach (Building building in buildings) - building.texture = texture; -#endif - - return true; - } - return null; + string type = Path.GetFileName(key); + return this.ReloadBuildings(content, key, type); } - return null; + return false; } /********* ** Private methods *********/ - /// Get all player-constructed buildings in the world. - private IEnumerable GetAllBuildings() + /// Reload building textures. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// The type to reload. + /// Returns whether any textures were reloaded. + private bool ReloadBuildings(LocalizedContentManager content, string key, string type) + { + Building[] buildings = Game1.locations + .OfType() + .SelectMany(p => p.buildings) + .Where(p => p.buildingType == type) + .ToArray(); + + if (buildings.Any()) + { + Lazy texture = new Lazy(() => content.Load(key)); + foreach (Building building in buildings) +#if STARDEW_VALLEY_1_3 + building.texture = texture; +#else + building.texture = texture.Value; +#endif + return true; + } + return false; + } + + /// Reload tree textures. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// The type to reload. + /// Returns whether any textures were reloaded. + private bool ReloadTreeTextures(LocalizedContentManager content, string key, int type) { - foreach (BuildableGameLocation location in Game1.locations.OfType()) + Tree[] trees = Game1.locations + .SelectMany(p => p.terrainFeatures.Values.OfType()) + .Where(tree => tree.treeType == type) + .ToArray(); + + if (trees.Any()) { - foreach (Building building in location.buildings) - yield return building; + Lazy texture = new Lazy(() => content.Load(key)); + foreach (Tree tree in trees) +#if STARDEW_VALLEY_1_3 + this.Reflection.GetField>(tree, "texture").SetValue(texture); +#else + this.Reflection.GetField(tree, "texture").SetValue(texture.Value); +#endif + return true; } + + return false; } } } -- cgit From 20b778390051569aa34a9ac42f03cd9b9a051df7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sat, 24 Mar 2018 20:26:33 -0400 Subject: update NPC textures when changed through the content API (#459) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 122 ++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 7 deletions(-) (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 21aaeb6c..4a1d6097 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -323,12 +323,18 @@ namespace StardewModdingAPI.Metadata return this.ReloadTreeTextures(content, key, Tree.pineTree); } - // building textures + // dynamic textures if (key.StartsWith(this.GetNormalisedPath("Buildings\\"), StringComparison.InvariantCultureIgnoreCase)) - { - string type = Path.GetFileName(key); - return this.ReloadBuildings(content, key, type); - } + return this.ReloadBuildings(content, key); + + if (key.StartsWith(this.GetNormalisedPath("Characters\\")) && this.CountSegments(key) == 2) // ignore Characters/Dialogue/*, etc + return this.ReloadNpcSprites(content, key, monster: false); + + if (key.StartsWith(this.GetNormalisedPath("Characters\\Monsters\\"))) + return this.ReloadNpcSprites(content, key, monster: true); + + if (key.StartsWith(this.GetNormalisedPath("Portraits\\"))) + return this.ReloadNpcPortraits(content, key); return false; } @@ -340,16 +346,18 @@ namespace StardewModdingAPI.Metadata /// Reload building textures. /// The content manager through which to reload the asset. /// The asset key to reload. - /// The type to reload. /// Returns whether any textures were reloaded. - private bool ReloadBuildings(LocalizedContentManager content, string key, string type) + private bool ReloadBuildings(LocalizedContentManager content, string key) { + // get buildings + string type = Path.GetFileName(key); Building[] buildings = Game1.locations .OfType() .SelectMany(p => p.buildings) .Where(p => p.buildingType == type) .ToArray(); + // reload buildings if (buildings.Any()) { Lazy texture = new Lazy(() => content.Load(key)); @@ -364,6 +372,61 @@ namespace StardewModdingAPI.Metadata return false; } + /// Reload the sprites for matching NPCs. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Whether to match monsters (true) or non-monsters (false). + /// Returns whether any textures were reloaded. + private bool ReloadNpcSprites(LocalizedContentManager content, string key, bool monster) + { + // get NPCs + string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); + NPC[] characters = + ( + from location in this.GetLocations() + from npc in location.characters + where npc.name == name && npc.IsMonster == monster + select npc + ) + .Distinct() + .ToArray(); + if (!characters.Any()) + return false; + + // update portrait + Texture2D texture = content.Load(key); + foreach (NPC character in characters) + character.Sprite.Texture = texture; + return true; + } + + /// Reload the portraits for matching NPCs. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + private bool ReloadNpcPortraits(LocalizedContentManager content, string key) + { + // get NPCs + string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); + NPC[] villagers = + ( + from location in this.GetLocations() + from npc in location.characters + where npc.name == name && npc.isVillager() + select npc + ) + .Distinct() + .ToArray(); + if (!villagers.Any()) + return false; + + // update portrait + Texture2D texture = content.Load(key); + foreach (NPC villager in villagers) + villager.Portrait = texture; + return true; + } + /// Reload tree textures. /// The content manager through which to reload the asset. /// The asset key to reload. @@ -390,5 +453,50 @@ namespace StardewModdingAPI.Metadata return false; } + + /// Get an NPC name from the name of their file under Content/Characters. + /// The file name. + /// Derived from . + private string GetNpcNameFromFileName(string name) + { + switch (name) + { + case "Mariner": + return "Old Mariner"; + case "DwarfKing": + return "Dwarf King"; + case "MrQi": + return "Mister Qi"; + default: + return name; + } + } + + /// Get all locations in the game. + private IEnumerable GetLocations() + { + foreach (GameLocation location in Game1.locations) + { + yield return location; + + if (location is BuildableGameLocation buildableLocation) + { + foreach (Building building in buildableLocation.buildings) + { + if (building.indoors != null) + yield return building.indoors; + } + } + } + } + + /// Count the number of segments in a path (e.g. 'a/b' is 2). + /// The path to check. + private int CountSegments(string path) + { + if (path == null) + return 0; + return path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length; + } } } -- cgit From 5a0e49827be92d19dfdda7bb15ca15fa8f269ecb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 00:52:37 -0400 Subject: update fence textures when changed through the content API (#459) --- docs/release-notes.md | 3 ++- src/SMAPI/Metadata/CoreAssetPropagator.cs | 44 ++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs') diff --git a/docs/release-notes.md b/docs/release-notes.md index e0e209a1..2a134d13 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,9 +17,10 @@ ## 2.5.4 * For players: - * Fixed NPC and tree textures not updated when changed through the content API. + * Fixed fence, NPC, and tree textures not updated when a mod changes them through the content API. * Fixed visual bug on Linux/Mac when mods overlay images through the content API. * Fixed error when a mod removes an asset editor/loader. + * Fixed minimum game version incorrectly changed from 1.2.30 to 1.2.33 in SMAPI 2.5.3. * For the [log parser][]: * Fixed error when log text contains certain tokens. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 4a1d6097..1702ee26 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -333,6 +333,9 @@ namespace StardewModdingAPI.Metadata if (key.StartsWith(this.GetNormalisedPath("Characters\\Monsters\\"))) return this.ReloadNpcSprites(content, key, monster: true); + if (key.StartsWith(this.GetNormalisedPath("LooseSprites\\Fence"))) + return this.ReloadFenceTextures(content, key); + if (key.StartsWith(this.GetNormalisedPath("Portraits\\"))) return this.ReloadNpcPortraits(content, key); @@ -372,6 +375,34 @@ namespace StardewModdingAPI.Metadata return false; } + /// Reload the sprites for a fence type. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + private bool ReloadFenceTextures(LocalizedContentManager content, string key) + { + // get fence type + if (!int.TryParse(this.GetSegments(key)[1].Substring("Fence".Length), out int fenceType)) + return false; + + // get fences + Fence[] fences = + ( + from location in this.GetLocations() + from fence in location.Objects.Values.OfType() + where fenceType == 1 + ? fence.isGate + : fence.whichType == fenceType + select fence + ) + .ToArray(); + + // update fence textures + foreach (Fence fence in fences) + fence.reloadSprite(); + return true; + } + /// Reload the sprites for matching NPCs. /// The content manager through which to reload the asset. /// The asset key to reload. @@ -490,13 +521,20 @@ namespace StardewModdingAPI.Metadata } } + /// Get the segments in a path (e.g. 'a/b' is 'a' and 'b'). + /// The path to check. + private string[] GetSegments(string path) + { + if (path == null) + return new string[0]; + return path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + } + /// Count the number of segments in a path (e.g. 'a/b' is 2). /// The path to check. private int CountSegments(string path) { - if (path == null) - return 0; - return path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).Length; + return this.GetSegments(path).Length; } } } -- cgit From b1cc6c1d9995db2eccead2a2c99f8f64ddb1da81 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 11:41:56 -0400 Subject: update new asset update logic for Stardew Valley 1.3 (#453) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 1702ee26..277da525 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -195,8 +195,10 @@ namespace StardewModdingAPI.Metadata /**** ** Content\Critters ****/ +#if !STARDEW_VALLEY_1_3 case "tilesheets\\critters": // Criter.InitShared return Critter.critterTexture = content.Load(key); +#endif case "tilesheets\\crops": // Game1.loadContent return Game1.cropSpriteSheet = content.Load(key); @@ -427,7 +429,11 @@ namespace StardewModdingAPI.Metadata // update portrait Texture2D texture = content.Load(key); foreach (NPC character in characters) +#if STARDEW_VALLEY_1_3 + this.Reflection.GetField(character.Sprite, "spriteTexture").SetValue(texture); +#else character.Sprite.Texture = texture; +#endif return true; } -- cgit From 0bcc1f6be95af4a309ff9bb31750f29b4b8338df Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 13:28:38 -0400 Subject: standardise folder checks when reloading assets (#459) --- src/SMAPI/Metadata/CoreAssetPropagator.cs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs') diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 277da525..38b205a5 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -326,19 +326,19 @@ namespace StardewModdingAPI.Metadata } // dynamic textures - if (key.StartsWith(this.GetNormalisedPath("Buildings\\"), StringComparison.InvariantCultureIgnoreCase)) + if (this.IsInFolder(key, "Buildings")) return this.ReloadBuildings(content, key); - if (key.StartsWith(this.GetNormalisedPath("Characters\\")) && this.CountSegments(key) == 2) // ignore Characters/Dialogue/*, etc + if (this.IsInFolder(key, "Characters")) return this.ReloadNpcSprites(content, key, monster: false); - if (key.StartsWith(this.GetNormalisedPath("Characters\\Monsters\\"))) + if (this.IsInFolder(key, "Characters\\Monsters")) return this.ReloadNpcSprites(content, key, monster: true); - if (key.StartsWith(this.GetNormalisedPath("LooseSprites\\Fence"))) + if (key.StartsWith(this.GetNormalisedPath("LooseSprites\\Fence"), StringComparison.InvariantCultureIgnoreCase)) return this.ReloadFenceTextures(content, key); - if (key.StartsWith(this.GetNormalisedPath("Portraits\\"))) + if (this.IsInFolder(key, "Portraits")) return this.ReloadNpcPortraits(content, key); return false; @@ -348,6 +348,9 @@ namespace StardewModdingAPI.Metadata /********* ** Private methods *********/ + /**** + ** Reload methods + ****/ /// Reload building textures. /// The content manager through which to reload the asset. /// The asset key to reload. @@ -491,6 +494,9 @@ namespace StardewModdingAPI.Metadata return false; } + /**** + ** Helpers + ****/ /// Get an NPC name from the name of their file under Content/Characters. /// The file name. /// Derived from . @@ -527,6 +533,17 @@ namespace StardewModdingAPI.Metadata } } + /// Get whether a normalised asset key is in the given folder. + /// The normalised asset key (like Animals/cat). + /// The key folder (like Animals); doesn't need to be normalised. + /// Whether to return true if the key is inside a subfolder of the . + private bool IsInFolder(string key, string folder, bool allowSubfolders = false) + { + return + key.StartsWith(this.GetNormalisedPath($"{folder}\\"), StringComparison.InvariantCultureIgnoreCase) + && (allowSubfolders || this.CountSegments(key) == this.CountSegments(folder) + 1); + } + /// Get the segments in a path (e.g. 'a/b' is 'a' and 'b'). /// The path to check. private string[] GetSegments(string path) -- cgit From 60fc4a64886d92e70475af5bbfeb29b2e3ef26cd Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 25 Mar 2018 14:59:06 -0400 Subject: update animal textures when changed through the content API (#459) --- docs/release-notes.md | 4 +- src/SMAPI/Metadata/CoreAssetPropagator.cs | 125 ++++++++++++++++++++++++------ 2 files changed, 104 insertions(+), 25 deletions(-) (limited to 'src/SMAPI/Metadata/CoreAssetPropagator.cs') diff --git a/docs/release-notes.md b/docs/release-notes.md index 2a134d13..c30d3a3b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -17,8 +17,8 @@ ## 2.5.4 * For players: - * Fixed fence, NPC, and tree textures not updated when a mod changes them through the content API. - * Fixed visual bug on Linux/Mac when mods overlay images through the content API. + * Fixed some textures not updated when a mod changes them (notably animals, fences, NPCs, and trees). + * Fixed visual bug on Linux/Mac when mods overlay textures. * Fixed error when a mod removes an asset editor/loader. * Fixed minimum game version incorrectly changed from 1.2.30 to 1.2.33 in SMAPI 2.5.3. diff --git a/src/SMAPI/Metadata/CoreAssetPropagator.cs b/src/SMAPI/Metadata/CoreAssetPropagator.cs index 38b205a5..e54e0286 100644 --- a/src/SMAPI/Metadata/CoreAssetPropagator.cs +++ b/src/SMAPI/Metadata/CoreAssetPropagator.cs @@ -7,6 +7,7 @@ using StardewModdingAPI.Framework.Reflection; using StardewValley; using StardewValley.BellsAndWhistles; using StardewValley.Buildings; +using StardewValley.Characters; using StardewValley.Locations; using StardewValley.Menus; using StardewValley.Objects; @@ -65,6 +66,16 @@ namespace StardewModdingAPI.Metadata Reflector reflection = this.Reflection; switch (key.ToLower().Replace("/", "\\")) // normalised key so we can compare statically { + /**** + ** Animals + ****/ + case "animals\\cat": + return this.ReloadPetOrHorseSprites(content, key); + case "animals\\dog": + return this.ReloadPetOrHorseSprites(content, key); + case "animals\\horse": + return this.ReloadPetOrHorseSprites(content, key); + /**** ** Buildings ****/ @@ -326,6 +337,9 @@ namespace StardewModdingAPI.Metadata } // dynamic textures + if (this.IsInFolder(key, "Animals")) + return this.ReloadFarmAnimalSprites(content, key); + if (this.IsInFolder(key, "Buildings")) return this.ReloadBuildings(content, key); @@ -351,6 +365,57 @@ namespace StardewModdingAPI.Metadata /**** ** Reload methods ****/ + /// Reload the sprites for matching pets or horses. + /// The animal type. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + private bool ReloadPetOrHorseSprites(LocalizedContentManager content, string key) + where TAnimal : NPC + { + // find matches + TAnimal[] animals = this.GetCharacters().OfType().ToArray(); + if (!animals.Any()) + return false; + + // update sprites + Texture2D texture = content.Load(key); + foreach (TAnimal animal in animals) + this.SetSpriteTexture(animal.sprite, texture); + return true; + } + + /// Reload the sprites for matching farm animals. + /// The content manager through which to reload the asset. + /// The asset key to reload. + /// Returns whether any textures were reloaded. + /// Derived from . + private bool ReloadFarmAnimalSprites(LocalizedContentManager content, string key) + { + // find matches + FarmAnimal[] animals = this.GetFarmAnimals().ToArray(); + if (!animals.Any()) + return false; + + // update sprites + Lazy texture = new Lazy(() => content.Load(key)); + foreach (FarmAnimal animal in animals) + { + // get expected key + string expectedKey = animal.age < animal.ageWhenMature + ? $"Baby{(animal.type == "Duck" ? "White Chicken" : animal.type)}" + : animal.type; + if (animal.showDifferentTextureWhenReadyForHarvest && animal.currentProduce <= 0) + expectedKey = $"Sheared{expectedKey}"; + expectedKey = $"Animals\\{expectedKey}"; + + // reload asset + if (expectedKey == key) + this.SetSpriteTexture(animal.sprite, texture.Value); + } + return texture.IsValueCreated; + } + /// Reload building textures. /// The content manager through which to reload the asset. /// The asset key to reload. @@ -417,26 +482,14 @@ namespace StardewModdingAPI.Metadata { // get NPCs string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); - NPC[] characters = - ( - from location in this.GetLocations() - from npc in location.characters - where npc.name == name && npc.IsMonster == monster - select npc - ) - .Distinct() - .ToArray(); + NPC[] characters = this.GetCharacters().Where(npc => npc.name == name && npc.IsMonster == monster).ToArray(); if (!characters.Any()) return false; // update portrait Texture2D texture = content.Load(key); foreach (NPC character in characters) -#if STARDEW_VALLEY_1_3 - this.Reflection.GetField(character.Sprite, "spriteTexture").SetValue(texture); -#else - character.Sprite.Texture = texture; -#endif + this.SetSpriteTexture(character.Sprite, texture); return true; } @@ -448,15 +501,7 @@ namespace StardewModdingAPI.Metadata { // get NPCs string name = this.GetNpcNameFromFileName(Path.GetFileName(key)); - NPC[] villagers = - ( - from location in this.GetLocations() - from npc in location.characters - where npc.name == name && npc.isVillager() - select npc - ) - .Distinct() - .ToArray(); + NPC[] villagers = this.GetCharacters().Where(npc => npc.name == name && npc.isVillager()).ToArray(); if (!villagers.Any()) return false; @@ -497,6 +542,18 @@ namespace StardewModdingAPI.Metadata /**** ** Helpers ****/ + /// Reload the texture for an animated sprite. + /// The animated sprite to update. + /// The texture to set. + private void SetSpriteTexture(AnimatedSprite sprite, Texture2D texture) + { +#if STARDEW_VALLEY_1_3 + this.Reflection.GetField(sprite, "spriteTexture").SetValue(texture); +#else + sprite.Texture = texture; +#endif + } + /// Get an NPC name from the name of their file under Content/Characters. /// The file name. /// Derived from . @@ -515,6 +572,28 @@ namespace StardewModdingAPI.Metadata } } + /// Get all NPCs in the game (excluding farm animals). + private IEnumerable GetCharacters() + { + return this.GetLocations().SelectMany(p => p.characters); + } + + /// Get all farm animals in the game. + private IEnumerable GetFarmAnimals() + { + foreach (GameLocation location in this.GetLocations()) + { + if (location is Farm farm) + { + foreach (FarmAnimal animal in farm.animals.Values) + yield return animal; + } + else if (location is AnimalHouse animalHouse) + foreach (FarmAnimal animal in animalHouse.animals.Values) + yield return animal; + } + } + /// Get all locations in the game. private IEnumerable GetLocations() { -- cgit