├── assets ├── honey.png ├── jelly.png ├── juice.png ├── wine.png ├── pickles.png ├── driedMushrooms.png └── data.json ├── BetterArtisanGoodIconsConfig.cs ├── .gitignore ├── Content ├── IContentSource.cs ├── CustomTextureData.cs ├── ContentPackSource.cs ├── ModSource.cs ├── TextureDataContentSource.cs └── ContentSourceManager.cs ├── ArtisanGood.cs ├── manifest.json ├── Extensions ├── ListExtensions.cs └── DictionaryExtensions.cs ├── EvenBetterArtisanGoodIcons.csproj ├── README.md ├── EvenBetterArtisanGoodIcons.sln ├── Patches ├── SObjectPatches │ ├── DrawWhenHeldPatch.cs │ ├── DrawInMenuPatch.cs │ └── DrawPatch.cs └── FurniturePatches │ └── DrawPatch.cs ├── Properties └── AssemblyInfo.cs ├── ArtisanGoodsManager.cs ├── BetterArtisanGoodIconsMod.cs └── ArtisanGoodTextureProvider.cs /assets/honey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsiao58/EvenBetterArtisanGoodIcons/HEAD/assets/honey.png -------------------------------------------------------------------------------- /assets/jelly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsiao58/EvenBetterArtisanGoodIcons/HEAD/assets/jelly.png -------------------------------------------------------------------------------- /assets/juice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsiao58/EvenBetterArtisanGoodIcons/HEAD/assets/juice.png -------------------------------------------------------------------------------- /assets/wine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsiao58/EvenBetterArtisanGoodIcons/HEAD/assets/wine.png -------------------------------------------------------------------------------- /assets/pickles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsiao58/EvenBetterArtisanGoodIcons/HEAD/assets/pickles.png -------------------------------------------------------------------------------- /assets/driedMushrooms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsiao58/EvenBetterArtisanGoodIcons/HEAD/assets/driedMushrooms.png -------------------------------------------------------------------------------- /BetterArtisanGoodIconsConfig.cs: -------------------------------------------------------------------------------- 1 | namespace BetterArtisanGoodIcons 2 | { 3 | internal class BetterArtisanGoodIconsConfig 4 | { 5 | public bool DisableSmallSourceIcons { get; set; } = true; 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # user-specific files 2 | *.suo 3 | *.user 4 | *.userosscache 5 | *.sln.docstates 6 | 7 | # build results 8 | [Dd]ebug/ 9 | [Rr]elease/ 10 | [Bb]in/ 11 | [Oo]bj/ 12 | 13 | # Visual Studio cache/options 14 | .vs/ 15 | 16 | # ReSharper 17 | _ReSharper*/ 18 | *.[Rr]e[Ss]harper 19 | *.DotSettings.user 20 | 21 | # NuGet packages 22 | *.nupkg 23 | **/packages/* 24 | *.nuget.props 25 | *.nuget.targets -------------------------------------------------------------------------------- /Content/IContentSource.cs: -------------------------------------------------------------------------------- 1 | using StardewModdingAPI; 2 | 3 | namespace BetterArtisanGoodIcons.Content 4 | { 5 | /// An abstraction over the ability to load textures and get manifest information. Used to unify loading vanilla and custom texture assets. 6 | internal interface IContentSource 7 | { 8 | T Load(string path); 9 | IManifest GetManifest(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ArtisanGood.cs: -------------------------------------------------------------------------------- 1 | namespace BetterArtisanGoodIcons 2 | { 3 | /// The artisan goods that we add unique icons foor. 4 | internal enum ArtisanGood 5 | { 6 | Jelly = 130, 7 | Pickles = 128, 8 | Wine = 123, 9 | Juice = 125, 10 | DriedMushrooms = 66, 11 | Honey = 340 // Honey is still on springObject sheet and has a different index 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "Even Better Artisan Good Icons", 3 | "Author": "Haze1nuts, 58 and Cat", 4 | "Version": "1.6.6", 5 | "Description": "Makes individual artisan goods icons be based on the source ingredient used to make them.", 6 | "UniqueID": "haze1nuts.evenbetterartisangoodicons", 7 | "EntryDll": "EvenBetterArtisanGoodIcons.dll", 8 | "MinimumApiVersion": "4.0.0", 9 | "UpdateKeys": [ "GitHub:chsiao58/EvenBetterArtisanGoodIcons" ] 10 | } 11 | -------------------------------------------------------------------------------- /Extensions/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace BetterArtisanGoodIcons.Extensions 5 | { 6 | static class ListExtensions 7 | { 8 | /// Create lists of tuples more easily. 9 | public static void Add(this List> list, T1 item1, T2 item2, T3 item3) 10 | { 11 | list.Add(new Tuple(item1, item2, item3)); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace BetterArtisanGoodIcons.Extensions 5 | { 6 | public static class DictionaryExtensions 7 | { 8 | /// Create dictionaries with tuples as values more easily. 9 | public static void Add(this Dictionary> dictionary, TKey key, T1 item1, T2 item2) 10 | { 11 | dictionary.Add(key, new Tuple(item1, item2)); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /EvenBetterArtisanGoodIcons.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.6.6 4 | net6.0 5 | true 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Content/CustomTextureData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace BetterArtisanGoodIcons.Content 4 | { 5 | /// Provides texture and source ingredient information. 6 | internal class CustomTextureData 7 | { 8 | public List Fruits { get; set; } = null; 9 | public List Vegetables { get; set; } = null; 10 | public List Flowers { get; set; } = null; 11 | public List Mushrooms { get; set; } = null; 12 | 13 | 14 | public string Jelly { get; set; } = null; 15 | public string Pickles { get; set; } = null; 16 | public string Wine { get; set; } = null; 17 | public string Juice { get; set; } = null; 18 | public string Honey { get; set; } = null; 19 | public string DriedMushrooms { get; set; } = null; 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Content/ContentPackSource.cs: -------------------------------------------------------------------------------- 1 | using StardewModdingAPI; 2 | 3 | namespace BetterArtisanGoodIcons.Content 4 | { 5 | /// 6 | /// A content source that comes from Content Packs. 7 | internal class ContentPackSource : TextureDataContentSource 8 | { 9 | private readonly IContentPack pack; 10 | public override CustomTextureData TextureData { get; } 11 | 12 | public ContentPackSource(IContentPack pack) 13 | { 14 | this.pack = pack; 15 | TextureData = pack.ReadJsonFile("data.json"); 16 | } 17 | 18 | public override T Load(string path) 19 | { 20 | return pack.ModContent.Load(path); 21 | } 22 | 23 | public override IManifest GetManifest() 24 | { 25 | return pack.Manifest; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Content/ModSource.cs: -------------------------------------------------------------------------------- 1 | using StardewModdingAPI; 2 | 3 | namespace BetterArtisanGoodIcons.Content 4 | { 5 | /// 6 | /// A content source that comes from a mod. 7 | internal class ModSource : TextureDataContentSource 8 | { 9 | private readonly IModHelper helper; 10 | 11 | public override CustomTextureData TextureData { get; } 12 | 13 | public ModSource(IModHelper helper) 14 | { 15 | this.helper = helper; 16 | //1.3.31 17 | TextureData = helper.Data.ReadJsonFile("assets/data.json"); 18 | } 19 | 20 | public override T Load(string path) 21 | { 22 | return helper.ModContent.Load(path); 23 | } 24 | 25 | public override IManifest GetManifest() 26 | { 27 | return helper.ModRegistry.Get(helper.ModRegistry.ModID).Manifest; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /assets/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "Fruits": [ "Ancient Fruit", "Apple", "Apricot", "Blackberry", "Blueberry", "Cactus Fruit", "Cherry", "Coconut", "Cranberries", "Crystal Fruit", "Grape", "Hot Pepper", "Melon", "Orange", "Peach", "Pomegranate", "Rhubarb", "Salmonberry", "Spice Berry", "Starfruit", "Strawberry", "Wild Plum", "Banana", "Pineapple", "Mango", "Qi Fruit", "Powdermelon" ], 3 | "Vegetables": [ "Amaranth", "Artichoke", "Beet", "Bok Choy", "Cauliflower", "Corn", "Eggplant", "Fiddlehead Fern", "Garlic", "Green Bean", "Hops", "Kale", "Parsnip", "Potato", "Pumpkin", "Radish", "Red Cabbage", "Tomato", "Wheat", "Yam", "Ginger", "Taro Root", "Tea Leaves", "Unmilled Rice", "Carrot", "Summer Squash", "Broccoli" ], 4 | "Flowers": [ "Blue Jazz", "Fairy Rose", "Poppy", "Summer Spangle", "Tulip", "Wild" ], 5 | "Mushrooms": [ "Common Mushroom", "Morel", "Chanterelle", "Purple Mushroom", "Magma Cap" ], 6 | "Honey": "assets/honey.png", 7 | "Jelly": "assets/jelly.png", 8 | "Juice": "assets/juice.png", 9 | "Pickles": "assets/pickles.png", 10 | "Wine": "assets/wine.png", 11 | "DriedMushrooms": "assets/driedMushrooms.png" 12 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Even Better Artisan Good Icons 2 | 3 | An updated mod fix to Better Artisan Good Icons (originally created by danvolchek). Link to download can be found [here](https://github.com/chsiao58/EvenBetterArtisanGoodIcons/releases). 4 | 5 | 6 | ## How it works 7 | 8 | The mod overwrites the corresponding draw functions for artisan goods using [Harmony](https://github.com/pardeike/Harmony), and then draws the right texture for them based on what was used to make them. 9 | 10 | Note that this repo does not contain the Harmony dll. 11 | 12 | ## For user 13 | 14 | For the ease of maintaining this mod, the unique id of this mod was updated to **haze1nuts.evenbetterartisangoodicons**. 15 | 16 | To use other content pack that is created for original BAGI along with this base mod, please replace the contant pack dependency from **cat.betterartisangoodicons** to **haze1nuts.evenbetterartisangoodicons**, in the content pack's manifest.json. 17 | 18 | ## Future Update 19 | 20 | * Combability fix for mods that add new base ingredients. 21 | * Wild Honey and Sunflower Honey fix 22 | * Adding Dried Fruits 23 | * Generic Mod Config Menu support 24 | * Mod optimization 25 | * Code support for Aged Roe 26 | 27 | -------------------------------------------------------------------------------- /EvenBetterArtisanGoodIcons.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34525.116 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EvenBetterArtisanGoodIcons", "EvenBetterArtisanGoodIcons.csproj", "{EE91AA88-5CB3-4367-93D8-DF02A2B3DEE8}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {EE91AA88-5CB3-4367-93D8-DF02A2B3DEE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {EE91AA88-5CB3-4367-93D8-DF02A2B3DEE8}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {EE91AA88-5CB3-4367-93D8-DF02A2B3DEE8}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {EE91AA88-5CB3-4367-93D8-DF02A2B3DEE8}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {9E713130-E1E2-41C2-9D6A-432A8DA8FA05} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Patches/SObjectPatches/DrawWhenHeldPatch.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using StardewValley; 4 | using System; 5 | using SObject = StardewValley.Object; 6 | 7 | namespace BetterArtisanGoodIcons.Patches.SObjectPatches 8 | { 9 | internal class DrawWhenHeldPatch 10 | { 11 | /// Draw the correct texture based on or . 12 | public static bool Prefix(SObject __instance, SpriteBatch spriteBatch, Vector2 objectPosition, Farmer f) 13 | { 14 | if (!ArtisanGoodsManager.GetDrawInfo(__instance, out Texture2D spriteSheet, out Rectangle position, out Rectangle iconPosition)) 15 | return true; 16 | 17 | //By popular demand, don't show icons when held. 18 | //if (iconPosition != Rectangle.Empty) 19 | // spriteBatch.Draw(Game1.objectSpriteSheet, objectPosition + new Vector2(1, 1), new Microsoft.Xna.Framework.Rectangle?(iconPosition), Color.White, 0.0f, new Vector2(4f, 4f), Game1.pixelZoom / 2, SpriteEffects.None, Math.Max(0.0f, (f.getStandingY() + 2) / 10000f)); 20 | 21 | spriteBatch.Draw(spriteSheet, objectPosition, position, Color.White, 0.0f, Vector2.Zero, Game1.pixelZoom, SpriteEffects.None, Math.Max(0.0f, (f.StandingPixel.Y + 2) / 10000f)); 22 | return false; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("BetterArtisanGoodIcons")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("BetterArtisanGoodIcons")] 12 | [assembly: AssemblyCopyright("Copyright © 2018")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("f4ac84cc-f578-4553-8de1-ec9f851d3700")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Content/TextureDataContentSource.cs: -------------------------------------------------------------------------------- 1 | using StardewModdingAPI; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace BetterArtisanGoodIcons.Content 6 | { 7 | /// 8 | /// A basic content source provider - is able to group data from each CustomTextureData together properly. 9 | internal abstract class TextureDataContentSource : IContentSource 10 | { 11 | public abstract CustomTextureData TextureData { get; } 12 | public abstract T Load(string path); 13 | public abstract IManifest GetManifest(); 14 | 15 | /// Group each texture path with its corresponding source list and artisan good type. 16 | public IEnumerable, ArtisanGood>> GetData() 17 | { 18 | yield return new Tuple, ArtisanGood>(TextureData.Honey, 19 | TextureData.Flowers, ArtisanGood.Honey); 20 | 21 | yield return new Tuple, ArtisanGood>(TextureData.Juice, 22 | TextureData.Vegetables, ArtisanGood.Juice); 23 | 24 | yield return new Tuple, ArtisanGood>(TextureData.Pickles, 25 | TextureData.Vegetables, ArtisanGood.Pickles); 26 | 27 | yield return new Tuple, ArtisanGood>(TextureData.Wine, 28 | TextureData.Fruits, ArtisanGood.Wine); 29 | 30 | yield return new Tuple, ArtisanGood>(TextureData.Jelly, 31 | TextureData.Fruits, ArtisanGood.Jelly); 32 | 33 | yield return new Tuple, ArtisanGood>(TextureData.DriedMushrooms, 34 | TextureData.Mushrooms, ArtisanGood.DriedMushrooms); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ArtisanGoodsManager.cs: -------------------------------------------------------------------------------- 1 | using BetterArtisanGoodIcons.Content; 2 | using Microsoft.Xna.Framework; 3 | using Microsoft.Xna.Framework.Graphics; 4 | using StardewModdingAPI; 5 | using StardewValley; 6 | using StardewValley.ItemTypeDefinitions; 7 | using System.Collections.Generic; 8 | using SObject = StardewValley.Object; 9 | 10 | namespace BetterArtisanGoodIcons 11 | { 12 | /// Manages textures for all artisan goods. 13 | internal static class ArtisanGoodsManager 14 | { 15 | public static IMonitor Monitor; 16 | /// Texture managers that get the correct texture for each item. 17 | private static readonly IList TextureProviders = new List(); 18 | 19 | /// Mod config. 20 | private static BetterArtisanGoodIconsConfig config; 21 | 22 | /// Initializes the manager. 23 | internal static void Init(IModHelper helper, IMonitor monitor) 24 | { 25 | Monitor = monitor; 26 | config = helper.ReadConfig(); 27 | 28 | foreach (ArtisanGoodTextureProvider provider in ContentSourceManager.GetTextureProviders(helper, monitor)) 29 | TextureProviders.Add(provider); 30 | } 31 | 32 | /// Gets the info needed to draw the correct texture. 33 | internal static bool GetDrawInfo(SObject output, out Texture2D textureSheet, out Rectangle mainPosition, out Rectangle iconPosition) 34 | { 35 | textureSheet = Game1.objectSpriteSheet; 36 | mainPosition = Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, output.ParentSheetIndex, 16, 16); 37 | iconPosition = Rectangle.Empty; 38 | 39 | foreach (ArtisanGoodTextureProvider manager in TextureProviders) 40 | { 41 | if (manager.GetDrawInfo(output, ref textureSheet, ref mainPosition, ref iconPosition)) 42 | { 43 | if (config.DisableSmallSourceIcons) 44 | iconPosition = Rectangle.Empty; 45 | return true; 46 | } 47 | } 48 | 49 | return false; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /BetterArtisanGoodIconsMod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using BetterArtisanGoodIcons.Extensions; 6 | using HarmonyLib; 7 | using StardewModdingAPI; 8 | using StardewValley.Objects; 9 | 10 | namespace BetterArtisanGoodIcons 11 | { 12 | /// Draws different icons for different Artisan Good types. 13 | public class BetterArtisanGoodIconsMod : Mod 14 | { 15 | /// The mod entry point, called after the mod is first loaded. 16 | /// Provides simplified APIs for writing mods. 17 | public override void Entry(IModHelper helper) 18 | { 19 | ArtisanGoodsManager.Init(this.Helper, this.Monitor); 20 | 21 | Harmony harmony = new Harmony("haze1nuts.evenbetterartisangoodicons"); 22 | 23 | //Don't need to override draw for Object because artisan goods can't be placed down. 24 | Type objectType = typeof(ColoredObject); 25 | IList> replacements = new List> 26 | { 27 | {"drawWhenHeld", objectType, typeof(Patches.SObjectPatches.DrawWhenHeldPatch)}, 28 | {"drawInMenu", objectType, typeof(Patches.SObjectPatches.DrawInMenuPatch)}, 29 | {"draw", objectType, typeof(Patches.SObjectPatches.DrawPatch)}, 30 | {"drawWhenHeld", typeof(StardewValley.Object), typeof(Patches.SObjectPatches.DrawWhenHeldPatch)}, // 31 | {"drawInMenu", typeof(StardewValley.Object), typeof(Patches.SObjectPatches.DrawInMenuPatch)}, // for honey, since honey is not a ColoredObject 32 | {"draw", typeof(StardewValley.Object), typeof(Patches.SObjectPatches.DrawPatch)}, // 33 | {"draw", typeof(Furniture), typeof(Patches.FurniturePatches.DrawPatch)} 34 | }; 35 | 36 | foreach (Tuple replacement in replacements) 37 | { 38 | MethodInfo original = replacement.Item2.GetMethods(BindingFlags.Instance | BindingFlags.Public).ToList().Find(m => m.Name == replacement.Item1); 39 | 40 | MethodInfo prefix = replacement.Item3.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(item => item.Name == "Prefix"); 41 | MethodInfo postfix = replacement.Item3.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(item => item.Name == "Postfix"); 42 | 43 | harmony.Patch(original, prefix == null ? null : new HarmonyMethod(prefix), postfix == null ? null : new HarmonyMethod(postfix)); 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Patches/FurniturePatches/DrawPatch.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using StardewValley; 4 | using StardewValley.Objects; 5 | 6 | namespace BetterArtisanGoodIcons.Patches.FurniturePatches 7 | { 8 | /// Draw the right texture for the object placed on furniture. 9 | internal class DrawPatch 10 | { 11 | public static bool Prefix(Furniture __instance, SpriteBatch spriteBatch, int x, int y, float alpha = 1f) 12 | { 13 | if (__instance.heldObject.Value == null || __instance.heldObject.Value is Furniture || 14 | !ArtisanGoodsManager.GetDrawInfo(__instance.heldObject.Value, out Texture2D spriteSheet, out Rectangle position, out Rectangle iconPosition)) 15 | return true; 16 | if (x == -1) 17 | { 18 | Vector2 drawPos = new Vector2(__instance.boundingBox.X, __instance.boundingBox.Y - (__instance.sourceRect.Height * Game1.pixelZoom - __instance.boundingBox.Value.Height)); 19 | spriteBatch.Draw(Game1.content.Load(Furniture.furnitureTextureName), Game1.GlobalToLocal(Game1.viewport, drawPos), new Rectangle?(__instance.sourceRect.Value), Color.White * alpha, 0.0f, Vector2.Zero, Game1.pixelZoom, __instance.Flipped ? SpriteEffects.FlipHorizontally : SpriteEffects.None, __instance.furniture_type.Value == 12 ? 0.0f : (__instance.boundingBox.Value.Bottom - 8) / 10000f); 20 | } 21 | else 22 | spriteBatch.Draw(Game1.content.Load(Furniture.furnitureTextureName), Game1.GlobalToLocal(Game1.viewport, new Vector2(x * Game1.tileSize, y * Game1.tileSize - (__instance.sourceRect.Value.Height * Game1.pixelZoom - __instance.boundingBox.Value.Height))), new Rectangle?(__instance.sourceRect.Value), Color.White * alpha, 0.0f, Vector2.Zero, Game1.pixelZoom, __instance.Flipped ? SpriteEffects.FlipHorizontally : SpriteEffects.None, __instance.furniture_type.Value == 12 ? 0.0f : (__instance.boundingBox.Value.Bottom - 8) / 10000f); 23 | 24 | spriteBatch.Draw(Game1.shadowTexture, Game1.GlobalToLocal(Game1.viewport, new Vector2(__instance.boundingBox.Value.Center.X - Game1.tileSize / 2, __instance.boundingBox.Value.Center.Y - (__instance.drawHeldObjectLow.Value ? Game1.tileSize / 2 : Game1.tileSize * 4 / 3))) + new Vector2(Game1.tileSize / 2f, Game1.tileSize * 5f / 6), new Rectangle?(Game1.shadowTexture.Bounds), Color.White * alpha, 0.0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 4f, SpriteEffects.None, __instance.boundingBox.Value.Bottom / 10000f); 25 | spriteBatch.Draw(spriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2(__instance.boundingBox.Value.Center.X - Game1.tileSize / 2, __instance.boundingBox.Value.Center.Y - (__instance.drawHeldObjectLow.Value ? Game1.tileSize / 2 : Game1.tileSize * 4 / 3))), new Rectangle?(position), Color.White * alpha, 0.0f, Vector2.Zero, Game1.pixelZoom, SpriteEffects.None, (__instance.boundingBox.Value.Bottom + 1) / 10000f); 26 | return false; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Patches/SObjectPatches/DrawInMenuPatch.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using StardewValley; 4 | using StardewValley.ItemTypeDefinitions; 5 | using System; 6 | using SObject = StardewValley.Object; 7 | 8 | namespace BetterArtisanGoodIcons.Patches.SObjectPatches 9 | { 10 | internal class DrawInMenuPatch 11 | { 12 | /// Draw the correct texture based on or . 13 | public static bool Prefix(SObject __instance, SpriteBatch spriteBatch, Vector2 location, float scaleSize, float transparency, float layerDepth, bool drawStackNumber) 14 | { 15 | if (!ArtisanGoodsManager.GetDrawInfo(__instance, out Texture2D spriteSheet, out Rectangle position, out Rectangle iconPosition)) 16 | return true; 17 | 18 | spriteBatch.Draw(Game1.shadowTexture, location + new Vector2(Game1.tileSize / 2, Game1.tileSize * 3 / 4), new Microsoft.Xna.Framework.Rectangle?(Game1.shadowTexture.Bounds), Color.White * 0.5f, 0.0f, new Vector2(Game1.shadowTexture.Bounds.Center.X, Game1.shadowTexture.Bounds.Center.Y), 3f, SpriteEffects.None, layerDepth - 0.0001f); 19 | 20 | spriteBatch.Draw(spriteSheet, location + new Vector2((int)(Game1.tileSize / 2 * (double)scaleSize), (int)(Game1.tileSize / 2 * (double)scaleSize)), new Microsoft.Xna.Framework.Rectangle?(position), Color.White * transparency, 0.0f, new Vector2(8f, 8f) * scaleSize, Game1.pixelZoom * scaleSize, SpriteEffects.None, layerDepth); 21 | 22 | //By popular demand, don't show icons near the mouse cursor, which are drawn with lowered transparency. 23 | if (transparency >= 1 && iconPosition != Rectangle.Empty) { 24 | ParsedItemData source = ItemRegistry.GetDataOrErrorItem("(O)" + __instance.preservedParentSheetIndex.Value); 25 | 26 | spriteBatch.Draw(source.GetTexture(), location + new Vector2(Game1.tileSize / 6 * scaleSize, Game1.tileSize / 6 * scaleSize), new Microsoft.Xna.Framework.Rectangle?(iconPosition), Color.White * transparency, 0.0f, new Vector2(4f, 4f), (3f / 2f) * scaleSize, SpriteEffects.None, layerDepth); 27 | } 28 | if (drawStackNumber && __instance.maximumStackSize() > 1 && (scaleSize > 0.3 && __instance.Stack != int.MaxValue) && __instance.Stack > 1) 29 | Utility.drawTinyDigits(__instance.Stack, spriteBatch, location + new Vector2(Game1.tileSize - Utility.getWidthOfTinyDigitString(__instance.Stack, 3f * scaleSize) + 3f * scaleSize, (float)(Game1.tileSize - 18.0 * scaleSize + 2.0)), 3f * scaleSize, 1f, Color.White); 30 | if (drawStackNumber && __instance.Quality > 0) 31 | { 32 | float num = __instance.Quality < 4 ? 0.0f : (float)((Math.Cos(Game1.currentGameTime.TotalGameTime.Milliseconds * Math.PI / 512.0) + 1.0) * 0.0500000007450581); 33 | spriteBatch.Draw(Game1.mouseCursors, location + new Vector2(12f, Game1.tileSize - 12 + num), new Microsoft.Xna.Framework.Rectangle?(__instance.Quality < 4 ? new Rectangle(338 + (__instance.Quality - 1) * 8, 400, 8, 8) : new Rectangle(346, 392, 8, 8)), Color.White * transparency, 0.0f, new Vector2(4f, 4f), (float)(3.0 * scaleSize * (1.0 + num)), SpriteEffects.None, layerDepth); 34 | } 35 | if (__instance.Category == -22 && __instance.scale.Y < 1.0) 36 | spriteBatch.Draw(Game1.staminaRect, new Rectangle((int)location.X, (int)(location.Y + (Game1.tileSize - 2 * Game1.pixelZoom) * (double)scaleSize), (int)(Game1.tileSize * (double)scaleSize * __instance.scale.Y), (int)((2 * Game1.pixelZoom) * (double)scaleSize)), Utility.getRedToGreenLerpColor(__instance.scale.Y)); 37 | 38 | return false; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Content/ContentSourceManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Xna.Framework.Graphics; 5 | using StardewModdingAPI; 6 | 7 | namespace BetterArtisanGoodIcons.Content 8 | { 9 | /// Handles alternate textures from content packs. 10 | internal class ContentSourceManager 11 | { 12 | /// Map an to its source name to make content pack debugging easier. 13 | private static readonly IDictionary artisanGoodToSourceType = new Dictionary 14 | { 15 | {ArtisanGood.Honey, "Flowers" }, 16 | {ArtisanGood.Jelly, "Fruits" }, 17 | {ArtisanGood.Wine, "Fruits" }, 18 | {ArtisanGood.Juice, "Vegetables" }, 19 | {ArtisanGood.Pickles, "Vegetables" }, 20 | {ArtisanGood.DriedMushrooms, "Mushrooms" } 21 | }; 22 | 23 | /// Gets all valid s that can be used to get artisan good icons. 24 | internal static IEnumerable GetTextureProviders(IModHelper helper, IMonitor monitor) 25 | { 26 | //Load content packs first so vanilla icons can be overwritten 27 | foreach (IContentPack pack in helper.ContentPacks.GetOwned()) 28 | { 29 | foreach (ArtisanGoodTextureProvider provider in TryLoadContentSource(new ContentPackSource(pack), monitor)) 30 | yield return provider; 31 | } 32 | 33 | //Load vanilla icons 34 | foreach (ArtisanGoodTextureProvider provider in TryLoadContentSource(new ModSource(helper), monitor)) 35 | yield return provider; 36 | } 37 | 38 | /// Tries to load textures for all artisan good types for a given . 39 | private static IEnumerable TryLoadContentSource(TextureDataContentSource contentSource, IMonitor monitor) 40 | { 41 | foreach (Tuple, ArtisanGood> item in contentSource.GetData()) 42 | { 43 | if (TryLoadTextureProvider(contentSource, item.Item1, item.Item2, item.Item3, monitor, 44 | out ArtisanGoodTextureProvider provider)) 45 | yield return provider; 46 | } 47 | } 48 | 49 | /// Tries to load a texture given the , the path to the texture, the list of source names for it, and the good type. 50 | private static bool TryLoadTextureProvider(IContentSource contentSource, string imagePath, List source, ArtisanGood good, IMonitor monitor, out ArtisanGoodTextureProvider provider) 51 | { 52 | provider = null; 53 | 54 | if (imagePath == null) 55 | return false; 56 | 57 | IManifest manifest = contentSource.GetManifest(); 58 | if (source == null || source.Count == 0 || source.Any(item => item == null)) 59 | { 60 | monitor.Log($"Couldn't load {good} from {manifest.Name} ({manifest.UniqueID}) because it has an invalid source list ({artisanGoodToSourceType[good]}).", LogLevel.Warn); 61 | monitor.Log($"{artisanGoodToSourceType[good]} must not be null, must not be empty, and cannot have null items inside it.", LogLevel.Warn); 62 | } 63 | else 64 | { 65 | try 66 | { 67 | provider = new ArtisanGoodTextureProvider(contentSource.Load(imagePath), source, good); 68 | return true; 69 | } 70 | catch (Exception) 71 | { 72 | monitor.Log($"Couldn't load {good} from {manifest.Name} ({manifest.UniqueID}) because the {good} texture file path is invalid ({imagePath}).", LogLevel.Warn); 73 | } 74 | } 75 | 76 | return false; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /ArtisanGoodTextureProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using StardewValley; 4 | using StardewValley.GameData.Objects; 5 | using StardewValley.ItemTypeDefinitions; 6 | using System.Collections.Generic; 7 | using SObject = StardewValley.Object; 8 | 9 | namespace BetterArtisanGoodIcons 10 | { 11 | /// Provides textures for certain artisan goods. 12 | internal class ArtisanGoodTextureProvider 13 | { 14 | /// The spritesheet to pull textures from. 15 | private readonly Texture2D spriteSheet; 16 | 17 | /// The rectangles that correspond to each item name. 18 | private readonly IDictionary positions = new Dictionary(); 19 | 20 | /// The type of artisan good this provides texture for. 21 | private readonly ArtisanGood good; 22 | 23 | internal ArtisanGoodTextureProvider(Texture2D texture, List names, ArtisanGood good) 24 | { 25 | this.spriteSheet = texture; 26 | this.good = good; 27 | 28 | //Get sprite positions assuming names go left to right 29 | int x = 0; 30 | int y = 0; 31 | foreach (string item in names) 32 | { 33 | this.positions[item] = new Rectangle(x, y, 16, 16); 34 | x += 16; 35 | if (x >= texture.Width) 36 | { 37 | x = 0; 38 | y += 16; 39 | } 40 | } 41 | 42 | } 43 | 44 | /// Gets the name of the source item used to create the given item. 45 | private bool GetSourceName(SObject item, string sourceIndex, out string sourceName) 46 | { 47 | //If the item name is equivalent to the base good, return _Base. 48 | if (item.Name == this.good.ToString()) 49 | { 50 | sourceName = "_Base"; 51 | return true; 52 | } 53 | 54 | //Lookup the name from the game's object information, or null if not found (a custom item that has its sourceIndex set incorrectly). 55 | if (Game1.objectData.TryGetValue(sourceIndex, out ObjectData information)) 56 | { 57 | sourceName = information.Name; 58 | return true; 59 | } 60 | 61 | sourceName = null; 62 | return false; 63 | } 64 | 65 | /// Gets the index of the source item used to create the given item name. 66 | private bool GetIndexOfSource(SObject item, out string index) 67 | { 68 | //Use preservedParentSheetIndex for wine, jelly, pickles, and juice 69 | if (item.preservedParentSheetIndex.Value != null) 70 | { 71 | index = item.preservedParentSheetIndex.Value; 72 | return true; 73 | } 74 | 75 | index = null; 76 | return false; 77 | } 78 | 79 | /// Gets the info needed to draw the right texture for the given item. 80 | internal bool GetDrawInfo(SObject item, ref Texture2D textureSheet, ref Rectangle mainPosition, ref Rectangle iconPosition) 81 | { 82 | //TODO: This actually disallows changing the base texture b/c it won't get past the second if statement, <-- TODO: check if this is still relevent 83 | //TODO: also the != -1 check will also be false. <-- TODO: check if this is still relevent 84 | 85 | //Only yield new textures for base items. If removed, everything *should* still work, but it needs more testing. 86 | if (item.ParentSheetIndex != (int)this.good) 87 | return false; 88 | 89 | //If the index of the source item can't be found, exit. 90 | if (!this.GetIndexOfSource(item, out string sourceIndex)) 91 | return false; 92 | 93 | ParsedItemData source = ItemRegistry.GetDataOrErrorItem("(O)" + item.preservedParentSheetIndex.Value); 94 | //Get the name of the item from its index, and from that, a new sprite. 95 | string sourceName = source.InternalName; 96 | if (!this.positions.TryGetValue(sourceName, out mainPosition)) 97 | return false; 98 | 99 | textureSheet = this.spriteSheet; 100 | iconPosition = sourceIndex != null ? source.GetSourceRect() : Rectangle.Empty; 101 | return true; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /Patches/SObjectPatches/DrawPatch.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using StardewValley; 4 | using StardewValley.ItemTypeDefinitions; 5 | using System; 6 | using SObject = StardewValley.Object; 7 | 8 | namespace BetterArtisanGoodIcons.Patches.SObjectPatches 9 | { 10 | class DrawPatch 11 | { 12 | /// Draw the correct texture for machines that are ready for harvest and displaying their results. 13 | /// We can't just set to be false during the draw call and draw the 14 | /// tool tip ourselves because draw calls getScale, which actually modifies the object scale based upon . 15 | public static bool Prefix(SObject __instance, SpriteBatch spriteBatch, int x, int y, float alpha = 1f) 16 | { 17 | if (!__instance.readyForHarvest.Value || !__instance.bigCraftable.Value || __instance.heldObject.Value == null || 18 | !ArtisanGoodsManager.GetDrawInfo(__instance.heldObject.Value, out Texture2D spriteSheet, out Rectangle position, out Rectangle iconPosition)) 19 | return true; 20 | Vector2 vector2 = __instance.getScale() * Game1.pixelZoom; 21 | Vector2 local = Game1.GlobalToLocal(Game1.viewport, new Vector2(x * Game1.tileSize, y * Game1.tileSize - Game1.tileSize)); 22 | Rectangle destinationRectangle = new((int)(local.X - vector2.X / 2.0) + (__instance.shakeTimer > 0 ? Game1.random.Next(-1, 2) : 0), (int)(local.Y - vector2.Y / 2.0) + (__instance.shakeTimer > 0 ? Game1.random.Next(-1, 2) : 0), (int)(Game1.tileSize + vector2.X), (int)((Game1.tileSize * 2) + vector2.Y / 2.0)); 23 | ParsedItemData source = ItemRegistry.GetDataOrErrorItem(__instance.QualifiedItemId); 24 | spriteBatch.Draw(source.GetTexture(), destinationRectangle, source.GetSourceRect(), Color.White * alpha, 0.0f, Vector2.Zero, SpriteEffects.None, (float)(Math.Max(0.0f, ((y + 1) * Game1.tileSize - Game1.pixelZoom * 6) / 10000f) + (__instance.ParentSheetIndex == 105 ? 0.00350000010803342 : 0.0) + x * 9.99999974737875E-06)); 25 | if (__instance.Name.Equals("Loom") && __instance.MinutesUntilReady > 0) 26 | spriteBatch.Draw(Game1.objectSpriteSheet, __instance.getLocalPosition(Game1.viewport) + new Vector2((Game1.tileSize / 2f), 0.0f), new Microsoft.Xna.Framework.Rectangle?(Game1.getSourceRectForStandardTileSheet(Game1.objectSpriteSheet, 435, 16, 16)), Color.White * alpha, __instance.scale.X, new Vector2(8f, 8f), Game1.pixelZoom, SpriteEffects.None, Math.Max(0.0f, (float)(((y + 1) * Game1.tileSize) / 10000.0 + 9.99999974737875E-05 + x * 9.99999974737875E-06))); 27 | if (__instance.isLamp.Value && Game1.isDarkOut(Game1.currentLocation)) 28 | spriteBatch.Draw(Game1.mouseCursors, local + new Vector2((-Game1.tileSize / 2f), (-Game1.tileSize / 2f)), new Microsoft.Xna.Framework.Rectangle?(new Rectangle(88, 1779, 32, 32)), Color.White * 0.75f, 0.0f, Vector2.Zero, Game1.pixelZoom, SpriteEffects.None, Math.Max(0.0f, ((y + 1) * Game1.tileSize - Game1.pixelZoom * 5) / 10000f)); 29 | if (__instance.ParentSheetIndex == 126 && __instance.Quality != 0) 30 | spriteBatch.Draw(FarmerRenderer.hatsTexture, local + new Vector2(-3f, -6f) * Game1.pixelZoom, new Microsoft.Xna.Framework.Rectangle?(new Rectangle((__instance.Quality - 1) * 20 % FarmerRenderer.hatsTexture.Width, (__instance.Quality - 1) * 20 / FarmerRenderer.hatsTexture.Width * 20 * 4, 20, 20)), Color.White * alpha, 0.0f, Vector2.Zero, Game1.pixelZoom, SpriteEffects.None, Math.Max(0.0f, ((y + 1) * Game1.tileSize - Game1.pixelZoom * 5) / 10000f) + x * 1E-05f); 31 | 32 | float num = (float)(4.0 * Math.Round(Math.Sin(DateTime.Now.TimeOfDay.TotalMilliseconds / 250.0), 2)); 33 | spriteBatch.Draw(Game1.mouseCursors, Game1.GlobalToLocal(Game1.viewport, new Vector2((x * Game1.tileSize - 8), (y * Game1.tileSize - Game1.tileSize * 3 / 2 - 16) + num)), new Microsoft.Xna.Framework.Rectangle?(new Microsoft.Xna.Framework.Rectangle(141, 465, 20, 24)), Color.White * 0.75f, 0.0f, Vector2.Zero, 4f, SpriteEffects.None, (float)(((y + 1) * Game1.tileSize) / 10000.0 + 9.99999997475243E-07 + __instance.TileLocation.X / 10000.0 + (__instance.ParentSheetIndex == 105 ? 0.00150000001303852 : 0.0))); 34 | spriteBatch.Draw(spriteSheet, Game1.GlobalToLocal(Game1.viewport, new Vector2((x * Game1.tileSize + Game1.tileSize / 2), (y * Game1.tileSize - Game1.tileSize - Game1.tileSize / 8) + num)), new Microsoft.Xna.Framework.Rectangle?(position), Color.White * 0.75f, 0.0f, new Vector2(8f, 8f), Game1.pixelZoom, SpriteEffects.None, (float)(((y + 1) * Game1.tileSize) / 10000.0 + 9.99999974737875E-06 + __instance.TileLocation.X / 10000.0 + (__instance.ParentSheetIndex == 105 ? 0.00150000001303852 : 0.0))); 35 | return false; 36 | } 37 | 38 | } 39 | } 40 | --------------------------------------------------------------------------------