// SalvageItems.cs "$Revision: 2502 $" "$Date: 2021-07-10 15:15:00 +1000 (la, 17 elo 2019) $" // https://www.ownedcore.com/forums/diablo-3/turbohud/turbohud-community-plugins/792083-intl-jarjar-salvageitems.html // https://pastebin.com/E9RhzxhT // https://www.icy-veins.com/d3/legendary-item-salvage-guide using System.Collections.Generic; using System.Linq; using SharpDX; using SharpDX.Direct2D1; using Turbo.Plugins.Default; namespace Turbo.Plugins.JarJar.DefaultUI { public enum BuildKind : uint { Class = 0, // => ClassBrush Other = 1, // => OtherBrush Special = 2, // => SpecialBrush None = 3, // => DarkenBrush } public class SalvageItems : BasePlugin, IInGameTopPainter { public bool SkipLoN { get; set; } = false; // Ignore LoN builds. public bool HighlightInventory { get; set; } = true; // Draw a coloured rectable around item type in bottom-right item rect corner. public IBrush ClassBrush { get; set; } // Build item is for current class. public IBrush OtherBrush { get; set; } // Build item is for other class. public IBrush SpecialBrush { get; set; } // Build item without class, like folower items and extra special items. public float OffsetTop { get; set; } = 18f; // Hard coded for 1200 screen height. public float OffsetBottom { get; set; } = 2f; public bool HighlightLegendary { get; set; } = true; // Extra highlight for legendary items public bool HighlightAncient { get; set; } = false; // Extra highlight for ancient and primary items public IBrush HighlightBrush { get; set; } public float HighlightOffset { get; set; } = 1.5f; public bool CheckItems { get; set; } = true; // Mark items for easier detection what to salvage. public bool SkipJewelryCheck { get; set; } = true; public string CheckItemIndicator1 { get; set; } = "✪"; // ✪ circled white star (more than one item with same main stat) public string CheckItemIndicator2 { get; set; } = "✪"; // ✪ circled white star (more than one item with same main stat and only in Inventory) public string SingleLegendary { get; set; } = "◇"; // ◇ white diamond (really the only item found) public string SingleAncient { get; set; } = "⦿"; // ⦿ circled bullet (really the only item found) public string SkippedJewelryIndicator { get; set; } = "✤"; // ✤ Jewelry ambiguous marker. public string SameCountPrefix { get; set; } = "+"; // Prefix for similar items in stash. public float FontMargin { get; set; } = 3f; // Item rect marker offset from top-right. public IFont CheckItemFont1 { get; set; } public IFont CheckItemFont2 { get; set; } public IFont SameCountFont { get; set; } public IFont SingleItemFont { get; set; } public IFont SkippedJewelryFont { get; set; } public bool DarkenInventory { get; set; } = true; public bool DarkenStash { get; set; } = true; public IBrush DarkenBrush { get; set; } public string[] ExtraSpecialItems { get; set; } = { // These are considered like build items but without a specific build! "Broken Crown", // Gem farming. "Hellfire Amulet", // Special item for certain builds. "Leoric's Signet", // XP/leveling ring "Hellfire Ring", // XP/leveling ring }; public string[] NoDarkenItems { get; set; } = { // Do not darken for these as they are useful! "Puzzle Ring", "Bovine Bardiche", }; private Dictionary heroBuilds; public SalvageItems() { Enabled = true; Order = 1000; } public override void Load(IController hud) { base.Load(hud); ClassBrush = createBrush(Color.OrangeRed); OtherBrush = createBrush(Color.GreenYellow, alpha: 200); SpecialBrush = createBrush(Color.MediumPurple); HighlightBrush = createBrush(Color.Silver, dash: DashStyle.Dash); CheckItemFont1 = createFont("arial", 9, Color.DodgerBlue, bold: true, shadow: true); CheckItemFont2 = createFont("arial", 9, Color.LightSeaGreen, bold: true, shadow: true); SameCountFont = createFont("arial", 9, Color.DarkOrange, bold: true, shadow: true); SingleItemFont = createFont("arial", 9, Color.Gold, bold: true, shadow: true); SkippedJewelryFont = createFont("arial", 9, Color.LightSkyBlue, bold: true, shadow: true); DarkenBrush = Hud.Render.CreateBrush(148, 38, 38, 38, 0); heroBuilds = new SalvageItemsData().HeroBuilds; foreach (var itemName in ExtraSpecialItems) { heroBuilds.Add(itemName, new Build[] { new Build(HeroClass.None, itemName) }); } } private IBrush createBrush(Color textColor, float brushWidth = -1f, DashStyle dash = DashStyle.Solid, int alpha = 255) { return Hud.Render.CreateBrush(alpha, textColor.R, textColor.G, textColor.B, brushWidth, dash); } private IFont createFont(string fontFamily, float size, Color textColor, bool bold = false, bool shadow = false) { if (shadow) return Hud.Render.CreateFont(fontFamily, size, textColor.A, textColor.R, textColor.G, textColor.B, bold, false, 220, 032, 032, 032, true); return Hud.Render.CreateFont(fontFamily, size, textColor.A, textColor.R, textColor.G, textColor.B, bold, false, true); } public void PaintTopInGame(ClipState clipState) { if (clipState != ClipState.Inventory) return; if (!Hud.Game.IsInTown || Hud.Render.UiHidden || Hud.Game.MapMode == MapMode.WaypointMap || Hud.Game.MapMode == MapMode.ActMap || Hud.Game.MapMode == MapMode.Map) return; var stashTab = Hud.Inventory.SelectedStashTabIndex; var stashPage = Hud.Inventory.SelectedStashPageIndex; var stashTabAbs = stashTab + (stashPage * Hud.Inventory.MaxStashTabCountPerPage); var items = Hud.Game.Items.Where(x => x.Location == ItemLocation.Inventory || x.Location == ItemLocation.Stash); foreach (var item in items) { if (item.Location == ItemLocation.Stash) { var tabIndex = item.InventoryY / 10; if (tabIndex != stashTabAbs) continue; } if (!item.IsLegendary) continue; if (item.SnoItem.Kind == ItemKind.gem) continue; if (item.SnoItem.Kind == ItemKind.craft) continue; if (item.SnoItem.Kind == ItemKind.uberstuff) continue; if (item.SnoItem.MainGroupCode == "potion" || item.SnoItem.MainGroupCode == "healthpotions") continue; if (item.SnoItem.MainGroupCode == "gems_unique") continue; if (item.SnoItem.MainGroupCode == "consumable") continue; if (item.SnoItem.MainGroupCode == "horadriccache") continue; var rect = Hud.Inventory.GetItemRect(item); if (rect != System.Drawing.RectangleF.Empty) { var buildKind = BuildKind.None; if (heroBuilds.TryGetValue(item.SnoItem.NameEnglish, out var builds)) { var build = SkipLoN ? builds.FirstOrDefault(x => x.HeroClass == Hud.Game.Me.HeroClassDefinition.HeroClass && x.BuildNames.Count(y => y.StartsWith("LoN ")) == 0) : builds.FirstOrDefault(x => x.HeroClass == Hud.Game.Me.HeroClassDefinition.HeroClass); if (build != null) { buildKind = BuildKind.Class; } else { buildKind = builds.Any(x => x.HeroClass == HeroClass.None) ? BuildKind.Special : BuildKind.Other; } } if (buildKind == BuildKind.None) { DarkenItem(item, rect); } if (HighlightInventory && buildKind != BuildKind.None) { HighlightItem(item, rect, buildKind); } } } } private void HighlightItem(IItem item, System.Drawing.RectangleF rect, BuildKind buildKind) { if (item.CaldesannRank == 0) { var brush = (IBrush)null; switch (buildKind) { case BuildKind.Class: brush = ClassBrush; break; case BuildKind.Other: brush = OtherBrush; break; case BuildKind.Special: brush = SpecialBrush; break; default: return; } if ((item.AncientRank == 0 && HighlightLegendary) || (item.AncientRank > 0 && HighlightAncient)) { HighlightBrush.DrawRectangle(rect.Right - OffsetTop - HighlightOffset, rect.Bottom - OffsetTop - HighlightOffset, OffsetTop - OffsetBottom + (2 * HighlightOffset), OffsetTop - OffsetBottom + (2 * HighlightOffset)); } brush.DrawRectangle(rect.Right - OffsetTop, rect.Bottom - OffsetTop, OffsetTop - OffsetBottom, OffsetTop - OffsetBottom); } if (CheckItems) { if (SkipJewelryCheck && (item.SnoItem.MainGroupCode == "amulet" || item.SnoItem.MainGroupCode == "ring")) { // Skip checking until we know what we want! showMarker(rect, SkippedJewelryIndicator, SkippedJewelryFont); } else { var showCheckMark = false; var matchedItems = new List(); var ranks = countItemRanks(item, ref matchedItems); if (ranks[2] > 0) { if (item.AncientRank >= 1) { if (item.Location == ItemLocation.Inventory) { // Check ancient/primal! showCheckMark = true; } else { // Count ancient/primal! showMarker(rect, SameCountPrefix + (ranks[1] + ranks[2]).ToString(), SameCountFont); } } } else if (ranks[1] > 0) { if (item.AncientRank == 1) { if (item.Location == ItemLocation.Inventory) { // Check ancient. showCheckMark = true; } else { // Count ancient. showMarker(rect, SameCountPrefix + ranks[1].ToString(), SameCountFont); } } } else if (ranks[0] > 0) { if (item.AncientRank == 0) { // We have only legendaries. showCheckMark = true; } } else { if (item.Location == ItemLocation.Inventory) { showMarker(rect, item.AncientRank == 0 ? SingleLegendary : SingleAncient, SingleItemFont); } } if (showCheckMark) { var nonInventory = matchedItems.Any(x => x.Location != ItemLocation.Inventory); if (nonInventory) { showMarker(rect, CheckItemIndicator1, CheckItemFont1); } else { showMarker(rect, CheckItemIndicator2, CheckItemFont2); } } } } } private void DarkenItem(IItem item, System.Drawing.RectangleF rect) { if (item.CaldesannRank > 0) { return; } if (NoDarkenItems.Contains(item.SnoItem.NameEnglish)) { HighlightBrush.DrawRectangle(rect.Right - OffsetTop, rect.Bottom - OffsetTop, OffsetTop - OffsetBottom, OffsetTop - OffsetBottom); return; } DarkenBrush.DrawRectangle(rect.X, rect.Y, rect.Width, rect.Height); } private void showMarker(System.Drawing.RectangleF rect, string symbol, IFont font) { // Top right corner. if (!string.IsNullOrEmpty(symbol)) { var layout = font.GetTextLayout(symbol); font.DrawText(layout, rect.Right - FontMargin - layout.Metrics.Width, rect.Top + FontMargin); } } private IItemPerfection getMainStat(IItem item) { if (item != null && !item.Unidentified && item.Perfections != null) { foreach (var perfection in item.Perfections) { if (perfection.Attribute.Code == "Dexterity_Item" || perfection.Attribute.Code == "Intelligence_Item" || perfection.Attribute.Code == "Strength_Item") { return perfection; } } } return null; } private bool hasMainStat(IItemPerfection mainStat ,IItem item) { if (item != null && !item.Unidentified && item.Perfections != null) { foreach (var perfection in item.Perfections) { if (perfection.Attribute.Code == "Dexterity_Item" || perfection.Attribute.Code == "Intelligence_Item" || perfection.Attribute.Code == "Strength_Item") { return mainStat == null ? false : mainStat.Attribute.Code == perfection.Attribute.Code; } } } return mainStat == null ? true : false; } private int[] countItemRanks(IItem item, ref List items) { // We always match main stat! var ranks = new int[] { 0, 0, 0 }; var itemMainStat = getMainStat(item); foreach (var x in Hud.Game.Items) { if (x.SnoItem.NameEnglish == item.SnoItem.NameEnglish && x.Location != ItemLocation.Merchant && x.Location != ItemLocation.Floor && x.Seed != item.Seed && hasMainStat(itemMainStat, x)) { ranks[x.AncientRank] += 1; items.Add(x); } } return ranks; } } }