Skip to content

Instantly share code, notes, and snippets.

@razer86
Last active August 8, 2021 17:07
Show Gist options
  • Select an option

  • Save razer86/30f9c45f1e8885e4024d70af6df9afb4 to your computer and use it in GitHub Desktop.

Select an option

Save razer86/30f9c45f1e8885e4024d70af6df9afb4 to your computer and use it in GitHub Desktop.

Revisions

  1. razer86 revised this gist Jul 23, 2021. 2 changed files with 88 additions and 116 deletions.
    30 changes: 15 additions & 15 deletions SalvageFromIcyVeins.cs
    Original file line number Diff line number Diff line change
    @@ -212,21 +212,21 @@ private void printItemsAndBuilds(Dictionary<string, List<Build>> itemBuilds, str
    keys.Sort();

    builder
    .Append("using System.Collections.Generic;").AppendLine()
    .Append("").AppendLine()
    .Append("namespace Turbo.Plugins.JarJar.DefaultUI").AppendLine()
    .Append("{").AppendLine()
    .Append(" public class Build").AppendLine()
    .Append(" {").AppendLine()
    .Append(" public readonly HeroClass HeroClass;").AppendLine()
    .Append(" public readonly string[] BuildNames;").AppendLine()
    .Append("").AppendLine()
    .Append(" public Build(HeroClass heroClass, params string[] buildNames)").AppendLine()
    .Append(" {").AppendLine()
    .Append(" HeroClass = heroClass;").AppendLine()
    .Append(" BuildNames = buildNames;").AppendLine()
    .Append(" }").AppendLine()
    .Append(" }").AppendLine()
    .Append("using System.Collections.Generic;").AppendLine()
    .Append("").AppendLine()
    .Append("namespace Turbo.Plugins.JarJar.DefaultUI").AppendLine()
    .Append("{").AppendLine()
    .Append(" public class Build").AppendLine()
    .Append(" {").AppendLine()
    .Append(" public readonly HeroClass HeroClass;").AppendLine()
    .Append(" public readonly string[] BuildNames;").AppendLine()
    .Append("").AppendLine()
    .Append(" public Build(HeroClass heroClass, params string[] buildNames)").AppendLine()
    .Append(" {").AppendLine()
    .Append(" HeroClass = heroClass;").AppendLine()
    .Append(" BuildNames = buildNames;").AppendLine()
    .Append(" }").AppendLine()
    .Append(" }").AppendLine()
    .Append(" public sealed class SalvageItemsData").AppendLine()
    .Append(" {").AppendLine()
    .Append(" public readonly Dictionary<string, Build[]> HeroBuilds = new Dictionary<string, Build[]>()").AppendLine()
    174 changes: 73 additions & 101 deletions SalvageItemsData.cs
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,4 @@
    // SalvageItemsData.cs "$Revision: 2504 $" "$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://www.icy-veins.com/d3/legendary-item-salvage-guide

    using System.Collections.Generic;

    namespace Turbo.Plugins.JarJar.DefaultUI
    @@ -16,7 +14,6 @@ public Build(HeroClass heroClass, params string[] buildNames)
    BuildNames = buildNames;
    }
    }

    public sealed class SalvageItemsData
    {
    public readonly Dictionary<string, Build[]> HeroBuilds = new Dictionary<string, Build[]>()
    @@ -27,17 +24,20 @@ public sealed class SalvageItemsData
    "Unhallowed Essence Grenades (BiS+1)",
    "Unhallowed Essence Multishot (BiS+1)"),
    }},
    { "Akarat's Awakening", new Build[] {
    new Build(HeroClass.Crusader,
    "Invoker Thorns (BiS+Cube+1)"),
    }},
    { "Akkhan's Manacles", new Build[] {
    new Build(HeroClass.Crusader,
    "LoD Blessed Shield (BiS)"),
    }},
    { "Ambo's Pride", new Build[] {
    new Build(HeroClass.Barbarian,
    "Rend Wastes (Cube+2)"),
    "Rend Wastes (Cube+1)"),
    }},
    { "Ancient Parthan Defenders", new Build[] {
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (Alt)",
    "Leap/Earthquake MotE (BiS)"),
    new Build(HeroClass.Wizard,
    "DMO Arcane Orbit (BiS)",
    @@ -48,8 +48,6 @@ public sealed class SalvageItemsData
    "Trag'Oul Blood Mages (BiS)"),
    }},
    { "Andariel's Visage", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (Alt)"),
    new Build(HeroClass.Barbarian,
    "Frenzy Thorns LoN (Alt+1)",
    "HotA GR LoD (Alt)"),
    @@ -133,12 +131,14 @@ public sealed class SalvageItemsData
    "Natalya Rapid Fire (BiS)",
    "Shadow Impale (BiS+1)"),
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (Alt+1)"),
    "Frenzy HotNS (BiS+1)"),
    new Build(HeroClass.Wizard,
    "Firebird Flame Blades (BiS)",
    "Firebird Flame Blades (BiS)"),
    new Build(HeroClass.WitchDoctor,
    "Zuni Carnevil Poison Dart (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Mystic Ally (BiS)"),
    new Build(HeroClass.Crusader,
    "Akkhan Condemn (BiS)"),
    }},
    @@ -147,12 +147,14 @@ public sealed class SalvageItemsData
    "Natalya Rapid Fire (BiS)",
    "Shadow Impale (BiS+1)"),
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (Alt+1)"),
    "Frenzy HotNS (BiS+1)"),
    new Build(HeroClass.Wizard,
    "Firebird Flame Blades (BiS)",
    "Firebird Flame Blades (BiS)"),
    new Build(HeroClass.WitchDoctor,
    "Zuni Carnevil Poison Dart (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Mystic Ally (BiS)"),
    new Build(HeroClass.Crusader,
    "Akkhan Condemn (BiS)"),
    }},
    @@ -205,8 +207,6 @@ public sealed class SalvageItemsData
    "Follower (BiS)"),
    }},
    { "Azurewrath", new Build[] {
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (Alt)"),
    new Build(HeroClass.Monk,
    "PoJ Tempest Rush (Alt)"),
    }},
    @@ -220,7 +220,7 @@ public sealed class SalvageItemsData
    }},
    { "Band of Might", new Build[] {
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (Cube+2)",
    "Frenzy HotNS (BiS+2)",
    "Furious Charge IK/Raekor (BiS+1)",
    "HotA GR LoD (BiS)",
    "HotA IK (Alt+Cube)",
    @@ -249,7 +249,7 @@ public sealed class SalvageItemsData
    }},
    { "Bindings of the Lesser Gods", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+2)",
    "Inna Mystic Ally (Alt+Cube+2)",
    "LoD LTK (BiS)",
    "LoD WoL (Alt)"),
    }},
    @@ -275,8 +275,7 @@ public sealed class SalvageItemsData
    }},
    { "Blade of Prophecy", new Build[] {
    new Build(HeroClass.Crusader,
    "Akkhan Condemn (Alt+Cube+1)",
    "LoD Condemn (Alt+Cube)"),
    "Akkhan Condemn (Cube+1)"),
    }},
    { "Blade of the Tribes", new Build[] {
    new Build(HeroClass.Barbarian,
    @@ -522,13 +521,13 @@ public sealed class SalvageItemsData
    }},
    { "Convention of Elements", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (BiS+Cube)",
    "Marauder Cluster Arrow (BiS+1)",
    "Natalya Rapid Fire (Alt)",
    "Shadow Impale (BiS+2)",
    "Unhallowed Essence Grenades (BiS)"),
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (BiS+2)",
    "Frenzy HotNS (BiS)",
    "Frenzy HotNS (BiS)",
    "Furious Charge IK/Raekor (BiS)",
    "HotA GR LoD (Alt+Cube)",
    "HotA IK (BiS)",
    @@ -553,7 +552,7 @@ public sealed class SalvageItemsData
    "Spirit Barrage Mundunugu (BiS)",
    "Zuni Gargantuan (Alt+Cube)"),
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+1)",
    "Inna Mystic Ally (BiS+1)",
    "LoD LTK (BiS)",
    "LoD WoL (BiS)",
    "PoJ Tempest Rush (BiS)",
    @@ -603,6 +602,10 @@ public sealed class SalvageItemsData
    "Aegis of Valor Fist of the Heavens (BiS+1)",
    "Aegis of Valor Heaven's Fury (BiS+2)"),
    }},
    { "Crystal Fist", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Mystic Ally (BiS)"),
    }},
    { "Cuirass of the Wastes", new Build[] {
    new Build(HeroClass.Barbarian,
    "Rend Wastes (BiS+1)",
    @@ -633,8 +636,7 @@ public sealed class SalvageItemsData
    { "Dawn", new Build[] {
    new Build(HeroClass.DemonHunter,
    "Chakram (BiS+1)",
    "GoD Hungering Arrow (BiS+1)",
    "LoD FoK (BiS)",
    "GoD Hungering Arrow (Cube+1)",
    "Marauder Cluster Arrow (Cube+2)",
    "Natalya Rain of Vengeance (BiS)",
    "Natalya Rapid Fire (BiS)",
    @@ -704,11 +706,12 @@ public sealed class SalvageItemsData
    }},
    { "Echoing Fury", new Build[] {
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (Alt+2)",
    "Frenzy HotNS (Alt)",
    "Frenzy HotNS (BiS)",
    "HotA GR LoD (BiS)",
    "HotA IK (BiS)",
    "HotA Raekor (BiS)",
    "Rend Wastes (Alt)",
    "Rend Wastes (BiS)",
    "Seismic Slam (BiS)",
    "Support Barbarian (BiS)"),
    new Build(HeroClass.WitchDoctor,
    @@ -724,7 +727,6 @@ public sealed class SalvageItemsData
    }},
    { "Elusive Ring", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (Alt)",
    "Marauder Cluster Arrow (Alt+Cube)",
    "Natalya Rapid Fire (BiS)",
    "Shadow Impale (Alt+1)"),
    @@ -836,6 +838,7 @@ public sealed class SalvageItemsData
    }},
    { "Flying Dragon", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Mystic Ally (Cube+2)",
    "Raiment Shenlong Generator (Cube+1)"),
    }},
    { "Focus", new Build[] {
    @@ -858,15 +861,13 @@ public sealed class SalvageItemsData
    new Build(HeroClass.WitchDoctor,
    "Helltooth Acid Cloud (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS)",
    "Inna Mystic Ally (BiS)",
    "Raiment Shenlong Generator (BiS)"),
    new Build(HeroClass.Crusader,
    "Akkhan Condemn (BiS)"),
    }},
    { "Fortress Ballista", new Build[] {
    new Build(HeroClass.DemonHunter,
    "GoD Hungering Arrow (Alt)",
    "LoD FoK (BiS)",
    "Unhallowed Essence Grenades (BiS)"),
    }},
    { "Foundation of the Earth", new Build[] {
    @@ -966,10 +967,6 @@ public sealed class SalvageItemsData
    "Follower (BiS)",
    "Follower (BiS)"),
    }},
    { "Golden Flense", new Build[] {
    new Build(HeroClass.Crusader,
    "Roland's Sweep Attack (Alt+Cube+1)"),
    }},
    { "Goldskin", new Build[] {
    new Build(HeroClass.None,
    "Follower (BiS)"),
    @@ -1024,7 +1021,6 @@ public sealed class SalvageItemsData
    }},
    { "Gungdo Gear", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (Alt+Cube)",
    "Uliana Seven-Sided Strike (BiS+1)"),
    }},
    { "Gyana Na Kashu", new Build[] {
    @@ -1175,8 +1171,6 @@ public sealed class SalvageItemsData
    "LoD Energy Twister (Alt)"),
    }},
    { "Hexing Pants of Mr. Yan", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (BiS)"),
    new Build(HeroClass.Barbarian,
    "Rend Wastes (Alt)"),
    new Build(HeroClass.Monk,
    @@ -1316,37 +1310,37 @@ public sealed class SalvageItemsData
    new Build(HeroClass.WitchDoctor,
    "Jade Harvester (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Mystic Ally (BiS)",
    "Sunwuko Wave of Light (BiS)",
    "Uliana Seven-Sided Strike (BiS)"),
    new Build(HeroClass.Crusader,
    "Aegis of Valor Heaven's Fury (BiS)"),
    new Build(HeroClass.Necromancer,
    "Corpse Lance (Alt)",
    "Generator (BiS)",
    "LoD Corpse Explosion (BiS)",
    "Support Necromancer (BiS)"),
    }},
    { "Inna's Favor", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+2)",
    "Inna Mystic Ally (BiS+2)",
    "Raiment Shenlong Generator (BiS)",
    "Sunwuko Wave of Light (BiS)"),
    }},
    { "Inna's Hold", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+2)",
    "Inna Mystic Ally (BiS+2)",
    "Sunwuko Wave of Light (BiS)",
    "Support Monk (BiS)"),
    }},
    { "Inna's Radiance", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+2)",
    "Inna Mystic Ally (BiS+2)",
    "Raiment Shenlong Generator (BiS)",
    "Support Monk (BiS)"),
    }},
    { "Inna's Reach", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+2)"),
    "Inna Mystic Ally (BiS)"),
    }},
    { "Inna's Sandals", new Build[] {
    new Build(HeroClass.Monk,
    @@ -1355,13 +1349,13 @@ public sealed class SalvageItemsData
    }},
    { "Inna's Temperance", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+2)",
    "Inna Mystic Ally (BiS+2)",
    "Raiment Shenlong Generator (BiS)",
    "Sunwuko Wave of Light (BiS)"),
    }},
    { "Inna's Vast Expanse", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+2)",
    "Inna Mystic Ally (BiS+2)",
    "Raiment Shenlong Generator (BiS)",
    "Sunwuko Wave of Light (BiS)",
    "Support Monk (BiS)"),
    @@ -1371,10 +1365,6 @@ public sealed class SalvageItemsData
    "LoD Blood Nova (BiS)",
    "LoD Corpse Explosion (BiS+1)"),
    }},
    { "Irontoe Mudsputters", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (BiS)"),
    }},
    { "Jade Harvester's Courage", new Build[] {
    new Build(HeroClass.WitchDoctor,
    "Jade Harvester (BiS)"),
    @@ -1421,8 +1411,7 @@ public sealed class SalvageItemsData
    }},
    { "Johanna's Argument", new Build[] {
    new Build(HeroClass.Crusader,
    "Aegis of Valor Heaven's Fury (BiS)",
    "Blessed Hammer (BiS+1)"),
    "Aegis of Valor Heaven's Fury (BiS)"),
    }},
    { "Justice Lantern", new Build[] {
    new Build(HeroClass.Crusader,
    @@ -1502,7 +1491,7 @@ public sealed class SalvageItemsData
    }},
    { "Lefebvre's Soliloquy", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+2)",
    "Inna Mystic Ally (BiS+2)",
    "LoD LTK (BiS)",
    "LoD WoL (BiS)"),
    }},
    @@ -1528,8 +1517,7 @@ public sealed class SalvageItemsData
    }},
    { "Leoric's Crown", new Build[] {
    new Build(HeroClass.DemonHunter,
    "GoD/Marauder Support (BiS)",
    "LoD FoK (BiS)"),
    "GoD/Marauder Support (BiS)"),
    new Build(HeroClass.Barbarian,
    "Frenzy Thorns LoN (BiS+Cube+1)",
    "HotA GR LoD (BiS)",
    @@ -1554,6 +1542,8 @@ public sealed class SalvageItemsData
    "Follower (BiS)"),
    }},
    { "Lidless Wall", new Build[] {
    new Build(HeroClass.Crusader,
    "Aegis of Valor Fist of the Heavens (BiS)"),
    new Build(HeroClass.None,
    "Follower (Alt for Templar)"),
    }},
    @@ -1576,10 +1566,12 @@ public sealed class SalvageItemsData
    }},
    { "Lost Time", new Build[] {
    new Build(HeroClass.Necromancer,
    "Corpse Lance (Alt)",
    "Corpse Lance (BiS+3)",
    "LoD Corpse Lance (BiS)",
    "LoD Singularity (BiS+2)",
    "Masquerade Bone Spear (BiS+1)"),
    "Masquerade Bone Spear (BiS+1)",
    "Rathma Army of the Dead (BiS+3)",
    "Trag'Oul Blood Mages (BiS+1)"),
    }},
    { "Lut Socks", new Build[] {
    new Build(HeroClass.Barbarian,
    @@ -1612,7 +1604,6 @@ public sealed class SalvageItemsData
    }},
    { "Mantle of Channeling", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (BiS)",
    "Natalya Rain of Vengeance (BiS)"),
    new Build(HeroClass.Wizard,
    "LoD Energy Twister (BiS)"),
    @@ -1702,11 +1693,11 @@ public sealed class SalvageItemsData
    }},
    { "Mortick's Brace", new Build[] {
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (Alt+1)",
    "Frenzy HotNS (BiS)",
    "Frenzy Thorns LoN (BiS+1)",
    "Furious Charge IK/Raekor (BiS)",
    "HotA GR LoD (BiS)",
    "HotA IK (Alt+Cube)",
    "HotA IK (Alt+Cube+1)",
    "Leap/Earthquake MotE (Alt)",
    "Rend Wastes (BiS)",
    "Whirlwind GR Wastes (BiS)"),
    @@ -1801,7 +1792,7 @@ public sealed class SalvageItemsData
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (BiS)",
    "Furious Charge IK/Raekor (BiS)",
    "HotA IK (Alt+Cube)",
    "HotA IK (Alt)",
    "Leap/Earthquake MotE (BiS)",
    "Rend Wastes (BiS)",
    "Support Barbarian (Alt+1)",
    @@ -1905,7 +1896,8 @@ public sealed class SalvageItemsData
    "LoD Spirit Barrage (BiS+2)",
    "Zuni Carnevil Poison Dart (BiS)"),
    new Build(HeroClass.Monk,
    "Sunwuko Wave of Light (BiS)"),
    "Sunwuko Wave of Light (BiS)",
    "Support Monk (BiS)"),
    new Build(HeroClass.Crusader,
    "LoD Blessed Shield (BiS)",
    "LoD Condemn (BiS)"),
    @@ -1947,7 +1939,7 @@ public sealed class SalvageItemsData
    { "Pig Sticker", new Build[] {
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (BiS)",
    "Support Barbarian (Alt)"),
    "Support Barbarian (BiS)"),
    new Build(HeroClass.Monk,
    "PoJ Tempest Rush (BiS)"),
    new Build(HeroClass.Crusader,
    @@ -2114,7 +2106,7 @@ public sealed class SalvageItemsData
    new Build(HeroClass.WitchDoctor,
    "Helltooth Acid Cloud (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS)",
    "Inna Mystic Ally (BiS)",
    "Raiment Shenlong Generator (BiS)"),
    new Build(HeroClass.Crusader,
    "Akkhan Condemn (BiS)"),
    @@ -2135,6 +2127,7 @@ public sealed class SalvageItemsData
    "Unhallowed Essence Grenades (Cube+1)",
    "Unhallowed Essence Multishot (Cube+1)"),
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (Cube+2)",
    "Furious Charge IK/Raekor (Cube+1)",
    "Support Barbarian (Alt+Cube)"),
    new Build(HeroClass.WitchDoctor,
    @@ -2143,6 +2136,7 @@ public sealed class SalvageItemsData
    "Spirit Barrage Mundunugu (Cube+2)",
    "Zuni Carnevil Poison Dart (Cube+1)"),
    new Build(HeroClass.Monk,
    "Inna Mystic Ally (Cube+1)",
    "PoJ Tempest Rush (Cube+1)",
    "Raiment Shenlong Generator (Cube+1)",
    "Sunwuko Wave of Light (Cube+2)",
    @@ -2224,8 +2218,8 @@ public sealed class SalvageItemsData
    "Arachyr Firebats (BiS+1)",
    "Helltooth Gargantuan (BiS)",
    "Jade Harvester (BiS)",
    "LoD Carnevil DoD (Alt+Cube)",
    "LoD Spirit Barrage (Alt+Cube)",
    "LoD Carnevil DoD (BiS+Cube)",
    "LoD Spirit Barrage (BiS+Cube)",
    "Spirit Barrage Mundunugu (BiS+1)",
    "Zuni Gargantuan (BiS+1)"),
    }},
    @@ -2324,16 +2318,14 @@ public sealed class SalvageItemsData
    }},
    { "Shenlong's Fist of Legend", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Mystic Ally (BiS)",
    "Raiment Shenlong Generator (BiS+2)"),
    }},
    { "Shenlong's Relentless Assault", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Mystic Ally (BiS)",
    "Raiment Shenlong Generator (BiS+2)"),
    }},
    { "Shi Mizu's Haori", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (BiS)"),
    }},
    { "Shield of Fury", new Build[] {
    new Build(HeroClass.Crusader,
    "Aegis of Valor Heaven's Fury (BiS+2)"),
    @@ -2388,7 +2380,8 @@ public sealed class SalvageItemsData
    }},
    { "Spines of Savages", new Build[] {
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (BiS+2)",
    "Frenzy HotNS (BiS)",
    "Frenzy HotNS (BiS)",
    "Support Barbarian (BiS+1)"),
    }},
    { "Spines of Seething Hatred", new Build[] {
    @@ -2402,7 +2395,7 @@ public sealed class SalvageItemsData
    }},
    { "Spirit Guards", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (Alt+Cube)",
    "Inna Mystic Ally (BiS+Cube+2)",
    "LoD LTK (Alt+Cube)",
    "Raiment Shenlong Generator (BiS+1)",
    "Support Monk (Alt+Cube)",
    @@ -2415,7 +2408,7 @@ public sealed class SalvageItemsData
    }},
    { "Spite", new Build[] {
    new Build(HeroClass.WitchDoctor,
    "Helltooth Gargantuan (Cube+1)"),
    "Helltooth Gargantuan (BiS+2)"),
    }},
    { "Squirt's Necklace", new Build[] {
    new Build(HeroClass.DemonHunter,
    @@ -2448,7 +2441,7 @@ public sealed class SalvageItemsData
    "Spirit Barrage Mundunugu (BiS)",
    "Zuni Carnevil Poison Dart (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS)",
    "Inna Mystic Ally (BiS)",
    "LoD WoL (BiS)",
    "PoJ Tempest Rush (BiS)",
    "Uliana Seven-Sided Strike (BiS)"),
    @@ -2464,8 +2457,6 @@ public sealed class SalvageItemsData
    "Trag'Oul Blood Mages (BiS)"),
    }},
    { "St. Archew's Gage", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (BiS)"),
    new Build(HeroClass.Barbarian,
    "Frenzy Thorns LoN (BiS+1)"),
    }},
    @@ -2537,8 +2528,6 @@ public sealed class SalvageItemsData
    "LoD Corpse Lance (BiS)"),
    }},
    { "Stormshield", new Build[] {
    new Build(HeroClass.Barbarian,
    "Support Barbarian (Alt)"),
    new Build(HeroClass.Monk,
    "Support Monk (BiS)"),
    new Build(HeroClass.Necromancer,
    @@ -2568,10 +2557,6 @@ public sealed class SalvageItemsData
    new Build(HeroClass.Necromancer,
    "Support Necromancer (BiS)"),
    }},
    { "Sun Keeper", new Build[] {
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (Alt)"),
    }},
    { "Sunwuko's Balance", new Build[] {
    new Build(HeroClass.Monk,
    "Sunwuko Wave of Light (BiS+1)",
    @@ -2675,7 +2660,7 @@ public sealed class SalvageItemsData
    { "The Barber", new Build[] {
    new Build(HeroClass.WitchDoctor,
    "LoD Spirit Barrage (BiS+2)",
    "Spirit Barrage Mundunugu (Alt+Cube+3)"),
    "Spirit Barrage Mundunugu (BiS+Cube+3)"),
    }},
    { "The Burning Axe of Sankis", new Build[] {
    new Build(HeroClass.Barbarian,
    @@ -2707,7 +2692,7 @@ public sealed class SalvageItemsData
    "Zuni Carnevil Poison Dart (BiS)",
    "Zuni Gargantuan (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+1)",
    "Inna Mystic Ally (BiS+1)",
    "Raiment Shenlong Generator (BiS+1)",
    "Sunwuko Wave of Light (BiS)",
    "Uliana Seven-Sided Strike (BiS)"),
    @@ -2721,7 +2706,7 @@ public sealed class SalvageItemsData
    }},
    { "The Crudest Boots", new Build[] {
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+2)",
    "Inna Mystic Ally (BiS+2)",
    "LoD WoL (BiS)"),
    }},
    { "The Dagger of Darts", new Build[] {
    @@ -2744,7 +2729,6 @@ public sealed class SalvageItemsData
    { "The Flavor of Time", new Build[] {
    new Build(HeroClass.DemonHunter,
    "GoD/Marauder Support (BiS)",
    "LoD FoK (BiS)",
    "Shadow Impale (BiS)"),
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (BiS)",
    @@ -2774,6 +2758,7 @@ public sealed class SalvageItemsData
    "Leap/Earthquake MotE (BiS)"),
    new Build(HeroClass.Crusader,
    "Akkhan Condemn (BiS)",
    "Blessed Hammer (BiS+1)",
    "LoD Condemn (BiS)",
    "Roland's Sweep Attack (BiS+Cube+1)"),
    new Build(HeroClass.None,
    @@ -2801,7 +2786,7 @@ public sealed class SalvageItemsData
    }},
    { "The Ninth Cirri Satchel", new Build[] {
    new Build(HeroClass.DemonHunter,
    "GoD Hungering Arrow (Cube+2)"),
    "GoD Hungering Arrow (BiS+Cube+2)"),
    }},
    { "The Shadow's Bane", new Build[] {
    new Build(HeroClass.DemonHunter,
    @@ -2884,7 +2869,7 @@ public sealed class SalvageItemsData
    "Zuni Carnevil Poison Dart (BiS)",
    "Zuni Gargantuan (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (BiS+1)",
    "Inna Mystic Ally (BiS+1)",
    "Raiment Shenlong Generator (BiS+1)",
    "Sunwuko Wave of Light (BiS)",
    "Uliana Seven-Sided Strike (BiS)"),
    @@ -2944,10 +2929,6 @@ public sealed class SalvageItemsData
    "Follower (BiS)",
    "Follower (BiS)"),
    }},
    { "Thundergod's Vigor", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (BiS)"),
    }},
    { "Towers of the Light", new Build[] {
    new Build(HeroClass.Crusader,
    "Blessed Hammer (BiS+1)"),
    @@ -3029,7 +3010,7 @@ public sealed class SalvageItemsData
    }},
    { "Uhkapian Serpent", new Build[] {
    new Build(HeroClass.WitchDoctor,
    "Helltooth Gargantuan (Alt)",
    "Helltooth Gargantuan (BiS)",
    "LoD Carnevil DoD (BiS)"),
    }},
    { "Uliana's Burden", new Build[] {
    @@ -3066,8 +3047,6 @@ public sealed class SalvageItemsData
    "Unhallowed Essence Multishot (BiS)"),
    }},
    { "Unity", new Build[] {
    new Build(HeroClass.DemonHunter,
    "LoD FoK (BiS)"),
    new Build(HeroClass.Wizard,
    "DMO Frozen Orb (BiS)",
    "LoD Energy Twister (Alt+Cube)",
    @@ -3078,7 +3057,7 @@ public sealed class SalvageItemsData
    "LoD Carnevil DoD (BiS)",
    "LoD Spirit Barrage (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Exploding Palm (Alt+Cube+2)",
    "Inna Mystic Ally (Alt+2)",
    "LoD LTK (BiS)",
    "LoD WoL (BiS)",
    "Raiment Shenlong Generator (BiS+1)",
    @@ -3114,14 +3093,6 @@ public sealed class SalvageItemsData
    new Build(HeroClass.WitchDoctor,
    "Helltooth Zombie Bears (BiS+1)"),
    }},
    { "Valla's Bequest", new Build[] {
    new Build(HeroClass.DemonHunter,
    "GoD Hungering Arrow (BiS)"),
    }},
    { "Vambraces of Sescheron", new Build[] {
    new Build(HeroClass.Barbarian,
    "Frenzy HotNS (BiS)"),
    }},
    { "Vengeful Wind", new Build[] {
    new Build(HeroClass.Monk,
    "PoJ Tempest Rush (BiS)",
    @@ -3136,6 +3107,7 @@ public sealed class SalvageItemsData
    { "Vile Hive", new Build[] {
    new Build(HeroClass.WitchDoctor,
    "Arachyr Firebats (BiS)",
    "Helltooth Acid Cloud (BiS+1)",
    "Jade Harvester (Alt+1)"),
    }},
    { "Vile Ward", new Build[] {
    @@ -3203,6 +3175,7 @@ public sealed class SalvageItemsData
    "Zuni Carnevil Poison Dart (BiS)",
    "Zuni Gargantuan (BiS)"),
    new Build(HeroClass.Monk,
    "Inna Mystic Ally (BiS)",
    "Raiment Shenlong Generator (BiS)",
    "SWK LTK (BiS)"),
    new Build(HeroClass.Crusader,
    @@ -3245,7 +3218,6 @@ public sealed class SalvageItemsData
    new Build(HeroClass.DemonHunter,
    "Chakram (BiS)",
    "GoD Hungering Arrow (BiS+1)",
    "LoD FoK (BiS)",
    "Marauder Cluster Arrow (BiS+2)",
    "Natalya Marauder Sentry (BiS)",
    "Natalya Rain of Vengeance (BiS)",
    @@ -3313,4 +3285,4 @@ public sealed class SalvageItemsData
    }},
    };
    }
    }
    }
  2. razer86 revised this gist Jul 23, 2021. 1 changed file with 18 additions and 2 deletions.
    20 changes: 18 additions & 2 deletions SalvageFromIcyVeins.cs
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // SalvageFromIcyVeins.cs "$Revision: 2505 $" "$Date: 2021-07-10 15:15:00 +1000 (la, 17 elo 2019) $"
    // SalvageFromIcyVeins.cs "$Revision: 2505 $" "$Date: 2021-07-23 21:05:00 +1000 $"
    // https://www.nuget.org/packages/HtmlAgilityPack/
    using HtmlAgilityPack;
    using System;
    @@ -212,6 +212,21 @@ private void printItemsAndBuilds(Dictionary<string, List<Build>> itemBuilds, str
    keys.Sort();

    builder
    .Append("using System.Collections.Generic;").AppendLine()
    .Append("").AppendLine()
    .Append("namespace Turbo.Plugins.JarJar.DefaultUI").AppendLine()
    .Append("{").AppendLine()
    .Append(" public class Build").AppendLine()
    .Append(" {").AppendLine()
    .Append(" public readonly HeroClass HeroClass;").AppendLine()
    .Append(" public readonly string[] BuildNames;").AppendLine()
    .Append("").AppendLine()
    .Append(" public Build(HeroClass heroClass, params string[] buildNames)").AppendLine()
    .Append(" {").AppendLine()
    .Append(" HeroClass = heroClass;").AppendLine()
    .Append(" BuildNames = buildNames;").AppendLine()
    .Append(" }").AppendLine()
    .Append(" }").AppendLine()
    .Append(" public sealed class SalvageItemsData").AppendLine()
    .Append(" {").AppendLine()
    .Append(" public readonly Dictionary<string, Build[]> HeroBuilds = new Dictionary<string, Build[]>()").AppendLine()
    @@ -270,7 +285,8 @@ private void printItemsAndBuilds(Dictionary<string, List<Build>> itemBuilds, str
    }
    builder
    .Append(" };").AppendLine()
    .Append(" }").AppendLine();
    .Append(" }").AppendLine()
    .Append("}").AppendLine();

    var result = builder.ToString();
    File.WriteAllText(salvageItemsDataFile, result);
  3. razer86 revised this gist Jul 10, 2021. 3 changed files with 601 additions and 771 deletions.
    26 changes: 25 additions & 1 deletion SalvageFromIcyVeins.cs
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // SalvageFromIcyVeins.cs "$Revision: 2505 $" "$Date: 2019-08-17 11:03:04 +0300 (la, 17 elo 2019) $"
    // SalvageFromIcyVeins.cs "$Revision: 2505 $" "$Date: 2021-07-10 15:15:00 +1000 (la, 17 elo 2019) $"
    // https://www.nuget.org/packages/HtmlAgilityPack/
    using HtmlAgilityPack;
    using System;
    @@ -336,6 +336,30 @@ private static string[] parseAsTokens(string buildName)
    buildName.Substring(pos2 + 1),
    };
    }
    if (pos1 == -1 && buildName.Contains("Enchantress Guide ("))
    {
    return new string[] {
    "Follower",
    HeroClass.Follower.ToString(),
    buildName.Substring(pos2 + 1),
    };
    }
    if (pos1 == -1 && buildName.Contains("Scoundrel Guide ("))
    {
    return new string[] {
    "Follower",
    HeroClass.Follower.ToString(),
    buildName.Substring(pos2 + 1),
    };
    }
    if (pos1 == -1 && buildName.Contains("Templar Guide ("))
    {
    return new string[] {
    "Follower",
    HeroClass.Follower.ToString(),
    buildName.Substring(pos2 + 1),
    };
    }
    if (pos1 == -1)
    throw new InvalidOperationException(buildName);
    return new string[] {
    2 changes: 1 addition & 1 deletion SalvageItems.cs
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    // SalvageItems.cs "$Revision: 2502 $" "$Date: 2019-08-17 10:11:50 +0300 (la, 17 elo 2019) $"
    // 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
    1,344 changes: 575 additions & 769 deletions SalvageItemsData.cs
    575 additions, 769 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  4. razer86 revised this gist Jul 30, 2020. No changes.
  5. razer86 revised this gist Jul 30, 2020. 3 changed files with 3882 additions and 0 deletions.
    16 changes: 16 additions & 0 deletions Program.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    using System;
    using LogDataCollector;

    namespace th_salvageupdater
    {
    class Program
    {
    static void Main(string[] args)
    {
    //Console.WriteLine("Hello World!");
    SalvageFromIcyVeins data = new SalvageFromIcyVeins();
    data.LoadData("D:\\Games\\th_salvageupdater");

    }
    }
    }
    356 changes: 356 additions & 0 deletions SalvageFromIcyVeins.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,356 @@
    // SalvageFromIcyVeins.cs "$Revision: 2505 $" "$Date: 2019-08-17 11:03:04 +0300 (la, 17 elo 2019) $"
    // https://www.nuget.org/packages/HtmlAgilityPack/
    using HtmlAgilityPack;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Xml.XPath;

    namespace LogDataCollector
    {
    public enum HeroClass : int
    {
    DemonHunter = 0,
    Barbarian = 1,
    Wizard = 2,
    WitchDoctor = 3,
    Monk = 4,
    Crusader = 5,
    Necromancer = 6,
    Follower = 7, // For our convenience only.
    Total = 8,
    None = 9, // Change Follower -> None for final output.
    }

    public class SalvageFromIcyVeins
    {
    private class Build
    {
    public readonly HeroClass HeroClass;
    public readonly List<string> BuildNames;

    public Build(HeroClass heroClass, string buildName)
    {
    HeroClass = heroClass;
    BuildNames = new List<string>() { buildName };
    }
    }

    private const string html = "https://www.icy-veins.com/d3/legendary-item-salvage-guide";

    public bool SkipLoN = false; // You can skip LoN build in the UI as well so include them by default.
    public bool FileTest = false;
    public HashSet<HeroClass> HeroClasses = new HashSet<HeroClass>()
    {
    HeroClass.DemonHunter,
    HeroClass.Barbarian,
    HeroClass.Wizard,
    HeroClass.WitchDoctor,
    HeroClass.Monk,
    HeroClass.Crusader,
    HeroClass.Necromancer,
    HeroClass.Follower,
    };

    // Replace with Console.WriteLine for simple work around to write somewhere or {} to disable.
    private void WriteLine(object value) { Console.WriteLine(value.ToString()); }
    private void WriteLine(string value = null) { Console.WriteLine(value); }
    private void WriteLine(string format, params object[] arg) { Console.WriteLine(string.Format(format, arg)); }

    public void LoadData(string _dataDir)
    {
    var filename = Path.Combine(_dataDir, "salvage-guide.html");
    var cacheDir = Path.Combine(_dataDir, "htmlCache");
    var salvageItemsDataFile = Path.Combine(_dataDir, "SalvageItemsData.cs");
    if (!Directory.Exists(cacheDir))
    {
    Directory.CreateDirectory(cacheDir);
    }
    var htmlDoc = (HtmlDocument)null;
    if (FileTest && File.Exists(filename))
    {
    this.WriteLine("Load HTML from: {0}", html);
    htmlDoc = new HtmlDocument();
    htmlDoc.Load(filename);
    }
    else
    {
    // Must create in this order.
    var htmlWeb = new HtmlWeb();
    htmlWeb.CachePath = cacheDir;
    htmlWeb.UsingCache = true;

    this.WriteLine("Load HTML from: {0}", html);
    htmlDoc = htmlWeb.Load(html);
    this.WriteLine("Load HTML took: {0} ms", htmlWeb.RequestDuration);
    }
    var salvageTable = XPathExpression.Compile("//table[@class='salvage_table']");
    var table = htmlDoc.DocumentNode.SelectSingleNode(salvageTable);
    if (table == null)
    {
    this.WriteLine("TABLE not found in HTML document, expr={0}", salvageTable.Expression);
    return;
    }
    try
    {
    Dictionary<string, List<Build>> itemBuilds = new Dictionary<string, List<Build>>();
    parseTable(table, itemBuilds);
    printItemsAndBuilds(itemBuilds, salvageItemsDataFile);
    }
    catch (Exception x)
    {
    this.WriteLine(x);
    if (Debugger.IsAttached) Debugger.Break();
    return;
    }
    }

    // private List<ItemData> loadItemData()
    // {
    // return new List<ItemData>();
    // }

    private void parseTable(HtmlNode table, Dictionary<string, List<Build>> itemBuilds)
    {
    var rowCount = 0;
    foreach (var row in table.SelectNodes("//tr"))
    {
    rowCount += 1;
    var elements = row.Elements("td");
    var columns = elements?.ToList();
    if (columns?.Count != 2)
    {
    if (rowCount > 1)
    {
    this.WriteLine("row{0,4} INVALID columns={1}", rowCount, columns?.Count);
    }
    continue;
    }
    // Find item name from link
    var links = columns[0].SelectNodes("./span/a")?.ToList();
    if (links?.Count != 1)
    {
    this.WriteLine("row{0,4} INVALID links={1}", rowCount, links?.Count);
    continue;
    }
    var itemName = links[0].InnerText;
    if (itemName.StartsWith("Hellfire ")) // Ignore Hellfire Amulet and Ring because they are so special and must be judged by the user!
    {
    this.WriteLine("row{0,4} {1} SKIPPED", rowCount, itemName);
    continue;
    }
    this.WriteLine("row{0,4} {1}", rowCount, itemName);

    // Find build names for unordered list (inside a link).
    var items = columns[1].SelectNodes("./ul/li")?.ToList();
    if (items?.Count == 0)
    {
    this.WriteLine("row{0,4} INVALID items={1}", rowCount, items?.Count);
    continue;
    }
    if (!itemBuilds.TryGetValue(itemName, out var buildList))
    {
    buildList = new List<Build>();
    itemBuilds.Add(itemName, buildList);
    }
    foreach (var item in items)
    {
    var buildNameText = item.InnerText.Trim(); // Trim to be sure.
    while (buildNameText.Contains(" ")) // InnerText can return double spaces which we must fix for parser.
    {
    buildNameText = buildNameText.Replace(" ", " ");
    }
    while (buildNameText.Contains("WD (")) // InnerText can return double spaces which we must fix for parser.
    {
    buildNameText = buildNameText.Replace("WD (", "Witch Doctor (");
    }
    if (SkipLoN && buildNameText.StartsWith("LoN ")) // Filter LoN builds
    continue;
    if (buildNameText.EndsWith("outdated")) // outdated!
    continue;
    if (buildNameText.EndsWith(" (Cube)")) // Only for Cube!
    continue;
    if (buildNameText.Contains("Dungeon Guide")) // Set Dungeon Guide skipped!
    continue;
    if (buildNameText.Contains("The Thrill")) // The Thrill Conquest Build!
    continue;
    var tuple = parseBuild(buildNameText);
    if (tuple != null)
    {
    var heroClass = tuple.Item1;
    if (!HeroClasses.Contains(heroClass)) // Filter builds by class.
    continue;
    var buildName = tuple.Item2;
    this.WriteLine(" {0,-11} {1}", heroClass, buildName);
    var build = buildList.FirstOrDefault(x => x.HeroClass == heroClass);
    if (build == null)
    {
    build = new Build(heroClass, buildName);
    buildList.Add(build);
    }
    else
    {
    build.BuildNames.Add(buildName);
    }
    }
    }
    }
    this.WriteLine("{0} rows processed", rowCount);
    }

    private void printItemsAndBuilds(Dictionary<string, List<Build>> itemBuilds, string salvageItemsDataFile)
    {

    // HeroClass -> build name -> item name
    var counters = new int[(int)HeroClass.Total + 1];
    var totalIndex = (int)HeroClass.Total;
    var builder = new StringBuilder().AppendLine();
    var keys = itemBuilds.Keys.ToList();
    keys.Sort();

    builder
    .Append(" public sealed class SalvageItemsData").AppendLine()
    .Append(" {").AppendLine()
    .Append(" public readonly Dictionary<string, Build[]> HeroBuilds = new Dictionary<string, Build[]>()").AppendLine()
    .Append(" {").AppendLine();

    foreach (var itemName in keys)
    {
    var builds = itemBuilds[itemName];
    if (builds.Count == 0)
    {
    continue;
    }
    // Item name.
    builder
    .Append(" { ")
    .AppendFormat("\"{0}\", new Build[] ", itemName)
    .Append("{")
    .AppendLine();
    counters[totalIndex] += 1;

    builds.Sort((a, b) => a.HeroClass.CompareTo(b.HeroClass));
    foreach (var build in builds)
    {
    // Hero class, convert Follower to None.
    var heroClass = build.HeroClass == HeroClass.Follower ? HeroClass.None : build.HeroClass;
    builder
    .AppendFormat(" new Build(HeroClass.{0},", heroClass.ToString())
    .AppendLine();

    // Build names - sort and loop.
    build.BuildNames.Sort();
    var heroIndex = (int)build.HeroClass;
    var lastIndex = build.BuildNames.Count - 1;
    for (var i = 0; i < build.BuildNames.Count; ++i)
    {
    var buildName = build.BuildNames[i];
    builder
    .AppendFormat(" \"{0}\"", buildName);
    if (i < lastIndex)
    {
    builder
    .Append(",")
    .AppendLine();
    }
    else
    {
    builder
    .Append("),")
    .AppendLine();
    }
    counters[heroIndex] += 1;
    }
    }
    builder
    .Append(" }},").AppendLine();
    }
    builder
    .Append(" };").AppendLine()
    .Append(" }").AppendLine();

    var result = builder.ToString();
    File.WriteAllText(salvageItemsDataFile, result);

    this.WriteLine(result);
    this.WriteLine("{0} items processed", keys.Count);
    builder
    .Clear().AppendLine();
    for (int i = 0; i < counters.Length; ++i)
    {
    var heroClass = (HeroClass)i;
    builder
    .AppendFormat("{0,-11}", heroClass.ToString())
    .AppendFormat("{0,5}", counters[i])
    .AppendLine();
    }
    builder
    .AppendFormat("{0,-11}", "Skipped")
    .AppendFormat("{0,5}", keys.Count - counters[totalIndex])
    .AppendLine();
    this.WriteLine(builder.ToString());
    }

    private static Tuple<HeroClass, string> parseBuild(string buildNameText)
    {
    string[] tokens = parseAsTokens(buildNameText);
    if (!Enum.TryParse(tokens[1], out HeroClass heroClass))
    throw new InvalidOperationException(tokens[1]);
    var name = tokens[0];
    if (name == "Support")
    {
    name += " " + tokens[1]; // Support build is class specific!
    }
    var spec = tokens[2];
    spec = spec.Replace(" + ", "+").Replace(" variation)", ")").Replace(" variations)", ")");

    return new Tuple<HeroClass, string>(heroClass, name + " " + spec);
    }

    private static string[] parseAsTokens(string buildName)
    {
    var pos2 = buildName.IndexOf(" (");
    if (pos2 == -1)
    throw new InvalidOperationException(buildName);
    var pos1 = buildName.IndexOf(" Demon Hunter (");
    if (pos1 == -1)
    pos1 = buildName.IndexOf(" Barbarian (");
    if (pos1 == -1)
    pos1 = buildName.IndexOf(" Wizard (");
    if (pos1 == -1)
    pos1 = buildName.IndexOf(" Witch Doctor (");
    if (pos1 == -1)
    pos1 = buildName.IndexOf(" Monk (");
    if (pos1 == -1)
    pos1 = buildName.IndexOf(" Crusader (");
    if (pos1 == -1)
    pos1 = buildName.IndexOf(" Necromancer (");
    if (pos1 == -1 && buildName.Contains("Follower Guide ("))
    {
    return new string[] {
    "Follower",
    HeroClass.Follower.ToString(),
    buildName.Substring(pos2 + 1),
    };
    }
    if (pos1 == -1)
    throw new InvalidOperationException(buildName);
    return new string[] {
    buildName.Substring(0, pos1),
    buildName.Substring(pos1 + 1, pos2 - pos1 - 1).Replace(" ", ""),
    buildName.Substring(pos2 + 1),
    };
    }

    private static string parseBaseName(string buildName)
    {
    var pos = buildName.IndexOf(" (");
    if (pos == -1)
    throw new InvalidOperationException(buildName);
    return buildName.Substring(0, pos);
    }
    }
    }
    3,510 changes: 3,510 additions & 0 deletions SalvageItemsData.cs
    3,510 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
  6. razer86 created this gist Jul 30, 2020.
    347 changes: 347 additions & 0 deletions SalvageItems.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,347 @@
    // SalvageItems.cs "$Revision: 2502 $" "$Date: 2019-08-17 10:11:50 +0300 (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<string, Build[]> 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<IItem>();
    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<IItem> 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;
    }
    }
    }