using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Mail; using System.Web; using System.Reflection; using System.Threading; using System.Windows; using System.Windows.Controls; using System.IO; using System.Windows.Markup; using Zeta; using Zeta.Common; using Zeta.Common.Plugins; using Zeta.CommonBot; using Zeta.CommonBot.Profile; using Zeta.CommonBot.Profile.Composites; using Zeta.Internals; using Zeta.Internals.Actors; using Zeta.Internals.Actors.Gizmos; using Zeta.Internals.SNO; using Zeta.Navigation; using Zeta.TreeSharp; using System.Text; using Zeta.XmlEngine; using GilesTrinity.Functions; namespace GilesTrinity { public class GilesTrinity : IPlugin { public Version Version { get { return new Version(1, 6, 3, 4); } } public string Author { get { return "GilesSmith"; } } public string Description { get { return "GilesTrinity version " + Version + " (v0.43)"; } } public string Name { get { return "GilesTrinity"; } } public bool Equals(IPlugin other) { return (other.Name == Name) && (other.Version == Version); } public static string SmtpServer = "smtp.gmail.com"; public static StringBuilder EmailMessage = new StringBuilder(); // ********************************************************************************************** // ***** A few special variables, mainly for Giles use, just at the top for easy access ***** // ********************************************************************************************** // Set the following to true, to disable file-logging for performance increase // WARNING: IF YOU GET CRASHES, ISSUES, OR PROBLEMS AND HAVE LOG-FILES DISABLED... // NOBODY CAN HELP YOU. Re-enable logging, wait for the issue/crash/problem, then report it with a log. // DO NOT DISABLE LOGGING AND THEN POST BLANK LOGS EXPECTING HELP! private const bool bDisableFileLogging = false; // For Giles use only (the data gathered by this being enabled is not useful for anyone else!): private const bool bLogBalanceDataForGiles = false; // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Various Dictionary lists for combat-stuff, like priority lists, ignore lists etc. ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // **************************************************************** // ***** Avoidance-related dictionaries/defaults ***** // **************************************************************** // A list of all the SNO's to avoid - you could add private static readonly HashSet hashAvoidanceSNOList = new HashSet { // Arcane Arcane 2 Desecrator Poison Tree Molten Core Molten Core 2 Molten Trail Plague Cloud Ice Balls 219702, 221225, 84608, 5482,6578, 4803, 4804, 224225, 247987, 95868, 108869, 402, 223675, // Bees-Wasps Plague-Hands Azmo Pools Azmo fireball Azmo bodies Belial 1 Belial 2 5212, 3865, 123124, 123842, 123839, 161822, 161833, // Sha-Ball Mol Ball Mage Fire Diablo Prison Diablo Meteor Ice-trail 4103, 160154, 432, 168031, 214845, 260377, // Zolt Bubble Zolt Twister Ghom Gas Maghda Proj 185924, 139741, 93837, 166686, }; // A list of SNO's that are projectiles (so constantly look for new locations while avoiding) private static readonly HashSet hashAvoidanceSNOProjectiles = new HashSet { // Bees-Wasps Sha-Ball Mol Ball Azmo fireball Zolt Twister Maghda Projectile 5212, 4103, 160154, 123842, 139741, 166686, }; // The rough radius of each avoidance thing (from centre to edge!) in feet private static readonly Dictionary dictAvoidanceRadiusDefaults = new Dictionary { // Arcane Arcane 2 Desecrator Poison Tree Molten Core Molten Core 2 Molten Trail Plague Cloud Ice Balls {219702, 12}, {221225, 12}, {84608, 10}, {5482, 14},{6578,14}, {4803, 19},{4804, 19}, {224225, 19}, {247987,19}, {95868, 6}, {108869, 14}, {402, 20}, {223675, 20}, // Bees-Wasps Plague-Hands Azmo Pools Azmo fireball Azmo bodies Belial 1 Belial 2 {5212, 10}, {3865, 12}, {123124, 54}, {123842, 16}, {123839, 47}, {161822, 20}, {161833, 20}, // Sha-Ball Mol Ball Mage Fire Diablo Prison Diablo Meteor Ice trail {4103, 8}, {160154, 8}, {432, 10}, {168031, 15}, {214845, 28}, {260377, 6}, // Zolt Bubble Zolt Twister Ghom Gas Maghda Proj {185924, 20}, {139741, 12}, {93837, 18}, {166686, 8}, }; private static Dictionary dictAvoidanceRadius = new Dictionary(dictAvoidanceRadiusDefaults); // How much health to look for and avoid each AOE (1 = 100% health, 0.5 = 50% health etc.), different values for each class // *************************** // ***** Barbarians ***** // *************************** private static readonly Dictionary dictAvoidanceHealthBarbDefaults = new Dictionary { // Arcane Arcane 2 Desecrator Poison Tree Molten Core Molten Core 2 Molten Trail Plague Cloud Ice Balls {219702, 1}, {221225, 1}, {84608, 1}, {5482, 0.55},{6578, 0.55}, {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.5}, {108869, 0.25}, {402, 0.85},{223675, 0.85}, // Bees-Wasps Plague-Hands Azmo Pools Azmo fireball Azmo bodies Belial 1 Belial 2 {5212, 0.7}, {3865, 0.80}, {123124, 0.8}, {123842, 0.7}, {123839, 0.7}, {161822, 1}, {161833, 1}, // Sha-Ball Mol Ball Mage Fire Diablo Prison Diablo Meteor Ice trail {4103, 0}, {160154, 0}, {432, 0.2}, {168031, 1}, {214845, 0.5}, {260377, 0.5}, // Zolt Bubble Zolt Twister Ghom Gas Maghda Proj {185924, 1}, {139741, 0.7}, {93837, 0.5}, {166686, 0.7}, }; private static Dictionary dictAvoidanceHealthBarb = new Dictionary(dictAvoidanceHealthBarbDefaults); // *************************** // ***** Monks ***** // *************************** private static readonly Dictionary dictAvoidanceHealthMonkDefaults = new Dictionary { // Arcane Arcane 2 Desecrator Poison Tree Molten Core Molten Core 2 Molten Trail Plague Cloud Ice Balls {219702, 1}, {221225, 1}, {84608, 1}, {5482, 0.65},{6578, 0.65}, {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.65}, {108869, 0.35}, {402, 0.85},{223675, 0.85}, // Bees-Wasps Plague-Hands Azmo Pools Azmo fireball Azmo bodies Belial 1 Belial 2 {5212, 0.75}, {3865, 0.85}, {123124, 0.85}, {123842, 0.75}, {123839, 0.75}, {161822, 1}, {161833, 1}, // Sha-Ball Mol Ball Mage Fire Diablo Prison Diablo Meteor Ice trail {4103, 0}, {160154, 0}, {432, 0.3}, {168031, 1}, {214845, 0.5}, {260377, 0.5}, // Zolt Bubble Zolt Twister Ghom Gas Maghda Proj {185924, 1}, {139741, 0.7}, {93837, 0.5}, {166686, 0.7}, }; private static Dictionary dictAvoidanceHealthMonk = new Dictionary(dictAvoidanceHealthMonkDefaults); // *************************** // ***** Wizards ***** // *************************** private static readonly Dictionary dictAvoidanceHealthWizardDefaults = new Dictionary { // Arcane Arcane 2 Desecrator Poison Tree Molten Core Molten Core 2 Molten Trail Plague Cloud Ice Balls {219702, 1}, {221225, 1}, {84608, 1}, {5482, 0.9},{6578, 0.9}, {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.9}, {108869, 0.9}, {402, 1},{223675, 1}, // Bees-Wasps Plague-Hands Azmo Pools Azmo fireball Azmo bodies Belial 1 Belial 2 {5212, 1}, {3865, 1}, {123124, 1}, {123842, 1}, {123839, 1}, {161822, 1}, {161833, 1}, // Sha-Ball Mol Ball Mage Fire Diablo Prison Diablo Meteor Ice trail {4103, 0.85}, {160154, 0.10}, {432, 0.10}, {168031, 1}, {214845, 0.8}, {260377, 0.9}, // Zolt Bubble Zolt Twister Ghom Gas Maghda Proj {185924, 1}, {139741, 0.7}, {93837, 1}, {166686, 0.7}, }; private static Dictionary dictAvoidanceHealthWizard = new Dictionary(dictAvoidanceHealthWizardDefaults); // *************************** // ***** Witch Doctors ***** // *************************** private static readonly Dictionary dictAvoidanceHealthWitchDefaults = new Dictionary { // Arcane Arcane 2 Desecrator Poison Tree Molten Core Molten Core 2 Molten Trail Plague Cloud Ice Balls {219702, 1}, {221225, 1}, {84608, 1}, {5482, 0.9},{6578, 0.9}, {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.9}, {108869, 0.9}, {402, 1},{223675, 1}, // Bees-Wasps Plague-Hands Azmo Pools Azmo fireball Azmo bodies Belial 1 Belial 2 {5212, 1}, {3865, 1}, {123124, 1}, {123842, 1}, {123839, 1}, {161822, 1}, {161833, 1}, // Sha-Ball Mol Ball Mage Fire Diablo Prison Diablo Meteor Ice trail {4103, 0.85}, {160154, 0.10}, {432, 0.10}, {168031, 1}, {214845, 0.8}, {260377, 0.9}, // Zolt Bubble Zolt Twister Ghom Gas Maghda Proj {185924, 1}, {139741, 0.7}, {93837, 1}, {166686, 0.7}, }; private static Dictionary dictAvoidanceHealthWitch = new Dictionary(dictAvoidanceHealthWitchDefaults); // *************************** // ***** Demon Hunter ***** // *************************** private static readonly Dictionary dictAvoidanceHealthDemonDefaults = new Dictionary { // Arcane Arcane 2 Desecrator Poison Tree Molten Core Molten Core 2 Molten Trail Plague Cloud Ice Balls {219702, 1}, {221225, 1}, {84608, 1}, {5482, 0.9},{6578, 0.9}, {4803, 1},{4804, 1}, {224225, 1}, {247987,1}, {95868, 0.9}, {108869, 0.9}, {402, 1},{223675, 1}, // Bees-Wasps Plague-Hands Azmo Pools Azmo fireball Azmo bodies Belial 1 Belial 2 {5212, 1}, {3865, 1}, {123124, 1}, {123842, 1}, {123839, 1}, {161822, 1}, {161833, 1}, // Sha-Ball Mol Ball Mage Fire Diablo Prison Diablo Meteor Ice trail {4103, 0.85}, {160154, 0.20}, {432, 0.20}, {168031, 1}, {214845, 0.8}, {260377, 0.9}, // Zolt Bubble Zolt Twister Ghom Gas Maghda Proj {185924, 1}, {139741, 0.7}, {93837, 1}, {166686, 0.7}, }; private static Dictionary dictAvoidanceHealthDemon = new Dictionary(dictAvoidanceHealthDemonDefaults); // The below thing should be UNCHANGED - it is automatically filled with the above dictionary sets based on your class - it defaults to barb but DOES CHANGE ITSELF! private static Dictionary dictAvoidanceHealth = new Dictionary(dictAvoidanceHealthBarb); // **************************************************************** // ***** Combat-related dictionaries/defaults ***** // **************************************************************** // A special list of things *NOT* to use whirlwind on (eg because they move too quick/WW is ineffective on) // 4304 = shielder fat dudes in act 3 hell zones private static readonly HashSet hashActorSNOWhirlwindIgnore = new HashSet { 4304, 5984, 5985, 5987, 5988, }; // Very fast moving mobs (eg wasps), for special skill-selection decisions // 5212 = act 2 wasps private static readonly HashSet hashActorSNOFastMobs = new HashSet { 5212 }; // A list of crappy "summoned mobs" we should always ignore unless they are very close to us, eg "grunts", summoned skeletons etc. private static readonly HashSet hashActorSNOShortRangeOnly = new HashSet { 4084, 4085, 5395, 144315, }; // Dictionary for priorities, like the skeleton summoner cos it keeps bringing new stuff private static readonly Dictionary dictActorSNOPriority = new Dictionary { // Wood wraiths all this line (495 & 496 & 6572 & 139454 & 139456 & 170324 & 170325) {495, 901}, {496, 901}, {6572, 901}, {139454, 901}, {139456, 901}, {170324, 901}, {170325, 901}, //intell -- added 4099 (act 2 fallen shaman) // Fallen Shaman prophets goblin Summoners (365 & 4100) {365, 1901}, {4099, 1901}, {4100, 1901}, // The annoying grunts summoned by the above {4084, -401}, // Wretched mothers that summon zombies in act 1 (6639) {6639, 951}, // Fallen lunatic (4095) {4095, 2999}, // Pestilence hands (4738) {4738, 1901}, // Maghda and her minions {6031, 801}, {178512, 901}, // Cydaea boss (95250) {95250, 1501}, //intell //Cydaea Spiderlings (137139) {137139, -301}, // GoatMutantshaman Elite (4304) {4304, 999}, // GoatMutantshaman (4300) {4300, 901}, // Succubus (5508) {5508, 801}, // skeleton summoners (5387, 5388, 5389) {5387, 951}, {5388, 951}, {5389, 951}, // Weak skeletons summoned by the above {5395, -401}, // Wasp/Bees - Act 2 annoying flyers (5212) {5212, 751}, // Dark summoner - summons the helion dogs (6035) {6035, 501}, // Dark berserkers - has the huge damaging slow hit (6052) {6052, 501}, // The giant undead fat grotesques that explode in act 1 (3847) {3847, 401}, // Hive pods that summon flyers in act 1 (4152, 4153, 4154) {4152, 901}, {4153, 901}, {4154, 901}, // Totems in act 1 that summon the ranged goatmen (166452) {166452, 901}, // Totems in act 1 dungeons that summon skeletons (176907) {176907, 901}, // Uber Bosses - Skeleton King {255929}, Maghda {256189} & Pets {219702} which must be killed first {255929, 2999}, {219702, 1999}, {256189, 999}, // Uber Bosses - Zoltun Kulle {256508} & Siegebreaker {256187} // Siegebreaker removed so the focus remains on Zoltun Kulle until he is dead {256508, 2999}, //{256508, 2999}, {256187, 1899}, // Uber Bosses - Ghom {256709} & Rakanot {256711} {256709, 2999}, {256711, 1899}, }; // A flag to say whether any NONE-hashActorSNOWhirlwindIgnore things are around private static bool bAnyNonWWIgnoreMobsInRange = false; // A list of all known SNO's of treasure goblins/bandits etc. private static readonly HashSet hashActorSNOGoblins = new HashSet { 5984, 5985, 5987, 5988 }; // A list of ranged mobs that should be attacked even if they are outside of the routines current kill radius //4286 = act 1 goatman ranged; 5388 = act 1 skeleton summoner; 365, 4100, 4099 = fallen shamans; 4300, 4304 = goat shaman; 4738 = pestilence; //4299 = goat ranged; 62736, 130794 = demon flyer; 5508 = succubus private static readonly HashSet hashActorSNORanged = new HashSet { 365, 4099, 4100, 4304, 4300, 4738, 4299, 62736, 130794, 5508, 5388, 4286, }; // A list of bosses in the game, just to make CERTAIN they are treated as elites private static readonly HashSet hashBossSNO = new HashSet { // Siegebreaker (96192), Azmodan (89690), Cydea (95250), Heart-thing (193077), Kulle (80509), Small Belial (220160), Big Belial (3349), Diablo 1 (114917), terror Diablo (133562) 96192, 89690, 95250, 193077, 80509, 220160, 3349, 114917, 133562, 255929, 256711, 256508, 256187, 256189, 256709, // Diablo shadow clones (needs all of them, there is a male & female version of each class!) 144001, 144003, 143996, 143994, // Jondar (act 1 dungeons) 86624, }; // IGNORE LIST / BLACKLIST - for units / monsters / npcs // Special blacklist for things like ravens, templar/scoundrel/enchantress in town, witch-doctor summons, tornado-animations etc. etc. that should never be attacked // Note: This is a MONSTER blacklist - so only stuff that needs to be ignored by the combat-engine. An "object" blacklist is further down! //intell -- added oldNecromancer (4798) and his Skeleton_A_Necromancer (183892) private static readonly HashSet hashActorSNOIgnoreBlacklist = new HashSet { 5840, 111456, 5013, 5014, 205756, 205746, 4182, 4183, 4644, 4062, 4538, 52693, 162575, 2928, 51291, 51292, 96132, 90958, 90959, 80980, 51292, 51291, 2928, 3546, 129345, 81857, 138428, 81857, 60583, 170038, 174854, 190390, 194263, 174900, 87189, 90072, 107031, 106584, 186130, 187265, 201426, 201242, 200969, 201423, 201438, 201464, 201454, 108012, 103279, 89578, 74004, 84531, 84538, 89579, 190492, 209133, 6318, 107705, 105681, 89934, 89933, 182276, 117574, 182271, 182283, 182278, 128895, 81980, 82111, 81226, 81227, 107067, 106749, 107107, 107112, 106731, 107752, 107829, 90321, 107828, 121327, 185391, 249320, 81232, 81231, 81239, 81515, 210433, 195414, 80758, 80757, 80745, 81229, 81230, 82109, 83024, 83025, 82972, 83959, 249190, 251396, 138472, 118260, 200226, 192654, 245828, 215103, 132951, 217508, 199998, 199997, 114527, 245910, 169123, 123885, 169890, 168878, 169891, 169077, 169904, 169907, 169906, 169908, 169905, 169909, 179780, 179778, 179772, 179779, 179776, 122305, 110959, 103235, 103215, 105763, 103217, 51353, 80140, 4176, 178664, 173827, 133741, 159144, 181748, 159098, 206569, 200706, 5895, 5896, 5897, 5899, 4686, 87037, 85843, 103919, 249338, 251416, 249192, 80812, 4798, 183892,196899, 196900, 196903, 223333, 220636, 218951, 245838, //blackhawk 3384, //bone pile }; // Three special lists used purely for checking for the existance of a player's summoned mystic ally, gargantuan, or zombie dog private static readonly HashSet hashMysticAlly = new HashSet { 169123, 123885, 169890, 168878, 169891, 169077, 169904, 169907, 169906, 169908, 169905, 169909 }; private static readonly HashSet hashGargantuan = new HashSet { 179780, 179778, 179772, 179779, 179776, 122305 }; private static readonly HashSet hashZombie = new HashSet { 110959, 103235, 103215, 105763, 103217, 51353 }; private static readonly HashSet hashDHPets = new HashSet { 178664, 173827, 133741, 159144, 181748, 159098 }; // ********************************************************************************************** // ***** World-object dictionaries eg large object lists, ignore lists etc. ***** // ********************************************************************************************** // A list of SNO's to *FORCE* to type: Item. (BE CAREFUL WITH THIS!). // 166943 = infernal key private static readonly HashSet hashForceSNOToItemList = new HashSet { 166943, }; // Interactable whitelist - things that need interacting with like special wheels, levers - they will be blacklisted for 30 seconds after one-use private static readonly HashSet hashSNOInteractWhitelist = new HashSet { 54908, 56686, 54850, 454, 211999, 52685, 54882, }; // NOTE: you don't NEED interactable SNO's listed here. But if they are listed here, *THIS* is the range at which your character will try to walk to within the object // BEFORE trying to actually "click it". Certain objects need you to get very close, so it's worth having them listed with low interact ranges private static readonly Dictionary dictInteractableRange = new Dictionary { {56686, 4}, {52685, 4}, {54850, 14}, {54882, 40}, {54908, 4} }; // 174900 = fire-spewers (demonic forge) in Act 3, 54908 = iron gates // Navigation obstacles for standard navigation down dungeons etc. to help DB movement // MAKE SURE you add the *SAME* SNO to the "size" dictionary below, and include a reasonable size (keep it smaller rather than larger) for the SNO. private static readonly HashSet hashSNONavigationObstacles = new HashSet { 174900, 191459, 54908 }; // Size of the navigation obstacles above (actual SNO list must be matching the above list!) public static readonly Dictionary dictSNONavigationSize = new Dictionary { {174900, 10}, {191459, 13}, {54908, 10} }; // Destructible things that are very large and need breaking at a bigger distance - eg logstacks, large crates, carts, etc. private static readonly Dictionary dictSNOExtendedDestructRange = new Dictionary { {2972, 10}, {80357, 16}, {116508, 10}, {113932, 8}, {197514, 18}, {108587, 8}, {108618, 8}, {108612, 8}, {116409, 18}, {121586, 18}, {195101, 10}, {195108, 25}, {170657, 8}, {181228, 10}, {211959, 25}, {210418, 25}, {174496, 8}, {193963, 10}, {159066, 12}, {160570, 12}, {55325, 14}, {5718, 14}, {5909, 10}, {5792, 8}, {108194, 8}, {129031, 20}, {192867, 8}, {155255, 8} }; // Destructible things that need targeting by a location instead of an ACDGUID (stuff you can't "click on" to destroy in-game) private static readonly HashSet hashDestructableLocationTarget = new HashSet { 170657, 116409, 121586, }; // Resplendent chest SNO list private static readonly HashSet hashSNOContainerResplendant = new HashSet { 62873, 95011, 81424, 108230, 111808, 111809, }; // Chests/average-level containers that deserve a bit of extra radius (ie - they are more worthwhile to loot than "random" misc/junk containers) private static readonly HashSet hashSNOContainerWhitelist = new HashSet { 62859, 62865, 62872, 78790, 79016, 94708, 96522, 130170, 108122, 111870, 111947, 213447, 213446, 51300, 179865, 109264, 212491, 210422, }; // IGNORE LIST / BLACKLIST - for world objects // World objects that should always be ignored - eg certain destructables, certain containers, etc. - anything handled as a "world object" rather than a monster private static readonly HashSet hashSNOIgnoreBlacklist = new HashSet { 163449, 78030, 2909, 58283, 58299, 58309, 58321, 87809, 88005, 90150, 91600, 97023, 97350, 97381, 72689, 121327, 54952, 54515, 3340, 122076, 123640, 60665, 60844, 78554, 86400, 86428, 81699, 86266, 86400, 110769, 192466, 211456, 6190, 80002, 104596, 58836, 104827, 74909, 6155, 6156, 6158, 6159, 75132, 181504, 91688, 3016, 3007, 3011, 3014, 130858, 131573, 214396, 182730, 226087, 141639, 206569, 15119, 54413, 54926, 2979, 56416, 53802, 5776, 3949, 108490, 52833, //bone pile //192867, }; // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Majority of variables and class/structs used etc. ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** GilesItemType, GilesBaseItemType and GilesObjectType - for readability etc. ***** // ********************************************************************************************** // Primary "lowest level" item type (eg EXACTLY what kind of item it is) public enum GilesItemType { Unknown, Axe, CeremonialKnife, HandCrossbow, Dagger, FistWeapon, Mace, MightyWeapon, Spear, Sword, Wand, TwoHandAxe, TwoHandBow, TwoHandDaibo, TwoHandCrossbow, TwoHandMace, TwoHandMighty, TwoHandPolearm, TwoHandStaff, TwoHandSword, StaffOfHerding, Mojo, Source, Quiver, Shield, Amulet, Ring, Belt, Boots, Bracers, Chest, Cloak, Gloves, Helm, Pants, MightyBelt, Shoulders, SpiritStone, VoodooMask, WizardHat, FollowerEnchantress, FollowerScoundrel, FollowerTemplar, CraftingMaterial, CraftTome, Ruby, Emerald, Topaz, Amethyst, SpecialItem, CraftingPlan, HealthPotion, Dye, HealthGlobe, InfernalKey, } // Base types, eg "one handed weapons" "armors" etc. public enum GilesBaseItemType { Unknown, WeaponOneHand, WeaponTwoHand, WeaponRange, Offhand, Armor, Jewelry, FollowerItem, Misc, Gem, HealthGlobe } // Generic object types - eg a monster, an item to pickup, a shrine to click etc. public enum GilesObjectType { Unit, Shrine, Interactable, Destructible, Barricade, Container, Item, Gold, Globe, Avoidance, Backtrack, Unknown } // ********************************************************************************************** // ***** Current cached player data ***** // ********************************************************************************************** // Just stores the data on YOU, well, your character's current status - for readability of those variables more than anything, but also caching public class GilesPlayerCache { public DateTime lastUpdatedPlayer { get; set; } public bool bIsIncapacitated { get; set; } public bool bIsRooted { get; set; } public bool bIsInTown { get; set; } public double dCurrentHealthPct { get; set; } public double dCurrentEnergy { get; set; } public double dCurrentEnergyPct { get; set; } public double dDiscipline { get; set; } public double dDisciplinePct { get; set; } public Vector3 vCurrentPosition { get; set; } public bool bWaitingForReserveEnergy { get; set; } public int iMyDynamicID { get; set; } public int iMyLevel { get; set; } public GilesPlayerCache(DateTime lastupdated, bool incapacitated, bool isrooted, bool isintown, double currenthealth, double currentenergy, double currentenergypct, double discipline, double disciplinepct, Vector3 currentpos, bool waitingreserve, int dynamicid, int mylevel) { lastUpdatedPlayer = lastupdated; bIsIncapacitated = incapacitated; bIsRooted = isrooted; bIsInTown = isintown; dCurrentHealthPct = currenthealth; dCurrentEnergy = currentenergy; dCurrentEnergyPct = currentenergypct; dDiscipline = discipline; dDisciplinePct = disciplinepct; vCurrentPosition = currentpos; bWaitingForReserveEnergy = waitingreserve; iMyDynamicID = dynamicid; iMyLevel = mylevel; } } // ********************************************************************************************** // ***** GilesObject type used to cache all data ***** // ********************************************************************************************** // Let's me create an object list with ONLY the data I need read from D3 memory, and then read from this while // Handling movement and interaction with the target - whether the target is a shrine, an item or a monster // Completely minimizing the D3 memory reads to the bare minimum public class GilesObject { // Generic stuff applicable to all objects public GilesObjectType ThisGilesObjectType { get; set; } public double dThisWeight { get; set; } public Vector3 vThisPosition { get; set; } public float fCentreDistance { get; set; } public float fRadiusDistance { get; set; } public string sThisInternalName { get; set; } public int iThisACDGUID { get; set; } public int iThisRActorGuid { get; set; } public int iThisDynamicID { get; set; } public int iThisBalanceID { get; set; } public int iThisActorSNO { get; set; } // Item/gold/other stuff public int iThisLevel { get; set; } public int iThisGoldAmount { get; set; } public bool bThisOneHanded { get; set; } public ItemQuality ThisQuality { get; set; } public ItemType ThisDBItemType { get; set; } public FollowerType ThisFollowerType { get; set; } public GilesItemType ThisGilesItemType { get; set; } // Monster/unit stuff public bool bThisElite { get; set; } public bool bThisRare { get; set; } public bool bThisUnique { get; set; } public bool bThisMinion { get; set; } public bool bThisTreasureGoblin { get; set; } public bool bThisEliteRareUnique { get; set; } public bool bThisBoss { get; set; } public bool bThisAttackable { get; set; } public double iThisHitPoints { get; set; } public float fThisRadius { get; set; } public bool bForceLeapAgainst { get; set; } public MonsterSize unit_thisMonsterSize { get; set; } // A reference to the original object for fast updates public DiaUnit unit_thisDiaUnit { get; set; } public GilesObject(Vector3 thisposition, GilesObjectType thisobjecttype = GilesObjectType.Unknown, double thisweight = 0, float thisdistance = 0f, float thisradiusdistance = 0f, string thisinternalname = "", int thisacdguid = -1, int thisractorguid = -1, int thisdynamicid = -1, int thisbalanceid = -1, int thisactorsno = -1, int thislevel = -1, int thisgold = -1, bool thisonehand = true, ItemQuality thisquality = ItemQuality.Invalid, ItemType thisitemtype = ItemType.Unknown, FollowerType thisfollowertype = FollowerType.None, GilesItemType thisgilestype = GilesItemType.Unknown, bool thiselite = false, bool thisrare = false, bool thisunique = false, bool thisminion = false, bool thistreasure = false, bool thisboss = false, bool thisattackable = false, double thishitpoints = 0d, float thisradius = 0f, MonsterSize thismonstersize = MonsterSize.Unknown, DiaUnit thisunit = null, bool uniquerareelite = false, bool forceleap = false) { vThisPosition = thisposition; ThisGilesObjectType = thisobjecttype; dThisWeight = thisweight; fCentreDistance = thisdistance; fRadiusDistance = thisradiusdistance; sThisInternalName = thisinternalname; iThisACDGUID = thisacdguid; iThisRActorGuid = thisractorguid; iThisDynamicID = thisdynamicid; iThisBalanceID = thisbalanceid; iThisActorSNO = thisactorsno; iThisLevel = thislevel; iThisGoldAmount = thisgold; bThisOneHanded = thisonehand; ThisQuality = thisquality; ThisDBItemType = thisitemtype; ThisFollowerType = thisfollowertype; ThisGilesItemType = thisgilestype; bThisElite = thiselite; bThisRare = thisrare; bThisUnique = thisunique; bThisMinion = thisminion; bThisTreasureGoblin = thistreasure; bThisEliteRareUnique = uniquerareelite; bThisBoss = thisboss; bThisAttackable = thisattackable; iThisHitPoints = thishitpoints; fThisRadius = thisradius; unit_thisMonsterSize = thismonstersize; unit_thisDiaUnit = thisunit; bForceLeapAgainst = forceleap; } // For cloning the object (and not simply referencing it) public GilesObject Clone() { GilesObject newGilesObject = new GilesObject(vThisPosition, ThisGilesObjectType, dThisWeight, fCentreDistance, fRadiusDistance, sThisInternalName, iThisACDGUID, iThisRActorGuid, iThisDynamicID, iThisBalanceID, iThisActorSNO, iThisLevel, iThisGoldAmount, bThisOneHanded, ThisQuality, ThisDBItemType, ThisFollowerType, ThisGilesItemType, bThisElite, bThisRare, bThisUnique, bThisMinion, bThisTreasureGoblin, bThisBoss, bThisAttackable, iThisHitPoints, fThisRadius, unit_thisMonsterSize, unit_thisDiaUnit, bThisEliteRareUnique, bForceLeapAgainst); return newGilesObject; } } // ********************************************************************************************** // ***** GilesCachedACDItem - Special caching class to help with backpack-item handling ***** // ********************************************************************************************** // So we can make an object, read all item stats from a backpack item *ONCE*, then store it here while my behavior trees process everything // Preventing any need for calling D3 memory again after the initial read (every D3 memory read is a chance for a DB crash/item mis-read/stuck!) public class GilesCachedACDItem { public string ThisInternalName { get; set; } public string ThisRealName { get; set; } public int ThisLevel { get; set; } public ItemQuality ThisQuality { get; set; } public int ThisGoldAmount { get; set; } public int ThisBalanceID { get; set; } public int ThisDynamicID { get; set; } public float ThisWeaponDPS { get; set; } public bool ThisOneHanded { get; set; } public DyeType ThisDyeType { get; set; } public ItemType ThisDBItemType { get; set; } public FollowerType ThisFollowerType { get; set; } public bool IsUnidentified { get; set; } public int ThisItemStackQuantity { get; set; } public float Dexterity { get; set; } public float Intelligence { get; set; } public float Strength { get; set; } public float Vitality { get; set; } public float LifePercent { get; set; } public float LifeOnHit { get; set; } public float LifeSteal { get; set; } public float HealthPerSecond { get; set; } public float MagicFind { get; set; } public float GoldFind { get; set; } public float MovementSpeed { get; set; } public float PickUpRadius { get; set; } public float Sockets { get; set; } public float CritPercent { get; set; } public float CritDamagePercent { get; set; } public float AttackSpeedPercent { get; set; } public float MinDamage { get; set; } public float MaxDamage { get; set; } public float BlockChance { get; set; } public float Thorns { get; set; } public float ResistAll { get; set; } public float ResistArcane { get; set; } public float ResistCold { get; set; } public float ResistFire { get; set; } public float ResistHoly { get; set; } public float ResistLightning { get; set; } public float ResistPhysical { get; set; } public float ResistPoison { get; set; } public float WeaponDamagePerSecond { get; set; } public float ArmorBonus { get; set; } public float MaxDiscipline { get; set; } public float MaxMana { get; set; } public float ArcaneOnCrit { get; set; } public float ManaRegen { get; set; } public float GlobeBonus { get; set; } public ACDItem ACDItem { get; set; } public GilesCachedACDItem(string internalname, string realname, int level, ItemQuality quality, int goldamount, int balanceid, int dynamicid, float dps, bool onehanded, DyeType dyetype, ItemType dbitemtype, FollowerType dbfollowertype, bool unidentified, int stackquantity, ItemStats thesestats, ACDItem item) { ThisInternalName = internalname; ThisRealName = realname; ThisLevel = level; ThisQuality = quality; ThisGoldAmount = goldamount; ThisBalanceID = balanceid; ThisDynamicID = dynamicid; ThisWeaponDPS = dps; ThisOneHanded = onehanded; ThisDyeType = dyetype; ThisDBItemType = dbitemtype; ThisFollowerType = dbfollowertype; IsUnidentified = unidentified; ThisItemStackQuantity = stackquantity; Dexterity = thesestats.Dexterity; Intelligence = thesestats.Intelligence; Strength = thesestats.Strength; Vitality = thesestats.Vitality; LifePercent = thesestats.LifePercent; LifeOnHit = thesestats.LifeOnHit; LifeSteal = thesestats.LifeSteal; HealthPerSecond = thesestats.HealthPerSecond; MagicFind = thesestats.MagicFind; GoldFind = thesestats.GoldFind; MovementSpeed = thesestats.MovementSpeed; PickUpRadius = thesestats.PickUpRadius; Sockets = thesestats.Sockets; CritPercent = thesestats.CritPercent; CritDamagePercent = thesestats.CritDamagePercent; AttackSpeedPercent = thesestats.AttackSpeedPercent; MinDamage = thesestats.MinDamage; MaxDamage = thesestats.MaxDamage; BlockChance = thesestats.BlockChance; Thorns = thesestats.Thorns; ResistAll = thesestats.ResistAll; ResistArcane = thesestats.ResistArcane; ResistCold = thesestats.ResistCold; ResistFire = thesestats.ResistFire; ResistHoly = thesestats.ResistHoly; ResistLightning = thesestats.ResistLightning; ResistPhysical = thesestats.ResistPhysical; ResistPoison = thesestats.ResistPoison; WeaponDamagePerSecond = thesestats.WeaponDamagePerSecond; ArmorBonus = thesestats.ArmorBonus; MaxDiscipline = thesestats.MaxDiscipline; MaxMana = thesestats.MaxMana; ArcaneOnCrit = thesestats.ArcaneOnCrit; ManaRegen = thesestats.ManaRegen; GlobeBonus = thesestats.HealthGlobeBonus; ACDItem = item; } } // ********************************************************************************************** // ***** Giles Power - used when picking a power to use to cache where/how to use it ***** // ********************************************************************************************** public class GilesPower { public SNOPower powerThis { get; set; } public float iMinimumRange { get; set; } public Vector3 vTargetLocation { get; set; } public int iTargetWorldID { get; set; } public int iTargetGUID { get; set; } public int iForceWaitLoopsBefore { get; set; } public int iForceWaitLoopsAfter { get; set; } public bool bWaitWhileAnimating { get; set; } public GilesPower(SNOPower thispower, float thisrange, Vector3 thisloc, int thisworld, int thisguid, int waitloops, int afterloops, bool repeat) { powerThis = thispower; iMinimumRange = thisrange; vTargetLocation = thisloc; iTargetWorldID = thisworld; iTargetGUID = thisguid; iForceWaitLoopsBefore = waitloops; iForceWaitLoopsAfter = afterloops; bWaitWhileAnimating = repeat; } } // ********************************************************************************************** // ***** Obstacles for quick mapping of paths etc. ***** // ********************************************************************************************** public class GilesObstacle { public Vector3 vThisLocation { get; set; } public float fThisRadius { get; set; } public int iThisSNOID { get; set; } public double dThisWeight { get; set; } public GilesObstacle(Vector3 thislocation, float thisradius, int thissnoid, double thisweight = 0) { vThisLocation = thislocation; fThisRadius = thisradius; iThisSNOID = thissnoid; dThisWeight = thisweight; } } // ********************************************************************************************** // ***** Super Special Giles Sauce Data Caching ***** // ********************************************************************************************** public class GilesGameBalanceDataCache { public int iThisItemLevel { get; set; } public ItemType thisItemType { get; set; } public bool bThisOneHand { get; set; } public FollowerType thisFollowerType { get; set; } public GilesGameBalanceDataCache(int itemlevel, ItemType itemtype, bool onehand, FollowerType followertype) { iThisItemLevel = itemlevel; thisItemType = itemtype; bThisOneHand = onehand; thisFollowerType = followertype; } } // ********************************************************************************************** // ***** Item Stats Class and Variables - for the detailed item drop/pickup etc. stats ***** // ********************************************************************************************** private class GilesItemStats { public double iTotal { get; set; } public double[] iTotalPerQuality { get; set; } public double[] iTotalPerLevel { get; set; } public double[,] iTotalPerQPerL { get; set; } public double iTotalPotions { get; set; } public double[] iPotionsPerLevel { get; set; } public double iTotalGems { get; set; } public double[] iGemsPerType { get; set; } public double[] iGemsPerLevel { get; set; } public double[,] iGemsPerTPerL { get; set; } public double iTotalInfernalKeys { get; set; } public GilesItemStats(double total, double[] totalperq, double[] totalperl, double[,] totalperqperl, double totalpotions, double[] potionsperlevel, double totalgems, double[] gemspertype, double[] gemsperlevel, double[,] gemspertperl, double totalkeys) { iTotal = total; iTotalPerQuality = totalperq; iTotalPerLevel = totalperl; iTotalPerQPerL = totalperqperl; iTotalPotions = totalpotions; iPotionsPerLevel = potionsperlevel; iTotalGems = totalgems; iGemsPerType = gemspertype; iGemsPerLevel = gemsperlevel; iGemsPerTPerL = gemspertperl; iTotalInfernalKeys = totalkeys; } } // ********************************************************************************************** // ***** Current plugin settings ***** // ********************************************************************************************** public class GilesSettings { public bool bUseGilesFilters { get; set; } public int iMinimumGoldStack { get; set; } public double iNeedPointsToKeepJewelry { get; set; } public double iNeedPointsToKeepArmor { get; set; } public double iNeedPointsToKeepWeapon { get; set; } public int iFilterPotions { get; set; } public int iFilterLegendary { get; set; } public int iFilterBlueWeapons { get; set; } public int iFilterYellowWeapons { get; set; } public int iFilterBlueArmor { get; set; } public int iFilterYellowArmor { get; set; } public int iFilterBlueJewelry { get; set; } public int iFilterYellowJewelry { get; set; } public int iFilterGems { get; set; } public int iFilterMisc { get; set; } public int iFilterPotionLevel { get; set; } public bool bGemsEmerald { get; set; } public bool bGemsAmethyst { get; set; } public bool bGemsTopaz { get; set; } public bool bGemsRuby { get; set; } public bool bSalvageJunk { get; set; } public bool bPickupCraftTomes { get; set; } public bool bPickupPlans { get; set; } public bool bPickupFollower { get; set; } // Combat replacer config settings public bool bEnableBacktracking { get; set; } public bool bEnableAvoidance { get; set; } public bool bEnableGlobes { get; set; } public bool bEnableCriticalMass { get; set; } public int iTreasureGoblinPriority { get; set; } public double iMonsterKillRange { get; set; } public bool bOutOfCombatMovementPowers { get; set; } public bool bExtendedKillRange { get; set; } public bool bSelectiveWhirlwind { get; set; } public bool bWaitForWrath { get; set; } public bool bGoblinWrath { get; set; } public bool bFuryDumpWrath { get; set; } public bool bFuryDumpAlways { get; set; } public int iKillLootDelay { get; set; } public int iDHVaultMovementDelay { get; set; } public bool bMonkInnaSet { get; set; } public bool bWaitForArchon { get; set; } public bool bKiteOnlyArchon { get; set; } public bool bWrath90Seconds { get; set; } // World object handler config settings public bool bIgnoreAllShrines { get; set; } public bool bIgnoreCorpses { get; set; } public double iContainerOpenRange { get; set; } public double iDestructibleAttackRange { get; set; } // Performance stuff public bool bEnableTPS { get; set; } public double iTPSAmount { get; set; } public bool bDebugInfo { get; set; } // Enable Prowl public bool bEnableProwl { get; set; } public bool bEnableAndroid { get; set; } public bool bEnableLegendaryNotifyScore { get; set; } public double iNeedPointsToNotifyJewelry { get; set; } public double iNeedPointsToNotifyArmor { get; set; } public double iNeedPointsToNotifyWeapon { get; set; } // Enable Email public bool bEnableEmail { get; set; } // Log stuck points public bool bLogStucks { get; set; } // Enable unstucker public bool bEnableUnstucker { get; set; } public bool bEnableProfileReloading { get; set; } // Pot & Globe usage public double dEmergencyHealthPotionBarb { get; set; } public double dEmergencyHealthGlobeBarb { get; set; } public double dEmergencyHealthPotionMonk { get; set; } public double dEmergencyHealthGlobeMonk { get; set; } public double dEmergencyHealthPotionWiz { get; set; } public double dEmergencyHealthGlobeWiz { get; set; } public double dEmergencyHealthPotionWitch { get; set; } public double dEmergencyHealthGlobeWitch { get; set; } public double dEmergencyHealthPotionDemon { get; set; } public double dEmergencyHealthGlobeDemon { get; set; } public int iKiteDistanceBarb { get; set; } public int iKiteDistanceWiz { get; set; } public int iKiteDistanceWitch { get; set; } public int iKiteDistanceDemon { get; set; } // Defaults For All Settings can be set here public GilesSettings(bool usegilesfilters = true, int mingoldstack = 300, double pointsj = 15000, double pointsa = 16000, double pointsw = 70000, int filterpot = 2, int filterbluew = 63, int filteryelloww = 61, int filterbluea = 0, int filteryellowa = 61, int filterbluej = 0, int filteryellowj = 54, int filtergems = 60, int filtermisc = 60, int filterpotlevel = 60, bool emerald = true, bool amethyst = false, bool topaz = false, bool ruby = true, bool salvage = false, bool pickupcrafttomes = false, bool pickupplans = true, bool pickupfollower = true, bool enablebacktracking = false, bool enableavoidance = true, bool enableglobes = true, bool enablecriticalmass = false, int treasuregoblins = 2, double monsterkillrange = 18, bool outofcombatmovement = true, bool ignoreshrines = false, bool ignorecorpses = true, double containeropen = 15, double destructibleattack = 6, bool enabletps = false, double tpsamount = 10, bool logstucks = false, bool enableunstucker = true, bool extendedrange = true, bool selectiveww = false, bool debuginfo = false, double healthpot0 = 0.42, double healthpot1 = 0.46, double healthpot2 = 0.7, double healthpot3 = 0.7, double healthpot4 = 0.7, double healthglobe0 = 0.55, double healthglobe1 = 0.6, double healthglobe2 = 0.8, double healthglobe3 = 0.8, double healthglobe4 = 0.8, bool enableprowl = false, bool enableandroid = false, double pointsnotifyj = 28000, double pointsnotifya = 30000, double pointsnotifyw = 100000, int killlootdelay = 800, int vaultdelay = 400, bool waitforwrath = true, bool goblinwrath = true, bool furydumpwrath = true, bool furydumpalways = false, int filterlegendary = 1, bool profilereload = false, bool monkinna = false, int kitebarb = 0, int kitewiz = 0, int kitewitch = 0, int kitedemon = 0, bool waitarchon = true, bool kitearchon = false, bool wrath90 = false, bool enableEmail = false, bool bEnableLegendaryNotifyScore = false) { bUseGilesFilters = usegilesfilters; iMinimumGoldStack = mingoldstack; iNeedPointsToKeepJewelry = pointsj; iNeedPointsToKeepArmor = pointsa; iNeedPointsToKeepWeapon = pointsw; iFilterPotions = filterpot; iFilterLegendary = filterlegendary; iFilterBlueWeapons = filterbluew; iFilterYellowWeapons = filteryelloww; iFilterBlueArmor = filterbluea; iFilterYellowArmor = filteryellowa; iFilterBlueJewelry = filterbluej; iFilterYellowJewelry = filteryellowj; iFilterGems = filtergems; iFilterMisc = filtermisc; iFilterPotionLevel = filterpotlevel; bGemsEmerald = emerald; bGemsAmethyst = amethyst; bGemsTopaz = topaz; bGemsRuby = ruby; bSalvageJunk = salvage; bPickupCraftTomes = pickupcrafttomes; bPickupPlans = pickupplans; bPickupFollower = pickupfollower; // Combat replacer config settings bEnableBacktracking = enablebacktracking; bEnableAvoidance = enableavoidance; bEnableGlobes = enableglobes; bEnableCriticalMass = enablecriticalmass; iTreasureGoblinPriority = treasuregoblins; iMonsterKillRange = monsterkillrange; bOutOfCombatMovementPowers = outofcombatmovement; bExtendedKillRange = extendedrange; bSelectiveWhirlwind = selectiveww; bWaitForWrath = waitforwrath; bGoblinWrath = goblinwrath; bFuryDumpWrath = furydumpwrath; bFuryDumpAlways = furydumpalways; iKillLootDelay = killlootdelay; iDHVaultMovementDelay = vaultdelay; bMonkInnaSet = monkinna; bWrath90Seconds = wrath90; // World object handler config settings bIgnoreAllShrines = ignoreshrines; bIgnoreCorpses = ignorecorpses; iContainerOpenRange = containeropen; iDestructibleAttackRange = destructibleattack; // Advanced stuff bEnableTPS = enabletps; iTPSAmount = tpsamount; bLogStucks = logstucks; bEnableUnstucker = enableunstucker; bEnableProfileReloading = profilereload; bDebugInfo = debuginfo; // Enable prowl bEnableProwl = enableprowl; bEnableAndroid = enableandroid; this.bEnableLegendaryNotifyScore = bEnableLegendaryNotifyScore; iNeedPointsToNotifyJewelry = pointsnotifyj; iNeedPointsToNotifyArmor = pointsnotifya; iNeedPointsToNotifyWeapon = pointsnotifyw; // Enable email this.bEnableEmail = enableEmail; // Globes and pots dEmergencyHealthPotionBarb = healthpot0; dEmergencyHealthPotionMonk = healthpot1; dEmergencyHealthPotionWiz = healthpot2; dEmergencyHealthPotionWitch = healthpot3; dEmergencyHealthPotionDemon = healthpot4; dEmergencyHealthGlobeBarb = healthglobe0; dEmergencyHealthGlobeMonk = healthglobe1; dEmergencyHealthGlobeWiz = healthglobe2; dEmergencyHealthGlobeWitch = healthglobe3; dEmergencyHealthGlobeDemon = healthglobe4; iKiteDistanceBarb = kitebarb; iKiteDistanceWiz = kitewiz; iKiteDistanceWitch = kitewitch; iKiteDistanceDemon = kitedemon; bWaitForArchon = waitarchon; bKiteOnlyArchon = kitearchon; } } public static GilesSettings settings = new GilesSettings(); // ***************************************** // ***** End of config variables ***** // ***************************************** // ********************************************************************************************** // ***** End of classes, actual variables now ***** // ********************************************************************************************** // instantiating the interpreter for itemruleset private static Interpreter interpreter = new Interpreter(); // I create so many variables that it's a pain in the arse to categorize them // So I just throw them all here for quick searching, reference etc. // I've tried to make most variable names be pretty damned obvious what they are for! // I've also commented a lot of variables/sections of variables to explain what they are for, incase you are trying to work them all out! // A null location, may shave off the tiniest fraction of CPU time, but probably not. Still, I like using this variable! :D private static readonly Vector3 vNullLocation = Vector3.Zero; // Used for a global bot-pause private static bool bMainBotPaused = false; // Used to force-refresh dia objects at least once every XX milliseconds public static DateTime lastRefreshedObjects = DateTime.Today; // This object is used for the main handling - the "current target" etc. as selected by the target-selecter, whether it be a unit, an item, a shrine, anything // It's cached data using my own class, so I never need to hit D3 memory to "re-check" the data or to call an interact request or anything private static GilesObject targetCurrent = null; // A flag to indicate whether we have a new target from the overlord (decorator) or not, in which case don't refresh targets again this first loop private static bool bWholeNewTarget = false; // A flag to indicate if we should pick a new power/ability to use or not private static bool bPickNewAbilities = false; // Flag used to indicate if we are simply waiting for a power to go off - so don't do any new target checking or anything private static bool bWaitingForPower = false; // And a special post-use pause private static bool bWaitingAfterPower = false; // If we are waiting before popping a potion private static bool bWaitingForPotion = false; // Status text for DB main window status private static string sStatusText = ""; // A flag to indicate if we just entered or just left archon form (and so to force-update the hotbar) private static bool bRefreshHotbarAbilities = false; private static bool bHasHadArchonbuff = false; // A "fake" object to send to target provider for stuck handlers etc. public static DiaObject thisFakeObject; // Timestamp of when our position was last measured as changed private static DateTime lastMovedDuringCombat = DateTime.Today; // The following three dictionaries are special caches - they start empty but fill up with data that gets re-used while the bot runs, saving on D3 memory hits // Monster type caching private static Dictionary dictionaryStoredMonsterTypes = new Dictionary(); // Monster size caching private static Dictionary dictionaryStoredMonsterSizes = new Dictionary(); // Temporarily ignore this object for *THREE* cycles private static int iIgnoreThisRactorGUID = 0; private static int iIgnoreThisForLoops = 0; // Holds all of the player's current info handily cached, updated once per loop with a minimum timer on updates to save D3 memory hits public static GilesPlayerCache playerStatus = new GilesPlayerCache(DateTime.Today, false, false, false, 0d, 0d, 0d, 0d, 0d, vNullLocation, false, 0, 1); // A list of obstacles for the navigation handler public static HashSet hashNavigationObstacleCache = new HashSet(); // Related to the profile reloaded when restarting games, to pick the FIRST profile. // Also storing a list of all profiles, for experimental reasons/incase I want to use them down the line public static List listProfilesLoaded = new List(); public static string sLastProfileSeen = ""; public static string sFirstProfileSeen = ""; // A list of small areas covering zones we move through while fighting to help our custom move-handler skip ahead waypoints public static HashSet hashSkipAheadAreaCache = new HashSet(); public static DateTime lastAddedLocationCache = DateTime.Today; public static Vector3 vLastRecordedLocationCache = Vector3.Zero; public static bool bSkipAheadAGo = false; // A list of all monsters and their positions, so we don't try to walk through them during avoidance private static HashSet hashMonsterObstacleCache = new HashSet(); // A list of all current obstacles, to help avoid running through them when picking targets private static HashSet hashAvoidanceObstacleCache = new HashSet(); // Blacklist avoidance spots we failed to reach in time, for a period of time private static HashSet hashAvoidanceBlackspot = new HashSet(); private static DateTime lastClearedAvoidanceBlackspots = DateTime.Today; // A count for player mystic ally, gargantuans, and zombie dogs private static int iPlayerOwnedMysticAlly = 0; private static int iPlayerOwnedGargantuan = 0; private static int iPlayerOwnedZombieDog = 0; private static int iPlayerOwnedDHPets = 0; // These are a bunch of safety counters for how many times in a row we register having *NO* ability to select when we need one (eg all off cooldown) // After so many, give the player a friendly warning to check their skill/build setup private static int iNoAbilitiesAvailableInARow = 0; private static DateTime lastRemindedAboutAbilities = DateTime.Today; // Prowl API Key public static string sProwlAPIKey = ""; // Android API Key public static string sAndroidAPIKey = ""; // Email Settings public static string sEmailAddress = ""; public static string sEmailPassword = ""; public static string sBotName = ZetaDia.Service.CurrentHero.BattleTagName; // Last had any mob in range, for loot-waiting private static DateTime lastHadUnitInSights = DateTime.Today; // When we last saw a boss/elite etc. private static DateTime lastHadEliteUnitInSights = DateTime.Today; // Do we need to reset the debug bar after combat handling? private static bool bResetStatusText = false; // A list of "useonceonly" tags that have been triggered this xml profile public static HashSet hashUseOnceID = new HashSet(); public static Dictionary dictUseOnceID = new Dictionary(); // For the random ID tag public static Dictionary dictRandomID = new Dictionary(); // Death counts public static int iMaxDeathsAllowed = 0; public static int iDeathsThisRun = 0; // Force a target update after certain interactions private static bool bForceTargetUpdate = false; // This holds whether or not we want to prioritize a close-target, used when we might be body-blocked by monsters private static bool bForceCloseRangeTarget = false; // How many times a movement fails because of being "blocked" private static int iTimesBlockedMoving = 0; // how long to force close-range targets for private static int iMillisecondsForceCloseRange = 0; // Date time we were last told to stick to close range targets private static DateTime lastForcedKeepCloseRange = DateTime.Today; // The distance last loop, so we can compare to current distance to work out if we moved private static float iLastDistance = 0f; // Caching of the current primary target's health, to detect if we AREN'T damaging it for a period of time private static double iTargetLastHealth = 0f; // This is used so we don't use certain skills until we "top up" our primary resource by enough private static double iWaitingReservedAmount = 0d; // When did we last clear the temporary blacklist? private static DateTime dateSinceTemporaryBlacklistClear = DateTime.Today; // And the full blacklist? private static DateTime dateSinceBlacklistClear = DateTime.Today; // Store the date-time when we *FIRST* picked this target, so we can blacklist after X period of time targeting private static DateTime dateSincePickedTarget = DateTime.Today; // Total main loops so we can update things every XX loops private static int iCombatLoops = 0; // These values below are set on a per-class basis later on, so don't bother changing them here! These are the old default values private static double iEmergencyHealthPotionLimit = 0.46; private static double iEmergencyHealthGlobeLimit = 0.6; private static int iKiteDistance = 0; // These are blacklists used to blacklist objects/monsters/items either for the rest of the current game, or for a shorter period private static HashSet hashRGUIDTemporaryIgnoreBlacklist = new HashSet(); private static HashSet hashRGUIDIgnoreBlacklist = new HashSet(); // This is a blacklist that is cleared within 3 seconds of last attacking a destructible private static HashSet hashRGUIDDestructibleBlacklist = new HashSet(); private static DateTime lastDestroyedDestructible = DateTime.Today; private static bool bNeedClearDestructibles = false; // This is a blacklist that is cleared within 3 seconds of last attacking a destructible private static HashSet hashRGUIDTemporaryBlacklist = new HashSet(); private static DateTime lastTemporaryBlacklist = DateTime.Today; private static bool bNeedClearTemporaryBlacklist = false; // An ordered list of all of the backtrack locations to navigate through once we finish our current activities public static SortedList vBacktrackList = new SortedList(); public static int iTotalBacktracks = 0; // The number of loops to extend kill range for after a fight to try to maximize kill bonus exp etc. private static int iKeepKillRadiusExtendedFor = 0; // The number of loops to extend loot range for after a fight to try to stop missing loot private static int iKeepLootRadiusExtendedFor = 0; // Some avoidance related variables // Whether or not we need avoidance this target-search-loop private static bool bRequireAvoidance = false; // Whether or not there are projectiles to avoid private static bool bTravellingAvoidance = false; // When we last FOUND a safe spot private static DateTime lastFoundSafeSpot = DateTime.Today; private static Vector3 vlastSafeSpot = Vector3.Zero; // This lets us know if there is a target but it's in avoidance so we can just "stay put" until avoidance goes private static bool bStayPutDuringAvoidance = false; // This force-prevents avoidance for XX loops incase we get stuck trying to avoid stuff private static DateTime timeCancelledEmergencyMove = DateTime.Today; private static int iMillisecondsCancelledEmergencyMoveFor = 0; // Prevent spam-kiting too much - allow fighting between each kite movement private static DateTime timeCancelledKiteMove = DateTime.Today; private static int iMillisecondsCancelledKiteMoveFor = 0; // For if we have emergency teleport abilities available right now or not private static bool bHasEmergencyTeleportUp = false; // How many follower items were ignored, purely for item stat tracking private static int iTotalFollowerItemsIgnored = 0; // Variable to let us force new target creations immediately after a root private static bool bWasRootedLastTick = false; // Random variables used during item handling and town-runs private static int iItemDelayLoopLimit = 0; private static int iCurrentItemLoops = 0; private static bool bLoggedAnythingThisStash = false; private static bool bUpdatedStashMap = false; private static bool bLoggedJunkThisStash = false; private static string sValueItemStatString = ""; private static string sJunkItemStatString = ""; private static bool bTestingBackpack = false; // Stash mapper - it's an array representing every slot in your stash, true or false dictating if the slot is free or not private static bool[,] GilesStashSlotBlocked = new bool[7, 30]; private static bool bOutputItemScores = false; // Full Analysis SPAMS like hell. But useful for seeing how the score-calculator is adding/removing points // Really this is only for Giles to debug and improve the formula, users likely won't find this useful private const bool bFullAnalysis = false; // Teehee I typed "Anal" LOLOLOL // Variables used to actually hold powers the power-selector has picked to use, for buffing and main power use private static GilesPower powerBuff; private static GilesPower powerPrime; private static SNOPower powerLastSnoPowerUsed = SNOPower.None; // Two variables to stop DB from attempting any navigator movement mid-combat/mid-backtrack public static bool bDontMoveMeIAmDoingShit = false; public static bool bDontSpamOutofCombat = false; public static bool bOnlyTarget = false; // Target provider and core routine variables public static string sTrinityPluginPath = ""; private static string sTrinityConfigFile = ""; private static string sTrinityEmailConfigFile = ""; private static bool bSavingConfig = false; public static ActorClass iMyCachedActorClass = ActorClass.Invalid; private static bool bAnyChampionsPresent = false; private static bool bAnyTreasureGoblinsPresent = false; private static bool bAnyMobsInCloseRange = false; private static float iCurrentMaxKillRadius = 0f; private static float iCurrentMaxLootRadius = 0f; // Goblinney things private static bool bUseBerserker = false; private static bool bWaitingForSpecial = false; private static int iTotalNumberGoblins = 0; private static DateTime lastGoblinTime = DateTime.Today; // Variables relating to quick-reference of monsters within sepcific ranges (if anyone has suggestion for similar functionality with reduced CPU use, lemme know, but this is fast atm!) private static int[] iElitesWithinRange = new int[] { 0, 0, 0, 0, 0, 0, 0 }; private static int[] iAnythingWithinRange = new int[] { 0, 0, 0, 0, 0, 0, 0 }; private static bool bAnyBossesInRange = false; private const int RANGE_50 = 0; private const int RANGE_40 = 1; private const int RANGE_30 = 2; private const int RANGE_25 = 3; private const int RANGE_20 = 4; private const int RANGE_15 = 5; private const int RANGE_12 = 6; private const int RANGE_6 = 7; private static int iWithinRangeLastRend = 0; // Unique ID of mob last targetting when using rend private static int iACDGUIDLastRend = 0; // Unique ID of mob last targetting when using whirlwind private static int iACDGUIDLastWhirlwind = 0; private static bool bAlreadyMoving = false; private static Vector3 vLastMoveToTarget; private static float fLastDistanceFromTarget; private static DateTime lastMovementCommand = DateTime.Today; // Actual combat function variables private static bool bMappedPlayerAbilities = false; // Contains our apparent *CURRENT* hotbar abilities, cached in a fast hash public static HashSet hashPowerHotbarAbilities = new HashSet(); // Contains a hash of our LAST hotbar abilities before we transformed into archon (for quick and safe hotbar restoration) public static HashSet hashCachedPowerHotbarAbilities = new HashSet(); // A list and a dictionary for quick buff checking and buff references private static List listCachedBuffs = new List(); private static Dictionary dictCachedBuffs = new Dictionary(); // For "position-shifting" to navigate around obstacle SNO's private static Vector3 vShiftedPosition = Vector3.Zero; private static DateTime lastShiftedPosition = DateTime.Today; private static int iShiftPositionFor = 0; private static Vector3 vCurrentDestination; private static Vector3 vSideToSideTarget; private static DateTime lastChangedZigZag = DateTime.Today; private static Vector3 vPositionLastZigZagCheck = Vector3.Zero; public static int iCurrentWorldID = -1; public static GameDifficulty iCurrentGameDifficulty = GameDifficulty.Invalid; private const bool USE_COMBAT_ONLY = false; private const bool USE_ANY_TIME = true; private const bool SIGNATURE_SPAM = false; private const bool USE_SLOWLY = true; // Constants and variables used by the item-stats stuff private const int QUALITYWHITE = 0; private const int QUALITYBLUE = 1; private const int QUALITYYELLOW = 2; private const int QUALITYORANGE = 3; private static readonly string[] sQualityString = new string[4] { "White", "Magic", "Rare", "Legendary" }; private const int GEMRUBY = 0; private const int GEMTOPAZ = 1; private const int GEMAMETHYST = 2; private const int GEMEMERALD = 3; private static readonly string[] sGemString = new string[4] { "Ruby", "Topaz", "Amethyst", "Emerald" }; private static DateTime ItemStatsLastPostedReport = DateTime.Now; private static DateTime ItemStatsWhenStartedBot = DateTime.Now; private static bool bMaintainStatTracking = false; // Store items already logged by item-stats, to make sure no stats get doubled up by accident private static HashSet _hashsetItemStatsLookedAt = new HashSet(); private static HashSet _hashsetItemPicksLookedAt = new HashSet(); private static HashSet _hashsetItemFollowersIgnored = new HashSet(); // This dictionary stores attempted stash counts on items, to help detect any stash stucks on the same item etc. private static Dictionary _dictItemStashAttempted = new Dictionary(); // These objects are instances of my stats class above, holding identical types of data for two different things - one holds item DROP stats, one holds item PICKUP stats private static GilesItemStats ItemsDroppedStats = new GilesItemStats(0, new double[4], new double[64], new double[4, 64], 0, new double[64], 0, new double[4], new double[64], new double[4, 64], 0); private static GilesItemStats ItemsPickedStats = new GilesItemStats(0, new double[4], new double[64], new double[4, 64], 0, new double[64], 0, new double[4], new double[64], new double[4, 64], 0); private static DateTime TickInformation = DateTime.Now; // These constants are used for item scoring and stashing private const int DEXTERITY = 0; private const int INTELLIGENCE = 1; private const int STRENGTH = 2; private const int VITALITY = 3; private const int LIFEPERCENT = 4; private const int LIFEONHIT = 5; private const int LIFESTEAL = 6; private const int LIFEREGEN = 7; private const int MAGICFIND = 8; private const int GOLDFIND = 9; private const int MOVEMENTSPEED = 10; private const int PICKUPRADIUS = 11; private const int SOCKETS = 12; private const int CRITCHANCE = 13; private const int CRITDAMAGE = 14; private const int ATTACKSPEED = 15; private const int MINDAMAGE = 16; private const int MAXDAMAGE = 17; private const int BLOCKCHANCE = 18; private const int THORNS = 19; private const int ALLRESIST = 20; private const int RANDOMRESIST = 21; private const int TOTALDPS = 22; private const int ARMOR = 23; private const int MAXDISCIPLINE = 24; private const int MAXMANA = 25; private const int ARCANECRIT = 26; private const int MANAREGEN = 27; private const int GLOBEBONUS = 28; private const int TOTALSTATS = 29; // starts at 0, remember... 0-26 = 1-27! // Readable names of the above stats that get output into the trash/stash log files private static readonly string[] StatNames = new string[29] { "Dexterity", "Intelligence", "Strength", "Vitality", "Life %", "Life On Hit", "Life Steal %", "Life Regen", "Magic Find %", "Gold Find %", "Movement Speed %", "Pickup Radius", "Sockets", "Crit Chance %", "Crit Damage %", "Attack Speed %", "+Min Damage", "+Max Damage", "Total Block %", "Thorns", "+All Resist", "+Highest Single Resist", "DPS", "Armor", "Max Disc.", "Max Mana", "Arcane-On-Crit", "Mana Regen", "Globe Bonus"}; // Stores the apparent maximums of each stat for each item slot // Note that while these SHOULD be *actual* maximums for most stats - for things like DPS, these can just be more sort of "what a best-in-slot DPS would be" // Dex Int Str Vit Life% LOH Steal% LPS Magic% Gold% MSPD Rad. Sox Crit% CDam% ASPD Min+ Max+ Block% Thorn Allres Res DPS ARMOR Disc.Mana Arc. Regen Globes private static double[] iMaxWeaponOneHand = new double[29] { 320, 320, 320, 320, 0, 850, 2.8, 0, 0, 0, 0, 0, 1, 0, 85, 0, 0, 0, 0, 0, 0, 0, 1210, 0, 10, 150, 10, 14, 0 }; private static double[] iMaxWeaponTwoHand = new double[29] { 530, 530, 530, 530, 0, 1800, 5.8, 0, 0, 0, 0, 0, 1, 0, 170, 0, 0, 0, 0, 0, 0, 0, 1590, 0, 10, 119, 10, 14, 0 }; private static double[] iMaxWeaponRanged = new double[29] { 320, 320, 320, 320, 0, 850, 2.8, 0, 0, 0, 0, 0, 1, 0, 85, 0, 0, 0, 0, 0, 0, 0, 1410, 0, 0, 0, 0, 14, 0 }; private static double[] iMaxOffHand = new double[29] { 300, 300, 300, 300, 9, 0, 0, 234, 18, 20, 0, 0, 1, 8.5, 0, 15, 110, 402, 0, 979, 0, 0, 0, 0, 10, 119, 10, 11, 5977 }; private static double[] iMaxShield = new double[29] { 330, 330, 330, 330, 16, 0, 0, 342, 20, 25, 0, 0, 1, 10, 0, 0, 0, 0, 30, 2544, 80, 60, 0, 397, 0, 0, 0, 0, 12794 }; private static double[] iMaxRing = new double[29] { 200, 200, 200, 200, 12, 479, 0, 234, 20, 25, 0, 0, 1, 6, 50, 9, 36, 100, 0, 979, 80, 60, 0, 240, 0, 0, 0, 0, 5977 }; private static double[] iMaxAmulet = new double[29] { 350, 350, 350, 350, 16, 959, 0, 410, 45, 50, 0, 0, 1, 10, 100, 9, 36, 100, 0, 1712, 80, 60, 0, 360, 0, 0, 0, 0, 5977 }; private static double[] iMaxShoulders = new double[29] { 200, 200, 300, 200, 12, 0, 0, 342, 20, 25, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2544, 80, 60, 0, 265, 0, 0, 0, 0, 12794 }; private static double[] iMaxHelm = new double[29] { 200, 300, 200, 200, 12, 0, 0, 342, 20, 25, 0, 7, 1, 6, 0, 0, 0, 0, 0, 1454, 80, 60, 0, 397, 0, 0, 0, 0, 12794 }; private static double[] iMaxPants = new double[29] { 200, 200, 200, 300, 0, 0, 0, 342, 20, 25, 0, 7, 2, 0, 0, 0, 0, 0, 0, 1454, 80, 60, 0, 397, 0, 0, 0, 0, 12794 }; private static double[] iMaxGloves = new double[29] { 300, 300, 200, 200, 0, 0, 0, 342, 20, 25, 0, 7, 0, 10, 50, 9, 0, 0, 0, 1454, 80, 60, 0, 265, 0, 0, 0, 0, 12794 }; private static double[] iMaxChest = new double[29] { 200, 200, 200, 300, 12, 0, 0, 599, 20, 25, 0, 7, 3, 0, 0, 0, 0, 0, 0, 2544, 80, 60, 0, 397, 0, 0, 0, 0, 12794 }; private static double[] iMaxBracer = new double[29] { 200, 200, 200, 200, 0, 0, 0, 342, 20, 25, 0, 7, 0, 6, 0, 0, 0, 0, 0, 1454, 80, 60, 0, 265, 0, 0, 0, 0, 12794 }; private static double[] iMaxBoots = new double[29] { 300, 200, 200, 200, 0, 0, 0, 342, 20, 25, 12, 7, 0, 0, 0, 0, 0, 0, 0, 1454, 80, 60, 0, 265, 0, 0, 0, 0, 12794 }; private static double[] iMaxBelt = new double[29] { 200, 200, 300, 200, 12, 0, 0, 342, 20, 25, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2544, 80, 60, 0, 265, 0, 0, 0, 0, 12794 }; private static double[] iMaxCloak = new double[29] { 200, 200, 200, 300, 12, 0, 0, 410, 20, 25, 0, 7, 3, 0, 0, 0, 0, 0, 0, 2544, 70, 50, 0, 397, 10, 0, 0, 0, 12794 }; private static double[] iMaxMightyBelt = new double[29] { 200, 200, 300, 200, 12, 0, 3, 342, 20, 25, 0, 7, 0, 0, 0, 0, 0, 0, 0, 2544, 70, 50, 0, 265, 0, 0, 0, 0, 12794 }; private static double[] iMaxSpiritStone = new double[29] { 200, 300, 200, 200, 12, 0, 0, 342, 20, 25, 0, 7, 1, 6, 0, 0, 0, 0, 0, 1454, 70, 50, 0, 397, 0, 0, 0, 0, 12794 }; private static double[] iMaxVoodooMask = new double[29] { 200, 300, 200, 200, 12, 0, 0, 342, 20, 25, 0, 7, 1, 6, 0, 0, 0, 0, 0, 1454, 70, 50, 0, 397, 0, 119, 0, 11, 12794 }; private static double[] iMaxWizardHat = new double[29] { 200, 300, 200, 200, 12, 0, 0, 342, 20, 25, 0, 7, 1, 6, 0, 0, 0, 0, 0, 1454, 70, 50, 0, 397, 0, 0, 10, 0, 12794 }; private static double[] iMaxFollower = new double[29] { 300, 300, 300, 200, 0, 300, 0, 234, 0, 0, 0, 0, 0, 0, 55, 0, 0, 0, 0, 0, 50, 40, 0, 0, 0, 0, 0, 0, 0 }; // Stores the total points this stat is worth at the above % point of maximum // Note that these values get all sorts of bonuses, multipliers, and extra things applied in the actual scoring routine. These values are more of a "base" value. // Dex Int Str Vit Life% LOH Steal% LPS Magic% Gold% MSPD Rad Sox Crit% CDam% ASPD Min+ Max+ Block% Thorn Allres Res DPS ARMOR Disc. Mana Arc. Regen Globes private static double[] iWeaponPointsAtMax = new double[29] { 14000, 14000, 14000, 14000, 13000, 20000, 7000, 1000, 6000, 6000, 6000, 500, 16000, 15000, 15000, 0, 0, 0, 0, 1000, 11000, 0, 64000, 0, 10000, 8500, 8500, 10000, 8000 }; // Dex Int Str Vit Life% LOH Steal% LPS Magic% Gold% MSPD Rad. Sox Crit% CDam% ASPD Min+ Max+ Block% Thorn Allres Res DPS ARMOR Disc. Mana Arc. Regen Globes private static double[] iArmorPointsAtMax = new double[29] { 11000, 11000, 11000, 9500, 9000, 10000, 4000, 1200, 3000, 3000, 3500, 1000, 4300, 9000, 6100, 7000, 3000, 3000, 5000, 1200, 7500, 1500, 0, 5000, 4000, 3000, 3000, 6000, 5000 }; private static double[] iJewelryPointsAtMax = new double[29] { 11500, 11000, 11000, 10000, 8000, 11000, 4000, 1200, 4500, 4500, 3500, 1000, 3500, 7500, 6300, 6800, 800, 800, 5000, 1200, 7500, 1500, 0, 4500, 4000, 3000, 3000, 6000, 5000 }; // Some special values for score calculations // BonusThreshold is a percentage of the "max-stat possible", that the stat starts to get a multiplier on it's score. 1 means it has to be above 100% of the "max-stat" to get a multiplier (so only possible if the max-stat isn't ACTUALLY the max possible) // MinimumThreshold is a percentage of the "max-stat possible", that the stat will simply be ignored for being too low. eg if set to .5 - then anything less than 50% of the max-stat will be ignored. // MinimumPrimary is used for some stats only - and means that at least ONE primary stat has to be above that level, to get score. Eg magic-find has .5 - meaning any item without at least 50% of a max-stat primary, will ignore magic-find scoring. // Dex Int Str Vit Life% LOH Steal% LPS Magic% Gold% MSPD Radi Sox Crit% CDam% ASPD Min+ Max+ Block% Thorn Allres Res DPS ARMOR Disc. Mana Arc. Regen Globes private static double[] iBonusThreshold = new double[29] { .75, .75, .75, .75, .80, .80, .9, 1, 1, 1, .95, 1, 1, .70, .90, 1, .9, .9, .83, 1, .85, .95, .90, .90, 1, 1, 1, .9, 1 }; private static double[] iMinimumThreshold = new double[29] { .40, .40, .40, .30, .60, .45, .7, .7, .64, .64, .75, .8, .4, .40, .60, .40, .2, .2, .65, .6, .40, .55, .40, .80, .7, .7, .7, .7, .8 }; private static double[] iStatMinimumPrimary = new double[29] { 0, 0, 0, 0, 0, 0, 0, .2, .50, .50, .30, 0, 0, 0, 0, 0, .40, .40, .40, .40, .40, .40, 0, .40, .40, .40, .40, .4, .4 }; // These three lists are used to cache item data from the backpack when handling sales, salvaging and stashing // It completely minimized D3 <-> DB memory access, to reduce any random bugs/crashes etc. private static HashSet hashGilesCachedKeepItems = new HashSet(); private static HashSet hashGilesCachedSalvageItems = new HashSet(); private static HashSet hashGilesCachedSellItems = new HashSet(); // Whether to try forcing a vendor-run for custom reasons public static bool bGilesForcedVendoring = false; public static bool bWantToTownRun = false; private static bool bLastTownRunCheckResult = false; // Whether salvage/sell run should go to a middle-waypoint first to help prevent stucks private static bool bGoToSafetyPointFirst = false; private static bool bGoToSafetyPointSecond = false; private static bool bReachedSafety = false; // DateTime check to prevent inventory-check spam when looking for repairs being needed private static DateTime TimeLastCheckedForTownRun = DateTime.Now; private static bool bCurrentlyMoving = false; private static bool bReachedDestination = false; private static bool bNeedsEquipmentRepairs = false; private static float iLowestDurabilityFound = -1; // Bunch of temporary variables that get used when creating the current object/target list - this was just a nicer way for me to handle it code wise at first // Even if it looks a bit messy and probably should have just used it's own object instance of the cache-class instead! :D private static Vector3 tmp_vThisPosition = Vector3.Zero; private static GilesObjectType tmp_ThisGilesObjectType = GilesObjectType.Unknown; private static double tmp_dThisWeight = 0d; private static float tmp_fCentreDistance = 0f; private static float tmp_fRadiusDistance = 0f; private static string tmp_sThisInternalName = ""; private static int tmp_iThisACDGUID = 0; private static int tmp_iThisRActorGuid = 0; private static int tmp_iThisDynamicID = 0; private static int tmp_iThisBalanceID = 0; private static int tmp_iThisActorSNO = 0; private static int tmp_item_iThisLevel = 0; private static int tmp_item_iThisGoldAmount = 0; private static bool tmp_item_bThisOneHanded = false; private static ItemQuality tmp_item_ThisQuality = ItemQuality.Invalid; private static ItemType tmp_item_ThisDBItemType = ItemType.Unknown; private static FollowerType tmp_item_ThisFollowerType = FollowerType.None; private static GilesItemType tmp_item_ThisGilesItemType = GilesItemType.Unknown; private static bool tmp_unit_bThisElite = false; private static bool tmp_unit_bThisRare = false; private static bool tmp_unit_bThisUnique = false; private static bool tmp_unit_bThisMinion = false; private static bool tmp_unit_bThisTreasureGoblin = false; private static bool tmp_bThisEliteRareUnique = false; private static bool tmp_unit_bThisBoss = false; private static bool tmp_unit_bThisAttackable = false; private static bool tmp_bForceLeapAgainst = false; private static double tmp_unit_iThisHitPoints = 0d; private static float tmp_fThisRadius = 0f; private static MonsterSize tmp_unit_ThisMonsterSize = MonsterSize.Unknown; private static DiaUnit tmp_unit_diaUnit = null; // ********************************************************************************************** // ***** Special Sauce Dictionaries for SPEEEEEEED ***** // ********************************************************************************************** // This set of dictionaries are used for huge performance increases throughout, and a minimization of DB mis-read/null exception errors // Uses a little more ram - but for a massive CPU gain. And ram is cheap, CPU is not! // The following 2 variables are used to clear the dictionaries out - clearing one dictionary out per maximum every 2 seconds, working through in sequential order private static DateTime lastClearedCacheDictionary = DateTime.Today; private static int iLastClearedCacheDictionary = 0; // Caches the GilesObjectType of each object as we find it (RactorGUID based) private static Dictionary dictGilesObjectTypeCache = new Dictionary(); // Caches monster affixes for the monster ID, as this value can be a pain to get and slow (RactorGUID based) private static Dictionary dictGilesMonsterAffixCache = new Dictionary(); // Caches each monster's max-health, since this never changes (RactorGUID based) private static Dictionary dictGilesMaxHealthCache = new Dictionary(); // Caches each monster's current health for brief periods (RactorGUID based) private static Dictionary dictGilesLastHealthCache = new Dictionary(); private static Dictionary dictGilesLastHealthChecked = new Dictionary(); // Store a "not-burrowed" value for monsters that we have already checked a burrowed-status of and found false (RactorGUID based) private static Dictionary dictGilesBurrowedCache = new Dictionary(); // Store Actor SNO for each object (RactorGUID based) private static Dictionary dictGilesActorSNOCache = new Dictionary(); // Store ACDGUID for each object (RactorGUID based) private static Dictionary dictGilesACDGUIDCache = new Dictionary(); // Store internal name for each object (RactorGUID based) private static Dictionary dictGilesInternalNameCache = new Dictionary(); // Store Collision-sphere radius for each object (SNO based) private static Dictionary dictGilesCollisionSphereCache = new Dictionary(); // Caches the game balance ID for each object, which can then be used to pull up the appropriate entry from within dictGilesGameBalanceDataCache (RactorGUID based) private static Dictionary dictGilesGameBalanceIDCache = new Dictionary(); // Caches the Dynamic ID for each object (only used for non-units) (RactorGUID based) private static Dictionary dictGilesDynamicIDCache = new Dictionary(); // Caches the position for each object (only used for non-units, as this data is static so can be cached) (RactorGUID based) private static Dictionary dictGilesVectorCache = new Dictionary(); // Same as above but for gold-amount of pile (RactorGUID based) private static Dictionary dictGilesGoldAmountCache = new Dictionary(); // Same as above but for quality of item, we check this twice to make bloody sure we don't miss a legendary from a mis-read though (RactorGUID based) private static Dictionary dictGilesQualityCache = new Dictionary(); private static Dictionary dictGilesQualityRechecked = new Dictionary(); // Same as above but for whether we want to pick it up or not (RactorGUID based) private static Dictionary dictGilesPickupItem = new Dictionary(); // How many times the player tried to interact with this object in total private static Dictionary dictTotalInteractionAttempts = new Dictionary(); // Physics SNO for certain objects (SNO based) private static Dictionary dictPhysicsSNO = new Dictionary(); // Summoned-by ID for units (RactorGUID based) private static Dictionary dictSummonedByID = new Dictionary(); // ********************************************************************************************** // ***** Giant Super Special Sauce Dictionary ***** // ********************************************************************************************** // Here's the a huuuuuge dictionary I have been building up containing cached data on items to minimize D3 memory reads and help prevent some DB mis-handling of items // Note that even if an item is not on the list - it will add it to the cache "temporarily" (lasting for the duration of the bot run) - so if your bot encounters the // same kind of item twice in a row - the second time, it will use the cached data - so it will still minimize issues even if I haven't added all items to this list :D private static Dictionary dictGilesGameBalanceDataCache = new Dictionary #region GameBalanceIDCache { {-1106917318, new GilesGameBalanceDataCache(1, ItemType.CraftingPage, false, FollowerType.None)}, //Lore_Book_Flippy-10246 - Could be an error item!?!? {181033993, new GilesGameBalanceDataCache(62, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_07-946 {-970366835, new GilesGameBalanceDataCache(61, ItemType.CraftingPage, false, FollowerType.None)}, //Lore_Book_Flippy-898 {126259833, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldSmall-914 {126259831, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldCoin-1881 {126259832, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldCoins-1007 {126259834, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldMedium-657 {126259835, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //GoldLarge-7981 {-1483610851, new GilesGameBalanceDataCache(60, ItemType.Potion, false, FollowerType.None)}, //healthPotion_Mythic-776 {-1962741247, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //HealthGlobe-580 {1661414572, new GilesGameBalanceDataCache(61, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_05-495 {-1533912123, new GilesGameBalanceDataCache(61, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-1995 {-330720411, new GilesGameBalanceDataCache(63, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_07-1580 {-733829188, new GilesGameBalanceDataCache(61, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-1696 {2140882331, new GilesGameBalanceDataCache(60, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-1701 {-1616888606, new GilesGameBalanceDataCache(55, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_02-1697 {1565456762, new GilesGameBalanceDataCache(60, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-3182 {2140882332, new GilesGameBalanceDataCache(61, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-3189 {-2115689173, new GilesGameBalanceDataCache(62, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_06-3325 {-1962741209, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //HealthGlobe_02-3320 {2140882334, new GilesGameBalanceDataCache(63, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-3495 {40857596, new GilesGameBalanceDataCache(56, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-3571 {2058771892, new GilesGameBalanceDataCache(54, ItemType.Gem, false, FollowerType.None)}, //Topaz_07-3299 {-1303413119, new GilesGameBalanceDataCache(62, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_06-3766 {-2115689174, new GilesGameBalanceDataCache(61, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_05-3744 {1565456763, new GilesGameBalanceDataCache(61, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-3738 {290068679, new GilesGameBalanceDataCache(57, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_01-3850 {-1533912124, new GilesGameBalanceDataCache(60, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-3917 {88667232, new GilesGameBalanceDataCache(61, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_05-4365 {1146967348, new GilesGameBalanceDataCache(62, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-4767 {-1512729955, new GilesGameBalanceDataCache(63, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-4731 {-1303413123, new GilesGameBalanceDataCache(55, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_02-4517 {1700549963, new GilesGameBalanceDataCache(61, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_03-4584 {-2115689176, new GilesGameBalanceDataCache(57, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_03-4601 {-335464095, new GilesGameBalanceDataCache(59, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_03-4602 {-733830276, new GilesGameBalanceDataCache(52, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-4600 {620036246, new GilesGameBalanceDataCache(61, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-4950 {-1616888603, new GilesGameBalanceDataCache(62, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_05-4944 {1682228653, new GilesGameBalanceDataCache(62, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-5681 {-733829189, new GilesGameBalanceDataCache(60, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-6048 {-1303413121, new GilesGameBalanceDataCache(59, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_04-1383 {1815809035, new GilesGameBalanceDataCache(55, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_01-2564 {1565456765, new GilesGameBalanceDataCache(63, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-3402 {1603007817, new GilesGameBalanceDataCache(60, ItemType.Gem, false, FollowerType.None)}, //Ruby_08-3158 {1236607149, new GilesGameBalanceDataCache(61, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_02-3659 {1146967346, new GilesGameBalanceDataCache(60, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-3918 {-1337761336, new GilesGameBalanceDataCache(62, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_07-4052 {181033988, new GilesGameBalanceDataCache(53, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_02-4078 {-1337761340, new GilesGameBalanceDataCache(56, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_03-5687 {2140882330, new GilesGameBalanceDataCache(58, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-5685 {1565456761, new GilesGameBalanceDataCache(58, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-5684 {-1533912125, new GilesGameBalanceDataCache(58, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-6368 {-270936739, new GilesGameBalanceDataCache(59, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_03-6486 {-1337761339, new GilesGameBalanceDataCache(58, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_04-7960 {-2091501889, new GilesGameBalanceDataCache(63, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_06-7966 {-101310578, new GilesGameBalanceDataCache(58, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_02-7962 {1755623811, new GilesGameBalanceDataCache(62, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-8781 {1236607148, new GilesGameBalanceDataCache(60, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_03-8789 {-1733388799, new GilesGameBalanceDataCache(60, ItemType.Gem, false, FollowerType.None)}, //Emerald_08-10718 {1682228651, new GilesGameBalanceDataCache(60, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-10727 {-136815383, new GilesGameBalanceDataCache(53, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_04-10810 {1700549962, new GilesGameBalanceDataCache(59, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_02-10812 {2112157586, new GilesGameBalanceDataCache(61, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-11311 {-1533912121, new GilesGameBalanceDataCache(63, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-11314 {181033989, new GilesGameBalanceDataCache(54, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_03-12195 {2140882329, new GilesGameBalanceDataCache(55, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-12217 {-1303413122, new GilesGameBalanceDataCache(57, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_03-13817 {-1303413120, new GilesGameBalanceDataCache(61, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_05-14074 {-270936738, new GilesGameBalanceDataCache(61, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_04-14072 {-1337761337, new GilesGameBalanceDataCache(61, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_06-14964 {365492431, new GilesGameBalanceDataCache(62, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-1437 {1565456764, new GilesGameBalanceDataCache(62, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-1315 {-101310577, new GilesGameBalanceDataCache(61, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_03-3318 {-1512729959, new GilesGameBalanceDataCache(58, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-7736 {-875942695, new GilesGameBalanceDataCache(63, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_06-8312 {-2015049108, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-11075 {-733829186, new GilesGameBalanceDataCache(63, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-11066 {2140882333, new GilesGameBalanceDataCache(62, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-11993 {1539238478, new GilesGameBalanceDataCache(56, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-12027 {-733829187, new GilesGameBalanceDataCache(62, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-13327 {-231801347, new GilesGameBalanceDataCache(61, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_05-14009 {-331906332, new GilesGameBalanceDataCache(62, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_06-15167 {-1616888604, new GilesGameBalanceDataCache(61, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_04-16886 {-875942700, new GilesGameBalanceDataCache(55, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_01-668 {-1337761335, new GilesGameBalanceDataCache(63, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_08-1354 {1700549964, new GilesGameBalanceDataCache(62, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_04-1346 {-363389486, new GilesGameBalanceDataCache(61, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_06-1802 {-1512729958, new GilesGameBalanceDataCache(60, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-3342 {-2115689175, new GilesGameBalanceDataCache(59, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_04-3662 {-1411866890, new GilesGameBalanceDataCache(60, ItemType.Gem, false, FollowerType.None)}, //Amethyst_08-3681 {2058771893, new GilesGameBalanceDataCache(60, ItemType.Gem, false, FollowerType.None)}, //Topaz_08-3683 {-875942698, new GilesGameBalanceDataCache(60, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_03-5860 {1565456760, new GilesGameBalanceDataCache(55, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-7297 {-1512729957, new GilesGameBalanceDataCache(61, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-8321 {-875942699, new GilesGameBalanceDataCache(58, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_02-9464 {-1337761341, new GilesGameBalanceDataCache(54, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_02-9531 {-363389487, new GilesGameBalanceDataCache(60, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_05-9530 {-229899869, new GilesGameBalanceDataCache(57, ItemType.FollowerSpecial, false, FollowerType.Scoundrel)}, //JewelBox_Flippy-10528 {-733829191, new GilesGameBalanceDataCache(55, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-10549 {1146967347, new GilesGameBalanceDataCache(61, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-10553 {1612259883, new GilesGameBalanceDataCache(58, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-13213 {1682228652, new GilesGameBalanceDataCache(61, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-13904 {-101310576, new GilesGameBalanceDataCache(62, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_04-14801 {-333092253, new GilesGameBalanceDataCache(61, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_05-15478 {-875942696, new GilesGameBalanceDataCache(62, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_05-2766 {1661414569, new GilesGameBalanceDataCache(56, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_02-4779 {-363389485, new GilesGameBalanceDataCache(62, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_07-6008 {2140881244, new GilesGameBalanceDataCache(52, ItemType.Boots, false, FollowerType.None)}, //Boots_norm_base_flippy-6020 {290068680, new GilesGameBalanceDataCache(61, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_02-6907 {365492432, new GilesGameBalanceDataCache(63, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-7287 {-1656025083, new GilesGameBalanceDataCache(51, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_07-14357 {-635267403, new GilesGameBalanceDataCache(60, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_03-14369 {-242893289, new GilesGameBalanceDataCache(60, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-17237 {1539238479, new GilesGameBalanceDataCache(59, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-17846 {1612259885, new GilesGameBalanceDataCache(61, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-4464 {1612259886, new GilesGameBalanceDataCache(62, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-7114 {290068681, new GilesGameBalanceDataCache(62, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_03-7147 {-1616888605, new GilesGameBalanceDataCache(58, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_03-7560 {1146967345, new GilesGameBalanceDataCache(57, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-7571 {-2091501894, new GilesGameBalanceDataCache(52, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_01-7570 {1612259882, new GilesGameBalanceDataCache(55, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-7569 {1661414570, new GilesGameBalanceDataCache(58, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_03-7578 {-363389488, new GilesGameBalanceDataCache(58, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_04-9381 {1147341802, new GilesGameBalanceDataCache(60, ItemType.FollowerSpecial, false, FollowerType.Templar)}, //JewelBox_Flippy-10391 {-1512729960, new GilesGameBalanceDataCache(55, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-13909 {365492429, new GilesGameBalanceDataCache(60, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-13913 {-101310575, new GilesGameBalanceDataCache(63, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_05-15926 {88667230, new GilesGameBalanceDataCache(58, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_03-17386 {-1656023995, new GilesGameBalanceDataCache(62, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_06-17434 {-363389490, new GilesGameBalanceDataCache(54, ItemType.HandCrossbow, true, FollowerType.None)}, //handXbow_norm_base_flippy_02-19180 {365491342, new GilesGameBalanceDataCache(52, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-19272 {365492430, new GilesGameBalanceDataCache(61, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-20609 {-136814295, new GilesGameBalanceDataCache(61, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_03-24992 {-1512729956, new GilesGameBalanceDataCache(62, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-3581 {181033991, new GilesGameBalanceDataCache(59, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_05-5325 {-2091501892, new GilesGameBalanceDataCache(58, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_03-6306 {1565455675, new GilesGameBalanceDataCache(52, ItemType.Helm, false, FollowerType.None)}, //Helm_norm_base_flippy-2176 {181033992, new GilesGameBalanceDataCache(61, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_06-3659 {1661413485, new GilesGameBalanceDataCache(52, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_07-3991 {-1533912122, new GilesGameBalanceDataCache(62, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-3996 {2112157587, new GilesGameBalanceDataCache(62, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-6378 {-1303413118, new GilesGameBalanceDataCache(63, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_07-8259 {-1733388800, new GilesGameBalanceDataCache(54, ItemType.Gem, false, FollowerType.None)}, //Emerald_07-8261 {1612258797, new GilesGameBalanceDataCache(52, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-8260 {1682228650, new GilesGameBalanceDataCache(55, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-9071 {-1411866891, new GilesGameBalanceDataCache(54, ItemType.Gem, false, FollowerType.None)}, //Amethyst_07-2037 {-1512731045, new GilesGameBalanceDataCache(52, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-2899 {88667231, new GilesGameBalanceDataCache(60, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_04-3797 {-2115689177, new GilesGameBalanceDataCache(55, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_02-5676 {-1656023997, new GilesGameBalanceDataCache(59, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_04-11365 {-136814294, new GilesGameBalanceDataCache(62, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_04-11892 {329204073, new GilesGameBalanceDataCache(61, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_02-1331 {-270936736, new GilesGameBalanceDataCache(63, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_07-2509 {-1616888602, new GilesGameBalanceDataCache(63, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_06-3566 {-1656023999, new GilesGameBalanceDataCache(55, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_02-6299 {329204072, new GilesGameBalanceDataCache(56, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_01-6293 {-733829190, new GilesGameBalanceDataCache(58, ItemType.Belt, false, FollowerType.None)}, //Belt_norm_base_flippy-7011 {1661414573, new GilesGameBalanceDataCache(62, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_06-8628 {181033994, new GilesGameBalanceDataCache(63, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_08-9102 {1603007816, new GilesGameBalanceDataCache(54, ItemType.Gem, false, FollowerType.None)}, //Ruby_07-11231 {-1962741148, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //HealthGlobe_02-15599 {-334278174, new GilesGameBalanceDataCache(60, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_04-11811 {1539238480, new GilesGameBalanceDataCache(60, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-13479 {-1656023996, new GilesGameBalanceDataCache(61, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_05-16080 {1815809036, new GilesGameBalanceDataCache(57, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_02-16556 {40857598, new GilesGameBalanceDataCache(61, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-18151 {329204074, new GilesGameBalanceDataCache(62, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_03-18904 {-1656023998, new GilesGameBalanceDataCache(57, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_03-19117 {329202986, new GilesGameBalanceDataCache(51, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_04-21596 {1755622722, new GilesGameBalanceDataCache(53, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-1940 {1236607150, new GilesGameBalanceDataCache(62, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_03-3679 {-231801345, new GilesGameBalanceDataCache(63, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_06-7909 {365492428, new GilesGameBalanceDataCache(58, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-8662 {620036244, new GilesGameBalanceDataCache(56, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-11733 {88667228, new GilesGameBalanceDataCache(54, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_01-19036 {-875942697, new GilesGameBalanceDataCache(61, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_04-21441 {-363389484, new GilesGameBalanceDataCache(63, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_08-21449 {-270936737, new GilesGameBalanceDataCache(62, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_08-3422 {1661414568, new GilesGameBalanceDataCache(54, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_01-4034 {2112156498, new GilesGameBalanceDataCache(53, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-4089 {-2091501890, new GilesGameBalanceDataCache(62, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_05-5750 {1539238481, new GilesGameBalanceDataCache(61, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-7305 {-363389489, new GilesGameBalanceDataCache(56, ItemType.HandCrossbow, true, FollowerType.None)}, //handXBow_norm_base_flippy_03-7443 {-1533912126, new GilesGameBalanceDataCache(55, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-7516 {-2091501893, new GilesGameBalanceDataCache(55, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_02-10478 {-231801349, new GilesGameBalanceDataCache(55, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_03-10866 {-231801348, new GilesGameBalanceDataCache(58, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_02-15614 {-1337761342, new GilesGameBalanceDataCache(52, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_01-15747 {620036245, new GilesGameBalanceDataCache(60, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-16062 {1755623809, new GilesGameBalanceDataCache(60, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-18920 {-242893288, new GilesGameBalanceDataCache(61, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-18926 {1612259884, new GilesGameBalanceDataCache(60, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-22557 {181033990, new GilesGameBalanceDataCache(56, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_04-22861 {1661414571, new GilesGameBalanceDataCache(60, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_04-2058 {1146967320, new GilesGameBalanceDataCache(51, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-5049 {-229899868, new GilesGameBalanceDataCache(60, ItemType.FollowerSpecial, false, FollowerType.Scoundrel)}, //JewelBox_Flippy-7661 {1146967321, new GilesGameBalanceDataCache(54, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-8882 {-242893290, new GilesGameBalanceDataCache(56, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-8875 {761439027, new GilesGameBalanceDataCache(60, ItemType.FollowerSpecial, false, FollowerType.Enchantress)}, //JewelBox_Flippy-12503 {-231801346, new GilesGameBalanceDataCache(62, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_04-18285 {-1656024000, new GilesGameBalanceDataCache(53, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_01-18617 {1755623808, new GilesGameBalanceDataCache(56, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-1483 {40857597, new GilesGameBalanceDataCache(60, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-8723 {-875943785, new GilesGameBalanceDataCache(52, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_05-11841 {1700549961, new GilesGameBalanceDataCache(55, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_01-14697 {-1656023994, new GilesGameBalanceDataCache(63, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_base_flippy_07-23400 {-2115690261, new GilesGameBalanceDataCache(51, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_07-440 {1771751032, new GilesGameBalanceDataCache(61, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_02-9926 {-270936742, new GilesGameBalanceDataCache(53, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_02-11499 {-2091501891, new GilesGameBalanceDataCache(61, ItemType.Bow, false, FollowerType.None)}, //Bow_norm_base_flippy_04-15879 {1612259887, new GilesGameBalanceDataCache(63, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-16971 {88667229, new GilesGameBalanceDataCache(56, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_02-17893 {-242893287, new GilesGameBalanceDataCache(62, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-21766 {-635267401, new GilesGameBalanceDataCache(62, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_03-5833 {1661414574, new GilesGameBalanceDataCache(63, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_base_flippy_07-7199 {1771751034, new GilesGameBalanceDataCache(63, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_04-11249 {2112157584, new GilesGameBalanceDataCache(56, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-11552 {40857599, new GilesGameBalanceDataCache(62, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-14809 {-363389491, new GilesGameBalanceDataCache(52, ItemType.HandCrossbow, true, FollowerType.None)}, //handXbow_norm_base_flippy_01-15178 {-2115689172, new GilesGameBalanceDataCache(63, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_07-17487 {1755623810, new GilesGameBalanceDataCache(61, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-21671 {1539237393, new GilesGameBalanceDataCache(53, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-23717 {1236607147, new GilesGameBalanceDataCache(56, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_02-8621 {1905181656, new GilesGameBalanceDataCache(62, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_04-5332 {620035158, new GilesGameBalanceDataCache(53, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-490 {1288600121, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-8240 {1147341801, new GilesGameBalanceDataCache(57, ItemType.FollowerSpecial, false, FollowerType.Templar)}, //JewelBox_Flippy-6201 {-635267400, new GilesGameBalanceDataCache(63, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_04-24457 {1700549965, new GilesGameBalanceDataCache(63, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_05-7304 {-2115689178, new GilesGameBalanceDataCache(53, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_base_flippy_01-20183 {365492427, new GilesGameBalanceDataCache(55, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-1374 {290067593, new GilesGameBalanceDataCache(52, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_04-6474 {-136814296, new GilesGameBalanceDataCache(60, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_02-9438 {-1533913211, new GilesGameBalanceDataCache(52, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-15515 {-270936741, new GilesGameBalanceDataCache(55, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_05-23963 {-101310579, new GilesGameBalanceDataCache(55, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_01-3836 {88667233, new GilesGameBalanceDataCache(62, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_06-5434 {1905181655, new GilesGameBalanceDataCache(61, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_03-18857 {1236607151, new GilesGameBalanceDataCache(63, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_04-4638 {2112157585, new GilesGameBalanceDataCache(60, ItemType.MightyBelt, false, FollowerType.None)}, //Belt_norm_base_flippy-688 {88667234, new GilesGameBalanceDataCache(63, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_07-2315 {761439026, new GilesGameBalanceDataCache(57, ItemType.FollowerSpecial, false, FollowerType.Enchantress)}, //JewelBox_Flippy-5680 {-1337761338, new GilesGameBalanceDataCache(60, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_base_flippy_05-1977 {1236607146, new GilesGameBalanceDataCache(52, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_base_flippy_01-6741 {-635267402, new GilesGameBalanceDataCache(61, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_02-682 {1771751033, new GilesGameBalanceDataCache(62, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_03-1186 {290068682, new GilesGameBalanceDataCache(63, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_base_flippy_04-1455 {1815807951, new GilesGameBalanceDataCache(51, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_06-7216 {-242894376, new GilesGameBalanceDataCache(53, ItemType.SpiritStone, false, FollowerType.None)}, //Helm_norm_base_flippy-7932 {-1303413124, new GilesGameBalanceDataCache(53, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_01-7929 {-136814297, new GilesGameBalanceDataCache(56, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_base_flippy_01-10670 {-1303414207, new GilesGameBalanceDataCache(51, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_base_flippy_07-15282 {-270936740, new GilesGameBalanceDataCache(57, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_06-22060 {88666145, new GilesGameBalanceDataCache(52, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_base_flippy_07-25299 {-275669100, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-6906 {1815807952, new GilesGameBalanceDataCache(53, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_base_flippy_07-45607 {1905181654, new GilesGameBalanceDataCache(60, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_02-3074 {-231801350, new GilesGameBalanceDataCache(52, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_01-10063 {620036247, new GilesGameBalanceDataCache(62, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-17813 {-229899870, new GilesGameBalanceDataCache(54, ItemType.FollowerSpecial, false, FollowerType.Scoundrel)}, //JewelBox_Flippy-66005 {-270936743, new GilesGameBalanceDataCache(51, ItemType.Sword, true, FollowerType.None)}, //Sword_norm_base_flippy_01-9813 {1771751031, new GilesGameBalanceDataCache(58, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_03-996 {-635267404, new GilesGameBalanceDataCache(56, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_02-20442 {-1616888607, new GilesGameBalanceDataCache(52, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_base_flippy_01-12628 {1147341800, new GilesGameBalanceDataCache(54, ItemType.FollowerSpecial, false, FollowerType.Templar)}, //JewelBox_Flippy-4753 {1539238482, new GilesGameBalanceDataCache(62, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-16011 {761439025, new GilesGameBalanceDataCache(54, ItemType.FollowerSpecial, false, FollowerType.Enchantress)}, //JewelBox_Flippy-17285 {1771751030, new GilesGameBalanceDataCache(55, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_02-19316 {1771751029, new GilesGameBalanceDataCache(52, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_base_flippy_01-10268 {1905180567, new GilesGameBalanceDataCache(53, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_04-10666 {181032905, new GilesGameBalanceDataCache(52, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_base_flippy_08-15728 {-246124382, new GilesGameBalanceDataCache(63, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-9313 {1809242064, new GilesGameBalanceDataCache(56, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-33597 {-1137443897, new GilesGameBalanceDataCache(61, ItemType.Amulet, false, FollowerType.None)}, //Amulet_norm_base_flippy-10421 {1905181653, new GilesGameBalanceDataCache(56, ItemType.Orb, false, FollowerType.None)}, //orb_norm_base_flippy_01-18233 {329204075, new GilesGameBalanceDataCache(63, ItemType.MightyWeapon, false, FollowerType.None)}, //mightyWeapon_2H_norm_base_flippy_04-14928 {386201988, new GilesGameBalanceDataCache(62, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_base_flippy_07-59260 {-1849339001, new GilesGameBalanceDataCache(63, ItemType.Unknown, false, FollowerType.None)}, //Helm_norm_base_flippy-14437 {1700548876, new GilesGameBalanceDataCache(51, ItemType.Axe, false, FollowerType.None)}, //twoHandedAxe_norm_base_flippy_05-10421 {40856510, new GilesGameBalanceDataCache(53, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-9233 {-635267405, new GilesGameBalanceDataCache(52, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_base_flippy_01-10948 {-101311664, new GilesGameBalanceDataCache(52, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_base_flippy_05-2331 {255305004, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-12352 {402571149, new GilesGameBalanceDataCache(63, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_01-7789 {-247310303, new GilesGameBalanceDataCache(58, ItemType.Shoulder, false, FollowerType.None)}, //shoulderPads_norm_base_flippy-21253 {-327168932, new GilesGameBalanceDataCache(63, ItemType.Quiver, false, FollowerType.None)}, //Quiver_norm_base_flippy_01-49247 {1810427985, new GilesGameBalanceDataCache(63, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-57737 {399013386, new GilesGameBalanceDataCache(60, ItemType.Bracer, false, FollowerType.None)}, //Bracers_norm_base_01-16344 {-494657717, new GilesGameBalanceDataCache(63, ItemType.WizardHat, false, FollowerType.None)}, //HelmCloth_norm_base_flippy-19691 {-578170868, new GilesGameBalanceDataCache(61, ItemType.Ring, false, FollowerType.None)}, //Ring_flippy-17344 {-1953228509, new GilesGameBalanceDataCache(63, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-17168 {-1960344035, new GilesGameBalanceDataCache(58, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-18639 {253088131, new GilesGameBalanceDataCache(63, ItemType.Legs, false, FollowerType.None)}, //pants_norm_base_flippy-67142 {-1961529956, new GilesGameBalanceDataCache(62, ItemType.VoodooMask, false, FollowerType.None)}, //Helm_norm_base_flippy-26460 {1841261931, new GilesGameBalanceDataCache(61, ItemType.Gloves, false, FollowerType.None)}, //Gloves_norm_base_flippy-24850 {-1855268606, new GilesGameBalanceDataCache(62, ItemType.Unknown, false, FollowerType.None)}, //Helm_norm_base_flippy-69134 {469402902, new GilesGameBalanceDataCache(60, ItemType.Chest, false, FollowerType.None)}, //chestArmor_norm_base_flippy-54263 {-1271477944, new GilesGameBalanceDataCache(54, ItemType.Cloak, false, FollowerType.None)}, //chestArmor_norm_base_flippy-72607 {761573024, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger01-7009 {761573025, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger02-7561 {761573026, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger03-8774 {761573027, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger04-9277 {761573028, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger05-7156 {761573029, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //BlackRockLedger06-7390 {193631, new GilesGameBalanceDataCache(0, ItemType.Unknown, false, FollowerType.None)}, //A2C2AlcarnusPrisoner2-5586 {-1385743629, new GilesGameBalanceDataCache(1, ItemType.Unknown, false, FollowerType.None)}, //Lore_AzmodansOrders6-41895 {543691114, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-13741 {368302887, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-11107 {-636820188, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-18831 {-576445432, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-20698 {-275669102, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-1847 {435695962, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-10675 {-636820187, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-14144 {-115137849, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-31858 {1108898771, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-15883 {1134806015, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-17495 {-1689047028, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-12708 {-1051150314, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-18380 {623242820, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-5655 {-1660666895, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-18640 {-1661852814, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-36893 {368302886, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-52051 {-1205502140, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-52452 {-1162323497, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-43350 {-576445431, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-7553 {-807237754, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-22338 {-807237753, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-8329 {82340209, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-28639 {398631475, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-65157 {543691115, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-19549 {-1690232950, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-50444 {1717766203, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-12732 {844895417, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-1926 {1288600122, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-4913 {-1661852816, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-9603 {972140825, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-21742 {2129978459, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-30273 {844895416, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-68468 {-1690232948, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-31138 {255305005, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-6242 {364927530, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-7631 {623242821, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-11172 {-1690232949, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-64675 {1110084692, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-9210 {-638006108, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-59824 {368302888, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-11092 {82340210, new GilesGameBalanceDataCache(46, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Smith_Drop-29649 {521743063, new GilesGameBalanceDataCache(61, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Jeweler_Drop-7137 {-1171649812, new GilesGameBalanceDataCache(61, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Jeweler_Drop-60398 {872611723, new GilesGameBalanceDataCache(61, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Jeweler_Drop-3119 {2147165121, new GilesGameBalanceDataCache(62, ItemType.CraftingPlan, false, FollowerType.None)}, //CraftingPlan_Jeweler_Drop-51842 {626121463, new GilesGameBalanceDataCache(63, ItemType.HandCrossbow, true, FollowerType.None)}, //handXbow_norm_unique_flippy_08-28023 {1738057815, new GilesGameBalanceDataCache(63, ItemType.Spear, true, FollowerType.None)}, //Spear_norm_unique_flippy_02-46594 {-1864479819, new GilesGameBalanceDataCache(62, ItemType.FistWeapon, true, FollowerType.None)}, //fistWeapon_norm_unique_flippy_04-52011 {1880318728, new GilesGameBalanceDataCache(62, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_unique_flippy_07-42689 {1845044989, new GilesGameBalanceDataCache(63, ItemType.Daibo, false, FollowerType.None)}, //combatStaff_norm_unique_flippy_08-45075 {1025903124, new GilesGameBalanceDataCache(61, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_unique_flippy_08-10113 {140743477, new GilesGameBalanceDataCache(60, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_unique_flippy_06-8301 {589357912, new GilesGameBalanceDataCache(60, ItemType.HandCrossbow, true, FollowerType.None)}, //handXbow_norm_unique_flippy_02-9863 {-1665099598, new GilesGameBalanceDataCache(57, ItemType.Mojo, false, FollowerType.None)}, //Mojo_norm_unique_flippy_04-9022 {-1269640592, new GilesGameBalanceDataCache(60, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_unique_flippy_07-4037 {-1178676596, new GilesGameBalanceDataCache(60, ItemType.Polearm, false, FollowerType.None)}, //Polearm_norm_unique_flippy_02-24734 {421779618, new GilesGameBalanceDataCache(63, ItemType.Sword, false, FollowerType.None)}, //twoHandedSword_norm_unique_flippy_04-45679 {-1451977669, new GilesGameBalanceDataCache(61, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_unique_flippy_02-36957 {1034204571, new GilesGameBalanceDataCache(63, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_unique_flippy_06-26363 {1033018650, new GilesGameBalanceDataCache(56, ItemType.Shield, false, FollowerType.None)}, //Shield_norm_unique_flippy_07-69093 {1949306026, new GilesGameBalanceDataCache(62, ItemType.Mace, false, FollowerType.None)}, //twoHandedMace_norm_unique_flippy_04-35520 {-1241813500, new GilesGameBalanceDataCache(61, ItemType.CeremonialDagger, true, FollowerType.None)}, //ceremonialDagger_norm_unique_flippy_09-12122 {-1264896908, new GilesGameBalanceDataCache(61, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_unique_flippy_05-15262 {-1450791748, new GilesGameBalanceDataCache(63, ItemType.Crossbow, false, FollowerType.None)}, //XBow_norm_unique_flippy_06-41422 {-2078257915, new GilesGameBalanceDataCache(56, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_unique_flippy_02-1460 {-2041494364, new GilesGameBalanceDataCache(63, ItemType.Dagger, true, FollowerType.None)}, //Dagger_norm_unique_flippy_05-46093 {1888620175, new GilesGameBalanceDataCache(60, ItemType.Mace, true, FollowerType.None)}, //Mace_norm_unique_flippy_02-15158 {-1262525066, new GilesGameBalanceDataCache(63, ItemType.Staff, false, FollowerType.None)}, //Staff_norm_unique_flippy_04-17468 {1664512741, new GilesGameBalanceDataCache(63, ItemType.MightyWeapon, true, FollowerType.None)}, //mightyWeapon_1H_norm_unique_flippy_06-26377 {1279577735, new GilesGameBalanceDataCache(63, ItemType.Wand, true, FollowerType.None)}, //Wand_norm_unique_flippy_01-21404 {139557556, new GilesGameBalanceDataCache(63, ItemType.Axe, true, FollowerType.None)}, //Axe_norm_unique_flippy_04-39065 }; #endregion // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Locate a new best target, update object caches etc. ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // Grab the list of world objects public static void RefreshDiaObjects() { // Blank current/last/next targets Vector3 vSafePointNear = targetCurrent != null ? targetCurrent.vThisPosition : vNullLocation; Vector3 vKitePointAvoid = vNullLocation; int iCurrentTargetRactorGUID = targetCurrent != null ? targetCurrent.iThisRActorGuid : -1; targetCurrent = null; // Reset all variables for target-weight finding bAnyTreasureGoblinsPresent = false; //intell -- iCurrentMaxKillRadius = Zeta.CommonBot.Settings.CharacterSettings.Instance.KillRadius; iCurrentMaxKillRadius = (float)(settings.iMonsterKillRange); //intell iCurrentMaxLootRadius = Zeta.CommonBot.Settings.CharacterSettings.Instance.LootRadius; bStayPutDuringAvoidance = false; // Set up the fake object for the target handler thisFakeObject = null; // Not allowed to kill monsters due to profile/routine/combat targeting settings - just set the kill range to a third if (!ProfileManager.CurrentProfile.KillMonsters || !CombatTargeting.Instance.AllowedToKillMonsters) { iCurrentMaxKillRadius /= 3; } // Always have a minimum kill radius, so we're never getting whacked without retaliating if (iCurrentMaxKillRadius < 10) iCurrentMaxKillRadius = 10; // Not allowed to loots due to profile/routine/loot targeting settings - just set range to a quarter if (!ProfileManager.CurrentProfile.PickupLoot || !LootTargeting.Instance.AllowedToLoot) { iCurrentMaxLootRadius /= 4; } // Counter for how many cycles we extend or reduce our attack/kill radius, and our loot radius, after a last kill if (iKeepKillRadiusExtendedFor > 0) iKeepKillRadiusExtendedFor--; if (iKeepLootRadiusExtendedFor > 0) iKeepLootRadiusExtendedFor--; // Refresh buffs (so we can check for wrath being up to ignore ice balls and anything else like that) GilesRefreshBuffs(); // Clear forcing close-range priority on mobs after XX period of time if (bForceCloseRangeTarget && DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds > iMillisecondsForceCloseRange) { bForceCloseRangeTarget = false; } // Bunch of variables used throughout int iUnitsSurrounding = 0; hashMonsterObstacleCache = new HashSet(); hashAvoidanceObstacleCache = new HashSet(); hashNavigationObstacleCache = new HashSet(); bAnyChampionsPresent = false; bAnyMobsInCloseRange = false; iLastDistance = 0f; bTravellingAvoidance = false; // Every 15 seconds, clear the "blackspots" where avoidance failed, so we can re-check them if (DateTime.Now.Subtract(lastClearedAvoidanceBlackspots).TotalSeconds > 15) { lastClearedAvoidanceBlackspots = DateTime.Now; hashAvoidanceBlackspot = new HashSet(); } // Clear our very short-term destructible blacklist within 3 seconds of last attacking a destructible if (bNeedClearDestructibles && DateTime.Now.Subtract(lastDestroyedDestructible).TotalMilliseconds > 2500) { bNeedClearDestructibles = false; hashRGUIDDestructibleBlacklist = new HashSet(); } // Clear our very short-term ignore-monster blacklist (from not being able to raycast on them or already dead units) if (bNeedClearTemporaryBlacklist && DateTime.Now.Subtract(lastTemporaryBlacklist).TotalMilliseconds > 3000) { bNeedClearTemporaryBlacklist = false; hashRGUIDTemporaryBlacklist = new HashSet(); } // Clear certain cache dictionaries sequentially, spaced out over time, to force data updates if (DateTime.Now.Subtract(lastClearedCacheDictionary).TotalMilliseconds >= 4000) { lastClearedCacheDictionary = DateTime.Now; iLastClearedCacheDictionary++; if (iLastClearedCacheDictionary > 5) iLastClearedCacheDictionary = 1; switch (iLastClearedCacheDictionary) { case 1: dictGilesVectorCache = new Dictionary(); dictGilesObjectTypeCache = new Dictionary(); dictGilesActorSNOCache = new Dictionary(); dictGilesACDGUIDCache = new Dictionary(); dictGilesLastHealthCache = new Dictionary(); dictGilesLastHealthChecked = new Dictionary(); break; case 2: dictGilesMonsterAffixCache = new Dictionary(); dictGilesMaxHealthCache = new Dictionary(); dictionaryStoredMonsterTypes = new Dictionary(); dictionaryStoredMonsterSizes = new Dictionary(); dictGilesBurrowedCache = new Dictionary(); dictSummonedByID = new Dictionary(); break; case 3: case 4: case 5: dictGilesGameBalanceIDCache = new Dictionary(); dictGilesDynamicIDCache = new Dictionary(); dictGilesQualityCache = new Dictionary(); dictGilesQualityRechecked = new Dictionary(); dictGilesPickupItem = new Dictionary(); break; } } // Reset the counters for player-owned things iPlayerOwnedMysticAlly = 0; iPlayerOwnedGargantuan = 0; iPlayerOwnedZombieDog = 0; iPlayerOwnedDHPets = 0; // Reset the counters for monsters at various ranges iElitesWithinRange = new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }; iAnythingWithinRange = new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }; bAnyBossesInRange = false; // Flag for if we should search for an avoidance spot or not bRequireAvoidance = false; // Highest weight found as we progress through, so we can pick the best target at the end (the one with the highest weight) double iHighestWeightFound = 0; // Here's the list we'll use to store each object List listGilesObjectCache = new List(); HashSet hashDoneThisRactor = new HashSet(); // Now pull up all the data and store anything we want to handle in the super special cache list // Also use many cache dictionaries to minimize DB<->D3 memory hits, and speed everything up a lot foreach (Actor thisactor in ZetaDia.Actors.RActorList) { bool bWantThis = false; // Convert the actor to a DiaObject DiaObject thisobj = (DiaObject)thisactor; // Ractor GUID tmp_iThisRActorGuid = thisobj.RActorGuid; // See if we've already checked this ractor, this loop if (hashDoneThisRactor.Contains(tmp_iThisRActorGuid)) { continue; } hashDoneThisRactor.Add(tmp_iThisRActorGuid); // Temporary ractor GUID ignoring, to prevent 2 interactions in a very short time which can cause stucks if (iIgnoreThisForLoops > 0 && iIgnoreThisRactorGUID == tmp_iThisRActorGuid) { continue; } // Check our extremely short-term destructible-blacklist if (hashRGUIDDestructibleBlacklist.Contains(tmp_iThisRActorGuid)) { continue; } // Check our extremely short-term destructible-blacklist if (hashRGUIDTemporaryBlacklist.Contains(tmp_iThisRActorGuid)) { continue; } // See if it's on our temporary blacklist (from being stuck targeting it), as long as it's distance is not extremely close if (hashRGUIDTemporaryIgnoreBlacklist.Contains(tmp_iThisRActorGuid)) { continue; } // Or on our more permanent "per-game" blacklist if (hashRGUIDIgnoreBlacklist.Contains(tmp_iThisRActorGuid)) { continue; } // Get the Actor SNO, cached if possible if (!dictGilesActorSNOCache.TryGetValue(tmp_iThisRActorGuid, out tmp_iThisActorSNO)) { try { tmp_iThisActorSNO = thisobj.ActorSNO; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting ActorSNO for an object."); Logging.WriteDiagnostic(ex.ToString()); continue; } dictGilesActorSNOCache.Add(tmp_iThisRActorGuid, tmp_iThisActorSNO); } var tempCommonData = thisobj.CommonData; // Count up Mystic Allys, gargantuans, and zombies - if the player has those skills if (iMyCachedActorClass == ActorClass.Monk) { if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MysticAlly) && hashMysticAlly.Contains(tmp_iThisActorSNO)) { int iSummonedByID = -1; // Get the summoned-by info, cached if possible if (!dictSummonedByID.TryGetValue(tmp_iThisRActorGuid, out tmp_iThisActorSNO)) { try { iSummonedByID = tempCommonData.GetAttribute(ActorAttributeType.SummonedByACDID); } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting summoned-by info for Mystic Ally [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } dictSummonedByID.Add(tmp_iThisRActorGuid, iSummonedByID); } if (playerStatus.iMyDynamicID == iSummonedByID) { iPlayerOwnedMysticAlly++; continue; } } } // Count up Demon Hunter pets if (iMyCachedActorClass == ActorClass.DemonHunter) { if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Companion) && hashDHPets.Contains(tmp_iThisActorSNO)) { iPlayerOwnedDHPets++; continue; } } // Count up zombie dogs and gargantuans next if (iMyCachedActorClass == ActorClass.WitchDoctor) { if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Gargantuan) && hashGargantuan.Contains(tmp_iThisActorSNO)) { iPlayerOwnedGargantuan++; continue; } if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_SummonZombieDog) && hashZombie.Contains(tmp_iThisActorSNO)) { iPlayerOwnedZombieDog++; continue; } } // Check for navigation obstacle hashlist bool bThisObstacle = hashSNONavigationObstacles.Contains(tmp_iThisActorSNO); // See if it's something we should always ignore like ravens etc. if (!bThisObstacle && hashActorSNOIgnoreBlacklist.Contains(tmp_iThisActorSNO)) { continue; } // Start this object as off as unknown type tmp_ThisGilesObjectType = GilesObjectType.Unknown; // Either get the cached Giles object type, or calculate it fresh if (!bThisObstacle && !dictGilesObjectTypeCache.TryGetValue(tmp_iThisRActorGuid, out tmp_ThisGilesObjectType)) { // See if it's an avoidance first from the SNO if (hashAvoidanceSNOList.Contains(tmp_iThisActorSNO)) { // If avoidance is disabled, ignore this avoidance stuff if (!settings.bEnableAvoidance) { continue; } // Avoidance isn't disabled, so set this object type to avoidance tmp_ThisGilesObjectType = GilesObjectType.Avoidance; } // It's not an avoidance, so let's calculate it's object type "properly" else { // Calculate the object type of this object if (thisobj is DiaUnit) { if (tempCommonData == null) continue; if (thisobj.ACDGuid != tempCommonData.ACDGuid) continue; tmp_ThisGilesObjectType = GilesObjectType.Unit; } else if (hashForceSNOToItemList.Contains(tmp_iThisActorSNO) || thisobj is DiaItem) { if (tempCommonData == null) continue; if (thisobj.ACDGuid != tempCommonData.ACDGuid) continue; tmp_ThisGilesObjectType = GilesObjectType.Item; } // 138989 is the SNO for health well/health pools. The hashlist is for interactable objects like levers, wheels etc. else if (thisobj is GizmoShrine || tmp_iThisActorSNO == 138989) tmp_ThisGilesObjectType = GilesObjectType.Shrine; else if (thisobj is GizmoDestructibleLootContainer) tmp_ThisGilesObjectType = GilesObjectType.Destructible; else if (thisobj is GizmoDestructible) tmp_ThisGilesObjectType = GilesObjectType.Barricade; else if (thisobj is GizmoLootContainer) tmp_ThisGilesObjectType = GilesObjectType.Container; else if (hashSNOInteractWhitelist.Contains(tmp_iThisActorSNO)) tmp_ThisGilesObjectType = GilesObjectType.Interactable; } // Now cache the object type dictGilesObjectTypeCache.Add(tmp_iThisRActorGuid, tmp_ThisGilesObjectType); } // Null commondata checking for new DB builds switch (tmp_ThisGilesObjectType) { case GilesObjectType.Unit: case GilesObjectType.Item: case GilesObjectType.Gold: if (tempCommonData == null) { continue; } break; } // We couldn't get a valid object type, so ignore it if (!bThisObstacle && tmp_ThisGilesObjectType == GilesObjectType.Unknown) { continue; } // Get the ACDGUID, cached if possible, only for non-avoidance stuff if (!bThisObstacle && tmp_ThisGilesObjectType != GilesObjectType.Avoidance) { if (!dictGilesACDGUIDCache.TryGetValue(tmp_iThisRActorGuid, out tmp_iThisACDGUID)) { try { tmp_iThisACDGUID = thisobj.ACDGuid; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting ACDGUID for an object [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } dictGilesACDGUIDCache.Add(tmp_iThisRActorGuid, tmp_iThisACDGUID); } // No ACDGUID, so shouldn't be anything we want to deal with if (tmp_iThisACDGUID == -1) { continue; } } else { // Give AOE's -1 ACDGUID, since it's not needed for avoidance stuff tmp_iThisACDGUID = -1; } // We will set weight up later in RefreshDiaObjects after we process all valid items tmp_dThisWeight = 0; // Try and get a cached position for anything that isn't avoidance or units (avoidance and units can move, sadly, so we risk DB mis-reads for those things! if (tmp_ThisGilesObjectType != GilesObjectType.Avoidance && tmp_ThisGilesObjectType != GilesObjectType.Unit) { // Get the position, cached if possible if (!dictGilesVectorCache.TryGetValue(tmp_iThisRActorGuid, out tmp_vThisPosition)) { try { tmp_vThisPosition = thisobj.Position; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting position for a static object [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } // Now cache it dictGilesVectorCache.Add(tmp_iThisRActorGuid, tmp_vThisPosition); } } // Ok pull up live-position data for units/avoidance now... else { try { tmp_vThisPosition = thisobj.Position; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting position for a unit or avoidance object [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } } // Calculate distance, don't rely on DB's internal method as this may hit Diablo 3 memory again tmp_fCentreDistance = Vector3.Distance(playerStatus.vCurrentPosition, tmp_vThisPosition); // Set radius-distance to centre distance at first tmp_fRadiusDistance = tmp_fCentreDistance; // Now store the location etc. of this obstacle and continue if (bThisObstacle) { hashNavigationObstacleCache.Add(new GilesObstacle(tmp_vThisPosition, dictSNONavigationSize[tmp_iThisActorSNO], tmp_iThisActorSNO)); continue; } // Ignore stuff which has a Z-height-difference too great, it's probably on a different level etc. - though not avoidance! if (tmp_ThisGilesObjectType != GilesObjectType.Avoidance) { // Calculate the z-height difference between our current position, and this object's position float fThisHeightDifference = Math.Abs(playerStatus.vCurrentPosition.Z - tmp_vThisPosition.Z); switch (tmp_ThisGilesObjectType) { case GilesObjectType.Unit: case GilesObjectType.Barricade: // Ignore monsters (units) who's Z-height is 14 foot or more than our own z-height if (fThisHeightDifference >= 14f) { continue; } break; case GilesObjectType.Item: // Items at 26+ z-height difference (we don't want to risk missing items so much) if (fThisHeightDifference >= 26f) { continue; } break; case GilesObjectType.Gold: case GilesObjectType.Globe: // Gold/Globes at 11+ z-height difference if (fThisHeightDifference >= 11f) { continue; } break; case GilesObjectType.Destructible: case GilesObjectType.Shrine: case GilesObjectType.Container: // Destructibles, shrines and containers are the least important, so a z-height change of only 7 is enough to ignore (help avoid stucks at stairs etc.) if (fThisHeightDifference >= 7f) { continue; } break; case GilesObjectType.Interactable: // Special interactable objects if (fThisHeightDifference >= 9f) { continue; } break; } } // This is "internalname" for items, and just a "generic" name for objects and units - cached if possible if (!dictGilesInternalNameCache.TryGetValue(tmp_iThisRActorGuid, out tmp_sThisInternalName)) { try { tmp_sThisInternalName = thisobj.Name; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting InternalName for an object [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } dictGilesInternalNameCache.Add(tmp_iThisRActorGuid, tmp_sThisInternalName); } // Try and grab the dynamic id and game balance id, if necessary and if possible if (tmp_ThisGilesObjectType == GilesObjectType.Item) { // Get the Dynamic ID, cached if possible if (!dictGilesDynamicIDCache.TryGetValue(tmp_iThisRActorGuid, out tmp_iThisDynamicID)) { try { tmp_iThisDynamicID = tempCommonData.DynamicId; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting DynamicID for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } dictGilesDynamicIDCache.Add(tmp_iThisRActorGuid, tmp_iThisDynamicID); } // Get the Game Balance ID, cached if possible if (!dictGilesGameBalanceIDCache.TryGetValue(tmp_iThisRActorGuid, out tmp_iThisBalanceID)) { try { tmp_iThisBalanceID = tempCommonData.GameBalanceId; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting GameBalanceID for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } dictGilesGameBalanceIDCache.Add(tmp_iThisRActorGuid, tmp_iThisBalanceID); } } else { tmp_iThisDynamicID = -1; tmp_iThisBalanceID = -1; } // Variables used by multiple parts of the switch below int iCurrentMinimumStackSize; double iPercentage; // Now do stuff specific to object types switch (tmp_ThisGilesObjectType) { // ************************************************************ // ***** Handle Unit-type Objects ***** // ************************************************************ case GilesObjectType.Unit: // See if this is a boss tmp_unit_bThisBoss = hashBossSNO.Contains(tmp_iThisActorSNO); // Dictionary based caching of monster types based on the SNO codes MonsterType monsterType; // See if we need to refresh the monster type or not bool bAddToDictionary = !dictionaryStoredMonsterTypes.TryGetValue(tmp_iThisActorSNO, out monsterType); bool bRefreshMonsterType = bAddToDictionary; // If it's a boss and it was an ally, keep refreshing until it's not an ally // Because some bosses START as allied for cutscenes etc. until they become hostile if (tmp_unit_bThisBoss && !bRefreshMonsterType) { switch (monsterType) { case MonsterType.Ally: case MonsterType.Scenery: case MonsterType.Helper: case MonsterType.Team: bRefreshMonsterType = true; break; } } // Now see if we do need to get new data for this boss or not if (bRefreshMonsterType) { try { SNORecordMonster monsterInfo = tempCommonData.MonsterInfo; if (monsterInfo != null) { // Force Jondar as an undead, since Diablo 3 sticks him as a permanent ally if (tmp_iThisActorSNO == 86624) { monsterType = MonsterType.Undead; } else { monsterType = monsterInfo.MonsterType; } // Is this going to be a new dictionary entry, or updating one already existing? if (bAddToDictionary) dictionaryStoredMonsterTypes.Add(tmp_iThisActorSNO, monsterType); else dictionaryStoredMonsterTypes[tmp_iThisActorSNO] = monsterType; } else { monsterType = MonsterType.Undead; } } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting monsterinfo and monstertype for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); Logging.WriteDiagnostic("ActorTypeAttempt="); Logging.WriteDiagnostic(thisobj.ActorType.ToString()); continue; } } // Make sure it's a valid monster type switch (monsterType) { case MonsterType.Ally: case MonsterType.Scenery: case MonsterType.Helper: case MonsterType.Team: continue; } // health calculations double dThisMaxHealth; // Get the max health of this unit, a cached version if available, if not cache it if (!dictGilesMaxHealthCache.TryGetValue(tmp_iThisRActorGuid, out dThisMaxHealth)) { try { dThisMaxHealth = tempCommonData.GetAttribute(ActorAttributeType.HitpointsMax); } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting attribute max health for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } dictGilesMaxHealthCache.Add(tmp_iThisRActorGuid, dThisMaxHealth); } // Now try to get the current health - using temporary and intelligent caching // Health calculations int iLastCheckedHealth; double dThisCurrentHealth; bool bHasCachedHealth; // See if we already have a cached value for health or not for this monster if (dictGilesLastHealthChecked.TryGetValue(tmp_iThisRActorGuid, out iLastCheckedHealth)) { bHasCachedHealth = true; iLastCheckedHealth++; if (iLastCheckedHealth > 6) iLastCheckedHealth = 1; if (iCurrentTargetRactorGUID == tmp_iThisRActorGuid && iLastCheckedHealth > 3) iLastCheckedHealth = 1; } else { bHasCachedHealth = false; iLastCheckedHealth = 1; } // Update health once every 5 cycles, except for current target, which is every cycle if (iLastCheckedHealth == 1) { try { dThisCurrentHealth = tempCommonData.GetAttribute(ActorAttributeType.HitpointsCur); } catch { // This happens so frequently in DB/D3 that this fails, let's not even bother logging it anymore //Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting current health for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); // Add this monster to our very short-term ignore list hashRGUIDTemporaryBlacklist.Add(tmp_iThisRActorGuid); lastTemporaryBlacklist = DateTime.Now; bNeedClearTemporaryBlacklist = true; continue; } if (!bHasCachedHealth) { dictGilesLastHealthCache.Add(tmp_iThisRActorGuid, dThisCurrentHealth); dictGilesLastHealthChecked.Add(tmp_iThisRActorGuid, iLastCheckedHealth); } else { dictGilesLastHealthCache[tmp_iThisRActorGuid] = dThisCurrentHealth; dictGilesLastHealthChecked[tmp_iThisRActorGuid] = iLastCheckedHealth; } } else { dThisCurrentHealth = dictGilesLastHealthCache[tmp_iThisRActorGuid]; dictGilesLastHealthChecked[tmp_iThisRActorGuid] = iLastCheckedHealth; } // And finally put the two together for a current health percentage tmp_unit_iThisHitPoints = dThisCurrentHealth / dThisMaxHealth; // Unit is already dead if (tmp_unit_iThisHitPoints <= 0d) { // Add this monster to our very short-term ignore list hashRGUIDTemporaryBlacklist.Add(tmp_iThisRActorGuid); lastTemporaryBlacklist = DateTime.Now; bNeedClearTemporaryBlacklist = true; continue; } // Only set treasure goblins to true *IF* they haven't disabled goblins! Then check the SNO in the goblin hash list! tmp_unit_bThisTreasureGoblin = false; // Flag this as a treasure goblin *OR* ignore this object altogether if treasure goblins are set to ignore if (hashActorSNOGoblins.Contains(tmp_iThisActorSNO)) { if (settings.iTreasureGoblinPriority != 0) { tmp_unit_bThisTreasureGoblin = true; } else { continue; } } // Pull up the Monster Affix cached data MonsterAffixes theseaffixes; if (!dictGilesMonsterAffixCache.TryGetValue(tmp_iThisRActorGuid, out theseaffixes)) { try { theseaffixes = tempCommonData.MonsterAffixes; dictGilesMonsterAffixCache.Add(tmp_iThisRActorGuid, theseaffixes); } catch { theseaffixes = MonsterAffixes.None; } } tmp_unit_bThisElite = theseaffixes.HasFlag(MonsterAffixes.Elite); tmp_unit_bThisRare = theseaffixes.HasFlag(MonsterAffixes.Rare); tmp_unit_bThisUnique = theseaffixes.HasFlag(MonsterAffixes.Unique); tmp_unit_bThisMinion = theseaffixes.HasFlag(MonsterAffixes.Minion); /*if (tmp_unit_bThisElite || tmp_unit_bThisRare || tmp_unit_bThisUnique || tmp_unit_bThisMinion) Logging.Write("Arcane: " + theseaffixes.HasFlag(MonsterAffixes.ArcaneEnchanted) + " Frozen: " + theseaffixes.HasFlag(MonsterAffixes.Frozen) + " Jailer: " + theseaffixes.HasFlag(MonsterAffixes.Jailer) + " Molten: " + theseaffixes.HasFlag(MonsterAffixes.Molten)); */ //intell -- Other dangerous: Nightmarish, Mortar, Desecrator, Fire Chains, Knockback, Electrified //if (PowerManager.CanCast(SNOPower.Barbarian_WrathOfTheBerserker)) { //if (DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_WrathOfTheBerserker]).TotalSeconds >= 90) if (GilesUseTimer(SNOPower.Barbarian_WrathOfTheBerserker, true)) { //WotB only used on Arcane, Frozen, Jailer, Molten and Electrified+Reflect Damage elites if (theseaffixes.HasFlag(MonsterAffixes.ArcaneEnchanted) || theseaffixes.HasFlag(MonsterAffixes.Frozen) || theseaffixes.HasFlag(MonsterAffixes.Jailer) || theseaffixes.HasFlag(MonsterAffixes.Molten) || (theseaffixes.HasFlag(MonsterAffixes.Electrified) && theseaffixes.HasFlag(MonsterAffixes.ReflectsDamage)) || //Bosses and uber elites tmp_unit_bThisBoss || tmp_iThisActorSNO == 256015 || tmp_iThisActorSNO == 256000 || tmp_iThisActorSNO == 255996 || //...or more than 4 elite mobs in range (only elites/rares/uniques, not minions!) iElitesWithinRange[RANGE_50] > 4) bUseBerserker = true; } else bUseBerserker = false; // Is this something we should try to force leap/other movement abilities against? tmp_bForceLeapAgainst = false; // Cancel altogether if it's not even in range, unless it's a boss or an injured treasure goblin double dUseKillRadius = iCurrentMaxKillRadius; // Bosses get extra radius if (tmp_unit_bThisBoss) { if (tmp_iThisActorSNO != 80509) // Kulle Exception dUseKillRadius *= 1.5; // And even more if they're already injured if (tmp_unit_iThisHitPoints <= 0.98) dUseKillRadius *= 4; // And make sure we have a MINIMUM range for bosses - incase they are at screen edge etc. if (dUseKillRadius <= 200) if (tmp_iThisActorSNO != 80509) // Kulle Exception dUseKillRadius = 200; } // Special short-range list to ignore weakling mobs if (hashActorSNOShortRangeOnly.Contains(tmp_iThisActorSNO)) dUseKillRadius = 12; // Prevent long-range mobs beign ignored while they may be pounding on us if (dUseKillRadius <= 30 && hashActorSNORanged.Contains(tmp_iThisActorSNO)) dUseKillRadius = 30; //intell //GoatMutant_Ranged_A_Unique_Uber-10955 ActorSNO: 255996 (act 1) //DuneDervish_B_Unique_Uber-14252 ActorSNO: 256000 (act 2) //morluSpellcaster_A_Unique_Uber-17451 ActorSNO: 256015 (act 3) if (tmp_iThisActorSNO == 256015 || tmp_iThisActorSNO == 256000 || tmp_iThisActorSNO == 255996) dUseKillRadius = 80; // Injured treasure goblins get a huge extra radius - since they don't stay on the map long if injured, anyway! if (tmp_unit_bThisTreasureGoblin && (tmp_fCentreDistance <= 60 || tmp_unit_iThisHitPoints <= 0.99)) { tmp_bForceLeapAgainst = true; if (settings.iTreasureGoblinPriority <= 2) dUseKillRadius *= 2.5; else dUseKillRadius *= 4; // Minimum distance of 60 if (dUseKillRadius <= 60) dUseKillRadius = 60; } // Elitey type mobs and things else if ((tmp_unit_bThisElite || tmp_unit_bThisRare || tmp_unit_bThisUnique || tmp_unit_bThisMinion)) { tmp_bForceLeapAgainst = true; if (tmp_unit_iThisHitPoints <= 0.99) { dUseKillRadius *= 2; if (dUseKillRadius <= 90) dUseKillRadius = 90; } else { if (dUseKillRadius <= 60) dUseKillRadius = 60; } } // Safety for Giles own portal-back-to-town for full-backpack else if (bGilesForcedVendoring) { if (dUseKillRadius <= 60) dUseKillRadius = 60; //intell } // Now ignore any unit not within our kill or extended kill radius if (tmp_fCentreDistance > dUseKillRadius) { continue; } // Safe is-attackable detection tmp_unit_bThisAttackable = true; if (tmp_unit_bThisBoss || theseaffixes.HasFlag(MonsterAffixes.Shielding)) { try { DiaUnit thisunit = thisobj as DiaUnit; tmp_unit_bThisAttackable = !thisunit.IsInvulnerable; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting is-invulnerable attribute for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); tmp_unit_bThisAttackable = true; } } // Inactive units like trees, withermoths etc. still underground if (tmp_unit_iThisHitPoints >= 1f || tmp_unit_bThisBoss) { // Get the burrowing data for this unit bool bBurrowed; if (!dictGilesBurrowedCache.TryGetValue(tmp_iThisRActorGuid, out bBurrowed) || tmp_unit_bThisBoss) { try { DiaUnit thisunit = thisobj as DiaUnit; bBurrowed = !thisunit.IsAttackable; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting is-untargetable or is-burrowed attribute for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } // Only cache it if it's NOT burrowed (if it *IS* - then we need to keep re-checking until it comes out!) if (!bBurrowed) { // Don't cache for bosses, as we have to check for bosses popping in and out of the game during a complex fight if (!tmp_unit_bThisBoss) dictGilesBurrowedCache.Add(tmp_iThisRActorGuid, bBurrowed); } else { // Unit is burrowed, so we need to ignore it until it isn't! continue; } } } // Only if at full health, else don't bother checking each loop // See if we already have this monster's size stored, if not get it and cache it if (!dictionaryStoredMonsterSizes.TryGetValue(tmp_iThisActorSNO, out tmp_unit_ThisMonsterSize)) { try { SNORecordMonster monsterInfo = thisobj.MonsterInfo; if (monsterInfo != null) { tmp_unit_ThisMonsterSize = monsterInfo.MonsterSize; dictionaryStoredMonsterSizes.Add(tmp_iThisActorSNO, tmp_unit_ThisMonsterSize); } else { tmp_unit_ThisMonsterSize = MonsterSize.Unknown; } } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting monstersize info for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } } // Retrieve collision sphere radius, cached if possible if (!dictGilesCollisionSphereCache.TryGetValue(tmp_iThisActorSNO, out tmp_fThisRadius)) { try { tmp_fThisRadius = thisobj.CollisionSphere.Radius; // Take 6 from the radius tmp_fThisRadius -= 6f; // Minimum range clamp if (tmp_fThisRadius <= 1f) tmp_fThisRadius = 1f; // Maximum range clamp if (tmp_fThisRadius >= 20f) tmp_fThisRadius = 20f; } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting collisionsphere radius for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } dictGilesCollisionSphereCache.Add(tmp_iThisActorSNO, tmp_fThisRadius); } // A "fake distance" to account for the large-object size of monsters tmp_fRadiusDistance -= (float)tmp_fThisRadius; if (tmp_fRadiusDistance <= 1f) tmp_fRadiusDistance = 1f; // Handle bosses if (tmp_unit_bThisBoss) { // Force to elite and add to the collision list tmp_unit_bThisElite = true; hashMonsterObstacleCache.Add(new GilesObstacle(tmp_vThisPosition, (tmp_fThisRadius * 0.15f), tmp_iThisActorSNO)); } else { // Add to the collision-list hashMonsterObstacleCache.Add(new GilesObstacle(tmp_vThisPosition, (tmp_fThisRadius * 0.10f), tmp_iThisActorSNO)); } // All-in-one flag for quicker if checks throughout tmp_bThisEliteRareUnique = (tmp_unit_bThisElite || tmp_unit_bThisRare || tmp_unit_bThisUnique || tmp_unit_bThisMinion); // Special flags to decide whether to target anything at all if (tmp_bThisEliteRareUnique || tmp_unit_bThisBoss) bAnyChampionsPresent = true; // Extended kill radius after last fighting, or when we want to force a town run if ((settings.bExtendedKillRange && iKeepKillRadiusExtendedFor > 0) || bGilesForcedVendoring) { if (tmp_fCentreDistance <= dUseKillRadius) bAnyMobsInCloseRange = true; } else { if (tmp_fCentreDistance <= settings.iMonsterKillRange) bAnyMobsInCloseRange = true; } if (tmp_unit_bThisTreasureGoblin) bAnyTreasureGoblinsPresent = true; // Units with very high priority (1900+) allow an extra 50% on the non-elite kill slider range if (!bAnyMobsInCloseRange && !bAnyChampionsPresent && !bAnyTreasureGoblinsPresent && tmp_fCentreDistance <= (settings.iMonsterKillRange * 1.5)) { int iExtraPriority; // Enable extended kill radius for specific unit-types if (hashActorSNORanged.Contains(tmp_iThisActorSNO)) { bAnyMobsInCloseRange = true; } if (!bAnyMobsInCloseRange && dictActorSNOPriority.TryGetValue(tmp_iThisActorSNO, out iExtraPriority)) { if (iExtraPriority >= 1900) { bAnyMobsInCloseRange = true; } } } // Store the dia unit reference (we'll be able to remove this if we update ractor list every single loop, yay!) tmp_unit_diaUnit = null; try { tmp_unit_diaUnit = (DiaUnit)thisobj; // Prepare the fake object for target handler if (thisFakeObject == null) thisFakeObject = thisobj; } catch { continue; } bWantThis = true; break; // ************************************************************ // ***** Handle Item-type Objects ***** // ************************************************************ case GilesObjectType.Item: if (tmp_iThisBalanceID == -1) continue; // Try and pull up cached item data on this item, if not, add to our local memory cache GilesGameBalanceDataCache tempGilesGameBalanceId; if (!dictGilesGameBalanceDataCache.TryGetValue(tmp_iThisBalanceID, out tempGilesGameBalanceId)) { DiaItem tempitem = thisobj as DiaItem; if (tempitem != null) { try { tmp_item_iThisLevel = tempitem.CommonData.Level; tmp_item_ThisDBItemType = tempitem.CommonData.ItemType; tmp_item_bThisOneHanded = tempitem.CommonData.IsOneHand; tmp_item_ThisFollowerType = tempitem.CommonData.FollowerSpecialType; dictGilesGameBalanceDataCache.Add(tmp_iThisBalanceID, new GilesGameBalanceDataCache(tmp_item_iThisLevel, tmp_item_ThisDBItemType, tmp_item_bThisOneHanded, tmp_item_ThisFollowerType)); // Temporarily log stuff if (bLogBalanceDataForGiles) { FileStream LogStream = File.Open(sTrinityPluginPath + "_BalanceData_" + ZetaDia.Service.CurrentHero.BattleTagName + ".log", FileMode.Append, FileAccess.Write, FileShare.Read); using (StreamWriter LogWriter = new StreamWriter(LogStream)) { LogWriter.WriteLine("{" + tmp_iThisBalanceID.ToString() + ", new GilesGameBalanceDataCache(" + tmp_item_iThisLevel.ToString() + ", ItemType." + tmp_item_ThisDBItemType.ToString() + ", " + tmp_item_bThisOneHanded.ToString().ToLower() + ", FollowerType." + tmp_item_ThisFollowerType.ToString() + ")}, //" + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); } LogStream.Close(); } } catch (Exception ex) { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting un-cached ACD Item data (level/item type etc.) for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); Logging.WriteDiagnostic(ex.ToString()); continue; } } else { // Couldn't get the game balance data for this item, so ignore it for now continue; } } else { // We pulled this data from the dictionary cache, so use it instead of trying to get new data from DB/D3 memory! tmp_item_iThisLevel = tempGilesGameBalanceId.iThisItemLevel; tmp_item_ThisDBItemType = tempGilesGameBalanceId.thisItemType; tmp_item_bThisOneHanded = tempGilesGameBalanceId.bThisOneHand; tmp_item_ThisFollowerType = tempGilesGameBalanceId.thisFollowerType; } // Able to get cached data or not? // Error reading the item? if (tmp_iThisBalanceID == -1) { continue; } // Calculate custom Giles item type tmp_item_ThisGilesItemType = DetermineItemType(tmp_sThisInternalName, tmp_item_ThisDBItemType, tmp_item_ThisFollowerType); // And temporarily store the base type GilesBaseItemType tempbasetype = DetermineBaseType(tmp_item_ThisGilesItemType); // Treat all globes as a yes if (tmp_item_ThisGilesItemType == GilesItemType.HealthGlobe) { tmp_ThisGilesObjectType = GilesObjectType.Globe; // Create or alter this cached object type GilesObjectType tempobjecttype; if (!dictGilesObjectTypeCache.TryGetValue(tmp_iThisRActorGuid, out tempobjecttype)) dictGilesObjectTypeCache.Add(tmp_iThisRActorGuid, tmp_ThisGilesObjectType); else dictGilesObjectTypeCache[tmp_iThisRActorGuid] = tmp_ThisGilesObjectType; bWantThis = true; break; } // Gold amount for gold piles tmp_item_iThisGoldAmount = -1; // Handle gold piles first if (tmp_sThisInternalName.ToLower().StartsWith("gold")) { // Get the gold amount of this pile, cached if possible if (!dictGilesGoldAmountCache.TryGetValue(tmp_iThisRActorGuid, out tmp_item_iThisGoldAmount)) { try { tmp_item_iThisGoldAmount = tempCommonData.GetAttribute(ActorAttributeType.Gold); } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting gold pile amount for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); continue; } dictGilesGoldAmountCache.Add(tmp_iThisRActorGuid, tmp_item_iThisGoldAmount); } // Ignore gold piles that are (currently) too small... iCurrentMinimumStackSize = settings.iMinimumGoldStack; // Up to 40% less gold limit needed at close range if (tmp_fCentreDistance <= 20f) { iPercentage = (1 - (tmp_fCentreDistance / 20)) * 0.4; iCurrentMinimumStackSize -= (int)Math.Floor(iPercentage * iCurrentMinimumStackSize); } // And up to 40% or even higher extra gold limit at distant range else if (tmp_fCentreDistance > 20f) { iPercentage = (tmp_fCentreDistance / 50) * 0.8; iCurrentMinimumStackSize += (int)Math.Floor(iPercentage * iCurrentMinimumStackSize); } // Now check if this gold pile is currently less than this limit if (tmp_item_iThisGoldAmount < iCurrentMinimumStackSize) { continue; } // Blacklist gold piles already in pickup radius range if (tmp_fCentreDistance <= ZetaDia.Me.GoldPickUpRadius) { hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid); continue; } tmp_ThisGilesObjectType = GilesObjectType.Gold; // Create or alter this cached object type GilesObjectType tempobjecttype; if (!dictGilesObjectTypeCache.TryGetValue(tmp_iThisRActorGuid, out tempobjecttype)) dictGilesObjectTypeCache.Add(tmp_iThisRActorGuid, tmp_ThisGilesObjectType); else dictGilesObjectTypeCache[tmp_iThisRActorGuid] = tmp_ThisGilesObjectType; bWantThis = true; break; } // Quality of item for "genuine" items tmp_item_ThisQuality = ItemQuality.Invalid; if (tempbasetype != GilesBaseItemType.Unknown && tempbasetype != GilesBaseItemType.HealthGlobe && tempbasetype != GilesBaseItemType.Gem && tempbasetype != GilesBaseItemType.Misc && !hashForceSNOToItemList.Contains(tmp_iThisActorSNO)) { // Get the quality of this item, cached if possible if (!dictGilesQualityCache.TryGetValue(tmp_iThisRActorGuid, out tmp_item_ThisQuality)) { try { tmp_item_ThisQuality = (ItemQuality)tempCommonData.GetAttribute(ActorAttributeType.ItemQualityLevel); } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting item-quality for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); continue; } dictGilesQualityCache.Add(tmp_iThisRActorGuid, tmp_item_ThisQuality); dictGilesQualityRechecked.Add(tmp_iThisRActorGuid, false); } else { // Because item-quality is such a sensitive thing, we don't want to risk losing items // So we check a cached item quality a 2nd time - as long as it's the same, we won't check again // However, if there's any inconsistencies, we keep checking, and keep the highest-read quality as the real value if (!dictGilesQualityRechecked[tmp_iThisRActorGuid]) { ItemQuality temporaryItemQualityCheck = ItemQuality.Invalid; bool bFailedReading = false; try { temporaryItemQualityCheck = (ItemQuality)tempCommonData.GetAttribute(ActorAttributeType.ItemQualityLevel); } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception double-checking item-quality for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); bFailedReading = true; } // Make sure we didn't get a failure re-reading the quality, if we did we'll just keep re-checking until we don't if (!bFailedReading) { // If the newly-received quality is higher, then store the new quality if (temporaryItemQualityCheck > tmp_item_ThisQuality) { dictGilesQualityCache[tmp_iThisRActorGuid] = temporaryItemQualityCheck; tmp_item_ThisQuality = temporaryItemQualityCheck; } // And now flag it so we don't check this item again dictGilesQualityRechecked[tmp_iThisRActorGuid] = true; } } } } // Item stats if (!_hashsetItemStatsLookedAt.Contains(tmp_iThisRActorGuid)) { _hashsetItemStatsLookedAt.Add(tmp_iThisRActorGuid); if (tempbasetype == GilesBaseItemType.Armor || tempbasetype == GilesBaseItemType.WeaponOneHand || tempbasetype == GilesBaseItemType.WeaponTwoHand || tempbasetype == GilesBaseItemType.WeaponRange || tempbasetype == GilesBaseItemType.Jewelry || tempbasetype == GilesBaseItemType.FollowerItem || tempbasetype == GilesBaseItemType.Offhand) { int iThisQuality; ItemsDroppedStats.iTotal++; if (tmp_item_ThisQuality >= ItemQuality.Legendary) iThisQuality = QUALITYORANGE; else if (tmp_item_ThisQuality >= ItemQuality.Rare4) iThisQuality = QUALITYYELLOW; else if (tmp_item_ThisQuality >= ItemQuality.Magic1) iThisQuality = QUALITYBLUE; else iThisQuality = QUALITYWHITE; ItemsDroppedStats.iTotalPerQuality[iThisQuality]++; ItemsDroppedStats.iTotalPerLevel[tmp_item_iThisLevel]++; ItemsDroppedStats.iTotalPerQPerL[iThisQuality, tmp_item_iThisLevel]++; } else if (tempbasetype == GilesBaseItemType.Gem) { int iThisGemType = 0; ItemsDroppedStats.iTotalGems++; if (tmp_item_ThisGilesItemType == GilesItemType.Topaz) iThisGemType = GEMTOPAZ; if (tmp_item_ThisGilesItemType == GilesItemType.Ruby) iThisGemType = GEMRUBY; if (tmp_item_ThisGilesItemType == GilesItemType.Emerald) iThisGemType = GEMEMERALD; if (tmp_item_ThisGilesItemType == GilesItemType.Amethyst) iThisGemType = GEMAMETHYST; ItemsDroppedStats.iGemsPerType[iThisGemType]++; ItemsDroppedStats.iGemsPerLevel[tmp_item_iThisLevel]++; ItemsDroppedStats.iGemsPerTPerL[iThisGemType, tmp_item_iThisLevel]++; } else if (tmp_item_ThisGilesItemType == GilesItemType.HealthPotion) { ItemsDroppedStats.iTotalPotions++; ItemsDroppedStats.iPotionsPerLevel[tmp_item_iThisLevel]++; } else if (tmp_item_ThisGilesItemType == GilesItemType.InfernalKey) { ItemsDroppedStats.iTotalInfernalKeys++; } // See if we should update the stats file if (DateTime.Now.Subtract(ItemStatsLastPostedReport).TotalSeconds > 10) { ItemStatsLastPostedReport = DateTime.Now; OutputReport(); } } // Ignore it if it's not in range yet - allow legendary items to have 15 feet extra beyond our profile max loot radius float fExtraRange = 0f; if (iKeepLootRadiusExtendedFor > 0 || tmp_item_ThisQuality >= ItemQuality.Rare4) { fExtraRange = 30f; } if (iKeepLootRadiusExtendedFor > 0 || tmp_item_ThisQuality >= ItemQuality.Legendary) { fExtraRange = 50f; } if (tmp_fCentreDistance > (iCurrentMaxLootRadius + fExtraRange)) { continue; } // Now see if we actually want it if (settings.bUseGilesFilters) { // Get whether or not we want this item, cached if possible bool bWantThisItem; if (!dictGilesPickupItem.TryGetValue(tmp_iThisRActorGuid, out bWantThisItem)) { bWantThisItem = GilesPickupItemValidation(tmp_sThisInternalName, tmp_item_iThisLevel, tmp_item_ThisQuality, tmp_iThisBalanceID, tmp_item_ThisDBItemType, tmp_item_ThisFollowerType, tmp_iThisDynamicID); dictGilesPickupItem.Add(tmp_iThisRActorGuid, bWantThisItem); } // Using Giles filters if (bWantThisItem) { // If we are trying to run a vendor-run, then don't deal with items atm if (bGilesForcedVendoring) { continue; } bWantThis = true; break; } } else { // Get whether or not we want this item, cached if possible bool bWantThisItem; if (!dictGilesPickupItem.TryGetValue(tmp_iThisRActorGuid, out bWantThisItem)) { bWantThisItem = ItemManager.EvaluateItem((ACDItem)tempCommonData, ItemManager.RuleType.PickUp); dictGilesPickupItem.Add(tmp_iThisRActorGuid, bWantThisItem); } // Using DB built-in item rules if (bWantThisItem) { if (bGilesForcedVendoring) { continue; } bWantThis = true; break; } } // Didn't pass giles pickup rules/DB internal rule match, so ignore it continue; // ************************************************************ // ***** Handle Gold ***** // ************************************************************ // NOTE: Only identified as gold after *FIRST* loop as an "item" by above code case GilesObjectType.Gold: // Get the gold amount of this pile, cached if possible if (!dictGilesGoldAmountCache.TryGetValue(tmp_iThisRActorGuid, out tmp_item_iThisGoldAmount)) { try { tmp_item_iThisGoldAmount = tempCommonData.GetAttribute(ActorAttributeType.Gold); } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting gold pile amount for item " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); continue; } dictGilesGoldAmountCache.Add(tmp_iThisRActorGuid, tmp_item_iThisGoldAmount); } // Ignore gold piles that are (currently) too small... iCurrentMinimumStackSize = settings.iMinimumGoldStack; // Up to 40% less gold limit needed at close range if (tmp_fCentreDistance <= 20f) { iPercentage = (1 - (tmp_fCentreDistance / 20)) * 0.4; iCurrentMinimumStackSize -= (int)Math.Floor(iPercentage * iCurrentMinimumStackSize); } // And up to 40% or even higher extra gold limit at distant range else if (tmp_fCentreDistance >= 30f) { iPercentage = (tmp_fCentreDistance / 40) * 0.4; iCurrentMinimumStackSize += (int)Math.Floor(iPercentage * iCurrentMinimumStackSize); } // Now check if this gold pile is currently less than this limit if (tmp_item_iThisGoldAmount < iCurrentMinimumStackSize) { continue; } // Blacklist gold piles already in pickup radius range if (tmp_fCentreDistance <= ZetaDia.Me.GoldPickUpRadius) { hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid); continue; } tmp_ThisGilesObjectType = GilesObjectType.Gold; bWantThis = true; break; // ************************************************************ // ***** Handle Globes ***** // ************************************************************ // NOTE: Only identified as globe after *FIRST* loop as an "item" by above code case GilesObjectType.Globe: // Ignore it if it's not in range yet if (tmp_fCentreDistance > iCurrentMaxLootRadius || tmp_fCentreDistance > 37f) { continue; } bWantThis = true; break; // ************************************************************ // ***** Handle Avoidance Objects ***** // ************************************************************ case GilesObjectType.Avoidance: // Note if you are looking here - an AOE object won't even appear at this stage if you have settings.bEnableAvoidance switched off! if (!hashAvoidanceSNOList.Contains(tmp_iThisActorSNO)) { Logging.WriteDiagnostic("GSDebug: Invalid avoidance detected, SNO=" + tmp_iThisActorSNO.ToString() + ", name=" + tmp_sThisInternalName + ", object type=" + tmp_ThisGilesObjectType.ToString()); continue; } bool bIgnoreThisAvoidance = false; double dThisHealthAvoid = dictAvoidanceHealth[tmp_iThisActorSNO]; // Monks with Serenity up ignore all AOE's if (iMyCachedActorClass == ActorClass.Monk && hashPowerHotbarAbilities.Contains(SNOPower.Monk_Serenity) && GilesHasBuff(SNOPower.Monk_Serenity)) { // Monks with serenity are immune bIgnoreThisAvoidance = true; } // Witch doctors with spirit walk available and not currently Spirit Walking will subtly ignore ice balls, arcane, desecrator & plague cloud if (iMyCachedActorClass == ActorClass.WitchDoctor && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_SpiritWalk) && (!GilesHasBuff(SNOPower.Witchdoctor_SpiritWalk) && GilesUseTimer(SNOPower.Witchdoctor_SpiritWalk)) || GilesHasBuff(SNOPower.Witchdoctor_SpiritWalk)) { if (tmp_iThisActorSNO == 223675 || tmp_iThisActorSNO == 402 || tmp_iThisActorSNO == 219702 || tmp_iThisActorSNO == 221225 || tmp_iThisActorSNO == 84608 || tmp_iThisActorSNO == 108869) { // Ignore ICE/Arcane/Desc/PlagueCloud altogether with spirit walk up or available bIgnoreThisAvoidance = true; } } // Remove ice balls if the barbarian has wrath of the berserker up, and reduce health from most other SNO avoidances if (iMyCachedActorClass == ActorClass.Barbarian && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_WrathOfTheBerserker) && GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker)) { if (tmp_iThisActorSNO == 223675 || tmp_iThisActorSNO == 402) { // Ignore ice-balls altogether with wrath up bIgnoreThisAvoidance = true; } else { // Use half-health for anything else except arcanes or desecrate with wrath up if (tmp_iThisActorSNO == 219702 || tmp_iThisActorSNO == 221225) // Arcane bIgnoreThisAvoidance = true; else if (tmp_iThisActorSNO == 84608) // Desecrator dThisHealthAvoid *= 0.2; else if (tmp_iThisActorSNO == 4803 || tmp_iThisActorSNO == 4804 || tmp_iThisActorSNO == 224225 || tmp_iThisActorSNO == 247987) // Molten core dThisHealthAvoid *= 1; else // Anything else dThisHealthAvoid *= 0.3; } } // Add it to the list of known avoidance objects, *IF* our health is lower than this avoidance health limit if (!bIgnoreThisAvoidance && dThisHealthAvoid >= playerStatus.dCurrentHealthPct) { // Generate a "weight" for how badly we want to avoid this obstacle, based on a percentage of 100% the avoidance health is, multiplied into a max of 200 weight double dThisWeight = (200 * dThisHealthAvoid); hashAvoidanceObstacleCache.Add(new GilesObstacle(tmp_vThisPosition, (float)dictAvoidanceRadius[tmp_iThisActorSNO], tmp_iThisActorSNO, dThisWeight)); // Is this one under our feet? If so flag it up so we can find an avoidance spot if (tmp_fCentreDistance <= dictAvoidanceRadius[tmp_iThisActorSNO]) { bRequireAvoidance = true; // Note if this is a travelling projectile or not so we can constantly update our safe points if (hashAvoidanceSNOProjectiles.Contains(tmp_iThisActorSNO)) bTravellingAvoidance = true; } } // continue because we aren't actually treating this as a TARGET - avoidance has special handling after all targets are found continue; // ************************************************************ // ***** Handle Other-type Objects ***** // ************************************************************ case GilesObjectType.Destructible: case GilesObjectType.Barricade: case GilesObjectType.Container: case GilesObjectType.Shrine: case GilesObjectType.Interactable: // Check the primary object blacklist if (hashSNOIgnoreBlacklist.Contains(tmp_iThisActorSNO)) { continue; } // Ignore it if it's not in range yet if (tmp_fCentreDistance > iCurrentMaxLootRadius || tmp_fCentreDistance > 50) { continue; } if (tmp_sThisInternalName.ToLower().StartsWith("minimapicon")) { // Minimap icons caused a few problems in the past, so this force-blacklists them hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid); continue; } // Retrieve collision sphere radius, cached if possible if (!dictGilesCollisionSphereCache.TryGetValue(tmp_iThisActorSNO, out tmp_fThisRadius)) { try { tmp_fThisRadius = thisobj.CollisionSphere.Radius; // Take 8 from the radius tmp_fThisRadius -= 10f; // Minimum range clamp if (tmp_fThisRadius <= 1f) tmp_fThisRadius = 1f; // Maximum range clamp if (tmp_fThisRadius >= 16f) tmp_fThisRadius = 16f; } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting collisionsphere radius for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); continue; } dictGilesCollisionSphereCache.Add(tmp_iThisActorSNO, tmp_fThisRadius); } // A "fake distance" to account for the large-object size of monsters tmp_fRadiusDistance -= (float)tmp_fThisRadius; if (tmp_fRadiusDistance <= 1f) tmp_fRadiusDistance = 1f; // Now for the specifics int iThisPhysicsSNO; int iExtendedRange; double iMinDistance; switch (tmp_ThisGilesObjectType) { case GilesObjectType.Interactable: // Special interactables if (tmp_fCentreDistance > 30f) { continue; } tmp_fThisRadius = 4f; bWantThis = true; break; case GilesObjectType.Shrine: // Shrines // Check if either we want to ignore all shrines if (settings.bIgnoreAllShrines) { // We're ignoring all shrines, so blacklist this one hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid); continue; } // Already used, blacklist it and don't look at it again bool bThisUsed = false; try { bThisUsed = (tempCommonData.GetAttribute(ActorAttributeType.GizmoHasBeenOperated) > 0); } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting shrine-been-operated attribute for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); continue; } if (bThisUsed) { // It's already open! hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid); continue; } // Bag it! tmp_fThisRadius = 4f; bWantThis = true; break; case GilesObjectType.Barricade: // Get the cached physics SNO of this object if (!dictPhysicsSNO.TryGetValue(tmp_iThisActorSNO, out iThisPhysicsSNO)) { try { iThisPhysicsSNO = thisobj.PhysicsSNO; } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting physics SNO for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); continue; } dictPhysicsSNO.Add(tmp_iThisActorSNO, iThisPhysicsSNO); } // No physics mesh? Ignore this destructible altogether if (iThisPhysicsSNO <= 0) { // No physics mesh on a destructible, probably bugged hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid); continue; } // Set min distance to user-defined setting iMinDistance = settings.iDestructibleAttackRange + (tmp_fThisRadius * 0.70); if (bForceCloseRangeTarget) iMinDistance += 6f; // Large objects, like logs - Give an extra xx feet of distance if (dictSNOExtendedDestructRange.TryGetValue(tmp_iThisActorSNO, out iExtendedRange)) iMinDistance = settings.iDestructibleAttackRange + iExtendedRange; // This object isn't yet in our destructible desire range if (iMinDistance <= 0 || tmp_fCentreDistance > iMinDistance) { continue; } // Bag it! bWantThis = true; break; case GilesObjectType.Destructible: // Get the cached physics SNO of this object if (!dictPhysicsSNO.TryGetValue(tmp_iThisActorSNO, out iThisPhysicsSNO)) { try { iThisPhysicsSNO = thisobj.PhysicsSNO; } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting physics SNO for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); continue; } dictPhysicsSNO.Add(tmp_iThisActorSNO, iThisPhysicsSNO); } // No physics mesh? Ignore this destructible altogether if (iThisPhysicsSNO <= 0) { // No physics mesh on a destructible, probably bugged hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid); continue; } // Set min distance to user-defined setting iMinDistance = settings.iDestructibleAttackRange + (tmp_fThisRadius * 0.30); if (bForceCloseRangeTarget) iMinDistance += 6f; // Large objects, like logs - Give an extra xx feet of distance if (dictSNOExtendedDestructRange.TryGetValue(tmp_iThisActorSNO, out iExtendedRange)) iMinDistance = settings.iDestructibleAttackRange + iExtendedRange; // This object isn't yet in our destructible desire range if (iMinDistance <= 0 || tmp_fCentreDistance > iMinDistance) { continue; } // Bag it! bWantThis = true; break; case GilesObjectType.Container: // We want to do some vendoring, so don't open anything new yet if (bGilesForcedVendoring) { continue; } // Already open, blacklist it and don't look at it again bool bThisOpen = false; try { bThisOpen = (tempCommonData.GetAttribute(ActorAttributeType.ChestOpen) > 0); } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting container-been-opened attribute for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); continue; } if (bThisOpen) { // It's already open! hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid); continue; } // Default to blacklisting all containers, then find reasons not to bool bBlacklistThis = true; iMinDistance = 0f; // Get the cached physics SNO of this object if (!dictPhysicsSNO.TryGetValue(tmp_iThisActorSNO, out iThisPhysicsSNO)) { try { iThisPhysicsSNO = thisobj.PhysicsSNO; } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting physics SNO for object " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); continue; } dictPhysicsSNO.Add(tmp_iThisActorSNO, iThisPhysicsSNO); } // Any physics mesh? Give a minimum distance of 5 feet if (tmp_sThisInternalName.ToLower().Contains("corpse") && settings.bIgnoreCorpses) { bBlacklistThis = true; } else if (iThisPhysicsSNO > 0 || !settings.bIgnoreCorpses) { Logging.WriteDiagnostic("[GilesTrinity] open container " + tmp_sThisInternalName + "[" + tmp_iThisActorSNO.ToString() + "]" + iThisPhysicsSNO); bBlacklistThis = false; iMinDistance = settings.iContainerOpenRange; } else { bBlacklistThis = true; } // Whitelist for chests we want to open if we ever get close enough to them if (hashSNOContainerWhitelist.Contains(tmp_iThisActorSNO)) { bBlacklistThis = false; if (settings.iContainerOpenRange > 0) iMinDistance = settings.iContainerOpenRange + 5; } else if (tmp_sThisInternalName.Contains("chest") && !tmp_sThisInternalName.Contains("chest_rare")) { Logging.WriteDiagnostic("GSDebug: Possible Chest SNO: " + tmp_sThisInternalName + ", SNO=" + tmp_iThisActorSNO.ToString()); } // Superlist for rare chests etc. if (hashSNOContainerResplendant.Contains(tmp_iThisActorSNO)) { bBlacklistThis = false; if (settings.iContainerOpenRange > 0) iMinDistance = settings.iContainerOpenRange + 20; else iMinDistance = 10; } else if (tmp_sThisInternalName.Contains("chest_rare")) { Logging.WriteDiagnostic("GSDebug: Possible Resplendant Chest SNO: " + tmp_sThisInternalName + ", SNO=" + tmp_iThisActorSNO.ToString()); } // Blacklist this if it's something we should never bother looking at again if (bBlacklistThis) { hashRGUIDIgnoreBlacklist.Add(tmp_iThisRActorGuid); continue; } if (iMinDistance <= 0 || tmp_fCentreDistance > iMinDistance) { continue; } // Bag it! tmp_fThisRadius = 4f; bWantThis = true; break; } // Object switch on type (to seperate shrines, destructibles, barricades etc.) break; } // Main huge switch on object type (core types - units, items, objects) // Nothing we want this loop! if (bWantThis) { listGilesObjectCache.Add(new GilesObject(tmp_vThisPosition, tmp_ThisGilesObjectType, tmp_dThisWeight, tmp_fCentreDistance, tmp_fRadiusDistance, tmp_sThisInternalName, tmp_iThisACDGUID, tmp_iThisRActorGuid, tmp_iThisDynamicID, tmp_iThisBalanceID, tmp_iThisActorSNO, tmp_item_iThisLevel, tmp_item_iThisGoldAmount, tmp_item_bThisOneHanded, tmp_item_ThisQuality, tmp_item_ThisDBItemType, tmp_item_ThisFollowerType, tmp_item_ThisGilesItemType, tmp_unit_bThisElite, tmp_unit_bThisRare, tmp_unit_bThisUnique, tmp_unit_bThisMinion, tmp_unit_bThisTreasureGoblin, tmp_unit_bThisBoss, tmp_unit_bThisAttackable, tmp_unit_iThisHitPoints, tmp_fThisRadius, tmp_unit_ThisMonsterSize, tmp_unit_diaUnit, tmp_bThisEliteRareUnique, tmp_bForceLeapAgainst)); } } // Reduce ignore-for-loops counter if (iIgnoreThisForLoops > 0) iIgnoreThisForLoops--; // If we have an avoidance under our feet, then create a new object which contains a safety point to move to // But only if we aren't force-cancelling avoidance for XX time bool bFoundSafeSpot = false; // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin! if (bRequireAvoidance && (!bAnyTreasureGoblinsPresent || settings.iTreasureGoblinPriority <= 2) && DateTime.Now.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= iMillisecondsCancelledEmergencyMoveFor) { Vector3 vAnySafePoint = FindSafeZone(false, 1, vSafePointNear); // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach if (vAnySafePoint != vNullLocation) { bFoundSafeSpot = true; targetCurrent = new GilesObject(vAnySafePoint, GilesObjectType.Avoidance, 20000, Vector3.Distance(playerStatus.vCurrentPosition, vAnySafePoint), Vector3.Distance(playerStatus.vCurrentPosition, vAnySafePoint), "GilesSafePoint"); } else { // Didn't find any safe spot we could reach, so don't look for any more safe spots for at least 2.8 seconds iMillisecondsCancelledEmergencyMoveFor = 2800; timeCancelledEmergencyMove = DateTime.Now; } } // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Give weights to objects ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** bool bNeedToKite = false; bool bShouldTryKiting = false; // Special flag for special whirlwind circumstances bAnyNonWWIgnoreMobsInRange = false; // Now give each object a weight *IF* we aren't skipping direcly to a safe-spot if (!bFoundSafeSpot) { // Store if we are ignoring all units this cycle or not bool bIgnoreAllUnits = !bAnyChampionsPresent && !bAnyMobsInCloseRange && ((!bAnyTreasureGoblinsPresent && settings.iTreasureGoblinPriority >= 2) || settings.iTreasureGoblinPriority < 2) && playerStatus.dCurrentHealthPct >= 0.85d; bool bPrioritizeCloseRange = (bForceCloseRangeTarget || playerStatus.bIsRooted); bool bIsBerserked = GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker); foreach (GilesObject thisgilesobject in listGilesObjectCache) { // Just to make sure each one starts at 0 weight... thisgilesobject.dThisWeight = 0d; // Now do different calculations based on the object type switch (thisgilesobject.ThisGilesObjectType) { case GilesObjectType.Unit: // ************************************************************ // ***** Weight Units ***** // ************************************************************ // No champions, no mobs nearby, no treasure goblins to prioritize, and not injured, so skip mobs if (bIgnoreAllUnits) { break; } // Total up monsters at various ranges if (thisgilesobject.fRadiusDistance <= 50f) { bool bCountAsElite = (thisgilesobject.bThisEliteRareUnique || thisgilesobject.bThisBoss); //intell -- removed thisgilesobject.bThisTreasureGoblin // Flag up any bosses in range if (thisgilesobject.bThisBoss) bAnyBossesInRange = true; if (thisgilesobject.fRadiusDistance <= 6f) { iAnythingWithinRange[RANGE_6]++; if (bCountAsElite) iElitesWithinRange[RANGE_6]++; } if (thisgilesobject.fRadiusDistance <= 12f) { iAnythingWithinRange[RANGE_12]++; if (bCountAsElite) iElitesWithinRange[RANGE_12]++; } if (thisgilesobject.fRadiusDistance <= 15f) { iAnythingWithinRange[RANGE_15]++; if (bCountAsElite) iElitesWithinRange[RANGE_15]++; } if (thisgilesobject.fRadiusDistance <= 20f) { iAnythingWithinRange[RANGE_20]++; if (bCountAsElite) iElitesWithinRange[RANGE_20]++; } if (thisgilesobject.fRadiusDistance <= 25f) { if (!bAnyNonWWIgnoreMobsInRange && !hashActorSNOWhirlwindIgnore.Contains(thisgilesobject.iThisActorSNO)) bAnyNonWWIgnoreMobsInRange = true; iAnythingWithinRange[RANGE_25]++; if (bCountAsElite) iElitesWithinRange[RANGE_25]++; } if (thisgilesobject.fRadiusDistance <= 30f) { iAnythingWithinRange[RANGE_30]++; if (bCountAsElite) iElitesWithinRange[RANGE_30]++; } if (thisgilesobject.fRadiusDistance <= 40f) { iAnythingWithinRange[RANGE_40]++; if (bCountAsElite) iElitesWithinRange[RANGE_40]++; } if (thisgilesobject.fRadiusDistance <= 50f) { iAnythingWithinRange[RANGE_50]++; if (bCountAsElite) iElitesWithinRange[RANGE_50]++; } } // Force a close range target because we seem to be stuck *OR* if not ranged and currently rooted if (bPrioritizeCloseRange) { thisgilesobject.dThisWeight = 20000 - (Math.Floor(thisgilesobject.fCentreDistance) * 200); // Goblin priority KAMIKAZEEEEEEEE if (thisgilesobject.bThisTreasureGoblin && settings.iTreasureGoblinPriority == 3) thisgilesobject.dThisWeight += 25000; } else { // Not attackable, could be shielded, make super low priority if (!thisgilesobject.bThisAttackable) { // Only 500 weight helps prevent it being prioritized over an unshielded thisgilesobject.dThisWeight = 500; } // Not forcing close-ranged targets from being stuck, so let's calculate a weight! else { // Starting weight of 5000 to beat a lot of crap weight stuff thisgilesobject.dThisWeight = 5000; // Distance as a percentage of max radius gives a value up to 1000 (1000 would be point-blank range) if (thisgilesobject.fRadiusDistance < iCurrentMaxKillRadius) thisgilesobject.dThisWeight += (1200 * (1 - (thisgilesobject.fRadiusDistance / iCurrentMaxKillRadius))); // Give extra weight to ranged enemies if ((iMyCachedActorClass == ActorClass.Barbarian || iMyCachedActorClass == ActorClass.Monk) && (thisgilesobject.unit_thisMonsterSize == MonsterSize.Ranged || hashActorSNORanged.Contains(tmp_iThisActorSNO))) { thisgilesobject.dThisWeight += 1100; thisgilesobject.bForceLeapAgainst = true; } // Give more weight to elites and minions //intell -- no weight for uber elites (key wardens), they already got 200 radius kill if ((thisgilesobject.bThisEliteRareUnique || thisgilesobject.bThisMinion) && tmp_iThisActorSNO != 256015 && tmp_iThisActorSNO != 256000 && tmp_iThisActorSNO != 255996) thisgilesobject.dThisWeight += 2000; // Give more weight to bosses if (thisgilesobject.bThisBoss) thisgilesobject.dThisWeight += 4000; // Barbarians with wrath of the berserker up should prioritize elites more if (bIsBerserked && (thisgilesobject.bThisEliteRareUnique || thisgilesobject.bThisTreasureGoblin || thisgilesobject.bThisBoss)) thisgilesobject.dThisWeight += 2000; // Swarmers/boss-likes get more weight if (thisgilesobject.unit_thisMonsterSize == MonsterSize.Swarm || thisgilesobject.unit_thisMonsterSize == MonsterSize.Boss) thisgilesobject.dThisWeight += 900; // Standard/big get a small bonus incase of "unknown" monster types being present if (thisgilesobject.unit_thisMonsterSize == MonsterSize.Standard || thisgilesobject.unit_thisMonsterSize == MonsterSize.Big) thisgilesobject.dThisWeight += 150; // Lower health gives higher weight - health is worth up to 300 extra weight if (thisgilesobject.iThisHitPoints < 0.20) thisgilesobject.dThisWeight += (300 * (1 - (thisgilesobject.iThisHitPoints / 0.5))); // Elites on low health get extra priority - up to 1500 if ((thisgilesobject.bThisEliteRareUnique || thisgilesobject.bThisTreasureGoblin) && thisgilesobject.iThisHitPoints < 0.20) thisgilesobject.dThisWeight += (1500 * (1 - (thisgilesobject.iThisHitPoints / 0.45))); // Goblins on low health get extra priority - up to 2500 if (settings.iTreasureGoblinPriority >= 2 && thisgilesobject.bThisTreasureGoblin && thisgilesobject.iThisHitPoints <= 0.98) thisgilesobject.dThisWeight += (3000 * (1 - (thisgilesobject.iThisHitPoints / 0.85))); // Bonuses to priority type monsters from the dictionary/hashlist set at the top of the code int iExtraPriority; if (dictActorSNOPriority.TryGetValue(thisgilesobject.iThisActorSNO, out iExtraPriority)) { thisgilesobject.dThisWeight += iExtraPriority; } // Close range get higher weights the more of them there are, to prevent body-blocking // Plus a free bonus to anything close anyway if (thisgilesobject.fRadiusDistance <= 11f) { // Extra bonus for point-blank range iUnitsSurrounding++; // Give special "surrounded" weight to each unit thisgilesobject.dThisWeight += (200 * iUnitsSurrounding); } // Special additional weight for corrupt growths in act 4 ONLY if they are at close range (not a standard priority thing) if ((thisgilesobject.iThisActorSNO == 210120 || thisgilesobject.iThisActorSNO == 210268) && thisgilesobject.fCentreDistance <= 25f) thisgilesobject.dThisWeight += 2000; // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f) thisgilesobject.dThisWeight += 400; // Lower the priority for EACH AOE *BETWEEN* us and the target, NOT counting the one directly under-foot, up to a maximum of 1500 reduction Vector3 point = thisgilesobject.vThisPosition; float fWeightRemoval = 0; foreach (GilesObstacle tempobstacle in hashAvoidanceObstacleCache.Where(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point) && cp.vThisLocation.Distance(point) > dictAvoidanceRadius[cp.iThisSNOID])) { fWeightRemoval += (float)tempobstacle.dThisWeight * 8; } if (fWeightRemoval > 1500) fWeightRemoval = 1500; thisgilesobject.dThisWeight -= fWeightRemoval; // Lower the priority if there is AOE *UNDER* the target, by the HIGHEST weight there only fWeightRemoval = 0; foreach (GilesObstacle tempobstacle in hashAvoidanceObstacleCache.Where(cp => cp.vThisLocation.Distance(point) <= dictAvoidanceRadius[cp.iThisSNOID] && cp.vThisLocation.Distance(playerStatus.vCurrentPosition) <= (thisgilesobject.fRadiusDistance - 4f))) { // Up to 200 weight for a high-priority AOE - maximum 3400 weight reduction if (tempobstacle.dThisWeight > fWeightRemoval) fWeightRemoval = (float)tempobstacle.dThisWeight * 30; } thisgilesobject.dThisWeight -= fWeightRemoval; // Prevent going less than 300 yet to prevent annoyances (should only lose this much weight from priority reductions in priority list?) if (thisgilesobject.dThisWeight < 300) thisgilesobject.dThisWeight = 300; // Deal with treasure goblins - note, of priority is set to "0", then the is-a-goblin flag isn't even set for use here - the monster is ignored if (thisgilesobject.bThisTreasureGoblin) { // Logging goblin sightings if (lastGoblinTime == DateTime.Today) { iTotalNumberGoblins++; lastGoblinTime = DateTime.Now; Logging.Write("[GilesTrinity] Goblin #" + iTotalNumberGoblins.ToString() + " in sight. Distance=" + thisgilesobject.fCentreDistance); } else { if (DateTime.Now.Subtract(lastGoblinTime).TotalMilliseconds > 30000) lastGoblinTime = DateTime.Today; } // Original Trinity stuff for priority handling now switch (settings.iTreasureGoblinPriority) { case 1: // Treating goblins as "normal monsters". Ok so I lied a little in the config, they get a little extra weight really! ;) thisgilesobject.dThisWeight += 751; break; case 2: // Super-high priority option below... thisgilesobject.dThisWeight += 10101; break; case 3: // KAMIKAZE SUICIDAL TREASURE GOBLIN RAPE AHOY! thisgilesobject.dThisWeight += 40000; break; // PS: 58008 is an awesome number on any calculator. } } } // Forcing close range target or not? } // This is an attackable unit break; case GilesObjectType.Item: case GilesObjectType.Gold: // ************************************************************ // ***** Weight Items ***** // ************************************************************ // We'll weight them based on distance, giving gold less weight and close objects more if (thisgilesobject.iThisGoldAmount > 0) thisgilesobject.dThisWeight = 11000d - (Math.Floor(thisgilesobject.fCentreDistance) * 200d); else thisgilesobject.dThisWeight = 13000d - (Math.Floor(thisgilesobject.fCentreDistance) * 190d); // Point-blank items get a weight increase if (thisgilesobject.iThisGoldAmount <= 0 && thisgilesobject.fCentreDistance <= 12f) thisgilesobject.dThisWeight += 600d; // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f) thisgilesobject.dThisWeight += 600; // Give yellows more weight if (thisgilesobject.iThisGoldAmount <= 0 && thisgilesobject.ThisQuality >= ItemQuality.Rare4) thisgilesobject.dThisWeight += 6000d; // Give legendaries more weight if (thisgilesobject.iThisGoldAmount <= 0 && thisgilesobject.ThisQuality >= ItemQuality.Legendary) thisgilesobject.dThisWeight += 10000d; // Are we prioritizing close-range stuff atm? If so limit it at a value 3k lower than monster close-range priority if (bPrioritizeCloseRange) thisgilesobject.dThisWeight = 18000 - (Math.Floor(thisgilesobject.fCentreDistance) * 200); // If there's a monster in the path-line to the item, reduce the weight by 25% if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight *= 0.75; // See if there's any AOE avoidance in that spot or inbetween us, if so reduce the weight to 1 if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight = 1; // Calculate a spot reaching a little bit further out from the item, to help pickup-movements /*if (thisgilesobject.dThisWeight > 0) { if (thisgilesobject.iThisGoldAmount > 0) thisgilesobject.vThisPosition = MathEx.CalculatePointFrom(thisgilesobject.vThisPosition, playerStatus.vCurrentPosition, thisgilesobject.fCentreDistance + 2f); else thisgilesobject.vThisPosition = MathEx.CalculatePointFrom(thisgilesobject.vThisPosition, playerStatus.vCurrentPosition, thisgilesobject.fCentreDistance + 1f); }*/ break; case GilesObjectType.Globe: // ************************************************************ // ***** Weight Health Globes ***** // ************************************************************ // Give all globes 0 weight (so never gone-to), unless we have low health, then go for them if (playerStatus.dCurrentHealthPct > iEmergencyHealthGlobeLimit || !settings.bEnableGlobes) { thisgilesobject.dThisWeight = 0; } else { // Ok we have globes enabled, and our health is low...! thisgilesobject.dThisWeight = 17000d - (Math.Floor(thisgilesobject.fCentreDistance) * 90d); // Point-blank items get a weight increase if (thisgilesobject.fCentreDistance <= 15f) thisgilesobject.dThisWeight += 3000d; // Close items get a weight increase if (thisgilesobject.fCentreDistance <= 60f) thisgilesobject.dThisWeight += 1500d; // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f) thisgilesobject.dThisWeight += 400; // Are we prioritizing close-range stuff atm? If so limit it at a value 3k lower than monster close-range priority //if (bPrioritizeCloseRange) // thisgilesobject.dThisWeight = 22000 - (Math.Floor(thisgilesobject.fCentreDistance) * 200); // If there's a monster in the path-line to the item, reduce the weight by 15% for each Vector3 point = thisgilesobject.vThisPosition; foreach (GilesObstacle tempobstacle in hashMonsterObstacleCache.Where(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point))) { thisgilesobject.dThisWeight *= 0.85; } // See if there's any AOE avoidance in that spot, if so reduce the weight by 10% if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight *= 0.9; // Calculate a spot reaching a little bit further out from the globe, to help globe-movements if (thisgilesobject.dThisWeight > 0) thisgilesobject.vThisPosition = MathEx.CalculatePointFrom(thisgilesobject.vThisPosition, playerStatus.vCurrentPosition, thisgilesobject.fCentreDistance + 3f); } break; case GilesObjectType.Shrine: // ************************************************************ // ***** Weight Shrines ***** // ************************************************************ thisgilesobject.dThisWeight = 14500d - (Math.Floor(thisgilesobject.fCentreDistance) * 170d); // Very close shrines get a weight increase if (thisgilesobject.fCentreDistance <= 20f) thisgilesobject.dThisWeight += 1000d; switch (thisgilesobject.iThisActorSNO) { case 138989: // health pool if (playerStatus.dCurrentHealthPct >= .7) thisgilesobject.dThisWeight = 0; else thisgilesobject.dThisWeight += 4000d; break; case 176074: // protection shrine break; case 176076: // fortune shrine break; case 176077: // frenzied shrine break; } if (thisgilesobject.dThisWeight > 0) { // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f) thisgilesobject.dThisWeight += 400; // Are we prioritizing close-range stuff atm? If so limit it at a value 3k lower than monster close-range priority if (bPrioritizeCloseRange) thisgilesobject.dThisWeight = 18500d - (Math.Floor(thisgilesobject.fCentreDistance) * 200); // If there's a monster in the path-line to the item, reduce the weight by 25% if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight *= 0.75; // See if there's any AOE avoidance in that spot, if so reduce the weight to 1 if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight = 1; } break; case GilesObjectType.Destructible: case GilesObjectType.Barricade: // ************************************************************ // ***** Weight Destructibles ***** // ************************************************************ thisgilesobject.dThisWeight = 1750d - (Math.Floor(thisgilesobject.fCentreDistance) * 175d); //intell // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f) thisgilesobject.dThisWeight += 400; // Close destructibles get a weight increase if (thisgilesobject.fCentreDistance <= 16f) thisgilesobject.dThisWeight += 1500d; // If there's a monster in the path-line to the item, reduce the weight by 50% if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight *= 0.5; // See if there's any AOE avoidance in that spot, if so reduce the weight to 1 if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight = 1; // Are we prioritizing close-range stuff atm? If so limit it at a value 3k lower than monster close-range priority if (bPrioritizeCloseRange) thisgilesobject.dThisWeight = 19200d - (Math.Floor(thisgilesobject.fCentreDistance) * 200d); // Very close destructibles get a final weight increase if (thisgilesobject.fCentreDistance <= 6f) //intell thisgilesobject.dThisWeight += 2000d; break; case GilesObjectType.Interactable: // ************************************************************ // ***** Weight Interactable Specials ***** // ************************************************************ // Very close interactables get a weight increase thisgilesobject.dThisWeight = 15000d - (Math.Floor(thisgilesobject.fCentreDistance) * 170d); if (thisgilesobject.fCentreDistance <= 12f) thisgilesobject.dThisWeight += 800d; // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f) thisgilesobject.dThisWeight += 400; // If there's a monster in the path-line to the item, reduce the weight by 50% if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight *= 0.5; // See if there's any AOE avoidance in that spot, if so reduce the weight to 1 if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight = 1; break; case GilesObjectType.Container: // ************************************************************ // ***** Weight Containers ***** // ************************************************************ // Very close containers get a weight increase thisgilesobject.dThisWeight = 11000d - (Math.Floor(thisgilesobject.fCentreDistance) * 190d); if (thisgilesobject.fCentreDistance <= 12f) thisgilesobject.dThisWeight += 600d; // Was already a target and is still viable, give it some free extra weight, to help stop flip-flopping between two targets if (thisgilesobject.iThisRActorGuid == iCurrentTargetRactorGUID && thisgilesobject.fCentreDistance <= 25f) thisgilesobject.dThisWeight += 400; // If there's a monster in the path-line to the item, reduce the weight by 50% if (hashMonsterObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight *= 0.5; // See if there's any AOE avoidance in that spot, if so reduce the weight to 1 if (hashAvoidanceObstacleCache.Any(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, thisgilesobject.vThisPosition))) thisgilesobject.dThisWeight = 1; break; } // Switch on object type // Force the character to stay where it is if there is nothing available that is out of avoidance stuff and we aren't already in avoidance stuff if (thisgilesobject.dThisWeight == 1 && !bRequireAvoidance) { thisgilesobject.dThisWeight = 0; bStayPutDuringAvoidance = true; } // Is the weight of this one higher than the current-highest weight? Then make this the new primary target! if (thisgilesobject.dThisWeight > iHighestWeightFound && thisgilesobject.dThisWeight > 0) { // Clone the current Giles-cache object targetCurrent = thisgilesobject.Clone(); iHighestWeightFound = thisgilesobject.dThisWeight; // See if we can try attempting kiting later bNeedToKite = false; vKitePointAvoid = vNullLocation; if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit) { Vector3 point = targetCurrent.vThisPosition; if (hashAvoidanceObstacleCache.Any(cp => cp.vThisLocation.Distance(point) <= (dictAvoidanceRadius[cp.iThisSNOID] * 1.2) && cp.vThisLocation.Distance(playerStatus.vCurrentPosition) <= (thisgilesobject.fRadiusDistance - 4f))) { vKitePointAvoid = targetCurrent.vThisPosition; bNeedToKite = true; } } } } // Loop through all the objects and give them a weight if (targetCurrent != null && targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && iKiteDistance > 0 && (iMyCachedActorClass != ActorClass.Wizard || (iMyCachedActorClass == ActorClass.Wizard && (!settings.bKiteOnlyArchon || GilesHasBuff(SNOPower.Wizard_Archon))))) { if (targetCurrent.fRadiusDistance <= iKiteDistance) { bShouldTryKiting = true; } } // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin! if ((bShouldTryKiting || bNeedToKite) && (!bAnyTreasureGoblinsPresent || settings.iTreasureGoblinPriority <= 2) && DateTime.Now.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= iMillisecondsCancelledEmergencyMoveFor && (DateTime.Now.Subtract(timeCancelledKiteMove).TotalMilliseconds >= iMillisecondsCancelledKiteMoveFor || (DateTime.Now.Subtract(timeCancelledKiteMove).TotalMilliseconds >= 2500 && bNeedToKite))) { Vector3 vAnySafePoint = FindSafeZone(false, 1, vKitePointAvoid, true); // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach if (vAnySafePoint != vNullLocation) { //Logging.Write("Found a kiting spot, attempting to kite!"); targetCurrent = new GilesObject(vAnySafePoint, GilesObjectType.Avoidance, 20000, Vector3.Distance(playerStatus.vCurrentPosition, vAnySafePoint), Vector3.Distance(playerStatus.vCurrentPosition, vAnySafePoint), "GilesKiting"); timeCancelledKiteMove = DateTime.Today; iMillisecondsCancelledKiteMoveFor = 5000; } else { // Didn't find any kiting we could reach, so don't look for any more kite spots for at least 1.5 seconds timeCancelledKiteMove = DateTime.Today; iMillisecondsCancelledKiteMoveFor = 2500; } } } // Not heading straight for a safe-spot? // No valid targets but we were told to stay put? if (targetCurrent == null && bStayPutDuringAvoidance && !bRequireAvoidance) { targetCurrent = new GilesObject(playerStatus.vCurrentPosition, GilesObjectType.Avoidance, 20000, 2f, 2f, "GilesStayPutPoint"); } // Still no target, let's see if we should backtrack or wait for wrath to come off cooldown... if (targetCurrent == null) { // See if we should wait for [playersetting] milliseconds for possible loot drops before continuing run if (DateTime.Now.Subtract(lastHadUnitInSights).TotalMilliseconds <= settings.iKillLootDelay && DateTime.Now.Subtract(lastHadEliteUnitInSights).TotalMilliseconds <= 10000) { targetCurrent = new GilesObject(playerStatus.vCurrentPosition, GilesObjectType.Avoidance, 20000, 2f, 2f, "GilesWaitForLootDrops"); } // Now see if we need to do any backtracking if (targetCurrent == null && iTotalBacktracks >= 2 && settings.bEnableBacktracking && !playerStatus.bIsInTown) // Never bother with the 1st backtrack position nor if we are in town { // See if we're already within 18 feet of our start position first if (Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[1]) <= 18f) { vBacktrackList = new SortedList(); iTotalBacktracks = 0; } // See if we can raytrace to the final location and it's within 25 feet if (iTotalBacktracks >= 2 && Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[1]) <= 25f && GilesCanRayCast(playerStatus.vCurrentPosition, vBacktrackList[1], NavCellFlags.AllowWalk)) { vBacktrackList = new SortedList(); iTotalBacktracks = 0; } if (iTotalBacktracks >= 2) { // See if we can skip to the next backtracker location first if (iTotalBacktracks >= 3) { if (Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks - 1]) <= 10f) { vBacktrackList.Remove(iTotalBacktracks); iTotalBacktracks--; } } targetCurrent = new GilesObject(vBacktrackList[iTotalBacktracks], GilesObjectType.Backtrack, 20000, Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks]), Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks]), "GilesBacktrack"); } } else { vBacktrackList = new SortedList(); iTotalBacktracks = 0; } // End of backtracking check // Finally, a special check for waiting for wrath of the berserker cooldown before engaging Azmodan if (targetCurrent == null && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_WrathOfTheBerserker) && settings.bWaitForWrath && !GilesUseTimer(SNOPower.Barbarian_WrathOfTheBerserker) && ZetaDia.CurrentWorldId == 121214 && (Vector3.Distance(playerStatus.vCurrentPosition, new Vector3(711.25f, 716.25f, 80.13903f)) <= 40f || Vector3.Distance(playerStatus.vCurrentPosition, new Vector3(546.8467f, 551.7733f, 1.576313f)) <= 40f)) { bDontSpamOutofCombat = true; Logging.Write("[GilesTrinity] Waiting for Wrath Of The Berserker cooldown before continuing to Azmodan."); targetCurrent = new GilesObject(playerStatus.vCurrentPosition, GilesObjectType.Avoidance, 20000, 2f, 2f, "GilesWaitForWrath"); } // And a special check for wizard archon if (targetCurrent == null && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon) && !GilesUseTimer(SNOPower.Wizard_Archon) && settings.bWaitForArchon && ZetaDia.CurrentWorldId == 121214 && (Vector3.Distance(playerStatus.vCurrentPosition, new Vector3(711.25f, 716.25f, 80.13903f)) <= 40f || Vector3.Distance(playerStatus.vCurrentPosition, new Vector3(546.8467f, 551.7733f, 1.576313f)) <= 40f)) { Logging.Write("[GilesTrinity] Waiting for Wizard Archon cooldown before continuing to Azmodan."); targetCurrent = new GilesObject(playerStatus.vCurrentPosition, GilesObjectType.Avoidance, 20000, 2f, 2f, "GilesWaitForArchon"); } // And a very sexy special check for WD BigBadVoodoo if (targetCurrent == null && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_BigBadVoodoo) && !PowerManager.CanCast(SNOPower.Witchdoctor_BigBadVoodoo) && ZetaDia.CurrentWorldId == 121214 && (Vector3.Distance(playerStatus.vCurrentPosition, new Vector3(711.25f, 716.25f, 80.13903f)) <= 40f || Vector3.Distance(playerStatus.vCurrentPosition, new Vector3(546.8467f, 551.7733f, 1.576313f)) <= 40f)) { Logging.Write("[GilesTrinity] Waiting for WD BigBadVoodoo cooldown before continuing to Azmodan."); targetCurrent = new GilesObject(playerStatus.vCurrentPosition, GilesObjectType.Avoidance, 20000, 2f, 2f, "GilesWaitForVoodooo"); } } // Still no target, let's end it all! if (targetCurrent == null) { return; } // Ok record the time we last saw any unit at all if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit) { lastHadUnitInSights = DateTime.Now; // And record when we last saw any form of elite if (targetCurrent.bThisBoss || targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin) lastHadEliteUnitInSights = DateTime.Now; } // Record the last time our target changed etc. if (iCurrentTargetRactorGUID != targetCurrent.iThisRActorGuid) { dateSincePickedTarget = DateTime.Now; iTargetLastHealth = 0f; } else { // We're sticking to the same target, so update the target's health cache to check for stucks if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit) { // Check if the health has changed, if so update the target-pick time before we blacklist them again if (targetCurrent.iThisHitPoints != iTargetLastHealth) { dateSincePickedTarget = DateTime.Now; } // Now store the target's last-known health iTargetLastHealth = targetCurrent.iThisHitPoints; } } } // Refresh object list from Diablo 3 memory RefreshDiaObjects() // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Replace DB's actual core Behavior-Tree with Giles functions ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Blank decorator and action to wipe some of DB's behavior trees ***** // ********************************************************************************************** private static void GilesTrinityStart(IBot bot) { if (!bPluginEnabled && bot != null) { Logging.Write("****************************************************"); Logging.Write("WARNING: Giles Trinity is NOT YET ENABLED. Bot start detected"); Logging.Write("****************************************************"); Logging.Write("Ignore this message if you are not currently using Giles Trinity."); return; } // Recording of all the XML's in use this run try { string sThisProfile = Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile; if (sThisProfile != sLastProfileSeen) { listProfilesLoaded.Add(sThisProfile); sLastProfileSeen = sThisProfile; if (sFirstProfileSeen == "") sFirstProfileSeen = sThisProfile; } } catch { } // Update actors if possible (if already in-game) if (ZetaDia.IsInGame && !ZetaDia.IsLoadingWorld) { ZetaDia.Actors.Update(); } bMappedPlayerAbilities = false; if (!bMaintainStatTracking) { ItemStatsWhenStartedBot = DateTime.Now; ItemStatsLastPostedReport = DateTime.Now; bMaintainStatTracking = true; } else { Log("Note: Maintaining item stats from previous run. To reset stats fully, please restart DB."); } foreach (var hook in TreeHooks.Instance.Hooks) { // Replace the combat behavior tree, as that happens first and so gets done quicker! if (hook.Key.Contains("Combat")) { // Replace the pause just after identify stuff to ensure we wait before trying to run to vendor etc. CanRunDecoratorDelegate canRunDelegateCombatTargetCheck = new CanRunDecoratorDelegate(GilesGlobalOverlord); ActionDelegate actionDelegateCoreTarget = new ActionDelegate(GilesHandleTarget); Sequence sequencecombat = new Sequence( new Zeta.TreeSharp.Action(actionDelegateCoreTarget) ); hook.Value[0] = new Zeta.TreeSharp.Decorator(canRunDelegateCombatTargetCheck, sequencecombat); } // Vendor run hook // Wipe the vendorrun and loot behavior trees, since we no longer want them if (hook.Key.Contains("VendorRun")) { Zeta.TreeSharp.Decorator GilesDecorator = hook.Value[0] as Zeta.TreeSharp.Decorator; PrioritySelector GilesReplacement = GilesDecorator.Children[0] as PrioritySelector; // Replace the pause just after identify stuff to ensure we wait before trying to run to vendor etc. CanRunDecoratorDelegate canRunDelegateStashGilesPreStashPause = new CanRunDecoratorDelegate(GilesPreStashPauseOverlord); ActionDelegate actionDelegatePrePause = new ActionDelegate(GilesStashPrePause); ActionDelegate actionDelegatePause = new ActionDelegate(GilesStashPause); Sequence sequencepause = new Sequence( new Zeta.TreeSharp.Action(actionDelegatePrePause), new Zeta.TreeSharp.Action(actionDelegatePause) ); GilesReplacement.Children[3] = new Zeta.TreeSharp.Decorator(canRunDelegateStashGilesPreStashPause, sequencepause); // Replace DB stashing behavior tree with my optimized version with loot rule replacement CanRunDecoratorDelegate canRunDelegateStashGilesOverlord = new CanRunDecoratorDelegate(GilesStashOverlord); ActionDelegate actionDelegatePreStash = new ActionDelegate(GilesOptimisedPreStash); ActionDelegate actionDelegateStashing = new ActionDelegate(GilesOptimisedStash); ActionDelegate actionDelegatePostStash = new ActionDelegate(GilesOptimisedPostStash); Sequence sequencestash = new Sequence( new Zeta.TreeSharp.Action(actionDelegatePreStash), new Zeta.TreeSharp.Action(actionDelegateStashing), new Zeta.TreeSharp.Action(actionDelegatePostStash), new Sequence( new Zeta.TreeSharp.Action(actionDelegatePrePause), new Zeta.TreeSharp.Action(actionDelegatePause) ) ); GilesReplacement.Children[4] = new Zeta.TreeSharp.Decorator(canRunDelegateStashGilesOverlord, sequencestash); // Replace DB vendoring behavior tree with my optimized & "one-at-a-time" version CanRunDecoratorDelegate canRunDelegateSellGilesOverlord = new CanRunDecoratorDelegate(GilesSellOverlord); ActionDelegate actionDelegatePreSell = new ActionDelegate(GilesOptimisedPreSell); ActionDelegate actionDelegateSell = new ActionDelegate(GilesOptimisedSell); ActionDelegate actionDelegatePostSell = new ActionDelegate(GilesOptimisedPostSell); Sequence sequenceSell = new Sequence( new Zeta.TreeSharp.Action(actionDelegatePreSell), new Zeta.TreeSharp.Action(actionDelegateSell), new Zeta.TreeSharp.Action(actionDelegatePostSell), new Sequence( new Zeta.TreeSharp.Action(actionDelegatePrePause), new Zeta.TreeSharp.Action(actionDelegatePause) ) ); GilesReplacement.Children[5] = new Zeta.TreeSharp.Decorator(canRunDelegateSellGilesOverlord, sequenceSell); // Replace DB salvaging behavior tree with my optimized & "one-at-a-time" version CanRunDecoratorDelegate canRunDelegateSalvageGilesOverlord = new CanRunDecoratorDelegate(GilesSalvageOverlord); ActionDelegate actionDelegatePreSalvage = new ActionDelegate(GilesOptimisedPreSalvage); ActionDelegate actionDelegateSalvage = new ActionDelegate(GilesOptimisedSalvage); ActionDelegate actionDelegatePostSalvage = new ActionDelegate(GilesOptimisedPostSalvage); Sequence sequenceSalvage = new Sequence( new Zeta.TreeSharp.Action(actionDelegatePreSalvage), new Zeta.TreeSharp.Action(actionDelegateSalvage), new Zeta.TreeSharp.Action(actionDelegatePostSalvage), new Sequence( new Zeta.TreeSharp.Action(actionDelegatePrePause), new Zeta.TreeSharp.Action(actionDelegatePause) ) ); GilesReplacement.Children[6] = new Zeta.TreeSharp.Decorator(canRunDelegateSalvageGilesOverlord, sequenceSalvage); CanRunDecoratorDelegate canRunDelegateGilesTownRunCheck = new CanRunDecoratorDelegate(GilesTownRunCheckOverlord); hook.Value[0] = new Zeta.TreeSharp.Decorator(canRunDelegateGilesTownRunCheck, new PrioritySelector(GilesReplacement)); } // Vendor run hook if (hook.Key.Contains("Loot")) { // Replace the loot behavior tree with a blank one, as we no longer need it CanRunDecoratorDelegate canRunDelegateBlank = new CanRunDecoratorDelegate(GilesBlankDecorator); ActionDelegate actionDelegateBlank = new ActionDelegate(GilesBlankAction); Sequence sequenceblank = new Sequence( new Zeta.TreeSharp.Action(actionDelegateBlank) ); hook.Value[0] = new Zeta.TreeSharp.Decorator(canRunDelegateBlank, sequenceblank); } // Vendor run hook } } // ********************************************************************************************** // ***** Blank decorator and action to wipe some of DB's behavior trees ***** // ********************************************************************************************** private static bool GilesBlankDecorator(object ret) { return false; } private static RunStatus GilesBlankAction(object ret) { return RunStatus.Success; } // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Find fresh targets, start main BT if needed, cast any buffs needed etc. ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** private static bool GilesGlobalOverlord(object ret) { // If we aren't in the game of a world is loading, don't do anything yet if (!ZetaDia.IsInGame || ZetaDia.IsLoadingWorld) { lastChangedZigZag = DateTime.Today; vPositionLastZigZagCheck = Vector3.Zero; return false; } // Big main-bot pause button if (bMainBotPaused) { return true; } // World ID safety caching incase it's ever unavailable if (ZetaDia.CurrentWorldDynamicId != -1) iCurrentWorldID = ZetaDia.CurrentWorldDynamicId; // Game difficulty, used really for vault on DH's if (ZetaDia.Service.CurrentHero.CurrentDifficulty != GameDifficulty.Invalid) iCurrentGameDifficulty = ZetaDia.Service.CurrentHero.CurrentDifficulty; // Refresh player buffs (to check for archon) GilesRefreshBuffs(); // Store all of the player's abilities every now and then, to keep it cached and handy, also check for critical-mass timer changes etc. iCombatLoops++; if (!bMappedPlayerAbilities || iCombatLoops >= 50 || bRefreshHotbarAbilities) { // Update the cached player's cache ActorClass tempClass = ActorClass.Invalid; try { tempClass = ZetaDia.Actors.Me.ActorClass; } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception trying to get character class."); } if (tempClass != ActorClass.Invalid) iMyCachedActorClass = ZetaDia.Actors.Me.ActorClass; iCombatLoops = 0; GilesRefreshHotbar(GilesHasBuff(SNOPower.Wizard_Archon)); dictAbilityRepeatDelay = new Dictionary(dictAbilityRepeatDefaults); if (settings.bEnableCriticalMass && (iMyCachedActorClass == ActorClass.Wizard || iMyCachedActorClass == ActorClass.WitchDoctor)) { dictAbilityRepeatDelay[SNOPower.Wizard_FrostNova] = 25; dictAbilityRepeatDelay[SNOPower.Wizard_ExplosiveBlast] = 25; dictAbilityRepeatDelay[SNOPower.Wizard_DiamondSkin] = 100; dictAbilityRepeatDelay[SNOPower.Wizard_SlowTime] = 6000; dictAbilityRepeatDelay[SNOPower.Wizard_WaveOfForce] = 1500; dictAbilityRepeatDelay[SNOPower.Wizard_MirrorImage] = 1500; dictAbilityRepeatDelay[SNOPower.Wizard_Archon_ArcaneBlast] = 1500; dictAbilityRepeatDelay[SNOPower.Wizard_Teleport] = 2700; dictAbilityRepeatDelay[SNOPower.Wizard_Archon_SlowTime] = 1500; dictAbilityRepeatDelay[SNOPower.Wizard_Archon_Teleport] = 2700; dictAbilityRepeatDelay[SNOPower.Witchdoctor_SoulHarvest] = 1000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_SpiritWalk] = 1000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_Horrify] = 1000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_Gargantuan] = 20000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_SummonZombieDog] = 20000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_GraspOfTheDead] = 500; dictAbilityRepeatDelay[SNOPower.Witchdoctor_SpiritBarrage] = 2000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_Locust_Swarm] = 2000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_Haunt] = 2000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_Hex] = 3000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_MassConfusion] = 15000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_FetishArmy] = 20000; dictAbilityRepeatDelay[SNOPower.Witchdoctor_BigBadVoodoo] = 20000; } if (settings.bWrath90Seconds && iMyCachedActorClass == ActorClass.Barbarian) { dictAbilityRepeatDelay[SNOPower.Barbarian_Earthquake] = 90500; dictAbilityRepeatDelay[SNOPower.Barbarian_CallOfTheAncients] = 90500; dictAbilityRepeatDelay[SNOPower.Barbarian_WrathOfTheBerserker] = 90500; } // Pick an appropriate health set etc. based on class switch (iMyCachedActorClass) { case ActorClass.Barbarian: // What health % should we use a potion, or look for a globe iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionBarb; iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeBarb; iKiteDistance = settings.iKiteDistanceBarb; // The health percentage to avoid these objects at dictAvoidanceHealth = new Dictionary(dictAvoidanceHealthBarb); break; case ActorClass.Monk: // What health % should we use a potion, or look for a globe iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionMonk; iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeMonk; iKiteDistance = 0; // The health percentage to avoid these objects at dictAvoidanceHealth = new Dictionary(dictAvoidanceHealthMonk); break; case ActorClass.Wizard: // What health % should we use a potion, or look for a globe iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionWiz; iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeWiz; iKiteDistance = settings.iKiteDistanceWiz; // The health percentage to avoid these objects at dictAvoidanceHealth = new Dictionary(dictAvoidanceHealthWizard); break; case ActorClass.WitchDoctor: // What health % should we use a potion, or look for a globe iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionWitch; iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeWitch; iKiteDistance = settings.iKiteDistanceWitch; // The health percentage to avoid these objects at dictAvoidanceHealth = new Dictionary(dictAvoidanceHealthWitch); break; case ActorClass.DemonHunter: // What health % should we use a potion, or look for a globe iEmergencyHealthPotionLimit = settings.dEmergencyHealthPotionDemon; iEmergencyHealthGlobeLimit = settings.dEmergencyHealthGlobeDemon; iKiteDistance = settings.iKiteDistanceDemon; // The health percentage to avoid these objects at dictAvoidanceHealth = new Dictionary(dictAvoidanceHealthDemon); break; } } // Clear target current and reset key variables used during the target-handling function targetCurrent = null; bDontMoveMeIAmDoingShit = false; iTimesBlockedMoving = 0; bAlreadyMoving = false; lastMovementCommand = DateTime.Today; bAvoidDirectionBlacklisting = false; bWaitingForPower = false; bWaitingAfterPower = false; bWaitingForPotion = false; bWasRootedLastTick = false; // Let's calculate whether or not we want a new target list... update at least once every 150 milliseconds in main decorator bool bShouldRefreshDiaObjects = DateTime.Now.Subtract(lastRefreshedObjects).TotalMilliseconds >= 150; if (DateTime.Now.Subtract(TickInformation).TotalMinutes > 1) { Logging.WriteDiagnostic("[GilesTrinity] We still run smoothly"); TickInformation = DateTime.Now; } // So, do we want a new target or not? if (bShouldRefreshDiaObjects) { // Update player data UpdateCachedPlayerData(); // Check for death / player being dead if (playerStatus.dCurrentHealthPct <= 0) { return false; } RefreshDiaObjects(); // Update when we last refreshed with current time lastRefreshedObjects = DateTime.Now; // We have a target, start the target handler! if (targetCurrent != null) { bWholeNewTarget = true; bDontMoveMeIAmDoingShit = true; bPickNewAbilities = true; return true; } } else { // Return false here means we only do all of the below OOC stuff at max once every 150ms return false; } // Pop a potion when necessary if (playerStatus.dCurrentHealthPct <= iEmergencyHealthPotionLimit) { if (!playerStatus.bIsIncapacitated && GilesUseTimer(SNOPower.DrinkHealthPotion)) { ACDItem thisBestPotion = ZetaDia.Me.Inventory.Backpack.Where(i => i.IsPotion).OrderByDescending(p => p.HitpointsGranted).FirstOrDefault(); if (thisBestPotion != null) { WaitWhileAnimating(4, true); ZetaDia.Me.Inventory.UseItem((thisBestPotion.DynamicId)); } dictAbilityLastUse[SNOPower.DrinkHealthPotion] = DateTime.Now; WaitWhileAnimating(3, true); } } // Clear the temporary blacklist every 90 seconds if (DateTime.Now.Subtract(dateSinceTemporaryBlacklistClear).TotalSeconds > 90) { dateSinceTemporaryBlacklistClear = DateTime.Now; hashRGUIDTemporaryIgnoreBlacklist = new HashSet(); } // Clear the full blacklist every 60 seconds if (DateTime.Now.Subtract(dateSinceBlacklistClear).TotalSeconds > 60) { dateSinceBlacklistClear = DateTime.Now; hashRGUIDIgnoreBlacklist = new HashSet(); } if (settings.bDebugInfo && bResetStatusText) { bResetStatusText = false; BotMain.StatusText = "[GilesTrinity] No more targets - DemonBuddy/profile management is now in control"; } // Nothing to do... do we have some maintenance we can do instead, like out of combat buffing? lastChangedZigZag = DateTime.Today; vPositionLastZigZagCheck = Vector3.Zero; // Out of combat buffing etc. but only if we don't want to return to town etc. ACDAnimationInfo myAnimationState = ZetaDia.Me.CommonData.AnimationInfo; if (!playerStatus.bIsInTown && !bWantToTownRun && !bGilesForcedVendoring && myAnimationState != null && myAnimationState.State != AnimationState.Attacking && myAnimationState.State != AnimationState.Casting && myAnimationState.State != AnimationState.Channeling) { bDontSpamOutofCombat = false; powerBuff = GilesAbilitySelector(false, true, false); if (powerBuff.powerThis != SNOPower.None) { WaitWhileAnimating(4, true); ZetaDia.Me.UsePower(powerBuff.powerThis, powerBuff.vTargetLocation, powerBuff.iTargetWorldID, powerBuff.iTargetGUID); powerLastSnoPowerUsed = powerBuff.powerThis; dictAbilityLastUse[powerBuff.powerThis] = DateTime.Now; WaitWhileAnimating(3, true); } } else { // Check if we are portalling to town, if so increase our kill radius temporarily if (myAnimationState != null) { switch (myAnimationState.Current) { case SNOAnim.barbarian_male_HTH_Recall_Channel_01: case SNOAnim.Barbarian_Female_HTH_Recall_Channel_01: case SNOAnim.Monk_Male_recall_channel: case SNOAnim.Monk_Female_recall_channel: case SNOAnim.WitchDoctor_Male_recall_channel: case SNOAnim.WitchDoctor_Female_recall_channel: case SNOAnim.Wizard_Male_HTH_recall_channel: case SNOAnim.Wizard_Female_HTH_recall_channel: case SNOAnim.Demonhunter_Male_HTH_recall_channel: case SNOAnim.Demonhunter_Female_HTH_recall_channel: iKeepKillRadiusExtendedFor = 20; break; } } } // Ok let DemonBuddy do stuff this loop, since we're done for the moment return false; } // ********************************************************************************************** // ***** Update the cached data on the player status - health, location etc. ***** // ********************************************************************************************** private static void UpdateCachedPlayerData() { if (DateTime.Now.Subtract(playerStatus.lastUpdatedPlayer).TotalMilliseconds <= 50) return; // If we aren't in the game of a world is loading, don't do anything yet if (!ZetaDia.IsInGame || ZetaDia.IsLoadingWorld) return; var me = ZetaDia.Me; if (me == null) return; try { playerStatus.lastUpdatedPlayer = DateTime.Now; playerStatus.bIsInTown = me.IsInTown; playerStatus.bIsIncapacitated = (me.IsFeared || me.IsStunned || me.IsFrozen || me.IsBlind); playerStatus.bIsRooted = me.IsRooted; playerStatus.dCurrentHealthPct = me.HitpointsCurrentPct; playerStatus.dCurrentEnergy = me.CurrentPrimaryResource; playerStatus.dCurrentEnergyPct = playerStatus.dCurrentEnergy / me.MaxPrimaryResource; playerStatus.dDiscipline = me.CurrentSecondaryResource; playerStatus.dDisciplinePct = playerStatus.dDiscipline / me.MaxSecondaryResource; playerStatus.vCurrentPosition = me.Position; if (playerStatus.dCurrentEnergy >= iWaitingReservedAmount) playerStatus.bWaitingForReserveEnergy = false; if (playerStatus.dCurrentEnergy < 20) playerStatus.bWaitingForReserveEnergy = true; playerStatus.iMyDynamicID = me.CommonData.DynamicId; playerStatus.iMyLevel = me.Level; } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception for grabbing player data."); } } // ********************************************************************************************** // ***** Quick and Dirty routine just to force a wait until the character is "free" ***** // ********************************************************************************************** public static void WaitWhileAnimating(int iMaxSafetyLoops = 10, bool bWaitForAttacking = false) { bool bKeepLooping = true; int iSafetyLoops = 0; while (bKeepLooping) { iSafetyLoops++; if (iSafetyLoops > iMaxSafetyLoops) bKeepLooping = false; bool bIsAnimating = false; try { ACDAnimationInfo myAnimationState = ZetaDia.Me.CommonData.AnimationInfo; if (myAnimationState == null || myAnimationState.State == AnimationState.Casting || myAnimationState.State == AnimationState.Channeling) bIsAnimating = true; if (bWaitForAttacking && (myAnimationState == null || myAnimationState.State == AnimationState.Attacking)) bIsAnimating = true; } catch { bIsAnimating = true; } if (!bIsAnimating) bKeepLooping = false; } } // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Start the process of moving to target object and dealing with it ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** private static RunStatus GilesHandleTarget(object ret) { // If we aren't in the game of a world is loading, don't do anything yet if (!ZetaDia.IsInGame || ZetaDia.IsLoadingWorld) { return RunStatus.Success; } // Make sure we reset unstucker stuff here GilesPlayerMover.iTimesReachedStuckPoint = 0; GilesPlayerMover.vSafeMovementLocation = Vector3.Zero; GilesPlayerMover.timeLastRecordedPosition = DateTime.Now; // Big main-bot pause button if (bMainBotPaused) { return RunStatus.Success; } // Whether we should refresh the target list or not bool bShouldRefreshDiaObjects = false; // See if we should update hotbar abilities if (bRefreshHotbarAbilities) GilesRefreshHotbar(GilesHasBuff(SNOPower.Wizard_Archon)); // Special pausing *AFTER* using certain powers if (bWaitingAfterPower && powerPrime.iForceWaitLoopsAfter >= 1) { if (powerPrime.iForceWaitLoopsAfter >= 1) powerPrime.iForceWaitLoopsAfter--; if (powerPrime.iForceWaitLoopsAfter <= 0) bWaitingAfterPower = false; return RunStatus.Running; } // Update player-data cache UpdateCachedPlayerData(); // Check for death / player being dead if (playerStatus.dCurrentHealthPct <= 0) { return RunStatus.Success; } // See if we have been "newly rooted", to force target updates if (playerStatus.bIsRooted && !bWasRootedLastTick) { bWasRootedLastTick = true; bForceTargetUpdate = true; } if (!playerStatus.bIsRooted) bWasRootedLastTick = false; // Let's calculate whether or not we want a new target list... if (!bWholeNewTarget && !bWaitingForPower && !bWaitingForPotion) { // Update targets at least once every 80 milliseconds if (bForceTargetUpdate || bTravellingAvoidance || ((DateTime.Now.Subtract(lastRefreshedObjects).TotalMilliseconds >= 80 && targetCurrent.ThisGilesObjectType != GilesObjectType.Avoidance) || DateTime.Now.Subtract(lastRefreshedObjects).TotalMilliseconds >= 800)) { bShouldRefreshDiaObjects = true; } // If we AREN'T getting new targets - find out if we SHOULD because the current unit has died etc. if (!bShouldRefreshDiaObjects && targetCurrent.ThisGilesObjectType == GilesObjectType.Unit) { if (targetCurrent.unit_thisDiaUnit == null || targetCurrent.unit_thisDiaUnit.BaseAddress == IntPtr.Zero) { bShouldRefreshDiaObjects = true; } else { // health calculations double dThisMaxHealth; // Get the max health of this unit, a cached version if available, if not cache it if (!dictGilesMaxHealthCache.TryGetValue(tmp_iThisRActorGuid, out dThisMaxHealth)) { try { dThisMaxHealth = targetCurrent.unit_thisDiaUnit.CommonData.GetAttribute(ActorAttributeType.HitpointsMax); dictGilesMaxHealthCache.Add(tmp_iThisRActorGuid, dThisMaxHealth); } catch { Logging.WriteDiagnostic("[GilesTrinity] Safely handled exception getting attribute max health #2 for unit " + tmp_sThisInternalName + " [" + tmp_iThisActorSNO.ToString() + "]"); bShouldRefreshDiaObjects = true; } } // Ok check we didn't fail getting the maximum health, now try to get live current health... if (!bShouldRefreshDiaObjects) { try { double dTempHitpoints = (targetCurrent.unit_thisDiaUnit.CommonData.GetAttribute(ActorAttributeType.HitpointsCur) / dThisMaxHealth); if (dTempHitpoints <= 0d) { bShouldRefreshDiaObjects = true; } else { targetCurrent.iThisHitPoints = dTempHitpoints; targetCurrent.vThisPosition = targetCurrent.unit_thisDiaUnit.Position; } } catch { bShouldRefreshDiaObjects = true; } } } } } // So, after all that, do we actually want a new target list? if (!bWholeNewTarget && !bWaitingForPower && !bWaitingForPotion) { // If we *DO* want a new target list, do this... if (bShouldRefreshDiaObjects) { // Now call the function that refreshes targets RefreshDiaObjects(); // Update when we last refreshed with current time lastRefreshedObjects = DateTime.Now; // No target, return success if (targetCurrent == null) { return RunStatus.Success; } // Been trying to handle the same target for more than 30 seconds without damaging/reaching it? Blacklist it! // Note: The time since target picked updates every time the current target loses health, if it's a monster-target if (targetCurrent.ThisGilesObjectType != GilesObjectType.Avoidance && ((targetCurrent.ThisGilesObjectType != GilesObjectType.Unit && DateTime.Now.Subtract(dateSincePickedTarget).TotalSeconds > 12) || (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && !targetCurrent.bThisBoss && DateTime.Now.Subtract(dateSincePickedTarget).TotalSeconds > 40)) ) { // NOTE: This only blacklists if it's remained the PRIMARY TARGET that we are trying to actually directly attack! // So it won't blacklist a monster "on the edge of the screen" who isn't even being targetted // Don't blacklist monsters on <= 50% health though, as they can't be in a stuck location... can they!? Maybe give them some extra time! bool bBlacklistThis = true; // PREVENT blacklisting a monster on less than 90% health unless we haven't damaged it for more than 25 seconds if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit) { if (targetCurrent.bThisTreasureGoblin && settings.iTreasureGoblinPriority >= 3) bBlacklistThis = false; if (targetCurrent.iThisHitPoints <= 0.90 && DateTime.Now.Subtract(dateSincePickedTarget).TotalSeconds <= 25) bBlacklistThis = false; } if (bBlacklistThis) { if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit) { Logging.Write("[GilesTrinity] Blacklisting a monster because of possible stuck issues. Monster=" + targetCurrent.sThisInternalName + " {" + targetCurrent.iThisActorSNO.ToString() + "}. Range=" + targetCurrent.fCentreDistance.ToString() + ", health %=" + targetCurrent.iThisHitPoints.ToString()); } hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid); dateSinceTemporaryBlacklistClear = DateTime.Now; } } // Make sure we start trying to move again should we need to! bAlreadyMoving = false; lastMovementCommand = DateTime.Today; bPickNewAbilities = true; } // Ok we didn't want a new target list, should we at least update the position of the current target, if it's a monster? else if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && targetCurrent.unit_thisDiaUnit != null && targetCurrent.unit_thisDiaUnit.BaseAddress != IntPtr.Zero) { try { targetCurrent.vThisPosition = targetCurrent.unit_thisDiaUnit.Position; } catch { // Keep going anyway if we failed to get a new position from DemonBuddy Logging.WriteDiagnostic("GSDEBUG: Caught position read failure in main target handler loop."); } } } // This variable just prevents an instant 2-target update after coming here from the main decorator function above bWholeNewTarget = false; // Find a valid ability if the target is a monster if (bPickNewAbilities && !bWaitingForPower && !bWaitingForPotion) { bPickNewAbilities = false; if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit) { // Pick a suitable ability powerPrime = GilesAbilitySelector(false, false, false); if (powerPrime.powerThis == SNOPower.None && !playerStatus.bIsIncapacitated) { iNoAbilitiesAvailableInARow++; if (DateTime.Now.Subtract(lastRemindedAboutAbilities).TotalSeconds > 60 && iNoAbilitiesAvailableInARow >= 4) { lastRemindedAboutAbilities = DateTime.Now; Logging.Write("Fatal Error: Couldn't find a valid attack ability. Not enough resource for any abilities or all on cooldown"); Logging.Write("If you get this message frequently, you should consider changing your build"); Logging.Write("Perhaps you don't have enough critical hit chance % for your current build, or just have a bad skill setup?"); } } else { iNoAbilitiesAvailableInARow = 0; } } // Select an ability for destroying a destructible with in advance if (targetCurrent.ThisGilesObjectType == GilesObjectType.Destructible || targetCurrent.ThisGilesObjectType == GilesObjectType.Barricade) powerPrime = GilesAbilitySelector(false, false, true); } // Pop a potion when necessary // Note that we force a single-loop pause first, to help potion popping "go off" if (playerStatus.dCurrentHealthPct <= iEmergencyHealthPotionLimit && !bWaitingForPower && !bWaitingForPotion && !playerStatus.bIsIncapacitated && GilesUseTimer(SNOPower.DrinkHealthPotion)) { bWaitingForPotion = true; return RunStatus.Running; } if (bWaitingForPotion) { bWaitingForPotion = false; if (!playerStatus.bIsIncapacitated && GilesUseTimer(SNOPower.DrinkHealthPotion)) { ACDItem thisBestPotion = ZetaDia.Me.Inventory.Backpack.Where(i => i.IsPotion).OrderByDescending(p => p.HitpointsGranted).FirstOrDefault(); if (thisBestPotion != null) { WaitWhileAnimating(3, true); ZetaDia.Me.Inventory.UseItem((thisBestPotion.DynamicId)); } dictAbilityLastUse[SNOPower.DrinkHealthPotion] = DateTime.Now; WaitWhileAnimating(2, true); } } // See if we can use any special buffs etc. while in avoidance if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance) { powerBuff = GilesAbilitySelector(true, false, false); if (powerBuff.powerThis != SNOPower.None) { ZetaDia.Me.UsePower(powerBuff.powerThis, powerBuff.vTargetLocation, powerBuff.iTargetWorldID, powerBuff.iTargetGUID); powerLastSnoPowerUsed = powerBuff.powerThis; dictAbilityLastUse[powerBuff.powerThis] = DateTime.Now; } } // ************************************************************************ // ***** Pick the destination point and range of target ***** // ************************************************************************ // How close we need to get to the target before we consider it "reached" float fRangeRequired = 1f; float fDistanceReduction = 0f; // Set current destination to our current target's destination vCurrentDestination = targetCurrent.vThisPosition; switch (targetCurrent.ThisGilesObjectType) { // ***** Unit, we need to pick an ability to use and get within range ***** case GilesObjectType.Unit: // Treat the distance as closer based on the radius of monsters fDistanceReduction = targetCurrent.fThisRadius; if (bForceCloseRangeTarget) fDistanceReduction -= 3f; if (fDistanceReduction <= 0f) fDistanceReduction = 0f; // Pick a range to try to reach fRangeRequired = powerPrime.powerThis == SNOPower.None ? 9f : powerPrime.iMinimumRange; break; // ***** Item - need to get within 6 feet and then interact with it ***** case GilesObjectType.Item: fRangeRequired = 5f; // If we're having stuck issues, try forcing us to get closer to this item if (bForceCloseRangeTarget) fRangeRequired -= 1f; // Try and randomize the distances required if we have problems looting this if (iIgnoreThisRactorGUID == targetCurrent.iThisRActorGuid) { Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber)); fRangeRequired = (rndNum.Next(5)) + 2f; } // Treat the distance as closer if the X & Y distance are almost point-blank, for items if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f) fDistanceReduction += 1f; break; // ***** Gold - need to get within pickup radius only ***** case GilesObjectType.Gold: fRangeRequired = (float)ZetaDia.Me.GoldPickUpRadius; if (fRangeRequired < 2f) fRangeRequired = 2f; break; // ***** Globes - need to get within pickup radius only ***** case GilesObjectType.Globe: fRangeRequired = (float)ZetaDia.Me.GoldPickUpRadius; if (fRangeRequired < 2f) fRangeRequired = 2f; if (fRangeRequired > 5f) fRangeRequired = 5f; break; // ***** Shrine & Container - need to get within 8 feet and interact ***** case GilesObjectType.Shrine: case GilesObjectType.Container: // Treat the distance as closer based on the radius of the object fDistanceReduction = targetCurrent.fThisRadius; fRangeRequired = 3f; if (bForceCloseRangeTarget) fRangeRequired -= 2f; // Treat the distance as closer if the X & Y distance are almost point-blank, for objects if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f) fDistanceReduction += 1f; break; case GilesObjectType.Interactable: // Treat the distance as closer based on the radius of the object fDistanceReduction = targetCurrent.fThisRadius; fRangeRequired = 12f; if (bForceCloseRangeTarget) fRangeRequired -= 2f; // Check if it's in our interactable range dictionary or not int iTempRange; if (dictInteractableRange.TryGetValue(targetCurrent.iThisActorSNO, out iTempRange)) { fRangeRequired = (float)iTempRange; } // Treat the distance as closer if the X & Y distance are almost point-blank, for objects if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f) fDistanceReduction += 1f; break; // ***** Destructible - need to pick an ability and attack it ***** case GilesObjectType.Destructible: case GilesObjectType.Barricade: // Pick a range to try to reach + (tmp_fThisRadius * 0.70); fRangeRequired = powerPrime.powerThis == SNOPower.None ? 9f : powerPrime.iMinimumRange; fDistanceReduction = targetCurrent.fThisRadius; if (bForceCloseRangeTarget) fDistanceReduction -= 3f; if (fDistanceReduction <= 0f) fDistanceReduction = 0f; // Treat the distance as closer if the X & Y distance are almost point-blank, for destructibles if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f) fDistanceReduction += 1f; break; // ***** Avoidance - need to pick an avoid location and move there ***** case GilesObjectType.Avoidance: fRangeRequired = 2f; // Treat the distance as closer if the X & Y distance are almost point-blank, for avoidance spots if (Vector3.Distance(new Vector3(playerStatus.vCurrentPosition.X, playerStatus.vCurrentPosition.Y, playerStatus.vCurrentPosition.Z), new Vector3(vCurrentDestination.X, vCurrentDestination.Y, playerStatus.vCurrentPosition.Z)) <= 1.5f) fDistanceReduction += 2f; break; // ***** Backtrack Destination ***** case GilesObjectType.Backtrack: fRangeRequired = 5f; if (bForceCloseRangeTarget) fRangeRequired -= 2f; break; } // Maintain an area list of all zones we pass through/near while moving, for our custom navigation handler if (DateTime.Now.Subtract(lastAddedLocationCache).TotalMilliseconds >= 100) { lastAddedLocationCache = DateTime.Now; if (Vector3.Distance(playerStatus.vCurrentPosition, vLastRecordedLocationCache) >= 5f) { hashSkipAheadAreaCache.Add(new GilesObstacle(playerStatus.vCurrentPosition, 20f, 0)); } } // Maintain a backtrack list only while fighting monsters if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && settings.bEnableBacktracking && (iTotalBacktracks == 0 || Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks]) >= 10f)) { bool bAddThisBacktrack = true; // Check we aren't within 12 feet of 2 backtracks again (eg darting back & forth) if (iTotalBacktracks >= 2) { if (Vector3.Distance(playerStatus.vCurrentPosition, vBacktrackList[iTotalBacktracks - 1]) < 12f) bAddThisBacktrack = false; } if (bAddThisBacktrack) { iTotalBacktracks++; vBacktrackList.Add(iTotalBacktracks, playerStatus.vCurrentPosition); } } // Calculate the player's current distance from destination float fDistanceFromTarget = Vector3.Distance(playerStatus.vCurrentPosition, vCurrentDestination) - fDistanceReduction; if (fDistanceFromTarget < 0f) fDistanceFromTarget = 0f; // ************************************************************************ // ***** Interact/use power on target if already in range ***** // ************************************************************************ if (fRangeRequired <= 0f || fDistanceFromTarget <= fRangeRequired) { // If avoidance, instantly skip if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance) { //vlastSafeSpot = vNullLocation; bForceTargetUpdate = true; bAvoidDirectionBlacklisting = false; return RunStatus.Running; } if (settings.bDebugInfo) { sStatusText = "[GilesTrinity] "; switch (targetCurrent.ThisGilesObjectType) { case GilesObjectType.Avoidance: sStatusText += "Avoid "; break; case GilesObjectType.Unit: sStatusText += "Attack "; break; case GilesObjectType.Item: case GilesObjectType.Gold: case GilesObjectType.Globe: sStatusText += "Pickup "; break; case GilesObjectType.Backtrack: sStatusText += "Backtrack "; break; case GilesObjectType.Interactable: sStatusText += "Interact "; break; case GilesObjectType.Container: sStatusText += "Open "; break; case GilesObjectType.Destructible: case GilesObjectType.Barricade: sStatusText += "Destroy "; break; case GilesObjectType.Shrine: sStatusText += "Click "; break; } sStatusText += "Target=" + targetCurrent.sThisInternalName + " {" + targetCurrent.iThisActorSNO + "}. C-Dist=" + Math.Round(targetCurrent.fCentreDistance, 2).ToString() + ". " + "R-Dist=" + Math.Round(targetCurrent.fRadiusDistance, 2).ToString() + ". "; if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && powerPrime.powerThis != SNOPower.None) sStatusText += "Power=" + powerPrime.powerThis.ToString() + " (range " + fRangeRequired.ToString() + ") "; sStatusText += "Weight=" + targetCurrent.dThisWeight.ToString() + " IN RANGE, NOW INTERACTING"; BotMain.StatusText = sStatusText; bResetStatusText = true; } // An integer to log total interact attempts on a particular object or item int iInteractAttempts; switch (targetCurrent.ThisGilesObjectType) { // ************************************************************************ // ***** Unit, use our primary power to attack ***** // ************************************************************************ case GilesObjectType.Unit: if (powerPrime.powerThis != SNOPower.None) { // Force waiting for global cooldown timer or long-animation abilities if (powerPrime.iForceWaitLoopsBefore >= 1 || (powerPrime.bWaitWhileAnimating != SIGNATURE_SPAM && DateTime.Now.Subtract(lastGlobalCooldownUse).TotalMilliseconds <= 50)) { //Logging.WriteDiagnostic("Debug: Force waiting BEFORE ability " + powerPrime.powerThis.ToString() + "..."); bWaitingForPower = true; if (powerPrime.iForceWaitLoopsBefore >= 1) powerPrime.iForceWaitLoopsBefore--; return RunStatus.Running; } bWaitingForPower = false; // Wait while animating before an attack if (powerPrime.bWaitWhileAnimating) WaitWhileAnimating(5, false); // Use the power bool bUsePowerSuccess = false; // Note that whirlwinds use an off-on-off-on to avoid spam if (powerPrime.powerThis != SNOPower.Barbarian_Whirlwind && powerPrime.powerThis != SNOPower.DemonHunter_Strafe) { ZetaDia.Me.UsePower(powerPrime.powerThis, powerPrime.vTargetLocation, powerPrime.iTargetWorldID, powerPrime.iTargetGUID); bUsePowerSuccess = true; lastChangedZigZag = DateTime.Today; vPositionLastZigZagCheck = Vector3.Zero; } else { // Special code to prevent whirlwind double-spam, this helps save fury bool bUseThisLoop = powerPrime.powerThis != powerLastSnoPowerUsed; if (!bUseThisLoop) { //powerLastSnoPowerUsed = SNOPower.None; if (DateTime.Now.Subtract(dictAbilityLastUse[powerPrime.powerThis]).TotalMilliseconds >= 200) bUseThisLoop = true; } if (bUseThisLoop) { ZetaDia.Me.UsePower(powerPrime.powerThis, powerPrime.vTargetLocation, powerPrime.iTargetWorldID, powerPrime.iTargetGUID); bUsePowerSuccess = true; } } if (bUsePowerSuccess) { //Logging.Write(powerPrime.powerThis.ToString() + " used successfully"); dictAbilityLastUse[powerPrime.powerThis] = DateTime.Now; lastGlobalCooldownUse = DateTime.Now; powerLastSnoPowerUsed = powerPrime.powerThis; powerPrime.powerThis = SNOPower.None; // Wait for animating AFTER the attack if (powerPrime.bWaitWhileAnimating) WaitWhileAnimating(3, false); } else { //Logging.Write(powerPrime.powerThis.ToString() + " reported failure"); //dictAbilityLastFailed[powerPrime.powerThis] = DateTime.Now; //*Logging.WriteDiagnostic("GSDebug: Skill use apparently failed=" + powerPrime.powerThis.ToString() + ", against enemy: " + targetCurrent.sThisInternalName + // " (skill use range=" + powerPrime.iMinimumRange.ToString() + ", enemy centre range=" + targetCurrent.fCentreDistance.ToString() + ", radius range=" + // targetCurrent.fRadiusDistance.ToString() + " (radius=" + targetCurrent.fThisRadius.ToString() + ")");*/ } // Wait for animating AFTER the attack if (powerPrime.bWaitWhileAnimating) WaitWhileAnimating(3, false); bPickNewAbilities = true; // Keep looking for monsters at "normal kill range" a few moments after we successfully attack a monster incase we can pull them into range iKeepKillRadiusExtendedFor = 8; iKeepLootRadiusExtendedFor = 8; // if at full or nearly full health, see if we can raycast to it, if not, ignore it for 2000 ms // K: this is the source of get all boss wait. we will check it later // temporary solution here, only blacklist it if it's not the only target. if (targetCurrent.iThisHitPoints >= 0.9d && iAnythingWithinRange[RANGE_50] > 3) { if (!GilesCanRayCast(playerStatus.vCurrentPosition, targetCurrent.vThisPosition, NavCellFlags.AllowWalk)) { iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid; iIgnoreThisForLoops = 6; // Add this monster to our very short-term ignore list hashRGUIDTemporaryBlacklist.Add(targetCurrent.iThisRActorGuid); lastTemporaryBlacklist = DateTime.Now; bNeedClearTemporaryBlacklist = true; } } // See if we should force a long wait AFTERWARDS, too // Force waiting AFTER power use for certain abilities bWaitingAfterPower = false; if (powerPrime.iForceWaitLoopsAfter >= 1) { //Logging.WriteDiagnostic("Force waiting AFTER ability " + powerPrime.powerThis.ToString() + "..."); bWaitingAfterPower = true; } return RunStatus.Running; } return RunStatus.Running; // ************************************************************************ // ***** Item, interact with it and log item stats ***** // ************************************************************************ case GilesObjectType.Item: // Check if we actually have room for this item first Vector2 ValidLocation = FindValidBackpackLocation(true); if (ValidLocation.X < 0 || ValidLocation.Y < 0) { Logging.Write("No more space to pickup a 2-slot item, town-run requested at next free moment."); bGilesForcedVendoring = true; return RunStatus.Running; } // Pick the item up the usepower way, and "blacklist" for a couple of loops WaitWhileAnimating(12, true); ZetaDia.Me.UsePower(SNOPower.Axe_Operate_Gizmo, Vector3.Zero, 0, targetCurrent.iThisACDGUID); lastChangedZigZag = DateTime.Today; vPositionLastZigZagCheck = Vector3.Zero; iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid; iIgnoreThisForLoops = 3; // Store item pickup stats if (!_hashsetItemPicksLookedAt.Contains(targetCurrent.iThisRActorGuid)) { _hashsetItemPicksLookedAt.Add(targetCurrent.iThisRActorGuid); GilesItemType thisgilesitemtype = DetermineItemType(targetCurrent.sThisInternalName, targetCurrent.ThisDBItemType, targetCurrent.ThisFollowerType); GilesBaseItemType thisgilesbasetype = DetermineBaseType(thisgilesitemtype); if (thisgilesbasetype == GilesBaseItemType.Armor || thisgilesbasetype == GilesBaseItemType.WeaponOneHand || thisgilesbasetype == GilesBaseItemType.WeaponTwoHand || thisgilesbasetype == GilesBaseItemType.WeaponRange || thisgilesbasetype == GilesBaseItemType.Jewelry || thisgilesbasetype == GilesBaseItemType.FollowerItem || thisgilesbasetype == GilesBaseItemType.Offhand) { int iThisQuality; ItemsPickedStats.iTotal++; if (targetCurrent.ThisQuality >= ItemQuality.Legendary) iThisQuality = QUALITYORANGE; else if (targetCurrent.ThisQuality >= ItemQuality.Rare4) iThisQuality = QUALITYYELLOW; else if (targetCurrent.ThisQuality >= ItemQuality.Magic1) iThisQuality = QUALITYBLUE; else iThisQuality = QUALITYWHITE; ItemsPickedStats.iTotalPerQuality[iThisQuality]++; ItemsPickedStats.iTotalPerLevel[targetCurrent.iThisLevel]++; ItemsPickedStats.iTotalPerQPerL[iThisQuality, targetCurrent.iThisLevel]++; } else if (thisgilesbasetype == GilesBaseItemType.Gem) { int iThisGemType = 0; ItemsPickedStats.iTotalGems++; if (thisgilesitemtype == GilesItemType.Topaz) iThisGemType = GEMTOPAZ; if (thisgilesitemtype == GilesItemType.Ruby) iThisGemType = GEMRUBY; if (thisgilesitemtype == GilesItemType.Emerald) iThisGemType = GEMEMERALD; if (thisgilesitemtype == GilesItemType.Amethyst) iThisGemType = GEMAMETHYST; ItemsPickedStats.iGemsPerType[iThisGemType]++; ItemsPickedStats.iGemsPerLevel[targetCurrent.iThisLevel]++; ItemsPickedStats.iGemsPerTPerL[iThisGemType, targetCurrent.iThisLevel]++; } else if (thisgilesitemtype == GilesItemType.HealthPotion) { ItemsPickedStats.iTotalPotions++; ItemsPickedStats.iPotionsPerLevel[targetCurrent.iThisLevel]++; } else if (tmp_item_ThisGilesItemType == GilesItemType.InfernalKey) { ItemsPickedStats.iTotalInfernalKeys++; } // See if we should update the stats file if (DateTime.Now.Subtract(ItemStatsLastPostedReport).TotalSeconds > 10) { ItemStatsLastPostedReport = DateTime.Now; OutputReport(); } } WaitWhileAnimating(5, true); // Count how many times we've tried interacting if (!dictTotalInteractionAttempts.TryGetValue(targetCurrent.iThisRActorGuid, out iInteractAttempts)) { dictTotalInteractionAttempts.Add(targetCurrent.iThisRActorGuid, 1); } else { dictTotalInteractionAttempts[targetCurrent.iThisRActorGuid]++; } // If we've tried interacting too many times, blacklist this for a while if (iInteractAttempts > 20) { hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid); dateSinceTemporaryBlacklistClear = DateTime.Now; } // Now tell Trinity to get a new target! bForceTargetUpdate = true; return RunStatus.Running; // ************************************************************************ // ***** Gold & Globe - need to get within pickup radius only ***** // ************************************************************************ case GilesObjectType.Gold: case GilesObjectType.Globe: // Count how many times we've tried interacting if (!dictTotalInteractionAttempts.TryGetValue(targetCurrent.iThisRActorGuid, out iInteractAttempts)) { dictTotalInteractionAttempts.Add(targetCurrent.iThisRActorGuid, 1); } else { dictTotalInteractionAttempts[targetCurrent.iThisRActorGuid]++; } // If we've tried interacting too many times, blacklist this for a while if (iInteractAttempts > 3) { hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid); dateSinceTemporaryBlacklistClear = DateTime.Now; hashRGUIDIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid); } iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid; iIgnoreThisForLoops = 3; // Now tell Trinity to get a new target! lastChangedZigZag = DateTime.Today; vPositionLastZigZagCheck = Vector3.Zero; bForceTargetUpdate = true; return RunStatus.Running; // ************************************************************************ // ***** Shrine & Container - need to get within 8 feet and interact ***** // ************************************************************************ case GilesObjectType.Shrine: case GilesObjectType.Container: case GilesObjectType.Interactable: WaitWhileAnimating(5, true); ZetaDia.Me.UsePower(SNOPower.Axe_Operate_Gizmo, Vector3.Zero, 0, targetCurrent.iThisACDGUID); iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid; iIgnoreThisForLoops = 2; // Interactables can have a long channeling time... if (targetCurrent.ThisGilesObjectType == GilesObjectType.Interactable) WaitWhileAnimating(1500, true); else WaitWhileAnimating(12, true); if (targetCurrent.ThisGilesObjectType == GilesObjectType.Interactable) { iIgnoreThisForLoops = 30; hashRGUIDIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid); } // Count how many times we've tried interacting if (!dictTotalInteractionAttempts.TryGetValue(targetCurrent.iThisRActorGuid, out iInteractAttempts)) { dictTotalInteractionAttempts.Add(targetCurrent.iThisRActorGuid, 1); } else { dictTotalInteractionAttempts[targetCurrent.iThisRActorGuid]++; } // If we've tried interacting too many times, blacklist this for a while if (iInteractAttempts > 5 || (targetCurrent.ThisGilesObjectType == GilesObjectType.Interactable && iInteractAttempts > 3)) { hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid); dateSinceTemporaryBlacklistClear = DateTime.Now; } // Now tell Trinity to get a new target! lastChangedZigZag = DateTime.Today; vPositionLastZigZagCheck = Vector3.Zero; bForceTargetUpdate = true; return RunStatus.Running; // ************************************************************************ // ***** Destructible - need to pick an ability and attack it ***** // ************************************************************************ case GilesObjectType.Destructible: case GilesObjectType.Barricade: if (powerPrime.powerThis != SNOPower.None) { if (targetCurrent.ThisGilesObjectType == GilesObjectType.Barricade) Logging.WriteDiagnostic("[GilesTrinity] Barricade: Name=" + targetCurrent.sThisInternalName + ". SNO=" + targetCurrent.iThisActorSNO.ToString() + ", Range=" + targetCurrent.fCentreDistance.ToString() + ". Needed range=" + fRangeRequired.ToString() + ". Radius=" + targetCurrent.fThisRadius.ToString() + ". Type=" + targetCurrent.ThisGilesObjectType.ToString() + ". Using power=" + powerPrime.powerThis.ToString()); else Logging.WriteDiagnostic("[GilesTrinity] Destructible: Name=" + targetCurrent.sThisInternalName + ". SNO=" + targetCurrent.iThisActorSNO.ToString() + ", Range=" + targetCurrent.fCentreDistance.ToString() + ". Needed range=" + fRangeRequired.ToString() + ". Radius=" + targetCurrent.fThisRadius.ToString() + ". Type=" + targetCurrent.ThisGilesObjectType.ToString() + ". Using power=" + powerPrime.powerThis.ToString()); WaitWhileAnimating(12, true); if (targetCurrent.iThisRActorGuid == iIgnoreThisRactorGUID || hashDestructableLocationTarget.Contains(targetCurrent.iThisActorSNO)) { // Location attack - attack the Vector3/map-area (equivalent of holding shift and left-clicking the object in-game to "force-attack") Vector3 vAttackPoint; if (targetCurrent.fCentreDistance >= 6f) vAttackPoint = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, 6f); else vAttackPoint = targetCurrent.vThisPosition; vAttackPoint.Z += 1.5f; Logging.WriteDiagnostic("[GilesTrinity] (NB: Attacking location of destructable)"); ZetaDia.Me.UsePower(powerPrime.powerThis, vAttackPoint, iCurrentWorldID, -1); } else { // Standard attack - attack the ACDGUID (equivalent of left-clicking the object in-game) ZetaDia.Me.UsePower(powerPrime.powerThis, vNullLocation, -1, targetCurrent.iThisACDGUID); } // Count how many times we've tried interacting if (!dictTotalInteractionAttempts.TryGetValue(targetCurrent.iThisRActorGuid, out iInteractAttempts)) { dictTotalInteractionAttempts.Add(targetCurrent.iThisRActorGuid, 1); } else { dictTotalInteractionAttempts[targetCurrent.iThisRActorGuid]++; } // If we've tried interacting too many times, blacklist this for a while if (iInteractAttempts > 3) { hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid); dateSinceTemporaryBlacklistClear = DateTime.Now; } dictAbilityLastUse[powerPrime.powerThis] = DateTime.Now; powerPrime.powerThis = SNOPower.None; WaitWhileAnimating(6, true); // Prevent this EXACT object being targetted again for a short while, just incase iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid; iIgnoreThisForLoops = 3; // Add this destructible/barricade to our very short-term ignore list hashRGUIDDestructibleBlacklist.Add(targetCurrent.iThisRActorGuid); lastDestroyedDestructible = DateTime.Now; bNeedClearDestructibles = true; } // Now tell Trinity to get a new target! bForceTargetUpdate = true; lastChangedZigZag = DateTime.Today; vPositionLastZigZagCheck = Vector3.Zero; return RunStatus.Running; // ************************************************************************ // ***** Backtrack - clear this waypoint ***** // ************************************************************************ case GilesObjectType.Backtrack: // Remove the current backtrack location now we reached it vBacktrackList.Remove(iTotalBacktracks); iTotalBacktracks--; // Never bother with the very first backtrack location if (iTotalBacktracks <= 1) { iTotalBacktracks = 0; vBacktrackList = new SortedList(); } bForceTargetUpdate = true; return RunStatus.Running; } return RunStatus.Running; } // ************************************************************************ // ***** Out-of-range, so move towards the target ***** // ************************************************************************ if (settings.bDebugInfo) { sStatusText = "[GilesTrinity] "; switch (targetCurrent.ThisGilesObjectType) { case GilesObjectType.Avoidance: sStatusText += "Avoid "; break; case GilesObjectType.Unit: sStatusText += "Attack "; break; case GilesObjectType.Item: case GilesObjectType.Gold: case GilesObjectType.Globe: sStatusText += "Pickup "; break; case GilesObjectType.Backtrack: sStatusText += "Backtrack "; break; case GilesObjectType.Interactable: sStatusText += "Interact "; break; case GilesObjectType.Container: sStatusText += "Open "; break; case GilesObjectType.Destructible: case GilesObjectType.Barricade: sStatusText += "Destroy "; break; case GilesObjectType.Shrine: sStatusText += "Click "; break; } sStatusText += "Target=" + targetCurrent.sThisInternalName + " {" + targetCurrent.iThisActorSNO + "}. C-Dist=" + Math.Round(targetCurrent.fCentreDistance, 2).ToString() + ". " + "R-Dist=" + Math.Round(targetCurrent.fRadiusDistance, 2).ToString() + ". "; if (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && powerPrime.powerThis != SNOPower.None) sStatusText += "Power=" + powerPrime.powerThis.ToString() + " (range " + fRangeRequired.ToString() + ") "; sStatusText += "Weight=" + targetCurrent.dThisWeight.ToString() + " MOVING INTO RANGE"; BotMain.StatusText = sStatusText; bResetStatusText = true; } // Are we currently incapacitated? If so then wait... if (playerStatus.bIsIncapacitated || playerStatus.bIsRooted) { return RunStatus.Running; } // Some stuff to avoid spamming usepower EVERY loop, and also to detect stucks/staying in one place for too long bool bForceNewMovement = false; // Count how long we have failed to move - body block stuff etc. if (fDistanceFromTarget == fLastDistanceFromTarget) { bForceNewMovement = true; if (DateTime.Now.Subtract(lastMovedDuringCombat).TotalMilliseconds >= 250) { lastMovedDuringCombat = DateTime.Now; // We've been stuck at least 250 ms, let's go and pick new targets etc. iTimesBlockedMoving++; bForceCloseRangeTarget = true; lastForcedKeepCloseRange = DateTime.Now; // And tell Trinity to get a new target bForceTargetUpdate = true; // Blacklist an 80 degree direction for avoidance if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance) { bAvoidDirectionBlacklisting = true; fAvoidBlacklistDirection = FindDirectionDegree(playerStatus.vCurrentPosition, targetCurrent.vThisPosition); } // Tell target finder to prioritize close-combat targets incase we were bodyblocked switch (iTimesBlockedMoving) { case 1: iMillisecondsForceCloseRange = 850; break; case 2: iMillisecondsForceCloseRange = 1300; // Cancel avoidance attempts for 500ms iMillisecondsCancelledEmergencyMoveFor = 1500; timeCancelledEmergencyMove = DateTime.Now; vlastSafeSpot = vNullLocation; // Check for raycastability against objects switch (targetCurrent.ThisGilesObjectType) { case GilesObjectType.Container: case GilesObjectType.Shrine: case GilesObjectType.Globe: case GilesObjectType.Gold: case GilesObjectType.Item: // No raycast available, try and force-ignore this for a little while, and blacklist for a few seconds if (!GilesCanRayCast(playerStatus.vCurrentPosition, targetCurrent.vThisPosition, NavCellFlags.AllowWalk)) { iIgnoreThisRactorGUID = targetCurrent.iThisRActorGuid; iIgnoreThisForLoops = 6; hashRGUIDTemporaryIgnoreBlacklist.Add(targetCurrent.iThisRActorGuid); dateSinceTemporaryBlacklistClear = DateTime.Now; } break; } break; case 3: iMillisecondsForceCloseRange = 2000; // Cancel avoidance attempts for 1.5 seconds iMillisecondsCancelledEmergencyMoveFor = 2000; timeCancelledEmergencyMove = DateTime.Now; vlastSafeSpot = vNullLocation; // Blacklist the current avoidance target area for the next avoidance-spot find if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance) hashAvoidanceBlackspot.Add(new GilesObstacle(targetCurrent.vThisPosition, 12f, -1, 0)); break; default: iMillisecondsForceCloseRange = 4000; // Cancel avoidance attempts for 3.5 seconds iMillisecondsCancelledEmergencyMoveFor = 4000; timeCancelledEmergencyMove = DateTime.Now; vlastSafeSpot = vNullLocation; // Blacklist the current avoidance target area for the next avoidance-spot find if (iTimesBlockedMoving == 4 && targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance) hashAvoidanceBlackspot.Add(new GilesObstacle(targetCurrent.vThisPosition, 16f, -1, 0)); break; } // If we were backtracking and failed, remove the current backtrack and try and move to the next if (targetCurrent.ThisGilesObjectType == GilesObjectType.Backtrack && iTimesBlockedMoving >= 2) { vBacktrackList.Remove(iTotalBacktracks); iTotalBacktracks--; if (iTotalBacktracks <= 1) { iTotalBacktracks = 0; vBacktrackList = new SortedList(); } } // Reset the emergency loop counter and return success return RunStatus.Running; } // Been 250 milliseconds of non-movement? } else { // Movement has been made, so count the time last moved! lastMovedDuringCombat = DateTime.Now; } // Update the last distance stored fLastDistanceFromTarget = fDistanceFromTarget; // See if there's an obstacle in our way, if so try to navigate around it if (targetCurrent.ThisGilesObjectType != GilesObjectType.Avoidance) { Vector3 point = vCurrentDestination; foreach (GilesTrinity.GilesObstacle tempobstacle in GilesTrinity.hashNavigationObstacleCache.Where(cp => GilesTrinity.GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point) && cp.vThisLocation.Distance(playerStatus.vCurrentPosition) > GilesTrinity.dictSNONavigationSize[cp.iThisSNOID])) { if (vShiftedPosition == Vector3.Zero) { if (DateTime.Now.Subtract(lastShiftedPosition).TotalSeconds >= 10) { float fDirectionToTarget = GilesTrinity.FindDirectionDegree(playerStatus.vCurrentPosition, vCurrentDestination); vCurrentDestination = MathEx.GetPointAt(playerStatus.vCurrentPosition, 15f, MathEx.ToRadians(fDirectionToTarget - 50)); if (!GilesCanRayCast(playerStatus.vCurrentPosition, vCurrentDestination, NavCellFlags.AllowWalk)) { vCurrentDestination = MathEx.GetPointAt(playerStatus.vCurrentPosition, 15f, MathEx.ToRadians(fDirectionToTarget + 50)); if (!GilesCanRayCast(playerStatus.vCurrentPosition, vCurrentDestination, NavCellFlags.AllowWalk)) { vCurrentDestination = point; } } if (vCurrentDestination != point) { vShiftedPosition = vCurrentDestination; iShiftPositionFor = 1000; lastShiftedPosition = DateTime.Now; Logging.WriteDiagnostic("[GilesTrinity] Mid-Target-Handle position shift location to: " + vCurrentDestination.ToString() + " (was " + point.ToString() + ")"); } } // Make sure we only shift max once every 10 seconds } else { if (DateTime.Now.Subtract(lastShiftedPosition).TotalMilliseconds <= iShiftPositionFor) { vCurrentDestination = vShiftedPosition; } else { vShiftedPosition = Vector3.Zero; } } } // Position shifting code } // Only position-shift when not avoiding // See if we want to ACTUALLY move, or are just waiting for the last move command... if (!bForceNewMovement && bAlreadyMoving && vCurrentDestination == vLastMoveToTarget && DateTime.Now.Subtract(lastMovementCommand).TotalMilliseconds <= 100) { return RunStatus.Running; } // If we're doing avoidance, globes or backtracking, try to use special abilities to move quicker if (targetCurrent.ThisGilesObjectType == GilesObjectType.Avoidance || targetCurrent.ThisGilesObjectType == GilesObjectType.Globe || (targetCurrent.ThisGilesObjectType == GilesObjectType.Backtrack && settings.bOutOfCombatMovementPowers)) { // Log whether we used a special movement (for avoidance really) bool bFoundSpecialMovement = false; // Leap movement for a barb if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Leap) && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Leap]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Barbarian_Leap] && PowerManager.CanCast(SNOPower.Barbarian_Leap)) { WaitWhileAnimating(3, true); ZetaDia.Me.UsePower(SNOPower.Barbarian_Leap, vCurrentDestination, iCurrentWorldID, -1); dictAbilityLastUse[SNOPower.Barbarian_Leap] = DateTime.Now; bFoundSpecialMovement = true; } // Furious Charge movement for a barb if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_FuriousCharge) && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Barbarian_FuriousCharge] && PowerManager.CanCast(SNOPower.Barbarian_FuriousCharge)) { WaitWhileAnimating(3, true); ZetaDia.Me.UsePower(SNOPower.Barbarian_FuriousCharge, vCurrentDestination, iCurrentWorldID, -1); dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge] = DateTime.Now; bFoundSpecialMovement = true; } // Vault for a Demon Hunter if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Vault) && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.DemonHunter_Vault]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.DemonHunter_Vault] && PowerManager.CanCast(SNOPower.DemonHunter_Vault)) { WaitWhileAnimating(3, true); ZetaDia.Me.UsePower(SNOPower.DemonHunter_Vault, vCurrentDestination, iCurrentWorldID, -1); dictAbilityLastUse[SNOPower.DemonHunter_Vault] = DateTime.Now; bFoundSpecialMovement = true; } // Teleport for a wizard (need to be able to check skill rune in DB for a 3-4 teleport spam in a row) if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Teleport) && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Wizard_Teleport]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Wizard_Teleport] && playerStatus.dCurrentEnergy >= 15 && PowerManager.CanCast(SNOPower.Wizard_Teleport)) { WaitWhileAnimating(3, true); ZetaDia.Me.UsePower(SNOPower.Wizard_Teleport, vCurrentDestination, iCurrentWorldID, -1); dictAbilityLastUse[SNOPower.Wizard_Teleport] = DateTime.Now; bFoundSpecialMovement = true; } // Archon Teleport for a wizard (need to be able to check skill rune in DB for a 3-4 teleport spam in a row) if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon_Teleport) && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Wizard_Archon_Teleport] && PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport)) { WaitWhileAnimating(3, true); ZetaDia.Me.UsePower(SNOPower.Wizard_Archon_Teleport, vCurrentDestination, iCurrentWorldID, -1); dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport] = DateTime.Now; bFoundSpecialMovement = true; } if (targetCurrent.ThisGilesObjectType != GilesObjectType.Backtrack) { // Whirlwind for a barb //intell if (!bWaitingForSpecial && powerPrime.powerThis != SNOPower.Barbarian_WrathOfTheBerserker && !bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) && playerStatus.dCurrentEnergy >= 10) { ZetaDia.Me.UsePower(SNOPower.Barbarian_Whirlwind, vCurrentDestination, iCurrentWorldID, -1); // Store the current destination for comparison incase of changes next loop vLastMoveToTarget = vCurrentDestination; // Reset total body-block count, since we should have moved if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000) iTimesBlockedMoving = 0; return RunStatus.Running; } // Tempest rush for a monk if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.Monk_TempestRush) && playerStatus.dCurrentEnergy >= 20) { ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, vCurrentDestination, iCurrentWorldID, -1); // Store the current destination for comparison incase of changes next loop vLastMoveToTarget = vCurrentDestination; // Reset total body-block count, since we should have moved if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000) iTimesBlockedMoving = 0; return RunStatus.Running; } // Strafe for a Demon Hunter if (!bFoundSpecialMovement && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Strafe) && playerStatus.dCurrentEnergy >= 15) { ZetaDia.Me.UsePower(SNOPower.DemonHunter_Strafe, vCurrentDestination, iCurrentWorldID, -1); // Store the current destination for comparison incase of changes next loop vLastMoveToTarget = vCurrentDestination; // Reset total body-block count, since we should have moved if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000) iTimesBlockedMoving = 0; return RunStatus.Running; } } if (bFoundSpecialMovement) { WaitWhileAnimating(6, true); // Store the current destination for comparison incase of changes next loop vLastMoveToTarget = vCurrentDestination; // Reset total body-block count, since we should have moved if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000) iTimesBlockedMoving = 0; return RunStatus.Running; } } // Whirlwind against everything within range (except backtrack points) //intell if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) && playerStatus.dCurrentEnergy >= 10 && iAnythingWithinRange[RANGE_20] >= 1 && !bWaitingForSpecial && powerPrime.powerThis != SNOPower.Barbarian_WrathOfTheBerserker && fDistanceFromTarget <= 12f && targetCurrent.ThisGilesObjectType != GilesObjectType.Container && targetCurrent.ThisGilesObjectType != GilesObjectType.Backtrack && (!hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Sprint) || GilesHasBuff(SNOPower.Barbarian_Sprint)) && targetCurrent.ThisGilesObjectType != GilesObjectType.Backtrack && (targetCurrent.ThisGilesObjectType != GilesObjectType.Item && targetCurrent.ThisGilesObjectType != GilesObjectType.Gold && fDistanceFromTarget >= 6f) && (targetCurrent.ThisGilesObjectType != GilesObjectType.Unit || (targetCurrent.ThisGilesObjectType == GilesObjectType.Unit && !targetCurrent.bThisTreasureGoblin && (!settings.bSelectiveWhirlwind || bAnyNonWWIgnoreMobsInRange || !hashActorSNOWhirlwindIgnore.Contains(targetCurrent.iThisActorSNO))))) { // Special code to prevent whirlwind double-spam, this helps save fury bool bUseThisLoop = SNOPower.Barbarian_Whirlwind != powerLastSnoPowerUsed; if (!bUseThisLoop) { powerLastSnoPowerUsed = SNOPower.None; if (DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Whirlwind]).TotalMilliseconds >= 200) bUseThisLoop = true; } if (bUseThisLoop) { ZetaDia.Me.UsePower(SNOPower.Barbarian_Whirlwind, vCurrentDestination, iCurrentWorldID, -1); powerLastSnoPowerUsed = SNOPower.Barbarian_Whirlwind; dictAbilityLastUse[SNOPower.Barbarian_Whirlwind] = DateTime.Now; } // Store the current destination for comparison incase of changes next loop vLastMoveToTarget = vCurrentDestination; // Reset total body-block count if ((!bForceCloseRangeTarget || DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds > iMillisecondsForceCloseRange) && DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000) iTimesBlockedMoving = 0; return RunStatus.Running; } // Now for the actual movement request stuff bAlreadyMoving = true; lastMovementCommand = DateTime.Now; if (DateTime.Now.Subtract(lastSentMovePower).TotalMilliseconds >= 250 || Vector3.Distance(vLastMoveToTarget, vCurrentDestination) >= 2f || bForceNewMovement) { ZetaDia.Me.UsePower(SNOPower.Walk, vCurrentDestination, iCurrentWorldID, -1); lastSentMovePower = DateTime.Now; // Store the current destination for comparison incase of changes next loop vLastMoveToTarget = vCurrentDestination; // Reset total body-block count, since we should have moved if (DateTime.Now.Subtract(lastForcedKeepCloseRange).TotalMilliseconds >= 2000) iTimesBlockedMoving = 0; } return RunStatus.Running; } // GilesMoveToTarget() // When did we last send a move-power command? private static DateTime lastSentMovePower = DateTime.Today; // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Timers for abilities and selection of best ability etc. ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Dictionaries holding the data/timers ***** // ********************************************************************************************** private static readonly Dictionary dictAbilityRepeatDefaults = new Dictionary { {SNOPower.DrinkHealthPotion, 30000}, {SNOPower.Weapon_Melee_Instant, 5}, {SNOPower.Weapon_Ranged_Instant, 5}, {SNOPower.Barbarian_Bash, 5}, {SNOPower.Barbarian_Cleave, 5}, {SNOPower.Barbarian_Frenzy, 5}, {SNOPower.Barbarian_HammerOfTheAncients, 150}, {SNOPower.Barbarian_Rend, 2650}, {SNOPower.Barbarian_SeismicSlam, 200}, {SNOPower.Barbarian_Whirlwind, 5}, {SNOPower.Barbarian_GroundStomp, 12200}, {SNOPower.Barbarian_Leap, 10200}, {SNOPower.Barbarian_Sprint, 2900}, {SNOPower.Barbarian_IgnorePain, 30200}, {SNOPower.Barbarian_AncientSpear, 300}, // Has a rune that resets cooldown from 10 seconds to 0 on crit {SNOPower.Barbarian_Revenge, 600}, {SNOPower.Barbarian_FuriousCharge, 500}, // Need to be able to check skill-rune for the dynamic cooldown - set to 10 always except for the skill rune :( {SNOPower.Barbarian_Overpower, 200}, {SNOPower.Barbarian_WeaponThrow, 5}, {SNOPower.Barbarian_ThreateningShout, 10200}, {SNOPower.Barbarian_BattleRage, 118000}, {SNOPower.Barbarian_WarCry, 20500}, {SNOPower.Barbarian_Earthquake, 120500}, // Need to be able to check skill-run for dynamic cooldown, and passive for extra cooldown {SNOPower.Barbarian_CallOfTheAncients, 120500}, // Need to be able to check passive for cooldown {SNOPower.Barbarian_WrathOfTheBerserker, 120500}, // Need to be able to check passive for cooldown // Monk skills {SNOPower.Monk_FistsofThunder, 5}, {SNOPower.Monk_DeadlyReach, 5}, {SNOPower.Monk_CripplingWave, 5}, {SNOPower.Monk_WayOfTheHundredFists, 5}, {SNOPower.Monk_LashingTailKick, 250}, {SNOPower.Monk_TempestRush, 250}, {SNOPower.Monk_WaveOfLight, 250}, {SNOPower.Monk_BlindingFlash, 15200}, {SNOPower.Monk_BreathOfHeaven, 15200}, {SNOPower.Monk_Serenity, 20200}, {SNOPower.Monk_InnerSanctuary, 20200}, {SNOPower.Monk_DashingStrike, 1000}, {SNOPower.Monk_ExplodingPalm, 5000}, {SNOPower.Monk_SweepingWind, 5700}, {SNOPower.Monk_CycloneStrike, 10000}, {SNOPower.Monk_SevenSidedStrike, 30200}, {SNOPower.Monk_MysticAlly, 30000}, {SNOPower.Monk_MantraOfEvasion, 3300}, {SNOPower.Monk_MantraOfRetribution, 3300}, {SNOPower.Monk_MantraOfHealing, 3300}, {SNOPower.Monk_MantraOfConviction, 3300}, // Wizard skills {SNOPower.Wizard_MagicMissile, 5}, {SNOPower.Wizard_ShockPulse, 5}, {SNOPower.Wizard_SpectralBlade, 5}, {SNOPower.Wizard_Electrocute, 5}, {SNOPower.Wizard_RayOfFrost, 5}, {SNOPower.Wizard_ArcaneOrb, 500}, {SNOPower.Wizard_ArcaneTorrent, 5}, {SNOPower.Wizard_Disintegrate, 5}, {SNOPower.Wizard_FrostNova, 9000}, {SNOPower.Wizard_DiamondSkin, 15000}, {SNOPower.Wizard_SlowTime, 16000}, // Is actually 20 seconds, with a rune that changes to 16 seconds {SNOPower.Wizard_Teleport, 16000}, // Need to be able to check rune that let's us spam this 3 times in a row then cooldown {SNOPower.Wizard_WaveOfForce, 12000}, // normally 15/16 seconds, but a certain rune can allow 12 seconds :( {SNOPower.Wizard_EnergyTwister, 5}, {SNOPower.Wizard_Hydra, 12000}, {SNOPower.Wizard_Meteor, 1000}, {SNOPower.Wizard_Blizzard, 6000}, // Effect lasts for 6 seconds, actual cooldown is 0... {SNOPower.Wizard_IceArmor, 115000}, {SNOPower.Wizard_StormArmor, 115000}, {SNOPower.Wizard_MagicWeapon, 60000}, {SNOPower.Wizard_Familiar, 60000}, {SNOPower.Wizard_EnergyArmor, 115000}, {SNOPower.Wizard_ExplosiveBlast, 6000}, {SNOPower.Wizard_MirrorImage, 5000}, {SNOPower.Wizard_Archon, 100000}, // Actually 120 seconds, but 100 seconds with a rune {SNOPower.Wizard_Archon_ArcaneBlast, 5000}, {SNOPower.Wizard_Archon_ArcaneStrike, 200}, {SNOPower.Wizard_Archon_DisintegrationWave, 5}, {SNOPower.Wizard_Archon_SlowTime, 16000}, {SNOPower.Wizard_Archon_Teleport, 10000}, // Witch Doctor skills {SNOPower.Witchdoctor_PoisonDart, 5}, {SNOPower.Witchdoctor_CorpseSpider, 5}, {SNOPower.Witchdoctor_PlagueOfToads, 5}, {SNOPower.Witchdoctor_Firebomb, 5}, {SNOPower.Witchdoctor_GraspOfTheDead, 6000}, {SNOPower.Witchdoctor_Firebats, 5}, {SNOPower.Witchdoctor_Haunt, 12000}, {SNOPower.Witchdoctor_Locust_Swarm, 8000}, {SNOPower.Witchdoctor_SummonZombieDog, 25000}, {SNOPower.Witchdoctor_Horrify, 16200}, {SNOPower.Witchdoctor_SpiritWalk, 15200}, {SNOPower.Witchdoctor_Hex, 15200}, {SNOPower.Witchdoctor_SoulHarvest, 15000}, {SNOPower.Witchdoctor_Sacrifice, 1000}, {SNOPower.Witchdoctor_MassConfusion, 45200}, {SNOPower.Witchdoctor_ZombieCharger, 5}, {SNOPower.Witchdoctor_SpiritBarrage, 15000}, {SNOPower.Witchdoctor_AcidCloud, 5}, {SNOPower.Witchdoctor_WallOfZombies, 25200}, {SNOPower.Witchdoctor_Gargantuan, 25000}, {SNOPower.Witchdoctor_BigBadVoodoo, 120000}, {SNOPower.Witchdoctor_FetishArmy, 90000}, // Demon Hunter skills {SNOPower.DemonHunter_HungeringArrow, 5}, {SNOPower.DemonHunter_EntanglingShot, 5}, {SNOPower.DemonHunter_BolaShot, 5}, {SNOPower.DemonHunter_Grenades, 5}, {SNOPower.DemonHunter_Impale, 5}, {SNOPower.DemonHunter_RapidFire, 5}, {SNOPower.DemonHunter_Chakram, 5}, {SNOPower.DemonHunter_ElementalArrow, 5}, {SNOPower.DemonHunter_Caltrops, 6000}, {SNOPower.DemonHunter_SmokeScreen, 3000}, {SNOPower.DemonHunter_ShadowPower, 5000}, {SNOPower.DemonHunter_Vault, 400}, {SNOPower.DemonHunter_Preparation, 5000}, {SNOPower.DemonHunter_Companion, 30000}, {SNOPower.DemonHunter_MarkedForDeath, 10000}, {SNOPower.DemonHunter_EvasiveFire, 300}, {SNOPower.DemonHunter_FanOfKnives, 10000}, {SNOPower.DemonHunter_SpikeTrap, 1000}, {SNOPower.DemonHunter_Sentry, 12000}, {SNOPower.DemonHunter_Strafe, 5}, {SNOPower.DemonHunter_Multishot, 5}, {SNOPower.DemonHunter_ClusterArrow, 150}, {SNOPower.DemonHunter_RainOfVengeance, 10000}, }; // Actual delays copy the defaults public static Dictionary dictAbilityRepeatDelay = new Dictionary(dictAbilityRepeatDefaults); // Last used-timers for all abilities to prevent spamming D3 memory for cancast checks too often // These should NEVER need manually editing // But you do need to make sure every skill used by Trinity is listed in here once! private static Dictionary dictAbilityLastUseDefaults = new Dictionary { {SNOPower.DrinkHealthPotion, DateTime.Today},{SNOPower.Weapon_Melee_Instant, DateTime.Today},{SNOPower.Weapon_Ranged_Instant, DateTime.Today}, // Barbarian last-used timers {SNOPower.Barbarian_Bash, DateTime.Today},{SNOPower.Barbarian_Cleave, DateTime.Today},{SNOPower.Barbarian_Frenzy, DateTime.Today}, {SNOPower.Barbarian_HammerOfTheAncients, DateTime.Today},{SNOPower.Barbarian_Rend, DateTime.Today},{SNOPower.Barbarian_SeismicSlam, DateTime.Today}, {SNOPower.Barbarian_Whirlwind, DateTime.Today},{SNOPower.Barbarian_GroundStomp, DateTime.Today},{SNOPower.Barbarian_Leap, DateTime.Today}, {SNOPower.Barbarian_Sprint, DateTime.Today},{SNOPower.Barbarian_IgnorePain, DateTime.Today},{SNOPower.Barbarian_AncientSpear, DateTime.Today}, {SNOPower.Barbarian_Revenge, DateTime.Today},{SNOPower.Barbarian_FuriousCharge, DateTime.Today},{SNOPower.Barbarian_Overpower, DateTime.Today}, {SNOPower.Barbarian_WeaponThrow, DateTime.Today},{SNOPower.Barbarian_ThreateningShout, DateTime.Today},{SNOPower.Barbarian_BattleRage, DateTime.Today}, {SNOPower.Barbarian_WarCry, DateTime.Today},{SNOPower.Barbarian_Earthquake, DateTime.Today},{SNOPower.Barbarian_CallOfTheAncients, DateTime.Today}, {SNOPower.Barbarian_WrathOfTheBerserker, DateTime.Today }, // Monk last-used timers {SNOPower.Monk_FistsofThunder, DateTime.Today},{SNOPower.Monk_DeadlyReach, DateTime.Today},{SNOPower.Monk_CripplingWave, DateTime.Today}, {SNOPower.Monk_WayOfTheHundredFists, DateTime.Today},{SNOPower.Monk_LashingTailKick, DateTime.Today},{SNOPower.Monk_TempestRush, DateTime.Today}, {SNOPower.Monk_WaveOfLight, DateTime.Today},{SNOPower.Monk_BlindingFlash, DateTime.Today},{SNOPower.Monk_BreathOfHeaven, DateTime.Today}, {SNOPower.Monk_Serenity, DateTime.Today}, {SNOPower.Monk_InnerSanctuary, DateTime.Today},{SNOPower.Monk_DashingStrike, DateTime.Today}, {SNOPower.Monk_ExplodingPalm, DateTime.Today},{SNOPower.Monk_SweepingWind, DateTime.Today},{SNOPower.Monk_CycloneStrike, DateTime.Today}, {SNOPower.Monk_SevenSidedStrike, DateTime.Today},{SNOPower.Monk_MysticAlly, DateTime.Today},{SNOPower.Monk_MantraOfEvasion, DateTime.Today}, {SNOPower.Monk_MantraOfRetribution, DateTime.Today},{SNOPower.Monk_MantraOfHealing, DateTime.Today}, {SNOPower.Monk_MantraOfConviction, DateTime.Today}, // Wizard last-used timers {SNOPower.Wizard_MagicMissile, DateTime.Today},{SNOPower.Wizard_ShockPulse, DateTime.Today},{SNOPower.Wizard_SpectralBlade, DateTime.Today}, {SNOPower.Wizard_Electrocute, DateTime.Today},{SNOPower.Wizard_RayOfFrost, DateTime.Today},{SNOPower.Wizard_ArcaneOrb, DateTime.Today}, {SNOPower.Wizard_ArcaneTorrent, DateTime.Today},{SNOPower.Wizard_Disintegrate, DateTime.Today},{SNOPower.Wizard_FrostNova, DateTime.Today}, {SNOPower.Wizard_DiamondSkin, DateTime.Today},{SNOPower.Wizard_SlowTime, DateTime.Today},{SNOPower.Wizard_Teleport, DateTime.Today}, {SNOPower.Wizard_WaveOfForce, DateTime.Today},{SNOPower.Wizard_EnergyTwister, DateTime.Today},{SNOPower.Wizard_Hydra, DateTime.Today}, {SNOPower.Wizard_Meteor, DateTime.Today},{SNOPower.Wizard_Blizzard, DateTime.Today},{SNOPower.Wizard_IceArmor, DateTime.Today}, {SNOPower.Wizard_StormArmor, DateTime.Today},{SNOPower.Wizard_MagicWeapon, DateTime.Today},{SNOPower.Wizard_Familiar, DateTime.Today}, {SNOPower.Wizard_EnergyArmor, DateTime.Today},{SNOPower.Wizard_ExplosiveBlast, DateTime.Today},{SNOPower.Wizard_MirrorImage, DateTime.Today}, {SNOPower.Wizard_Archon, DateTime.Today},{SNOPower.Wizard_Archon_ArcaneBlast, DateTime.Today},{SNOPower.Wizard_Archon_ArcaneStrike, DateTime.Today}, {SNOPower.Wizard_Archon_DisintegrationWave, DateTime.Today},{SNOPower.Wizard_Archon_SlowTime, DateTime.Today},{SNOPower.Wizard_Archon_Teleport, DateTime.Today}, // Witch Doctor last-used timers {SNOPower.Witchdoctor_PoisonDart, DateTime.Today},{SNOPower.Witchdoctor_CorpseSpider, DateTime.Today},{SNOPower.Witchdoctor_PlagueOfToads, DateTime.Today}, {SNOPower.Witchdoctor_Firebomb, DateTime.Today},{SNOPower.Witchdoctor_GraspOfTheDead, DateTime.Today},{SNOPower.Witchdoctor_Firebats, DateTime.Today}, {SNOPower.Witchdoctor_Haunt, DateTime.Today},{SNOPower.Witchdoctor_Locust_Swarm, DateTime.Today},{SNOPower.Witchdoctor_SummonZombieDog, DateTime.Today}, {SNOPower.Witchdoctor_Horrify, DateTime.Today},{SNOPower.Witchdoctor_SpiritWalk, DateTime.Today},{SNOPower.Witchdoctor_Hex, DateTime.Today}, {SNOPower.Witchdoctor_SoulHarvest, DateTime.Today},{SNOPower.Witchdoctor_Sacrifice, DateTime.Today},{SNOPower.Witchdoctor_MassConfusion, DateTime.Today}, {SNOPower.Witchdoctor_ZombieCharger, DateTime.Today},{SNOPower.Witchdoctor_SpiritBarrage, DateTime.Today},{SNOPower.Witchdoctor_AcidCloud, DateTime.Today}, {SNOPower.Witchdoctor_WallOfZombies, DateTime.Today},{SNOPower.Witchdoctor_Gargantuan, DateTime.Today},{SNOPower.Witchdoctor_BigBadVoodoo, DateTime.Today}, {SNOPower.Witchdoctor_FetishArmy, DateTime.Today}, // Demon Hunter last-used timers {SNOPower.DemonHunter_HungeringArrow, DateTime.Today},{SNOPower.DemonHunter_EntanglingShot, DateTime.Today},{SNOPower.DemonHunter_BolaShot, DateTime.Today}, {SNOPower.DemonHunter_Grenades, DateTime.Today},{SNOPower.DemonHunter_Impale, DateTime.Today},{SNOPower.DemonHunter_RapidFire, DateTime.Today}, {SNOPower.DemonHunter_Chakram, DateTime.Today},{SNOPower.DemonHunter_ElementalArrow, DateTime.Today},{SNOPower.DemonHunter_Caltrops, DateTime.Today}, {SNOPower.DemonHunter_SmokeScreen, DateTime.Today},{SNOPower.DemonHunter_ShadowPower, DateTime.Today},{SNOPower.DemonHunter_Vault, DateTime.Today}, {SNOPower.DemonHunter_Preparation, DateTime.Today},{SNOPower.DemonHunter_Companion, DateTime.Today},{SNOPower.DemonHunter_MarkedForDeath, DateTime.Today}, {SNOPower.DemonHunter_EvasiveFire, DateTime.Today},{SNOPower.DemonHunter_FanOfKnives, DateTime.Today},{SNOPower.DemonHunter_SpikeTrap, DateTime.Today}, {SNOPower.DemonHunter_Sentry, DateTime.Today},{SNOPower.DemonHunter_Strafe, DateTime.Today},{SNOPower.DemonHunter_Multishot, DateTime.Today}, {SNOPower.DemonHunter_ClusterArrow, DateTime.Today},{SNOPower.DemonHunter_RainOfVengeance, DateTime.Today}, }; // This is the ACTUAL dictionary used now (the above are used to quickly reset all timers back to defaults on death etc.) public static Dictionary dictAbilityLastUse = new Dictionary(dictAbilityLastUseDefaults); // And this is to avoid using certain long-cooldown skills immediately after a fail public static Dictionary dictAbilityLastFailed = new Dictionary(dictAbilityLastUseDefaults); // And a "global cooldown" to prevent non-signature-spells being used too fast public static DateTime lastGlobalCooldownUse = DateTime.Today; // Used only for certain skills that spam the powermanager regularly, to limit their CPU hits private static Dictionary dictAbilityLastPowerChecked = new Dictionary { {SNOPower.Barbarian_Revenge, DateTime.Today}, {SNOPower.Barbarian_FuriousCharge, DateTime.Today}, {SNOPower.Wizard_DiamondSkin, DateTime.Today}, {SNOPower.Wizard_FrostNova, DateTime.Today}, {SNOPower.Wizard_ExplosiveBlast, DateTime.Today}, {SNOPower.Witchdoctor_Hex, DateTime.Today}, {SNOPower.Witchdoctor_SoulHarvest, DateTime.Today}, }; // ********************************************************************************************** // ***** Check re-use timers on skills ***** // ********************************************************************************************** // Returns whether or not we can use a skill, or if it's on our own internal Trinity cooldown timer private static bool GilesUseTimer(SNOPower thispower, bool bReCheck = false) { if (DateTime.Now.Subtract(dictAbilityLastUse[thispower]).TotalMilliseconds >= dictAbilityRepeatDelay[thispower]) return true; if (bReCheck && DateTime.Now.Subtract(dictAbilityLastUse[thispower]).TotalMilliseconds >= 150 && DateTime.Now.Subtract(dictAbilityLastUse[thispower]).TotalMilliseconds <= 600) return true; return false; } // This function checks when the spell last failed (according to D3 memory, which isn't always reliable) // To prevent Trinity getting stuck re-trying the same spell over and over and doing nothing else // No longer used but keeping this here incase I re-use it private static bool GilesCanRecastAfterFailure(SNOPower thispower, int iMaxRecheckTime = 250) { if (DateTime.Now.Subtract(dictAbilityLastFailed[thispower]).TotalMilliseconds <= iMaxRecheckTime) return false; return true; } // When last hit the power-manager for this - not currently used, saved here incase I use it again in the future! // This is a safety function to prevent spam of the CPU and time-intensive "PowerManager.CanCast" function in DB // No longer used but keeping this here incase I re-use it private static bool GilesPowerManager(SNOPower thispower, int iMaxRecheckTime) { if (DateTime.Now.Subtract(dictAbilityLastPowerChecked[thispower]).TotalMilliseconds <= iMaxRecheckTime) return false; dictAbilityLastPowerChecked[thispower] = DateTime.Now; if (PowerManager.CanCast(thispower)) return true; return false; } // ********************************************************************************************** // ***** Checking for buffs and caching the buff list ***** // ********************************************************************************************** // Cache all current buffs on character public static void GilesRefreshBuffs() { listCachedBuffs = new List(); dictCachedBuffs = new Dictionary(); listCachedBuffs = ZetaDia.Me.GetAllBuffs().ToList(); // Special flag for detecting the activation and de-activation of archon bool bThisArchonBuff = false; int iTempStackCount; // Store how many stacks of each buff we have foreach (Buff thisbuff in listCachedBuffs) { // Store the stack count of this buff if (!dictCachedBuffs.TryGetValue(thisbuff.SNOId, out iTempStackCount)) dictCachedBuffs.Add(thisbuff.SNOId, thisbuff.StackCount); // Check for archon stuff if (thisbuff.SNOId == (int)SNOPower.Wizard_Archon) bThisArchonBuff = true; } // Archon stuff if (bThisArchonBuff) { if (!bHasHadArchonbuff) bRefreshHotbarAbilities = true; bHasHadArchonbuff = true; } else { if (bHasHadArchonbuff) { hashPowerHotbarAbilities = new HashSet(hashCachedPowerHotbarAbilities); } bHasHadArchonbuff = false; } //"g_killElitePack : 1, snoid=230745" <- Noting this here incase I ever want to monitor NV stacks, this is the SNO ID code for it! } // Check if a particular buff is present public static bool GilesHasBuff(SNOPower power) { int id = (int)power; return listCachedBuffs.Any(u => u.SNOId == id); } // Returns how many stacks of a particular buff there are public static int GilesBuffStacks(SNOPower thispower) { int iStacks; if (dictCachedBuffs.TryGetValue((int)thispower, out iStacks)) { return iStacks; } return 0; } // Refresh the skills in our hotbar // Also caches the values after - but ONLY if we aren't in archon mode (or if this function is told NOT to cache this) public static void GilesRefreshHotbar(bool bDontCacheThis = false) { bMappedPlayerAbilities = true; hashPowerHotbarAbilities = new HashSet(); for (int i = 0; i <= 5; i++) hashPowerHotbarAbilities.Add(ZetaDia.Me.GetHotbarPowerId((HotbarSlot)i)); bRefreshHotbarAbilities = false; if (!bDontCacheThis) hashCachedPowerHotbarAbilities = new HashSet(hashPowerHotbarAbilities); } // ********************************************************************************************** // ***** Now Find the best ability to use ***** // ********************************************************************************************** // Special check to force re-buffing before castign archon private static bool bCanCastArchon = false; // Extra height added on for location-based-attacks on targets - may be needed for beam-spells etc. for wizards? (eg add 2 foot height from their feet) private const float iExtraHeight = 2f; public static GilesPower GilesAbilitySelector(bool bCurrentlyAvoiding = false, bool bOOCBuff = false, bool bDestructiblePower = false) { // Refresh buffs once to save buff-check-spam GilesRefreshBuffs(); // See if archon just appeared/disappeared, so update the hotbar if (bRefreshHotbarAbilities) GilesRefreshHotbar(GilesHasBuff(SNOPower.Wizard_Archon)); // Extra height thingy, not REALLY used as it was originally going to be, will probably get phased out... float iThisHeight = iExtraHeight; // Switch based on the cached character class switch (iMyCachedActorClass) { // ***************** // ***** Barbs ***** // ***************** case ActorClass.Barbarian: // Pick the best destructible power available if (bDestructiblePower) { if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Frenzy)) return new GilesPower(SNOPower.Barbarian_Frenzy, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Bash)) return new GilesPower(SNOPower.Barbarian_Bash, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Cleave)) return new GilesPower(SNOPower.Barbarian_Cleave, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Rend) && playerStatus.dCurrentEnergyPct >= 0.65) return new GilesPower(SNOPower.Barbarian_Rend, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_WeaponThrow) && playerStatus.dCurrentEnergy >= 20) return new GilesPower(SNOPower.Barbarian_WeaponThrow, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); return new GilesPower(SNOPower.Weapon_Melee_Instant, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); } // Barbarians need 56 reserve for special spam like WW iWaitingReservedAmount = 56; // Ignore Pain when low on health if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_IgnorePain) && playerStatus.dCurrentHealthPct <= 0.45 && GilesUseTimer(SNOPower.Barbarian_IgnorePain, true) && PowerManager.CanCast(SNOPower.Barbarian_IgnorePain)) { return new GilesPower(SNOPower.Barbarian_IgnorePain, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Flag up a variable to see if we should reserve 50 fury for special abilities bWaitingForSpecial = false; if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Earthquake) && iElitesWithinRange[RANGE_25] >= 1 && GilesUseTimer(SNOPower.Barbarian_Earthquake)) { bWaitingForSpecial = true; } if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_WrathOfTheBerserker) && iElitesWithinRange[RANGE_25] >= 1 && GilesUseTimer(SNOPower.Barbarian_WrathOfTheBerserker)) { bWaitingForSpecial = true; } if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_CallOfTheAncients) && iElitesWithinRange[RANGE_25] >= 1 && GilesUseTimer(SNOPower.Barbarian_CallOfTheAncients)) { bWaitingForSpecial = true; } // Earthquake, elites close-range only if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Earthquake) && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_15] > 0 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 13f)) && GilesUseTimer(SNOPower.Barbarian_Earthquake, true) && PowerManager.CanCast(SNOPower.Barbarian_Earthquake)) { if (playerStatus.dCurrentEnergy >= 50) return new GilesPower(SNOPower.Barbarian_Earthquake, 13f, vNullLocation, iCurrentWorldID, -1, 4, 4, USE_SLOWLY); bWaitingForSpecial = true; } // Wrath of the berserker, elites only (wrath of berserker) //intell if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_WrathOfTheBerserker) && bUseBerserker && // Not on heart of sin after Cydaea targetCurrent.iThisActorSNO != 193077 && // Make sure we are allowed to use wrath on goblins, else make sure this isn't a goblin (unless an elite is nearby) (settings.bGoblinWrath || !targetCurrent.bThisTreasureGoblin || iElitesWithinRange[RANGE_15] >= 1) && // If on a boss, only when injured ((targetCurrent.bThisBoss && targetCurrent.iThisHitPoints <= 0.99 && !hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind)) || // If *NOT* on a boss, and definitely no boss in range, then judge based on any elites at all within 30 feet ((!targetCurrent.bThisBoss || hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind)) && (!bAnyBossesInRange || hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind)) && ((iElitesWithinRange[RANGE_20] >= 1 || targetCurrent.bThisEliteRareUnique) && (targetCurrent.iThisHitPoints >= 0.30 || playerStatus.dCurrentHealthPct <= 0.60)) )) && // Don't still have the buff !GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker) && GilesUseTimer(SNOPower.Barbarian_WrathOfTheBerserker, true) && PowerManager.CanCast(SNOPower.Barbarian_WrathOfTheBerserker)) { if (playerStatus.dCurrentEnergy >= 50) { if (targetCurrent.iThisActorSNO == 255996) Logging.Write("[GilesTrinity] Berserk being used: Act 1 Warden, Odeg!"); else if (targetCurrent.iThisActorSNO == 256000) Logging.Write("[GilesTrinity] Berserk being used: Act 2 Warden, Sokahr!"); else if (targetCurrent.iThisActorSNO == 256015) Logging.Write("[GilesTrinity] Berserk being used: Act 3 Warden, Xah'rith!"); else Logging.Write("[GilesTrinity] Berserk being used!"); bUseBerserker = false; return new GilesPower(SNOPower.Barbarian_WrathOfTheBerserker, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); //intell -- 4, 4 } else { Logging.Write("[GilesTrinity] Berserk ready, waiting for fury..."); bWaitingForSpecial = true; } } // Call of the ancients, elites only if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_CallOfTheAncients) && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_25] > 0 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 25f)) && GilesUseTimer(SNOPower.Barbarian_CallOfTheAncients, true) && PowerManager.CanCast(SNOPower.Barbarian_CallOfTheAncients)) { if (playerStatus.dCurrentEnergy >= 50) return new GilesPower(SNOPower.Barbarian_CallOfTheAncients, 0f, vNullLocation, iCurrentWorldID, -1, 4, 4, USE_SLOWLY); bWaitingForSpecial = true; } // Battle rage, for if being followed and before we do sprint if (bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage) && (GilesUseTimer(SNOPower.Barbarian_BattleRage) || !GilesHasBuff(SNOPower.Barbarian_BattleRage)) && playerStatus.dCurrentEnergy >= 20 && PowerManager.CanCast(SNOPower.Barbarian_BattleRage)) { return new GilesPower(SNOPower.Barbarian_BattleRage, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Special segment for sprint as an out-of-combat only //K: avoid spam in the room of azmodan if (bOOCBuff && !bDontSpamOutofCombat && (settings.bOutOfCombatMovementPowers || GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker)) && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Sprint) && !GilesHasBuff(SNOPower.Barbarian_Sprint) && playerStatus.dCurrentEnergy >= 20 && GilesUseTimer(SNOPower.Barbarian_Sprint) && PowerManager.CanCast(SNOPower.Barbarian_Sprint) ) { return new GilesPower(SNOPower.Barbarian_Sprint, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // War cry, constantly maintain if (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_WarCry) && (playerStatus.dCurrentEnergy <= 60 || !GilesHasBuff(SNOPower.Barbarian_WarCry)) && GilesUseTimer(SNOPower.Barbarian_WarCry, true) && (!GilesHasBuff(SNOPower.Barbarian_WarCry) || PowerManager.CanCast(SNOPower.Barbarian_WarCry))) { return new GilesPower(SNOPower.Barbarian_WarCry, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Threatening shout //intell -- added spam usage if fury is low AND is a ww build or waiting for special if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_ThreateningShout) && !playerStatus.bIsIncapacitated && ( iElitesWithinRange[RANGE_20] > 1 || (targetCurrent.bThisBoss && targetCurrent.fRadiusDistance <= 20) || (iAnythingWithinRange[RANGE_20] > 2 && !bAnyBossesInRange && (iElitesWithinRange[RANGE_50] == 0 || hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_SeismicSlam))) || playerStatus.dCurrentHealthPct <= 0.75 || (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) && playerStatus.dCurrentEnergy < 10) || (bWaitingForSpecial && playerStatus.dCurrentEnergy <= 50) ) && GilesUseTimer(SNOPower.Barbarian_ThreateningShout, true) && PowerManager.CanCast(SNOPower.Barbarian_ThreateningShout)) { return new GilesPower(SNOPower.Barbarian_ThreateningShout, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Threatening shout out-of-combat: helps battle rage and sprint (5+15=20) //intell if (bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_ThreateningShout) && (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Sprint) || hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage)) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 5 && playerStatus.dCurrentEnergy < 20 && GilesUseTimer(SNOPower.Barbarian_ThreateningShout, true) && PowerManager.CanCast(SNOPower.Barbarian_ThreateningShout)) { return new GilesPower(SNOPower.Barbarian_ThreateningShout, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Ground Stomp if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_GroundStomp) && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_15] > 0 || iAnythingWithinRange[RANGE_15] > 4 || playerStatus.dCurrentHealthPct <= 0.7) && GilesUseTimer(SNOPower.Barbarian_GroundStomp, true) && PowerManager.CanCast(SNOPower.Barbarian_GroundStomp)) { return new GilesPower(SNOPower.Barbarian_GroundStomp, 16f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Revenge used off-cooldown if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Revenge) && !playerStatus.bIsIncapacitated && // Don't use revenge on goblins, too slow! (!targetCurrent.bThisTreasureGoblin || iAnythingWithinRange[RANGE_12] >= 5) && // Doesn't need CURRENT target to be in range, just needs ANYTHING to be within 9 foot, since it's an AOE! (iAnythingWithinRange[RANGE_6] > 0 || targetCurrent.fRadiusDistance <= 6f) && GilesUseTimer(SNOPower.Barbarian_Revenge) && PowerManager.CanCast(SNOPower.Barbarian_Revenge)) { // Note - we have LONGER animation times for whirlwind-users // Since whirlwind seems to interrupt rend so easily int iPreDelay = 3; int iPostDelay = 3; if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind)) { if (powerLastSnoPowerUsed == SNOPower.Barbarian_Whirlwind) { iPreDelay = 5; iPostDelay = 5; } } return new GilesPower(SNOPower.Barbarian_Revenge, 0f, playerStatus.vCurrentPosition, iCurrentWorldID, -1, iPreDelay, iPostDelay, USE_SLOWLY); } // Furious charge if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_FuriousCharge) && (iElitesWithinRange[RANGE_12] > 3 && GilesUseTimer(SNOPower.Barbarian_FuriousCharge) && PowerManager.CanCast(SNOPower.Barbarian_FuriousCharge))) { float fExtraDistance; if (targetCurrent.fCentreDistance <= 25) fExtraDistance = 30; else fExtraDistance = (25 - targetCurrent.fCentreDistance); if (fExtraDistance < 5f) fExtraDistance = 5f; Vector3 vNewTarget = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, targetCurrent.fCentreDistance + fExtraDistance); return new GilesPower(SNOPower.Barbarian_FuriousCharge, 32f, vNewTarget, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Leap used when off-cooldown, or when out-of-range if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Leap) && !playerStatus.bIsIncapacitated && // Less than 90% health *OR* target >= 18 feet away *OR* an elite within 30 feet of us (iAnythingWithinRange[RANGE_20] > 1 || iElitesWithinRange[RANGE_20] > 0) && GilesUseTimer(SNOPower.Barbarian_Leap, true) && PowerManager.CanCast(SNOPower.Barbarian_Leap)) { // For close-by monsters, try to leap a little further than their centre-point float fExtraDistance = targetCurrent.fThisRadius; if (fExtraDistance <= 4f) fExtraDistance = 4f; if (targetCurrent.fCentreDistance + fExtraDistance > 35f) fExtraDistance = 35 - targetCurrent.fCentreDistance; Vector3 vNewTarget = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, targetCurrent.fCentreDistance + fExtraDistance); return new GilesPower(SNOPower.Barbarian_Leap, 35f, vNewTarget, iCurrentWorldID, -1, 2, 2, USE_SLOWLY); } // Rend spam if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Rend) && iAnythingWithinRange[RANGE_12] >= 1 && // Doesn't need CURRENT target to be in range, just needs ANYTHING to be within 9 foot, since it's an AOE! (iAnythingWithinRange[RANGE_6] > 0 || targetCurrent.fRadiusDistance <= 6f) && // Don't use against goblins (they run too quick!) (!targetCurrent.bThisTreasureGoblin || iAnythingWithinRange[RANGE_12] >= 5) && ( // This segment is for people who DON'T have whirlwind (!hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) && ( // *DON'T* use rend if we currently have wrath/earthquake/call available & needed but need to save up energy energy (!bWaitingForSpecial || playerStatus.dCurrentEnergy >= 75) && // Bunch of optionals now that go hand in hand with all of the above... ( // Either off full 4 second or so cooldown... GilesUseTimer(SNOPower.Barbarian_Rend) || // ... or ability to spam rend every 0.4 seconds if more enemies in range than when last used rend... (iAnythingWithinRange[RANGE_6] > iWithinRangeLastRend && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Rend]).TotalMilliseconds >= 1000) || // ... or ability to spam rend every 1.1 seconds if current primary target changes... (targetCurrent.iThisACDGUID != iACDGUIDLastRend && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Rend]).TotalMilliseconds >= 1800) || // ... or ability to spam rend every 1.5 seconds with almost full fury (playerStatus.dCurrentEnergyPct >= 0.85 && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Rend]).TotalMilliseconds >= 2500) || // ... or ability to spam rend every 2 seconds with a lot of fury (playerStatus.dCurrentEnergyPct >= 0.65 && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Rend]).TotalMilliseconds >= 3500) ) )) || // This segment is for people who *DO* have whirlwind (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) && // See if it's off-cooldown and at least 40 fury, or use as a fury dump ( (settings.bFuryDumpWrath && playerStatus.dCurrentEnergyPct >= 0.92 && GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker)) || (settings.bFuryDumpAlways && playerStatus.dCurrentEnergyPct >= 0.92) || (DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Rend]).TotalMilliseconds >= 2800) ) && // Max once every 1.2 seconds even if fury dumping, so sprint can be fury dumped too // DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Rend]).TotalMilliseconds >= 1200 && // 3+ mobs of any kind at close range *OR* one elite/boss/special at close range ( (iAnythingWithinRange[RANGE_15] >= 3 && iElitesWithinRange[RANGE_12] >= 1) || (iAnythingWithinRange[RANGE_15] >= 3 && targetCurrent.bThisTreasureGoblin && targetCurrent.fRadiusDistance <= 13f) || iAnythingWithinRange[RANGE_15] >= 5 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 13f && iAnythingWithinRange[RANGE_15] >= 3) ) ) ) && // And finally, got at least 20 energy playerStatus.dCurrentEnergy >= 20) { iWithinRangeLastRend = iAnythingWithinRange[RANGE_6]; iACDGUIDLastRend = targetCurrent.iThisACDGUID; // Note - we have LONGER animation times for whirlwind-users // Since whirlwind seems to interrupt rend so easily int iPreDelay = 0; int iPostDelay = 0; if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind)) { if (powerLastSnoPowerUsed == SNOPower.Barbarian_Whirlwind || powerLastSnoPowerUsed == SNOPower.None) { iPreDelay = 5; iPostDelay = 5; } } return new GilesPower(SNOPower.Barbarian_Rend, 0f, playerStatus.vCurrentPosition, iCurrentWorldID, -1, iPreDelay, iPostDelay, USE_SLOWLY); } // Overpower used off-cooldown if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Overpower) && !playerStatus.bIsIncapacitated && // Doesn't need CURRENT target to be in range, just needs ANYTHING to be within 9 foot, since it's an AOE! //(iAnythingWithinRange[RANGE_5] > 0 || targetCurrent.fRadiusDistance <= 6f) && //intell -- now used on melee goblin ( iAnythingWithinRange[RANGE_6] >= 2 || (playerStatus.dCurrentHealthPct <= 0.85 && targetCurrent.fRadiusDistance <= 5f) || ( iAnythingWithinRange[RANGE_6] >= 1 && (targetCurrent.bThisEliteRareUnique || targetCurrent.bThisMinion || targetCurrent.bThisBoss || GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker) || (targetCurrent.bThisTreasureGoblin && targetCurrent.fCentreDistance <= 6f) || hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_SeismicSlam)) ) ) && GilesUseTimer(SNOPower.Barbarian_Overpower) && PowerManager.CanCast(SNOPower.Barbarian_Overpower)) { // Note - we have LONGER animation times for whirlwind-users // Since whirlwind seems to interrupt rend so easily /*int iPreDelay = 3; int iPostDelay = 3; if (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind)) { if (powerLastSnoPowerUsed == SNOPower.Barbarian_Whirlwind || powerLastSnoPowerUsed == SNOPower.None) { iPreDelay = 5; iPostDelay = 5; } }*/ int iPreDelay = 0; int iPostDelay = 0; return new GilesPower(SNOPower.Barbarian_Overpower, 0f, playerStatus.vCurrentPosition, iCurrentWorldID, -1, iPreDelay, iPostDelay, USE_SLOWLY); } // Seismic slam enemies within close range if (!bOOCBuff && !bWaitingForSpecial && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_SeismicSlam) && !playerStatus.bIsIncapacitated && (!hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage) || (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage) && GilesHasBuff(SNOPower.Barbarian_BattleRage))) && playerStatus.dCurrentEnergy >= 15 && targetCurrent.fCentreDistance <= 40f && (iAnythingWithinRange[RANGE_50] > 1 || (iAnythingWithinRange[RANGE_50] > 0 && playerStatus.dCurrentEnergyPct >= 0.85 && targetCurrent.iThisHitPoints >= 0.30) || (targetCurrent.bThisBoss || targetCurrent.bThisEliteRareUnique || (targetCurrent.bThisTreasureGoblin && targetCurrent.fCentreDistance <= 20f)))) { return new GilesPower(SNOPower.Barbarian_SeismicSlam, 40f, vNullLocation, -1, targetCurrent.iThisACDGUID, 2, 2, USE_SLOWLY); } // Ancient spear if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_AncientSpear) && GilesUseTimer(SNOPower.Barbarian_AncientSpear) && PowerManager.CanCast(SNOPower.Barbarian_AncientSpear) && targetCurrent.iThisHitPoints >= 0.20) { // For close-by monsters, try to leap a little further than their centre-point float fExtraDistance = targetCurrent.fThisRadius; if (fExtraDistance <= 4f) fExtraDistance = 30f; if (targetCurrent.fCentreDistance + fExtraDistance > 60f) fExtraDistance = 60 - targetCurrent.fCentreDistance; if (fExtraDistance < 30) fExtraDistance = 30f; Vector3 vNewTarget = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, targetCurrent.fCentreDistance + fExtraDistance); return new GilesPower(SNOPower.Barbarian_AncientSpear, 55f, vNewTarget, iCurrentWorldID, -1, 2, 2, USE_SLOWLY); } // Sprint buff, if same suitable targets as elites, keep maintained for WW users // K: avoid spam in the room of azmodan if (!bOOCBuff && !bDontSpamOutofCombat && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Sprint) && !playerStatus.bIsIncapacitated && //intell -- Let's check if is not spaming to much DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Sprint]).TotalMilliseconds >= 200 && // Fury Dump Options for sprint: use at max energy constantly, or on a timer ( (settings.bFuryDumpWrath && playerStatus.dCurrentEnergyPct >= 0.95 && GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker)) || (settings.bFuryDumpAlways && playerStatus.dCurrentEnergyPct >= 0.95) || ((GilesUseTimer(SNOPower.Barbarian_Sprint) && !GilesHasBuff(SNOPower.Barbarian_Sprint)) && // Always keep up if we are whirlwinding, or if the target is a goblin (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) || targetCurrent.bThisTreasureGoblin)) ) && // Or if the target is >16 feet away and we have 50+ fury //(targetCurrent.fCentreDistance >= 16f && playerStatus.dCurrentEnergy >= 50) // If they have battle-rage, make sure it's up (!hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage) || (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage) && GilesHasBuff(SNOPower.Barbarian_BattleRage))) && // Check for reserved-energy waiting or not //((playerStatus.dCurrentEnergy >= 40 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount) && playerStatus.dCurrentEnergy >= 20) { //return new GilesPower(SNOPower.Barbarian_Sprint, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); return new GilesPower(SNOPower.Barbarian_Sprint, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Whirlwind spam as long as necessary pre-buffs are up if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) && !playerStatus.bIsIncapacitated && !playerStatus.bIsRooted && // Don't WW against goblins, units in the special SNO list (!settings.bSelectiveWhirlwind || bAnyNonWWIgnoreMobsInRange || !hashActorSNOWhirlwindIgnore.Contains(targetCurrent.iThisActorSNO)) && // Only if within 15 foot of main target ((targetCurrent.fRadiusDistance <= 25f || iAnythingWithinRange[RANGE_25] >= 1)) && (iAnythingWithinRange[RANGE_50] >= 2 || targetCurrent.iThisHitPoints >= 0.30 || targetCurrent.bThisBoss || targetCurrent.bThisEliteRareUnique || playerStatus.dCurrentHealthPct <= 0.60) && // Check for energy reservation amounts //((playerStatus.dCurrentEnergy >= 20 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount) && playerStatus.dCurrentEnergy >= 10 && // If they have battle-rage, make sure it's up (!hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage) || (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage) && GilesHasBuff(SNOPower.Barbarian_BattleRage)))) // If they have sprint, make sure it's up //(!hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Sprint) || (hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Sprint) && GilesHasBuff(SNOPower.Barbarian_Sprint)))) { bool bGenerateNewZigZag = (DateTime.Now.Subtract(lastChangedZigZag).TotalMilliseconds >= 2000f || (vPositionLastZigZagCheck != vNullLocation && playerStatus.vCurrentPosition == vPositionLastZigZagCheck && DateTime.Now.Subtract(lastChangedZigZag).TotalMilliseconds >= 600f) || Vector3.Distance(playerStatus.vCurrentPosition, vSideToSideTarget) <= 5f || targetCurrent.iThisACDGUID != iACDGUIDLastWhirlwind); vPositionLastZigZagCheck = playerStatus.vCurrentPosition; if (bGenerateNewZigZag) { //float fExtraDistance = targetCurrent.fCentreDistance+(targetCurrent.fCentreDistance <= 15f ? 12f : 7f); //K: Direction is more important than distance, we cannot run 20f in 1500ms anyway //K: Randomize start point to help stuck in wall if (iAnythingWithinRange[RANGE_30] >= 6 || iElitesWithinRange[RANGE_30] >= 3 || tmp_iThisActorSNO == 89690) vSideToSideTarget = FindZigZagTargetLocation(targetCurrent.vThisPosition, 20f, false, false, true); else vSideToSideTarget = FindZigZagTargetLocation(targetCurrent.vThisPosition, 20f); powerLastSnoPowerUsed = SNOPower.None; iACDGUIDLastWhirlwind = targetCurrent.iThisACDGUID; lastChangedZigZag = DateTime.Now; } return new GilesPower(SNOPower.Barbarian_Whirlwind, 10f, vSideToSideTarget, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Battle rage, constantly maintain if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_BattleRage) && !playerStatus.bIsIncapacitated && // Fury Dump Options for battle rage IF they don't have sprint ( (settings.bFuryDumpWrath && playerStatus.dCurrentEnergyPct >= 0.99 && GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker)) || (settings.bFuryDumpAlways && playerStatus.dCurrentEnergyPct >= 0.99) || !GilesHasBuff(SNOPower.Barbarian_BattleRage) ) && playerStatus.dCurrentEnergy >= 20 && PowerManager.CanCast(SNOPower.Barbarian_BattleRage)) { //intell return new GilesPower(SNOPower.Barbarian_BattleRage, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Hammer of the ancients spam-attacks - never use if waiting for special if (!bOOCBuff && !bCurrentlyAvoiding && !bWaitingForSpecial && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_HammerOfTheAncients) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 20 && ( // More than 75 energy... *OR* 55 energy and target is high on health... playerStatus.dCurrentEnergy >= 75 || (playerStatus.dCurrentEnergy >= 55 && targetCurrent.iThisHitPoints >= 0.50) || // OR... target is elite/goblin/boss... targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss || // OR... player WOTB is active... OR player is low on health... GilesHasBuff(SNOPower.Barbarian_WrathOfTheBerserker) || playerStatus.dCurrentHealthPct <= 0.38 ) && GilesUseTimer(SNOPower.Barbarian_HammerOfTheAncients)) { return new GilesPower(SNOPower.Barbarian_HammerOfTheAncients, 12f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Weapon throw if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_WeaponThrow)) { return new GilesPower(SNOPower.Barbarian_WeaponThrow, 80f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, SIGNATURE_SPAM); } // Frenzy rapid-attacks if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Frenzy)) { return new GilesPower(SNOPower.Barbarian_Frenzy, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, SIGNATURE_SPAM); } // Bash fast-attacks if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Bash)) { return new GilesPower(SNOPower.Barbarian_Bash, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Cleave fast-attacks if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Cleave)) { return new GilesPower(SNOPower.Barbarian_Cleave, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 2, USE_SLOWLY); } // Default attacks if (!bOOCBuff && !bCurrentlyAvoiding) { return new GilesPower(SNOPower.Weapon_Melee_Instant, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } break; // ********************************************************************************************** // ***** Monks ***** // ********************************************************************************************** case ActorClass.Monk: // Pick the best destructible power available if (bDestructiblePower) { if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_FistsofThunder)) return new GilesPower(SNOPower.Monk_FistsofThunder, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_DeadlyReach)) return new GilesPower(SNOPower.Monk_DeadlyReach, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_CripplingWave)) return new GilesPower(SNOPower.Monk_CripplingWave, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_WayOfTheHundredFists)) return new GilesPower(SNOPower.Monk_WayOfTheHundredFists, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); return new GilesPower(SNOPower.Weapon_Melee_Instant, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); } // Monks need 80 for special spam like tempest rushing iWaitingReservedAmount = 80; // 4 Mantras for the initial buff (slow-use) if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfEvasion) && !GilesHasBuff(SNOPower.Monk_MantraOfEvasion) && playerStatus.dCurrentEnergy >= 50 && GilesUseTimer(SNOPower.Monk_MantraOfEvasion, true)) { return new GilesPower(SNOPower.Monk_MantraOfEvasion, 0f, vNullLocation, iCurrentWorldID, -1, 0, 1, USE_SLOWLY); } if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) && !GilesHasBuff(SNOPower.Monk_MantraOfConviction) && playerStatus.dCurrentEnergy >= 50 && GilesUseTimer(SNOPower.Monk_MantraOfConviction, true)) { return new GilesPower(SNOPower.Monk_MantraOfConviction, 0f, vNullLocation, iCurrentWorldID, -1, 0, 1, USE_SLOWLY); } if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfHealing) && !GilesHasBuff(SNOPower.Monk_MantraOfHealing) && playerStatus.dCurrentEnergy >= 50 && GilesUseTimer(SNOPower.Monk_MantraOfHealing, true)) { return new GilesPower(SNOPower.Monk_MantraOfHealing, 0f, vNullLocation, iCurrentWorldID, -1, 0, 1, USE_SLOWLY); } if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfRetribution) && !GilesHasBuff(SNOPower.Monk_MantraOfRetribution) && playerStatus.dCurrentEnergy >= 50 && GilesUseTimer(SNOPower.Monk_MantraOfRetribution, true)) { return new GilesPower(SNOPower.Monk_MantraOfRetribution, 0f, vNullLocation, iCurrentWorldID, -1, 0, 1, USE_SLOWLY); } // Mystic ally if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MysticAlly) && playerStatus.dCurrentEnergy >= 90 && iPlayerOwnedMysticAlly == 0 && GilesUseTimer(SNOPower.Monk_MysticAlly) && PowerManager.CanCast(SNOPower.Monk_MysticAlly)) { return new GilesPower(SNOPower.Monk_MysticAlly, 0f, vNullLocation, iCurrentWorldID, -1, 2, 2, USE_SLOWLY); } // InnerSanctuary if (!bOOCBuff && playerStatus.dCurrentHealthPct <= 0.45 && hashPowerHotbarAbilities.Contains(SNOPower.Monk_InnerSanctuary) && GilesUseTimer(SNOPower.Monk_InnerSanctuary, true) && playerStatus.dCurrentEnergy >= 30 && PowerManager.CanCast(SNOPower.Monk_InnerSanctuary)) { return new GilesPower(SNOPower.Monk_InnerSanctuary, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Serenity if health is low if ((playerStatus.dCurrentHealthPct <= 0.50 || (playerStatus.bIsIncapacitated && playerStatus.dCurrentHealthPct <= 0.90)) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_Serenity) && GilesUseTimer(SNOPower.Monk_Serenity, true) && playerStatus.dCurrentEnergy >= 10 && PowerManager.CanCast(SNOPower.Monk_Serenity)) { return new GilesPower(SNOPower.Monk_Serenity, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Breath of heaven when needing healing or the buff if (!bOOCBuff && (playerStatus.dCurrentHealthPct <= 0.6 || !GilesHasBuff(SNOPower.Monk_BreathOfHeaven)) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_BreathOfHeaven) && (playerStatus.dCurrentEnergy >= 35 || (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_Serenity) && playerStatus.dCurrentEnergy >= 25)) && GilesUseTimer(SNOPower.Monk_BreathOfHeaven) && PowerManager.CanCast(SNOPower.Monk_BreathOfHeaven)) { return new GilesPower(SNOPower.Monk_BreathOfHeaven, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Blinding Flash if (!bOOCBuff && playerStatus.dCurrentEnergy >= 15 && hashPowerHotbarAbilities.Contains(SNOPower.Monk_BlindingFlash) && (iElitesWithinRange[RANGE_15] > 0 || iAnythingWithinRange[RANGE_15] >= 6 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 15f)) && // Check if sweeping wind is on and at least 1.5 sec active (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_SweepingWind) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_SweepingWind) && GilesHasBuff(SNOPower.Monk_SweepingWind) && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Monk_SweepingWind]).TotalMilliseconds >= 1500)) && GilesUseTimer(SNOPower.Monk_BlindingFlash) && PowerManager.CanCast(SNOPower.Monk_BlindingFlash)) { return new GilesPower(SNOPower.Monk_BlindingFlash, 11f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Blinding Flash as a DEFENSE if (!bOOCBuff && playerStatus.dCurrentEnergy >= 10 && hashPowerHotbarAbilities.Contains(SNOPower.Monk_BlindingFlash) && playerStatus.dCurrentHealthPct <= 0.25 && iAnythingWithinRange[RANGE_15] >= 1 && GilesUseTimer(SNOPower.Monk_BlindingFlash) && PowerManager.CanCast(SNOPower.Monk_BlindingFlash)) { return new GilesPower(SNOPower.Monk_BlindingFlash, 11f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Sweeping wind if ((!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Monk_SweepingWind) && !GilesHasBuff(SNOPower.Monk_SweepingWind) && (iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_20] >= 2 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 25f)) && // Check if either we don't have blinding flash, or we do and it's been cast in the last 6000ms //(!hashPowerHotbarAbilities.Contains(SNOPower.Monk_BlindingFlash) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_BlindingFlash) && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Monk_BlindingFlash]).TotalMilliseconds <= 6000)) && // Check our mantras, if we have them, are up first (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfEvasion) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfEvasion) && GilesHasBuff(SNOPower.Monk_MantraOfEvasion))) && (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) && GilesHasBuff(SNOPower.Monk_MantraOfConviction))) && (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfRetribution) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfRetribution) && GilesHasBuff(SNOPower.Monk_MantraOfRetribution))) && // Check the re-use timer and energy costs (playerStatus.dCurrentEnergy >= 75 || (settings.bMonkInnaSet && playerStatus.dCurrentEnergy >= 5)) && GilesUseTimer(SNOPower.Monk_SweepingWind)) || // OR, if we have Inna's and Wind is up, we just spam it to keep it up (settings.bMonkInnaSet && GilesHasBuff(SNOPower.Monk_SweepingWind) && GilesUseTimer(SNOPower.Monk_SweepingWind) && playerStatus.dCurrentEnergy >= 6)) { return new GilesPower(SNOPower.Monk_SweepingWind, 0f, vNullLocation, iCurrentWorldID, -1, 2, 2, USE_SLOWLY); } // Seven Sided Strike if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_15] >= 1 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 15f) || playerStatus.dCurrentHealthPct <= 0.55) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_SevenSidedStrike) && ((playerStatus.dCurrentEnergy >= 50 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount) && GilesUseTimer(SNOPower.Monk_SevenSidedStrike, true) && PowerManager.CanCast(SNOPower.Monk_SevenSidedStrike)) { return new GilesPower(SNOPower.Monk_SevenSidedStrike, 16f, targetCurrent.vThisPosition, iCurrentWorldID, -1, 2, 3, USE_SLOWLY); } // Exploding Palm if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_15] >= 3 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 14f)) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_ExplodingPalm) && ((playerStatus.dCurrentEnergy >= 40 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount) && GilesUseTimer(SNOPower.Monk_ExplodingPalm) && PowerManager.CanCast(SNOPower.Monk_ExplodingPalm)) { return new GilesPower(SNOPower.Monk_ExplodingPalm, 14f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Cyclone Strike if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_20] >= 1 || iAnythingWithinRange[RANGE_20] >= 2 || playerStatus.dCurrentEnergyPct >= 0.5 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 18f)) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_CycloneStrike) && ((playerStatus.dCurrentEnergy >= 70 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount) && GilesUseTimer(SNOPower.Monk_CycloneStrike) && PowerManager.CanCast(SNOPower.Monk_CycloneStrike)) { return new GilesPower(SNOPower.Monk_CycloneStrike, 0f, vNullLocation, iCurrentWorldID, -1, 2, 2, USE_SLOWLY); } // 4 Mantra spam for the 4 second buff if (!bOOCBuff && ( playerStatus.dCurrentEnergy >= 135 || (GilesHasBuff(SNOPower.Monk_SweepingWind) && (playerStatus.dCurrentEnergy >= 75 || (playerStatus.dCurrentEnergy >= 65 && playerStatus.dCurrentHealthPct <= 1.0)) && // Checking we have no expensive finishers !hashPowerHotbarAbilities.Contains(SNOPower.Monk_SevenSidedStrike) && !hashPowerHotbarAbilities.Contains(SNOPower.Monk_LashingTailKick) && !hashPowerHotbarAbilities.Contains(SNOPower.Monk_WaveOfLight) && !hashPowerHotbarAbilities.Contains(SNOPower.Monk_CycloneStrike) && !hashPowerHotbarAbilities.Contains(SNOPower.Monk_ExplodingPalm))) && (iElitesWithinRange[RANGE_30] >= 1 || iAnythingWithinRange[RANGE_30] >= 3)) { if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfEvasion) && GilesUseTimer(SNOPower.Monk_MantraOfEvasion)) { return new GilesPower(SNOPower.Monk_MantraOfEvasion, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) && GilesUseTimer(SNOPower.Monk_MantraOfConviction)) { return new GilesPower(SNOPower.Monk_MantraOfConviction, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfRetribution) && GilesUseTimer(SNOPower.Monk_MantraOfRetribution)) { return new GilesPower(SNOPower.Monk_MantraOfRetribution, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } if (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfHealing) && GilesUseTimer(SNOPower.Monk_MantraOfHealing)) { return new GilesPower(SNOPower.Monk_MantraOfHealing, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } } // Lashing Tail Kick if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Monk_LashingTailKick) && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_15] > 0 || iAnythingWithinRange[RANGE_15] > 4 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 10f)) && // Either doesn't have sweeping wind, or does but the buff is already up (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_SweepingWind) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_SweepingWind) && GilesHasBuff(SNOPower.Monk_SweepingWind))) && GilesUseTimer(SNOPower.Monk_LashingTailKick) && ((playerStatus.dCurrentEnergy >= 65 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) { return new GilesPower(SNOPower.Monk_LashingTailKick, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Wave of light if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_25] > 0 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 14f) || iAnythingWithinRange[RANGE_15] > 2) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_WaveOfLight) && GilesUseTimer(SNOPower.Monk_WaveOfLight) && (playerStatus.dCurrentEnergy >= 90 || playerStatus.dCurrentEnergyPct >= 0.85) && (!hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) || (hashPowerHotbarAbilities.Contains(SNOPower.Monk_MantraOfConviction) && GilesHasBuff(SNOPower.Monk_MantraOfConviction)))) { return new GilesPower(SNOPower.Monk_WaveOfLight, 16f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Tempest rush at elites or groups of mobs if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated && !playerStatus.bIsRooted && (iElitesWithinRange[RANGE_25] > 0 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 14f) || iAnythingWithinRange[RANGE_15] > 2) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_TempestRush) && ((playerStatus.dCurrentEnergy >= 20 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount) && PowerManager.CanCast(SNOPower.Monk_TempestRush)) { bool bGenerateNewZigZag = (DateTime.Now.Subtract(lastChangedZigZag).TotalMilliseconds >= 1500 || (vPositionLastZigZagCheck != vNullLocation && playerStatus.vCurrentPosition == vPositionLastZigZagCheck && DateTime.Now.Subtract(lastChangedZigZag).TotalMilliseconds >= 200) || Vector3.Distance(playerStatus.vCurrentPosition, vSideToSideTarget) <= 4f || targetCurrent.iThisACDGUID != iACDGUIDLastWhirlwind); vPositionLastZigZagCheck = playerStatus.vCurrentPosition; if (bGenerateNewZigZag) { float fExtraDistance = targetCurrent.fCentreDistance <= 20f ? 15f : 5f; vSideToSideTarget = FindZigZagTargetLocation(targetCurrent.vThisPosition, targetCurrent.fCentreDistance + fExtraDistance); // Resetting this to ensure the "no-spam" is reset since we changed our target location powerLastSnoPowerUsed = SNOPower.None; iACDGUIDLastWhirlwind = targetCurrent.iThisACDGUID; lastChangedZigZag = DateTime.Now; } return new GilesPower(SNOPower.Monk_TempestRush, 23f, vSideToSideTarget, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Dashing Strike if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_25] > 0 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 14f) || iAnythingWithinRange[RANGE_15] > 2) && hashPowerHotbarAbilities.Contains(SNOPower.Monk_DashingStrike) && ((playerStatus.dCurrentEnergy >= 30 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) { return new GilesPower(SNOPower.Monk_DashingStrike, 14f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Fists of thunder as the primary, repeatable attack if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Monk_FistsofThunder)) { return new GilesPower(SNOPower.Monk_FistsofThunder, 30f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, SIGNATURE_SPAM); } // Deadly reach if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Monk_DeadlyReach)) { return new GilesPower(SNOPower.Monk_DeadlyReach, 16f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Crippling wave if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Monk_CripplingWave)) { return new GilesPower(SNOPower.Monk_CripplingWave, 14f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Way of hundred fists if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Monk_WayOfTheHundredFists)) { return new GilesPower(SNOPower.Monk_WayOfTheHundredFists, 14f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, SIGNATURE_SPAM); } // Default attacks if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated) { return new GilesPower(SNOPower.Weapon_Melee_Instant, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } break; // ********************************************************************************************** // ***** Wizards ***** // ********************************************************************************************** case ActorClass.Wizard: // Pick the best destructible power available if (bDestructiblePower) { if (!GilesHasBuff(SNOPower.Wizard_Archon)) { if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_EnergyTwister) && playerStatus.dCurrentEnergy >= 35) return new GilesPower(SNOPower.Wizard_EnergyTwister, 9f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_MagicMissile)) return new GilesPower(SNOPower.Wizard_MagicMissile, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_ShockPulse)) return new GilesPower(SNOPower.Wizard_ShockPulse, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_SpectralBlade)) return new GilesPower(SNOPower.Wizard_SpectralBlade, 9f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Electrocute)) return new GilesPower(SNOPower.Wizard_Electrocute, 9f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); } else { if (targetCurrent.fRadiusDistance <= 10f) return new GilesPower(SNOPower.Wizard_Archon_ArcaneStrike, 20f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); return new GilesPower(SNOPower.Wizard_Archon_DisintegrationWave, 19f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); } return new GilesPower(SNOPower.Weapon_Melee_Instant, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); } // Wizards want to save up to a reserve of 65+ energy iWaitingReservedAmount = 65; if (!GilesHasBuff(SNOPower.Wizard_Archon)) { // Slow time, for if being followed if (bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_SlowTime) && GilesUseTimer(SNOPower.Wizard_SlowTime, true) && PowerManager.CanCast(SNOPower.Wizard_SlowTime)) { return new GilesPower(SNOPower.Wizard_SlowTime, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Slow Time for in combat if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_SlowTime) && (iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_25] > 1 || playerStatus.dCurrentHealthPct <= 0.7 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 35f)) && PowerManager.CanCast(SNOPower.Wizard_SlowTime)) { return new GilesPower(SNOPower.Wizard_SlowTime, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Wave of force if (!bOOCBuff && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 25 && ( // Check this isn't a critical mass wizard, cos they won't want to use this except for low health unless they don't have nova/blast in which case go for it (settings.bEnableCriticalMass && ((!hashPowerHotbarAbilities.Contains(SNOPower.Wizard_FrostNova) && !hashPowerHotbarAbilities.Contains(SNOPower.Wizard_ExplosiveBlast)) || (playerStatus.dCurrentHealthPct <= 0.7 && (iElitesWithinRange[RANGE_15] > 0 || iAnythingWithinRange[RANGE_15] > 0 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 23f))))) // Else normal wizard in which case check standard stuff || (!settings.bEnableCriticalMass && iElitesWithinRange[RANGE_15] > 0 || iAnythingWithinRange[RANGE_15] > 3 || playerStatus.dCurrentHealthPct <= 0.7 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 23f)) ) && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_WaveOfForce) && GilesUseTimer(SNOPower.Wizard_WaveOfForce, true) && PowerManager.CanCast(SNOPower.Wizard_WaveOfForce)) { return new GilesPower(SNOPower.Wizard_WaveOfForce, 0f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Blizzard if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Blizzard) && powerLastSnoPowerUsed != SNOPower.Wizard_Blizzard && (iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_25] > 2 || targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss || playerStatus.dCurrentHealthPct <= 0.7) && playerStatus.dCurrentEnergy >= 40 && GilesUseTimer(SNOPower.Wizard_Blizzard)) { return new GilesPower(SNOPower.Wizard_Blizzard, 40f, new Vector3(targetCurrent.vThisPosition.X, targetCurrent.vThisPosition.Y, targetCurrent.vThisPosition.Z), iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Meteor if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Meteor) && (iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_25] > 2 || targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss || targetCurrent.bThisTreasureGoblin) && playerStatus.dCurrentEnergy >= 50 && PowerManager.CanCast(SNOPower.Wizard_Meteor)) { return new GilesPower(SNOPower.Wizard_Meteor, 21f, new Vector3(targetCurrent.vThisPosition.X, targetCurrent.vThisPosition.Y, targetCurrent.vThisPosition.Z), iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Teleport in combat for critical-mass wizards if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Teleport) && settings.bEnableCriticalMass && powerLastSnoPowerUsed != SNOPower.Wizard_Teleport && playerStatus.dCurrentEnergy >= 15 && targetCurrent.fCentreDistance <= 35f && PowerManager.CanCast(SNOPower.Wizard_Teleport)) { vSideToSideTarget = FindZigZagTargetLocation(targetCurrent.vThisPosition, targetCurrent.fCentreDistance, true); return new GilesPower(SNOPower.Wizard_Teleport, 35f, vSideToSideTarget, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Diamond Skin SPAM if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_DiamondSkin) && powerLastSnoPowerUsed != SNOPower.Wizard_DiamondSkin && (iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_25] > 0 || playerStatus.dCurrentHealthPct <= 0.90 || playerStatus.bIsIncapacitated || playerStatus.bIsRooted || (!bOOCBuff && targetCurrent.fRadiusDistance <= 40f)) && ((settings.bEnableCriticalMass && !bOOCBuff) || !GilesHasBuff(SNOPower.Wizard_DiamondSkin)) && PowerManager.CanCast(SNOPower.Wizard_DiamondSkin)) { return new GilesPower(SNOPower.Wizard_DiamondSkin, 0f, vNullLocation, iCurrentWorldID, -1, 0, 1, USE_SLOWLY); } // The three wizard armors, done in an else-if loop so it doesn't keep replacing one with the other if (!playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 25) { // Energy armor as priority cast if available and not buffed if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_EnergyArmor)) { if ((!GilesHasBuff(SNOPower.Wizard_EnergyArmor) && PowerManager.CanCast(SNOPower.Wizard_EnergyArmor)) || (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon) && (!GilesHasBuff(SNOPower.Wizard_EnergyArmor) || GilesUseTimer(SNOPower.Wizard_EnergyArmor)))) { return new GilesPower(SNOPower.Wizard_EnergyArmor, 0f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } } // Ice Armor else if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_IceArmor)) { if (!GilesHasBuff(SNOPower.Wizard_IceArmor) && PowerManager.CanCast(SNOPower.Wizard_IceArmor)) { return new GilesPower(SNOPower.Wizard_IceArmor, 0f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } } // Storm Armor else if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_StormArmor)) { if (!GilesHasBuff(SNOPower.Wizard_StormArmor) || ((DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Wizard_StormArmor]).TotalMilliseconds >= 15000) && PowerManager.CanCast(SNOPower.Wizard_Archon))) { return new GilesPower(SNOPower.Wizard_StormArmor, 0f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } } } // Magic Weapon if (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_MagicWeapon) && PowerManager.CanCast(SNOPower.Wizard_MagicWeapon) && (!GilesHasBuff(SNOPower.Wizard_MagicWeapon) || ((DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Wizard_MagicWeapon]).TotalMilliseconds >= 10000) && PowerManager.CanCast(SNOPower.Wizard_Archon)))) { return new GilesPower(SNOPower.Wizard_MagicWeapon, 0f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Familiar if (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Familiar) && playerStatus.dCurrentEnergy >= 25 && GilesUseTimer(SNOPower.Wizard_Familiar)) { return new GilesPower(SNOPower.Wizard_Familiar, 0f, vNullLocation, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Hydra if (!bOOCBuff && !playerStatus.bIsIncapacitated && powerLastSnoPowerUsed != SNOPower.Wizard_Hydra && (iElitesWithinRange[RANGE_15] > 0 || iAnythingWithinRange[RANGE_15] > 4 || playerStatus.dCurrentHealthPct <= 0.7 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss || targetCurrent.bThisTreasureGoblin) && targetCurrent.fRadiusDistance <= 15f)) && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Hydra) && playerStatus.dCurrentEnergy >= 15 && GilesUseTimer(SNOPower.Wizard_Hydra)) { // For distant monsters, try to target a little bit in-front of them (as they run towards us), if it's not a treasure goblin float fExtraDistance = 0f; if (targetCurrent.fCentreDistance > 17f && !targetCurrent.bThisTreasureGoblin) { fExtraDistance = targetCurrent.fCentreDistance - 17f; if (fExtraDistance > 5f) fExtraDistance = 5f; if (targetCurrent.fCentreDistance - fExtraDistance < 15f) fExtraDistance -= 2; } Vector3 vNewTarget = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, targetCurrent.fCentreDistance - fExtraDistance); return new GilesPower(SNOPower.Wizard_Hydra, 30f, vNewTarget, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Mirror Image @ half health or 5+ monsters or rooted/incapacitated or last elite left @25% health if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_MirrorImage) && (playerStatus.dCurrentHealthPct <= 0.50 || iAnythingWithinRange[RANGE_30] >= 5 || playerStatus.bIsIncapacitated || playerStatus.bIsRooted || (iElitesWithinRange[RANGE_30] == 1 && targetCurrent.bThisEliteRareUnique && !targetCurrent.bThisBoss && targetCurrent.iThisHitPoints <= 0.35)) && PowerManager.CanCast(SNOPower.Wizard_MirrorImage)) { return new GilesPower(SNOPower.Wizard_MirrorImage, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Archon if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon) && (iElitesWithinRange[RANGE_30] >= 1 || iAnythingWithinRange[RANGE_25] >= 3 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 30f)) && playerStatus.dCurrentEnergy >= 25 && playerStatus.dCurrentHealthPct >= 0.10 && PowerManager.CanCast(SNOPower.Wizard_Archon)) { // Familiar has been removed for now. Uncomment the three comments below relating to familiars to force re-buffing them bool bHasBuffAbilities = (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_MagicWeapon) || //hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Familiar) || hashPowerHotbarAbilities.Contains(SNOPower.Wizard_EnergyArmor) || hashPowerHotbarAbilities.Contains(SNOPower.Wizard_IceArmor) || hashPowerHotbarAbilities.Contains(SNOPower.Wizard_StormArmor)); int iExtraEnergyNeeded = 25; if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_MagicWeapon)) iExtraEnergyNeeded += 25; //if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Familiar)) iExtraEnergyNeeded += 25; if (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_EnergyArmor) || hashPowerHotbarAbilities.Contains(SNOPower.Wizard_IceArmor) || hashPowerHotbarAbilities.Contains(SNOPower.Wizard_StormArmor)) iExtraEnergyNeeded += 25; if (!bHasBuffAbilities || playerStatus.dCurrentEnergy <= iExtraEnergyNeeded) bCanCastArchon = true; if (!bCanCastArchon) { dictAbilityLastUse[SNOPower.Wizard_MagicWeapon] = DateTime.Today; //dictAbilityLastUse[SNOPower.Wizard_Familiar] = DateTime.Today; dictAbilityLastUse[SNOPower.Wizard_EnergyArmor] = DateTime.Today; dictAbilityLastUse[SNOPower.Wizard_IceArmor] = DateTime.Today; dictAbilityLastUse[SNOPower.Wizard_StormArmor] = DateTime.Today; bCanCastArchon = true; } else { bCanCastArchon = false; return new GilesPower(SNOPower.Wizard_Archon, 0f, vNullLocation, iCurrentWorldID, -1, 4, 5, USE_SLOWLY); } } // Frost Nova SPAM if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_FrostNova) && !playerStatus.bIsIncapacitated && ((iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_25] > 0 || playerStatus.dCurrentHealthPct <= 0.7) && targetCurrent.fRadiusDistance <= 12f) && PowerManager.CanCast(SNOPower.Wizard_FrostNova)) { float fThisRange = 14f; if (settings.bEnableCriticalMass) fThisRange = 9f; return new GilesPower(SNOPower.Wizard_FrostNova, fThisRange, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Explosive Blast SPAM when enough AP, blow erry thing up, nah mean if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_ExplosiveBlast) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 20 && ((iElitesWithinRange[RANGE_25] >= 1 || iAnythingWithinRange[RANGE_25] >= 1 || playerStatus.dCurrentHealthPct <= 0.7) && targetCurrent.fRadiusDistance <= 12f) && PowerManager.CanCast(SNOPower.Wizard_ExplosiveBlast)) { float fThisRange = 11f; if (settings.bEnableCriticalMass) fThisRange = 9f; return new GilesPower(SNOPower.Wizard_ExplosiveBlast, fThisRange, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Check to see if we have a signature spell on our hotbar, for energy twister check bool bHasSignatureSpell = (hashPowerHotbarAbilities.Contains(SNOPower.Wizard_MagicMissile) || hashPowerHotbarAbilities.Contains(SNOPower.Wizard_ShockPulse) || hashPowerHotbarAbilities.Contains(SNOPower.Wizard_SpectralBlade) || hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Electrocute)); // Energy Twister SPAMS whenever 35 or more ap to generate Arcane Power if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_EnergyTwister) && // If using storm chaser, then force a signature spell every 1 stack of the buff, if we have a signature spell (!bHasSignatureSpell || GilesBuffStacks(SNOPower.Wizard_EnergyTwister) < 1) && (iElitesWithinRange[RANGE_30] >= 1 || iAnythingWithinRange[RANGE_25] >= 1 || targetCurrent.fRadiusDistance <= 12f) && (!hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Electrocute) || !hashActorSNOFastMobs.Contains(targetCurrent.iThisActorSNO)) && ((settings.bEnableCriticalMass && (!bHasSignatureSpell || playerStatus.dCurrentEnergy >= 35)) || (!settings.bEnableCriticalMass && playerStatus.dCurrentEnergy >= 35))) { float fThisRange = 28f; if (settings.bEnableCriticalMass) fThisRange = 9f; return new GilesPower(SNOPower.Wizard_EnergyTwister, fThisRange, new Vector3(targetCurrent.vThisPosition.X, targetCurrent.vThisPosition.Y, targetCurrent.vThisPosition.Z), iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Disintegrate if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Disintegrate) && ((playerStatus.dCurrentEnergy >= 20 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) { float fThisRange = 35f; if (settings.bEnableCriticalMass) fThisRange = 20f; return new GilesPower(SNOPower.Wizard_Disintegrate, fThisRange, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, SIGNATURE_SPAM); } // Arcane Orb if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_ArcaneOrb) && ((playerStatus.dCurrentEnergy >= 35 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount) && GilesUseTimer(SNOPower.Wizard_ArcaneOrb)) { float fThisRange = 40f; if (settings.bEnableCriticalMass) fThisRange = 20f; return new GilesPower(SNOPower.Wizard_ArcaneOrb, fThisRange, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Arcane Torrent if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_ArcaneTorrent) && ((playerStatus.dCurrentEnergy >= 16 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount) && GilesUseTimer(SNOPower.Wizard_ArcaneTorrent)) { float fThisRange = 40f; /*if (settings.bEnableCriticalMass) fThisRange = 20f;*/ return new GilesPower(SNOPower.Wizard_ArcaneTorrent, fThisRange, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); } // Ray of Frost if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_RayOfFrost) && playerStatus.dCurrentEnergy >= 12) { float fThisRange = 35f; if (settings.bEnableCriticalMass) fThisRange = 20f; return new GilesPower(SNOPower.Wizard_RayOfFrost, fThisRange, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, SIGNATURE_SPAM); } // Magic Missile if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_MagicMissile)) { float fThisRange = 35f; if (settings.bEnableCriticalMass) fThisRange = 20f; return new GilesPower(SNOPower.Wizard_MagicMissile, fThisRange, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); } // Shock Pulse if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_ShockPulse)) { return new GilesPower(SNOPower.Wizard_ShockPulse, 15f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Spectral Blade if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_SpectralBlade)) { return new GilesPower(SNOPower.Wizard_SpectralBlade, 14f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Electrocute if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Electrocute)) { return new GilesPower(SNOPower.Wizard_Electrocute, 40f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); } // Default attacks if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated) { return new GilesPower(SNOPower.Weapon_Melee_Instant, 10f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } } else { // Archon form // Archon Slow Time for in combat if (!bOOCBuff && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_25] > 1 || playerStatus.dCurrentHealthPct <= 0.7 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 35f)) && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon_SlowTime) && GilesUseTimer(SNOPower.Wizard_Archon_SlowTime, true) && PowerManager.CanCast(SNOPower.Wizard_Archon_SlowTime)) { return new GilesPower(SNOPower.Wizard_Archon_SlowTime, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Archon Teleport in combat if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon_Teleport) && // Try and teleport-retreat from 1 elite or 3+ greys or a boss at 15 foot range (iElitesWithinRange[RANGE_15] >= 1 || iAnythingWithinRange[RANGE_15] >= 3 || (targetCurrent.bThisBoss && targetCurrent.fRadiusDistance <= 15f)) && GilesUseTimer(SNOPower.Wizard_Archon_Teleport) && PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport)) { Vector3 vNewTarget = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, -20f); return new GilesPower(SNOPower.Wizard_Archon_Teleport, 35f, vNewTarget, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Arcane Blast if (!bOOCBuff && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_15] >= 1 || iAnythingWithinRange[RANGE_15] >= 1 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 15f)) && GilesUseTimer(SNOPower.Wizard_Archon_ArcaneBlast) && PowerManager.CanCast(SNOPower.Wizard_Archon_ArcaneBlast)) { return new GilesPower(SNOPower.Wizard_Archon_ArcaneBlast, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Arcane Strike (Arcane Strike) Rapid Spam at close-range only if (!bOOCBuff && !playerStatus.bIsIncapacitated && targetCurrent.fRadiusDistance <= 13f && (targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss)) { return new GilesPower(SNOPower.Wizard_Archon_ArcaneStrike, 11f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Disintegrate if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated) { return new GilesPower(SNOPower.Wizard_Archon_DisintegrationWave, 49f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, SIGNATURE_SPAM); } } break; // ********************************************************************************************** // ***** Witch Doctors ***** // ********************************************************************************************** case ActorClass.WitchDoctor: // Pick the best destructible power available if (bDestructiblePower) { if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Firebats)) return new GilesPower(SNOPower.Witchdoctor_Firebats, 12f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Firebomb)) return new GilesPower(SNOPower.Witchdoctor_Firebomb, 12f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_PoisonDart)) return new GilesPower(SNOPower.Witchdoctor_PoisonDart, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_ZombieCharger) && playerStatus.dCurrentEnergy >= 140) return new GilesPower(SNOPower.Witchdoctor_ZombieCharger, 12f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_CorpseSpider)) return new GilesPower(SNOPower.Witchdoctor_CorpseSpider, 12f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_PlagueOfToads)) return new GilesPower(SNOPower.Witchdoctor_PlagueOfToads, 12f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); return new GilesPower(SNOPower.Weapon_Melee_Instant, 10f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); } // Witch doctors have no reserve requirements? iWaitingReservedAmount = 0; // Spirit Walk Cast on 65% health or while avoiding anything but molten core or incapacitated or Chasing Goblins if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_SpiritWalk) && playerStatus.dCurrentEnergy >= 49 && ( playerStatus.dCurrentHealthPct <= 0.65 || playerStatus.bIsIncapacitated || playerStatus.bIsRooted || (settings.bOutOfCombatMovementPowers && bOOCBuff) || (!bOOCBuff && targetCurrent.bThisTreasureGoblin && targetCurrent.iThisHitPoints < 0.90 && targetCurrent.fRadiusDistance <= 40f) ) && PowerManager.CanCast(SNOPower.Witchdoctor_SpiritWalk)) { return new GilesPower(SNOPower.Witchdoctor_SpiritWalk, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Soul Harvest Any Elites or 2+ Norms and baby it's harvest season if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_SoulHarvest) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 59 && GilesBuffStacks(SNOPower.Witchdoctor_SoulHarvest) < 4 && (iElitesWithinRange[RANGE_6] >= 1 || iAnythingWithinRange[RANGE_6] >= 2 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin) && targetCurrent.fRadiusDistance <= 7f)) && PowerManager.CanCast(SNOPower.Witchdoctor_SoulHarvest)) { return new GilesPower(SNOPower.Witchdoctor_SoulHarvest, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Sacrifice AKA Zombie Dog Jihad, use on Elites Only or to try and Save yourself if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Sacrifice) && (iElitesWithinRange[RANGE_15] > 0 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss || targetCurrent.bThisTreasureGoblin) && targetCurrent.fRadiusDistance <= 15f)) && PowerManager.CanCast(SNOPower.Witchdoctor_Sacrifice)) { return new GilesPower(SNOPower.Witchdoctor_Sacrifice, 0f, vNullLocation, iCurrentWorldID, -1, 1, 0, USE_SLOWLY); } // Gargantuan, Recast on 1+ Elites or Bosses to trigger Restless Giant if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Gargantuan) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 147 && (iElitesWithinRange[RANGE_15] >= 1 || (targetCurrent != null && ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 15f)) || iPlayerOwnedGargantuan == 0) && PowerManager.CanCast(SNOPower.Witchdoctor_Gargantuan)) { return new GilesPower(SNOPower.Witchdoctor_Gargantuan, 0f, vNullLocation, iCurrentWorldID, -1, 2, 1, USE_SLOWLY); } // Zombie dogs Woof Woof, good for being blown up, cast when less than or equal to 1 Dog or Not Blowing them up and cast when less than 4 if (!bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_SummonZombieDog) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 49 && (iElitesWithinRange[RANGE_20] >= 2 || iAnythingWithinRange[RANGE_20] >= 5 || (targetCurrent != null && ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin) && targetCurrent.fRadiusDistance <= 30f)) || iPlayerOwnedZombieDog <= 2) && PowerManager.CanCast(SNOPower.Witchdoctor_SummonZombieDog)) { return new GilesPower(SNOPower.Witchdoctor_SummonZombieDog, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Hex Spam Cast on ANYTHING in range, mmm pork and chicken if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Hex) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 49 && (iElitesWithinRange[RANGE_12] >= 1 || iAnythingWithinRange[RANGE_12] >= 1 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 18f)) && PowerManager.CanCast(SNOPower.Witchdoctor_Hex)) { return new GilesPower(SNOPower.Witchdoctor_Hex, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Mass Confuse, elites only or big mobs or to escape on low health if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_MassConfusion) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 74 && (iElitesWithinRange[RANGE_12] >= 1 || iAnythingWithinRange[RANGE_12] >= 6 || playerStatus.dCurrentHealthPct <= 0.25 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 12f)) && !targetCurrent.bThisTreasureGoblin && PowerManager.CanCast(SNOPower.Witchdoctor_MassConfusion)) { return new GilesPower(SNOPower.Witchdoctor_MassConfusion, 0f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Big Bad Voodoo, elites and bosses only if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_BigBadVoodoo) && !playerStatus.bIsIncapacitated && !targetCurrent.bThisTreasureGoblin && (iElitesWithinRange[RANGE_6] > 0 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 12f)) && PowerManager.CanCast(SNOPower.Witchdoctor_BigBadVoodoo)) { return new GilesPower(SNOPower.Witchdoctor_BigBadVoodoo, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Grasp of the Dead, look below, droping globes and dogs when using it on elites and 3 norms if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_GraspOfTheDead) && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_25] > 0 || iAnythingWithinRange[RANGE_25] > 2) && playerStatus.dCurrentEnergy >= 122 && PowerManager.CanCast(SNOPower.Witchdoctor_GraspOfTheDead)) { return new GilesPower(SNOPower.Witchdoctor_GraspOfTheDead, 25f, targetCurrent.vThisPosition, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Horrify Buff When not in combat for movement speed if (bOOCBuff && settings.bEnableCriticalMass == true && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Horrify) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 37 && PowerManager.CanCast(SNOPower.Witchdoctor_Horrify)) { return new GilesPower(SNOPower.Witchdoctor_Horrify, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Horrify Buff at 35% health if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Horrify) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 37 && playerStatus.dCurrentHealthPct <= 0.35 && PowerManager.CanCast(SNOPower.Witchdoctor_Horrify)) { return new GilesPower(SNOPower.Witchdoctor_Horrify, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Fetish Army, elites only if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_FetishArmy) && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_25] > 0 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 16f)) && PowerManager.CanCast(SNOPower.Witchdoctor_FetishArmy)) { return new GilesPower(SNOPower.Witchdoctor_FetishArmy, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Spirit Barrage if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_SpiritBarrage) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 108 && PowerManager.CanCast(SNOPower.Witchdoctor_SpiritBarrage)) { return new GilesPower(SNOPower.Witchdoctor_SpiritBarrage, 21f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Haunt the shit out of monster and maybe they will give you treats if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Haunt) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 98 && PowerManager.CanCast(SNOPower.Witchdoctor_Haunt)) { return new GilesPower(SNOPower.Witchdoctor_Haunt, 21f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Locust if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Locust_Swarm) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 196 && PowerManager.CanCast(SNOPower.Witchdoctor_Locust_Swarm)) { return new GilesPower(SNOPower.Witchdoctor_Locust_Swarm, 12f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Wall of Zombies if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_WallOfZombies) && !playerStatus.bIsIncapacitated && (iElitesWithinRange[RANGE_15] > 0 || iAnythingWithinRange[RANGE_15] > 3 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 25f)) && playerStatus.dCurrentEnergy >= 103 && PowerManager.CanCast(SNOPower.Witchdoctor_WallOfZombies)) { return new GilesPower(SNOPower.Witchdoctor_WallOfZombies, 25f, targetCurrent.vThisPosition, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Zombie Charger aka Zombie bears Spams Bears @ Everything from 11feet away if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_ZombieCharger) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 134 && (iElitesWithinRange[RANGE_12] > 0 || iAnythingWithinRange[RANGE_12] >= 1 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 11f)) && PowerManager.CanCast(SNOPower.Witchdoctor_ZombieCharger)) { return new GilesPower(SNOPower.Witchdoctor_ZombieCharger, 11f, new Vector3(targetCurrent.vThisPosition.X, targetCurrent.vThisPosition.Y, targetCurrent.vThisPosition.Z + iThisHeight), iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Acid Cloud if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_AcidCloud) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 172 && PowerManager.CanCast(SNOPower.Witchdoctor_AcidCloud)) { // For distant monsters, try to target a little bit in-front of them (as they run towards us), if it's not a treasure goblin float fExtraDistance = 0f; if (targetCurrent.fCentreDistance > 17f && !targetCurrent.bThisTreasureGoblin) { fExtraDistance = targetCurrent.fCentreDistance - 17f; if (fExtraDistance > 5f) fExtraDistance = 5f; if (targetCurrent.fCentreDistance - fExtraDistance < 15f) fExtraDistance -= 2; } Vector3 vNewTarget = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, targetCurrent.fCentreDistance - fExtraDistance); return new GilesPower(SNOPower.Witchdoctor_AcidCloud, 30f, vNewTarget, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Fire Bats fast-attack if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Firebats) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 98) { return new GilesPower(SNOPower.Witchdoctor_Firebats, 40f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Poison Darts fast-attack Spams Darts when mana is too low (to cast bears) @12yds or @10yds if Bears avialable if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_PoisonDart) && !playerStatus.bIsIncapacitated) { float fUseThisRange = 35f; if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_ZombieCharger) && playerStatus.dCurrentEnergy >= 150) fUseThisRange = 30f; return new GilesPower(SNOPower.Witchdoctor_PoisonDart, fUseThisRange, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 2, USE_SLOWLY); } // Corpse Spiders fast-attacks Spams Spiders when mana is too low (to cast bears) @12yds or @10yds if Bears avialable if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_CorpseSpider) && !playerStatus.bIsIncapacitated) { float fUseThisRange = 35f; if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_ZombieCharger) && playerStatus.dCurrentEnergy >= 150) fUseThisRange = 30f; return new GilesPower(SNOPower.Witchdoctor_CorpseSpider, fUseThisRange, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Toads fast-attacks Spams Toads when mana is too low (to cast bears) @12yds or @10yds if Bears avialable if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_PlagueOfToads) && !playerStatus.bIsIncapacitated) { float fUseThisRange = 35f; if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_ZombieCharger) && playerStatus.dCurrentEnergy >= 150) fUseThisRange = 30f; return new GilesPower(SNOPower.Witchdoctor_PlagueOfToads, fUseThisRange, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Fire Bomb fast-attacks Spams Bomb when mana is too low (to cast bears) @12yds or @10yds if Bears avialable if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_Firebomb) && !playerStatus.bIsIncapacitated) { float fUseThisRange = 35f; if (hashPowerHotbarAbilities.Contains(SNOPower.Witchdoctor_ZombieCharger) && playerStatus.dCurrentEnergy >= 150) fUseThisRange = 30f; return new GilesPower(SNOPower.Witchdoctor_Firebomb, fUseThisRange, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Default attacks if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated) { return new GilesPower(SNOPower.Weapon_Melee_Instant, 11f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } break; // ********************************************************************************************** // ***** Demon Hunters ***** // ********************************************************************************************** case ActorClass.DemonHunter: // Pick the best destructible power available if (bDestructiblePower) { if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_HungeringArrow)) return new GilesPower(SNOPower.DemonHunter_HungeringArrow, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_EntanglingShot)) return new GilesPower(SNOPower.DemonHunter_EntanglingShot, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_BolaShot)) return new GilesPower(SNOPower.DemonHunter_BolaShot, 13f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Grenades)) return new GilesPower(SNOPower.DemonHunter_Grenades, 12f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_ElementalArrow) && playerStatus.dCurrentEnergy >= 10) return new GilesPower(SNOPower.DemonHunter_ElementalArrow, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_RapidFire) && playerStatus.dCurrentEnergy >= 10) return new GilesPower(SNOPower.DemonHunter_RapidFire, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Chakram) && playerStatus.dCurrentEnergy >= 20) return new GilesPower(SNOPower.DemonHunter_Chakram, 15f, vNullLocation, -1, -1, 0, 0, USE_SLOWLY); return new GilesPower(SNOPower.Weapon_Ranged_Instant, 20f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); } iWaitingReservedAmount = 70; // Shadow Power if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_ShadowPower) && !playerStatus.bIsIncapacitated && playerStatus.dDiscipline >= 14 && (playerStatus.dCurrentHealthPct <= 0.99 || playerStatus.bIsRooted || iElitesWithinRange[RANGE_25] >= 1 || iAnythingWithinRange[RANGE_15] >= 3) && GilesUseTimer(SNOPower.DemonHunter_ShadowPower)) { return new GilesPower(SNOPower.DemonHunter_ShadowPower, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Smoke Screen if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_SmokeScreen) && !GilesHasBuff(SNOPower.DemonHunter_ShadowPower) && playerStatus.dDiscipline >= 14 && (playerStatus.dCurrentHealthPct <= 0.90 || playerStatus.bIsRooted || iElitesWithinRange[RANGE_20] >= 1 || iAnythingWithinRange[RANGE_15] >= 3 || playerStatus.bIsIncapacitated) && GilesUseTimer(SNOPower.DemonHunter_SmokeScreen)) { return new GilesPower(SNOPower.DemonHunter_SmokeScreen, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Preparation if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Preparation) && !playerStatus.bIsIncapacitated && playerStatus.dDiscipline <= 9 && iAnythingWithinRange[RANGE_40] >= 1 && GilesUseTimer(SNOPower.DemonHunter_Preparation) && PowerManager.CanCast(SNOPower.DemonHunter_Preparation)) { return new GilesPower(SNOPower.DemonHunter_Preparation, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Evasive Fire if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_EvasiveFire) && !playerStatus.bIsIncapacitated && (iAnythingWithinRange[RANGE_20] >= 1 || targetCurrent.fRadiusDistance <= 20f) && GilesUseTimer(SNOPower.DemonHunter_EvasiveFire)) { return new GilesPower(SNOPower.DemonHunter_EvasiveFire, 0f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Companion if (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Companion) && iPlayerOwnedDHPets == 0 && playerStatus.dDiscipline >= 10 && GilesUseTimer(SNOPower.DemonHunter_Companion)) { return new GilesPower(SNOPower.DemonHunter_Companion, 0f, vNullLocation, iCurrentWorldID, -1, 2, 1, USE_SLOWLY); } // Sentry Turret if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Sentry) && powerLastSnoPowerUsed != SNOPower.DemonHunter_Sentry && (iElitesWithinRange[RANGE_50] >= 1 || iAnythingWithinRange[RANGE_50] >= 2 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 50f)) && playerStatus.dCurrentEnergy >= 30 && GilesUseTimer(SNOPower.DemonHunter_Sentry)) { return new GilesPower(SNOPower.DemonHunter_Sentry, 0f, vNullLocation, iCurrentWorldID, -1, 0, 0, SIGNATURE_SPAM); } // Marked for Death if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_MarkedForDeath) && !playerStatus.bIsIncapacitated && playerStatus.dDiscipline >= 3 && (iElitesWithinRange[RANGE_40] >= 1 || iAnythingWithinRange[RANGE_40] >= 3 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 40f)) && GilesUseTimer(SNOPower.DemonHunter_MarkedForDeath)) { return new GilesPower(SNOPower.DemonHunter_MarkedForDeath, 40f, vNullLocation, iCurrentWorldID, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Vault if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Vault) && !playerStatus.bIsRooted && !playerStatus.bIsIncapacitated && // Only use vault to retreat if < level 60, or if in inferno difficulty for level 60's (playerStatus.iMyLevel < 60 || iCurrentGameDifficulty == GameDifficulty.Inferno) && (targetCurrent.fRadiusDistance <= 10f || iAnythingWithinRange[RANGE_6] >= 1) && playerStatus.dDiscipline >= 8 && GilesUseTimer(SNOPower.DemonHunter_Vault) && PowerManager.CanCast(SNOPower.DemonHunter_Vault)) { Vector3 vNewTarget = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, -15f); return new GilesPower(SNOPower.DemonHunter_Vault, 20f, vNewTarget, iCurrentWorldID, -1, 1, 2, USE_SLOWLY); } // Rain of Vengeance if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_RainOfVengeance) && !playerStatus.bIsIncapacitated && (iAnythingWithinRange[RANGE_25] >= 7 || iElitesWithinRange[RANGE_25] >= 1) && GilesUseTimer(SNOPower.DemonHunter_RainOfVengeance) && PowerManager.CanCast(SNOPower.DemonHunter_RainOfVengeance)) { return new GilesPower(SNOPower.DemonHunter_RainOfVengeance, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Cluster Arrow if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_ClusterArrow) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 50 && (iElitesWithinRange[RANGE_50] >= 1 || iAnythingWithinRange[RANGE_50] >= 5 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 69f)) && GilesUseTimer(SNOPower.DemonHunter_ClusterArrow)) { return new GilesPower(SNOPower.DemonHunter_ClusterArrow, 69f, vNullLocation, iCurrentWorldID, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } // Multi Shot if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Multishot) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 30 && (iElitesWithinRange[RANGE_40] >= 1 || iAnythingWithinRange[RANGE_40] >= 2 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 50f))) { return new GilesPower(SNOPower.DemonHunter_Multishot, 50f, targetCurrent.vThisPosition, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Fan of Knives if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_FanOfKnives) && !playerStatus.bIsIncapacitated && playerStatus.dCurrentEnergy >= 20 && (iAnythingWithinRange[RANGE_15] >= 4 || iElitesWithinRange[RANGE_15] >= 1) && GilesUseTimer(SNOPower.DemonHunter_FanOfKnives)) { return new GilesPower(SNOPower.DemonHunter_FanOfKnives, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Strafe spam - similar to barbarian whirlwind routine if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Strafe) && !playerStatus.bIsIncapacitated && !playerStatus.bIsRooted && // Only if within 25 foot of main target targetCurrent.fRadiusDistance <= 15f && // Check for energy reservation amounts ((playerStatus.dCurrentEnergy >= 15 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) { bool bGenerateNewZigZag = (DateTime.Now.Subtract(lastChangedZigZag).TotalMilliseconds >= 1500 || (vPositionLastZigZagCheck != vNullLocation && playerStatus.vCurrentPosition == vPositionLastZigZagCheck && DateTime.Now.Subtract(lastChangedZigZag).TotalMilliseconds >= 200) || Vector3.Distance(playerStatus.vCurrentPosition, vSideToSideTarget) <= 4f || targetCurrent.iThisACDGUID != iACDGUIDLastWhirlwind); vPositionLastZigZagCheck = playerStatus.vCurrentPosition; if (bGenerateNewZigZag) { float fExtraDistance = targetCurrent.fCentreDistance <= 10f ? 10f : 5f; vSideToSideTarget = FindZigZagTargetLocation(targetCurrent.vThisPosition, targetCurrent.fCentreDistance + fExtraDistance); // Resetting this to ensure the "no-spam" is reset since we changed our target location powerLastSnoPowerUsed = SNOPower.None; iACDGUIDLastWhirlwind = targetCurrent.iThisACDGUID; lastChangedZigZag = DateTime.Now; } return new GilesPower(SNOPower.DemonHunter_Strafe, 25f, vSideToSideTarget, iCurrentWorldID, -1, 0, 0, USE_SLOWLY); } // Spike Trap if (!bOOCBuff && !playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_SpikeTrap) && powerLastSnoPowerUsed != SNOPower.DemonHunter_SpikeTrap && (iElitesWithinRange[RANGE_30] >= 1 || iAnythingWithinRange[RANGE_25] > 4 || ((targetCurrent.bThisEliteRareUnique || targetCurrent.bThisTreasureGoblin || targetCurrent.bThisBoss) && targetCurrent.fRadiusDistance <= 35f)) && playerStatus.dCurrentEnergy >= 30 && GilesUseTimer(SNOPower.DemonHunter_SpikeTrap)) { // For distant monsters, try to target a little bit in-front of them (as they run towards us), if it's not a treasure goblin float fExtraDistance = 0f; if (targetCurrent.fCentreDistance > 17f && !targetCurrent.bThisTreasureGoblin) { fExtraDistance = targetCurrent.fCentreDistance - 17f; if (fExtraDistance > 5f) fExtraDistance = 5f; if (targetCurrent.fCentreDistance - fExtraDistance < 15f) fExtraDistance -= 2; } Vector3 vNewTarget = MathEx.CalculatePointFrom(targetCurrent.vThisPosition, playerStatus.vCurrentPosition, targetCurrent.fCentreDistance - fExtraDistance); return new GilesPower(SNOPower.DemonHunter_SpikeTrap, 40f, vNewTarget, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Caltrops if (!bOOCBuff && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Caltrops) && !playerStatus.bIsIncapacitated && playerStatus.dDiscipline >= 6 && (iAnythingWithinRange[RANGE_30] >= 2 || iElitesWithinRange[RANGE_40] >= 1) && GilesUseTimer(SNOPower.DemonHunter_Caltrops)) { return new GilesPower(SNOPower.DemonHunter_Caltrops, 0f, vNullLocation, iCurrentWorldID, -1, 1, 1, USE_SLOWLY); } // Elemental Arrow if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_ElementalArrow) && !playerStatus.bIsIncapacitated && ((playerStatus.dCurrentEnergy >= 10 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) { // Players with grenades *AND* elemental arrow should spam grenades at close-range instead if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Grenades) && targetCurrent.fRadiusDistance <= 18f) return new GilesPower(SNOPower.DemonHunter_Grenades, 18f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); // Now return elemental arrow, if not sending grenades instead return new GilesPower(SNOPower.DemonHunter_ElementalArrow, 69f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Chakram if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Chakram) && !playerStatus.bIsIncapacitated && // If we have elemental arrow or rapid fire, then use chakram as a 110 second buff, instead ((!hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_ClusterArrow)) || DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.DemonHunter_Chakram]).TotalMilliseconds >= 110000) && ((playerStatus.dCurrentEnergy >= 10 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) { return new GilesPower(SNOPower.DemonHunter_Chakram, 69f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Rapid Fire if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_RapidFire) && !playerStatus.bIsIncapacitated && ((playerStatus.dCurrentEnergy >= 20 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) { // Players with grenades *AND* rapid fire should spam grenades at close-range instead if (hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Grenades) && targetCurrent.fRadiusDistance <= 18f) return new GilesPower(SNOPower.DemonHunter_Grenades, 18f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); // Now return rapid fire, if not sending grenades instead return new GilesPower(SNOPower.DemonHunter_RapidFire, 69f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, SIGNATURE_SPAM); } // Impale if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Impale) && !playerStatus.bIsIncapacitated && ((playerStatus.dCurrentEnergy >= 25 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount) && targetCurrent.fRadiusDistance <= 12f) { return new GilesPower(SNOPower.DemonHunter_Impale, 12f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Hungering Arrow if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_HungeringArrow) && !playerStatus.bIsIncapacitated) { return new GilesPower(SNOPower.DemonHunter_HungeringArrow, 50f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); } // Entangling shot if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_EntanglingShot) && !playerStatus.bIsIncapacitated) { return new GilesPower(SNOPower.DemonHunter_EntanglingShot, 69f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 0, USE_SLOWLY); } // Bola Shot if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_BolaShot) && !playerStatus.bIsIncapacitated) { return new GilesPower(SNOPower.DemonHunter_BolaShot, 69f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Grenades if (!bOOCBuff && !bCurrentlyAvoiding && hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Grenades) && !playerStatus.bIsIncapacitated) { return new GilesPower(SNOPower.DemonHunter_Grenades, 40f, vNullLocation, -1, targetCurrent.iThisACDGUID, 0, 1, USE_SLOWLY); } // Default attacks if (!bOOCBuff && !bCurrentlyAvoiding && !playerStatus.bIsIncapacitated) { return new GilesPower(SNOPower.Weapon_Ranged_Projectile, 40f, vNullLocation, -1, targetCurrent.iThisACDGUID, 1, 1, USE_SLOWLY); } break; } return new GilesPower(SNOPower.None, 0, vNullLocation, -1, -1, 0, 0, false); } // ********************************************************************************************** // ***** Special Zig-Zag movement for whirlwind/tempest ***** // ********************************************************************************************** public static Vector3 FindZigZagTargetLocation(Vector3 vTargetLocation, float fDistanceOutreach, bool bRandomizeDistance = false, bool bNinetyDegree = false, bool bRandomizeStart = false) { Vector3 vThisZigZag = vNullLocation; Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber)); bool bCanRayCast; float iFakeStart = 0; //K: bRandomizeStart is for boss and elite, they usually jump around, make obstacles, let you Incapacitated. // you usually have to move back and forth to hit them if (bRandomizeStart) iFakeStart = rndNum.Next(36) * 5; if (bRandomizeDistance) fDistanceOutreach += rndNum.Next(18); float fDirectionToTarget = FindDirectionDegree(playerStatus.vCurrentPosition, vTargetLocation); float fPointToTarget; float fHighestWeight = float.NegativeInfinity; Vector3 vBestLocation = vNullLocation; bool bFoundSafeSpotsFirstLoop = false; float fAdditionalRange = 0f; //K: Direction is more important than distance for (int iMultiplier = 1; iMultiplier <= 2; iMultiplier++) { if (iMultiplier == 2) { if (bFoundSafeSpotsFirstLoop) break; fAdditionalRange = 210f; if (bRandomizeStart) iFakeStart = 60f+(rndNum.Next(12) * 5); else iFakeStart = (rndNum.Next(13) * 5); } float fRunDistance = fDistanceOutreach; for (float iDegreeChange = iFakeStart; iDegreeChange <= 30f+fAdditionalRange; iDegreeChange += 5) { float iPosition = iDegreeChange; //point to target is better, otherwise we have to avoid obstacle first if (iPosition > 210f) iPosition = 120f-iPosition; else if (iPosition > 180f) iPosition = iPosition - 90f; else if (iPosition > 105f) iPosition = 90f - iPosition; else if (iPosition > 30f) iPosition -= 15f; else iPosition = 15f - iPosition; fPointToTarget = iPosition; iPosition += fDirectionToTarget; if (iPosition < 0) iPosition = 360 + iPosition; if (iPosition > 360) iPosition = iPosition - 360; vThisZigZag = MathEx.GetPointAt(playerStatus.vCurrentPosition, fRunDistance, MathEx.ToRadians(iPosition)); if (fPointToTarget <= 30f || fPointToTarget >= 330f) { vThisZigZag.Z = vTargetLocation.Z; } else if (fPointToTarget <= 60f || fPointToTarget >= 300f) { //K: we are trying to find position that we can circle around the target // but we shouldn't run too far away from target vThisZigZag.Z = (vTargetLocation.Z + playerStatus.vCurrentPosition.Z) / 2; fRunDistance = fDistanceOutreach - 5f; } else { //K: don't move too far if we are not point to target, we just try to move // this can help a lot when we are near stairs fRunDistance = 8f; } //K: The idea here is still we care more about direction than others things, if we are near plain area, no need to care about obstacles if (Math.Abs(playerStatus.vCurrentPosition.Z - vThisZigZag.Z) < 1f) bCanRayCast = GilesCanRayCast(playerStatus.vCurrentPosition, vThisZigZag, NavCellFlags.AllowWalk); //K: carefully handle stairs, we cannot jump up and down stairs, so we need judge this on the ground else bCanRayCast = ZetaDia.Physics.Raycast(playerStatus.vCurrentPosition, vThisZigZag, NavCellFlags.AllowWalk); // Give weight to each zigzag point, so we can find the best one to aim for if (bCanRayCast) { bool bAnyAvoidance = false; float fThisWeight = 1000f; if (iMultiplier == 2) fThisWeight -= 80f; // Remove weight for each avoidance *IN* that location foreach (GilesObstacle tempobstacle in hashAvoidanceObstacleCache.Where(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, vThisZigZag))) { bAnyAvoidance = true; fThisWeight -= (float)tempobstacle.dThisWeight; } // Give extra weight to areas we've been inside before bool bExtraSafetyWeight = hashSkipAheadAreaCache.Any(cp => cp.vThisLocation.Distance(vThisZigZag) <= cp.fThisRadius); if (bExtraSafetyWeight) fThisWeight += 100f; // Use this one if it's more weight, or we haven't even found one yet, or if same weight as another with a random chance if (fThisWeight > fHighestWeight) { fHighestWeight = fThisWeight; vBestLocation = vThisZigZag; if (!bAnyAvoidance) bFoundSafeSpotsFirstLoop = true; } } // Can we raycast to the point at minimum? } // Loop through degrees } // Loop through multiplier return vBestLocation; } // ********************************************************************************************** // ***** Quick Easy Raycast Function for quick changes ***** // ********************************************************************************************** public static bool GilesCanRayCast(Vector3 vStartLocation, Vector3 vDestination, NavCellFlags NavType = NavCellFlags.AllowWalk) { if (ZetaDia.Physics.Raycast(new Vector3(vStartLocation.X, vStartLocation.Y, vStartLocation.Z + 3f), new Vector3(vDestination.X, vDestination.Y, vDestination.Z + 3f), NavType)) return true; return false; } // ********************************************************************************************** // ***** Calculate direction of A to B ***** // ********************************************************************************************** // Quickly calculate the direction a vector is from ourselves, and return it as a float public static float FindDirectionDegree(Vector3 vStartLocation, Vector3 vTargetLocation) { return (float)RadianToDegree(NormalizeRadian((float)Math.Atan2(vTargetLocation.Y - vStartLocation.Y, vTargetLocation.X - vStartLocation.X))); } // ********************************************************************************************** // ***** Find A Safe Movement Location ***** // ********************************************************************************************** private static bool bAvoidDirectionBlacklisting = false; private static float fAvoidBlacklistDirection = 0f; public static Vector3 FindSafeZone(bool bFindAntiStuckSpot, int iAntiStuckAttempts, Vector3 vNearbyPoint, bool bKitingSpot = false) { if (!bFindAntiStuckSpot) { // Already searched & found a safe spot in last 800 milliseconds, stick to it if (!bTravellingAvoidance && DateTime.Now.Subtract(lastFoundSafeSpot).TotalMilliseconds <= 800 && vlastSafeSpot != vNullLocation) { return vlastSafeSpot; } bHasEmergencyTeleportUp = ( // Leap is available (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Leap) && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Barbarian_Leap]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Barbarian_Leap]) || // Whirlwind is available (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Whirlwind) && ((playerStatus.dCurrentEnergy >= 10 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) || // Tempest rush is available (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Monk_TempestRush) && ((playerStatus.dCurrentEnergy >= 20 && !playerStatus.bWaitingForReserveEnergy) || playerStatus.dCurrentEnergy >= iWaitingReservedAmount)) || // Teleport is available (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Teleport) && playerStatus.dCurrentEnergy >= 15 && PowerManager.CanCast(SNOPower.Wizard_Teleport)) || // Archon Teleport is available (!playerStatus.bIsIncapacitated && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon_Teleport) && PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport)) ); // Wizards can look for bee stings in range and try a wave of force to dispel them if (!bKitingSpot && iMyCachedActorClass == ActorClass.Wizard && hashPowerHotbarAbilities.Contains(SNOPower.Wizard_WaveOfForce) && playerStatus.dCurrentEnergy >= 25 && DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.Wizard_WaveOfForce]).TotalMilliseconds >= dictAbilityRepeatDelay[SNOPower.Wizard_WaveOfForce] && !playerStatus.bIsIncapacitated && hashAvoidanceObstacleCache.Count(u => u.iThisSNOID == 5212 && u.vThisLocation.Distance(playerStatus.vCurrentPosition) <= 15f) >= 2 && (settings.bEnableCriticalMass || PowerManager.CanCast(SNOPower.Wizard_WaveOfForce))) { ZetaDia.Me.UsePower(SNOPower.Wizard_WaveOfForce, vNullLocation, iCurrentWorldID, -1); } } // Only looking for an anti-stuck location? // Now find a randomized safe point we can actually move to // We randomize the order so we don't spam walk by accident back and forth Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber)); int iFakeStart = (rndNum.Next(36)) * 10; float fHighestWeight = 0f; Vector3 vBestLocation = vNullLocation; // Start off checking every 12 degrees (which is 30 loops for a full circle) const int iMaxRadiusChecks = 30; const int iRadiusMultiplier = 12; for (int iStep = 1; iStep <= 8; iStep++) { // Distance of 10 for each step loop at first int iDistanceOut = 10; if (!bKitingSpot) { switch (iStep) { case 1: iDistanceOut = 10; break; case 2: iDistanceOut = 18; break; case 3: iDistanceOut = 26; break; case 4: iDistanceOut = 34; break; case 5: iDistanceOut = 42; break; case 6: iDistanceOut = 50; break; case 7: iDistanceOut = 58; break; case 8: iDistanceOut = 66; break; } } else { switch (iStep) { case 8: iDistanceOut = 10; break; case 7: iDistanceOut = 18; break; case 6: iDistanceOut = 26; break; case 5: iDistanceOut = 34; break; case 4: iDistanceOut = 42; break; case 3: iDistanceOut = 50; break; case 2: iDistanceOut = 58; break; case 1: iDistanceOut = 66; break; } } int iRandomUse = 3 + ((iStep - 1) * 4); // Try to return "early", or as soon as possible, beyond step 4, except when unstucking, when the max steps is based on the unstuck attempt if (fHighestWeight > 0 && ((!bFindAntiStuckSpot && iStep > 5) || (bFindAntiStuckSpot && iStep > iAntiStuckAttempts)) ) { lastFoundSafeSpot = DateTime.Now; vlastSafeSpot = vBestLocation; break; } // Loop through all possible radii for (int iThisRadius = 0; iThisRadius < iMaxRadiusChecks; iThisRadius++) { int iPosition = iFakeStart + (iThisRadius * iRadiusMultiplier); if (iPosition >= 360) iPosition -= 360; float fBonusAmount = 0f; // See if we've blacklisted a 70 degree arc around this direction if (bAvoidDirectionBlacklisting) { if (Math.Abs(fAvoidBlacklistDirection - iPosition) <= 35 || Math.Abs(fAvoidBlacklistDirection - iPosition) >= 325) continue; if (Math.Abs(fAvoidBlacklistDirection - iPosition) >= 145 || Math.Abs(fAvoidBlacklistDirection - iPosition) <= 215) fBonusAmount = 200f; } Vector3 vTestPoint = MathEx.GetPointAt(playerStatus.vCurrentPosition, iDistanceOut, MathEx.ToRadians(iPosition)); // First check no avoidance obstacles in this spot if (!hashAvoidanceObstacleCache.Any(u => u.vThisLocation.Distance(vTestPoint) <= dictAvoidanceRadius[u.iThisSNOID])) { bool bAvoidBlackspot = hashAvoidanceBlackspot.Any(cp => Vector3.Distance(cp.vThisLocation, vTestPoint) <= cp.fThisRadius); bool bCanRaycast = false; if (!bAvoidBlackspot) bCanRaycast = GilesCanRayCast(playerStatus.vCurrentPosition, vTestPoint, NavCellFlags.AllowWalk); // Now see if the client can navigate there, and we haven't temporarily blacklisted this spot if (!bAvoidBlackspot && bCanRaycast) { // Now calculate a weight to pick the "best" avoidance safety spot at the moment float fThisWeight = 1000f + fBonusAmount; if (!bFindAntiStuckSpot) { fThisWeight -= ((iStep - 1) * 150); } // is it near a point we'd prefer to be close to? if (vNearbyPoint != vNullLocation) { float fDistanceToNearby = Vector3.Distance(vTestPoint, vNearbyPoint); if (fDistanceToNearby <= 25f) { if (!bKitingSpot) fThisWeight += (160 * (1 - (fDistanceToNearby / 25))); else fThisWeight -= (300 * (1 - (fDistanceToNearby / 25))); } } // See if we should check for avoidance spots and monsters in the pathing if (!bFindAntiStuckSpot) { Vector3 point = vTestPoint; int iMonsterCount = hashMonsterObstacleCache.Count(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point)); fThisWeight -= (iMonsterCount * 30); foreach (GilesObstacle tempobstacle in hashAvoidanceObstacleCache.Where(cp => GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, playerStatus.vCurrentPosition, point))) { fThisWeight -= (float)(tempobstacle.dThisWeight * 0.6); } } if (bKitingSpot) { // Kiting spots don't like to end up near other monsters foreach (GilesObstacle tempobstacle in hashMonsterObstacleCache.Where(cp => Vector3.Distance(cp.vThisLocation, vTestPoint) <= (cp.fThisRadius * 1.2))) { fThisWeight -= 30; } } // Give extra weight to areas we've been inside before bool bExtraSafetyWeight = hashSkipAheadAreaCache.Any(cp => cp.vThisLocation.Distance(vTestPoint) <= cp.fThisRadius); if (bExtraSafetyWeight) { if (bKitingSpot) { fThisWeight += 350f; } else if (bFindAntiStuckSpot) { fThisWeight += 300f; } else { fThisWeight += 100f; } } if (fThisWeight <= 1) fThisWeight = 1; // Use this one if it's more weight, or we haven't even found one yet, or if same weight as another with a random chance if (fThisWeight > fHighestWeight || fHighestWeight == 0f || (fThisWeight == fHighestWeight && rndNum.Next(iRandomUse) == 1)) { fHighestWeight = fThisWeight; vBestLocation = vTestPoint; // Found a very good spot so just use this one! //if (iAOECount == 0 && fThisWeight > 400) // break; } } } } // Loop through the circle } // Loop through distance-range steps if (fHighestWeight > 0) { lastFoundSafeSpot = DateTime.Now; vlastSafeSpot = vBestLocation; } return vBestLocation; } // ********************************************************************************************** // ***** Check if an obstacle is blocking our path ***** // ********************************************************************************************** public static bool GilesIntersectsPath(Vector3 obstacle, float radius, Vector3 start, Vector3 destination) { float fDirectionToTarget = NormalizeRadian((float)Math.Atan2(destination.Y - start.Y, destination.X - start.X)); float fDirectionToObstacle = NormalizeRadian((float)Math.Atan2(obstacle.Y - start.Y, obstacle.X - start.X)); if (Math.Abs(RadianToDegree(fDirectionToTarget) - RadianToDegree(fDirectionToObstacle)) > 30) { return false; } if (radius <= 1f) radius = 1f; if (radius >= 15f) radius = 15f; Ray ray = new Ray(start, Vector3.NormalizedDirection(start, destination)); Sphere sphere = new Sphere(obstacle, radius); float? nullable = ray.Intersects(sphere); return (nullable.HasValue && (nullable.Value < start.Distance(destination))); } public static float NormalizeRadian(float radian) { if (radian < 0) { double mod = -radian; mod %= Math.PI * 2d; mod = -mod + Math.PI * 2d; return (float)mod; } return (float)(radian % (Math.PI * 2d)); } public static double RadianToDegree(double angle) { return angle * (180.0 / Math.PI); } // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Item handling Stuff ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Randomize the timer between stashing/salvaging etc. ***** // ********************************************************************************************** private static void RandomizeTheTimer() { Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber)); int rnd = rndNum.Next(7); iItemDelayLoopLimit = 4 + rnd; } // ********************************************************************************************** // ***** Pickup Validation - Determines what should or should not be picked up ***** // ********************************************************************************************** private static bool GilesPickupItemValidation(string tempname, int templevel, ItemQuality tempquality, int tempbalanceid, ItemType thisdbitemtype, FollowerType thisfollowertype, int iDynamicID = 0) { // If it's legendary, we always want it *IF* it's level is right if (tempquality >= ItemQuality.Legendary) { if (settings.iFilterLegendary > 0 && (templevel >= settings.iFilterLegendary || settings.iFilterLegendary == 1)) return true; return false; } // Calculate giles item types and base types etc. GilesItemType thisGilesItemType = DetermineItemType(tempname, thisdbitemtype, thisfollowertype); GilesBaseItemType thisGilesBaseType = DetermineBaseType(thisGilesItemType); // Error logging for DemonBuddy item mis-reading ItemType gilesDBItemType = GilesToDBItemType(thisGilesItemType); if (gilesDBItemType != thisdbitemtype) { Log("GSError: Item type mis-match detected: Item Internal=" + tempname + ". DemonBuddy ItemType thinks item type is=" + thisdbitemtype.ToString() + ". Giles thinks item type is=" + gilesDBItemType.ToString() + ". [pickup]", true); } switch (thisGilesBaseType) { case GilesBaseItemType.WeaponTwoHand: case GilesBaseItemType.WeaponOneHand: case GilesBaseItemType.WeaponRange: // Not enough DPS, so analyse for possibility to blacklist if (tempquality < ItemQuality.Magic1) { // White item, blacklist return false; } if (tempquality >= ItemQuality.Magic1 && tempquality < ItemQuality.Rare4) { if (settings.iFilterBlueWeapons == 0 || (settings.iFilterBlueWeapons != 0 && templevel < settings.iFilterBlueWeapons)) { // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want return false; } } else { if (settings.iFilterYellowWeapons == 0 || (settings.iFilterYellowWeapons != 0 && templevel < settings.iFilterYellowWeapons)) { // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want return false; } } break; case GilesBaseItemType.Armor: case GilesBaseItemType.Offhand: if (tempquality < ItemQuality.Magic1) { // White item, blacklist return false; } if (tempquality >= ItemQuality.Magic1 && tempquality < ItemQuality.Rare4) { if (settings.iFilterBlueArmor == 0 || (settings.iFilterBlueArmor != 0 && templevel < settings.iFilterBlueArmor)) { // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want return false; } } else { if (settings.iFilterYellowArmor == 0 || (settings.iFilterYellowArmor != 0 && templevel < settings.iFilterYellowArmor)) { // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want return false; } } break; case GilesBaseItemType.Jewelry: if (tempquality < ItemQuality.Magic1) { // White item, blacklist return false; } if (tempquality >= ItemQuality.Magic1 && tempquality < ItemQuality.Rare4) { if (settings.iFilterBlueJewelry == 0 || (settings.iFilterBlueJewelry != 0 && templevel < settings.iFilterBlueJewelry)) { // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want return false; } } else { if (settings.iFilterYellowJewelry == 0 || (settings.iFilterYellowJewelry != 0 && templevel < settings.iFilterYellowJewelry)) { // Between magic and rare, and either we want no blues, or this level is higher than the blue level we want return false; } } break; case GilesBaseItemType.FollowerItem: if (templevel < 60 || !settings.bPickupFollower || tempquality < ItemQuality.Rare4) { if (!_hashsetItemFollowersIgnored.Contains(iDynamicID)) { _hashsetItemFollowersIgnored.Add(iDynamicID); iTotalFollowerItemsIgnored++; } return false; } break; case GilesBaseItemType.Gem: if (templevel < settings.iFilterGems || (thisGilesItemType == GilesItemType.Ruby && !settings.bGemsRuby) || (thisGilesItemType == GilesItemType.Emerald && !settings.bGemsEmerald) || (thisGilesItemType == GilesItemType.Amethyst && !settings.bGemsAmethyst) || (thisGilesItemType == GilesItemType.Topaz && !settings.bGemsTopaz)) { return false; } break; case GilesBaseItemType.Misc: // Note; Infernal keys are misc, so should be picked up here - we aren't filtering them out, so should default to true at the end of this function if (thisGilesItemType == GilesItemType.CraftingMaterial && templevel < settings.iFilterMisc) { return false; } if (thisGilesItemType == GilesItemType.CraftTome && (templevel < settings.iFilterMisc || !settings.bPickupCraftTomes)) { return false; } if (thisGilesItemType == GilesItemType.CraftingPlan && !settings.bPickupPlans) { return false; } // Potion filtering if (thisGilesItemType == GilesItemType.HealthPotion) { if (settings.iFilterPotions == 1 || templevel < settings.iFilterPotionLevel) { return false; } if (settings.iFilterPotions == 2) { // Map out all the items already in the backpack int iTotalPotions = (from tempitem in ZetaDia.Me.Inventory.Backpack where tempitem.BaseAddress != IntPtr.Zero where tempitem.GameBalanceId == tempbalanceid select tempitem.ItemStackQuantity).Sum(); if (iTotalPotions > 98) { return false; } } } break; case GilesBaseItemType.HealthGlobe: return false; case GilesBaseItemType.Unknown: return false; default: return false; } // Switch giles base item type // Didn't cancel it, so default to true! return true; } // ********************************************************************************************** // ***** Sell Validation - Determines what should or should not be sold to vendor ***** // ********************************************************************************************** private static bool GilesSellValidation(string thisinternalname, int thislevel, ItemQuality thisquality, ItemType thisdbitemtype, FollowerType thisfollowertype) { // Check this isn't something we want to salvage if (settings.bSalvageJunk) { if (GilesSalvageValidation(thisinternalname, thislevel, thisquality, thisdbitemtype, thisfollowertype)) return false; } // Make sure it's not legendary // if (thisquality >= ItemQuality.Legendary) // return false; GilesItemType thisGilesItemType = DetermineItemType(thisinternalname, thisdbitemtype, thisfollowertype); GilesBaseItemType thisGilesBaseType = DetermineBaseType(thisGilesItemType); switch (thisGilesBaseType) { case GilesBaseItemType.WeaponRange: case GilesBaseItemType.WeaponOneHand: case GilesBaseItemType.WeaponTwoHand: case GilesBaseItemType.Armor: case GilesBaseItemType.Offhand: case GilesBaseItemType.Jewelry: case GilesBaseItemType.FollowerItem: return true; case GilesBaseItemType.Gem: case GilesBaseItemType.Misc: case GilesBaseItemType.Unknown: return false; } // Switch giles base item type return false; } // ********************************************************************************************** // ***** Salvage Validation - Determines what should or should not be salvaged ***** // ********************************************************************************************** private static bool GilesSalvageValidation(string thisinternalname, int thislevel, ItemQuality thisquality, ItemType thisdbitemtype, FollowerType thisfollowertype) { if (!settings.bSalvageJunk) return false; // Make sure it's not legendary // if (thisquality >= ItemQuality.Legendary) // return false; GilesItemType thisGilesItemType = DetermineItemType(thisinternalname, thisdbitemtype, thisfollowertype); GilesBaseItemType thisGilesBaseType = DetermineBaseType(thisGilesItemType); switch (thisGilesBaseType) { case GilesBaseItemType.WeaponRange: case GilesBaseItemType.WeaponOneHand: case GilesBaseItemType.WeaponTwoHand: case GilesBaseItemType.Armor: case GilesBaseItemType.Offhand: if (thislevel >= 61 && thisquality >= ItemQuality.Magic1) { return true; } return false; case GilesBaseItemType.Jewelry: if (thislevel >= 59 && thisquality >= ItemQuality.Magic1) { return true; } return false; case GilesBaseItemType.FollowerItem: if (thislevel >= 60 && thisquality >= ItemQuality.Magic1) { return true; } return false; case GilesBaseItemType.Gem: case GilesBaseItemType.Misc: case GilesBaseItemType.Unknown: return false; } // Switch giles base item type return false; } // ********************************************************************************************** // ***** DetermineItemType - Calculates what kind of item it is from D3 internalnames ***** // ********************************************************************************************** private static GilesItemType DetermineItemType(string sThisInternalName, ItemType DBItemType, FollowerType dbFollowerType = FollowerType.None) { sThisInternalName = sThisInternalName.ToLower(); if (sThisInternalName.StartsWith("axe_")) return GilesItemType.Axe; if (sThisInternalName.StartsWith("ceremonialdagger_")) return GilesItemType.CeremonialKnife; if (sThisInternalName.StartsWith("handxbow_")) return GilesItemType.HandCrossbow; if (sThisInternalName.StartsWith("dagger_")) return GilesItemType.Dagger; if (sThisInternalName.StartsWith("fistweapon_")) return GilesItemType.FistWeapon; if (sThisInternalName.StartsWith("mace_")) return GilesItemType.Mace; if (sThisInternalName.StartsWith("mightyweapon_1h_")) return GilesItemType.MightyWeapon; if (sThisInternalName.StartsWith("spear_")) return GilesItemType.Spear; if (sThisInternalName.StartsWith("sword_")) return GilesItemType.Sword; if (sThisInternalName.StartsWith("wand_")) return GilesItemType.Wand; if (sThisInternalName.StartsWith("twohandedaxe_")) return GilesItemType.TwoHandAxe; if (sThisInternalName.StartsWith("bow_")) return GilesItemType.TwoHandBow; if (sThisInternalName.StartsWith("combatstaff_")) return GilesItemType.TwoHandDaibo; if (sThisInternalName.StartsWith("xbow_")) return GilesItemType.TwoHandCrossbow; if (sThisInternalName.StartsWith("twohandedmace_")) return GilesItemType.TwoHandMace; if (sThisInternalName.StartsWith("mightyweapon_2h_")) return GilesItemType.TwoHandMighty; if (sThisInternalName.StartsWith("polearm_")) return GilesItemType.TwoHandPolearm; if (sThisInternalName.StartsWith("staff_")) return GilesItemType.TwoHandStaff; if (sThisInternalName.StartsWith("twohandedsword_")) return GilesItemType.TwoHandSword; if (sThisInternalName.StartsWith("staffofcow")) return GilesItemType.StaffOfHerding; if (sThisInternalName.StartsWith("mojo_")) return GilesItemType.Mojo; if (sThisInternalName.StartsWith("orb_")) return GilesItemType.Source; if (sThisInternalName.StartsWith("quiver_")) return GilesItemType.Quiver; if (sThisInternalName.StartsWith("shield_")) return GilesItemType.Shield; if (sThisInternalName.StartsWith("amulet_")) return GilesItemType.Amulet; if (sThisInternalName.StartsWith("ring_")) return GilesItemType.Ring; if (sThisInternalName.StartsWith("boots_")) return GilesItemType.Boots; if (sThisInternalName.StartsWith("bracers_")) return GilesItemType.Bracers; if (sThisInternalName.StartsWith("cloak_")) return GilesItemType.Cloak; if (sThisInternalName.StartsWith("gloves_")) return GilesItemType.Gloves; if (sThisInternalName.StartsWith("pants_")) return GilesItemType.Pants; if (sThisInternalName.StartsWith("barbbelt_")) return GilesItemType.MightyBelt; if (sThisInternalName.StartsWith("shoulderpads_")) return GilesItemType.Shoulders; if (sThisInternalName.StartsWith("spiritstone_")) return GilesItemType.SpiritStone; if (sThisInternalName.StartsWith("voodoomask_")) return GilesItemType.VoodooMask; if (sThisInternalName.StartsWith("wizardhat_")) return GilesItemType.WizardHat; if (sThisInternalName.StartsWith("lore_book_")) return GilesItemType.CraftTome; if (sThisInternalName.StartsWith("page_of_")) return GilesItemType.CraftTome; if (sThisInternalName.StartsWith("blacksmithstome")) return GilesItemType.CraftTome; if (sThisInternalName.StartsWith("ruby_")) return GilesItemType.Ruby; if (sThisInternalName.StartsWith("emerald_")) return GilesItemType.Emerald; if (sThisInternalName.StartsWith("topaz_")) return GilesItemType.Topaz; if (sThisInternalName.StartsWith("amethyst")) return GilesItemType.Amethyst; if (sThisInternalName.StartsWith("healthpotion_")) return GilesItemType.HealthPotion; if (sThisInternalName.StartsWith("followeritem_enchantress_")) return GilesItemType.FollowerEnchantress; if (sThisInternalName.StartsWith("followeritem_scoundrel_")) return GilesItemType.FollowerScoundrel; if (sThisInternalName.StartsWith("followeritem_templar_")) return GilesItemType.FollowerTemplar; if (sThisInternalName.StartsWith("craftingplan_")) return GilesItemType.CraftingPlan; if (sThisInternalName.StartsWith("dye_")) return GilesItemType.Dye; if (sThisInternalName.StartsWith("a1_")) return GilesItemType.SpecialItem; if (sThisInternalName.StartsWith("healthglobe")) return GilesItemType.HealthGlobe; // Follower item types if (sThisInternalName.StartsWith("jewelbox_") || DBItemType == ItemType.FollowerSpecial) { if (dbFollowerType == FollowerType.Scoundrel) return GilesItemType.FollowerScoundrel; if (dbFollowerType == FollowerType.Templar) return GilesItemType.FollowerTemplar; if (dbFollowerType == FollowerType.Enchantress) return GilesItemType.FollowerEnchantress; } // Fall back on some partial DB item type checking if (sThisInternalName.StartsWith("crafting_")) { if (DBItemType == ItemType.CraftingPage) return GilesItemType.CraftTome; return GilesItemType.CraftingMaterial; } if (sThisInternalName.StartsWith("chestarmor_")) { if (DBItemType == ItemType.Cloak) return GilesItemType.Cloak; return GilesItemType.Chest; } if (sThisInternalName.StartsWith("helm_")) { if (DBItemType == ItemType.SpiritStone) return GilesItemType.SpiritStone; if (DBItemType == ItemType.VoodooMask) return GilesItemType.VoodooMask; if (DBItemType == ItemType.WizardHat) return GilesItemType.WizardHat; return GilesItemType.Helm; } if (sThisInternalName.StartsWith("helmcloth_")) { if (DBItemType == ItemType.SpiritStone) return GilesItemType.SpiritStone; if (DBItemType == ItemType.VoodooMask) return GilesItemType.VoodooMask; if (DBItemType == ItemType.WizardHat) return GilesItemType.WizardHat; return GilesItemType.Helm; } if (sThisInternalName.StartsWith("belt_")) { if (DBItemType == ItemType.MightyBelt) return GilesItemType.MightyBelt; return GilesItemType.Belt; } if (sThisInternalName.StartsWith("demonkey_") || sThisInternalName.StartsWith("demontrebuchetkey")) { return GilesItemType.InfernalKey; } // ORGANS QUICK HACK IN if (sThisInternalName.StartsWith("quest_")) { return GilesItemType.InfernalKey; } return GilesItemType.Unknown; } // ********************************************************************************************** // ***** DetermineBaseType - Calculates a more generic, "basic" type of item ***** // ********************************************************************************************** private static GilesBaseItemType DetermineBaseType(GilesItemType thisGilesItemType) { GilesBaseItemType thisGilesBaseType = GilesBaseItemType.Unknown; if (thisGilesItemType == GilesItemType.Axe || thisGilesItemType == GilesItemType.CeremonialKnife || thisGilesItemType == GilesItemType.Dagger || thisGilesItemType == GilesItemType.FistWeapon || thisGilesItemType == GilesItemType.Mace || thisGilesItemType == GilesItemType.MightyWeapon || thisGilesItemType == GilesItemType.Spear || thisGilesItemType == GilesItemType.Sword || thisGilesItemType == GilesItemType.Wand) { thisGilesBaseType = GilesBaseItemType.WeaponOneHand; } else if (thisGilesItemType == GilesItemType.TwoHandDaibo || thisGilesItemType == GilesItemType.TwoHandMace || thisGilesItemType == GilesItemType.TwoHandMighty || thisGilesItemType == GilesItemType.TwoHandPolearm || thisGilesItemType == GilesItemType.TwoHandStaff || thisGilesItemType == GilesItemType.TwoHandSword || thisGilesItemType == GilesItemType.TwoHandAxe) { thisGilesBaseType = GilesBaseItemType.WeaponTwoHand; } else if (thisGilesItemType == GilesItemType.TwoHandCrossbow || thisGilesItemType == GilesItemType.HandCrossbow || thisGilesItemType == GilesItemType.TwoHandBow) { thisGilesBaseType = GilesBaseItemType.WeaponRange; } else if (thisGilesItemType == GilesItemType.Mojo || thisGilesItemType == GilesItemType.Source || thisGilesItemType == GilesItemType.Quiver || thisGilesItemType == GilesItemType.Shield) { thisGilesBaseType = GilesBaseItemType.Offhand; } else if (thisGilesItemType == GilesItemType.Boots || thisGilesItemType == GilesItemType.Bracers || thisGilesItemType == GilesItemType.Chest || thisGilesItemType == GilesItemType.Cloak || thisGilesItemType == GilesItemType.Gloves || thisGilesItemType == GilesItemType.Helm || thisGilesItemType == GilesItemType.Pants || thisGilesItemType == GilesItemType.Shoulders || thisGilesItemType == GilesItemType.SpiritStone || thisGilesItemType == GilesItemType.VoodooMask || thisGilesItemType == GilesItemType.WizardHat || thisGilesItemType == GilesItemType.Belt || thisGilesItemType == GilesItemType.MightyBelt) { thisGilesBaseType = GilesBaseItemType.Armor; } else if (thisGilesItemType == GilesItemType.Amulet || thisGilesItemType == GilesItemType.Ring) { thisGilesBaseType = GilesBaseItemType.Jewelry; } else if (thisGilesItemType == GilesItemType.FollowerEnchantress || thisGilesItemType == GilesItemType.FollowerScoundrel || thisGilesItemType == GilesItemType.FollowerTemplar) { thisGilesBaseType = GilesBaseItemType.FollowerItem; } else if (thisGilesItemType == GilesItemType.CraftingMaterial || thisGilesItemType == GilesItemType.CraftTome || thisGilesItemType == GilesItemType.SpecialItem || thisGilesItemType == GilesItemType.CraftingPlan || thisGilesItemType == GilesItemType.HealthPotion || thisGilesItemType == GilesItemType.Dye || thisGilesItemType == GilesItemType.StaffOfHerding || thisGilesItemType == GilesItemType.InfernalKey) { thisGilesBaseType = GilesBaseItemType.Misc; } else if (thisGilesItemType == GilesItemType.Ruby || thisGilesItemType == GilesItemType.Emerald || thisGilesItemType == GilesItemType.Topaz || thisGilesItemType == GilesItemType.Amethyst) { thisGilesBaseType = GilesBaseItemType.Gem; } else if (thisGilesItemType == GilesItemType.HealthGlobe) { thisGilesBaseType = GilesBaseItemType.HealthGlobe; } return thisGilesBaseType; } // ********************************************************************************************** // ***** DetermineIsStackable - Calculates what items can be stacked up ***** // ********************************************************************************************** private static bool DetermineIsStackable(GilesItemType thisGilesItemType) { bool bIsStackable = thisGilesItemType == GilesItemType.CraftingMaterial || thisGilesItemType == GilesItemType.CraftTome || thisGilesItemType == GilesItemType.Ruby || thisGilesItemType == GilesItemType.Emerald || thisGilesItemType == GilesItemType.Topaz || thisGilesItemType == GilesItemType.Amethyst || thisGilesItemType == GilesItemType.HealthPotion || thisGilesItemType == GilesItemType.CraftingPlan || thisGilesItemType == GilesItemType.Dye || thisGilesItemType == GilesItemType.InfernalKey; return bIsStackable; } // ********************************************************************************************** // ***** DetermineIsTwoSlot - Tries to calculate what items take up 2 slots or 1 ***** // ********************************************************************************************** private static bool DetermineIsTwoSlot(GilesItemType thisGilesItemType) { if (thisGilesItemType == GilesItemType.Axe || thisGilesItemType == GilesItemType.CeremonialKnife || thisGilesItemType == GilesItemType.Dagger || thisGilesItemType == GilesItemType.FistWeapon || thisGilesItemType == GilesItemType.Mace || thisGilesItemType == GilesItemType.MightyWeapon || thisGilesItemType == GilesItemType.Spear || thisGilesItemType == GilesItemType.Sword || thisGilesItemType == GilesItemType.Wand || thisGilesItemType == GilesItemType.TwoHandDaibo || thisGilesItemType == GilesItemType.TwoHandCrossbow || thisGilesItemType == GilesItemType.TwoHandMace || thisGilesItemType == GilesItemType.TwoHandMighty || thisGilesItemType == GilesItemType.TwoHandPolearm || thisGilesItemType == GilesItemType.TwoHandStaff || thisGilesItemType == GilesItemType.TwoHandSword || thisGilesItemType == GilesItemType.TwoHandAxe || thisGilesItemType == GilesItemType.HandCrossbow || thisGilesItemType == GilesItemType.TwoHandBow || thisGilesItemType == GilesItemType.Mojo || thisGilesItemType == GilesItemType.Source || thisGilesItemType == GilesItemType.Quiver || thisGilesItemType == GilesItemType.Shield || thisGilesItemType == GilesItemType.Boots || thisGilesItemType == GilesItemType.Bracers || thisGilesItemType == GilesItemType.Chest || thisGilesItemType == GilesItemType.Cloak || thisGilesItemType == GilesItemType.Gloves || thisGilesItemType == GilesItemType.Helm || thisGilesItemType == GilesItemType.Pants || thisGilesItemType == GilesItemType.Shoulders || thisGilesItemType == GilesItemType.SpiritStone || thisGilesItemType == GilesItemType.VoodooMask || thisGilesItemType == GilesItemType.WizardHat || thisGilesItemType == GilesItemType.StaffOfHerding) return true; return false; } // ********************************************************************************************** // ***** This is for DemonBuddy error checking - see what sort of item DB THINKS it is ***** // ********************************************************************************************** private static ItemType GilesToDBItemType(GilesItemType thisgilesitemtype) { switch (thisgilesitemtype) { case GilesItemType.Axe: return ItemType.Axe; case GilesItemType.CeremonialKnife: return ItemType.CeremonialDagger; case GilesItemType.HandCrossbow: return ItemType.HandCrossbow; case GilesItemType.Dagger: return ItemType.Dagger; case GilesItemType.FistWeapon: return ItemType.FistWeapon; case GilesItemType.Mace: return ItemType.Mace; case GilesItemType.MightyWeapon: return ItemType.MightyWeapon; case GilesItemType.Spear: return ItemType.Spear; case GilesItemType.Sword: return ItemType.Sword; case GilesItemType.Wand: return ItemType.Wand; case GilesItemType.TwoHandAxe: return ItemType.Axe; case GilesItemType.TwoHandBow: return ItemType.Bow; case GilesItemType.TwoHandDaibo: return ItemType.Daibo; case GilesItemType.TwoHandCrossbow: return ItemType.Crossbow; case GilesItemType.TwoHandMace: return ItemType.Mace; case GilesItemType.TwoHandMighty: return ItemType.MightyWeapon; case GilesItemType.TwoHandPolearm: return ItemType.Polearm; case GilesItemType.TwoHandStaff: return ItemType.Staff; case GilesItemType.TwoHandSword: return ItemType.Sword; case GilesItemType.StaffOfHerding: return ItemType.Staff; case GilesItemType.Mojo: return ItemType.Mojo; case GilesItemType.Source: return ItemType.Orb; case GilesItemType.Quiver: return ItemType.Quiver; case GilesItemType.Shield: return ItemType.Shield; case GilesItemType.Amulet: return ItemType.Amulet; case GilesItemType.Ring: return ItemType.Ring; case GilesItemType.Belt: return ItemType.Belt; case GilesItemType.Boots: return ItemType.Boots; case GilesItemType.Bracers: return ItemType.Bracer; case GilesItemType.Chest: return ItemType.Chest; case GilesItemType.Cloak: return ItemType.Cloak; case GilesItemType.Gloves: return ItemType.Gloves; case GilesItemType.Helm: return ItemType.Helm; case GilesItemType.Pants: return ItemType.Legs; case GilesItemType.MightyBelt: return ItemType.MightyBelt; case GilesItemType.Shoulders: return ItemType.Shoulder; case GilesItemType.SpiritStone: return ItemType.SpiritStone; case GilesItemType.VoodooMask: return ItemType.VoodooMask; case GilesItemType.WizardHat: return ItemType.WizardHat; case GilesItemType.FollowerEnchantress: return ItemType.FollowerSpecial; case GilesItemType.FollowerScoundrel: return ItemType.FollowerSpecial; case GilesItemType.FollowerTemplar: return ItemType.FollowerSpecial; case GilesItemType.CraftingMaterial: return ItemType.CraftingReagent; case GilesItemType.CraftTome: return ItemType.CraftingPage; case GilesItemType.Ruby: return ItemType.Gem; case GilesItemType.Emerald: return ItemType.Gem; case GilesItemType.Topaz: return ItemType.Gem; case GilesItemType.Amethyst: return ItemType.Gem; case GilesItemType.SpecialItem: return ItemType.Unknown; case GilesItemType.CraftingPlan: return ItemType.CraftingPlan; case GilesItemType.HealthPotion: return ItemType.Potion; case GilesItemType.Dye: return ItemType.Unknown; case GilesItemType.InfernalKey: return ItemType.Unknown; } return ItemType.Unknown; } // ********************************************************************************************** // ***** TownRunCheckOverlord - determine if we should do a town-run or not ***** // ********************************************************************************************** private static bool GilesTownRunCheckOverlord(object ret) { bWantToTownRun = false; // Check if we should be forcing a town-run if (bGilesForcedVendoring || Zeta.CommonBot.Logic.BrainBehavior.IsVendoring) { if (!bLastTownRunCheckResult) { bPreStashPauseDone = false; if (Zeta.CommonBot.Logic.BrainBehavior.IsVendoring) { Log("Looks like we are being asked to force a town-run by a profile/plugin/new DB feature, now doing so."); } } bWantToTownRun = true; } // Time safety switch for more advanced town-run checking to prevent CPU spam else if (DateTime.Now.Subtract(TimeLastCheckedForTownRun).TotalSeconds > 6) { TimeLastCheckedForTownRun = DateTime.Now; // Check for no space in backpack Vector2 ValidLocation = FindValidBackpackLocation(true); if (ValidLocation.X < 0 || ValidLocation.Y < 0) { Log("No more space to pickup a 2-slot item, now running town-run routine."); if (!bLastTownRunCheckResult) bPreStashPauseDone = false; bLastTownRunCheckResult = true; return true; } // Check durability percentages foreach (ACDItem tempitem in ZetaDia.Me.Inventory.Equipped) { if (tempitem.BaseAddress != IntPtr.Zero) { if (tempitem.DurabilityPercent <= Zeta.CommonBot.Settings.CharacterSettings.Instance.RepairWhenDurabilityBelow) { Log("Items may need repair, now running town-run routine."); if (!bLastTownRunCheckResult) bPreStashPauseDone = false; bWantToTownRun = true; } } } } bLastTownRunCheckResult = bWantToTownRun; return bWantToTownRun; } // ********************************************************************************************** // ***** Safety pauses to make sure we aren't still coming through the portal or selling ***** // ********************************************************************************************** private static bool bPreStashPauseDone = false; private static double iPreStashLoops = 0; private static bool GilesPreStashPauseOverlord(object ret) { return (!bPreStashPauseDone); } private static RunStatus GilesStashPrePause(object ret) { bPreStashPauseDone = true; iPreStashLoops = 0; return RunStatus.Success; } private static RunStatus GilesStashPause(object ret) { iPreStashLoops++; if (iPreStashLoops < 30) return RunStatus.Running; return RunStatus.Success; } // ********************************************************************************************** // ***** Stash Overlord values all items and checks if we have anything to stash ***** // ********************************************************************************************** private static bool GilesStashOverlord(object ret) { hashGilesCachedKeepItems = new HashSet(); bNeedsEquipmentRepairs = false; bGilesForcedVendoring = false; bool bShouldVisitStash = false; foreach (ACDItem thisitem in ZetaDia.Me.Inventory.Backpack) { if (thisitem.BaseAddress != IntPtr.Zero) { // Find out if this item's in a protected bag slot if (!ItemManager.ItemIsProtected(thisitem)) { GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(thisitem.InternalName, thisitem.Name, thisitem.Level, thisitem.ItemQualityLevel, thisitem.Gold, thisitem.GameBalanceId, thisitem.DynamicId, thisitem.Stats.WeaponDamagePerSecond, thisitem.IsOneHand, thisitem.DyeType, thisitem.ItemType, thisitem.FollowerSpecialType, thisitem.IsUnidentified, thisitem.ItemStackQuantity, thisitem.Stats, thisitem); bool bShouldStashThis = settings.bUseGilesFilters ? ShouldWeStashThis(thiscacheditem) : ItemManager.ShouldStashItem(thisitem); if (bShouldStashThis) { hashGilesCachedKeepItems.Add(thiscacheditem); bShouldVisitStash = true; } } } else { Log("GSError: Diablo 3 memory read error, or item became invalid [StashOver-1]", true); } } return bShouldVisitStash; } // ********************************************************************************************** // ***** Pre Stash prepares stuff for our stash run ***** // ********************************************************************************************** private static RunStatus GilesOptimisedPreStash(object ret) { if (settings.bDebugInfo) BotMain.StatusText = "Town run: Stash routine started"; Log("GSDebug: Stash routine started.", true); bLoggedAnythingThisStash = false; bUpdatedStashMap = false; iCurrentItemLoops = 0; RandomizeTheTimer(); return RunStatus.Success; } // ********************************************************************************************** // ***** Post Stash tidies up and signs off log file after a stash run ***** // ********************************************************************************************** private static RunStatus GilesOptimisedPostStash(object ret) { Log("GSDebug: Stash routine ending sequence...", true); // See if there's any legendary items we should send Prowl notifications about while (pushQueue.Count > 0) { SendNotification(pushQueue.Dequeue()); } // Lock memory (probably not actually necessary anymore, since we handle all item stuff ourselves!?) using (ZetaDia.Memory.AcquireFrame()) { ZetaDia.Actors.Update(); } if (bLoggedAnythingThisStash) { FileStream LogStream = null; try { LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - StashLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read); using (StreamWriter LogWriter = new StreamWriter(LogStream)) LogWriter.WriteLine(""); LogStream.Close(); if(settings.bEnableEmail && EmailMessage.Length > 0) SendEmail(sEmailAddress, sEmailAddress, "New DB stash loot - " + sBotName, EmailMessage.ToString(), SmtpServer, sEmailPassword); EmailMessage.Clear(); } catch (IOException) { Log("Fatal Error: File access error for signing off the stash log file."); if (LogStream != null) LogStream.Close(); } bLoggedAnythingThisStash = false; } Log("GSDebug: Stash routine finished.", true); return RunStatus.Success; } // ********************************************************************************************** // ***** Lovely smooth one-at-a-time stashing routine ***** // ********************************************************************************************** private static RunStatus GilesOptimisedStash(object ret) { ZetaDia.Actors.Update(); if (ZetaDia.Actors.Me == null) { Log("GSError: Diablo 3 memory read error, or item became invalid [CoreStash-1]", true); return RunStatus.Failure; } Vector3 vectorPlayerPosition = ZetaDia.Me.Position; Vector3 vectorStashLocation = new Vector3(0f, 0f, 0f); DiaObject objPlayStash = ZetaDia.Actors.GetActorsOfType(true).FirstOrDefault(); if (objPlayStash != null) vectorStashLocation = objPlayStash.Position; else switch (ZetaDia.CurrentAct) { case Act.A1: vectorStashLocation = new Vector3(2971.285f, 2798.801f, 24.04533f); break; case Act.A2: vectorStashLocation = new Vector3(323.4543f, 228.5806f, 0.1f); break; case Act.A3: case Act.A4: vectorStashLocation = new Vector3(389.3798f, 390.7143f, 0.3321428f); break; } float iDistanceFromStash = Vector3.Distance(vectorPlayerPosition, vectorStashLocation); if (iDistanceFromStash > 120f) return RunStatus.Failure; if (iDistanceFromStash > 7f) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorStashLocation, ZetaDia.Me.WorldDynamicId); return RunStatus.Running; } if (objPlayStash == null) return RunStatus.Failure; if (!UIElements.StashWindow.IsVisible) { objPlayStash.Interact(); return RunStatus.Running; } if (!bUpdatedStashMap) { // Array for what blocks are or are not blocked for (int iRow = 0; iRow <= 29; iRow++) for (int iColumn = 0; iColumn <= 6; iColumn++) GilesStashSlotBlocked[iColumn, iRow] = false; // Block off the entire of any "protected stash pages" foreach (int iProtPage in Zeta.CommonBot.Settings.CharacterSettings.Instance.ProtectedStashPages) for (int iProtRow = 0; iProtRow <= 9; iProtRow++) for (int iProtColumn = 0; iProtColumn <= 6; iProtColumn++) GilesStashSlotBlocked[iProtColumn, iProtRow + (iProtPage * 10)] = true; // Remove rows we don't have for (int iRow = (ZetaDia.Me.NumSharedStashSlots / 7); iRow <= 29; iRow++) for (int iColumn = 0; iColumn <= 6; iColumn++) GilesStashSlotBlocked[iColumn, iRow] = true; // Map out all the items already in the stash foreach (ACDItem tempitem in ZetaDia.Me.Inventory.StashItems) { if (tempitem.BaseAddress != IntPtr.Zero) { int inventoryRow = tempitem.InventoryRow; int inventoryColumn = tempitem.InventoryColumn; // Mark this slot as not-free GilesStashSlotBlocked[inventoryColumn, inventoryRow] = true; // Try and reliably find out if this is a two slot item or not GilesItemType tempItemType = DetermineItemType(tempitem.InternalName, tempitem.ItemType, tempitem.FollowerSpecialType); if (DetermineIsTwoSlot(tempItemType) && inventoryRow != 19 && inventoryRow != 9 && inventoryRow != 29) { GilesStashSlotBlocked[inventoryColumn, inventoryRow + 1] = true; } else if (DetermineIsTwoSlot(tempItemType) && (inventoryRow == 19 || inventoryRow == 9 || inventoryRow == 29)) { Log("GSError: DemonBuddy thinks this item is 2 slot even though it's at bottom row of a stash page: " + tempitem.Name + " [" + tempitem.InternalName + "] type=" + tempItemType.ToString() + " @ slot " + (inventoryRow + 1).ToString() + "/" + (inventoryColumn + 1).ToString(), true); } } } // Loop through all stash items bUpdatedStashMap = true; } // Need to update the stash map? if (hashGilesCachedKeepItems.Count > 0) { iCurrentItemLoops++; if (iCurrentItemLoops < iItemDelayLoopLimit) return RunStatus.Running; iCurrentItemLoops = 0; RandomizeTheTimer(); GilesCachedACDItem thisitem = hashGilesCachedKeepItems.FirstOrDefault(); bool bDidStashSucceed = GilesStashAttempt(thisitem); if (!bDidStashSucceed) Log("There was an unknown error stashing an item.", true); if (thisitem != null) hashGilesCachedKeepItems.Remove(thisitem); if (hashGilesCachedKeepItems.Count > 0) return RunStatus.Running; } return RunStatus.Success; } // ********************************************************************************************** // ***** Sell Overlord - determines if we should visit the vendor for repairs or selling ***** // ********************************************************************************************** private static bool GilesSellOverlord(object ret) { bGilesForcedVendoring = false; hashGilesCachedSellItems = new HashSet(); bool bShouldVisitVendor = false; // Check durability percentages iLowestDurabilityFound = -1; foreach (ACDItem tempitem in ZetaDia.Me.Inventory.Equipped) { if (tempitem.BaseAddress != IntPtr.Zero) { if (tempitem.DurabilityPercent <= Zeta.CommonBot.Settings.CharacterSettings.Instance.RepairWhenDurabilityBelow) { iLowestDurabilityFound = tempitem.DurabilityPercent; bNeedsEquipmentRepairs = true; bShouldVisitVendor = true; } } } // Check for anything to sell foreach (ACDItem thisitem in ZetaDia.Me.Inventory.Backpack) { if (thisitem.BaseAddress != IntPtr.Zero) { if (!ItemManager.ItemIsProtected(thisitem)) { GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(thisitem.InternalName, thisitem.Name, thisitem.Level, thisitem.ItemQualityLevel, thisitem.Gold, thisitem.GameBalanceId, thisitem.DynamicId, thisitem.Stats.WeaponDamagePerSecond, thisitem.IsOneHand, thisitem.DyeType, thisitem.ItemType, thisitem.FollowerSpecialType, thisitem.IsUnidentified, thisitem.ItemStackQuantity, thisitem.Stats, thisitem); bool bShouldSellThis = settings.bUseGilesFilters ? GilesSellValidation(thiscacheditem.ThisInternalName, thiscacheditem.ThisLevel, thiscacheditem.ThisQuality, thiscacheditem.ThisDBItemType, thiscacheditem.ThisFollowerType) : ItemManager.ShouldSellItem(thisitem); // Don't sell stuff that we want to salvage, if using custom loot-rules if (!settings.bUseGilesFilters && ItemManager.ShouldSalvageItem(thisitem)) { bShouldSellThis = false; } if (bShouldSellThis) { hashGilesCachedSellItems.Add(thiscacheditem); bShouldVisitVendor = true; } } } else { Log("GSError: Diablo 3 memory read error, or item became invalid [SellOver-1]", true); } } return bShouldVisitVendor; } // ********************************************************************************************** // ***** Pre Sell sets everything up ready for running to vendor ***** // ********************************************************************************************** private static RunStatus GilesOptimisedPreSell(object ret) { if (settings.bDebugInfo) BotMain.StatusText = "Town run: Sell routine started"; Log("GSDebug: Sell routine started.", true); ZetaDia.Actors.Update(); if (ZetaDia.Actors.Me == null) { Log("GSError: Diablo 3 memory read error, or item became invalid [PreSell-1]", true); return RunStatus.Failure; } bGoToSafetyPointFirst = true; bGoToSafetyPointSecond = false; bLoggedJunkThisStash = false; bCurrentlyMoving = false; bReachedDestination = false; iCurrentItemLoops = 0; RandomizeTheTimer(); return RunStatus.Success; } // ********************************************************************************************** // ***** Sell Routine replacement for smooth one-at-a-time item selling and handling ***** // ********************************************************************************************** private static RunStatus GilesOptimisedSell(object ret) { string sVendorName = ""; switch (ZetaDia.CurrentAct) { case Act.A1: sVendorName = "a1_uniquevendor_miner"; break; case Act.A2: sVendorName = "a2_uniquevendor_peddler"; break; case Act.A3: sVendorName = "a3_uniquevendor_collector"; break; case Act.A4: sVendorName = "a4_uniquevendor_collector"; break; } if (bGoToSafetyPointFirst) { Vector3 vectorPlayerPosition = ZetaDia.Me.Position; Vector3 vectorSellLocation = new Vector3(0f, 0f, 0f); switch (ZetaDia.CurrentAct) { case Act.A1: vectorSellLocation = new Vector3(2941.904f, 2812.825f, 24.04533f); break; case Act.A2: vectorSellLocation = new Vector3(295.2101f, 265.1436f, 0.1000002f); break; case Act.A3: case Act.A4: vectorSellLocation = new Vector3(410.6073f, 355.8762f, 0.1000005f); break; } float iDistanceFromSell = Vector3.Distance(vectorPlayerPosition, vectorSellLocation); if (bCurrentlyMoving) { if (iDistanceFromSell <= 8f) { bGoToSafetyPointFirst = false; /*if (ZetaDia.CurrentAct == Act.A2) bGoToSafetyPointSecond = true;*/ bCurrentlyMoving = false; } else if (iLastDistance == iDistanceFromSell) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId); } return RunStatus.Running; } iLastDistance = iDistanceFromSell; if (iDistanceFromSell > 120f) return RunStatus.Failure; if (iDistanceFromSell > 8f) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId); bCurrentlyMoving = true; return RunStatus.Running; } bCurrentlyMoving = false; bGoToSafetyPointFirst = false; /*if (ZetaDia.CurrentAct == Act.A2) bGoToSafetyPointSecond = true;*/ return RunStatus.Running; } /*if (bGoToSafetyPointSecond) { Vector3 vectorPlayerPosition = ZetaDia.Me.Position; Vector3 vectorSellLocation = new Vector3(0f, 0f, 0f); switch (ZetaDia.CurrentAct) { case Act.A1: vectorSellLocation = new Vector3(2941.904f, 2812.825f, 24.04533f); break; case Act.A2: vectorSellLocation = new Vector3(295.0274f, 156.2243f, -1.834799f); break; case Act.A3: case Act.A4: vectorSellLocation = new Vector3(410.6073f, 355.8762f, 0.1000005f); break; } float iDistanceFromSell = Vector3.Distance(vectorPlayerPosition, vectorSellLocation); if (bCurrentlyMoving) { if (iDistanceFromSell <= 8f) { bGoToSafetyPointSecond = false; bCurrentlyMoving = false; } else if (iLastDistance == iDistanceFromSell) { try { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId); } catch { Log("GSError: Diablo 3 move command error [CoreSell-1: " + thisGilesDiaItem.ThisInternalName + "]", true); } } return RunStatus.Running; } iLastDistance = iDistanceFromSell; if (iDistanceFromSell > 120f) return RunStatus.Failure; if (iDistanceFromSell > 8f) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId); bCurrentlyMoving = true; return RunStatus.Running; } bCurrentlyMoving = false; bGoToSafetyPointSecond = false; return RunStatus.Running; }*/ if (!bReachedDestination) { Vector3 vectorPlayerPosition = ZetaDia.Me.Position; Vector3 vectorSellLocation = new Vector3(0f, 0f, 0f); switch (ZetaDia.CurrentAct) { case Act.A1: vectorSellLocation = new Vector3(2896.159f, 2779.443f, 24.04532f); break; case Act.A2: vectorSellLocation = new Vector3(286.0302f, 280.2442f, 0.1000038f); break; case Act.A3: case Act.A4: vectorSellLocation = new Vector3(447.8373f, 324.1446f, 0.1000005f); break; } DiaUnit objSellNavigation = ZetaDia.Actors.GetActorsOfType(true).FirstOrDefault(u => u.Name.ToLower().StartsWith(sVendorName)); if (objSellNavigation != null) vectorSellLocation = objSellNavigation.Position; float iDistanceFromSell = Vector3.Distance(vectorPlayerPosition, vectorSellLocation); if (bCurrentlyMoving) { if (iDistanceFromSell <= 9.5f) { bReachedDestination = true; if (objSellNavigation == null) return RunStatus.Failure; objSellNavigation.Interact(); } else if (iLastDistance == iDistanceFromSell) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId); } return RunStatus.Running; } iLastDistance = iDistanceFromSell; if (iDistanceFromSell > 120f) return RunStatus.Failure; if (iDistanceFromSell > 9.5f) { bCurrentlyMoving = true; ZetaDia.Me.UsePower(SNOPower.Walk, vectorSellLocation, ZetaDia.Me.WorldDynamicId); return RunStatus.Running; } bReachedDestination = true; if (objSellNavigation == null) return RunStatus.Failure; objSellNavigation.Interact(); } if (!Zeta.Internals.UIElement.IsValidElement(12123456831356216535L) || !Zeta.Internals.UIElement.FromHash(12123456831356216535L).IsVisible) { DiaUnit objSellNavigation = ZetaDia.Actors.GetActorsOfType(true).FirstOrDefault(u => u.Name.ToLower().StartsWith(sVendorName)); if (objSellNavigation == null) return RunStatus.Failure; objSellNavigation.Interact(); return RunStatus.Running; } if (hashGilesCachedSellItems.Count > 0) { iCurrentItemLoops++; if (iCurrentItemLoops < iItemDelayLoopLimit) return RunStatus.Running; iCurrentItemLoops = 0; RandomizeTheTimer(); GilesCachedACDItem thisitem = hashGilesCachedSellItems.FirstOrDefault(); // Item log for cool stuff sold if (thisitem != null) { GilesItemType OriginalGilesItemType = DetermineItemType(thisitem.ThisInternalName, thisitem.ThisDBItemType, thisitem.ThisFollowerType); GilesBaseItemType thisGilesBaseType = DetermineBaseType(OriginalGilesItemType); if (thisGilesBaseType == GilesBaseItemType.WeaponTwoHand || thisGilesBaseType == GilesBaseItemType.WeaponOneHand || thisGilesBaseType == GilesBaseItemType.WeaponRange || thisGilesBaseType == GilesBaseItemType.Armor || thisGilesBaseType == GilesBaseItemType.Jewelry || thisGilesBaseType == GilesBaseItemType.Offhand || thisGilesBaseType == GilesBaseItemType.FollowerItem) { double iThisItemValue = ValueThisItem(thisitem, OriginalGilesItemType); LogJunkItems(thisitem, thisGilesBaseType, OriginalGilesItemType, iThisItemValue); } ZetaDia.Me.Inventory.SellItem(thisitem.ThisDynamicID); } if (thisitem != null) hashGilesCachedSellItems.Remove(thisitem); if (hashGilesCachedSellItems.Count > 0) return RunStatus.Running; } bCurrentlyMoving = false; bReachedSafety = false; return RunStatus.Success; } // ********************************************************************************************** // ***** Post Sell tidies everything up and signs off junk log after selling ***** // ********************************************************************************************** private static RunStatus GilesOptimisedPostSell(object ret) { Log("GSDebug: Sell routine ending sequence...", true); using (ZetaDia.Memory.AcquireFrame()) { ZetaDia.Actors.Update(); } // Always repair, but only if we have enough money if (bNeedsEquipmentRepairs && iLowestDurabilityFound < 20 && iLowestDurabilityFound > -1 && ZetaDia.Me.Inventory.Coinage < 40000) { Log("***************************"); Log("Emergency Stop: You need repairs but don't have enough money. Stopping the bot to prevent infinite death loop."); BotMain.Stop(); } ZetaDia.Me.Inventory.RepairEquippedItems(); bNeedsEquipmentRepairs = false; if (bLoggedJunkThisStash) { FileStream LogStream = null; try { LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - JunkLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read); using (StreamWriter LogWriter = new StreamWriter(LogStream)) LogWriter.WriteLine(""); LogStream.Close(); } catch (IOException) { Log("Fatal Error: File access error for signing off the junk log file."); if (LogStream != null) LogStream.Close(); } bLoggedJunkThisStash = false; } // See if we can close the inventory window if (Zeta.Internals.UIElement.IsValidElement(0x368FF8C552241695)) { try { var el = Zeta.Internals.UIElement.FromHash(0x368FF8C552241695); if (el != null && el.IsValid && el.IsVisible && el.IsEnabled) el.Click(); } catch { // Do nothing if it fails, just catching to prevent any big errors/plugin crashes from this } } /*if (!bReachedSafety && ZetaDia.CurrentAct == Act.A2) { Vector3 vectorPlayerPosition = ZetaDia.Me.Position; Vector3 vectorSafeLocation = new Vector3(284.3047f, 212.2945f, 0.1f); float iDistanceFromSafety = Vector3.Distance(vectorPlayerPosition, vectorSafeLocation); if (bCurrentlyMoving) { if (iDistanceFromSafety <= 8f) { bReachedSafety = true; bCurrentlyMoving = false; } else if (iLastDistance == iDistanceFromSafety) { try { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSafeLocation, ZetaDia.Me.WorldDynamicId); } catch { Log("GSError: Diablo 3 move command error [PostSell-1: " + thisGilesDiaItem.ThisInternalName + "]", true); } } return RunStatus.Running; } iLastDistance = iDistanceFromSafety; if (iDistanceFromSafety > 120f) return RunStatus.Failure; if (iDistanceFromSafety > 8f) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSafeLocation, ZetaDia.Me.WorldDynamicId); bCurrentlyMoving = true; return RunStatus.Running; } bCurrentlyMoving = false; bReachedSafety = true; }*/ Log("GSDebug: Sell routine finished.", true); return RunStatus.Success; } // ********************************************************************************************** // ***** Salvage Overlord determines if we should visit the blacksmith or not ***** // ********************************************************************************************** private static bool GilesSalvageOverlord(object ret) { bGilesForcedVendoring = false; hashGilesCachedSalvageItems = new HashSet(); bool bShouldVisitSmith = false; // Check for anything to salvage foreach (ACDItem thisitem in ZetaDia.Me.Inventory.Backpack) { if (thisitem.BaseAddress != IntPtr.Zero) { if (!ItemManager.ItemIsProtected(thisitem)) { GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(thisitem.InternalName, thisitem.Name, thisitem.Level, thisitem.ItemQualityLevel, thisitem.Gold, thisitem.GameBalanceId, thisitem.DynamicId, thisitem.Stats.WeaponDamagePerSecond, thisitem.IsOneHand, thisitem.DyeType, thisitem.ItemType, thisitem.FollowerSpecialType, thisitem.IsUnidentified, thisitem.ItemStackQuantity, thisitem.Stats, thisitem); bool bShouldSalvageThis = settings.bUseGilesFilters ? GilesSalvageValidation(thiscacheditem.ThisInternalName, thiscacheditem.ThisLevel, thiscacheditem.ThisQuality, thiscacheditem.ThisDBItemType, thiscacheditem.ThisFollowerType) : ItemManager.ShouldSalvageItem(thisitem); if (bShouldSalvageThis) { hashGilesCachedSalvageItems.Add(thiscacheditem); bShouldVisitSmith = true; } } } else { Log("GSError: Diablo 3 memory read error, or item became invalid [SalvageOver-1]", true); } } return bShouldVisitSmith; } // ********************************************************************************************** // ***** Pre Salvage sets everything up ready for our blacksmith run ***** // ********************************************************************************************** private static RunStatus GilesOptimisedPreSalvage(object ret) { if (settings.bDebugInfo) BotMain.StatusText = "Town run: Salvage routine started"; Log("GSDebug: Salvage routine started.", true); ZetaDia.Actors.Update(); if (ZetaDia.Actors.Me == null) { Log("GSError: Diablo 3 memory read error, or item became invalid [PreSalvage-1]", true); return RunStatus.Failure; } bGoToSafetyPointFirst = true; bGoToSafetyPointSecond = false; bLoggedJunkThisStash = false; bCurrentlyMoving = false; bReachedDestination = false; iCurrentItemLoops = 0; RandomizeTheTimer(); return RunStatus.Success; } // ********************************************************************************************** // ***** Nice smooth one-at-a-time salvaging replacement ***** // ********************************************************************************************** private static RunStatus GilesOptimisedSalvage(object ret) { if (bGoToSafetyPointFirst) { Vector3 vectorPlayerPosition = ZetaDia.Me.Position; Vector3 vectorSalvageLocation = new Vector3(0f, 0f, 0f); switch (ZetaDia.CurrentAct) { case Act.A1: vectorSalvageLocation = new Vector3(2949.626f, 2815.065f, 24.04389f); break; case Act.A2: vectorSalvageLocation = new Vector3(289.6358f, 232.1146f, 0.1f); break; case Act.A3: case Act.A4: vectorSalvageLocation = new Vector3(379.6096f, 415.6198f, 0.3321424f); break; } float iDistanceFromSalvage = Vector3.Distance(vectorPlayerPosition, vectorSalvageLocation); if (bCurrentlyMoving) { if (iDistanceFromSalvage <= 8f) { bGoToSafetyPointFirst = false; if (ZetaDia.CurrentAct == Act.A3) bGoToSafetyPointSecond = true; bCurrentlyMoving = false; } else if (iLastDistance == iDistanceFromSalvage) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId); } return RunStatus.Running; } iLastDistance = iDistanceFromSalvage; if (iDistanceFromSalvage > 120f) return RunStatus.Failure; if (iDistanceFromSalvage > 8f) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId); bCurrentlyMoving = true; return RunStatus.Running; } bCurrentlyMoving = false; bGoToSafetyPointFirst = false; if (ZetaDia.CurrentAct == Act.A3) bGoToSafetyPointSecond = true; return RunStatus.Running; } if (bGoToSafetyPointSecond) { Vector3 vectorPlayerPosition = ZetaDia.Me.Position; Vector3 vectorSalvageLocation = new Vector3(0f, 0f, 0f); switch (ZetaDia.CurrentAct) { case Act.A1: vectorSalvageLocation = new Vector3(2949.626f, 2815.065f, 24.04389f); break; case Act.A2: vectorSalvageLocation = new Vector3(289.6358f, 232.1146f, 0.1f); break; case Act.A3: case Act.A4: vectorSalvageLocation = new Vector3(328.6024f, 425.4113f, 0.2758033f); break; } float iDistanceFromSalvage = Vector3.Distance(vectorPlayerPosition, vectorSalvageLocation); if (bCurrentlyMoving) { if (iDistanceFromSalvage <= 8f) { bGoToSafetyPointSecond = false; bCurrentlyMoving = false; } else if (iLastDistance == iDistanceFromSalvage) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId); } return RunStatus.Running; } iLastDistance = iDistanceFromSalvage; if (iDistanceFromSalvage > 120f) return RunStatus.Failure; if (iDistanceFromSalvage > 8f) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId); bCurrentlyMoving = true; return RunStatus.Running; } bCurrentlyMoving = false; bGoToSafetyPointSecond = false; return RunStatus.Running; } if (!bReachedDestination) { Vector3 vectorPlayerPosition = ZetaDia.Me.Position; Vector3 vectorSalvageLocation = new Vector3(0f, 0f, 0f); DiaUnit objSalvageNavigation = ZetaDia.Actors.GetActorsOfType(true).FirstOrDefault(u => u.IsSalvageShortcut); if (objSalvageNavigation != null) vectorSalvageLocation = objSalvageNavigation.Position; else switch (ZetaDia.CurrentAct) { case Act.A1: vectorSalvageLocation = new Vector3(2942.137f, 2854.078f, 24.04533f); break; case Act.A2: vectorSalvageLocation = new Vector3(275.6705f, 221.1727f, 0.1f); break; case Act.A3: case Act.A4: vectorSalvageLocation = new Vector3(328.6024f, 425.4113f, 0.2758033f); break; } float iDistanceFromSalvage = Vector3.Distance(vectorPlayerPosition, vectorSalvageLocation); if (bCurrentlyMoving) { if (iDistanceFromSalvage <= 9.5f) { bReachedDestination = true; if (objSalvageNavigation == null) return RunStatus.Failure; objSalvageNavigation.Interact(); } else if (iLastDistance == iDistanceFromSalvage) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId); } return RunStatus.Running; } iLastDistance = iDistanceFromSalvage; if (iDistanceFromSalvage > 120f) return RunStatus.Failure; if (iDistanceFromSalvage > 9.5f) { bCurrentlyMoving = true; ZetaDia.Me.UsePower(SNOPower.Walk, vectorSalvageLocation, ZetaDia.Me.WorldDynamicId); return RunStatus.Running; } bReachedDestination = true; if (objSalvageNavigation == null) return RunStatus.Failure; objSalvageNavigation.Interact(); } if (!Zeta.Internals.UIElement.IsValidElement(0x359867fd497d2ff3L) || !Zeta.Internals.UIElement.FromHash(0x359867fd497d2ff3L).IsVisible) { DiaUnit objSalvageNavigation = ZetaDia.Actors.GetActorsOfType(true).FirstOrDefault(u => u.IsSalvageShortcut); if (objSalvageNavigation == null) return RunStatus.Failure; objSalvageNavigation.Interact(); return RunStatus.Running; } if (hashGilesCachedSalvageItems.Count > 0) { iCurrentItemLoops++; if (iCurrentItemLoops < iItemDelayLoopLimit) return RunStatus.Running; iCurrentItemLoops = 0; RandomizeTheTimer(); GilesCachedACDItem thisitem = hashGilesCachedSalvageItems.FirstOrDefault(); if (thisitem != null) { // Item log for cool stuff stashed GilesItemType OriginalGilesItemType = DetermineItemType(thisitem.ThisInternalName, thisitem.ThisDBItemType, thisitem.ThisFollowerType); GilesBaseItemType thisGilesBaseType = DetermineBaseType(OriginalGilesItemType); if (thisGilesBaseType == GilesBaseItemType.WeaponTwoHand || thisGilesBaseType == GilesBaseItemType.WeaponOneHand || thisGilesBaseType == GilesBaseItemType.WeaponRange || thisGilesBaseType == GilesBaseItemType.Armor || thisGilesBaseType == GilesBaseItemType.Jewelry || thisGilesBaseType == GilesBaseItemType.Offhand || thisGilesBaseType == GilesBaseItemType.FollowerItem) { double iThisItemValue = ValueThisItem(thisitem, OriginalGilesItemType); LogJunkItems(thisitem, thisGilesBaseType, OriginalGilesItemType, iThisItemValue); } ZetaDia.Me.Inventory.SalvageItem(thisitem.ThisDynamicID); } if (thisitem != null) hashGilesCachedSalvageItems.Remove(thisitem); if (hashGilesCachedSalvageItems.Count > 0) return RunStatus.Running; } bReachedSafety = false; bCurrentlyMoving = false; return RunStatus.Success; } // ********************************************************************************************** // ***** Post salvage cleans up and signs off junk log file after salvaging ***** // ********************************************************************************************** private static RunStatus GilesOptimisedPostSalvage(object ret) { Log("GSDebug: Salvage routine ending sequence...", true); using (ZetaDia.Memory.AcquireFrame()) { ZetaDia.Actors.Update(); } if (bLoggedJunkThisStash) { FileStream LogStream = null; try { LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - JunkLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read); using (StreamWriter LogWriter = new StreamWriter(LogStream)) LogWriter.WriteLine(""); LogStream.Close(); } catch (IOException) { Log("Fatal Error: File access error for signing off the junk log file."); if (LogStream != null) LogStream.Close(); } bLoggedJunkThisStash = false; } if (!bReachedSafety && ZetaDia.CurrentAct == Act.A3) { Vector3 vectorPlayerPosition = ZetaDia.Me.Position; Vector3 vectorSafeLocation = new Vector3(379.6096f, 415.6198f, 0.3321424f); float iDistanceFromSafety = Vector3.Distance(vectorPlayerPosition, vectorSafeLocation); if (bCurrentlyMoving) { if (iDistanceFromSafety <= 8f) { bGoToSafetyPointSecond = false; bCurrentlyMoving = false; } else if (iLastDistance == iDistanceFromSafety) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSafeLocation, ZetaDia.Me.WorldDynamicId); } return RunStatus.Running; } iLastDistance = iDistanceFromSafety; if (iDistanceFromSafety > 120f) return RunStatus.Failure; if (iDistanceFromSafety > 8f) { ZetaDia.Me.UsePower(SNOPower.Walk, vectorSafeLocation, ZetaDia.Me.WorldDynamicId); bCurrentlyMoving = true; return RunStatus.Running; } bCurrentlyMoving = false; bReachedSafety = true; } Log("GSDebug: Salvage routine finished.", true); return RunStatus.Success; } // ********************************************************************************************** // ***** Arrange your stash by highest to lowest scoring items ***** // ********************************************************************************************** public class GilesStashSort { public double dStashScore { get; set; } public int iStashOrPack { get; set; } public int iInventoryColumn { get; set; } public int iInventoryRow { get; set; } public int iDynamicID { get; set; } public bool bIsTwoSlot { get; set; } public GilesStashSort(double stashscore, int stashorpack, int icolumn, int irow, int dynamicid, bool twoslot) { dStashScore = stashscore; iStashOrPack = stashorpack; iInventoryColumn = icolumn; iInventoryRow = irow; iDynamicID = dynamicid; bIsTwoSlot = twoslot; } } // ********************************************************************************************** // ***** Search backpack to see if we have room for a 2-slot item anywhere ***** // ********************************************************************************************** private static bool[,] GilesBackpackSlotBlocked = new bool[10, 6]; private static Vector2 SortingFindLocationBackpack(bool bOriginalTwoSlot) { int iPointX = -1; int iPointY = -1; for (int iRow = 0; iRow <= 5; iRow++) { for (int iColumn = 0; iColumn <= 9; iColumn++) { if (!GilesBackpackSlotBlocked[iColumn, iRow]) { bool bNotEnoughSpace = false; if (iRow < 5) { bNotEnoughSpace = (bOriginalTwoSlot && GilesBackpackSlotBlocked[iColumn, iRow + 1]); } else { if (bOriginalTwoSlot) bNotEnoughSpace = true; } if (!bNotEnoughSpace) { iPointX = iColumn; iPointY = iRow; goto FoundPackLocation; } } } } FoundPackLocation: if ((iPointX < 0) || (iPointY < 0)) { return new Vector2(-1, -1); } return new Vector2(iPointX, iPointY); } private static Vector2 SortingFindLocationStash(bool bOriginalTwoSlot, bool bEndOfStash = false) { int iPointX = -1; int iPointY = -1; for (int iRow = 0; iRow <= 29; iRow++) { for (int iColumn = 0; iColumn <= 6; iColumn++) { if (!GilesStashSlotBlocked[iColumn, iRow]) { bool bNotEnoughSpace = false; if (iRow != 9 && iRow != 19 && iRow != 29) { bNotEnoughSpace = (bOriginalTwoSlot && GilesStashSlotBlocked[iColumn, iRow + 1]); } else { if (bOriginalTwoSlot) bNotEnoughSpace = true; } if (!bNotEnoughSpace) { iPointX = iColumn; iPointY = iRow; if (!bEndOfStash) goto FoundStashLocation; } } } } FoundStashLocation: if ((iPointX < 0) || (iPointY < 0)) { return new Vector2(-1, -1); } return new Vector2(iPointX, iPointY); } private static void SortStash() { // Try and update the player-data ZetaDia.Actors.Update(); // Check we can get the player dynamic ID int iPlayerDynamicID = -1; try { iPlayerDynamicID = ZetaDia.Me.CommonData.DynamicId; } catch { Log("Failure getting your player data from DemonBuddy, abandoning the sort!"); return; } if (iPlayerDynamicID == -1) { Log("Failure getting your player data, abandoning the sort!"); return; } // List used for all the sorting List listSortMyStash = new List(); // Map out the backpack free slots for (int iRow = 0; iRow <= 5; iRow++) for (int iColumn = 0; iColumn <= 9; iColumn++) GilesBackpackSlotBlocked[iColumn, iRow] = false; foreach (ACDItem tempitem in ZetaDia.Me.Inventory.Backpack) { int inventoryRow = tempitem.InventoryRow; int inventoryColumn = tempitem.InventoryColumn; // Mark this slot as not-free GilesBackpackSlotBlocked[inventoryColumn, inventoryRow] = true; // Try and reliably find out if this is a two slot item or not GilesItemType tempItemType = DetermineItemType(tempitem.InternalName, tempitem.ItemType, tempitem.FollowerSpecialType); if (DetermineIsTwoSlot(tempItemType) && inventoryRow < 5) { GilesBackpackSlotBlocked[inventoryColumn, inventoryRow + 1] = true; } } // Map out the stash free slots for (int iRow = 0; iRow <= 29; iRow++) for (int iColumn = 0; iColumn <= 6; iColumn++) GilesStashSlotBlocked[iColumn, iRow] = false; // Block off the entire of any "protected stash pages" foreach (int iProtPage in Zeta.CommonBot.Settings.CharacterSettings.Instance.ProtectedStashPages) for (int iProtRow = 0; iProtRow <= 9; iProtRow++) for (int iProtColumn = 0; iProtColumn <= 6; iProtColumn++) GilesStashSlotBlocked[iProtColumn, iProtRow + (iProtPage * 10)] = true; // Remove rows we don't have for (int iRow = (ZetaDia.Me.NumSharedStashSlots / 7); iRow <= 29; iRow++) for (int iColumn = 0; iColumn <= 6; iColumn++) GilesStashSlotBlocked[iColumn, iRow] = true; // Map out all the items already in the stash and store their scores if appropriate foreach (ACDItem thisitem in ZetaDia.Me.Inventory.StashItems) { int inventoryRow = thisitem.InventoryRow; int inventoryColumn = thisitem.InventoryColumn; // Mark this slot as not-free GilesStashSlotBlocked[inventoryColumn, inventoryRow] = true; // Try and reliably find out if this is a two slot item or not GilesItemType tempItemType = DetermineItemType(thisitem.InternalName, thisitem.ItemType, thisitem.FollowerSpecialType); bool bIsTwoSlot = DetermineIsTwoSlot(tempItemType); if (bIsTwoSlot && inventoryRow != 19 && inventoryRow != 9 && inventoryRow != 29) { GilesStashSlotBlocked[inventoryColumn, inventoryRow + 1] = true; } else if (bIsTwoSlot && (inventoryRow == 19 || inventoryRow == 9 || inventoryRow == 29)) { Log("WARNING: There was an error reading your stash, abandoning the process."); Log("Always make sure you empty your backpack, open the stash, then RESTART DEMONBUDDY before sorting!"); return; } GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(thisitem.InternalName, thisitem.Name, thisitem.Level, thisitem.ItemQualityLevel, thisitem.Gold, thisitem.GameBalanceId, thisitem.DynamicId, thisitem.Stats.WeaponDamagePerSecond, thisitem.IsOneHand, thisitem.DyeType, thisitem.ItemType, thisitem.FollowerSpecialType, thisitem.IsUnidentified, thisitem.ItemStackQuantity, thisitem.Stats, thisitem); double iThisItemValue = ValueThisItem(thiscacheditem, tempItemType); double iNeedScore = ScoreNeeded(tempItemType); // Ignore stackable items if (!DetermineIsStackable(tempItemType) && tempItemType != GilesItemType.StaffOfHerding) { listSortMyStash.Add(new GilesStashSort(((iThisItemValue / iNeedScore) * 1000), 1, inventoryColumn, inventoryRow, thisitem.DynamicId, bIsTwoSlot)); } } // Loop through all stash items // Sort the items in the stash by their row number, lowest to highest listSortMyStash.Sort((p1, p2) => p1.iInventoryRow.CompareTo(p2.iInventoryRow)); // Now move items into your backpack until full, then into the END of the stash Vector2 vFreeSlot; foreach (GilesStashSort thisstashsort in listSortMyStash) { vFreeSlot = SortingFindLocationBackpack(thisstashsort.bIsTwoSlot); int iStashOrPack = 1; if (vFreeSlot.X == -1 || vFreeSlot.Y == -1) { vFreeSlot = SortingFindLocationStash(thisstashsort.bIsTwoSlot, true); if (vFreeSlot.X == -1 || vFreeSlot.Y == -1) continue; iStashOrPack = 2; } if (iStashOrPack == 1) { ZetaDia.Me.Inventory.MoveItem(thisstashsort.iDynamicID, iPlayerDynamicID, InventorySlot.PlayerBackpack, (int)vFreeSlot.X, (int)vFreeSlot.Y); GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow] = false; if (thisstashsort.bIsTwoSlot) GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow + 1] = false; GilesBackpackSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y] = true; if (thisstashsort.bIsTwoSlot) GilesBackpackSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y + 1] = true; thisstashsort.iInventoryColumn = (int)vFreeSlot.X; thisstashsort.iInventoryRow = (int)vFreeSlot.Y; thisstashsort.iStashOrPack = 2; } else { ZetaDia.Me.Inventory.MoveItem(thisstashsort.iDynamicID, iPlayerDynamicID, InventorySlot.PlayerSharedStash, (int)vFreeSlot.X, (int)vFreeSlot.Y); GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow] = false; if (thisstashsort.bIsTwoSlot) GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow + 1] = false; GilesStashSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y] = true; if (thisstashsort.bIsTwoSlot) GilesStashSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y + 1] = true; thisstashsort.iInventoryColumn = (int)vFreeSlot.X; thisstashsort.iInventoryRow = (int)vFreeSlot.Y; thisstashsort.iStashOrPack = 1; } Thread.Sleep(150); } // Now sort the items by their score, highest to lowest listSortMyStash.Sort((p1, p2) => p1.dStashScore.CompareTo(p2.dStashScore)); listSortMyStash.Reverse(); // Now fill the stash in ordered-order foreach (GilesStashSort thisstashsort in listSortMyStash) { vFreeSlot = SortingFindLocationStash(thisstashsort.bIsTwoSlot, false); if (vFreeSlot.X == -1 || vFreeSlot.Y == -1) { Log("Failure trying to put things back into stash, no stash slots free? Abandoning..."); return; } ZetaDia.Me.Inventory.MoveItem(thisstashsort.iDynamicID, iPlayerDynamicID, InventorySlot.PlayerSharedStash, (int)vFreeSlot.X, (int)vFreeSlot.Y); if (thisstashsort.iStashOrPack == 1) { GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow] = false; if (thisstashsort.bIsTwoSlot) GilesStashSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow + 1] = false; } else { GilesBackpackSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow] = false; if (thisstashsort.bIsTwoSlot) GilesBackpackSlotBlocked[thisstashsort.iInventoryColumn, thisstashsort.iInventoryRow + 1] = false; } GilesStashSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y] = true; if (thisstashsort.bIsTwoSlot) GilesStashSlotBlocked[(int)vFreeSlot.X, (int)vFreeSlot.Y + 1] = true; thisstashsort.iStashOrPack = 1; thisstashsort.iInventoryRow = (int)vFreeSlot.Y; thisstashsort.iInventoryColumn = (int)vFreeSlot.X; Thread.Sleep(150); } Log("Stash sorted!"); } // ********************************************************************************************** // ***** Output test scores for everything in the backpack ***** // ********************************************************************************************** private static void TestScoring() { if (bTestingBackpack) return; bTestingBackpack = true; ZetaDia.Actors.Update(); if (ZetaDia.Actors.Me == null) { Logging.Write("Error testing scores - not in game world?"); return; } if (ZetaDia.IsInGame && !ZetaDia.IsLoadingWorld) { bOutputItemScores = true; Logging.Write("===== Outputting Test Scores ====="); foreach (ACDItem item in ZetaDia.Actors.Me.Inventory.Backpack) { if (item.BaseAddress == IntPtr.Zero) { Logging.Write("GSError: Diablo 3 memory read error, or item became invalid [TestScore-1]"); } else { GilesCachedACDItem thiscacheditem = new GilesCachedACDItem(item.InternalName, item.Name, item.Level, item.ItemQualityLevel, item.Gold, item.GameBalanceId, item.DynamicId, item.Stats.WeaponDamagePerSecond, item.IsOneHand, item.DyeType, item.ItemType, item.FollowerSpecialType, item.IsUnidentified, item.ItemStackQuantity, item.Stats, item); bool bShouldStashTest = ShouldWeStashThis(thiscacheditem); Logging.Write(bShouldStashTest ? "***** KEEP *****" : "-- TRASH --"); } } Logging.Write("===== Finished Test Score Outputs ====="); Logging.Write("Note: See bad scores? Wrong item types? Known DB bug - restart DB before using the test button!"); bOutputItemScores = false; } else { Logging.Write("Error testing scores - not in game world?"); } bTestingBackpack = false; } // ********************************************************************************************** // ***** Determine if we should stash this item or not based on item type and score ***** // ********************************************************************************************** private static bool ShouldWeStashThis(GilesCachedACDItem thisitem) { // Stash all unidentified items - assume we want to keep them since we are using an identifier over-ride if (thisitem.IsUnidentified) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] = (autokeep unidentified items)"); return true; } // Now look for Misc items we might want to keep GilesItemType TrueItemType = DetermineItemType(thisitem.ThisInternalName, thisitem.ThisDBItemType, thisitem.ThisFollowerType); if (TrueItemType == GilesItemType.StaffOfHerding) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep staff of herding)"); return true; } if (TrueItemType == GilesItemType.CraftingMaterial) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep craft materials)"); return true; } if (TrueItemType == GilesItemType.CraftingPlan) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep plans)"); return true; } if (TrueItemType == GilesItemType.Emerald) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep gems)"); return true; } if (TrueItemType == GilesItemType.Amethyst) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep gems)"); return true; } if (TrueItemType == GilesItemType.Topaz) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep gems)"); return true; } if (TrueItemType == GilesItemType.Ruby) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep gems)"); return true; } if (TrueItemType == GilesItemType.CraftTome) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep tomes)"); return true; } if (TrueItemType == GilesItemType.InfernalKey) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep infernal key)"); return true; } if (TrueItemType == GilesItemType.HealthPotion) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (ignoring potions)"); return false; } // let the item rule set decide about this item Interpreter.InterpreterAction action = interpreter.checkItem(thisitem.ACDItem); switch (action) { case Interpreter.InterpreterAction.KEEP: Log("ItemRuleSet: " + thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] = (KEEP)"); return true; case Interpreter.InterpreterAction.TRASH: Log("ItemRuleSet: " + thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] = (TRASH)"); return false; default: break; } if (thisitem.ThisQuality >= ItemQuality.Legendary) { if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = (autokeep legendaries)"); return true; } // Ok now try to do some decent item scoring based on item types double iNeedScore = ScoreNeeded(TrueItemType); double iMyScore = ValueThisItem(thisitem, TrueItemType); if (bOutputItemScores) Log(thisitem.ThisRealName + " [" + thisitem.ThisInternalName + "] [" + TrueItemType.ToString() + "] = " + iMyScore.ToString()); if (iMyScore >= iNeedScore) return true; // If we reached this point, then we found no reason to keep the item! return false; } // ********************************************************************************************** // ***** Return the score needed to keep something by the item type ***** // ********************************************************************************************** private static double ScoreNeeded(GilesItemType thisGilesItemType) { double iThisNeedScore = 0; // Weapons if (thisGilesItemType == GilesItemType.Axe || thisGilesItemType == GilesItemType.CeremonialKnife || thisGilesItemType == GilesItemType.Dagger || thisGilesItemType == GilesItemType.FistWeapon || thisGilesItemType == GilesItemType.Mace || thisGilesItemType == GilesItemType.MightyWeapon || thisGilesItemType == GilesItemType.Spear || thisGilesItemType == GilesItemType.Sword || thisGilesItemType == GilesItemType.Wand || thisGilesItemType == GilesItemType.TwoHandDaibo || thisGilesItemType == GilesItemType.TwoHandCrossbow || thisGilesItemType == GilesItemType.TwoHandMace || thisGilesItemType == GilesItemType.TwoHandMighty || thisGilesItemType == GilesItemType.TwoHandPolearm || thisGilesItemType == GilesItemType.TwoHandStaff || thisGilesItemType == GilesItemType.TwoHandSword || thisGilesItemType == GilesItemType.TwoHandAxe || thisGilesItemType == GilesItemType.HandCrossbow || thisGilesItemType == GilesItemType.TwoHandBow) iThisNeedScore = settings.iNeedPointsToKeepWeapon; // Jewelry if (thisGilesItemType == GilesItemType.Ring || thisGilesItemType == GilesItemType.Amulet || thisGilesItemType == GilesItemType.FollowerEnchantress || thisGilesItemType == GilesItemType.FollowerScoundrel || thisGilesItemType == GilesItemType.FollowerTemplar) iThisNeedScore = settings.iNeedPointsToKeepJewelry; // Armor if (thisGilesItemType == GilesItemType.Mojo || thisGilesItemType == GilesItemType.Source || thisGilesItemType == GilesItemType.Quiver || thisGilesItemType == GilesItemType.Shield || thisGilesItemType == GilesItemType.Belt || thisGilesItemType == GilesItemType.Boots || thisGilesItemType == GilesItemType.Bracers || thisGilesItemType == GilesItemType.Chest || thisGilesItemType == GilesItemType.Cloak || thisGilesItemType == GilesItemType.Gloves || thisGilesItemType == GilesItemType.Helm || thisGilesItemType == GilesItemType.Pants || thisGilesItemType == GilesItemType.MightyBelt || thisGilesItemType == GilesItemType.Shoulders || thisGilesItemType == GilesItemType.SpiritStone || thisGilesItemType == GilesItemType.VoodooMask || thisGilesItemType == GilesItemType.WizardHat) iThisNeedScore = settings.iNeedPointsToKeepArmor; return Math.Round(iThisNeedScore); } // ********************************************************************************************** // ***** The bizarre mystery function to score your lovely items! ***** // ********************************************************************************************** private static double ValueThisItem(GilesCachedACDItem thisitem, GilesItemType thisGilesItemType) { double iTotalPoints = 0; bool bAbandonShip = true; double[] iThisItemsMaxStats = new double[TOTALSTATS]; double[] iThisItemsMaxPoints = new double[TOTALSTATS]; GilesBaseItemType thisGilesBaseType = DetermineBaseType(thisGilesItemType); // One Handed Weapons if (thisGilesItemType == GilesItemType.Axe || thisGilesItemType == GilesItemType.CeremonialKnife || thisGilesItemType == GilesItemType.Dagger || thisGilesItemType == GilesItemType.FistWeapon || thisGilesItemType == GilesItemType.Mace || thisGilesItemType == GilesItemType.MightyWeapon || thisGilesItemType == GilesItemType.Spear || thisGilesItemType == GilesItemType.Sword || thisGilesItemType == GilesItemType.Wand) { Array.Copy(iMaxWeaponOneHand, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iWeaponPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Two Handed Weapons if (thisGilesItemType == GilesItemType.TwoHandAxe || thisGilesItemType == GilesItemType.TwoHandDaibo || thisGilesItemType == GilesItemType.TwoHandMace || thisGilesItemType == GilesItemType.TwoHandMighty || thisGilesItemType == GilesItemType.TwoHandPolearm || thisGilesItemType == GilesItemType.TwoHandStaff || thisGilesItemType == GilesItemType.TwoHandSword) { Array.Copy(iMaxWeaponTwoHand, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iWeaponPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Ranged Weapons if (thisGilesItemType == GilesItemType.TwoHandCrossbow || thisGilesItemType == GilesItemType.TwoHandBow || thisGilesItemType == GilesItemType.HandCrossbow) { Array.Copy(iMaxWeaponRanged, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iWeaponPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); if (thisGilesItemType == GilesItemType.HandCrossbow) { iThisItemsMaxStats[TOTALDPS] -= 150; } bAbandonShip = false; } // Off-handed stuff // Mojo, Source, Quiver if (thisGilesItemType == GilesItemType.Mojo || thisGilesItemType == GilesItemType.Source || thisGilesItemType == GilesItemType.Quiver) { Array.Copy(iMaxOffHand, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Shields if (thisGilesItemType == GilesItemType.Shield) { Array.Copy(iMaxShield, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Jewelry // Ring if (thisGilesItemType == GilesItemType.Amulet) { Array.Copy(iMaxAmulet, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iJewelryPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Ring if (thisGilesItemType == GilesItemType.Ring) { Array.Copy(iMaxRing, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iJewelryPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Armor // Belt if (thisGilesItemType == GilesItemType.Belt) { Array.Copy(iMaxBelt, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Boots if (thisGilesItemType == GilesItemType.Boots) { Array.Copy(iMaxBoots, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Bracers if (thisGilesItemType == GilesItemType.Bracers) { Array.Copy(iMaxBracer, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Chest if (thisGilesItemType == GilesItemType.Chest) { Array.Copy(iMaxChest, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } if (thisGilesItemType == GilesItemType.Cloak) { Array.Copy(iMaxCloak, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Gloves if (thisGilesItemType == GilesItemType.Gloves) { Array.Copy(iMaxGloves, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Helm if (thisGilesItemType == GilesItemType.Helm) { Array.Copy(iMaxHelm, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Pants if (thisGilesItemType == GilesItemType.Pants) { Array.Copy(iMaxPants, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } if (thisGilesItemType == GilesItemType.MightyBelt) { Array.Copy(iMaxMightyBelt, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Shoulders if (thisGilesItemType == GilesItemType.Shoulders) { Array.Copy(iMaxShoulders, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } if (thisGilesItemType == GilesItemType.SpiritStone) { Array.Copy(iMaxSpiritStone, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } if (thisGilesItemType == GilesItemType.VoodooMask) { Array.Copy(iMaxVoodooMask, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Wizard Hat if (thisGilesItemType == GilesItemType.WizardHat) { Array.Copy(iMaxWizardHat, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iArmorPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Follower Items if (thisGilesItemType == GilesItemType.FollowerEnchantress || thisGilesItemType == GilesItemType.FollowerScoundrel || thisGilesItemType == GilesItemType.FollowerTemplar) { Array.Copy(iMaxFollower, iThisItemsMaxStats, TOTALSTATS); Array.Copy(iJewelryPointsAtMax, iThisItemsMaxPoints, TOTALSTATS); bAbandonShip = false; } // Constants for convenient stat names double[] iHadStat = new double[TOTALSTATS] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; double[] iHadPoints = new double[TOTALSTATS] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; double iSafeLifePercentage = 0; bool bSocketsCanReplacePrimaries = false; double iHighestScoringPrimary = 0; int iWhichPrimaryIsHighest = 0; double iAmountHighestScoringPrimary = 0; // Double safety check for unidentified items if (thisitem.IsUnidentified) bAbandonShip = true; // Make sure we got a valid item here, otherwise score it a big fat 0 if (bAbandonShip) { if (bFullAnalysis) Log("-- Invalid Item Type or Unidentified?"); return 0; } if (bFullAnalysis) Log("NEXT ITEM= " + thisitem.ThisRealName + " - " + thisitem.ThisInternalName + " [" + thisGilesBaseType.ToString() + " - " + thisGilesItemType.ToString() + "]"); double iGlobalMultiplier = 1; sValueItemStatString = ""; sJunkItemStatString = ""; // We loop through all of the stats, in a particular order. The order *IS* important, because it pulls up primary stats first, BEFORE other stats for (int i = 0; i <= (TOTALSTATS - 1); i++) { double iTempStatistic = 0; // Now we lookup each stat on this item we are scoring, and store it in the variable "iTempStatistic" - which is used for calculations further down switch (i) { case DEXTERITY: iTempStatistic = thisitem.Dexterity; break; case INTELLIGENCE: iTempStatistic = thisitem.Intelligence; break; case STRENGTH: iTempStatistic = thisitem.Strength; break; case VITALITY: iTempStatistic = thisitem.Vitality; break; case LIFEPERCENT: iTempStatistic = thisitem.LifePercent; break; case LIFEONHIT: iTempStatistic = thisitem.LifeOnHit; break; case LIFESTEAL: iTempStatistic = thisitem.LifeSteal; break; case LIFEREGEN: iTempStatistic = thisitem.HealthPerSecond; break; case MAGICFIND: iTempStatistic = thisitem.MagicFind; break; case GOLDFIND: iTempStatistic = thisitem.GoldFind; break; case MOVEMENTSPEED: iTempStatistic = thisitem.MovementSpeed; break; case PICKUPRADIUS: iTempStatistic = thisitem.PickUpRadius; break; case SOCKETS: iTempStatistic = thisitem.Sockets; break; case CRITCHANCE: iTempStatistic = thisitem.CritPercent; break; case CRITDAMAGE: iTempStatistic = thisitem.CritDamagePercent; break; case ATTACKSPEED: iTempStatistic = thisitem.AttackSpeedPercent; break; case MINDAMAGE: iTempStatistic = thisitem.MinDamage; break; case MAXDAMAGE: iTempStatistic = thisitem.MaxDamage; break; case BLOCKCHANCE: iTempStatistic = thisitem.BlockChance; break; case THORNS: iTempStatistic = thisitem.Thorns; break; case ALLRESIST: iTempStatistic = thisitem.ResistAll; break; case RANDOMRESIST: //intell -- sugerir if (thisitem.ResistArcane > iTempStatistic) iTempStatistic = thisitem.ResistArcane; if (thisitem.ResistCold > iTempStatistic) iTempStatistic = 0; //thisitem.ResistCold; if (thisitem.ResistFire > iTempStatistic) iTempStatistic = thisitem.ResistFire; if (thisitem.ResistHoly > iTempStatistic) iTempStatistic = thisitem.ResistHoly; if (thisitem.ResistLightning > iTempStatistic) iTempStatistic = 0; //thisitem.ResistLightning; if (thisitem.ResistPhysical > iTempStatistic) iTempStatistic = thisitem.ResistPhysical; if (thisitem.ResistPoison > iTempStatistic) iTempStatistic = 0; //thisitem.ResistPoison; break; case TOTALDPS: iTempStatistic = thisitem.WeaponDamagePerSecond; break; case ARMOR: iTempStatistic = thisitem.ArmorBonus; break; case MAXDISCIPLINE: iTempStatistic = thisitem.MaxDiscipline; break; case MAXMANA: iTempStatistic = thisitem.MaxMana; break; case ARCANECRIT: iTempStatistic = thisitem.ArcaneOnCrit; break; case MANAREGEN: iTempStatistic = thisitem.ManaRegen; break; case GLOBEBONUS: iTempStatistic = thisitem.GlobeBonus; break; } iHadStat[i] = iTempStatistic; iHadPoints[i] = 0; // Now we check that the current statistic in the "for" loop, actually exists on this item, and is a stat we are measuring (has >0 in the "max stats" array) if (iThisItemsMaxStats[i] > 0 && iTempStatistic > 0) { // Final bonus granted is an end-of-score multiplier. 1 = 100%, so all items start off with 100%, of course! double iFinalBonusGranted = 1; // Temp percent is what PERCENTAGE of the *MAXIMUM POSSIBLE STAT*, this stat is at. // Note that stats OVER the max will get a natural score boost, since this value will be over 1! double iTempPercent = iTempStatistic / iThisItemsMaxStats[i]; // Now multiply the "max points" value, by that percentage, as the start/basis of the scoring for this statistic double iTempPoints = iThisItemsMaxPoints[i] * iTempPercent; if (bFullAnalysis) Log("--- " + StatNames[i] + ": " + iTempStatistic.ToString() + " out of " + iThisItemsMaxStats[i].ToString() + " (" + iThisItemsMaxPoints[i].ToString() + " * " + iTempPercent.ToString() + " = " + iTempPoints.ToString() + ")"); // Check if this statistic is over the "bonus threshold" array value for this stat - if it is, then it gets a score bonus when over a certain % of max-stat if (iTempPercent > iBonusThreshold[i] && iBonusThreshold[i] > 0f) { iFinalBonusGranted += ((iTempPercent - iBonusThreshold[i]) * 0.9); } // We're going to store the life % stat here for quick-calculations against other stats. Don't edit this bit! if (i == LIFEPERCENT) { if (iThisItemsMaxStats[LIFEPERCENT] > 0) { iSafeLifePercentage = (iTempStatistic / iThisItemsMaxStats[LIFEPERCENT]); } else { iSafeLifePercentage = 0; } } // This *REMOVES* score from follower items for stats that followers don't care about if (thisGilesBaseType == GilesBaseItemType.FollowerItem && (i == CRITDAMAGE || i == LIFEONHIT || i == ALLRESIST)) iFinalBonusGranted -= 0.9; // Bonus 15% for being *at* the stat cap (ie - completely maxed out, or very very close to), but not for the socket stat (since sockets are usually 0 or 1!) if (i != SOCKETS) { if ((iTempStatistic / iThisItemsMaxStats[i]) >= 0.99) iFinalBonusGranted += 0.15; // Else bonus 10% for being in final 95% else if ((iTempStatistic / iThisItemsMaxStats[i]) >= 0.95) iFinalBonusGranted += 0.10; } // *************** // Socket handling // *************** // Sockets give special bonuses for certain items, depending how close to the max-socket-count it is for that item // It also enables bonus scoring for stats which usually rely on a high primary stat - since a socket can make up for a lack of a high primary (you can socket a +primary stat!) if (i == SOCKETS) { // Off-handers get less value from sockets if (thisGilesBaseType == GilesBaseItemType.Offhand) { iFinalBonusGranted -= 0.35; } // Chest if (thisGilesItemType == GilesItemType.Chest || thisGilesItemType == GilesItemType.Cloak) { if (iTempStatistic >= 2) { bSocketsCanReplacePrimaries = true; if (iTempStatistic >= 3) iFinalBonusGranted += 0.25; } } // Pants if (thisGilesItemType == GilesItemType.Pants) { if (iTempStatistic >= 2) { bSocketsCanReplacePrimaries = true; iFinalBonusGranted += 0.25; } } // Helmets can have a bonus for a socket since it gives amazing MF/GF if (iTempStatistic >= 1 && (thisGilesItemType == GilesItemType.Helm || thisGilesItemType == GilesItemType.WizardHat || thisGilesItemType == GilesItemType.VoodooMask || thisGilesItemType == GilesItemType.SpiritStone)) { bSocketsCanReplacePrimaries = true; } // And rings and amulets too if (iTempStatistic >= 1 && (thisGilesItemType == GilesItemType.Ring || thisGilesItemType == GilesItemType.Amulet)) { bSocketsCanReplacePrimaries = true; } } // Right, here's quite a long bit of code, but this is basically all about granting all sorts of bonuses based on primary stat values of all different ranges // For all item types *EXCEPT* weapons if (thisGilesBaseType != GilesBaseItemType.WeaponRange && thisGilesBaseType != GilesBaseItemType.WeaponOneHand && thisGilesBaseType != GilesBaseItemType.WeaponTwoHand) { double iSpecialBonus = 0; if (i > LIFEPERCENT) { // Knock off points for being particularly low if ((iTempStatistic / iThisItemsMaxStats[i]) < 0.2 && (iBonusThreshold[i] <= 0f || iBonusThreshold[i] >= 0.2)) iFinalBonusGranted -= 0.35; else if ((iTempStatistic / iThisItemsMaxStats[i]) < 0.4 && (iBonusThreshold[i] <= 0f || iBonusThreshold[i] >= 0.4)) iFinalBonusGranted -= 0.15; // Remove 80% if below minimum threshold if ((iTempStatistic / iThisItemsMaxStats[i]) < iMinimumThreshold[i] && iMinimumThreshold[i] > 0f) iFinalBonusGranted -= 0.8; // Primary stat/vitality minimums or zero-check reductions on other stats if (iStatMinimumPrimary[i] > 0) { // Remove 40% from all stats if there is no prime stat present or vitality/life present and this is below 90% of max if (((iTempStatistic / iThisItemsMaxStats[i]) < .90) && ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) < iStatMinimumPrimary[i]) && ((iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) < (iStatMinimumPrimary[i] + 0.1)) && ((iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) < iStatMinimumPrimary[i]) && ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) < iStatMinimumPrimary[i]) && (iSafeLifePercentage < (iStatMinimumPrimary[i] * 2.5)) && !bSocketsCanReplacePrimaries) { if (thisGilesItemType != GilesItemType.Ring && thisGilesItemType != GilesItemType.Amulet) iFinalBonusGranted -= 0.4; else iFinalBonusGranted -= 0.3; // And another 25% off for armor and all resist which are more useful with primaries, as long as not jewelry if ((i == ARMOR || i == ALLRESIST || i == RANDOMRESIST) && thisGilesItemType != GilesItemType.Ring && thisGilesItemType != GilesItemType.Amulet && !bSocketsCanReplacePrimaries) iFinalBonusGranted -= 0.15; } } else { // Almost no primary stats or health at all if (iHadStat[DEXTERITY] <= 60 && iHadStat[STRENGTH] <= 60 && iHadStat[INTELLIGENCE] <= 60 && iHadStat[VITALITY] <= 60 && iSafeLifePercentage < 0.9 && !bSocketsCanReplacePrimaries) { // So 35% off for all items except jewelry which is 20% off if (thisGilesItemType != GilesItemType.Ring && thisGilesItemType != GilesItemType.Amulet) { iFinalBonusGranted -= 0.35; // And another 25% off for armor and all resist which are more useful with primaries if (i == ARMOR || i == ALLRESIST) iFinalBonusGranted -= 0.15; } else { iFinalBonusGranted -= 0.20; } } } if (thisGilesBaseType == GilesBaseItemType.Armor || thisGilesBaseType == GilesBaseItemType.Jewelry) { // Grant a 50% bonus to stats if a primary is above 200 AND (vitality above 200 or life% within 90% max) if ((iHadStat[DEXTERITY] > 200 || iHadStat[STRENGTH] > 200 || iHadStat[INTELLIGENCE] > 200) && (iHadStat[VITALITY] > 200 || iSafeLifePercentage > .97)) { if (0.5 > iSpecialBonus) iSpecialBonus = 0.5; } // Else grant a 40% bonus to stats if a primary is above 200 if (iHadStat[DEXTERITY] > 200 || iHadStat[STRENGTH] > 200 || iHadStat[INTELLIGENCE] > 200) { if (0.4 > iSpecialBonus) iSpecialBonus = 0.4; } // Grant a 30% bonus if vitality > 200 or life percent within 90% of max if (iHadStat[VITALITY] > 200 || iSafeLifePercentage > .97) { if (0.3 > iSpecialBonus) iSpecialBonus = 0.3; } } // Checks for various primary & health levels if ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > .85 || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > .85 || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > .85) { if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .6 || iSafeLifePercentage > .90) { if (0.5 > iSpecialBonus) iSpecialBonus = 0.5; } else if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .35 || iSafeLifePercentage > .85) { if (0.4 > iSpecialBonus) iSpecialBonus = 0.4; } else { if (0.2 > iSpecialBonus) iSpecialBonus = 0.2; } } if ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > .75 || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > .75 || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > .75) { if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .6 || iSafeLifePercentage > .90) { if (0.35 > iSpecialBonus) iSpecialBonus = 0.35; } else if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .35 || iSafeLifePercentage > .85) { if (0.30 > iSpecialBonus) iSpecialBonus = 0.30; } else { if (0.15 > iSpecialBonus) iSpecialBonus = 0.15; } } if ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > .65 || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > .65 || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > .65) { if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .6 || iSafeLifePercentage > .90) { if (0.26 > iSpecialBonus) iSpecialBonus = 0.26; } else if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .35 || iSafeLifePercentage > .85) { if (0.22 > iSpecialBonus) iSpecialBonus = 0.22; } else { if (0.11 > iSpecialBonus) iSpecialBonus = 0.11; } } if ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > .55 || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > .55 || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > .55) { if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .6 || iSafeLifePercentage > .90) { if (0.18 > iSpecialBonus) iSpecialBonus = 0.18; } else if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .35 || iSafeLifePercentage > .85) { if (0.14 > iSpecialBonus) iSpecialBonus = 0.14; } else { if (0.08 > iSpecialBonus) iSpecialBonus = 0.08; } } if ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > .5 || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > .5 || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > .5) { if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .6 || iSafeLifePercentage > .90) { if (0.12 > iSpecialBonus) iSpecialBonus = 0.12; } else if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .35 || iSafeLifePercentage > .85) { if (0.05 > iSpecialBonus) iSpecialBonus = 0.05; } else { if (0.03 > iSpecialBonus) iSpecialBonus = 0.03; } } if (thisGilesItemType == GilesItemType.Ring || thisGilesItemType == GilesItemType.Amulet) { if ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > .4 || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > .4 || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > .4) { if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .6 || iSafeLifePercentage > .90) { if (0.10 > iSpecialBonus) iSpecialBonus = 0.10; } else if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .35 || iSafeLifePercentage > .85) { if (0.08 > iSpecialBonus) iSpecialBonus = 0.08; } else { if (0.05 > iSpecialBonus) iSpecialBonus = 0.05; } } } if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .8 || iSafeLifePercentage > .98) { if (0.20 > iSpecialBonus) iSpecialBonus = 0.20; } if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .7 || iSafeLifePercentage > .95) { if (0.16 > iSpecialBonus) iSpecialBonus = 0.16; } if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .6 || iSafeLifePercentage > .92) { if (0.12 > iSpecialBonus) iSpecialBonus = 0.12; } if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .55 || iSafeLifePercentage > .89) { if (0.07 > iSpecialBonus) iSpecialBonus = 0.07; } else if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .5 || iSafeLifePercentage > .87) { if (0.05 > iSpecialBonus) iSpecialBonus = 0.05; } else if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > .45 || iSafeLifePercentage > .86) { if (0.02 > iSpecialBonus) iSpecialBonus = 0.02; } } // This stat is one after life percent stat // Shields get less of a special bonus from high prime stats if (thisGilesItemType == GilesItemType.Shield) iSpecialBonus *= 0.7; if (bFullAnalysis) Log("------- special bonus =" + iSpecialBonus.ToString()); iFinalBonusGranted += iSpecialBonus; } // NOT A WEAPON!? //intell -- sugerir if (i == LIFESTEAL && thisGilesItemType == GilesItemType.MightyBelt) iFinalBonusGranted += 0.3; // Knock off points for being particularly low if ((iTempStatistic / iThisItemsMaxStats[i]) < iMinimumThreshold[i] && iMinimumThreshold[i] > 0f) iFinalBonusGranted -= 0.35; // Grant a 20% bonus to vitality or Life%, for being paired with any prime stat above minimum threshold +.1 if (((i == VITALITY && (iTempStatistic / iThisItemsMaxStats[VITALITY]) > iMinimumThreshold[VITALITY]) || i == LIFEPERCENT && (iTempStatistic / iThisItemsMaxStats[LIFEPERCENT]) > iMinimumThreshold[LIFEPERCENT]) && ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > (iMinimumThreshold[DEXTERITY] + 0.1) || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > (iMinimumThreshold[STRENGTH] + 0.1) || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > (iMinimumThreshold[INTELLIGENCE] + 0.1))) iFinalBonusGranted += 0.2; // Blue item point reduction for non-weapons if (thisitem.ThisQuality < ItemQuality.Rare4 && (thisGilesBaseType == GilesBaseItemType.Armor || thisGilesBaseType == GilesBaseItemType.Offhand || thisGilesBaseType == GilesBaseItemType.Jewelry || thisGilesBaseType == GilesBaseItemType.FollowerItem) && ((iTempStatistic / iThisItemsMaxStats[i]) < 0.88)) iFinalBonusGranted -= 0.9; // Special all-resist bonuses if (i == ALLRESIST) { // Shields with < 60% max all resist, lost some all resist score if (thisGilesItemType == GilesItemType.Shield && (iTempStatistic / iThisItemsMaxStats[i]) <= 0.6) iFinalBonusGranted -= 0.30; double iSpecialBonus = 0; // All resist gets a special bonus if paired with good strength and some vitality if ((iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > 0.7 && (iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > 0.3) if (0.45 > iSpecialBonus) iSpecialBonus = 0.45; // All resist gets a smaller special bonus if paired with good dexterity and some vitality if ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > 0.7 && (iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > 0.3) if (0.35 > iSpecialBonus) iSpecialBonus = 0.35; // All resist gets a slight special bonus if paired with good intelligence and some vitality if ((iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > 0.7 && (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > 0.3) if (0.25 > iSpecialBonus) iSpecialBonus = 0.25; // Smaller bonuses for smaller stats // All resist gets a special bonus if paired with good strength and some vitality if ((iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > 0.55 && (iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > 0.3) if (0.45 > iSpecialBonus) iSpecialBonus = 0.20; // All resist gets a smaller special bonus if paired with good dexterity and some vitality if ((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > 0.55 && (iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > 0.3) if (0.35 > iSpecialBonus) iSpecialBonus = 0.15; // All resist gets a slight special bonus if paired with good intelligence and some vitality if ((iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > 0.55 && (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > 0.3) if (0.25 > iSpecialBonus) iSpecialBonus = 0.10; // This stat is one after life percent stat iFinalBonusGranted += iSpecialBonus; // Global bonus to everything if ((iThisItemsMaxStats[i] - iTempStatistic) < 10.2f) iGlobalMultiplier += 0.05; } // All resist special bonuses if (thisGilesItemType != GilesItemType.Ring && thisGilesItemType != GilesItemType.Amulet) { // Shields get 10% less on everything if (thisGilesItemType == GilesItemType.Shield) iFinalBonusGranted -= 0.10; // Prime stat gets a 20% bonus if 50 from max possible if ((i == DEXTERITY || i == STRENGTH || i == INTELLIGENCE || i == VITALITY) && (iThisItemsMaxStats[i] - iTempStatistic) < 50.5f) iFinalBonusGranted += 0.25; // Reduce a prime stat by 75% if less than 100 *OR* less than 50% max if ((i == DEXTERITY || i == STRENGTH || i == INTELLIGENCE) && (iTempStatistic < 100 || ((iTempStatistic / iThisItemsMaxStats[i]) < 0.5))) iFinalBonusGranted -= 0.75; // Reduce a vitality/life% stat by 60% if less than 80 vitality/less than 60% max possible life% if ((i == VITALITY && iTempStatistic < 80) || (i == LIFEPERCENT && ((iTempStatistic / iThisItemsMaxStats[LIFEPERCENT]) < 0.6))) iFinalBonusGranted -= 0.6; // Grant 10% to any 4 main stat above 200 if ((i == DEXTERITY || i == STRENGTH || i == INTELLIGENCE || i == VITALITY) && iTempStatistic > 200) iFinalBonusGranted += 0.1; // ************************************************* // Special stat handling stuff for non-jewelry types // ************************************************* // Within 2 block chance if (i == BLOCKCHANCE && (iThisItemsMaxStats[i] - iTempStatistic) < 2.3f) iFinalBonusGranted += 1; // Within final 5 gold find if (i == GOLDFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 5.3f) { iFinalBonusGranted += 0.04; // Even bigger bonus if got prime stat & vit if (((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > iMinimumThreshold[DEXTERITY] || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > iMinimumThreshold[STRENGTH] || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > iMinimumThreshold[INTELLIGENCE]) && (iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > iMinimumThreshold[VITALITY]) iFinalBonusGranted += 0.02; } // Within final 3 gold find if (i == GOLDFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 3.3f) { iFinalBonusGranted += 0.04; } // Within final 2 gold find if (i == GOLDFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 2.3f) { iFinalBonusGranted += 0.05; } // Within final 3 magic find if (i == MAGICFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 3.3f) iFinalBonusGranted += 0.08; // Within final 2 magic find if (i == MAGICFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 2.3f) { iFinalBonusGranted += 0.04; // Even bigger bonus if got prime stat & vit if (((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > iMinimumThreshold[DEXTERITY] || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > iMinimumThreshold[STRENGTH] || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > iMinimumThreshold[INTELLIGENCE]) && (iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > iMinimumThreshold[VITALITY]) iFinalBonusGranted += 0.03; } // Within final magic find if (i == MAGICFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 1.3f) { iFinalBonusGranted += 0.05; } // Within final 10 all resist if (i == ALLRESIST && (iThisItemsMaxStats[i] - iTempStatistic) < 10.2f) { iFinalBonusGranted += 0.05; // Even bigger bonus if got prime stat & vit if (((iHadStat[DEXTERITY] / iThisItemsMaxStats[DEXTERITY]) > iMinimumThreshold[DEXTERITY] || (iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > iMinimumThreshold[STRENGTH] || (iHadStat[INTELLIGENCE] / iThisItemsMaxStats[INTELLIGENCE]) > iMinimumThreshold[INTELLIGENCE]) && (iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY]) > iMinimumThreshold[VITALITY]) iFinalBonusGranted += 0.20; } // Within final 50 armor if (i == ARMOR && (iThisItemsMaxStats[i] - iTempStatistic) < 50.2f) { iFinalBonusGranted += 0.10; if ((iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > iMinimumThreshold[STRENGTH]) iFinalBonusGranted += 0.10; } // Within final 15 armor if (i == ARMOR && (iThisItemsMaxStats[i] - iTempStatistic) < 15.2f) iFinalBonusGranted += 0.15; // Within final 5 critical hit damage if (i == CRITDAMAGE && (iThisItemsMaxStats[i] - iTempStatistic) < 5.2f) iFinalBonusGranted += 0.25; // More than 2.5 crit chance out if (i == CRITCHANCE && (iThisItemsMaxStats[i] - iTempStatistic) > 2.45f) iFinalBonusGranted -= 0.35; // More than 20 crit damage out if (i == CRITDAMAGE && (iThisItemsMaxStats[i] - iTempStatistic) > 19.95f) iFinalBonusGranted -= 0.35; // More than 2 attack speed out if (i == ATTACKSPEED && (iThisItemsMaxStats[i] - iTempStatistic) > 1.95f) iFinalBonusGranted -= 0.35; // More than 2 move speed if (i == MOVEMENTSPEED && (iThisItemsMaxStats[i] - iTempStatistic) > 1.95f) iFinalBonusGranted -= 0.35; // More than 5 gold find out if (i == GOLDFIND && (iThisItemsMaxStats[i] - iTempStatistic) > 5.2f) iFinalBonusGranted -= 0.40; // More than 8 gold find out if (i == GOLDFIND && (iThisItemsMaxStats[i] - iTempStatistic) > 8.2f) iFinalBonusGranted -= 0.1; // More than 5 magic find out if (i == MAGICFIND && (iThisItemsMaxStats[i] - iTempStatistic) > 5.2f) iFinalBonusGranted -= 0.40; // More than 7 magic find out if (i == MAGICFIND && (iThisItemsMaxStats[i] - iTempStatistic) > 7.2f) iFinalBonusGranted -= 0.1; // More than 20 all resist out if (i == ALLRESIST && (iThisItemsMaxStats[i] - iTempStatistic) > 20.2f) iFinalBonusGranted -= 0.50; // More than 30 all resist out if (i == ALLRESIST && (iThisItemsMaxStats[i] - iTempStatistic) > 30.2f) iFinalBonusGranted -= 0.20; } // And now for jewelry checks... else { // Global bonus to everything if jewelry has an all resist above 50% if (i == ALLRESIST && (iTempStatistic / iThisItemsMaxStats[i]) > 0.5) iGlobalMultiplier += 0.08; // Within final 10 all resist if (i == ALLRESIST && (iThisItemsMaxStats[i] - iTempStatistic) < 10.2f) iFinalBonusGranted += 0.10; // Within final 5 critical hit damage if (i == CRITDAMAGE && (iThisItemsMaxStats[i] - iTempStatistic) < 5.2f) iFinalBonusGranted += 0.25; // Within 3 block chance if (i == BLOCKCHANCE && (iThisItemsMaxStats[i] - iTempStatistic) < 3.3f) iFinalBonusGranted += 0.15; // Reduce a prime stat by 60% if less than 60 if ((i == DEXTERITY || i == STRENGTH || i == INTELLIGENCE) && (iTempStatistic < 60 || ((iTempStatistic / iThisItemsMaxStats[i]) < 0.3))) iFinalBonusGranted -= 0.6; // Reduce a vitality/life% stat by 50% if less than 50 vitality/less than 40% max possible life% if ((i == VITALITY && iTempStatistic < 50) || (i == LIFEPERCENT && ((iTempStatistic / iThisItemsMaxStats[LIFEPERCENT]) < 0.4))) iFinalBonusGranted -= 0.5; // Grant 20% to any 4 main stat above 150 if ((i == DEXTERITY || i == STRENGTH || i == INTELLIGENCE || i == VITALITY) && iTempStatistic > 150) iFinalBonusGranted += 0.2; // *************************************** // Special stat handling stuff for jewelry // *************************************** if (thisGilesItemType == GilesItemType.Ring) { // Prime stat gets a 25% bonus if 30 from max possible if ((i == DEXTERITY || i == STRENGTH || i == INTELLIGENCE || i == VITALITY) && (iThisItemsMaxStats[i] - iTempStatistic) < 30.5f) iFinalBonusGranted += 0.25; // Within final 5 magic find if (i == MAGICFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 5.2f) iFinalBonusGranted += 0.4; // Within final 5 gold find if (i == GOLDFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 5.2f) iFinalBonusGranted += 0.35; // Within final 45 life on hit if (i == LIFEONHIT && (iThisItemsMaxStats[i] - iTempStatistic) < 45.2f) iFinalBonusGranted += 1.2; // Within final 50 armor if (i == ARMOR && (iThisItemsMaxStats[i] - iTempStatistic) < 50.2f) { iFinalBonusGranted += 0.30; if ((iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > iMinimumThreshold[STRENGTH]) iFinalBonusGranted += 0.30; } // Within final 15 armor if (i == ARMOR && (iThisItemsMaxStats[i] - iTempStatistic) < 15.2f) iFinalBonusGranted += 0.20; // More than 2.5 crit chance out if (i == CRITCHANCE && (iThisItemsMaxStats[i] - iTempStatistic) > 5.55f) iFinalBonusGranted -= 0.20; // More than 20 crit damage out if (i == CRITDAMAGE && (iThisItemsMaxStats[i] - iTempStatistic) > 19.95f) iFinalBonusGranted -= 0.20; // More than 2 attack speed out if (i == ATTACKSPEED && (iThisItemsMaxStats[i] - iTempStatistic) > 1.95f) iFinalBonusGranted -= 0.20; // More than 15 gold find out if (i == GOLDFIND && (iThisItemsMaxStats[i] - iTempStatistic) > 15.2f) iFinalBonusGranted -= 0.1; // More than 15 magic find out if (i == MAGICFIND && (iThisItemsMaxStats[i] - iTempStatistic) > 15.2f) iFinalBonusGranted -= 0.1; // More than 30 all resist out if (i == ALLRESIST && (iThisItemsMaxStats[i] - iTempStatistic) > 20.2f) iFinalBonusGranted -= 0.1; // More than 40 all resist out if (i == ALLRESIST && (iThisItemsMaxStats[i] - iTempStatistic) > 30.2f) iFinalBonusGranted -= 0.1; } else { // Prime stat gets a 25% bonus if 60 from max possible if ((i == DEXTERITY || i == STRENGTH || i == INTELLIGENCE || i == VITALITY) && (iThisItemsMaxStats[i] - iTempStatistic) < 60.5f) iFinalBonusGranted += 0.25; // Within final 10 magic find if (i == MAGICFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 10.2f) iFinalBonusGranted += 0.4; // Within final 10 gold find if (i == GOLDFIND && (iThisItemsMaxStats[i] - iTempStatistic) < 10.2f) iFinalBonusGranted += 0.35; // Within final 40 life on hit if (i == LIFEONHIT && (iThisItemsMaxStats[i] - iTempStatistic) < 40.2f) iFinalBonusGranted += 1.2; // Within final 50 armor if (i == ARMOR && (iThisItemsMaxStats[i] - iTempStatistic) < 50.2f) { iFinalBonusGranted += 0.30; if ((iHadStat[STRENGTH] / iThisItemsMaxStats[STRENGTH]) > iMinimumThreshold[STRENGTH]) iFinalBonusGranted += 0.30; } // Within final 15 armor if (i == ARMOR && (iThisItemsMaxStats[i] - iTempStatistic) < 15.2f) iFinalBonusGranted += 0.20; // More than 2.5 crit chance out if (i == CRITCHANCE && (iThisItemsMaxStats[i] - iTempStatistic) > 5.55f) iFinalBonusGranted -= 0.20; // More than 20 crit damage out if (i == CRITDAMAGE && (iThisItemsMaxStats[i] - iTempStatistic) > 19.95f) iFinalBonusGranted -= 0.20; // More than 2 attack speed out if (i == ATTACKSPEED && (iThisItemsMaxStats[i] - iTempStatistic) > 1.95f) iFinalBonusGranted -= 0.20; // More than 15 gold find out if (i == GOLDFIND && (iThisItemsMaxStats[i] - iTempStatistic) > 15.2f) iFinalBonusGranted -= 0.1; // More than 15 magic find out if (i == MAGICFIND && (iThisItemsMaxStats[i] - iTempStatistic) > 15.2f) iFinalBonusGranted -= 0.1; // More than 30 all resist out if (i == ALLRESIST && (iThisItemsMaxStats[i] - iTempStatistic) > 20.2f) iFinalBonusGranted -= 0.1; // More than 40 all resist out if (i == ALLRESIST && (iThisItemsMaxStats[i] - iTempStatistic) > 30.2f) iFinalBonusGranted -= 0.1; } } // ***************************** // All the "set to 0" checks now // ***************************** // Disable specific primary stat scoring for certain class-specific item types if ((thisGilesItemType == GilesItemType.VoodooMask || thisGilesItemType == GilesItemType.WizardHat || thisGilesItemType == GilesItemType.Wand || thisGilesItemType == GilesItemType.CeremonialKnife || thisGilesItemType == GilesItemType.Mojo || thisGilesItemType == GilesItemType.Source) && (i == STRENGTH || i == DEXTERITY)) iFinalBonusGranted = 0; if ((thisGilesItemType == GilesItemType.Quiver || thisGilesItemType == GilesItemType.HandCrossbow || thisGilesItemType == GilesItemType.Cloak || thisGilesItemType == GilesItemType.SpiritStone || thisGilesItemType == GilesItemType.TwoHandDaibo || thisGilesItemType == GilesItemType.FistWeapon) && (i == STRENGTH || i == INTELLIGENCE)) iFinalBonusGranted = 0; if ((thisGilesItemType == GilesItemType.MightyBelt || thisGilesItemType == GilesItemType.MightyWeapon || thisGilesItemType == GilesItemType.TwoHandMighty) && (i == DEXTERITY || i == INTELLIGENCE)) iFinalBonusGranted = 0; // Remove unwanted follower stats for specific follower types if (thisGilesItemType == GilesItemType.FollowerEnchantress && (i == STRENGTH || i == DEXTERITY)) iFinalBonusGranted = 0; if (thisGilesItemType == GilesItemType.FollowerEnchantress && (i == INTELLIGENCE || i == VITALITY)) iFinalBonusGranted -= 0.4; if (thisGilesItemType == GilesItemType.FollowerScoundrel && (i == STRENGTH || i == INTELLIGENCE)) iFinalBonusGranted = 0; if (thisGilesItemType == GilesItemType.FollowerScoundrel && (i == DEXTERITY || i == VITALITY)) iFinalBonusGranted -= 0.4; if (thisGilesItemType == GilesItemType.FollowerTemplar && (i == DEXTERITY || i == INTELLIGENCE)) iFinalBonusGranted = 0; if (thisGilesItemType == GilesItemType.FollowerTemplar && (i == STRENGTH || i == VITALITY)) iFinalBonusGranted -= 0.4; // Attack speed is always on a quiver so forget it if ((thisGilesItemType == GilesItemType.Quiver) && (i == ATTACKSPEED)) iFinalBonusGranted = 0; // Single resists worth nothing without all-resist if (i == RANDOMRESIST && (iHadStat[ALLRESIST] / iThisItemsMaxStats[ALLRESIST]) < iMinimumThreshold[ALLRESIST]) iFinalBonusGranted = 0; if (iFinalBonusGranted < 0) iFinalBonusGranted = 0; // *************************** // Grant the final bonus total // *************************** iTempPoints *= iFinalBonusGranted; // If it's a primary stat, log the highest scoring primary... else add these points to the running total if (i == DEXTERITY || i == STRENGTH || i == INTELLIGENCE) { if (bFullAnalysis) Log("---- +" + iTempPoints.ToString() + " (*" + iFinalBonusGranted.ToString() + " multiplier) [MUST BE MAX STAT SCORE TO COUNT]"); if (iTempPoints > iHighestScoringPrimary) { iHighestScoringPrimary = iTempPoints; iWhichPrimaryIsHighest = i; iAmountHighestScoringPrimary = iTempStatistic; } } else { if (bFullAnalysis) Log("---- +" + iTempPoints.ToString() + " score (*" + iFinalBonusGranted.ToString() + " multiplier)"); iTotalPoints += iTempPoints; } iHadPoints[i] = iTempPoints; // For item logs if (i != DEXTERITY && i != STRENGTH && i != INTELLIGENCE) { if (sValueItemStatString != "") sValueItemStatString += ". "; sValueItemStatString += StatNames[i] + "=" + Math.Round(iTempStatistic).ToString(); if (sJunkItemStatString != "") sJunkItemStatString += ". "; sJunkItemStatString += StatNames[i] + "=" + Math.Round(iTempStatistic).ToString(); } } } // End of main 0-TOTALSTATS stat loop int iTotalRequirements; // Now add on one of the three primary stat scores, whichever was higher if (iHighestScoringPrimary > 0) { // Give a 30% of primary-stat-score-possible bonus to the primary scoring if paired with a good amount of life % or vitality if ((iHadStat[VITALITY] / iThisItemsMaxStats[VITALITY] > (iMinimumThreshold[VITALITY] + 0.1)) || iSafeLifePercentage > 0.85) iHighestScoringPrimary += iThisItemsMaxPoints[iWhichPrimaryIsHighest] * 0.3; // Reduce a primary a little if there is no vitality or life if ((iHadStat[VITALITY] < 40) || iSafeLifePercentage < 0.7) iHighestScoringPrimary *= 0.8; iTotalPoints += iHighestScoringPrimary; sValueItemStatString = StatNames[iWhichPrimaryIsHighest] + "=" + Math.Round(iAmountHighestScoringPrimary).ToString() + ". " + sValueItemStatString; sJunkItemStatString = StatNames[iWhichPrimaryIsHighest] + "=" + Math.Round(iAmountHighestScoringPrimary).ToString() + ". " + sJunkItemStatString; } if (bFullAnalysis) Log("--- +" + iTotalPoints.ToString() + " total score pre-special reductions. (GM=" + iGlobalMultiplier.ToString() + ")"); // Global multiplier iTotalPoints *= iGlobalMultiplier; // 2 handed weapons and ranged weapons lose a large score for low DPS if (thisGilesBaseType == GilesBaseItemType.WeaponRange || thisGilesBaseType == GilesBaseItemType.WeaponTwoHand) { if ((iHadStat[TOTALDPS] / iThisItemsMaxStats[TOTALDPS]) <= 0.7) iTotalPoints *= 0.75; } // Weapons should get a nice 15% bonus score for having very high primaries if (thisGilesBaseType == GilesBaseItemType.WeaponRange || thisGilesBaseType == GilesBaseItemType.WeaponOneHand || thisGilesBaseType == GilesBaseItemType.WeaponTwoHand) { if (iHighestScoringPrimary > 0 && (iHighestScoringPrimary >= iThisItemsMaxPoints[iWhichPrimaryIsHighest] * 0.9)) { iTotalPoints *= 1.15; } // And an extra 15% for a very high vitality if (iHadStat[VITALITY] > 0 && (iHadStat[VITALITY] >= iThisItemsMaxPoints[VITALITY] * 0.9)) { iTotalPoints *= 1.15; } // And an extra 15% for a very high life-on-hit if (iHadStat[LIFEONHIT] > 0 && (iHadStat[LIFEONHIT] >= iThisItemsMaxPoints[LIFEONHIT] * 0.9)) { iTotalPoints *= 1.15; } } // Shields if (thisGilesItemType == GilesItemType.Shield) { // Strength/Dex based shield calculations if (iWhichPrimaryIsHighest == STRENGTH || iWhichPrimaryIsHighest == DEXTERITY) { if (iHadStat[BLOCKCHANCE] < 20) { iTotalPoints *= 0.7; } else if (iHadStat[BLOCKCHANCE] < 25) { iTotalPoints *= 0.9; } } // Intelligence/no primary based shields else { if (iHadStat[BLOCKCHANCE] < 28) iTotalPoints -= iHadPoints[BLOCKCHANCE]; } } // Quivers if (thisGilesItemType == GilesItemType.Quiver) { iTotalRequirements = 0; if (iHadStat[DEXTERITY] >= 100) iTotalRequirements++; else iTotalRequirements -= 3; if (iHadStat[DEXTERITY] >= 160) iTotalRequirements++; if (iHadStat[DEXTERITY] >= 250) iTotalRequirements++; if (iHadStat[ATTACKSPEED] < 14) iTotalRequirements -= 2; if (iHadStat[VITALITY] >= 70 || iSafeLifePercentage >= 0.85) iTotalRequirements++; else iTotalRequirements--; if (iHadStat[VITALITY] >= 260) iTotalRequirements++; if (iHadStat[MAXDISCIPLINE] >= 8) iTotalRequirements++; if (iHadStat[MAXDISCIPLINE] >= 10) iTotalRequirements++; if (iHadStat[SOCKETS] >= 1) iTotalRequirements++; if (iHadStat[CRITCHANCE] >= 6) iTotalRequirements++; if (iHadStat[CRITCHANCE] >= 8) iTotalRequirements++; if (iHadStat[LIFEPERCENT] >= 8) iTotalRequirements++; if (iHadStat[MAGICFIND] >= 18) iTotalRequirements++; if (iTotalRequirements < 4) iTotalPoints *= 0.4; else if (iTotalRequirements < 5) iTotalPoints *= 0.5; if (iTotalRequirements >= 7) iTotalPoints *= 1.2; } // Mojos and Sources if (thisGilesItemType == GilesItemType.Source || thisGilesItemType == GilesItemType.Mojo) { iTotalRequirements = 0; if (iHadStat[INTELLIGENCE] >= 100) iTotalRequirements++; else if (iHadStat[INTELLIGENCE] < 80) iTotalRequirements -= 3; else if (iHadStat[INTELLIGENCE] < 100) iTotalRequirements -= 1; if (iHadStat[INTELLIGENCE] >= 160) iTotalRequirements++; if (iHadStat[MAXDAMAGE] >= 250) iTotalRequirements++; else iTotalRequirements -= 2; if (iHadStat[MAXDAMAGE] >= 340) iTotalRequirements++; if (iHadStat[MINDAMAGE] >= 50) iTotalRequirements++; else iTotalRequirements--; if (iHadStat[MINDAMAGE] >= 85) iTotalRequirements++; if (iHadStat[VITALITY] >= 70) iTotalRequirements++; if (iHadStat[SOCKETS] >= 1) iTotalRequirements++; if (iHadStat[CRITCHANCE] >= 6) iTotalRequirements++; if (iHadStat[CRITCHANCE] >= 8) iTotalRequirements++; if (iHadStat[LIFEPERCENT] >= 8) iTotalRequirements++; if (iHadStat[MAGICFIND] >= 15) iTotalRequirements++; if (iHadStat[MAXMANA] >= 60) iTotalRequirements++; if (iHadStat[ARCANECRIT] >= 8) iTotalRequirements++; if (iHadStat[ARCANECRIT] >= 10) iTotalRequirements++; if (iTotalRequirements < 4) iTotalPoints *= 0.4; else if (iTotalRequirements < 5) iTotalPoints *= 0.5; if (iTotalRequirements >= 8) iTotalPoints *= 1.2; } // Chests/cloaks/pants without a socket lose 17% of total score if ((thisGilesItemType == GilesItemType.Chest || thisGilesItemType == GilesItemType.Cloak || thisGilesItemType == GilesItemType.Pants) && iHadStat[SOCKETS] == 0) iTotalPoints *= 0.83; // Boots with no movement speed get reduced score if ((thisGilesItemType == GilesItemType.Boots) && iHadStat[MOVEMENTSPEED] <= 6) iTotalPoints *= 0.75; // Helmets if (thisGilesItemType == GilesItemType.Helm || thisGilesItemType == GilesItemType.WizardHat || thisGilesItemType == GilesItemType.VoodooMask || thisGilesItemType == GilesItemType.SpiritStone) { // Helmets without a socket lose 20% of total score, and most of any MF/GF bonus if (iHadStat[SOCKETS] == 0) { iTotalPoints *= 0.8; if (iHadStat[MAGICFIND] > 0 || iHadStat[GOLDFIND] > 0) { if (iHadStat[MAGICFIND] > 0 && iHadStat[GOLDFIND] > 0) iTotalPoints -= ((iHadPoints[MAGICFIND] * 0.25) + (iHadPoints[GOLDFIND] * 0.25)); else iTotalPoints -= ((iHadPoints[MAGICFIND] * 0.65) + (iHadPoints[GOLDFIND] * 0.65)); } } } // Gold-find and pickup radius combined if ((iHadStat[GOLDFIND] / iThisItemsMaxStats[GOLDFIND] > 0.55) && (iHadStat[PICKUPRADIUS] / iThisItemsMaxStats[PICKUPRADIUS] > 0.5)) iTotalPoints += (((iThisItemsMaxPoints[PICKUPRADIUS] + iThisItemsMaxPoints[GOLDFIND]) / 2) * 0.25); // All-resist and pickup radius combined if ((iHadStat[ALLRESIST] / iThisItemsMaxStats[ALLRESIST] > 0.55) && (iHadStat[PICKUPRADIUS] > 0)) iTotalPoints += (((iThisItemsMaxPoints[PICKUPRADIUS] + iThisItemsMaxPoints[ALLRESIST]) / 2) * 0.65); // Special crit hit/crit chance/attack speed combos double dBestFinalBonus = 1d; if ((iHadStat[CRITCHANCE] > (iThisItemsMaxStats[CRITCHANCE] * 0.8)) && (iHadStat[CRITDAMAGE] > (iThisItemsMaxStats[CRITDAMAGE] * 0.8)) && (iHadStat[ATTACKSPEED] > (iThisItemsMaxStats[ATTACKSPEED] * 0.8))) { if (dBestFinalBonus < 3.2 && thisGilesItemType != GilesItemType.Quiver) dBestFinalBonus = 3.2; } if ((iHadStat[CRITCHANCE] > (iThisItemsMaxStats[CRITCHANCE] * 0.8)) && (iHadStat[CRITDAMAGE] > (iThisItemsMaxStats[CRITDAMAGE] * 0.8))) { if (dBestFinalBonus < 2.3) dBestFinalBonus = 2.3; } if ((iHadStat[CRITCHANCE] > (iThisItemsMaxStats[CRITCHANCE] * 0.8)) && (iHadStat[ATTACKSPEED] > (iThisItemsMaxStats[ATTACKSPEED] * 0.8))) { if (dBestFinalBonus < 2.1 && thisGilesItemType != GilesItemType.Quiver) dBestFinalBonus = 2.1; } if ((iHadStat[CRITDAMAGE] > (iThisItemsMaxStats[CRITDAMAGE] * 0.8)) && (iHadStat[ATTACKSPEED] > (iThisItemsMaxStats[ATTACKSPEED] * 0.8))) { if (dBestFinalBonus < 1.8 && thisGilesItemType != GilesItemType.Quiver) dBestFinalBonus = 1.8; } if ((iHadStat[CRITCHANCE] > (iThisItemsMaxStats[CRITCHANCE] * 0.65)) && (iHadStat[CRITDAMAGE] > (iThisItemsMaxStats[CRITDAMAGE] * 0.65)) && (iHadStat[ATTACKSPEED] > (iThisItemsMaxStats[ATTACKSPEED] * 0.65))) { if (dBestFinalBonus < 2.1 && thisGilesItemType != GilesItemType.Quiver) dBestFinalBonus = 2.1; } if ((iHadStat[CRITCHANCE] > (iThisItemsMaxStats[CRITCHANCE] * 0.65)) && (iHadStat[CRITDAMAGE] > (iThisItemsMaxStats[CRITDAMAGE] * 0.65))) { if (dBestFinalBonus < 1.9) dBestFinalBonus = 1.9; } if ((iHadStat[CRITCHANCE] > (iThisItemsMaxStats[CRITCHANCE] * 0.65)) && (iHadStat[ATTACKSPEED] > (iThisItemsMaxStats[ATTACKSPEED] * 0.65))) { if (dBestFinalBonus < 1.7 && thisGilesItemType != GilesItemType.Quiver) dBestFinalBonus = 1.7; } if ((iHadStat[CRITDAMAGE] > (iThisItemsMaxStats[CRITDAMAGE] * 0.65)) && (iHadStat[ATTACKSPEED] > (iThisItemsMaxStats[ATTACKSPEED] * 0.65))) { if (dBestFinalBonus < 1.5 && thisGilesItemType != GilesItemType.Quiver) dBestFinalBonus = 1.5; } if ((iHadStat[CRITCHANCE] > (iThisItemsMaxStats[CRITCHANCE] * 0.45)) && (iHadStat[CRITDAMAGE] > (iThisItemsMaxStats[CRITDAMAGE] * 0.45)) && (iHadStat[ATTACKSPEED] > (iThisItemsMaxStats[ATTACKSPEED] * 0.45))) { if (dBestFinalBonus < 1.7 && thisGilesItemType != GilesItemType.Quiver) dBestFinalBonus = 1.7; } if ((iHadStat[CRITCHANCE] > (iThisItemsMaxStats[CRITCHANCE] * 0.45)) && (iHadStat[CRITDAMAGE] > (iThisItemsMaxStats[CRITDAMAGE] * 0.45))) { if (dBestFinalBonus < 1.4) dBestFinalBonus = 1.4; } if ((iHadStat[CRITCHANCE] > (iThisItemsMaxStats[CRITCHANCE] * 0.45)) && (iHadStat[ATTACKSPEED] > (iThisItemsMaxStats[ATTACKSPEED] * 0.45))) { if (dBestFinalBonus < 1.3 && thisGilesItemType != GilesItemType.Quiver) dBestFinalBonus = 1.3; } if ((iHadStat[CRITDAMAGE] > (iThisItemsMaxStats[CRITDAMAGE] * 0.45)) && (iHadStat[ATTACKSPEED] > (iThisItemsMaxStats[ATTACKSPEED] * 0.45))) { if (dBestFinalBonus < 1.1 && thisGilesItemType != GilesItemType.Quiver) dBestFinalBonus = 1.1; } iTotalPoints *= dBestFinalBonus; if (bFullAnalysis) Log("TOTAL: " + iTotalPoints.ToString()); if (bFullAnalysis) Log(""); return Math.Round(iTotalPoints); } public static void SendEmail(string toAddressStr, string fromAddressStr, string subject, string body, string smtpClient, string fromPassword) { try { var fromAddress = new MailAddress(fromAddressStr); var toAddress = new MailAddress(toAddressStr); var smtp = new SmtpClient { Host = smtpClient, Port = 587, EnableSsl = true, DeliveryMethod = SmtpDeliveryMethod.Network, UseDefaultCredentials = false, Credentials = new NetworkCredential(fromAddress.Address, fromPassword) }; using (var message = new MailMessage(fromAddress, toAddress) { Subject = subject, Body = body }) { smtp.Send(message); } } catch (Exception e) { Log("Error sending email." + Environment.NewLine + e.ToString()); } } public static bool EvaluateItemScoreForNotification(GilesBaseItemType thisgilesbaseitemtype, double ithisitemvalue) { switch (thisgilesbaseitemtype) { case GilesBaseItemType.WeaponOneHand: case GilesBaseItemType.WeaponRange: case GilesBaseItemType.WeaponTwoHand: if (ithisitemvalue >= settings.iNeedPointsToNotifyWeapon) return true; break; case GilesBaseItemType.Armor: case GilesBaseItemType.Offhand: if (ithisitemvalue >= settings.iNeedPointsToNotifyArmor) return true; break; case GilesBaseItemType.Jewelry: if (ithisitemvalue >= settings.iNeedPointsToNotifyJewelry) return true; break; } return false; } // // ********************************************************************************************** // ***** Log the nice items we found and stashed ***** // ********************************************************************************************** public static void LogGoodItems(GilesCachedACDItem thisgooditem, GilesBaseItemType thisgilesbaseitemtype, GilesItemType thisgilesitemtype, double ithisitemvalue) { FileStream LogStream = null; try { LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - StashLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read); using (StreamWriter LogWriter = new StreamWriter(LogStream)) { if (!bLoggedAnythingThisStash) { bLoggedAnythingThisStash = true; LogWriter.WriteLine(DateTime.Now.ToString() + ":"); LogWriter.WriteLine("===================="); } string sLegendaryString = ""; bool bShouldNotify = false; if (thisgooditem.ThisQuality >= ItemQuality.Legendary) { if (!settings.bEnableLegendaryNotifyScore) bShouldNotify = true; else if (settings.bEnableLegendaryNotifyScore && EvaluateItemScoreForNotification(thisgilesbaseitemtype, ithisitemvalue)) bShouldNotify = true; if(bShouldNotify) AddNotificationToQueue(thisgooditem.ThisRealName + " [" + thisgilesitemtype.ToString() + "] (Score=" + ithisitemvalue.ToString() + ". " + sValueItemStatString + ")", ZetaDia.Service.CurrentHero.Name + " new legendary!", ProwlNotificationPriority.Emergency); sLegendaryString = " {legendary item}"; // Change made by bombastic Logging.Write("+=+=+=+=+=+=+=+=+ LEGENDARY FOUND +=+=+=+=+=+=+=+=+"); Logging.Write("+ Name: " + thisgooditem.ThisRealName + " (" + thisgilesitemtype.ToString() + ")"); Logging.Write("+ Score: " + Math.Round(ithisitemvalue).ToString()); Logging.Write("+ Attributes: " + sValueItemStatString); Logging.Write("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+"); } else { // Check for non-legendary notifications bShouldNotify = EvaluateItemScoreForNotification(thisgilesbaseitemtype, ithisitemvalue); if (bShouldNotify) AddNotificationToQueue(thisgooditem.ThisRealName + " [" + thisgilesitemtype.ToString() + "] (Score=" + ithisitemvalue.ToString() + ". " + sValueItemStatString + ")", ZetaDia.Service.CurrentHero.Name + " new item!", ProwlNotificationPriority.Emergency); } if (bShouldNotify) { EmailMessage.AppendLine(thisgilesbaseitemtype.ToString() + " - " + thisgilesitemtype.ToString() + " '" + thisgooditem.ThisRealName + "'. Score = " + Math.Round(ithisitemvalue).ToString() + sLegendaryString) .AppendLine(" " + sValueItemStatString) .AppendLine(); } LogWriter.WriteLine(thisgilesbaseitemtype.ToString() + " - " + thisgilesitemtype.ToString() + " '" + thisgooditem.ThisRealName + "'. Score = " + Math.Round(ithisitemvalue).ToString() + sLegendaryString); LogWriter.WriteLine(" " + sValueItemStatString); LogWriter.WriteLine(""); } LogStream.Close(); } catch (IOException) { Log("Fatal Error: File access error for stash log file."); if (LogStream != null) LogStream.Close(); } } // ********************************************************************************************** // ***** Log the rubbish junk items we salvaged or sold ***** // ********************************************************************************************** public static void LogJunkItems(GilesCachedACDItem thisgooditem, GilesBaseItemType thisgilesbaseitemtype, GilesItemType thisgilesitemtype, double ithisitemvalue) { FileStream LogStream = null; try { LogStream = File.Open(sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - JunkLog - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read); using (StreamWriter LogWriter = new StreamWriter(LogStream)) { if (!bLoggedJunkThisStash) { bLoggedJunkThisStash = true; LogWriter.WriteLine(DateTime.Now.ToString() + ":"); LogWriter.WriteLine("===================="); } string sLegendaryString = ""; if (thisgooditem.ThisQuality >= ItemQuality.Legendary) sLegendaryString = " {legendary item}"; LogWriter.WriteLine(thisgilesbaseitemtype.ToString() + " - " + thisgilesitemtype.ToString() + " '" + thisgooditem.ThisRealName + "'. Score = " + Math.Round(ithisitemvalue).ToString() + sLegendaryString); if (sJunkItemStatString != "") LogWriter.WriteLine(" " + sJunkItemStatString); else LogWriter.WriteLine(" (no scorable attributes)"); LogWriter.WriteLine(""); } LogStream.Close(); } catch (IOException) { Log("Fatal Error: File access error for junk log file."); if (LogStream != null) LogStream.Close(); } } // ********************************************************************************************** // ***** Stash replacement accurately and neatly finds a free stash location ***** // ********************************************************************************************** private static bool GilesStashAttempt(GilesCachedACDItem item) { int iPlayerDynamicID = ZetaDia.Me.CommonData.DynamicId; int iOriginalGameBalanceId = item.ThisBalanceID; int iOriginalDynamicID = item.ThisDynamicID; int iOriginalStackQuantity = item.ThisItemStackQuantity; string sOriginalItemName = item.ThisRealName; string sOriginalInternalName = item.ThisInternalName; GilesItemType OriginalGilesItemType = DetermineItemType(item.ThisInternalName, item.ThisDBItemType, item.ThisFollowerType); GilesBaseItemType thisGilesBaseType = DetermineBaseType(OriginalGilesItemType); bool bOriginalTwoSlot = DetermineIsTwoSlot(OriginalGilesItemType); bool bOriginalIsStackable = DetermineIsStackable(OriginalGilesItemType); int iAttempts; if (_dictItemStashAttempted.TryGetValue(iOriginalDynamicID, out iAttempts)) { Log("GSError: Detected a duplicate stash attempt, DB item mis-read error, now forcing this item as a 2-slot item"); _dictItemStashAttempted[iOriginalDynamicID] = iAttempts + 1; bOriginalTwoSlot = true; bOriginalIsStackable = false; if (iAttempts > 6) { Log("GSError: Detected an item stash loop risk, now re-mapping stash treating everything as 2-slot and re-attempting"); // Array for what blocks are or are not blocked for (int iRow = 0; iRow <= 29; iRow++) for (int iColumn = 0; iColumn <= 6; iColumn++) GilesStashSlotBlocked[iColumn, iRow] = false; // Block off the entire of any "protected stash pages" foreach (int iProtPage in Zeta.CommonBot.Settings.CharacterSettings.Instance.ProtectedStashPages) for (int iProtRow = 0; iProtRow <= 9; iProtRow++) for (int iProtColumn = 0; iProtColumn <= 6; iProtColumn++) GilesStashSlotBlocked[iProtColumn, iProtRow + (iProtPage * 10)] = true; // Remove rows we don't have for (int iRow = (ZetaDia.Me.NumSharedStashSlots / 7); iRow <= 29; iRow++) for (int iColumn = 0; iColumn <= 6; iColumn++) GilesStashSlotBlocked[iColumn, iRow] = true; // Map out all the items already in the stash foreach (ACDItem tempitem in ZetaDia.Me.Inventory.StashItems) { if (tempitem.BaseAddress != IntPtr.Zero) { int inventoryRow = tempitem.InventoryRow; int inventoryColumn = tempitem.InventoryColumn; // Mark this slot as not-free GilesStashSlotBlocked[inventoryColumn, inventoryRow] = true; // Try and reliably find out if this is a two slot item or not GilesStashSlotBlocked[inventoryColumn, inventoryRow + 1] = true; if (inventoryRow != 19 && inventoryRow != 9 && inventoryRow != 29) { GilesStashSlotBlocked[inventoryColumn, inventoryRow + 1] = true; } } } } if (iAttempts > 15) { Log("***************************"); Log("GSError: Emergency Stop: No matter what we tried, we couldn't prevent an infinite stash loop. Sorry. Now stopping the bot."); BotMain.Stop(); return false; } } else { _dictItemStashAttempted.Add(iOriginalDynamicID, 1); } // Safety incase it's not actually in the backpack anymore /*if (item.InventorySlot != InventorySlot.PlayerBackpack) { Log("GSError: Diablo 3 memory read error, or item became invalid [StashAttempt-4]", true); return false; }*/ int iLeftoverStackQuantity = 0; // Item log for cool stuff stashed if (thisGilesBaseType == GilesBaseItemType.WeaponTwoHand || thisGilesBaseType == GilesBaseItemType.WeaponOneHand || thisGilesBaseType == GilesBaseItemType.WeaponRange || thisGilesBaseType == GilesBaseItemType.Armor || thisGilesBaseType == GilesBaseItemType.Jewelry || thisGilesBaseType == GilesBaseItemType.Offhand || thisGilesBaseType == GilesBaseItemType.FollowerItem) { double iThisItemValue = ValueThisItem(item, OriginalGilesItemType); LogGoodItems(item, thisGilesBaseType, OriginalGilesItemType, iThisItemValue); } int iPointX = -1; int iPointY = -1; // First check if we can top-up any already-existing stacks in the stash if (bOriginalIsStackable) { foreach (ACDItem tempitem in ZetaDia.Me.Inventory.StashItems) { if (tempitem.BaseAddress == IntPtr.Zero) { Log("GSError: Diablo 3 memory read error, or stash item became invalid [StashAttempt-5]", true); return false; } // Check if we combine the stacks, we won't overfill them if ((tempitem.GameBalanceId == iOriginalGameBalanceId) && (tempitem.ItemStackQuantity < tempitem.MaxStackCount)) { // Will we have leftovers? if ((tempitem.ItemStackQuantity + iOriginalStackQuantity) > tempitem.MaxStackCount) { iLeftoverStackQuantity = (tempitem.ItemStackQuantity + iOriginalStackQuantity) - tempitem.MaxStackCount; } iPointX = tempitem.InventoryColumn; iPointY = tempitem.InventoryRow; goto HandleStackMovement; } } HandleStackMovement: if ((iPointX >= 0) && (iPointY >= 0)) { ZetaDia.Me.Inventory.MoveItem(iOriginalDynamicID, iPlayerDynamicID, InventorySlot.PlayerSharedStash, iPointX, iPointY); // Only return if we have emptied this stack if (iLeftoverStackQuantity <= 0) { return true; } } } iPointX = -1; iPointY = -1; // If it's a 2-square item, find a double-slot free if (bOriginalTwoSlot) { for (int iRow = 0; iRow <= 29; iRow++) { bool bBottomPageRow = (iRow == 9 || iRow == 19 || iRow == 29); for (int iColumn = 0; iColumn <= 6; iColumn++) { // If nothing in the 1st row if (!GilesStashSlotBlocked[iColumn, iRow]) { bool bNotEnoughSpace = false; // Bottom row of a page = no room if (bBottomPageRow) bNotEnoughSpace = true; // Already something in the stash in the 2nd row) else if (GilesStashSlotBlocked[iColumn, iRow + 1]) bNotEnoughSpace = true; if (!bNotEnoughSpace) { iPointX = iColumn; iPointY = iRow; goto FoundStashLocation; } } } } } // 2 slot item? // Now deal with any leftover 1-slot items else { // First we try and find somewhere "sensible" for (int iRow = 0; iRow <= 29; iRow++) { bool bTopPageRow = (iRow == 0 || iRow == 10 || iRow == 20); bool bBottomPageRow = (iRow == 9 || iRow == 19 || iRow == 29); for (int iColumn = 0; iColumn <= 6; iColumn++) { // Nothing in this slot if (!GilesStashSlotBlocked[iColumn, iRow]) { bool bSensibleLocation = false; if (!bTopPageRow && !bBottomPageRow) { // Something above and below this slot, or an odd-numbered row, so put something here if ((GilesStashSlotBlocked[iColumn, iRow + 1] && GilesStashSlotBlocked[iColumn, iRow - 1]) || (iRow) % 2 != 0) bSensibleLocation = true; } // Top page row with something directly underneath already blocking else if (bTopPageRow) { if (GilesStashSlotBlocked[iColumn, iRow + 1]) bSensibleLocation = true; } // Bottom page row with something directly over already blocking else { bSensibleLocation = true; } // Sensible location? Yay, stash it here! if (bSensibleLocation) { iPointX = iColumn; iPointY = iRow; // Keep looking for places if it's a stackable to try to stick it at the end if (!bOriginalIsStackable) goto FoundStashLocation; } } } } // Didn't find a "sensible" place, let's try and force it in absolutely anywhere if ((iPointX < 0) || (iPointY < 0)) { for (int iRow = 0; iRow <= 29; iRow++) { for (int iColumn = 0; iColumn <= 6; iColumn++) { // Nothing in this spot, we're good! if (!GilesStashSlotBlocked[iColumn, iRow]) { iPointX = iColumn; iPointY = iRow; // Keep looking for places if it's a stackable to try to stick it at the end if (!bOriginalIsStackable) goto FoundStashLocation; } } } } } FoundStashLocation: if ((iPointX < 0) || (iPointY < 0)) { Log("Fatal Error: No valid stash location found for '" + sOriginalItemName + "' [" + sOriginalInternalName + " - " + OriginalGilesItemType.ToString() + "]", true); Log("***************************"); Log("GSError: Emergency Stop: You need to stash an item but no valid space could be found. Stash is full? Stopping the bot to prevent infinite town-run loop."); BotMain.Stop(); return false; } // We have two valid points that are empty, move the object here! GilesStashSlotBlocked[iPointX, iPointY] = true; if (bOriginalTwoSlot) GilesStashSlotBlocked[iPointX, iPointY + 1] = true; ZetaDia.Me.Inventory.MoveItem(iOriginalDynamicID, iPlayerDynamicID, InventorySlot.PlayerSharedStash, iPointX, iPointY); return true; } // Custom stashing routine // ********************************************************************************************** // ***** Full Output Of Item Stats ***** // ********************************************************************************************** private static void OutputReport() { TimeSpan TotalRunningTime = DateTime.Now.Subtract(ItemStatsWhenStartedBot); string sLogFileName = ZetaDia.Service.CurrentHero.BattleTagName + " - Stats - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log"; // Create whole new file FileStream LogStream = File.Open(sTrinityPluginPath + sLogFileName, FileMode.Create, FileAccess.Write, FileShare.Read); using (StreamWriter LogWriter = new StreamWriter(LogStream)) { LogWriter.WriteLine("===== Misc Statistics ====="); LogWriter.WriteLine("Total tracking time: " + TotalRunningTime.Hours.ToString() + "h " + TotalRunningTime.Minutes.ToString() + "m " + TotalRunningTime.Seconds.ToString() + "s"); LogWriter.WriteLine("Total deaths: " + iTotalDeaths.ToString() + " [" + Math.Round(iTotalDeaths / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); LogWriter.WriteLine("Total games (approx): " + iTotalLeaveGames.ToString() + " [" + Math.Round(iTotalLeaveGames / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); if (iTotalLeaveGames == 0 && iTotalJoinGames > 0) { if (iTotalJoinGames == 1 && iTotalProfileRecycles > 1) { LogWriter.WriteLine("(a profile manager/death handler is interfering with join/leave game events, attempting to guess total runs based on profile-loops)"); LogWriter.WriteLine("Total full profile cycles: " + iTotalProfileRecycles.ToString() + " [" + Math.Round(iTotalProfileRecycles / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); } else { LogWriter.WriteLine("(your games left value may be bugged @ 0 due to profile managers/routines etc., now showing games joined instead:)"); LogWriter.WriteLine("Total games joined: " + iTotalJoinGames.ToString() + " [" + Math.Round(iTotalJoinGames / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); } } LogWriter.WriteLine(""); LogWriter.WriteLine("===== Item DROP Statistics ====="); // Item stats if (ItemsDroppedStats.iTotal > 0) { LogWriter.WriteLine("Items:"); LogWriter.WriteLine("Total items dropped: " + ItemsDroppedStats.iTotal.ToString() + " [" + Math.Round(ItemsDroppedStats.iTotal / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); LogWriter.WriteLine("Items dropped by ilvl: "); for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsDroppedStats.iTotalPerLevel[iThisLevel] > 0) LogWriter.WriteLine("- ilvl" + iThisLevel.ToString() + ": " + ItemsDroppedStats.iTotalPerLevel[iThisLevel].ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalPerLevel[iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iTotalPerLevel[iThisLevel] / ItemsDroppedStats.iTotal) * 100, 2).ToString() + " %}"); LogWriter.WriteLine(""); LogWriter.WriteLine("Items dropped by quality: "); for (int iThisQuality = 0; iThisQuality <= 3; iThisQuality++) { if (ItemsDroppedStats.iTotalPerQuality[iThisQuality] > 0) { LogWriter.WriteLine("- " + sQualityString[iThisQuality] + ": " + ItemsDroppedStats.iTotalPerQuality[iThisQuality].ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalPerQuality[iThisQuality] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iTotalPerQuality[iThisQuality] / ItemsDroppedStats.iTotal) * 100, 2).ToString() + " %}"); for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsDroppedStats.iTotalPerQPerL[iThisQuality, iThisLevel] > 0) LogWriter.WriteLine("--- ilvl " + iThisLevel.ToString() + " " + sQualityString[iThisQuality] + ": " + ItemsDroppedStats.iTotalPerQPerL[iThisQuality, iThisLevel].ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalPerQPerL[iThisQuality, iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iTotalPerQPerL[iThisQuality, iThisLevel] / ItemsDroppedStats.iTotal) * 100, 2).ToString() + " %}"); } // Any at all this quality? } // For loop on quality LogWriter.WriteLine(""); } // End of item stats // Potion stats if (ItemsDroppedStats.iTotalPotions > 0) { LogWriter.WriteLine("Potion Drops:"); LogWriter.WriteLine("Total potions: " + ItemsDroppedStats.iTotalPotions.ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalPotions / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsDroppedStats.iPotionsPerLevel[iThisLevel] > 0) LogWriter.WriteLine("- ilvl " + iThisLevel.ToString() + ": " + ItemsDroppedStats.iPotionsPerLevel[iThisLevel].ToString() + " [" + Math.Round(ItemsDroppedStats.iPotionsPerLevel[iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iPotionsPerLevel[iThisLevel] / ItemsDroppedStats.iTotalPotions) * 100, 2).ToString() + " %}"); LogWriter.WriteLine(""); } // End of potion stats // Gem stats if (ItemsDroppedStats.iTotalGems > 0) { LogWriter.WriteLine("Gem Drops:"); LogWriter.WriteLine("Total gems: " + ItemsDroppedStats.iTotalGems.ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalGems / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); for (int iThisGemType = 0; iThisGemType <= 3; iThisGemType++) { if (ItemsDroppedStats.iGemsPerType[iThisGemType] > 0) { LogWriter.WriteLine("- " + sGemString[iThisGemType] + ": " + ItemsDroppedStats.iGemsPerType[iThisGemType].ToString() + " [" + Math.Round(ItemsDroppedStats.iGemsPerType[iThisGemType] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iGemsPerType[iThisGemType] / ItemsDroppedStats.iTotalGems) * 100, 2).ToString() + " %}"); for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsDroppedStats.iGemsPerTPerL[iThisGemType, iThisLevel] > 0) LogWriter.WriteLine("--- ilvl " + iThisLevel.ToString() + " " + sGemString[iThisGemType] + ": " + ItemsDroppedStats.iGemsPerTPerL[iThisGemType, iThisLevel].ToString() + " [" + Math.Round(ItemsDroppedStats.iGemsPerTPerL[iThisGemType, iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsDroppedStats.iGemsPerTPerL[iThisGemType, iThisLevel] / ItemsDroppedStats.iTotalGems) * 100, 2).ToString() + " %}"); } // Any at all this quality? } // For loop on quality } // End of gem stats // Key stats if (ItemsDroppedStats.iTotalInfernalKeys > 0) { LogWriter.WriteLine("Infernal Key Drops:"); LogWriter.WriteLine("Total Keys: " + ItemsDroppedStats.iTotalInfernalKeys.ToString() + " [" + Math.Round(ItemsDroppedStats.iTotalInfernalKeys / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); } // End of key stats LogWriter.WriteLine(""); LogWriter.WriteLine(""); LogWriter.WriteLine("===== Item PICKUP Statistics ====="); // Item stats if (ItemsPickedStats.iTotal > 0) { LogWriter.WriteLine("Items:"); LogWriter.WriteLine("Total items picked up: " + ItemsPickedStats.iTotal.ToString() + " [" + Math.Round(ItemsPickedStats.iTotal / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); LogWriter.WriteLine("Item picked up by ilvl: "); for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsPickedStats.iTotalPerLevel[iThisLevel] > 0) LogWriter.WriteLine("- ilvl" + iThisLevel.ToString() + ": " + ItemsPickedStats.iTotalPerLevel[iThisLevel].ToString() + " [" + Math.Round(ItemsPickedStats.iTotalPerLevel[iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iTotalPerLevel[iThisLevel] / ItemsPickedStats.iTotal) * 100, 2).ToString() + " %}"); LogWriter.WriteLine(""); LogWriter.WriteLine("Items picked up by quality: "); for (int iThisQuality = 0; iThisQuality <= 3; iThisQuality++) { if (ItemsPickedStats.iTotalPerQuality[iThisQuality] > 0) { LogWriter.WriteLine("- " + sQualityString[iThisQuality] + ": " + ItemsPickedStats.iTotalPerQuality[iThisQuality].ToString() + " [" + Math.Round(ItemsPickedStats.iTotalPerQuality[iThisQuality] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iTotalPerQuality[iThisQuality] / ItemsPickedStats.iTotal) * 100, 2).ToString() + " %}"); for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsPickedStats.iTotalPerQPerL[iThisQuality, iThisLevel] > 0) LogWriter.WriteLine("--- ilvl " + iThisLevel.ToString() + " " + sQualityString[iThisQuality] + ": " + ItemsPickedStats.iTotalPerQPerL[iThisQuality, iThisLevel].ToString() + " [" + Math.Round(ItemsPickedStats.iTotalPerQPerL[iThisQuality, iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iTotalPerQPerL[iThisQuality, iThisLevel] / ItemsPickedStats.iTotal) * 100, 2).ToString() + " %}"); } // Any at all this quality? } // For loop on quality LogWriter.WriteLine(""); if (iTotalFollowerItemsIgnored > 0) { LogWriter.WriteLine(" (note: " + iTotalFollowerItemsIgnored.ToString() + " follower items ignored for being ilvl <60 or blue)"); } } // End of item stats // Potion stats if (ItemsPickedStats.iTotalPotions > 0) { LogWriter.WriteLine("Potion Pickups:"); LogWriter.WriteLine("Total potions: " + ItemsPickedStats.iTotalPotions.ToString() + " [" + Math.Round(ItemsPickedStats.iTotalPotions / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsPickedStats.iPotionsPerLevel[iThisLevel] > 0) LogWriter.WriteLine("- ilvl " + iThisLevel.ToString() + ": " + ItemsPickedStats.iPotionsPerLevel[iThisLevel].ToString() + " [" + Math.Round(ItemsPickedStats.iPotionsPerLevel[iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iPotionsPerLevel[iThisLevel] / ItemsPickedStats.iTotalPotions) * 100, 2).ToString() + " %}"); LogWriter.WriteLine(""); } // End of potion stats // Gem stats if (ItemsPickedStats.iTotalGems > 0) { LogWriter.WriteLine("Gem Pickups:"); LogWriter.WriteLine("Total gems: " + ItemsPickedStats.iTotalGems.ToString() + " [" + Math.Round(ItemsPickedStats.iTotalGems / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); for (int iThisGemType = 0; iThisGemType <= 3; iThisGemType++) { if (ItemsPickedStats.iGemsPerType[iThisGemType] > 0) { LogWriter.WriteLine("- " + sGemString[iThisGemType] + ": " + ItemsPickedStats.iGemsPerType[iThisGemType].ToString() + " [" + Math.Round(ItemsPickedStats.iGemsPerType[iThisGemType] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iGemsPerType[iThisGemType] / ItemsPickedStats.iTotalGems) * 100, 2).ToString() + " %}"); for (int iThisLevel = 1; iThisLevel <= 63; iThisLevel++) if (ItemsPickedStats.iGemsPerTPerL[iThisGemType, iThisLevel] > 0) LogWriter.WriteLine("--- ilvl " + iThisLevel.ToString() + " " + sGemString[iThisGemType] + ": " + ItemsPickedStats.iGemsPerTPerL[iThisGemType, iThisLevel].ToString() + " [" + Math.Round(ItemsPickedStats.iGemsPerTPerL[iThisGemType, iThisLevel] / TotalRunningTime.TotalHours, 2).ToString() + " per hour] {" + Math.Round((ItemsPickedStats.iGemsPerTPerL[iThisGemType, iThisLevel] / ItemsPickedStats.iTotalGems) * 100, 2).ToString() + " %}"); } // Any at all this quality? } // For loop on quality } // End of gem stats // Key stats if (ItemsPickedStats.iTotalInfernalKeys > 0) { LogWriter.WriteLine("Infernal Key Pickups:"); LogWriter.WriteLine("Total Keys: " + ItemsPickedStats.iTotalInfernalKeys.ToString() + " [" + Math.Round(ItemsPickedStats.iTotalInfernalKeys / TotalRunningTime.TotalHours, 2).ToString() + " per hour]"); } // End of key stats LogWriter.WriteLine("===== End Of Report ====="); } LogStream.Close(); } // ********************************************************************************************** // ***** Search backpack to see if we have room for a 2-slot item anywhere ***** // ********************************************************************************************** private static Vector2 FindValidBackpackLocation(bool bOriginalTwoSlot) { bool[,] GilesBackpackSlotBlocked = new bool[10, 6]; // Block off the entire of any "protected bag slots" foreach (InventorySquare thissquare in Zeta.CommonBot.Settings.CharacterSettings.Instance.ProtectedBagSlots) GilesBackpackSlotBlocked[thissquare.Column, thissquare.Row] = true; // Map out all the items already in the backpack foreach (ACDItem tempitem in ZetaDia.Me.Inventory.Backpack) { if (tempitem.BaseAddress == IntPtr.Zero) { return new Vector2(-1, -1); } int inventoryRow = tempitem.InventoryRow; int inventoryColumn = tempitem.InventoryColumn; // Mark this slot as not-free GilesBackpackSlotBlocked[inventoryColumn, inventoryRow] = true; // Try and reliably find out if this is a two slot item or not GilesItemType tempItemType = DetermineItemType(tempitem.InternalName, tempitem.ItemType, tempitem.FollowerSpecialType); if (DetermineIsTwoSlot(tempItemType) && inventoryRow < 5) { GilesBackpackSlotBlocked[inventoryColumn, inventoryRow + 1] = true; } } int iPointX = -1; int iPointY = -1; for (int iRow = 0; iRow <= 5; iRow++) { for (int iColumn = 0; iColumn <= 9; iColumn++) { if (!GilesBackpackSlotBlocked[iColumn, iRow]) { bool bNotEnoughSpace = false; if (iRow < 5) { bNotEnoughSpace = (bOriginalTwoSlot && GilesBackpackSlotBlocked[iColumn, iRow + 1]); } else { if (bOriginalTwoSlot) bNotEnoughSpace = true; } if (!bNotEnoughSpace) { iPointX = iColumn; iPointY = iRow; goto FoundPackLocation; } } } } FoundPackLocation: if ((iPointX < 0) || (iPointY < 0)) { return new Vector2(-1, -1); } return new Vector2(iPointX, iPointY); } // ********************************************************************************************** // ***** Save Configuration ***** // ********************************************************************************************** private void SaveConfiguration() { if (bSavingConfig) return; bSavingConfig = true; FileStream configStream = File.Open(sTrinityConfigFile, FileMode.Create, FileAccess.Write, FileShare.Read); using (StreamWriter configWriter = new StreamWriter(configStream)) { configWriter.WriteLine("JewelryPoints=" + settings.iNeedPointsToKeepJewelry.ToString()); configWriter.WriteLine("ArmorPoints=" + settings.iNeedPointsToKeepArmor.ToString()); configWriter.WriteLine("WeaponPoints=" + settings.iNeedPointsToKeepWeapon.ToString()); configWriter.WriteLine(settings.bSalvageJunk ? "Salvage=true" : "Salvage=false"); configWriter.WriteLine(settings.bUseGilesFilters ? "Filters=true" : "Filters=false"); configWriter.WriteLine(settings.bGemsEmerald ? "Emerald=true" : "Emerald=false"); configWriter.WriteLine(settings.bGemsAmethyst ? "Amethyst=true" : "Amethyst=false"); configWriter.WriteLine(settings.bGemsTopaz ? "Topaz=true" : "Topaz=false"); configWriter.WriteLine(settings.bGemsRuby ? "Ruby=true" : "Ruby=false"); configWriter.WriteLine(settings.bPickupCraftTomes ? "Tomes=true" : "Tomes=false"); configWriter.WriteLine(settings.bPickupPlans ? "Plans=true" : "Plans=false"); configWriter.WriteLine(settings.bPickupFollower ? "Followers=true" : "Followers=false"); configWriter.WriteLine("Potions=" + settings.iFilterPotions.ToString()); configWriter.WriteLine("ilvlPots=" + settings.iFilterPotionLevel.ToString()); configWriter.WriteLine("ilvlLegendary=" + settings.iFilterLegendary.ToString()); configWriter.WriteLine("ilvlWB=" + settings.iFilterBlueWeapons.ToString()); configWriter.WriteLine("ilvlWY=" + settings.iFilterYellowWeapons.ToString()); configWriter.WriteLine("ilvlAB=" + settings.iFilterBlueArmor.ToString()); configWriter.WriteLine("ilvlAY=" + settings.iFilterYellowArmor.ToString()); configWriter.WriteLine("ilvlJB=" + settings.iFilterBlueJewelry.ToString()); configWriter.WriteLine("ilvlJY=" + settings.iFilterYellowJewelry.ToString()); configWriter.WriteLine("ilvlGems=" + settings.iFilterGems.ToString()); configWriter.WriteLine("ilvlMisc=" + settings.iFilterMisc.ToString()); configWriter.WriteLine("GoldPickup=" + settings.iMinimumGoldStack.ToString()); configWriter.WriteLine("GoblinPriority=" + settings.iTreasureGoblinPriority.ToString()); configWriter.WriteLine("TriggerRange=" + settings.iMonsterKillRange.ToString()); configWriter.WriteLine("LootDelay=" + settings.iKillLootDelay.ToString()); configWriter.WriteLine("VaultDelay=" + settings.iDHVaultMovementDelay.ToString()); configWriter.WriteLine("MonkInna=" + settings.bMonkInnaSet.ToString()); configWriter.WriteLine("Avoidance=" + settings.bEnableAvoidance.ToString()); configWriter.WriteLine("Globes=" + settings.bEnableGlobes.ToString()); configWriter.WriteLine("CriticalMass=" + settings.bEnableCriticalMass.ToString()); configWriter.WriteLine("OOCMovementPower=" + settings.bOutOfCombatMovementPowers.ToString()); configWriter.WriteLine("ExtendedKills=" + settings.bExtendedKillRange.ToString()); configWriter.WriteLine("SelectiveWW=" + settings.bSelectiveWhirlwind.ToString()); configWriter.WriteLine("Wrath90=" + settings.bWrath90Seconds.ToString()); configWriter.WriteLine("WizKiteArchonOnly=" + settings.bKiteOnlyArchon.ToString()); configWriter.WriteLine("WizWaitForArchon=" + settings.bWaitForArchon.ToString()); configWriter.WriteLine("BarbWaitForWrath=" + settings.bWaitForWrath.ToString()); configWriter.WriteLine("BarbGoblinWrath=" + settings.bGoblinWrath.ToString()); configWriter.WriteLine("BarbFuryDumpWrath=" + settings.bFuryDumpWrath.ToString()); configWriter.WriteLine("BarbFuryDumpAlways=" + settings.bFuryDumpAlways.ToString()); configWriter.WriteLine("LogStucks=" + settings.bLogStucks.ToString()); configWriter.WriteLine("Unstucker=" + settings.bEnableUnstucker.ToString()); configWriter.WriteLine("ProfileReloading=" + settings.bEnableProfileReloading.ToString()); configWriter.WriteLine("Backtracking=" + settings.bEnableBacktracking.ToString()); configWriter.WriteLine(settings.bIgnoreAllShrines ? "ShrineIgnore=all" : "ShrineIgnore=none"); configWriter.WriteLine("ContainerRange=" + settings.iContainerOpenRange.ToString()); configWriter.WriteLine("DestructibleRange=" + settings.iDestructibleAttackRange.ToString()); configWriter.WriteLine("IgnoreCorpses=" + settings.bIgnoreCorpses.ToString()); configWriter.WriteLine(settings.bEnableTPS ? "TPSEnabled=true" : "TPSEnabled=false"); configWriter.WriteLine("TPSAmount=" + settings.iTPSAmount.ToString()); configWriter.WriteLine(settings.bDebugInfo ? "DebugInfo=true" : "DebugInfo=false"); configWriter.WriteLine(settings.bEnableProwl ? "EnableProwl=true" : "EnableProwl=false"); configWriter.WriteLine(settings.bEnableAndroid ? "EnableAndroid=true" : "EnableAndroid=false"); configWriter.WriteLine(settings.bEnableEmail ? "EnableEmail=true" : "EnableEmail=false"); configWriter.WriteLine("EmailAddress=" + sEmailAddress); configWriter.WriteLine("EmailPassword=" + sEmailPassword); configWriter.WriteLine("ProwlKey=" + sProwlAPIKey); configWriter.WriteLine("AndroidKey=" + sAndroidAPIKey); configWriter.WriteLine("EnableLegendaryNotifyScore=" + (settings.bEnableLegendaryNotifyScore ? "true" : "false")); configWriter.WriteLine("JewelryNotify=" + settings.iNeedPointsToNotifyJewelry.ToString()); configWriter.WriteLine("ArmorNotify=" + settings.iNeedPointsToNotifyArmor.ToString()); configWriter.WriteLine("WeaponNotify=" + settings.iNeedPointsToNotifyWeapon.ToString()); configWriter.WriteLine("KiteBarb=" + settings.iKiteDistanceBarb.ToString()); configWriter.WriteLine("KiteWiz=" + settings.iKiteDistanceWiz.ToString()); configWriter.WriteLine("KiteWitch=" + settings.iKiteDistanceWitch.ToString()); configWriter.WriteLine("KiteDemon=" + settings.iKiteDistanceDemon.ToString()); configWriter.WriteLine("PotBarb=" + settings.dEmergencyHealthPotionBarb.ToString()); configWriter.WriteLine("PotMonk=" + settings.dEmergencyHealthPotionMonk.ToString()); configWriter.WriteLine("PotWiz=" + settings.dEmergencyHealthPotionWiz.ToString()); configWriter.WriteLine("PotWitch=" + settings.dEmergencyHealthPotionWitch.ToString()); configWriter.WriteLine("PotDemon=" + settings.dEmergencyHealthPotionDemon.ToString()); configWriter.WriteLine("GlobeBarb=" + settings.dEmergencyHealthGlobeBarb.ToString()); configWriter.WriteLine("GlobeMonk=" + settings.dEmergencyHealthGlobeMonk.ToString()); configWriter.WriteLine("GlobeWiz=" + settings.dEmergencyHealthGlobeWiz.ToString()); configWriter.WriteLine("GlobeWitch=" + settings.dEmergencyHealthGlobeWitch.ToString()); configWriter.WriteLine("GlobeDemon=" + settings.dEmergencyHealthGlobeDemon.ToString()); string sHealthLine = ""; for (int i = 1; i <= 13; i++) { switch (i) { case 1: sHealthLine += dictAvoidanceHealthBarb[219702].ToString(); break; case 2: sHealthLine += dictAvoidanceHealthBarb[84608].ToString(); break; case 3: sHealthLine += dictAvoidanceHealthBarb[4804].ToString(); break; case 4: sHealthLine += dictAvoidanceHealthBarb[95868].ToString(); break; case 5: sHealthLine += dictAvoidanceHealthBarb[5482].ToString(); break; case 6: sHealthLine += dictAvoidanceHealthBarb[108869].ToString(); break; case 7: sHealthLine += dictAvoidanceHealthBarb[223675].ToString(); break; case 8: sHealthLine += dictAvoidanceHealthBarb[3865].ToString(); break; case 9: sHealthLine += dictAvoidanceHealthBarb[5212].ToString(); break; case 10: sHealthLine += dictAvoidanceHealthBarb[123124].ToString(); break; case 11: sHealthLine += dictAvoidanceHealthBarb[123839].ToString(); break; case 12: sHealthLine += dictAvoidanceHealthBarb[4103].ToString(); break; case 13: sHealthLine += dictAvoidanceHealthBarb[93837].ToString(); break; } if (i < 13) sHealthLine += " "; } configWriter.WriteLine("AOEBarbHealth=" + sHealthLine); sHealthLine = ""; for (int i = 1; i <= 13; i++) { switch (i) { case 1: sHealthLine += dictAvoidanceHealthMonk[219702].ToString(); break; case 2: sHealthLine += dictAvoidanceHealthMonk[84608].ToString(); break; case 3: sHealthLine += dictAvoidanceHealthMonk[4804].ToString(); break; case 4: sHealthLine += dictAvoidanceHealthMonk[95868].ToString(); break; case 5: sHealthLine += dictAvoidanceHealthMonk[5482].ToString(); break; case 6: sHealthLine += dictAvoidanceHealthMonk[108869].ToString(); break; case 7: sHealthLine += dictAvoidanceHealthMonk[223675].ToString(); break; case 8: sHealthLine += dictAvoidanceHealthMonk[3865].ToString(); break; case 9: sHealthLine += dictAvoidanceHealthMonk[5212].ToString(); break; case 10: sHealthLine += dictAvoidanceHealthMonk[123124].ToString(); break; case 11: sHealthLine += dictAvoidanceHealthMonk[123839].ToString(); break; case 12: sHealthLine += dictAvoidanceHealthMonk[4103].ToString(); break; case 13: sHealthLine += dictAvoidanceHealthMonk[93837].ToString(); break; } if (i < 13) sHealthLine += " "; } configWriter.WriteLine("AOEMonkHealth=" + sHealthLine); sHealthLine = ""; for (int i = 1; i <= 13; i++) { switch (i) { case 1: sHealthLine += dictAvoidanceHealthWizard[219702].ToString(); break; case 2: sHealthLine += dictAvoidanceHealthWizard[84608].ToString(); break; case 3: sHealthLine += dictAvoidanceHealthWizard[4804].ToString(); break; case 4: sHealthLine += dictAvoidanceHealthWizard[95868].ToString(); break; case 5: sHealthLine += dictAvoidanceHealthWizard[5482].ToString(); break; case 6: sHealthLine += dictAvoidanceHealthWizard[108869].ToString(); break; case 7: sHealthLine += dictAvoidanceHealthWizard[223675].ToString(); break; case 8: sHealthLine += dictAvoidanceHealthWizard[3865].ToString(); break; case 9: sHealthLine += dictAvoidanceHealthWizard[5212].ToString(); break; case 10: sHealthLine += dictAvoidanceHealthWizard[123124].ToString(); break; case 11: sHealthLine += dictAvoidanceHealthWizard[123839].ToString(); break; case 12: sHealthLine += dictAvoidanceHealthWizard[4103].ToString(); break; case 13: sHealthLine += dictAvoidanceHealthWizard[93837].ToString(); break; } if (i < 13) sHealthLine += " "; } configWriter.WriteLine("AOEWizardHealth=" + sHealthLine); sHealthLine = ""; for (int i = 1; i <= 13; i++) { switch (i) { case 1: sHealthLine += dictAvoidanceHealthWitch[219702].ToString(); break; case 2: sHealthLine += dictAvoidanceHealthWitch[84608].ToString(); break; case 3: sHealthLine += dictAvoidanceHealthWitch[4804].ToString(); break; case 4: sHealthLine += dictAvoidanceHealthWitch[95868].ToString(); break; case 5: sHealthLine += dictAvoidanceHealthWitch[5482].ToString(); break; case 6: sHealthLine += dictAvoidanceHealthWitch[108869].ToString(); break; case 7: sHealthLine += dictAvoidanceHealthWitch[223675].ToString(); break; case 8: sHealthLine += dictAvoidanceHealthWitch[3865].ToString(); break; case 9: sHealthLine += dictAvoidanceHealthWitch[5212].ToString(); break; case 10: sHealthLine += dictAvoidanceHealthWitch[123124].ToString(); break; case 11: sHealthLine += dictAvoidanceHealthWitch[123839].ToString(); break; case 12: sHealthLine += dictAvoidanceHealthWitch[4103].ToString(); break; case 13: sHealthLine += dictAvoidanceHealthWitch[93837].ToString(); break; } if (i < 13) sHealthLine += " "; } configWriter.WriteLine("AOEWitchHealth=" + sHealthLine); sHealthLine = ""; for (int i = 1; i <= 13; i++) { switch (i) { case 1: sHealthLine += dictAvoidanceHealthDemon[219702].ToString(); break; case 2: sHealthLine += dictAvoidanceHealthDemon[84608].ToString(); break; case 3: sHealthLine += dictAvoidanceHealthDemon[4804].ToString(); break; case 4: sHealthLine += dictAvoidanceHealthDemon[95868].ToString(); break; case 5: sHealthLine += dictAvoidanceHealthDemon[5482].ToString(); break; case 6: sHealthLine += dictAvoidanceHealthDemon[108869].ToString(); break; case 7: sHealthLine += dictAvoidanceHealthDemon[223675].ToString(); break; case 8: sHealthLine += dictAvoidanceHealthDemon[3865].ToString(); break; case 9: sHealthLine += dictAvoidanceHealthDemon[5212].ToString(); break; case 10: sHealthLine += dictAvoidanceHealthDemon[123124].ToString(); break; case 11: sHealthLine += dictAvoidanceHealthDemon[123839].ToString(); break; case 12: sHealthLine += dictAvoidanceHealthDemon[4103].ToString(); break; case 13: sHealthLine += dictAvoidanceHealthDemon[93837].ToString(); break; } if (i < 13) sHealthLine += " "; } configWriter.WriteLine("AOEDemonHealth=" + sHealthLine); sHealthLine = ""; for (int i = 1; i <= 13; i++) { switch (i) { case 1: sHealthLine += dictAvoidanceRadius[219702].ToString(); break; case 2: sHealthLine += dictAvoidanceRadius[84608].ToString(); break; case 3: sHealthLine += dictAvoidanceRadius[4804].ToString(); break; case 4: sHealthLine += dictAvoidanceRadius[95868].ToString(); break; case 5: sHealthLine += dictAvoidanceRadius[5482].ToString(); break; case 6: sHealthLine += dictAvoidanceRadius[108869].ToString(); break; case 7: sHealthLine += dictAvoidanceRadius[223675].ToString(); break; case 8: sHealthLine += dictAvoidanceRadius[3865].ToString(); break; case 9: sHealthLine += dictAvoidanceRadius[5212].ToString(); break; case 10: sHealthLine += dictAvoidanceRadius[123124].ToString(); break; case 11: sHealthLine += dictAvoidanceRadius[123839].ToString(); break; case 12: sHealthLine += dictAvoidanceRadius[4103].ToString(); break; case 13: sHealthLine += dictAvoidanceRadius[93837].ToString(); break; } if (i < 13) sHealthLine += " "; } configWriter.WriteLine("AOERadius=" + sHealthLine); } configStream.Close(); saveEmailConfiguration(); bSavingConfig = false; bMappedPlayerAbilities = false; } private void saveEmailConfiguration() { FileStream emailConfigStream = File.Open(sTrinityEmailConfigFile, FileMode.Create, FileAccess.Write, FileShare.Read); using (StreamWriter configWriter = new StreamWriter(emailConfigStream)) { configWriter.WriteLine(settings.bEnableEmail ? "EnableEmail=true" : "EnableEmail=false"); configWriter.WriteLine("EmailAddress=" + sEmailAddress); configWriter.WriteLine("EmailPassword=" + sEmailPassword); configWriter.WriteLine("BotName=" + sBotName); } emailConfigStream.Close(); } // ********************************************************************************************** // ***** Load Configuration ***** // ********************************************************************************************** private void LoadConfiguration() { //Check for Config file if (!File.Exists(sTrinityConfigFile)) { Log("No config file found, now creating a new config from defaults at: " + sTrinityConfigFile); SaveConfiguration(); return; } //Load File string[] healthlevels; using (StreamReader configReader = new StreamReader(sTrinityConfigFile)) { while (!configReader.EndOfStream) { string[] config = configReader.ReadLine().Split('='); if (config != null) { switch (config[0]) { case "GoblinPriority": settings.iTreasureGoblinPriority = Convert.ToInt32(config[1]); break; case "TriggerRange": settings.iMonsterKillRange = Convert.ToDouble(config[1]); break; case "LootDelay": settings.iKillLootDelay = Convert.ToInt32(config[1]); break; case "VaultDelay": settings.iDHVaultMovementDelay = Convert.ToInt32(config[1]); break; case "MonkInna": settings.bMonkInnaSet = Convert.ToBoolean(config[1]); break; case "Avoidance": settings.bEnableAvoidance = Convert.ToBoolean(config[1]); break; case "Globes": settings.bEnableGlobes = Convert.ToBoolean(config[1]); break; case "CriticalMass": settings.bEnableCriticalMass = Convert.ToBoolean(config[1]); break; case "OOCMovementPower": settings.bOutOfCombatMovementPowers = Convert.ToBoolean(config[1]); break; case "Backtracking": settings.bEnableBacktracking = Convert.ToBoolean(config[1]); break; case "JewelryPoints": settings.iNeedPointsToKeepJewelry = Convert.ToDouble(config[1]); break; case "ArmorPoints": settings.iNeedPointsToKeepArmor = Convert.ToDouble(config[1]); break; case "WeaponPoints": settings.iNeedPointsToKeepWeapon = Convert.ToDouble(config[1]); break; case "JewelryNotify": settings.iNeedPointsToNotifyJewelry = Convert.ToDouble(config[1]); break; case "ArmorNotify": settings.iNeedPointsToNotifyArmor = Convert.ToDouble(config[1]); break; case "WeaponNotify": settings.iNeedPointsToNotifyWeapon = Convert.ToDouble(config[1]); break; case "Salvage": settings.bSalvageJunk = Convert.ToBoolean(config[1]); break; case "Filters": settings.bUseGilesFilters = Convert.ToBoolean(config[1]); break; case "Emerald": settings.bGemsEmerald = Convert.ToBoolean(config[1]); break; case "Amethyst": settings.bGemsAmethyst = Convert.ToBoolean(config[1]); break; case "Topaz": settings.bGemsTopaz = Convert.ToBoolean(config[1]); break; case "Ruby": settings.bGemsRuby = Convert.ToBoolean(config[1]); break; case "Tomes": settings.bPickupCraftTomes = Convert.ToBoolean(config[1]); break; case "Plans": settings.bPickupPlans = Convert.ToBoolean(config[1]); break; case "Followers": settings.bPickupFollower = Convert.ToBoolean(config[1]); break; case "Potions": settings.iFilterPotions = Convert.ToInt16(config[1]); break; case "ilvlPots": settings.iFilterPotionLevel = Convert.ToInt16(config[1]); break; case "ilvlLegendary": settings.iFilterLegendary = Convert.ToInt16(config[1]); break; case "ilvlWB": settings.iFilterBlueWeapons = Convert.ToInt16(config[1]); break; case "ilvlWY": settings.iFilterYellowWeapons = Convert.ToInt16(config[1]); break; case "ilvlAB": settings.iFilterBlueArmor = Convert.ToInt16(config[1]); break; case "ilvlAY": settings.iFilterYellowArmor = Convert.ToInt16(config[1]); break; case "ilvlJB": settings.iFilterBlueJewelry = Convert.ToInt16(config[1]); break; case "ilvlJY": settings.iFilterYellowJewelry = Convert.ToInt16(config[1]); break; case "ilvlGems": settings.iFilterGems = Convert.ToInt16(config[1]); break; case "ilvlMisc": settings.iFilterMisc = Convert.ToInt16(config[1]); break; case "GoldPickup": settings.iMinimumGoldStack = Convert.ToInt16(config[1]); break; case "ShrineIgnore": settings.bIgnoreAllShrines = (config[1] == "all"); break; case "ContainerRange": settings.iContainerOpenRange = Convert.ToDouble(config[1]); break; case "DestructibleRange": settings.iDestructibleAttackRange = Convert.ToDouble(config[1]); break; case "IgnoreCorpses": settings.bIgnoreCorpses = Convert.ToBoolean(config[1]); break; case "TPSEnabled": settings.bEnableTPS = Convert.ToBoolean(config[1]); break; case "TPSAmount": settings.iTPSAmount = Convert.ToDouble(config[1]); break; case "DebugInfo": settings.bDebugInfo = Convert.ToBoolean(config[1]); break; case "LogStucks": settings.bLogStucks = Convert.ToBoolean(config[1]); break; case "ProfileReloading": settings.bEnableProfileReloading = Convert.ToBoolean(config[1]); break; case "Unstucker": settings.bEnableUnstucker = Convert.ToBoolean(config[1]); if (settings.bEnableUnstucker) Navigator.StuckHandler = new GilesStuckHandler(); else Navigator.StuckHandler = new DefaultStuckHandler(); break; case "ExtendedKills": settings.bExtendedKillRange = Convert.ToBoolean(config[1]); break; case "SelectiveWW": settings.bSelectiveWhirlwind = Convert.ToBoolean(config[1]); break; case "Wrath90": settings.bWrath90Seconds = Convert.ToBoolean(config[1]); break; case "WizKiteArchonOnly": settings.bKiteOnlyArchon = Convert.ToBoolean(config[1]); break; case "WizWaitForArchon": settings.bWaitForArchon = Convert.ToBoolean(config[1]); break; case "BarbWaitForWrath": settings.bWaitForWrath = Convert.ToBoolean(config[1]); break; case "BarbGoblinWrath": settings.bGoblinWrath = Convert.ToBoolean(config[1]); break; case "BarbFuryDumpWrath": settings.bFuryDumpWrath = Convert.ToBoolean(config[1]); break; case "BarbFuryDumpAlways": settings.bFuryDumpAlways = Convert.ToBoolean(config[1]); break; case "EnableProwl": settings.bEnableProwl = Convert.ToBoolean(config[1]); break; case "EnableLegendaryNotifyScore": settings.bEnableLegendaryNotifyScore = Convert.ToBoolean(config[1]); break; case "ProwlKey": sProwlAPIKey = config[1]; break; case "EnableEmail": settings.bEnableEmail = Convert.ToBoolean(config[1]); break; case "EmailAddress": sEmailAddress = config[1]; break; case "EmailPassword": sEmailPassword = config[1]; break; case "EnableAndroid": settings.bEnableAndroid = Convert.ToBoolean(config[1]); break; case "AndroidKey": sAndroidAPIKey = config[1]; break; case "KiteBarb": settings.iKiteDistanceBarb = Convert.ToInt32(config[1]); break; case "KiteWiz": settings.iKiteDistanceWiz = Convert.ToInt32(config[1]); break; case "KiteWitch": settings.iKiteDistanceWitch = Convert.ToInt32(config[1]); break; case "KiteDemon": settings.iKiteDistanceDemon = Convert.ToInt32(config[1]); break; case "PotBarb": settings.dEmergencyHealthPotionBarb = Convert.ToDouble(config[1]); break; case "PotMonk": settings.dEmergencyHealthPotionMonk = Convert.ToDouble(config[1]); break; case "PotWiz": settings.dEmergencyHealthPotionWiz = Convert.ToDouble(config[1]); break; case "PotWitch": settings.dEmergencyHealthPotionWitch = Convert.ToDouble(config[1]); break; case "PotDemon": settings.dEmergencyHealthPotionDemon = Convert.ToDouble(config[1]); break; case "GlobeBarb": settings.dEmergencyHealthGlobeBarb = Convert.ToDouble(config[1]); break; case "GlobeMonk": settings.dEmergencyHealthGlobeMonk = Convert.ToDouble(config[1]); break; case "GlobeWiz": settings.dEmergencyHealthGlobeWiz = Convert.ToDouble(config[1]); break; case "GlobeWitch": settings.dEmergencyHealthGlobeWitch = Convert.ToDouble(config[1]); break; case "GlobeDemon": settings.dEmergencyHealthGlobeDemon = Convert.ToDouble(config[1]); break; case "AOEBarbHealth": healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None); for (int i = 1; i <= healthlevels.Length; i++) { switch (i) { case 1: dictAvoidanceHealthBarb[219702] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthBarb[221225] = Convert.ToDouble(healthlevels[i - 1]); break; case 2: dictAvoidanceHealthBarb[84608] = Convert.ToDouble(healthlevels[i - 1]); break; case 3: dictAvoidanceHealthBarb[4803] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthBarb[4804] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthBarb[224225] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthBarb[247987] = Convert.ToDouble(healthlevels[i - 1]); break; case 4: dictAvoidanceHealthBarb[95868] = Convert.ToDouble(healthlevels[i - 1]); break; case 5: dictAvoidanceHealthBarb[5482] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthBarb[6578] = Convert.ToDouble(healthlevels[i - 1]); break; case 6: dictAvoidanceHealthBarb[108869] = Convert.ToDouble(healthlevels[i - 1]); break; case 7: dictAvoidanceHealthBarb[402] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthBarb[223675] = Convert.ToDouble(healthlevels[i - 1]); break; case 8: dictAvoidanceHealthBarb[3865] = Convert.ToDouble(healthlevels[i - 1]); break; case 9: dictAvoidanceHealthBarb[5212] = Convert.ToDouble(healthlevels[i - 1]); break; case 10: dictAvoidanceHealthBarb[123124] = Convert.ToDouble(healthlevels[i - 1]); break; case 11: dictAvoidanceHealthBarb[123839] = Convert.ToDouble(healthlevels[i - 1]); break; case 12: dictAvoidanceHealthBarb[4103] = Convert.ToDouble(healthlevels[i - 1]); break; case 13: dictAvoidanceHealthBarb[93837] = Convert.ToDouble(healthlevels[i - 1]); break; } } break; case "AOEMonkHealth": healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None); for (int i = 1; i <= healthlevels.Length; i++) { switch (i) { case 1: dictAvoidanceHealthMonk[219702] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthMonk[221225] = Convert.ToDouble(healthlevels[i - 1]); break; case 2: dictAvoidanceHealthMonk[84608] = Convert.ToDouble(healthlevels[i - 1]); break; case 3: dictAvoidanceHealthMonk[4803] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthMonk[4804] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthMonk[224225] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthMonk[247987] = Convert.ToDouble(healthlevels[i - 1]); break; case 4: dictAvoidanceHealthMonk[95868] = Convert.ToDouble(healthlevels[i - 1]); break; case 5: dictAvoidanceHealthMonk[5482] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthMonk[6578] = Convert.ToDouble(healthlevels[i - 1]); break; case 6: dictAvoidanceHealthMonk[108869] = Convert.ToDouble(healthlevels[i - 1]); break; case 7: dictAvoidanceHealthMonk[402] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthMonk[223675] = Convert.ToDouble(healthlevels[i - 1]); break; case 8: dictAvoidanceHealthMonk[3865] = Convert.ToDouble(healthlevels[i - 1]); break; case 9: dictAvoidanceHealthMonk[5212] = Convert.ToDouble(healthlevels[i - 1]); break; case 10: dictAvoidanceHealthMonk[123124] = Convert.ToDouble(healthlevels[i - 1]); break; case 11: dictAvoidanceHealthMonk[123839] = Convert.ToDouble(healthlevels[i - 1]); break; case 12: dictAvoidanceHealthMonk[4103] = Convert.ToDouble(healthlevels[i - 1]); break; case 13: dictAvoidanceHealthMonk[93837] = Convert.ToDouble(healthlevels[i - 1]); break; } } break; case "AOEWizardHealth": healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None); for (int i = 1; i <= healthlevels.Length; i++) { switch (i) { case 1: dictAvoidanceHealthWizard[219702] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWizard[221225] = Convert.ToDouble(healthlevels[i - 1]); break; case 2: dictAvoidanceHealthWizard[84608] = Convert.ToDouble(healthlevels[i - 1]); break; case 3: dictAvoidanceHealthWizard[4803] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWizard[4804] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWizard[224225] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWizard[247987] = Convert.ToDouble(healthlevels[i - 1]); break; case 4: dictAvoidanceHealthWizard[95868] = Convert.ToDouble(healthlevels[i - 1]); break; case 5: dictAvoidanceHealthWizard[5482] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWizard[6578] = Convert.ToDouble(healthlevels[i - 1]); break; case 6: dictAvoidanceHealthWizard[108869] = Convert.ToDouble(healthlevels[i - 1]); break; case 7: dictAvoidanceHealthWizard[402] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWizard[223675] = Convert.ToDouble(healthlevels[i - 1]); break; case 8: dictAvoidanceHealthWizard[3865] = Convert.ToDouble(healthlevels[i - 1]); break; case 9: dictAvoidanceHealthWizard[5212] = Convert.ToDouble(healthlevels[i - 1]); break; case 10: dictAvoidanceHealthWizard[123124] = Convert.ToDouble(healthlevels[i - 1]); break; case 11: dictAvoidanceHealthWizard[123839] = Convert.ToDouble(healthlevels[i - 1]); break; case 12: dictAvoidanceHealthWizard[4103] = Convert.ToDouble(healthlevels[i - 1]); break; case 13: dictAvoidanceHealthWizard[93837] = Convert.ToDouble(healthlevels[i - 1]); break; } } break; case "AOEWitchHealth": healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None); for (int i = 1; i <= healthlevels.Length; i++) { switch (i) { case 1: dictAvoidanceHealthWitch[219702] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWitch[221225] = Convert.ToDouble(healthlevels[i - 1]); break; case 2: dictAvoidanceHealthWitch[84608] = Convert.ToDouble(healthlevels[i - 1]); break; case 3: dictAvoidanceHealthWitch[4803] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWitch[4804] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWitch[224225] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWitch[247987] = Convert.ToDouble(healthlevels[i - 1]); break; case 4: dictAvoidanceHealthWitch[95868] = Convert.ToDouble(healthlevels[i - 1]); break; case 5: dictAvoidanceHealthWitch[5482] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWitch[6578] = Convert.ToDouble(healthlevels[i - 1]); break; case 6: dictAvoidanceHealthWitch[108869] = Convert.ToDouble(healthlevels[i - 1]); break; case 7: dictAvoidanceHealthWitch[402] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthWitch[223675] = Convert.ToDouble(healthlevels[i - 1]); break; case 8: dictAvoidanceHealthWitch[3865] = Convert.ToDouble(healthlevels[i - 1]); break; case 9: dictAvoidanceHealthWitch[5212] = Convert.ToDouble(healthlevels[i - 1]); break; case 10: dictAvoidanceHealthWitch[123124] = Convert.ToDouble(healthlevels[i - 1]); break; case 11: dictAvoidanceHealthWitch[123839] = Convert.ToDouble(healthlevels[i - 1]); break; case 12: dictAvoidanceHealthWitch[4103] = Convert.ToDouble(healthlevels[i - 1]); break; case 13: dictAvoidanceHealthWitch[93837] = Convert.ToDouble(healthlevels[i - 1]); break; } } break; case "AOEDemonHealth": healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None); for (int i = 1; i <= healthlevels.Length; i++) { switch (i) { case 1: dictAvoidanceHealthDemon[219702] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthDemon[221225] = Convert.ToDouble(healthlevels[i - 1]); break; case 2: dictAvoidanceHealthDemon[84608] = Convert.ToDouble(healthlevels[i - 1]); break; case 3: dictAvoidanceHealthDemon[4803] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthDemon[4804] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthDemon[224225] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthDemon[247987] = Convert.ToDouble(healthlevels[i - 1]); break; case 4: dictAvoidanceHealthDemon[95868] = Convert.ToDouble(healthlevels[i - 1]); break; case 5: dictAvoidanceHealthDemon[5482] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthDemon[6578] = Convert.ToDouble(healthlevels[i - 1]); break; case 6: dictAvoidanceHealthDemon[108869] = Convert.ToDouble(healthlevels[i - 1]); break; case 7: dictAvoidanceHealthDemon[402] = Convert.ToDouble(healthlevels[i - 1]); dictAvoidanceHealthDemon[223675] = Convert.ToDouble(healthlevels[i - 1]); break; case 8: dictAvoidanceHealthDemon[3865] = Convert.ToDouble(healthlevels[i - 1]); break; case 9: dictAvoidanceHealthDemon[5212] = Convert.ToDouble(healthlevels[i - 1]); break; case 10: dictAvoidanceHealthDemon[123124] = Convert.ToDouble(healthlevels[i - 1]); break; case 11: dictAvoidanceHealthDemon[123839] = Convert.ToDouble(healthlevels[i - 1]); break; case 12: dictAvoidanceHealthDemon[4103] = Convert.ToDouble(healthlevels[i - 1]); break; case 13: dictAvoidanceHealthDemon[93837] = Convert.ToDouble(healthlevels[i - 1]); break; } } break; case "AOERadius": healthlevels = config[1].Split(new string[] { " " }, StringSplitOptions.None); for (int i = 1; i <= healthlevels.Length; i++) { switch (i) { case 1: dictAvoidanceRadius[219702] = Convert.ToInt32(healthlevels[i - 1]); dictAvoidanceRadius[221225] = Convert.ToInt32(healthlevels[i - 1]); break; case 2: dictAvoidanceRadius[84608] = Convert.ToInt32(healthlevels[i - 1]); break; case 3: dictAvoidanceRadius[4803] = Convert.ToInt32(healthlevels[i - 1]); dictAvoidanceRadius[4804] = Convert.ToInt32(healthlevels[i - 1]); dictAvoidanceRadius[224225] = Convert.ToInt32(healthlevels[i - 1]); dictAvoidanceRadius[247987] = Convert.ToInt32(healthlevels[i - 1]); break; case 4: dictAvoidanceRadius[95868] = Convert.ToInt32(healthlevels[i - 1]); break; case 5: dictAvoidanceRadius[5482] = Convert.ToInt32(healthlevels[i - 1]); dictAvoidanceRadius[6578] = Convert.ToInt32(healthlevels[i - 1]); break; case 6: dictAvoidanceRadius[108869] = Convert.ToInt32(healthlevels[i - 1]); break; case 7: dictAvoidanceRadius[402] = Convert.ToInt32(healthlevels[i - 1]); dictAvoidanceRadius[223675] = Convert.ToInt32(healthlevels[i - 1]); break; case 8: dictAvoidanceRadius[3865] = Convert.ToInt32(healthlevels[i - 1]); break; case 9: dictAvoidanceRadius[5212] = Convert.ToInt32(healthlevels[i - 1]); break; case 10: dictAvoidanceRadius[123124] = Convert.ToInt32(healthlevels[i - 1]); break; case 11: dictAvoidanceRadius[123839] = Convert.ToInt32(healthlevels[i - 1]); break; case 12: dictAvoidanceRadius[4103] = Convert.ToInt32(healthlevels[i - 1]); break; case 13: dictAvoidanceRadius[93837] = Convert.ToInt32(healthlevels[i - 1]); break; } } break; } } } configReader.Close(); } if (!File.Exists(sTrinityEmailConfigFile)) { saveEmailConfiguration(); } using (StreamReader configReader = new StreamReader(sTrinityEmailConfigFile)) { while (!configReader.EndOfStream) { string[] config = configReader.ReadLine().Split('='); if (config != null) { switch (config[0]) { case "EnableEmail": settings.bEnableEmail = Convert.ToBoolean(config[1]); break; case "EmailAddress": sEmailAddress = config[1]; break; case "EmailPassword": sEmailPassword = config[1]; break; case "BotName": sBotName = config[1]; break; } } } configReader.Close(); } bMappedPlayerAbilities = false; } // ******************************************** // *********** CONFIG WINDOW REGION *********** // ******************************************** #region configWindow // First we create a variable that is of the "type" of the actual config window item - eg a "RadioButton" for each, well, radiobutton // Later on we will "Link" these variables to the ACTUAL items within the XAML file, so we can do things with the XAML stuff // I try to match the names of the variables here, with the "Name=" I give the item in the XAML - this isn't necessary, but makes things simpler private Button saveButton, defaultButton, testButton, sortButton, resetCombat, resetAOE0, resetAOE1, resetAOE2, resetAOE3, resetAOE4, resetWorld, resetItems, resetTown, resetAdvanced, resetMobile; private RadioButton checkTreasureIgnore, checkTreasureNormal, checkTreasurePrioritize, checkTreasureKamikaze, btnRulesGiles, btnRulesCustom, btnSalvage, btnSell, checkIgnoreAll, checkIgnoreNone; private CheckBox checkAvoidance, checkGlobes, checkCritical, checkGrave, checkBacktracking, checkCraftTomes, checkDesigns, checkFollower, checkGemEmerald, checkGemAmethyst, checkGemTopaz, checkGemRuby, checkIgnoreCorpses, checkMovementAbilities, checkTPS, checkLogStucks, checkUnstucker, checkExtendedRange, checkDebugInfo, checkProwl, checkAndroid, checkSelectiveWW, checkWaitWrath, checkGoblinWrath, checkFuryDumpWrath, checkFuryDumpAlways, checkProfileReload, checkMonkInna, checkKiteArchonOnly, checkWaitArchonAzmo, checkWrath90, checkEmail, checkLegendaryNotify; private Slider slideTriggerRange, slideWeapon, slideJewelry, slideArmor, slideGoldAmount, slideContainerRange, slideDestructibleRange, slideTPS, slideNotifyWeapon, slideNotifyJewelry, slideNotifyArmor, slideLootDelay, slideVaultDelay, slideKite0, slideKite2, slideKite3, slideKite4; private TextBox textTriggerRange, JewelryText, ArmorText, WeaponText, JewelryNotifyText, ArmorNotifyText, WeaponNotifyText, textGoldAmount, textContainerRange, textDestructibleRange, textTPS, textProwlKey, textAndroidKey, textLootDelay, textVaultDelay, textKite0, textKite2, textKite3, textKite4, txtEmailAddress, txtEmailPassword, txtBotName; private ComboBox comboWB, comboWY, comboAB, comboAY, comboJB, comboJY, comboGems, comboMisc, comboPotions, comboPotionLevel, comboLegendary; // I used an array of sliders for all the AOE stuff because there were just too many to handle separately, and they all affect the same sort of values // So looping through arrays and doing things to them this way meant less code and easier to add more AOE stuff in the future private Slider[,] slideAOERadius = new Slider[5, 13]; private Slider[,] slideAOEHealth = new Slider[5, 13]; private TextBox[,] textAOERadius = new TextBox[5, 13]; private TextBox[,] textAOEHealth = new TextBox[5, 13]; // Sliders for health potions, and health globes, for each class private Slider slidePot0, slidePot1, slidePot2, slidePot3, slidePot4, slideGlobe0, slideGlobe1, slideGlobe2, slideGlobe3, slideGlobe4; private TextBox textPot0, textPot1, textPot2, textPot3, textPot4, textGlobe0, textGlobe1, textGlobe2, textGlobe3, textGlobe4; // This is needed by DB, is essentially the ACTUAL window object itself private Window configWindow; // This is what "creates" the window public Window DisplayWindow { get { // Check we can actually find the .xaml file first - if not, report an error if (!File.Exists(sTrinityPluginPath + "GilesTrinity.xaml")) Log("ERROR: Can't find \"" + sTrinityPluginPath + "GilesTrinity.xaml\""); try { if (configWindow == null) { configWindow = new Window(); } StreamReader xamlStream = new StreamReader(sTrinityPluginPath + "GilesTrinity.xaml"); DependencyObject xamlContent = XamlReader.Load(xamlStream.BaseStream) as DependencyObject; configWindow.Content = xamlContent; // I'm not going to comment everything below - it's all pretty similar // Basically the concept is this: // You take the variable you created above (30 lines up or so), and you use "FindLogicalNode" to sort of "link" the variable, to that object within the XAML file // By using the "Name" tag as the way of finding it // After assigning the variable to the actual node in the XAML, you then need to add event handlers - so we can do things when the user makes changes to those elements // You can also alter settings and values of the nodes - eg the min-max values, the current value etc. - by using the variable we link // Now - the huge list below is because I have so many damned config options of different types! // Note that I do *NOT* have any events on text boxes - because I set all textboxes to uneditable/unchangeable - they are "read only" // I simply use them to show the user what the slider-value is currently set to (so when the slider changes, my code updates the text box) slideWeapon = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideWeaponScore") as Slider; slideWeapon.ValueChanged += trackScoreWeapons_Scroll; slideWeapon.SmallChange = 200; slideWeapon.LargeChange = 1000; slideWeapon.TickFrequency = 2000; slideWeapon.IsSnapToTickEnabled = true; slideJewelry = LogicalTreeHelper.FindLogicalNode(xamlContent, "sliderJewelryScore") as Slider; slideJewelry.ValueChanged += trackScoreJewelry_Scroll; slideJewelry.SmallChange = 100; slideJewelry.LargeChange = 500; slideJewelry.TickFrequency = 1000; slideJewelry.IsSnapToTickEnabled = true; slideArmor = LogicalTreeHelper.FindLogicalNode(xamlContent, "sliderArmorScore") as Slider; slideArmor.ValueChanged += trackScoreArmor_Scroll; slideArmor.SmallChange = 100; slideArmor.LargeChange = 500; slideArmor.TickFrequency = 1000; slideArmor.IsSnapToTickEnabled = true; slideGoldAmount = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGoldAmount") as Slider; slideGoldAmount.ValueChanged += trackGoldAmount_Scroll; slideGoldAmount.SmallChange = 5; slideGoldAmount.LargeChange = 20; slideGoldAmount.TickFrequency = 50; slideGoldAmount.IsSnapToTickEnabled = true; slideNotifyWeapon = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideWeaponNotifyScore") as Slider; slideNotifyWeapon.ValueChanged += trackNotifyWeapons_Scroll; slideNotifyWeapon.SmallChange = 200; slideNotifyWeapon.LargeChange = 1000; slideNotifyWeapon.TickFrequency = 2000; slideNotifyWeapon.IsSnapToTickEnabled = true; slideNotifyJewelry = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideJewelryNotifyScore") as Slider; slideNotifyJewelry.ValueChanged += trackNotifyJewelry_Scroll; slideNotifyJewelry.SmallChange = 100; slideNotifyJewelry.LargeChange = 500; slideNotifyJewelry.TickFrequency = 1000; slideNotifyJewelry.IsSnapToTickEnabled = true; slideNotifyArmor = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideArmorNotifyScore") as Slider; slideNotifyArmor.ValueChanged += trackNotifyArmor_Scroll; slideNotifyArmor.SmallChange = 100; slideNotifyArmor.LargeChange = 500; slideNotifyArmor.TickFrequency = 1000; slideNotifyArmor.IsSnapToTickEnabled = true; textGoldAmount = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGoldAmount") as TextBox; JewelryText = LogicalTreeHelper.FindLogicalNode(xamlContent, "JewelryScore") as TextBox; ArmorText = LogicalTreeHelper.FindLogicalNode(xamlContent, "ArmorScore") as TextBox; WeaponText = LogicalTreeHelper.FindLogicalNode(xamlContent, "WeaponScore") as TextBox; JewelryNotifyText = LogicalTreeHelper.FindLogicalNode(xamlContent, "JewelryNotifyScore") as TextBox; ArmorNotifyText = LogicalTreeHelper.FindLogicalNode(xamlContent, "ArmorNotifyScore") as TextBox; WeaponNotifyText = LogicalTreeHelper.FindLogicalNode(xamlContent, "WeaponNotifyScore") as TextBox; comboWB = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboWB") as ComboBox; comboWB.SelectionChanged += comboWB_changed; comboWY = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboWY") as ComboBox; comboWY.SelectionChanged += comboWY_changed; comboAB = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboAB") as ComboBox; comboAB.SelectionChanged += comboAB_changed; comboAY = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboAY") as ComboBox; comboAY.SelectionChanged += comboAY_changed; comboJB = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboJB") as ComboBox; comboJB.SelectionChanged += comboJB_changed; comboJY = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboJY") as ComboBox; comboJY.SelectionChanged += comboJY_changed; comboGems = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboGems") as ComboBox; comboGems.SelectionChanged += comboGems_changed; comboMisc = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboMisc") as ComboBox; comboMisc.SelectionChanged += comboMisc_changed; comboPotions = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboPotions") as ComboBox; comboPotions.SelectionChanged += comboPotions_changed; comboPotionLevel = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboPotionLevel") as ComboBox; comboPotionLevel.SelectionChanged += comboPotionLevel_changed; comboLegendary = LogicalTreeHelper.FindLogicalNode(xamlContent, "comboLegendary") as ComboBox; comboLegendary.SelectionChanged += comboLegendary_changed; checkCraftTomes = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkCraftTomes") as CheckBox; checkCraftTomes.Checked += checkCraftTomes_check; checkCraftTomes.Unchecked += checkCraftTomes_uncheck; checkDesigns = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkDesigns") as CheckBox; checkDesigns.Checked += checkDesigns_check; checkDesigns.Unchecked += checkDesigns_uncheck; checkFollower = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkFollower") as CheckBox; checkFollower.Checked += checkFollower_check; checkFollower.Unchecked += checkFollower_uncheck; checkGemEmerald = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGemEmerald") as CheckBox; checkGemEmerald.Checked += checkEmerald_check; checkGemEmerald.Unchecked += checkEmerald_uncheck; checkGemTopaz = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGemTopaz") as CheckBox; checkGemTopaz.Checked += checkTopaz_check; checkGemTopaz.Unchecked += checkTopaz_uncheck; checkGemAmethyst = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGemAmethyst") as CheckBox; checkGemAmethyst.Checked += checkAmethyst_check; checkGemAmethyst.Unchecked += checkAmethyst_uncheck; checkGemRuby = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGemRuby") as CheckBox; checkGemRuby.Checked += checkRuby_check; checkGemRuby.Unchecked += checkRuby_uncheck; checkMovementAbilities = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkMovementAbilities") as CheckBox; checkMovementAbilities.Checked += checkMovementAbilities_check; checkMovementAbilities.Unchecked += checkMovementAbilities_uncheck; btnRulesGiles = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnRulesGiles") as RadioButton; btnRulesGiles.Checked += btnRulesGiles_check; btnRulesCustom = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnRulesCustom") as RadioButton; btnRulesCustom.Checked += btnRulesCustom_check; btnSalvage = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnSalvage") as RadioButton; btnSalvage.Checked += btnSalvage_check; btnSell = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnSell") as RadioButton; btnSell.Checked += btnSell_check; testButton = LogicalTreeHelper.FindLogicalNode(xamlContent, "buttonTest") as Button; testButton.Click += buttonTest_Click; sortButton = LogicalTreeHelper.FindLogicalNode(xamlContent, "buttonSort") as Button; sortButton.Click += buttonSort_Click; checkTreasureIgnore = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnTreasureIgnore") as RadioButton; checkTreasureIgnore.Checked += checkTreasureIgnore_check; checkTreasureNormal = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnTreasureNormal") as RadioButton; checkTreasureNormal.Checked += checkTreasureNormal_check; checkTreasurePrioritize = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnTreasurePrioritize") as RadioButton; checkTreasurePrioritize.Checked += checkTreasurePrioritize_check; checkTreasureKamikaze = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnTreasureKamikaze") as RadioButton; checkTreasureKamikaze.Checked += checkTreasureKamikaze_check; checkDebugInfo = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkDebugInfo") as CheckBox; checkDebugInfo.Checked += checkDebugInfo_check; checkDebugInfo.Unchecked += checkDebugInfo_uncheck; // prowl stuff checkProwl = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkProwl") as CheckBox; checkProwl.Checked += checkProwl_check; checkProwl.Unchecked += checkProwl_uncheck; textProwlKey = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtProwlAPI") as TextBox; textProwlKey.TextChanged += textProwl_change; checkLegendaryNotify = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkLegendaryNotify") as CheckBox; checkLegendaryNotify.Checked += checkLegendaryNotifyScore_check; checkLegendaryNotify.Unchecked += checkLegendaryNotifyScore_uncheck; //Email stuff checkEmail = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkEmail") as CheckBox; checkEmail.Checked += checkEmail_check; checkEmail.Unchecked += checkEmail_uncheck; txtEmailAddress = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtEmailAddress") as TextBox; txtEmailAddress.TextChanged += txtEmailAddress_change; txtEmailPassword = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtEmailPassword") as TextBox; txtEmailPassword.TextChanged += txtEmailPassword_change; txtBotName = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtBotName") as TextBox; txtBotName.TextChanged += txtBotName_change; // android stuff checkAndroid = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkAndroid") as CheckBox; checkAndroid.Checked += checkAndroid_check; checkAndroid.Unchecked += checkAndroid_uncheck; textAndroidKey = LogicalTreeHelper.FindLogicalNode(xamlContent, "txtAndroidAPI") as TextBox; textAndroidKey.TextChanged += textAndroid_change; checkTPS = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkTPS") as CheckBox; checkTPS.Checked += checkTPS_check; checkTPS.Unchecked += checkTPS_uncheck; textTPS = LogicalTreeHelper.FindLogicalNode(xamlContent, "textTPS") as TextBox; slideTPS = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideTPS") as Slider; slideTPS.ValueChanged += trackTPS_Scroll; slideTPS.SmallChange = 1; slideTPS.LargeChange = 1; slideTPS.TickFrequency = 5; slideTPS.IsSnapToTickEnabled = false; checkLogStucks = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkLogStucks") as CheckBox; checkLogStucks.Checked += checkLogStucks_check; checkLogStucks.Unchecked += checkLogStucks_uncheck; checkUnstucker = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkUnstucker") as CheckBox; checkUnstucker.Checked += checkUnstucker_check; checkUnstucker.Unchecked += checkUnstucker_uncheck; checkProfileReload = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkProfileReload") as CheckBox; checkProfileReload.Checked += checkProfileReload_check; checkProfileReload.Unchecked += checkProfileReload_uncheck; checkExtendedRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkExtendedRange") as CheckBox; checkExtendedRange.Checked += checkExtendedRange_check; checkExtendedRange.Unchecked += checkExtendedRange_uncheck; checkSelectiveWW = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkSelectiveWW") as CheckBox; checkSelectiveWW.Checked += checkSelectiveWW_check; checkSelectiveWW.Unchecked += checkSelectiveWW_uncheck; checkWrath90 = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkWrath90") as CheckBox; checkWrath90.Checked += checkWrath90_check; checkWrath90.Unchecked += checkWrath90_uncheck; checkKiteArchonOnly = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkKiteArchonOnly") as CheckBox; checkKiteArchonOnly.Checked += checkKiteArchonOnly_check; checkKiteArchonOnly.Unchecked += checkKiteArchonOnly_uncheck; checkWaitArchonAzmo = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkWaitArchonAzmo") as CheckBox; checkWaitArchonAzmo.Checked += checkWaitArchonAzmo_check; checkWaitArchonAzmo.Unchecked += checkWaitArchonAzmo_uncheck; checkWaitWrath = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkWaitWrath") as CheckBox; checkWaitWrath.Checked += checkWaitWrath_check; checkWaitWrath.Unchecked += checkWaitWrath_uncheck; checkGoblinWrath = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGoblinWrath") as CheckBox; checkGoblinWrath.Checked += checkGoblinWrath_check; checkGoblinWrath.Unchecked += checkGoblinWrath_uncheck; checkFuryDumpWrath = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkFuryDumpWrath") as CheckBox; checkFuryDumpWrath.Checked += checkFuryDumpWrath_check; checkFuryDumpWrath.Unchecked += checkFuryDumpWrath_uncheck; checkFuryDumpAlways = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkFuryDumpAlways") as CheckBox; checkFuryDumpAlways.Checked += checkFuryDumpAlways_check; checkFuryDumpAlways.Unchecked += checkFuryDumpAlways_uncheck; checkMonkInna = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkMonkInna") as CheckBox; checkMonkInna.Checked += checkMonkInna_check; checkMonkInna.Unchecked += checkMonkInna_uncheck; checkBacktracking = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkBacktracking") as CheckBox; checkBacktracking.Checked += checkBacktracking_check; checkBacktracking.Unchecked += checkBacktracking_uncheck; checkAvoidance = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkAvoidance") as CheckBox; checkAvoidance.Checked += checkAvoidance_check; checkAvoidance.Unchecked += checkAvoidance_uncheck; checkGlobes = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGlobes") as CheckBox; checkGlobes.Checked += checkGlobes_check; checkGlobes.Unchecked += checkGlobes_uncheck; checkCritical = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkCritical") as CheckBox; checkCritical.Checked += checkCritical_check; checkCritical.Unchecked += checkCritical_uncheck; checkGrave = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkGrave") as CheckBox; checkGrave.Checked += checkCritical_check; checkGrave.Unchecked += checkCritical_uncheck; textTriggerRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "textTriggerRange") as TextBox; slideTriggerRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideTriggerRange") as Slider; slideTriggerRange.ValueChanged += trackTriggerRange_Scroll; slideTriggerRange.SmallChange = 1; slideTriggerRange.LargeChange = 1; slideTriggerRange.TickFrequency = 5; slideTriggerRange.IsSnapToTickEnabled = false; slideLootDelay = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideLootDelay") as Slider; slideLootDelay.ValueChanged += slideLootDelay_Scroll; slideLootDelay.SmallChange = 100; slideLootDelay.LargeChange = 100; slideLootDelay.TickFrequency = 100; slideLootDelay.IsSnapToTickEnabled = true; textLootDelay = LogicalTreeHelper.FindLogicalNode(xamlContent, "textLootDelay") as TextBox; slideVaultDelay = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideVaultDelay") as Slider; slideVaultDelay.ValueChanged += slideVaultDelay_Scroll; slideVaultDelay.SmallChange = 100; slideVaultDelay.LargeChange = 100; slideVaultDelay.TickFrequency = 100; slideVaultDelay.IsSnapToTickEnabled = true; textVaultDelay = LogicalTreeHelper.FindLogicalNode(xamlContent, "textVaultDelay") as TextBox; slideKite0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideKite0") as Slider; slideKite0.ValueChanged += slideKite0_Scroll; slideKite0.IsSnapToTickEnabled = true; textKite0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textKite0") as TextBox; slideKite2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideKite2") as Slider; slideKite2.ValueChanged += slideKite2_Scroll; slideKite2.IsSnapToTickEnabled = true; textKite2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textKite2") as TextBox; slideKite3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideKite3") as Slider; slideKite3.ValueChanged += slideKite3_Scroll; slideKite3.IsSnapToTickEnabled = true; textKite3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textKite3") as TextBox; slideKite4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideKite4") as Slider; slideKite4.ValueChanged += slideKite4_Scroll; slideKite4.IsSnapToTickEnabled = true; textKite4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textKite4") as TextBox; checkIgnoreAll = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnIgnoreAll") as RadioButton; checkIgnoreAll.Checked += checkIgnoreAll_check; checkIgnoreNone = LogicalTreeHelper.FindLogicalNode(xamlContent, "btnIgnoreNone") as RadioButton; checkIgnoreNone.Checked += checkIgnoreNone_check; checkIgnoreCorpses = LogicalTreeHelper.FindLogicalNode(xamlContent, "checkIgnoreCorpses") as CheckBox; checkIgnoreCorpses.Checked += checkIgnoreCorpses_check; checkIgnoreCorpses.Unchecked += checkIgnoreCorpses_uncheck; textContainerRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "textContainerRange") as TextBox; textDestructibleRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "textDestructibleRange") as TextBox; slideContainerRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideContainerRange") as Slider; slideContainerRange.ValueChanged += trackContainerRange_Scroll; slideContainerRange.SmallChange = 1; slideContainerRange.LargeChange = 1; slideContainerRange.TickFrequency = 5; slideContainerRange.IsSnapToTickEnabled = false; slideDestructibleRange = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideDestructibleRange") as Slider; slideDestructibleRange.ValueChanged += trackDestructibleRange_Scroll; slideDestructibleRange.SmallChange = 1; slideDestructibleRange.LargeChange = 1; slideDestructibleRange.TickFrequency = 5; slideDestructibleRange.IsSnapToTickEnabled = false; // Globe & pot sliders slidePot0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot0") as Slider; slidePot0.ValueChanged += slidePot0_Scroll; slidePot1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot1") as Slider; slidePot1.ValueChanged += slidePot1_Scroll; slidePot2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot2") as Slider; slidePot2.ValueChanged += slidePot2_Scroll; slidePot3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot3") as Slider; slidePot3.ValueChanged += slidePot3_Scroll; slidePot4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slidePot4") as Slider; slidePot4.ValueChanged += slidePot4_Scroll; slideGlobe0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe0") as Slider; slideGlobe0.ValueChanged += slideGlobe0_Scroll; slideGlobe1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe1") as Slider; slideGlobe1.ValueChanged += slideGlobe1_Scroll; slideGlobe2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe2") as Slider; slideGlobe2.ValueChanged += slideGlobe2_Scroll; slideGlobe3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe3") as Slider; slideGlobe3.ValueChanged += slideGlobe3_Scroll; slideGlobe4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "slideGlobe4") as Slider; slideGlobe4.ValueChanged += slideGlobe4_Scroll; textPot0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot0") as TextBox; textPot1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot1") as TextBox; textPot2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot2") as TextBox; textPot3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot3") as TextBox; textPot4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textPot4") as TextBox; textGlobe0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe0") as TextBox; textGlobe1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe1") as TextBox; textGlobe2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe2") as TextBox; textGlobe3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe3") as TextBox; textGlobe4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "textGlobe4") as TextBox; // See how much less code for loops have!? The following links up to, and assigns events to, the 100 or so slider bars and textboxes for AOE config stuff! for (int i = 0; i <= 4; i++) { for (int n = 0; n <= 12; n++) { string sThisNode; sThisNode = "slideRadius" + i.ToString() + "_" + (n + 1).ToString(); slideAOERadius[i, n] = LogicalTreeHelper.FindLogicalNode(xamlContent, sThisNode) as Slider; sThisNode = "slideHealth" + i.ToString() + "_" + (n + 1).ToString(); slideAOEHealth[i, n] = LogicalTreeHelper.FindLogicalNode(xamlContent, sThisNode) as Slider; sThisNode = "textRadius" + i.ToString() + "_" + (n + 1).ToString(); textAOERadius[i, n] = LogicalTreeHelper.FindLogicalNode(xamlContent, sThisNode) as TextBox; sThisNode = "textHealth" + i.ToString() + "_" + (n + 1).ToString(); textAOEHealth[i, n] = LogicalTreeHelper.FindLogicalNode(xamlContent, sThisNode) as TextBox; slideAOERadius[i, n].ValueChanged += trackAOERadius_Scroll; slideAOEHealth[i, n].ValueChanged += trackAOEHealth_Scroll; slideAOERadius[i, n].SmallChange = 1; slideAOERadius[i, n].LargeChange = 1; slideAOERadius[i, n].TickFrequency = 1; slideAOERadius[i, n].IsSnapToTickEnabled = true; slideAOEHealth[i, n].SmallChange = 1; slideAOEHealth[i, n].LargeChange = 1; slideAOEHealth[i, n].TickFrequency = 1; slideAOEHealth[i, n].IsSnapToTickEnabled = true; } } // Finally the "defaults" button, and the save config button defaultButton = LogicalTreeHelper.FindLogicalNode(xamlContent, "buttonDefaults") as Button; defaultButton.Click += buttonDefaults_Click; resetCombat = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetCombat") as Button; resetCombat.Click += resetCombat_Click; resetAOE0 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE0") as Button; resetAOE0.Click += resetAOE0_Click; resetAOE1 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE1") as Button; resetAOE1.Click += resetAOE1_Click; resetAOE2 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE2") as Button; resetAOE2.Click += resetAOE2_Click; resetAOE3 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE3") as Button; resetAOE3.Click += resetAOE3_Click; resetAOE4 = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAOE4") as Button; resetAOE4.Click += resetAOE4_Click; resetWorld = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetWorld") as Button; resetWorld.Click += resetWorld_Click; resetItems = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetItems") as Button; resetItems.Click += resetItems_Click; resetTown = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetTown") as Button; resetTown.Click += resetTown_Click; resetAdvanced = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetAdvanced") as Button; resetAdvanced.Click += resetAdvanced_Click; resetMobile = LogicalTreeHelper.FindLogicalNode(xamlContent, "ResetMobile") as Button; resetMobile.Click += resetMobile_Click; saveButton = LogicalTreeHelper.FindLogicalNode(xamlContent, "buttonSave") as Button; saveButton.Click += buttonSave_Click; UserControl mainControl = LogicalTreeHelper.FindLogicalNode(xamlContent, "mainControl") as UserControl; // Set height and width and window title of main window configWindow.Height = mainControl.Height + 30; configWindow.Width = mainControl.Width; configWindow.Title = "Giles Trinity"; // Event handling for the config window loading up/closing configWindow.Loaded += configWindow_Loaded; configWindow.Closed += configWindow_Closed; // And finally put all of this content in effect configWindow.Content = xamlContent; } catch (XamlParseException ex) { // Log specific XAML exceptions that might have happened above Log(ex.ToString()); } catch (Exception ex) { // Log any other issues Log(ex.ToString()); } return configWindow; } } // The below are all event handlers for all the window-elements within the config window // WARNING: If you use code to alter the value of something that has an event attached... // For example a slider - then your code automatically also fires the event for that slider // I use "suppresseventchanges" to make sure that event code ONLY gets called from the USER changing values // And NOT from my own code trying to change values private static bool bSuppressEventChanges = false; // And now the start of all the events, starting with the largest ones - the AOE stuff - which has to figure out WHICH slider you changed // And then change the appropriate variable(s) private void trackAOEHealth_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; Slider thisslider = sender as Slider; string sSliderName = thisslider.Name.Substring(11); int iClass = Convert.ToInt32(sSliderName.Substring(0, 1)); int iAvoid = Convert.ToInt32(sSliderName.Substring(2)); double dThisHealthLimit = (Math.Round(thisslider.Value) / 100); textAOEHealth[iClass, iAvoid - 1].Text = (dThisHealthLimit * 100).ToString(); switch (iClass) { case 0: // Barbs switch (iAvoid) { case 1: dictAvoidanceHealthBarb[219702] = dThisHealthLimit; dictAvoidanceHealthBarb[221225] = dThisHealthLimit; break; case 2: dictAvoidanceHealthBarb[84608] = dThisHealthLimit; break; case 3: dictAvoidanceHealthBarb[4803] = dThisHealthLimit; dictAvoidanceHealthBarb[4804] = dThisHealthLimit; dictAvoidanceHealthBarb[224225] = dThisHealthLimit; dictAvoidanceHealthBarb[247987] = dThisHealthLimit; break; case 4: dictAvoidanceHealthBarb[95868] = dThisHealthLimit; break; case 5: dictAvoidanceHealthBarb[5482] = dThisHealthLimit; dictAvoidanceHealthBarb[6578] = dThisHealthLimit; break; case 6: dictAvoidanceHealthBarb[108869] = dThisHealthLimit; break; case 7: dictAvoidanceHealthBarb[402] = dThisHealthLimit; dictAvoidanceHealthBarb[223675] = dThisHealthLimit; break; case 8: dictAvoidanceHealthBarb[3865] = dThisHealthLimit; break; case 9: dictAvoidanceHealthBarb[5212] = dThisHealthLimit; break; case 10: dictAvoidanceHealthBarb[123124] = dThisHealthLimit; break; case 11: dictAvoidanceHealthBarb[123839] = dThisHealthLimit; break; case 12: dictAvoidanceHealthBarb[4103] = dThisHealthLimit; break; case 13: dictAvoidanceHealthBarb[93837] = dThisHealthLimit; break; } break; case 1: // Monks switch (iAvoid) { case 1: dictAvoidanceHealthMonk[219702] = dThisHealthLimit; dictAvoidanceHealthMonk[221225] = dThisHealthLimit; break; case 2: dictAvoidanceHealthMonk[84608] = dThisHealthLimit; break; case 3: dictAvoidanceHealthMonk[4803] = dThisHealthLimit; dictAvoidanceHealthMonk[4804] = dThisHealthLimit; dictAvoidanceHealthMonk[224225] = dThisHealthLimit; dictAvoidanceHealthMonk[247987] = dThisHealthLimit; break; case 4: dictAvoidanceHealthMonk[95868] = dThisHealthLimit; break; case 5: dictAvoidanceHealthMonk[5482] = dThisHealthLimit; dictAvoidanceHealthMonk[6578] = dThisHealthLimit; break; case 6: dictAvoidanceHealthMonk[108869] = dThisHealthLimit; break; case 7: dictAvoidanceHealthMonk[402] = dThisHealthLimit; dictAvoidanceHealthMonk[223675] = dThisHealthLimit; break; case 8: dictAvoidanceHealthMonk[3865] = dThisHealthLimit; break; case 9: dictAvoidanceHealthMonk[5212] = dThisHealthLimit; break; case 10: dictAvoidanceHealthMonk[123124] = dThisHealthLimit; break; case 11: dictAvoidanceHealthMonk[123839] = dThisHealthLimit; break; case 12: dictAvoidanceHealthMonk[4103] = dThisHealthLimit; break; case 13: dictAvoidanceHealthMonk[93837] = dThisHealthLimit; break; } break; case 2: // Wizards switch (iAvoid) { case 1: dictAvoidanceHealthWizard[219702] = dThisHealthLimit; dictAvoidanceHealthWizard[221225] = dThisHealthLimit; break; case 2: dictAvoidanceHealthWizard[84608] = dThisHealthLimit; break; case 3: dictAvoidanceHealthWizard[4803] = dThisHealthLimit; dictAvoidanceHealthWizard[4804] = dThisHealthLimit; dictAvoidanceHealthWizard[224225] = dThisHealthLimit; dictAvoidanceHealthWizard[247987] = dThisHealthLimit; break; case 4: dictAvoidanceHealthWizard[95868] = dThisHealthLimit; break; case 5: dictAvoidanceHealthWizard[5482] = dThisHealthLimit; dictAvoidanceHealthWizard[6578] = dThisHealthLimit; break; case 6: dictAvoidanceHealthWizard[108869] = dThisHealthLimit; break; case 7: dictAvoidanceHealthWizard[402] = dThisHealthLimit; dictAvoidanceHealthWizard[223675] = dThisHealthLimit; break; case 8: dictAvoidanceHealthWizard[3865] = dThisHealthLimit; break; case 9: dictAvoidanceHealthWizard[5212] = dThisHealthLimit; break; case 10: dictAvoidanceHealthWizard[123124] = dThisHealthLimit; break; case 11: dictAvoidanceHealthWizard[123839] = dThisHealthLimit; break; case 12: dictAvoidanceHealthWizard[4103] = dThisHealthLimit; break; case 13: dictAvoidanceHealthWizard[93837] = dThisHealthLimit; break; } break; case 3: // WD's switch (iAvoid) { case 1: dictAvoidanceHealthWitch[219702] = dThisHealthLimit; dictAvoidanceHealthWitch[221225] = dThisHealthLimit; break; case 2: dictAvoidanceHealthWitch[84608] = dThisHealthLimit; break; case 3: dictAvoidanceHealthWitch[4803] = dThisHealthLimit; dictAvoidanceHealthWitch[4804] = dThisHealthLimit; dictAvoidanceHealthWitch[224225] = dThisHealthLimit; dictAvoidanceHealthWitch[247987] = dThisHealthLimit; break; case 4: dictAvoidanceHealthWitch[95868] = dThisHealthLimit; break; case 5: dictAvoidanceHealthWitch[5482] = dThisHealthLimit; dictAvoidanceHealthWitch[6578] = dThisHealthLimit; dictAvoidanceHealthWitch[224225] = dThisHealthLimit; dictAvoidanceHealthWitch[247987] = dThisHealthLimit; break; case 6: dictAvoidanceHealthWitch[108869] = dThisHealthLimit; break; case 7: dictAvoidanceHealthWitch[402] = dThisHealthLimit; dictAvoidanceHealthWitch[223675] = dThisHealthLimit; break; case 8: dictAvoidanceHealthWitch[3865] = dThisHealthLimit; break; case 9: dictAvoidanceHealthWitch[5212] = dThisHealthLimit; break; case 10: dictAvoidanceHealthWitch[123124] = dThisHealthLimit; break; case 11: dictAvoidanceHealthWitch[123839] = dThisHealthLimit; break; case 12: dictAvoidanceHealthWitch[4103] = dThisHealthLimit; break; case 13: dictAvoidanceHealthWitch[93837] = dThisHealthLimit; break; } break; case 4: // DH's switch (iAvoid) { case 1: dictAvoidanceHealthDemon[219702] = dThisHealthLimit; dictAvoidanceHealthDemon[221225] = dThisHealthLimit; break; case 2: dictAvoidanceHealthDemon[84608] = dThisHealthLimit; break; case 3: dictAvoidanceHealthDemon[4803] = dThisHealthLimit; dictAvoidanceHealthDemon[4804] = dThisHealthLimit; dictAvoidanceHealthDemon[224225] = dThisHealthLimit; dictAvoidanceHealthDemon[247987] = dThisHealthLimit; break; case 4: dictAvoidanceHealthDemon[95868] = dThisHealthLimit; break; case 5: dictAvoidanceHealthDemon[5482] = dThisHealthLimit; dictAvoidanceHealthDemon[6578] = dThisHealthLimit; break; case 6: dictAvoidanceHealthDemon[108869] = dThisHealthLimit; break; case 7: dictAvoidanceHealthDemon[402] = dThisHealthLimit; dictAvoidanceHealthDemon[223675] = dThisHealthLimit; break; case 8: dictAvoidanceHealthDemon[3865] = dThisHealthLimit; break; case 9: dictAvoidanceHealthDemon[5212] = dThisHealthLimit; break; case 10: dictAvoidanceHealthDemon[123124] = dThisHealthLimit; break; case 11: dictAvoidanceHealthDemon[123839] = dThisHealthLimit; break; case 12: dictAvoidanceHealthDemon[4103] = dThisHealthLimit; break; case 13: dictAvoidanceHealthDemon[93837] = dThisHealthLimit; break; } break; } bMappedPlayerAbilities = false; } private void trackAOERadius_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; Slider thisslider = sender as Slider; string sSliderName = thisslider.Name.Substring(11); int iClass = Convert.ToInt32(sSliderName.Substring(0, 1)); int iAvoid = Convert.ToInt32(sSliderName.Substring(2)); int iThisAvoidRadius = (int)Math.Round(thisslider.Value); textAOERadius[iClass, iAvoid - 1].Text = iThisAvoidRadius.ToString(); switch (iAvoid) { case 1: dictAvoidanceRadius[219702] = iThisAvoidRadius; dictAvoidanceRadius[221225] = iThisAvoidRadius; break; case 2: dictAvoidanceRadius[84608] = iThisAvoidRadius; break; case 3: dictAvoidanceRadius[4803] = iThisAvoidRadius; dictAvoidanceRadius[4804] = iThisAvoidRadius; dictAvoidanceRadius[224225] = iThisAvoidRadius; dictAvoidanceRadius[247987] = iThisAvoidRadius; break; case 4: dictAvoidanceRadius[95868] = iThisAvoidRadius; break; case 5: dictAvoidanceRadius[5482] = iThisAvoidRadius; dictAvoidanceRadius[6578] = iThisAvoidRadius; break; case 6: dictAvoidanceRadius[108869] = iThisAvoidRadius; break; case 7: dictAvoidanceRadius[402] = iThisAvoidRadius; dictAvoidanceRadius[223675] = iThisAvoidRadius; break; case 8: dictAvoidanceRadius[3865] = iThisAvoidRadius; break; case 9: dictAvoidanceRadius[5212] = iThisAvoidRadius; break; case 10: dictAvoidanceRadius[123124] = iThisAvoidRadius; break; case 11: dictAvoidanceRadius[123839] = iThisAvoidRadius; break; case 12: dictAvoidanceRadius[4103] = iThisAvoidRadius; break; case 13: dictAvoidanceRadius[93837] = iThisAvoidRadius; break; } bool bOldSuppress = bSuppressEventChanges; bSuppressEventChanges = true; for (int i = 0; i <= 4; i++) { if (i != iClass) { slideAOERadius[i, iAvoid - 1].Value = iThisAvoidRadius; textAOERadius[i, iAvoid - 1].Text = iThisAvoidRadius.ToString(); } } bSuppressEventChanges = bOldSuppress; bMappedPlayerAbilities = false; } private void checkIgnoreCorpses_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bIgnoreCorpses = true; } private void checkIgnoreCorpses_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bIgnoreCorpses = false; } private void slidePot0_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slidePot0.Value = Math.Round(slidePot0.Value); textPot0.Text = slidePot0.Value.ToString(); settings.dEmergencyHealthPotionBarb = (Math.Round(slidePot0.Value) / 100); } private void slidePot1_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slidePot1.Value = Math.Round(slidePot1.Value); textPot1.Text = slidePot1.Value.ToString(); settings.dEmergencyHealthPotionMonk = (Math.Round(slidePot1.Value) / 100); } private void slidePot2_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slidePot2.Value = Math.Round(slidePot2.Value); textPot2.Text = slidePot2.Value.ToString(); settings.dEmergencyHealthPotionWiz = (Math.Round(slidePot2.Value) / 100); } private void slidePot3_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slidePot3.Value = Math.Round(slidePot3.Value); textPot3.Text = slidePot3.Value.ToString(); settings.dEmergencyHealthPotionWitch = (Math.Round(slidePot3.Value) / 100); } private void slidePot4_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slidePot4.Value = Math.Round(slidePot4.Value); textPot4.Text = slidePot4.Value.ToString(); settings.dEmergencyHealthPotionDemon = (Math.Round(slidePot4.Value) / 100); } private void slideGlobe0_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideGlobe0.Value = Math.Round(slideGlobe0.Value); textGlobe0.Text = slideGlobe0.Value.ToString(); settings.dEmergencyHealthGlobeBarb = (Math.Round(slideGlobe0.Value) / 100); } private void slideGlobe1_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideGlobe1.Value = Math.Round(slideGlobe1.Value); textGlobe1.Text = slideGlobe1.Value.ToString(); settings.dEmergencyHealthGlobeMonk = (Math.Round(slideGlobe1.Value) / 100); } private void slideGlobe2_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideGlobe2.Value = Math.Round(slideGlobe2.Value); textGlobe2.Text = slideGlobe2.Value.ToString(); settings.dEmergencyHealthGlobeWiz = (Math.Round(slideGlobe2.Value) / 100); } private void slideGlobe3_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideGlobe3.Value = Math.Round(slideGlobe3.Value); textGlobe3.Text = slideGlobe3.Value.ToString(); settings.dEmergencyHealthGlobeWitch = (Math.Round(slideGlobe3.Value) / 100); } private void slideGlobe4_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideGlobe4.Value = Math.Round(slideGlobe4.Value); textGlobe4.Text = slideGlobe4.Value.ToString(); settings.dEmergencyHealthGlobeDemon = (Math.Round(slideGlobe4.Value) / 100); } private void trackContainerRange_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideContainerRange.Value = Math.Round(slideContainerRange.Value); textContainerRange.Text = slideContainerRange.Value.ToString(); settings.iContainerOpenRange = Math.Round(slideContainerRange.Value); } private void trackDestructibleRange_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideDestructibleRange.Value = Math.Round(slideDestructibleRange.Value); textDestructibleRange.Text = slideDestructibleRange.Value.ToString(); settings.iDestructibleAttackRange = Math.Round(slideDestructibleRange.Value); } private void checkIgnoreAll_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bIgnoreAllShrines = true; } private void checkIgnoreNone_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bIgnoreAllShrines = false; } private void trackGoldAmount_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideGoldAmount.Value = Math.Round(slideGoldAmount.Value); textGoldAmount.Text = slideGoldAmount.Value.ToString(); settings.iMinimumGoldStack = Convert.ToInt32(Math.Round(slideGoldAmount.Value)); } private void comboWB_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterBlueWeapons = Convert.ToInt32(comboWB.SelectedValue); } private void comboLegendary_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterLegendary = Convert.ToInt32(comboLegendary.SelectedValue); } private void comboWY_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterYellowWeapons = Convert.ToInt32(comboWY.SelectedValue); } private void comboAB_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterBlueArmor = Convert.ToInt32(comboAB.SelectedValue); } private void comboAY_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterYellowArmor = Convert.ToInt32(comboAY.SelectedValue); } private void comboJB_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterBlueJewelry = Convert.ToInt32(comboJB.SelectedValue); } private void comboJY_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterYellowJewelry = Convert.ToInt32(comboJY.SelectedValue); } private void comboGems_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterGems = Convert.ToInt32(comboGems.SelectedValue); } private void comboMisc_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterMisc = Convert.ToInt32(comboMisc.SelectedValue); } private void comboPotions_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterPotions = Convert.ToInt32(comboPotions.SelectedValue); } private void comboPotionLevel_changed(object sender, SelectionChangedEventArgs e) { if (bSuppressEventChanges) return; settings.iFilterPotionLevel = Convert.ToInt32(comboPotionLevel.SelectedValue); } private void checkFollower_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bPickupFollower = true; } private void checkFollower_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bPickupFollower = false; } private void checkDesigns_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bPickupPlans = true; } private void checkDesigns_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bPickupPlans = false; } private void checkCraftTomes_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bPickupCraftTomes = true; } private void checkCraftTomes_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bPickupCraftTomes = false; } private void btnRulesGiles_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bUseGilesFilters = true; } private void btnRulesCustom_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bUseGilesFilters = false; } private void btnSalvage_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bSalvageJunk = true; } private void btnSell_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bSalvageJunk = false; } private void checkEmerald_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGemsEmerald = true; } private void checkEmerald_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGemsEmerald = false; } private void checkAmethyst_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGemsAmethyst = true; } private void checkAmethyst_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGemsAmethyst = false; } private void checkTopaz_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGemsTopaz = true; } private void checkTopaz_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGemsTopaz = false; } private void checkRuby_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGemsRuby = true; } private void checkRuby_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGemsRuby = false; } private void checkAndroid_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableAndroid = true; } private void checkAndroid_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableAndroid = false; } private void textAndroid_change(object sender, TextChangedEventArgs e) { if (bSuppressEventChanges) return; sAndroidAPIKey = textAndroidKey.Text; } private void checkProwl_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableProwl = true; } private void checkProwl_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableProwl = false; } private void textProwl_change(object sender, TextChangedEventArgs e) { if (bSuppressEventChanges) return; sProwlAPIKey = textProwlKey.Text; } private void checkEmail_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableEmail = true; } private void checkEmail_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableEmail = false; } private void checkLegendaryNotifyScore_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableLegendaryNotifyScore = true; } private void checkLegendaryNotifyScore_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableLegendaryNotifyScore = false; } private void txtEmailAddress_change(object sender, TextChangedEventArgs e) { if (bSuppressEventChanges) return; sEmailAddress = txtEmailAddress.Text; } private void txtEmailPassword_change(object sender, TextChangedEventArgs e) { if (bSuppressEventChanges) return; sEmailPassword = txtEmailPassword.Text; } private void txtBotName_change(object sender, TextChangedEventArgs e) { if (bSuppressEventChanges) return; sBotName = txtBotName.Text; } private void trackScoreWeapons_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideWeapon.Value = Math.Round(slideWeapon.Value); settings.iNeedPointsToKeepWeapon = slideWeapon.Value; WeaponText.Text = slideWeapon.Value.ToString(); } private void trackScoreArmor_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideArmor.Value = Math.Round(slideArmor.Value); settings.iNeedPointsToKeepArmor = slideArmor.Value; ArmorText.Text = slideArmor.Value.ToString(); } private void trackScoreJewelry_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideJewelry.Value = Math.Round(slideJewelry.Value); settings.iNeedPointsToKeepJewelry = slideJewelry.Value; JewelryText.Text = slideJewelry.Value.ToString(); } private void trackNotifyWeapons_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideNotifyWeapon.Value = Math.Round(slideNotifyWeapon.Value); settings.iNeedPointsToNotifyWeapon = slideNotifyWeapon.Value; WeaponNotifyText.Text = slideNotifyWeapon.Value.ToString(); } private void trackNotifyArmor_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideNotifyArmor.Value = Math.Round(slideNotifyArmor.Value); settings.iNeedPointsToNotifyArmor = slideNotifyArmor.Value; ArmorNotifyText.Text = slideNotifyArmor.Value.ToString(); } private void trackNotifyJewelry_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideNotifyJewelry.Value = Math.Round(slideNotifyJewelry.Value); settings.iNeedPointsToNotifyJewelry = slideNotifyJewelry.Value; JewelryNotifyText.Text = slideNotifyJewelry.Value.ToString(); } private void checkBacktracking_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableBacktracking = true; } private void checkBacktracking_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableBacktracking = false; } private void checkDebugInfo_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bDebugInfo = true; } private void checkDebugInfo_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bDebugInfo = false; } private void checkTPS_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableTPS = true; BotMain.TicksPerSecond = (int)settings.iTPSAmount; } private void checkTPS_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableTPS = false; BotMain.TicksPerSecond = 10; } private void checkProfileReload_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableProfileReloading = true; } private void checkProfileReload_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableProfileReloading = false; } private void checkUnstucker_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableUnstucker = true; Navigator.StuckHandler = new GilesStuckHandler(); } private void checkUnstucker_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableUnstucker = false; Navigator.StuckHandler = new DefaultStuckHandler(); } private void checkLogStucks_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bLogStucks = true; } private void checkLogStucks_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bLogStucks = false; } private void checkExtendedRange_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bExtendedKillRange = true; } private void checkExtendedRange_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bExtendedKillRange = false; } private void checkWrath90_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bWrath90Seconds = true; } private void checkWrath90_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bWrath90Seconds = false; } private void checkSelectiveWW_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bSelectiveWhirlwind = true; } private void checkSelectiveWW_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bSelectiveWhirlwind = false; } private void checkKiteArchonOnly_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bKiteOnlyArchon = true; } private void checkKiteArchonOnly_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bKiteOnlyArchon = false; } private void checkWaitArchonAzmo_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bWaitForArchon = true; } private void checkWaitArchonAzmo_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bWaitForArchon = false; } private void checkWaitWrath_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bWaitForWrath = true; } private void checkWaitWrath_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bWaitForWrath = false; } private void checkGoblinWrath_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGoblinWrath = true; } private void checkGoblinWrath_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bGoblinWrath = false; } private void checkMonkInna_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bMonkInnaSet = true; } private void checkMonkInna_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bMonkInnaSet = false; } private void checkFuryDumpWrath_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bFuryDumpWrath = true; } private void checkFuryDumpWrath_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bFuryDumpWrath = false; } private void checkFuryDumpAlways_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bFuryDumpAlways = true; } private void checkFuryDumpAlways_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bFuryDumpAlways = false; } private void trackTPS_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideTPS.Value = Math.Round(slideTPS.Value); textTPS.Text = slideTPS.Value.ToString(); settings.iTPSAmount = Math.Round(slideTPS.Value); if (settings.bEnableTPS) { BotMain.TicksPerSecond = (int)settings.iTPSAmount; } } private void checkAvoidance_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableAvoidance = true; } private void checkAvoidance_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableAvoidance = false; } private void checkGlobes_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableGlobes = true; } private void checkGlobes_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableGlobes = false; } private void checkCritical_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableCriticalMass = true; } private void checkCritical_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bEnableCriticalMass = false; } private void checkMovementAbilities_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bOutOfCombatMovementPowers = true; } private void checkMovementAbilities_uncheck(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.bOutOfCombatMovementPowers = false; } private void slideKite0_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideKite0.Value = Math.Round(slideKite0.Value); textKite0.Text = slideKite0.Value.ToString(); settings.iKiteDistanceBarb = (int)Math.Round(slideKite0.Value); } private void slideKite2_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideKite2.Value = Math.Round(slideKite2.Value); textKite2.Text = slideKite2.Value.ToString(); settings.iKiteDistanceWiz = (int)Math.Round(slideKite2.Value); } private void slideKite3_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideKite3.Value = Math.Round(slideKite3.Value); textKite3.Text = slideKite3.Value.ToString(); settings.iKiteDistanceWitch = (int)Math.Round(slideKite3.Value); } private void slideKite4_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideKite4.Value = Math.Round(slideKite4.Value); textKite4.Text = slideKite4.Value.ToString(); settings.iKiteDistanceDemon = (int)Math.Round(slideKite4.Value); } private void slideVaultDelay_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideVaultDelay.Value = Math.Round(slideVaultDelay.Value); textVaultDelay.Text = slideVaultDelay.Value.ToString(); settings.iDHVaultMovementDelay = (int)Math.Round(slideVaultDelay.Value); } private void slideLootDelay_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideLootDelay.Value = Math.Round(slideLootDelay.Value); textLootDelay.Text = slideLootDelay.Value.ToString(); settings.iKillLootDelay = (int)Math.Round(slideLootDelay.Value); } private void trackTriggerRange_Scroll(object sender, RoutedPropertyChangedEventArgs e) { if (bSuppressEventChanges) return; slideTriggerRange.Value = Math.Round(slideTriggerRange.Value); textTriggerRange.Text = slideTriggerRange.Value.ToString(); settings.iMonsterKillRange = Math.Round(slideTriggerRange.Value); } // The three events for the treasure goblin priority choice private void checkTreasureIgnore_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.iTreasureGoblinPriority = 0; } private void checkTreasureNormal_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.iTreasureGoblinPriority = 1; } private void checkTreasurePrioritize_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.iTreasureGoblinPriority = 2; } private void checkTreasureKamikaze_check(object sender, RoutedEventArgs e) { if (bSuppressEventChanges) return; settings.iTreasureGoblinPriority = 3; } // Event handler for the config window being closed private void configWindow_Closed(object sender, EventArgs e) { configWindow = null; } // Event handler for the config window loading, update all the window elements! private void configWindow_Loaded(object sender, RoutedEventArgs e) { settingsWindowResetValues(); } // Button-clicked for testing backpack stash-replacer scores private void buttonTest_Click(object sender, RoutedEventArgs e) { TestScoring(); configWindow.Close(); } // Button-clicked for testing backpack stash-replacer scores private void buttonSort_Click(object sender, RoutedEventArgs e) { configWindow.Close(); SortStash(); } // Button-clicked for saving the config private void buttonSave_Click(object sender, RoutedEventArgs e) { SaveConfiguration(); configWindow.Close(); } // Default button clicked private void buttonDefaults_Click(object sender, RoutedEventArgs e) { settings = new GilesSettings(); dictAvoidanceHealthBarb = new Dictionary(dictAvoidanceHealthBarbDefaults); dictAvoidanceHealthMonk = new Dictionary(dictAvoidanceHealthMonkDefaults); dictAvoidanceHealthWizard = new Dictionary(dictAvoidanceHealthWizardDefaults); dictAvoidanceHealthWitch = new Dictionary(dictAvoidanceHealthWitchDefaults); dictAvoidanceHealthDemon = new Dictionary(dictAvoidanceHealthDemonDefaults); dictAvoidanceRadius = new Dictionary(dictAvoidanceRadiusDefaults); settingsWindowResetValues(); } // Individual reset buttons private void resetCombat_Click(object sender, RoutedEventArgs e) { GilesSettings tempsettings = new GilesSettings(); settings.bEnableBacktracking = tempsettings.bEnableBacktracking; settings.bEnableAvoidance = tempsettings.bEnableAvoidance; settings.bEnableGlobes = tempsettings.bEnableGlobes; settings.bEnableCriticalMass = tempsettings.bEnableCriticalMass; settings.iTreasureGoblinPriority = tempsettings.iTreasureGoblinPriority; settings.iMonsterKillRange = tempsettings.iMonsterKillRange; settings.iKillLootDelay = tempsettings.iKillLootDelay; settings.iDHVaultMovementDelay = tempsettings.iDHVaultMovementDelay; settings.bMonkInnaSet = tempsettings.bMonkInnaSet; settings.bOutOfCombatMovementPowers = tempsettings.bOutOfCombatMovementPowers; settings.bExtendedKillRange = tempsettings.bExtendedKillRange; settings.bSelectiveWhirlwind = tempsettings.bSelectiveWhirlwind; settings.bWrath90Seconds = tempsettings.bWrath90Seconds; settings.bWaitForWrath = tempsettings.bWaitForWrath; settings.bGoblinWrath = tempsettings.bGoblinWrath; settings.bFuryDumpWrath = tempsettings.bFuryDumpWrath; settings.bFuryDumpAlways = tempsettings.bFuryDumpAlways; settings.bKiteOnlyArchon = tempsettings.bKiteOnlyArchon; settings.bWaitForArchon = tempsettings.bWaitForArchon; settingsWindowResetValues(); } private void resetAOE0_Click(object sender, RoutedEventArgs e) { dictAvoidanceHealthBarb = new Dictionary(dictAvoidanceHealthBarbDefaults); dictAvoidanceRadius = new Dictionary(dictAvoidanceRadiusDefaults); GilesSettings tempsettings = new GilesSettings(); settings.dEmergencyHealthPotionBarb = tempsettings.dEmergencyHealthPotionBarb; settings.dEmergencyHealthGlobeBarb = tempsettings.dEmergencyHealthGlobeBarb; settings.iKiteDistanceBarb = tempsettings.iKiteDistanceBarb; settingsWindowResetValues(); } private void resetAOE1_Click(object sender, RoutedEventArgs e) { dictAvoidanceHealthMonk = new Dictionary(dictAvoidanceHealthMonkDefaults); dictAvoidanceRadius = new Dictionary(dictAvoidanceRadiusDefaults); GilesSettings tempsettings = new GilesSettings(); settings.dEmergencyHealthPotionMonk = tempsettings.dEmergencyHealthPotionMonk; settings.dEmergencyHealthGlobeMonk = tempsettings.dEmergencyHealthGlobeMonk; settings.bMonkInnaSet = tempsettings.bMonkInnaSet; settingsWindowResetValues(); } private void resetAOE2_Click(object sender, RoutedEventArgs e) { dictAvoidanceHealthWizard = new Dictionary(dictAvoidanceHealthWizardDefaults); dictAvoidanceRadius = new Dictionary(dictAvoidanceRadiusDefaults); GilesSettings tempsettings = new GilesSettings(); settings.dEmergencyHealthPotionWiz = tempsettings.dEmergencyHealthPotionWiz; settings.dEmergencyHealthGlobeWiz = tempsettings.dEmergencyHealthGlobeWiz; settings.iKiteDistanceWiz = tempsettings.iKiteDistanceWiz; settings.bKiteOnlyArchon = tempsettings.bKiteOnlyArchon; settings.bWaitForArchon = tempsettings.bWaitForArchon; settingsWindowResetValues(); } private void resetAOE3_Click(object sender, RoutedEventArgs e) { dictAvoidanceHealthWitch = new Dictionary(dictAvoidanceHealthWitchDefaults); dictAvoidanceRadius = new Dictionary(dictAvoidanceRadiusDefaults); GilesSettings tempsettings = new GilesSettings(); settings.dEmergencyHealthPotionWitch = tempsettings.dEmergencyHealthPotionWitch; settings.dEmergencyHealthGlobeWitch = tempsettings.dEmergencyHealthGlobeWitch; settings.iKiteDistanceWitch = tempsettings.iKiteDistanceWitch; settingsWindowResetValues(); } private void resetAOE4_Click(object sender, RoutedEventArgs e) { dictAvoidanceHealthDemon = new Dictionary(dictAvoidanceHealthDemonDefaults); dictAvoidanceRadius = new Dictionary(dictAvoidanceRadiusDefaults); GilesSettings tempsettings = new GilesSettings(); settings.dEmergencyHealthPotionDemon = tempsettings.dEmergencyHealthPotionDemon; settings.dEmergencyHealthGlobeDemon = tempsettings.dEmergencyHealthGlobeDemon; settings.iKiteDistanceDemon = tempsettings.iKiteDistanceDemon; settingsWindowResetValues(); } private void resetWorld_Click(object sender, RoutedEventArgs e) { GilesSettings tempsettings = new GilesSettings(); settings.bIgnoreAllShrines = tempsettings.bIgnoreAllShrines; settings.bIgnoreCorpses = tempsettings.bIgnoreCorpses; settings.iContainerOpenRange = tempsettings.iContainerOpenRange; settings.iDestructibleAttackRange = tempsettings.iDestructibleAttackRange; settingsWindowResetValues(); } private void resetItems_Click(object sender, RoutedEventArgs e) { GilesSettings tempsettings = new GilesSettings(); settings.bUseGilesFilters = tempsettings.bUseGilesFilters; settings.iMinimumGoldStack = tempsettings.iMinimumGoldStack; settings.iFilterPotions = tempsettings.iFilterPotions; settings.iFilterLegendary = tempsettings.iFilterLegendary; settings.iFilterBlueWeapons = tempsettings.iFilterBlueWeapons; settings.iFilterYellowWeapons = tempsettings.iFilterYellowWeapons; settings.iFilterBlueArmor = tempsettings.iFilterBlueArmor; settings.iFilterYellowArmor = tempsettings.iFilterYellowArmor; settings.iFilterBlueJewelry = tempsettings.iFilterBlueJewelry; settings.iFilterYellowJewelry = tempsettings.iFilterYellowJewelry; settings.iFilterGems = tempsettings.iFilterGems; settings.iFilterMisc = tempsettings.iFilterMisc; settings.iFilterPotionLevel = tempsettings.iFilterPotionLevel; settings.bGemsEmerald = tempsettings.bGemsEmerald; settings.bGemsAmethyst = tempsettings.bGemsAmethyst; settings.bGemsTopaz = tempsettings.bGemsTopaz; settings.bGemsRuby = tempsettings.bGemsRuby; settings.bPickupCraftTomes = tempsettings.bPickupCraftTomes; settings.bPickupPlans = tempsettings.bPickupPlans; settings.bPickupFollower = tempsettings.bPickupFollower; settingsWindowResetValues(); } private void resetTown_Click(object sender, RoutedEventArgs e) { GilesSettings tempsettings = new GilesSettings(); settings.bSalvageJunk = tempsettings.bSalvageJunk; settings.iNeedPointsToKeepJewelry = tempsettings.iNeedPointsToKeepJewelry; settings.iNeedPointsToKeepArmor = tempsettings.iNeedPointsToKeepArmor; settings.iNeedPointsToKeepWeapon = tempsettings.iNeedPointsToKeepWeapon; settingsWindowResetValues(); } private void resetAdvanced_Click(object sender, RoutedEventArgs e) { GilesSettings tempsettings = new GilesSettings(); settings.bEnableTPS = tempsettings.bEnableTPS; settings.iTPSAmount = tempsettings.iTPSAmount; settings.bLogStucks = tempsettings.bLogStucks; settings.bEnableUnstucker = tempsettings.bEnableUnstucker; settings.bEnableProfileReloading = tempsettings.bEnableProfileReloading; settings.bDebugInfo = tempsettings.bDebugInfo; settingsWindowResetValues(); } private void resetMobile_Click(object sender, RoutedEventArgs e) { GilesSettings tempsettings = new GilesSettings(); settings.bEnableProwl = tempsettings.bEnableProwl; settings.bEnableAndroid = tempsettings.bEnableAndroid; settings.iNeedPointsToNotifyJewelry = tempsettings.iNeedPointsToNotifyJewelry; settings.iNeedPointsToNotifyArmor = tempsettings.iNeedPointsToNotifyArmor; settings.iNeedPointsToNotifyWeapon = tempsettings.iNeedPointsToNotifyWeapon; settings.bEnableEmail = tempsettings.bEnableEmail; settings.bEnableLegendaryNotifyScore = tempsettings.bEnableLegendaryNotifyScore; settingsWindowResetValues(); } // This function sets all of the window elements of the config window, to the current actual values held in the variables private void settingsWindowResetValues() { bSuppressEventChanges = true; for (int i = 0; i <= 4; i++) { for (int n = 1; n <= 13; n++) { switch (n) { case 1: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[219702]; break; case 2: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[84608]; break; case 3: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[4804]; break; case 4: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[95868]; break; case 5: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[5482]; break; case 6: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[108869]; break; case 7: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[223675]; break; case 8: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[3865]; break; case 9: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[5212]; break; case 10: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[123124]; break; case 11: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[123839]; break; case 12: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[4103]; break; case 13: slideAOERadius[i, n - 1].Value = dictAvoidanceRadius[93837]; break; } switch (i) { case 0: // barbs switch (n) { case 1: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[219702] * 100; break; case 2: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[84608] * 100; break; case 3: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[4804] * 100; break; case 4: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[95868] * 100; break; case 5: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[5482] * 100; break; case 6: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[108869] * 100; break; case 7: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[223675] * 100; break; case 8: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[3865] * 100; break; case 9: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[5212] * 100; break; case 10: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[123124] * 100; break; case 11: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[123839] * 100; break; case 12: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[4103] * 100; break; case 13: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthBarb[93837] * 100; break; } break; case 1: // Monks switch (n) { case 1: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[219702] * 100; break; case 2: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[84608] * 100; break; case 3: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[4804] * 100; break; case 4: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[95868] * 100; break; case 5: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[5482] * 100; break; case 6: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[108869] * 100; break; case 7: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[223675] * 100; break; case 8: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[3865] * 100; break; case 9: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[5212] * 100; break; case 10: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[123124] * 100; break; case 11: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[123839] * 100; break; case 12: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[4103] * 100; break; case 13: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthMonk[93837] * 100; break; } break; case 2: // Wizards switch (n) { case 1: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[219702] * 100; break; case 2: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[84608] * 100; break; case 3: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[4804] * 100; break; case 4: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[95868] * 100; break; case 5: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[5482] * 100; break; case 6: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[108869] * 100; break; case 7: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[223675] * 100; break; case 8: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[3865] * 100; break; case 9: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[5212] * 100; break; case 10: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[123124] * 100; break; case 11: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[123839] * 100; break; case 12: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[4103] * 100; break; case 13: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWizard[93837] * 100; break; } break; case 3: // Witch Doctors switch (n) { case 1: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[219702] * 100; break; case 2: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[84608] * 100; break; case 3: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[4804] * 100; break; case 4: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[95868] * 100; break; case 5: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[5482] * 100; break; case 6: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[108869] * 100; break; case 7: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[223675] * 100; break; case 8: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[3865] * 100; break; case 9: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[5212] * 100; break; case 10: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[123124] * 100; break; case 11: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[123839] * 100; break; case 12: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[4103] * 100; break; case 13: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthWitch[93837] * 100; break; } break; case 4: // Demon Hunters switch (n) { case 1: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[219702] * 100; break; case 2: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[84608] * 100; break; case 3: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[4804] * 100; break; case 4: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[95868] * 100; break; case 5: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[5482] * 100; break; case 6: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[108869] * 100; break; case 7: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[223675] * 100; break; case 8: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[3865] * 100; break; case 9: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[5212] * 100; break; case 10: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[123124] * 100; break; case 11: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[123839] * 100; break; case 12: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[4103] * 100; break; case 13: slideAOEHealth[i, n - 1].Value = dictAvoidanceHealthDemon[93837] * 100; break; } break; } // Switch on i textAOERadius[i, n - 1].Text = slideAOERadius[i, n - 1].Value.ToString(); textAOEHealth[i, n - 1].Text = slideAOEHealth[i, n - 1].Value.ToString(); } // Loop through the avoidances } // Loop through the classes comboLegendary.SelectedIndex = comboLegendary.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterLegendary.ToString())).Count(); comboWB.SelectedIndex = comboWB.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterBlueWeapons.ToString())).Count(); comboWY.SelectedIndex = comboWY.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterYellowWeapons.ToString())).Count(); comboAB.SelectedIndex = comboAB.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterBlueArmor.ToString())).Count(); comboAY.SelectedIndex = comboAY.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterYellowArmor.ToString())).Count(); comboJB.SelectedIndex = comboJB.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterBlueJewelry.ToString())).Count(); comboJY.SelectedIndex = comboJY.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterYellowJewelry.ToString())).Count(); comboGems.SelectedIndex = comboGems.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterGems.ToString())).Count(); comboMisc.SelectedIndex = comboMisc.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterMisc.ToString())).Count(); comboPotions.SelectedIndex = comboPotions.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterPotions.ToString())).Count(); comboPotionLevel.SelectedIndex = comboPotionLevel.Items.Cast().TakeWhile(cbi => !(cbi.Tag).Equals(settings.iFilterPotionLevel.ToString())).Count(); checkCraftTomes.IsChecked = settings.bPickupCraftTomes; checkDesigns.IsChecked = settings.bPickupPlans; checkFollower.IsChecked = settings.bPickupFollower; checkGemEmerald.IsChecked = settings.bGemsEmerald; checkGemAmethyst.IsChecked = settings.bGemsAmethyst; checkGemTopaz.IsChecked = settings.bGemsTopaz; checkGemRuby.IsChecked = settings.bGemsRuby; if (settings.bSalvageJunk) { btnSalvage.IsChecked = true; btnSell.IsChecked = false; } else { btnSell.IsChecked = true; btnSalvage.IsChecked = false; } if (settings.bUseGilesFilters) { btnRulesGiles.IsChecked = true; btnRulesCustom.IsChecked = false; } else { btnRulesCustom.IsChecked = true; btnRulesGiles.IsChecked = false; } slideWeapon.Value = Math.Round(settings.iNeedPointsToKeepWeapon); WeaponText.Text = slideWeapon.Value.ToString(); slideArmor.Value = Math.Round(settings.iNeedPointsToKeepArmor); ArmorText.Text = slideArmor.Value.ToString(); slideJewelry.Value = Math.Round(settings.iNeedPointsToKeepJewelry); JewelryText.Text = slideJewelry.Value.ToString(); slideNotifyWeapon.Value = Math.Round(settings.iNeedPointsToNotifyWeapon); WeaponNotifyText.Text = slideNotifyWeapon.Value.ToString(); slideNotifyArmor.Value = Math.Round(settings.iNeedPointsToNotifyArmor); ArmorNotifyText.Text = slideNotifyArmor.Value.ToString(); slideNotifyJewelry.Value = Math.Round(settings.iNeedPointsToNotifyJewelry); JewelryNotifyText.Text = slideNotifyJewelry.Value.ToString(); slideGoldAmount.Value = settings.iMinimumGoldStack; textGoldAmount.Text = settings.iMinimumGoldStack.ToString(); slideTriggerRange.Value = settings.iMonsterKillRange; textTriggerRange.Text = settings.iMonsterKillRange.ToString(); slideLootDelay.Value = settings.iKillLootDelay; textLootDelay.Text = settings.iKillLootDelay.ToString(); slideVaultDelay.Value = settings.iDHVaultMovementDelay; textVaultDelay.Text = settings.iDHVaultMovementDelay.ToString(); slideKite0.Value = settings.iKiteDistanceBarb; textKite0.Text = settings.iKiteDistanceBarb.ToString(); slideKite2.Value = settings.iKiteDistanceWiz; textKite2.Text = settings.iKiteDistanceWiz.ToString(); slideKite3.Value = settings.iKiteDistanceWitch; textKite3.Text = settings.iKiteDistanceWitch.ToString(); slideKite4.Value = settings.iKiteDistanceDemon; textKite4.Text = settings.iKiteDistanceDemon.ToString(); if (settings.iTreasureGoblinPriority == 0) { checkTreasureNormal.IsChecked = false; checkTreasurePrioritize.IsChecked = false; checkTreasureKamikaze.IsChecked = false; checkTreasureIgnore.IsChecked = true; } else if (settings.iTreasureGoblinPriority == 1) { checkTreasureIgnore.IsChecked = false; checkTreasurePrioritize.IsChecked = false; checkTreasureKamikaze.IsChecked = false; checkTreasureNormal.IsChecked = true; } else if (settings.iTreasureGoblinPriority == 2) { checkTreasureIgnore.IsChecked = false; checkTreasureNormal.IsChecked = false; checkTreasureKamikaze.IsChecked = false; checkTreasurePrioritize.IsChecked = true; } else { checkTreasureIgnore.IsChecked = false; checkTreasureNormal.IsChecked = false; checkTreasurePrioritize.IsChecked = false; checkTreasureKamikaze.IsChecked = true; } if (settings.bIgnoreAllShrines) { checkIgnoreNone.IsChecked = false; checkIgnoreAll.IsChecked = true; } else { checkIgnoreAll.IsChecked = false; checkIgnoreNone.IsChecked = true; } checkBacktracking.IsChecked = settings.bEnableBacktracking; checkCritical.IsChecked = settings.bEnableCriticalMass; checkGrave.IsChecked = settings.bEnableCriticalMass; checkAvoidance.IsChecked = settings.bEnableAvoidance; checkGlobes.IsChecked = settings.bEnableGlobes; slideContainerRange.Value = settings.iContainerOpenRange; textContainerRange.Text = settings.iContainerOpenRange.ToString(); slideDestructibleRange.Value = settings.iDestructibleAttackRange; textDestructibleRange.Text = settings.iDestructibleAttackRange.ToString(); checkIgnoreCorpses.IsChecked = settings.bIgnoreCorpses; checkMovementAbilities.IsChecked = settings.bOutOfCombatMovementPowers; textTPS.Text = settings.iTPSAmount.ToString(); slideTPS.Value = settings.iTPSAmount; checkTPS.IsChecked = settings.bEnableTPS; checkLogStucks.IsChecked = settings.bLogStucks; checkProwl.IsChecked = settings.bEnableProwl; textProwlKey.Text = sProwlAPIKey; checkEmail.IsChecked = settings.bEnableEmail; checkLegendaryNotify.IsChecked = settings.bEnableLegendaryNotifyScore; txtEmailAddress.Text = sEmailAddress; txtEmailPassword.Text = sEmailPassword; txtBotName.Text = sBotName; checkAndroid.IsChecked = settings.bEnableAndroid; textAndroidKey.Text = sAndroidAPIKey; checkUnstucker.IsChecked = settings.bEnableUnstucker; checkProfileReload.IsChecked = settings.bEnableProfileReloading; checkExtendedRange.IsChecked = settings.bExtendedKillRange; checkSelectiveWW.IsChecked = settings.bSelectiveWhirlwind; checkWrath90.IsChecked = settings.bWrath90Seconds; checkWaitWrath.IsChecked = settings.bWaitForWrath; checkKiteArchonOnly.IsChecked = settings.bKiteOnlyArchon; checkWaitArchonAzmo.IsChecked = settings.bWaitForArchon; checkGoblinWrath.IsChecked = settings.bGoblinWrath; checkFuryDumpWrath.IsChecked = settings.bFuryDumpWrath; checkFuryDumpAlways.IsChecked = settings.bFuryDumpAlways; checkDebugInfo.IsChecked = settings.bDebugInfo; checkMonkInna.IsChecked = settings.bMonkInnaSet; slidePot0.Value = Math.Floor(settings.dEmergencyHealthPotionBarb * 100); slidePot1.Value = Math.Floor(settings.dEmergencyHealthPotionMonk * 100); slidePot2.Value = Math.Floor(settings.dEmergencyHealthPotionWiz * 100); slidePot3.Value = Math.Floor(settings.dEmergencyHealthPotionWitch * 100); slidePot4.Value = Math.Floor(settings.dEmergencyHealthPotionDemon * 100); textPot0.Text = Math.Floor(settings.dEmergencyHealthPotionBarb * 100).ToString(); textPot1.Text = Math.Floor(settings.dEmergencyHealthPotionMonk * 100).ToString(); textPot2.Text = Math.Floor(settings.dEmergencyHealthPotionWiz * 100).ToString(); textPot3.Text = Math.Floor(settings.dEmergencyHealthPotionWitch * 100).ToString(); textPot4.Text = Math.Floor(settings.dEmergencyHealthPotionDemon * 100).ToString(); slideGlobe0.Value = Math.Floor(settings.dEmergencyHealthGlobeBarb * 100); slideGlobe1.Value = Math.Floor(settings.dEmergencyHealthGlobeMonk * 100); slideGlobe2.Value = Math.Floor(settings.dEmergencyHealthGlobeWiz * 100); slideGlobe3.Value = Math.Floor(settings.dEmergencyHealthGlobeWitch * 100); slideGlobe4.Value = Math.Floor(settings.dEmergencyHealthGlobeDemon * 100); textGlobe0.Text = Math.Floor(settings.dEmergencyHealthGlobeBarb * 100).ToString(); textGlobe1.Text = Math.Floor(settings.dEmergencyHealthGlobeMonk * 100).ToString(); textGlobe2.Text = Math.Floor(settings.dEmergencyHealthGlobeWiz * 100).ToString(); textGlobe3.Text = Math.Floor(settings.dEmergencyHealthGlobeWitch * 100).ToString(); textGlobe4.Text = Math.Floor(settings.dEmergencyHealthGlobeDemon * 100).ToString(); bSuppressEventChanges = false; } #endregion // *************************************************** // *********** END OF CONFIG WINDOW REGION *********** // *************************************************** // ********************************************************************************************** // ***** Primary/"Default" functions, like logs, initialize, DB event handling etc. ***** // ********************************************************************************************** private static void Log(string message, bool bIsDiagnostic = false) { string totalMessage = String.Format("[GilesTrinity] {0}", message); if (!bIsDiagnostic) Logging.Write(totalMessage); else Logging.WriteDiagnostic(totalMessage); } private static bool bPluginEnabled = false; private static Button btnPauseBot, btnTownRun; public void OnInitialize() { string battleTagName = ""; try { battleTagName = ZetaDia.Service.CurrentHero.BattleTagName; } catch{} string sDemonBuddyPath = Assembly.GetEntryAssembly().Location; sTrinityPluginPath = Path.GetDirectoryName(sDemonBuddyPath) + @"\Plugins\GilesTrinity\"; sTrinityConfigFile = Path.GetDirectoryName(sDemonBuddyPath) + @"\Settings\GilesTrinity.cfg"; sTrinityEmailConfigFile = Path.GetDirectoryName(sDemonBuddyPath) + @"\Settings\" + battleTagName + @"-Email.cfg"; Logging.WriteDiagnostic("Trinity Initialization, settings location=" + sTrinityConfigFile); BotMain.OnStart += GilesTrinityStart; // Force logging to disabled if (bDisableFileLogging) Zeta.Common.Logging.LogFileLevel = Zeta.Common.LogLevel.None; // Set up the pause button Application.Current.Dispatcher.Invoke( new System.Action( () => { Window mainWindow = Application.Current.MainWindow; try { mainWindow.Title = "DB - " + battleTagName; } catch { } var tab = mainWindow.FindName("tabControlMain") as TabControl; if (tab == null) return; var infoDumpTab = tab.Items[0] as TabItem; if (infoDumpTab == null) return; btnPauseBot = new Button { Width = 100, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Margin = new Thickness(232, 6, 0, 0), Content = "Pause Bot" }; btnPauseBot.Click += buttonPause_Click; btnTownRun = new Button { Width = 100, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, Margin = new Thickness(232, 32, 0, 0), Content = "Force Town Run" }; btnTownRun.Click += buttonTownRun_Click; var grid = infoDumpTab.Content as Grid; if (grid == null) return; grid.Children.Add(btnPauseBot); grid.Children.Add(btnTownRun); })); } private static bool botispaused() { return bMainBotPaused; } // Force town-run button private static void buttonTownRun_Click(object sender, RoutedEventArgs e) { if (!BotMain.IsRunning || !ZetaDia.IsInGame || ZetaDia.IsLoadingWorld) { Logging.Write("[GilesTrinity] You can only force a town run while DemonBuddy is started and running!"); return; } bGilesForcedVendoring = true; Logging.Write("[GilesTrinity] Town-run request received, will town-run at next possible moment."); } // Pause Button private static void buttonPause_Click(object sender, RoutedEventArgs e) { if (bMainBotPaused) { btnPauseBot.Content = "Pause Bot"; bMainBotPaused = false; bMappedPlayerAbilities = false; lastChangedZigZag = DateTime.Today; bAlreadyMoving = false; lastMovementCommand = DateTime.Today; } else { BotMain.PauseWhile(botispaused); btnPauseBot.Content = "Unpause Bot"; bMainBotPaused = true; } } public void OnPulse() { } public void OnEnabled() { if (!Directory.Exists(sTrinityPluginPath)) { Log("Fatal Error - cannot enable plugin. Invalid path: " + sTrinityPluginPath); Log("Please check you have installed the plugin to the correct location, and then restart DemonBuddy and re-enable the plugin."); Log(@"Plugin should be installed to \\Plugins\GilesTrinity\"); } else { bMappedPlayerAbilities = false; bPluginEnabled = true; LoadConfiguration(); Navigator.PlayerMover = new GilesPlayerMover(); Navigator.StuckHandler = new GilesStuckHandler(); GameEvents.OnPlayerDied += GilesTrinityOnDeath; BotMain.OnStop += GilesTrinityHandleBotStop; GameEvents.OnGameJoined += GilesTrinityOnJoinGame; GameEvents.OnGameLeft += GilesTrinityOnLeaveGame; ITargetingProvider newCombatTargetingProvider = new GilesCombatTargetingReplacer(); CombatTargeting.Instance.Provider = newCombatTargetingProvider; ITargetingProvider newLootTargetingProvider = new GilesLootTargetingProvider(); LootTargeting.Instance.Provider = newLootTargetingProvider; ITargetingProvider newObstacleTargetingProvider = new GilesObstacleTargetingProvider(); ObstacleTargeting.Instance.Provider = newObstacleTargetingProvider; // Safety check incase DB "OnStart" event didn't fire properly if (BotMain.IsRunning) { GilesTrinityStart(null); if (ZetaDia.IsInGame) GilesTrinityOnJoinGame(null, null); } // Carguy's ticks-per-second feature if (settings.bEnableTPS) { BotMain.TicksPerSecond = (int)settings.iTPSAmount; } Log("************************************"); Log("ENABLED: " + Description + " now in action!"); Log("************************************"); interpreter.init(); } } public void OnDisabled() { bPluginEnabled = false; Navigator.PlayerMover = new DefaultPlayerMover(); Navigator.StuckHandler = new DefaultStuckHandler(); CombatTargeting.Instance.Provider = new DefaultCombatTargetingProvider(); LootTargeting.Instance.Provider = new DefaultLootTargetingProvider(); ObstacleTargeting.Instance.Provider = new DefaultObstacleTargetingProvider(); GameEvents.OnPlayerDied -= GilesTrinityOnDeath; BotMain.OnStop -= GilesTrinityHandleBotStop; GameEvents.OnGameJoined -= GilesTrinityOnJoinGame; GameEvents.OnGameLeft -= GilesTrinityOnLeaveGame; Log("DISABLED: Giles Trinity is now shut down..."); } public void OnShutdown() { } // On death, clear the timers for all abilities private static DateTime lastDied = DateTime.Today; private static int iTotalDeaths = 0; private void GilesTrinityOnDeath(object src, EventArgs mea) { if (DateTime.Now.Subtract(lastDied).TotalSeconds > 10) { lastDied = DateTime.Now; iTotalDeaths++; iDeathsThisRun++; dictAbilityLastUse = new Dictionary(dictAbilityLastUseDefaults); vBacktrackList = new SortedList(); iTotalBacktracks = 0; GilesPlayerMover.iTotalAntiStuckAttempts = 1; GilesPlayerMover.vSafeMovementLocation = Vector3.Zero; // Does Trinity need to handle deaths? if (iMaxDeathsAllowed > 0) { if (iDeathsThisRun >= iMaxDeathsAllowed) { Logging.Write("[GilesTrinity] You have died too many times. Now restarting the game."); string sUseProfile = GilesTrinity.sFirstProfileSeen; ProfileManager.Load(!string.IsNullOrEmpty(sUseProfile) ? sUseProfile : Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile); Thread.Sleep(1000); GilesResetEverythingNewGame(); ZetaDia.Service.Games.LeaveGame(); Thread.Sleep(10000); } else { Logging.Write("[GilesTrinity] I'm sorry, but I seem to have let you die :( Now restarting the current profile."); ProfileManager.Load(Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile); Thread.Sleep(2000); } } } } // When the bot stops, output a final item-stats report so it is as up-to-date as can be private void GilesTrinityHandleBotStop(IBot bot) { // Issue final reports OutputReport(); vBacktrackList = new SortedList(); iTotalBacktracks = 0; GilesPlayerMover.iTotalAntiStuckAttempts = 1; GilesPlayerMover.vSafeMovementLocation = Vector3.Zero; GilesPlayerMover.vOldPosition = Vector3.Zero; GilesPlayerMover.iTimesReachedStuckPoint = 0; GilesPlayerMover.timeLastRecordedPosition = DateTime.Today; GilesPlayerMover.timeStartedUnstuckMeasure = DateTime.Today; hashUseOnceID = new HashSet(); dictUseOnceID = new Dictionary(); dictRandomID = new Dictionary(); iMaxDeathsAllowed = 0; iDeathsThisRun = 0; } // How many total leave games, for stat-tracking? public static int iTotalJoinGames = 0; // Each time we join & leave a game, might as well clear the hashset of looked-at dropped items - just to keep it smaller private static void GilesTrinityOnJoinGame(object src, EventArgs mea) { iTotalJoinGames++; GilesResetEverythingNewGame(); } // How many total leave games, for stat-tracking? public static int iTotalLeaveGames = 0; // Each time we join & leave a game, might as well clear the hashset of looked-at dropped items - just to keep it smaller private static void GilesTrinityOnLeaveGame(object src, EventArgs mea) { iTotalLeaveGames++; GilesResetEverythingNewGame(); } public static int iTotalProfileRecycles = 0; public static void GilesResetEverythingNewGame() { hashUseOnceID = new HashSet(); dictUseOnceID = new Dictionary(); iMaxDeathsAllowed = 0; iDeathsThisRun = 0; _hashsetItemStatsLookedAt = new HashSet(); _hashsetItemPicksLookedAt = new HashSet(); _hashsetItemFollowersIgnored = new HashSet(); _dictItemStashAttempted = new Dictionary(); hashRGUIDIgnoreBlacklist = new HashSet(); hashRGUIDTemporaryIgnoreBlacklist = new HashSet(); vBacktrackList = new SortedList(); iTotalBacktracks = 0; bMappedPlayerAbilities = false; GilesPlayerMover.iTotalAntiStuckAttempts = 1; GilesPlayerMover.vSafeMovementLocation = Vector3.Zero; GilesPlayerMover.vOldPosition = Vector3.Zero; GilesPlayerMover.iTimesReachedStuckPoint = 0; GilesPlayerMover.timeLastRecordedPosition = DateTime.Today; GilesPlayerMover.timeStartedUnstuckMeasure = DateTime.Today; GilesPlayerMover.iTimesReachedMaxUnstucks = 0; GilesPlayerMover.iCancelUnstuckerForSeconds = 0; GilesPlayerMover.timeCancelledUnstuckerFor = DateTime.Today; // Reset all the caches dictGilesObjectTypeCache = new Dictionary(); dictGilesMonsterAffixCache = new Dictionary(); dictGilesMaxHealthCache = new Dictionary(); dictGilesLastHealthCache = new Dictionary(); dictGilesLastHealthChecked = new Dictionary(); dictGilesBurrowedCache = new Dictionary(); dictGilesActorSNOCache = new Dictionary(); dictGilesACDGUIDCache = new Dictionary(); dictGilesInternalNameCache = new Dictionary(); dictGilesGameBalanceIDCache = new Dictionary(); dictGilesDynamicIDCache = new Dictionary(); dictGilesVectorCache = new Dictionary(); dictGilesGoldAmountCache = new Dictionary(); dictGilesQualityCache = new Dictionary(); dictGilesQualityRechecked = new Dictionary(); dictGilesPickupItem = new Dictionary(); dictSummonedByID = new Dictionary(); dictTotalInteractionAttempts = new Dictionary(); listProfilesLoaded = new List(); sLastProfileSeen = ""; sFirstProfileSeen = ""; } // ********************************************************************************************** // ***** Prowl Notification Support ***** // ********************************************************************************************** public static Queue pushQueue = new Queue(); public struct ProwlNotification { public string Event { get; set; } public string Description { get; set; } public ProwlNotificationPriority Priority { get; set; } } public enum ProwlNotificationPriority : sbyte { VeryLow = -2, Moderate = -1, Normal = 0, High = 1, Emergency = 2 } public static void AddNotificationToQueue(string description, string eventName, ProwlNotificationPriority priority) { // Queue the notification message var newNotification = new ProwlNotification { Description = description, Event = eventName, Priority = priority }; pushQueue.Enqueue(newNotification); } public static void SendNotification(ProwlNotification notification) { if (settings.bEnableProwl && sProwlAPIKey != "") { var newNotification = new ProwlNotification { Description = notification.Description, Event = notification.Event, Priority = notification.Priority }; try { PostNotification(newNotification); } catch { } } if (settings.bEnableAndroid && sAndroidAPIKey != "") { var newNotification = new ProwlNotification { Description = notification.Description, Event = notification.Event, Priority = notification.Priority }; try { PostNotification(newNotification, true); } catch { } } } public static void PostNotification(ProwlNotification notification_, bool bForAndroid = false) { string prowlUrlSb = !bForAndroid ? @"https://prowl.weks.net/publicapi/add" : @"https://www.notifymyandroid.com/publicapi/notify"; string sThisAPIKey = !bForAndroid ? sProwlAPIKey : sAndroidAPIKey; prowlUrlSb += "?apikey=" + HttpUtility.UrlEncode(sThisAPIKey) + "&application=" + HttpUtility.UrlEncode("GilesTrinity") + "&description=" + HttpUtility.UrlEncode(notification_.Description) + "&event=" + HttpUtility.UrlEncode(notification_.Event) + "&priority=" + HttpUtility.UrlEncode(notification_.Priority.ToString()); var updateRequest = (HttpWebRequest)WebRequest.Create(prowlUrlSb.ToString()); updateRequest.ContentLength = 0; updateRequest.ContentType = "application/x-www-form-urlencoded"; updateRequest.Method = "POST"; //updateRequest.Timeout = 5000; var postResponse = default(WebResponse); try { postResponse = updateRequest.GetResponse(); } finally { if (postResponse != null) postResponse.Close(); } } } // End of main routines // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ***** Giles Custom XML Codes ***** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // ********************************************************************************************** // Note to self: No more sandwich :( // ********************************************************************************************** // ***** TrinityTownRun forces a town-run request ***** // ********************************************************************************************** [XmlElement("TrinityTownRun")] public class TrinityTownRunTag : ProfileBehavior { private bool m_IsDone = false; public override bool IsDone { get { return m_IsDone; } } protected override Composite CreateBehavior() { return new Zeta.TreeSharp.Action(ret => { Logging.Write("[GilesTrinity] Town-run request received, will town-run at next possible moment."); GilesTrinity.bGilesForcedVendoring = true; m_IsDone = true; }); } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } } // ********************************************************************************************** // ***** TrinityLoadProfile will load the specified XML profile up ***** // ********************************************************************************************** [XmlElement("TrinityLoadProfile")] public class TrinityLoadProfile : ProfileBehavior { private bool m_IsDone = false; private string sFileName; private string sExitString; private string sNoDelay; public override bool IsDone { get { return m_IsDone; } } protected override Composite CreateBehavior() { return new Zeta.TreeSharp.Action(ret => { bool bExitGame = Exit != null && Exit.ToLower() == "true"; string sThisProfileString = File; // See if there are multiple profile choices, if so split them up and pick a random one if (sThisProfileString.Contains("!")) { string[] sProfileChoices; sProfileChoices = sThisProfileString.Split(new string[] { "!" }, StringSplitOptions.None); Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber)); int iChooseProfile = rndNum.Next(sProfileChoices.Count()); sThisProfileString = sProfileChoices[iChooseProfile]; } // Now calculate our current path by checking the currently loaded profile string sCurrentProfilePath = Path.GetDirectoryName(Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile); // And prepare a full string of the path, and the new .xml file name string sNextProfile = sCurrentProfilePath + @"\" + sThisProfileString; Logging.Write("[GilesTrinity] Loading new profile."); ProfileManager.Load(sNextProfile); // A quick nap-time helps prevent some funny issues if (NoDelay == null || NoDelay.ToLower() != "true") Thread.Sleep(3000); else Thread.Sleep(300); // See if the XML tag requested we exit the game after loading this profile or not if (bExitGame) { Logging.Write("[GilesTrinity] Exiting game to continue with next profile."); // Attempt to teleport to town first for a quicker exit int iSafetyLoops = 0; while (!ZetaDia.Me.IsInTown) { iSafetyLoops++; GilesTrinity.WaitWhileAnimating(5, true); ZetaDia.Me.UsePower(SNOPower.UseStoneOfRecall, ZetaDia.Me.Position, ZetaDia.Me.WorldDynamicId, -1); Thread.Sleep(1000); GilesTrinity.WaitWhileAnimating(1000, true); if (iSafetyLoops > 5) break; } Thread.Sleep(1000); ZetaDia.Service.Games.LeaveGame(); GilesTrinity.GilesResetEverythingNewGame(); // Wait for 10 second log out timer if not in town, else wait for 3 seconds instead Thread.Sleep(!ZetaDia.Me.IsInTown ? 10000 : 3000); } // Check if we want to restart the game m_IsDone = true; }); } [XmlAttribute("exit")] public string Exit { get { return sExitString; } set { sExitString = value; } } [XmlAttribute("nodelay")] public string NoDelay { get { return sNoDelay; } set { sNoDelay = value; } } [XmlAttribute("file")] public string File { get { return sFileName; } set { sFileName = value; } } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } } // ********************************************************************************************** // ***** TrinityMaxDeaths tells Trinity to handle deaths and exit game after X deaths ***** // ********************************************************************************************** [XmlElement("TrinityMaxDeaths")] public class TrinityMaxDeathsTag : ProfileBehavior { private bool m_IsDone = false; private int iMaxDeaths; private string sReset; public override bool IsDone { get { return m_IsDone; } } protected override Composite CreateBehavior() { return new Zeta.TreeSharp.Action(ret => { if (MaxDeaths != GilesTrinity.iMaxDeathsAllowed) Logging.Write("[GilesTrinity] Max deaths set by profile. Trinity now handling deaths, and will restart the game after " + MaxDeaths.ToString()); GilesTrinity.iMaxDeathsAllowed = MaxDeaths; if (Reset != null && Reset.ToLower() == "true") GilesTrinity.iDeathsThisRun = 0; m_IsDone = true; }); } [XmlAttribute("reset")] public string Reset { get { return sReset; } set { sReset = value; } } [XmlAttribute("max")] public int MaxDeaths { get { return iMaxDeaths; } set { iMaxDeaths = value; } } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } } // ********************************************************************************************** // ***** TrinityInteract attempts a blind object-use of an SNO without movement ***** // ********************************************************************************************** [XmlElement("TrinityInteract")] public class TrinityInteractTag : ProfileBehavior { private bool m_IsDone = false; private int iSNOID; public override bool IsDone { get { return m_IsDone; } } protected override Composite CreateBehavior() { return new Zeta.TreeSharp.Action(ret => { float fClosestRange = -1; int iACDGuid = -1; Vector3 vMyLocation = ZetaDia.Me.Position; foreach (DiaObject thisobject in ZetaDia.Actors.GetActorsOfType(true, false).Where(a => a.ActorSNO == SNOID)) { if (fClosestRange == -1 || thisobject.Position.Distance(vMyLocation) <= fClosestRange) { fClosestRange = thisobject.Position.Distance(vMyLocation); iACDGuid = thisobject.ACDGuid; } } if (iACDGuid != -1) { try { ZetaDia.Me.UsePower(SNOPower.Axe_Operate_Gizmo, Vector3.Zero, 0, iACDGuid); } catch { Logging.WriteDiagnostic("[GilesTrinity] There was a memory/DB failure trying to follow the TrinityInteract XML tag on SNO " + SNOID.ToString()); } } m_IsDone = true; }); } [XmlAttribute("snoid")] public int SNOID { get { return iSNOID; } set { iSNOID = value; } } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } } // ********************************************************************************************** // ***** Trinity Log lets profiles send log messages to DB ***** // ********************************************************************************************** [XmlElement("TrinityLog")] public class TrinityLogTag : ProfileBehavior { private bool m_IsDone = false; private string sLogOutput; private string sLogLevel; public override bool IsDone { get { return m_IsDone; } } protected override Composite CreateBehavior() { return new Zeta.TreeSharp.Action(ret => { if (Level != null && Level.ToLower() == "diagnostic") Logging.WriteDiagnostic(Output); else Logging.Write(Output); m_IsDone = true; }); } [XmlAttribute("level", true)] public string Level { get { return sLogLevel; } set { sLogLevel = value; } } [XmlAttribute("output", true)] public string Output { get { return sLogOutput; } set { sLogOutput = value; } } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } } // **************************************************************************************************** // ***** TrinityMoveTo moves in a straight line without any navigation hits, and allows tag-skips ***** // **************************************************************************************************** [XmlElement("TrinityMoveTo")] public class TrinityMoveToTag : ProfileBehavior { private bool m_IsDone; private float fPosX; private float fPosY; private float fPosZ; private float fPathPrecision; private float fRandomizedDistance; private string sDestinationName; private string sNoSkip; private Vector3? vMainVector; protected override Composite CreateBehavior() { Composite[] children = new Composite[2]; Composite[] compositeArray = new Composite[2]; compositeArray[0] = new Zeta.TreeSharp.Action(new ActionSucceedDelegate(FlagTagAsCompleted)); children[0] = new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckDistanceWithinPathPrecision), new Sequence(compositeArray)); ActionDelegate actionDelegateMove = new ActionDelegate(GilesMoveToLocation); Sequence sequenceblank = new Sequence( new Zeta.TreeSharp.Action(actionDelegateMove) ); children[1] = sequenceblank; return new PrioritySelector(children); } private RunStatus GilesMoveToLocation(object ret) { // First check if we can skip ahead because we recently moved here if (!GilesTrinity.settings.bEnableBacktracking && (NoSkip == null || NoSkip.ToLower() != "true")) { if (GilesTrinity.hashSkipAheadAreaCache.Any()) { // Loop through all the skip ahead zones and see if one of them is within radius of our intended destination to skip ahead foreach (GilesTrinity.GilesObstacle thisObject in GilesTrinity.hashSkipAheadAreaCache) { if (thisObject.vThisLocation.Distance(Position) <= thisObject.fThisRadius) { Logging.WriteDiagnostic("[GilesTrinity] Skipping ahead from moveto " + Position.ToString() + " to next moveto."); GilesTrinity.bSkipAheadAGo = true; return RunStatus.Success; } } GilesTrinity.hashSkipAheadAreaCache = new HashSet(); } } else { GilesTrinity.hashSkipAheadAreaCache = new HashSet(); } // Now use Trinity movement to try a direct movement towards that location Navigator.PlayerMover.MoveTowards(Position); return RunStatus.Success; } private bool CheckDistanceWithinPathPrecision(object object_0) { // First see if we should skip ahead one move because we were already at that location if (GilesTrinity.bSkipAheadAGo) { GilesTrinity.bSkipAheadAGo = false; return true; } // Ok not skipping, now see if we are already within pathprecision range of that location return (ZetaDia.Me.Position.Distance(Position) <= Math.Max(PathPrecision, Navigator.PathPrecision)); } private void FlagTagAsCompleted(object object_0) { m_IsDone = true; } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } public override bool IsDone { get { if (IsActiveQuestStep) { return m_IsDone; } return true; } } [XmlAttribute("noskip")] public string NoSkip { get { return sNoSkip; } set { sNoSkip = value; } } [XmlAttribute("name")] public string Name { get { return sDestinationName; } set { sDestinationName = value; } } [XmlAttribute("pathPrecision")] public float PathPrecision { get { return fPathPrecision; } set { fPathPrecision = value; } } public Vector3 Position { get { if (!vMainVector.HasValue) { if (UnsafeRandomDistance == 0f) { vMainVector = new Vector3(X, Y, Z); } else { float degrees = new Random().Next(0, 360); vMainVector = new Vector3?(MathEx.GetPointAt(new Vector3(X, Y, Z), (float)(new Random().NextDouble() * UnsafeRandomDistance), MathEx.ToRadians(degrees))); } } return vMainVector.Value; } } [XmlAttribute("unsafeRandomDistance")] public float UnsafeRandomDistance { get { return fRandomizedDistance; } set { fRandomizedDistance = value; } } [XmlAttribute("x")] public float X { get { return fPosX; } set { fPosX = value; } } [XmlAttribute("y")] public float Y { get { return fPosY; } set { fPosY = value; } } [XmlAttribute("z")] public float Z { get { return fPosZ; } set { fPosZ = value; } } } // **************************************************************************************************** // ***** TrinityMoveTo moves in a straight line without any navigation hits, and allows tag-skips ***** // **************************************************************************************************** [XmlElement("TrinityMoveToSNO")] public class TrinityMoveToSNOTag : ProfileBehavior { private bool m_IsDone; private float fPathPrecision; private int iSNOID; private string sDestinationName; protected override Composite CreateBehavior() { Composite[] children = new Composite[2]; Composite[] compositeArray = new Composite[2]; compositeArray[0] = new Zeta.TreeSharp.Action(new ActionSucceedDelegate(FlagTagAsCompleted)); children[0] = new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckDistanceWithinPathPrecision), new Sequence(compositeArray)); ActionDelegate actionDelegateMove = new ActionDelegate(GilesMoveToLocation); Sequence sequenceblank = new Sequence( new Zeta.TreeSharp.Action(actionDelegateMove) ); children[1] = sequenceblank; return new PrioritySelector(children); } private RunStatus GilesMoveToLocation(object ret) { DiaObject tempObject = ZetaDia.Actors.GetActorsOfType(true, false).FirstOrDefault(a => a.ActorSNO == SNOID); if (tempObject != null) { Navigator.PlayerMover.MoveTowards(tempObject.Position); return RunStatus.Success; } return RunStatus.Success; } private bool CheckDistanceWithinPathPrecision(object object_0) { DiaObject tempObject = ZetaDia.Actors.GetActorsOfType(true, false).FirstOrDefault(a => a.ActorSNO == SNOID); if (tempObject != null) { return (ZetaDia.Me.Position.Distance(tempObject.Position) <= Math.Max(PathPrecision, Navigator.PathPrecision)); } return false; } private void FlagTagAsCompleted(object object_0) { m_IsDone = true; } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } public override bool IsDone { get { if (IsActiveQuestStep) { return m_IsDone; } return true; } } [XmlAttribute("name")] public string Name { get { return sDestinationName; } set { sDestinationName = value; } } [XmlAttribute("pathPrecision")] public float PathPrecision { get { return fPathPrecision; } set { fPathPrecision = value; } } [XmlAttribute("snoid")] public int SNOID { get { return iSNOID; } set { iSNOID = value; } } } // Note to self: Could manually start a new game with; //ZetaDia.Service.Games.CreateGame(createGameParams.Act, createGameParams.Difficulty, createGameParams.Quest, createGameParams.Step, createGameParams.ResumeFromSave, createGameParams.IsPrivate); // ********************************************************************************************** // ***** Trinity If perfectly mimics DB If - this is just for experimenting ***** // ********************************************************************************************** [XmlElement("TrinityIf")] public class TrinityIfTag : ComplexNodeTag, IPythonExecutable { private bool? bComplexDoneCheck; private bool? bAlreadyCompleted; private Func funcConditionalProcess; private static Func funcBehaviorProcess; private string sConditionString; public void CompilePython() { ScriptManager.GetCondition(Condition); } protected override Composite CreateBehavior() { PrioritySelector decorated = new PrioritySelector(new Composite[0]); foreach (ProfileBehavior behavior in base.GetNodes()) { decorated.AddChild(behavior.Behavior); } return new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckNotAlreadyDone), decorated); } public bool GetConditionExec() { bool flag; try { if (Conditional == null) { Conditional = ScriptManager.GetCondition(Condition); } flag = Conditional(); } catch (Exception exception) { Logging.WriteDiagnostic(ScriptManager.FormatSyntaxErrorException(exception)); BotMain.Stop(false, ""); throw; } return flag; } private bool CheckNotAlreadyDone(object object_0) { return !IsDone; } public override void ResetCachedDone() { foreach (ProfileBehavior behavior in Body) { behavior.ResetCachedDone(); } bComplexDoneCheck = null; } private static bool CheckBehaviorIsDone(ProfileBehavior profileBehavior) { return profileBehavior.IsDone; } [XmlAttribute("condition", true)] public string Condition { get { return sConditionString; } set { sConditionString = value; } } public Func Conditional { get { return funcConditionalProcess; } set { funcConditionalProcess = value; } } public override bool IsDone { get { // Make sure we've not already completed this if if (bAlreadyCompleted.HasValue && bAlreadyCompleted == true) { return true; } // First check the actual "if" conditions if we haven't already if (!bComplexDoneCheck.HasValue) { bComplexDoneCheck = new bool?(GetConditionExec()); } if (bComplexDoneCheck == false) { return true; } // Ok we've already checked that and it was false FIRST check, so now go purely on behavior-done flag if (funcBehaviorProcess == null) { funcBehaviorProcess = new Func(CheckBehaviorIsDone); } bool bAllChildrenDone = Body.All(funcBehaviorProcess); if (bAllChildrenDone) { bAlreadyCompleted = true; } return bAllChildrenDone; } } } // ********************************************************************************************** // ***** TrinityIfWithinRange checks an SNO is in range and processes child nodes ***** // ********************************************************************************************** [XmlElement("TrinityIfSNOInRange")] public class IfSNOInRangeTag : ComplexNodeTag { private bool? bComplexDoneCheck; private bool? bAlreadyCompleted; private Func funcConditionalProcess; private static Func funcBehaviorProcess; private int iSNOID; private float fRadius; private string sType; protected override Composite CreateBehavior() { PrioritySelector decorated = new PrioritySelector(new Composite[0]); foreach (ProfileBehavior behavior in base.GetNodes()) { decorated.AddChild(behavior.Behavior); } return new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckNotAlreadyDone), decorated); } public bool GetConditionExec() { bool flag; Vector3 vMyLocation = ZetaDia.Me.Position; if (sType != null && sType == "reverse") flag = ZetaDia.Actors.GetActorsOfType(true, false).FirstOrDefault(a => a.ActorSNO == SNOID && a.Position.Distance(vMyLocation) <= Range) == null; else flag = (ZetaDia.Actors.GetActorsOfType(true, false).FirstOrDefault(a => a.ActorSNO == SNOID && a.Position.Distance(vMyLocation) <= Range) != null); return flag; } private bool CheckNotAlreadyDone(object object_0) { return !IsDone; } public override void ResetCachedDone() { foreach (ProfileBehavior behavior in Body) { behavior.ResetCachedDone(); } bComplexDoneCheck = null; } private static bool CheckBehaviorIsDone(ProfileBehavior profileBehavior) { return profileBehavior.IsDone; } [XmlAttribute("snoid")] public int SNOID { get { return iSNOID; } set { iSNOID = value; } } [XmlAttribute("range")] public float Range { get { return fRadius; } set { fRadius = value; } } [XmlAttribute("type")] public string Type { get { return sType; } set { sType = value; } } public Func Conditional { get { return funcConditionalProcess; } set { funcConditionalProcess = value; } } public override bool IsDone { get { // Make sure we've not already completed this tag if (bAlreadyCompleted.HasValue && bAlreadyCompleted == true) { return true; } if (!bComplexDoneCheck.HasValue) { bComplexDoneCheck = new bool?(GetConditionExec()); } if (bComplexDoneCheck == false) { return true; } if (funcBehaviorProcess == null) { funcBehaviorProcess = new Func(CheckBehaviorIsDone); } bool bAllChildrenDone = Body.All(funcBehaviorProcess); if (bAllChildrenDone) { bAlreadyCompleted = true; } return bAllChildrenDone; } } } // ********************************************************************************************** // ***** TrinityRandom assigns a random value between min and max to a specified id ***** // ********************************************************************************************** [XmlElement("TrinityRandomRoll")] public class TrinityRandomRollTag : ProfileBehavior { private bool m_IsDone = false; private int iID; private int iMin; private int iMax; public override bool IsDone { get { return m_IsDone; } } protected override Composite CreateBehavior() { return new Zeta.TreeSharp.Action(ret => { // Generate a random value between the selected min-max range, and assign it to our dictionary of random values int iOldValue; Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber)); int iNewRandomValue = (rndNum.Next((Max - Min) + 1)) + Min; Logging.Write("[GilesTrinity] Generating RNG for profile between " + Min.ToString() + " and " + Max.ToString() + ", result=" + iNewRandomValue.ToString()); if (!GilesTrinity.dictRandomID.TryGetValue(ID, out iOldValue)) { GilesTrinity.dictRandomID.Add(ID, iNewRandomValue); } else { GilesTrinity.dictRandomID[ID] = iNewRandomValue; } m_IsDone = true; }); } [XmlAttribute("id")] public int ID { get { return iID; } set { iID = value; } } [XmlAttribute("min")] public int Min { get { return iMin; } set { iMin = value; } } [XmlAttribute("max")] public int Max { get { return iMax; } set { iMax = value; } } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } } // ********************************************************************************************** // ***** TrinityIfRandom only runs the container stuff if the given id is the given value ***** // ********************************************************************************************** [XmlElement("TrinityIfRandom")] public class TrinityIfRandomTag : ComplexNodeTag { private bool? bComplexDoneCheck; private bool? bAlreadyCompleted; private Func funcConditionalProcess; private static Func funcBehaviorProcess; private int iID; private int iResult; protected override Composite CreateBehavior() { PrioritySelector decorated = new PrioritySelector(new Composite[0]); foreach (ProfileBehavior behavior in base.GetNodes()) { decorated.AddChild(behavior.Behavior); } return new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckNotAlreadyDone), decorated); } public bool GetConditionExec() { int iOldValue; // If the dictionary value doesn't even exist, FAIL! if (!GilesTrinity.dictRandomID.TryGetValue(ID, out iOldValue)) return false; // Ok, do the results match up what we want? then SUCCESS! if (iOldValue == Result) return true; // No? Fail! return false; } private bool CheckNotAlreadyDone(object object_0) { return !IsDone; } public override void ResetCachedDone() { foreach (ProfileBehavior behavior in Body) { behavior.ResetCachedDone(); } bComplexDoneCheck = null; } private static bool CheckBehaviorIsDone(ProfileBehavior profileBehavior) { return profileBehavior.IsDone; } [XmlAttribute("id")] public int ID { get { return iID; } set { iID = value; } } [XmlAttribute("result")] public int Result { get { return iResult; } set { iResult = value; } } public Func Conditional { get { return funcConditionalProcess; } set { funcConditionalProcess = value; } } public override bool IsDone { get { // Make sure we've not already completed this tag if (bAlreadyCompleted.HasValue && bAlreadyCompleted == true) { return true; } if (!bComplexDoneCheck.HasValue) { bComplexDoneCheck = new bool?(GetConditionExec()); } if (bComplexDoneCheck == false) { return true; } if (funcBehaviorProcess == null) { funcBehaviorProcess = new Func(CheckBehaviorIsDone); } bool bAllChildrenDone = Body.All(funcBehaviorProcess); if (bAllChildrenDone) { bAlreadyCompleted = true; } return bAllChildrenDone; } } } // ************************************************************************************************ // ***** TrinityUseReset - Resets a UseOnce tag as if it has never been used ***** // ************************************************************************************************ [XmlElement("TrinityUseReset")] public class TrinityUseResetTag : ProfileBehavior { private bool m_IsDone = false; private int iID; public override bool IsDone { get { return m_IsDone; } } protected override Composite CreateBehavior() { return new Zeta.TreeSharp.Action(ret => { // See if we've EVER hit this ID before // If so, delete it, if not, do nothing if (GilesTrinity.hashUseOnceID.Contains(ID)) { GilesTrinity.hashUseOnceID.Remove(ID); GilesTrinity.dictUseOnceID.Remove(ID); } m_IsDone = true; }); } [XmlAttribute("id")] public int ID { get { return iID; } set { iID = value; } } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } } // ************************************************************************************************ // ***** TrinityUseStop - prevents a useonce tag ID ever being used again ***** // ************************************************************************************************ [XmlElement("TrinityUseStop")] public class TrinityUseStopTag : ProfileBehavior { private bool m_IsDone = false; private int iID; public override bool IsDone { get { return m_IsDone; } } protected override Composite CreateBehavior() { return new Zeta.TreeSharp.Action(ret => { // See if we've EVER hit this ID before // If so, set it disabled - if not, add it and prevent it if (GilesTrinity.hashUseOnceID.Contains(ID)) { GilesTrinity.dictUseOnceID[ID] = -1; } else { GilesTrinity.hashUseOnceID.Add(ID); GilesTrinity.dictUseOnceID.Add(ID, -1); } m_IsDone = true; }); } [XmlAttribute("id")] public int ID { get { return iID; } set { iID = value; } } public override void ResetCachedDone() { m_IsDone = false; base.ResetCachedDone(); } } // ************************************************************************************************ // ***** TrinityUseOnce ensures a sequence of tags is only ever used once during this profile ***** // ************************************************************************************************ [XmlElement("TrinityUseOnce")] public class TrinityUseOnceTag : ComplexNodeTag { private bool? bComplexDoneCheck; private bool? bAlreadyCompleted; private Func funcConditionalProcess; private static Func funcBehaviorProcess; private int iUniqueID; private int iMaxRedo; private string sDisablePrevious; protected override Composite CreateBehavior() { PrioritySelector decorated = new PrioritySelector(new Composite[0]); foreach (ProfileBehavior behavior in base.GetNodes()) { decorated.AddChild(behavior.Behavior); } return new Zeta.TreeSharp.Decorator(new CanRunDecoratorDelegate(CheckNotAlreadyDone), decorated); } public bool GetConditionExec() { // See if we've EVER hit this ID before if (GilesTrinity.hashUseOnceID.Contains(ID)) { // See if we've hit it more than or equal to the max times before if (GilesTrinity.dictUseOnceID[ID] >= Max || GilesTrinity.dictUseOnceID[ID] < 0) return false; // Add 1 to our hit count, and let it run this time GilesTrinity.dictUseOnceID[ID]++; return true; } // Never hit this before, so create the entry and let it run // First see if we should disable all other ID's currently hit to prevent them ever being run again this run if (DisablePrevious != null && DisablePrevious.ToLower() == "true") { foreach (int thisid in GilesTrinity.hashUseOnceID) { if (thisid != ID) { GilesTrinity.dictUseOnceID[thisid] = -1; } } } // Now store the fact we have hit this ID and set up the dictionary entry for it GilesTrinity.hashUseOnceID.Add(ID); GilesTrinity.dictUseOnceID.Add(ID, 1); return true; } private bool CheckNotAlreadyDone(object object_0) { return !IsDone; } public override void ResetCachedDone() { foreach (ProfileBehavior behavior in Body) { behavior.ResetCachedDone(); } bComplexDoneCheck = null; } private static bool CheckBehaviorIsDone(ProfileBehavior profileBehavior) { return profileBehavior.IsDone; } [XmlAttribute("id")] public int ID { get { return iUniqueID; } set { iUniqueID = value; } } [XmlAttribute("disableprevious")] public string DisablePrevious { get { return sDisablePrevious; } set { sDisablePrevious = value; } } [XmlAttribute("max")] public int Max { get { return iMaxRedo; } set { iMaxRedo = value; } } public Func Conditional { get { return funcConditionalProcess; } set { funcConditionalProcess = value; } } public override bool IsDone { get { // Make sure we've not already completed this tag if (bAlreadyCompleted.HasValue && bAlreadyCompleted == true) { return true; } if (!bComplexDoneCheck.HasValue) { bComplexDoneCheck = new bool?(GetConditionExec()); } if (bComplexDoneCheck == false) { return true; } if (funcBehaviorProcess == null) { funcBehaviorProcess = new Func(CheckBehaviorIsDone); } bool bAllChildrenDone = Body.All(funcBehaviorProcess); if (bAllChildrenDone) { bAlreadyCompleted = true; } return bAllChildrenDone; } } } // ********************************************************************************************** // ***** Blank Stuck Handler - to disable DB stuck handler ***** // ********************************************************************************************** public class GilesStuckHandler : IStuckHandler { public Vector3 GetUnstuckPos() { return Vector3.Zero; } public bool IsStuck { get { return false; } } } // ********************************************************************************************** // ***** Player Mover Class ***** // ********************************************************************************************** public class GilesPlayerMover : IPlayerMover { private static readonly HashSet hashAvoidLeapingToSNO = new HashSet { 138989, 176074, 176076, 176077, 176536, 260330 }; // 138989 = health pool, 176074 = protection, 176076 = fortune, 176077 = frenzied, 176536 = portal in leorics, 260330 = cooldown shrine // Exp shrines = ???? Other shrines ???? private static bool ShrinesInArea(Vector3 targetpos) { return ZetaDia.Actors.GetActorsOfType(true).Any(u => hashAvoidLeapingToSNO.Contains(u.ActorSNO) && Vector3.Distance(u.Position, targetpos) <= 10f); } // ShrinesInArea checker public void MoveStop() { ZetaDia.Me.UsePower(SNOPower.Walk, ZetaDia.Me.Position, GilesTrinity.iCurrentWorldID, -1); } // Anti-stuck variables private static Vector3 vOldMoveToTarget = Vector3.Zero; public static int iTimesReachedStuckPoint = 0; public static int iTotalAntiStuckAttempts = 1; public static Vector3 vSafeMovementLocation = Vector3.Zero; public static DateTime timeLastRecordedPosition = DateTime.Today; public static Vector3 vOldPosition = Vector3.Zero; public static DateTime timeStartedUnstuckMeasure = DateTime.Today; public static int iTimesReachedMaxUnstucks = 0; public static DateTime timeCancelledUnstuckerFor = DateTime.Today; public static DateTime timeLastReportedAnyStuck = DateTime.Today; public static int iCancelUnstuckerForSeconds = 60; public static DateTime timeLastRestartedGame = DateTime.Today; // ********************************************************************************************** // ***** Check if we are stuck or not ***** // ********************************************************************************************** // Simply checks for position changing max once every 3 seconds, to decide on stuck public static bool UnstuckChecker(Vector3 vMyCurrentPosition) { // Keep checking distance changes every 3 seconds if (DateTime.Now.Subtract(timeLastRecordedPosition).TotalMilliseconds >= 3000) { timeLastRecordedPosition = DateTime.Now; if (vOldPosition != Vector3.Zero && vOldPosition.Distance(vMyCurrentPosition) <= 4f) { return true; } vOldPosition = vMyCurrentPosition; } return false; } // ********************************************************************************************** // ***** Actually deal with a stuck - find an unstuck point etc. ***** // ********************************************************************************************** public static Vector3 UnstuckHandler(Vector3 vMyCurrentPosition, Vector3 vOriginalDestination) { // Update the last time we generated a path timeStartedUnstuckMeasure = DateTime.Now; // If we got stuck on a 2nd/3rd/4th "chained" anti-stuck route, then return the old move to target to keep movement of some kind going if (iTimesReachedStuckPoint > 0) { vSafeMovementLocation = Vector3.Zero; iTimesReachedStuckPoint++; // Reset the path and allow a whole "New" unstuck generation next cycle iTimesReachedStuckPoint = 0; // And cancel unstucking for 9 seconds so DB can try to navigate iCancelUnstuckerForSeconds = (9 * iTotalAntiStuckAttempts); if (iCancelUnstuckerForSeconds < 20) iCancelUnstuckerForSeconds = 20; timeCancelledUnstuckerFor = DateTime.Now; Navigator.Clear(); Logging.WriteDiagnostic("[GilesTrinity] Clearing old route and trying new path find to: " + vOldMoveToTarget.ToString()); Navigator.MoveTo(vOldMoveToTarget, "original destination", false); return vSafeMovementLocation; } // Only try an unstuck 10 times maximum in XXX period of time if (Vector3.Distance(vOriginalDestination, vMyCurrentPosition) >= 700f) { Logging.Write("[GilesTrinity] You are " + Vector3.Distance(vOriginalDestination, vMyCurrentPosition).ToString() + " distance away from your destination."); Logging.Write("[GilesTrinity] This is too far for the unstucker, and is likely a sign of ending up in the wrong map zone."); iTotalAntiStuckAttempts = 20; } //intell if (iTotalAntiStuckAttempts <= 15) { Logging.Write("[GilesTrinity] Your bot got stuck! Trying to unstuck (attempt #" + iTotalAntiStuckAttempts.ToString() + " of 15 attempts)"); Logging.WriteDiagnostic("(destination=" + vOriginalDestination.ToString() + ", which is " + Vector3.Distance(vOriginalDestination, vMyCurrentPosition).ToString() + " distance away)"); GilesTrinity.playerStatus.vCurrentPosition = vMyCurrentPosition; vSafeMovementLocation = GilesTrinity.FindSafeZone(true, iTotalAntiStuckAttempts, Vector3.Zero); // Temporarily log stuff if (iTotalAntiStuckAttempts == 1 && GilesTrinity.settings.bLogStucks) { FileStream LogStream = File.Open(GilesTrinity.sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - Stucks - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read); using (StreamWriter LogWriter = new StreamWriter(LogStream)) { LogWriter.WriteLine(DateTime.Now.ToString() + ": Original Destination=" + vOldMoveToTarget.ToString() + ". Current player position when stuck=" + vMyCurrentPosition.ToString()); LogWriter.WriteLine("Profile Name=" + ProfileManager.CurrentProfile.Name); } LogStream.Close(); } // Now count up our stuck attempt generations iTotalAntiStuckAttempts++; return vSafeMovementLocation; } iTimesReachedMaxUnstucks++; iTotalAntiStuckAttempts = 1; vSafeMovementLocation = Vector3.Zero; vOldPosition = Vector3.Zero; iTimesReachedStuckPoint = 0; timeLastRecordedPosition = DateTime.Today; timeStartedUnstuckMeasure = DateTime.Today; int iSafetyLoops = 0; if (iTimesReachedMaxUnstucks == 1) { Navigator.Clear(); Logging.Write("[GilesTrinity] Anti-stuck measures now attempting to kickstart DB's path-finder into action."); Navigator.MoveTo(vOriginalDestination, "original destination", false); iCancelUnstuckerForSeconds = 40; timeCancelledUnstuckerFor = DateTime.Now; return vSafeMovementLocation; } if (iTimesReachedMaxUnstucks == 2) { Logging.Write("[GilesTrinity] Anti-stuck measures failed. Now attempting to reload current profile."); // First see if we need to, and can, teleport to town while (!ZetaDia.Me.IsInTown) { iSafetyLoops++; GilesTrinity.WaitWhileAnimating(5, true); ZetaDia.Me.UsePower(SNOPower.UseStoneOfRecall, ZetaDia.Me.Position, ZetaDia.Me.WorldDynamicId, -1); Thread.Sleep(1000); GilesTrinity.WaitWhileAnimating(1000, true); if (iSafetyLoops > 5) break; } Thread.Sleep(1000); // As long as we successfully reached town, reload the profile if (ZetaDia.Me.IsInTown) { ProfileManager.Load(Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile); Logging.Write("[GilesTrinity] Anti-stuck successfully reloaded current profile, DemonBuddy now navigating again."); Thread.Sleep(3000); return vSafeMovementLocation; } // Didn't make it to town, so skip instantly to the exit game system iTimesReachedMaxUnstucks = 3; } // Exit the game and reload the profile if (GilesTrinity.settings.bEnableProfileReloading && DateTime.Now.Subtract(timeLastRestartedGame).TotalMinutes >= 15) { timeLastRestartedGame = DateTime.Now; string sUseProfile = GilesTrinity.sFirstProfileSeen; Logging.Write("[GilesTrinity] Anti-stuck measures exiting current game."); // Load the first profile seen last run ProfileManager.Load(!string.IsNullOrEmpty(sUseProfile) ? sUseProfile : Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile); Thread.Sleep(1000); GilesTrinity.GilesResetEverythingNewGame(); ZetaDia.Service.Games.LeaveGame(); // Wait for 10 second log out timer if not in town if (!ZetaDia.Me.IsInTown) { Thread.Sleep(10000); } } else { Logging.Write("[GilesTrinity] Unstucking measures failed. Now stopping Trinity unstucker for 12 minutes to inactivity timers to kick in or DB to auto-fix."); iCancelUnstuckerForSeconds = 720; timeCancelledUnstuckerFor = DateTime.Now; return vSafeMovementLocation; } return vSafeMovementLocation; } // ********************************************************************************************** // ***** Handle moveto requests from the current routine/profile ***** // ********************************************************************************************** // This replaces DemonBuddy's own built-in "Basic movement handler" with a custom one private static Vector3 vLastMoveTo = Vector3.Zero; private static bool bLastWaypointWasTown = false; private static HashSet hashDoneThisVector = new HashSet(); private static Vector3 vShiftedPosition = Vector3.Zero; private static DateTime lastShiftedPosition = DateTime.Today; private static int iShiftPositionFor = 0; public void MoveTowards(Vector3 vMoveToTarget) { // Recording of all the XML's in use this run string sThisProfile = Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile; if (sThisProfile != GilesTrinity.sLastProfileSeen) { // See if we appear to have started a new game if (GilesTrinity.sFirstProfileSeen != "" && sThisProfile == GilesTrinity.sFirstProfileSeen) { GilesTrinity.iTotalProfileRecycles++; if (GilesTrinity.iTotalProfileRecycles > GilesTrinity.iTotalJoinGames && GilesTrinity.iTotalProfileRecycles > GilesTrinity.iTotalLeaveGames) { GilesTrinity.GilesResetEverythingNewGame(); } } GilesTrinity.listProfilesLoaded.Add(sThisProfile); GilesTrinity.sLastProfileSeen = sThisProfile; if (GilesTrinity.sFirstProfileSeen == "") GilesTrinity.sFirstProfileSeen = sThisProfile; } // The below code is to help profile/routine makers avoid waypoints with a long distance between them. // Long-distances between waypoints is bad - it increases stucks, and forces the DB nav-server to be called. if (GilesTrinity.settings.bLogStucks) { if (vLastMoveTo == Vector3.Zero) vLastMoveTo = vMoveToTarget; if (vMoveToTarget != vLastMoveTo) { float fDistance = Vector3.Distance(vMoveToTarget, vLastMoveTo); // Log if not in town, last waypoint wasn't FROM town, and the distance is >200 but <2000 (cos 2000+ probably means we changed map zones!) if (!ZetaDia.Me.IsInTown && !bLastWaypointWasTown && fDistance >= 200 & fDistance <= 2000) { if (!hashDoneThisVector.Contains(vMoveToTarget)) { // Log it FileStream LogStream = File.Open(GilesTrinity.sTrinityPluginPath + ZetaDia.Service.CurrentHero.BattleTagName + " - LongPaths - " + ZetaDia.Actors.Me.ActorClass.ToString() + ".log", FileMode.Append, FileAccess.Write, FileShare.Read); using (StreamWriter LogWriter = new StreamWriter(LogStream)) { LogWriter.WriteLine(DateTime.Now.ToString() + ":"); LogWriter.WriteLine("Profile Name=" + ProfileManager.CurrentProfile.Name); LogWriter.WriteLine("'From' Waypoint=" + vLastMoveTo.ToString() + ". 'To' Waypoint=" + vMoveToTarget.ToString() + ". Distance=" + fDistance.ToString()); } LogStream.Close(); hashDoneThisVector.Add(vMoveToTarget); } } vLastMoveTo = vMoveToTarget; bLastWaypointWasTown = false; if (ZetaDia.Me.IsInTown) bLastWaypointWasTown = true; } } // Make sure GilesTrinity doesn't want us to avoid routine-movement if (GilesTrinity.bDontMoveMeIAmDoingShit) return; // Store player current position Vector3 vMyCurrentPosition = ZetaDia.Me.Position; // Store distance to current moveto target float fDistanceFromTarget; // Do unstuckery things if (GilesTrinity.settings.bEnableUnstucker) { // Store the "real" (not anti-stuck) destination vOldMoveToTarget = vMoveToTarget; // See if we can reset the 10-limit unstuck counter, if >120 seconds since we last generated an unstuck location if (iTotalAntiStuckAttempts > 1 && DateTime.Now.Subtract(timeStartedUnstuckMeasure).TotalSeconds >= 120) { iTotalAntiStuckAttempts = 1; iTimesReachedStuckPoint = 0; vSafeMovementLocation = Vector3.Zero; } // See if we need to, and can, generate unstuck actions if (DateTime.Now.Subtract(timeCancelledUnstuckerFor).TotalSeconds > iCancelUnstuckerForSeconds && UnstuckChecker(vMyCurrentPosition)) { // Record the time we last apparently couldn't move for a brief period of time timeLastReportedAnyStuck = DateTime.Now; // See if there's any stuck position to try and navigate to generated by random mover vSafeMovementLocation = UnstuckHandler(vMyCurrentPosition, vOldMoveToTarget); if (vSafeMovementLocation == Vector3.Zero) return; } // See if we can clear the total unstuckattempts if we haven't been stuck in over 6 minutes. if (DateTime.Now.Subtract(timeLastReportedAnyStuck).TotalSeconds >= 360) { iTimesReachedMaxUnstucks = 0; } // Did we have a safe point already generated (eg from last loop through), if so use it as our current location instead if (vSafeMovementLocation != Vector3.Zero) { // Set our current movement target to the safe point we generated last cycle vMoveToTarget = vSafeMovementLocation; } // Get distance to current destination fDistanceFromTarget = Vector3.Distance(vMyCurrentPosition, vMoveToTarget); // Remove the stuck position if it's been reached, this bit of code also creates multiple stuck-patterns in an ever increasing amount if (vSafeMovementLocation != Vector3.Zero && fDistanceFromTarget <= 3f) { vSafeMovementLocation = Vector3.Zero; iTimesReachedStuckPoint++; // Do we want to immediately generate a 2nd waypoint to "chain" anti-stucks in an ever-increasing path-length? if (iTimesReachedStuckPoint <= iTotalAntiStuckAttempts) { GilesTrinity.playerStatus.vCurrentPosition = vMyCurrentPosition; vSafeMovementLocation = GilesTrinity.FindSafeZone(true, iTotalAntiStuckAttempts, Vector3.Zero); vMoveToTarget = vSafeMovementLocation; } else { Logging.WriteDiagnostic("[GilesTrinity] Clearing old route and trying new path find to: " + vOldMoveToTarget.ToString()); // Reset the path and allow a whole "New" unstuck generation next cycle iTimesReachedStuckPoint = 0; // And cancel unstucking for 9 seconds so DB can try to navigate iCancelUnstuckerForSeconds = (9 * iTotalAntiStuckAttempts); if (iCancelUnstuckerForSeconds < 20) iCancelUnstuckerForSeconds = 20; timeCancelledUnstuckerFor = DateTime.Now; Navigator.Clear(); Navigator.MoveTo(vOldMoveToTarget, "original destination", false); return; } } } else { // Get distance to current destination fDistanceFromTarget = Vector3.Distance(vMyCurrentPosition, vMoveToTarget); } // Is the built-in unstucker enabled or not? // See if there's an obstacle in our way, if so try to navigate around it Vector3 point = vMoveToTarget; foreach (GilesTrinity.GilesObstacle tempobstacle in GilesTrinity.hashNavigationObstacleCache.Where(cp => GilesTrinity.GilesIntersectsPath(cp.vThisLocation, cp.fThisRadius, vMyCurrentPosition, point) && cp.vThisLocation.Distance(vMyCurrentPosition) > GilesTrinity.dictSNONavigationSize[cp.iThisSNOID])) { if (vShiftedPosition == Vector3.Zero) { if (DateTime.Now.Subtract(lastShiftedPosition).TotalSeconds >= 10) { float fDirectionToTarget = GilesTrinity.FindDirectionDegree(vMyCurrentPosition, vMoveToTarget); vMoveToTarget = MathEx.GetPointAt(vMyCurrentPosition, 15f, MathEx.ToRadians(fDirectionToTarget - 50)); if (!GilesTrinity.GilesCanRayCast(vMyCurrentPosition, vMoveToTarget, NavCellFlags.AllowWalk)) { vMoveToTarget = MathEx.GetPointAt(vMyCurrentPosition, 15f, MathEx.ToRadians(fDirectionToTarget + 50)); if (!GilesTrinity.GilesCanRayCast(vMyCurrentPosition, vMoveToTarget, NavCellFlags.AllowWalk)) { vMoveToTarget = point; } } if (vMoveToTarget != point) { vShiftedPosition = vMoveToTarget; iShiftPositionFor = 900; lastShiftedPosition = DateTime.Now; Logging.WriteDiagnostic("[GilesTrinity] Navigation handler position shift to: " + vMoveToTarget.ToString() + " (was " + point.ToString() + ")"); } } // Make sure we only shift max once every 10 seconds } else { if (DateTime.Now.Subtract(lastShiftedPosition).TotalMilliseconds <= iShiftPositionFor) { vMoveToTarget = vShiftedPosition; } else { vShiftedPosition = Vector3.Zero; } } } // See if we can use abilities like leap etc. for movement out of combat, but not in town if (GilesTrinity.settings.bOutOfCombatMovementPowers && !ZetaDia.Me.IsInTown) { bool bTooMuchZChange = ((vMyCurrentPosition.Z - vMoveToTarget.Z) >= 4f); // Leap movement for a barb if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_Leap) && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_Leap]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Barbarian_Leap] && fDistanceFromTarget >= 20f && PowerManager.CanCast(SNOPower.Barbarian_Leap) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (fDistanceFromTarget > 35f) vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f); ZetaDia.Me.UsePower(SNOPower.Barbarian_Leap, vThisTarget, GilesTrinity.iCurrentWorldID, -1); GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_Leap] = DateTime.Now; return; } // Furious Charge movement for a barb if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Barbarian_FuriousCharge) && !bTooMuchZChange && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Barbarian_FuriousCharge] && fDistanceFromTarget >= 20f && PowerManager.CanCast(SNOPower.Barbarian_FuriousCharge) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (fDistanceFromTarget > 35f) vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f); ZetaDia.Me.UsePower(SNOPower.Barbarian_FuriousCharge, vThisTarget, GilesTrinity.iCurrentWorldID, -1); GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge] = DateTime.Now; return; } // Vault for a DH - maximum set by user-defined setting if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.DemonHunter_Vault) && !bTooMuchZChange && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.DemonHunter_Vault]).TotalMilliseconds >= GilesTrinity.settings.iDHVaultMovementDelay && fDistanceFromTarget >= 18f && PowerManager.CanCast(SNOPower.DemonHunter_Vault) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (fDistanceFromTarget > 35f) vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f); ZetaDia.Me.UsePower(SNOPower.DemonHunter_Vault, vThisTarget, GilesTrinity.iCurrentWorldID, -1); GilesTrinity.dictAbilityLastUse[SNOPower.DemonHunter_Vault] = DateTime.Now; return; } // Tempest rush for a monk if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Monk_TempestRush) && !bTooMuchZChange && ZetaDia.Me.CurrentPrimaryResource >= 20) { Vector3 vTargetAimPoint = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 10f); ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, vTargetAimPoint, GilesTrinity.iCurrentWorldID, -1); return; } // Teleport for a wizard (need to be able to check skill rune in DB for a 3-4 teleport spam in a row) if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Teleport) && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Wizard_Teleport] && fDistanceFromTarget >= 20f && PowerManager.CanCast(SNOPower.Wizard_Teleport) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (fDistanceFromTarget > 35f) vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f); ZetaDia.Me.UsePower(SNOPower.Wizard_Teleport, vThisTarget, GilesTrinity.iCurrentWorldID, -1); GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport] = DateTime.Now; return; } // Archon Teleport for a wizard if (GilesTrinity.hashPowerHotbarAbilities.Contains(SNOPower.Wizard_Archon_Teleport) && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Wizard_Archon_Teleport] && fDistanceFromTarget >= 20f && PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport) && !ShrinesInArea(vMoveToTarget)) { Vector3 vThisTarget = vMoveToTarget; if (fDistanceFromTarget > 35f) vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f); ZetaDia.Me.UsePower(SNOPower.Wizard_Archon_Teleport, vThisTarget, GilesTrinity.iCurrentWorldID, -1); GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport] = DateTime.Now; return; } } // Allowed to use movement powers to move out-of-combat? ZetaDia.Me.UsePower(SNOPower.Walk, vMoveToTarget, GilesTrinity.iCurrentWorldID, -1); } } // ********************************************************************************************** // ***** Give DB a Blank Combat Target Provider ***** // ********************************************************************************************** public class GilesCombatTargetingReplacer : ITargetingProvider { private static readonly List listEmptyList = new List(); public List GetObjectsByWeight() { if (!GilesTrinity.bDontMoveMeIAmDoingShit || GilesTrinity.thisFakeObject == null) return listEmptyList; List listFakeList = new List(); listFakeList.Add(GilesTrinity.thisFakeObject); return listFakeList; } } public class GilesLootTargetingProvider : ITargetingProvider { private static readonly List listEmptyList = new List(); public List GetObjectsByWeight() { return listEmptyList; } } public class GilesObstacleTargetingProvider : ITargetingProvider { private static readonly List listEmptyList = new List(); public List GetObjectsByWeight() { return listEmptyList; } } }