protected override bool ShouldVault(out Vector3 destination) { destination = Vector3.Zero; if (!Skills.DemonHunter.Vault.CanCast()) { return(false); } // The more we stand still the more damage we deal if (IsInCombat && !Core.Avoidance.Avoider.ShouldAvoid && !Core.Avoidance.Avoider.ShouldKite && Sets.EndlessWalk.IsEquipped) { return(false); } // Try to vault to Occulus AoE whenever possible Vector3 bestBuffedPosition; var bestClusterPoint = TargetUtil.GetBestClusterPoint(); if (TargetUtil.BestBuffPosition(45f, bestClusterPoint, false, out bestBuffedPosition) && Player.Position.Distance2D(bestBuffedPosition) > 25f && bestBuffedPosition != Vector3.Zero) { Core.Logger.Log($"Found buff position - distance: {Player.Position.Distance(bestBuffedPosition)} ({bestBuffedPosition})"); destination = bestBuffedPosition; return(destination != Vector3.Zero); } return(base.ShouldVault(out destination)); }
protected override bool ShouldDashingStrike(out Vector3 position) { position = Vector3.Zero; if (!Skills.Monk.DashingStrike.CanCast()) { return(false); } if (Skills.Monk.DashingStrike.TimeSinceUse < 750) { return(false); } if (!AllowedToUse(Settings.DashingStrike, Skills.Monk.DashingStrike)) { return(false); } Vector3 bestBuffedPosition; var bestClusterPoint = TargetUtil.GetBestClusterPoint(); if (TargetUtil.BestBuffPosition(40f, bestClusterPoint, false, out bestBuffedPosition) && bestBuffedPosition != Vector3.Zero) { Core.Logger.Log($"Found buff position - distance: {Player.Position.Distance(bestBuffedPosition)} ({bestBuffedPosition})"); position = bestBuffedPosition; return(position != Vector3.Zero); } return(false); }
protected override bool ShouldFuriousCharge(out Vector3 position) { position = Vector3.Zero; if (!Skills.Barbarian.FuriousCharge.CanCast()) { return(false); } if (!TargetUtil.AnyMobsInRange(60f)) { return(false); } if (Core.Avoidance.Grid.IsIntersectedByFlags(ZetaDia.Me.Position, position, AvoidanceFlags.CriticalAvoidance)) { return(false); } // Try to charge to Occulus AoE before the Whack-a-mole frenzy begins Vector3 bestBuffedPosition; var bestClusterPoint = TargetUtil.GetBestClusterPoint(); if (TargetUtil.BestBuffPosition(60f, bestClusterPoint, false, out bestBuffedPosition) && Player.Position.Distance2D(bestBuffedPosition) > 10f && bestBuffedPosition != Vector3.Zero && !ShouldWaitForConventionofElements(Skills.Crusader.Provoke, Element.Fire, 350)) { Core.Logger.Log($"Found buff position - distance: {Player.Position.Distance(bestBuffedPosition)} ({bestBuffedPosition})"); position = bestBuffedPosition; return(position != Vector3.Zero); } var chargeStacks = Core.Buffs.GetBuffStacks(SNOPower.P2_ItemPassive_Unique_Ring_026); if (Core.Buffs.ConventionElement == Element.Fire && chargeStacks > 0) { return(false); } // Quickest way to build stacks is to charge in place (if we can get a refund for every charge) var twelveYardsUnitCount = TargetUtil.UnitsInRangeOfPosition(Player.Position, 12f).Count(u => u.IsUnit); var twentyYardsUnitCount = TargetUtil.UnitsInRangeOfPosition(Player.Position, 20f).Count(u => u.IsUnit); if (twelveYardsUnitCount >= 3 || twentyYardsUnitCount == 1) { position = Player.Position; } position = TargetUtil.GetBestClusterPoint(15f, 20f, true, false); return(position != Vector3.Zero); }
/// <summary> /// Walk towards a location with positional bonuses e.g. occulus damage bonus / serenity defensive bonus. /// </summary> /// <param name="power">Trinity power configured to move player towards a buffed position</param> /// <param name="maxDistance">maximum distance spot can be from player's current position</param> /// <param name="arriveDistance">how close to get to the middle of the spot before stopping walking</param> /// <returns>if a location was found and should be moved to</returns> public static bool TryMoveToBuffedSpot(out TrinityPower power, float maxDistance, float arriveDistance = 20f) { power = null; if (!IsInCombat || IsCurrentlyKiting || IsCurrentlyAvoiding) { return(false); } if (!TargetUtil.BestBuffPosition(maxDistance, Player.Position, true, out Vector3 buffedLocation)) { return(false); } var distance = buffedLocation.Distance(Player.Position); Core.Logger.Verbose(LogCategory.Routine, $"Buffed location found Dist={distance}"); if (buffedLocation.Distance(Player.Position) < arriveDistance) { Core.Logger.Log(LogCategory.Routine, $"Standing in Buffed Position {buffedLocation} Dist={distance}"); } else if (!Core.Avoidance.Grid.CanRayWalk(Player.Position, buffedLocation)) { Core.Logger.Log(LogCategory.Routine, $"Unable to straight-line path to Buffed Position {buffedLocation} Dist={distance}"); } else if (!Core.Avoidance.Grid.CanRayWalk(TrinityCombat.Targeting.CurrentTarget.Position, buffedLocation)) { Core.Logger.Log(LogCategory.Routine, $"Can't see target from buffed position {buffedLocation} Dist={distance}"); } else if (Core.Avoidance.Avoider.IsKiteOnCooldown) { Core.Logger.Log(LogCategory.Routine, $"Not moving to buffed location while on kite cooldown"); } else if (IsKitingEnabled && TargetUtil.AnyMobsInRangeOfPosition(buffedLocation, TrinityCombat.Routines.Current.KiteDistance)) { Core.Logger.Verbose(LogCategory.Routine, $"Moving to buffed spot would trigger kiting away from it."); } else { Core.Logger.Verbose(LogCategory.Routine, $"Moving to Buffed Position {buffedLocation} Dist={distance}"); power = new TrinityPower(SNOPower.Walk, maxDistance, buffedLocation); return(true); } return(false); }
private bool ShouldWalkToGroundBuff(out Vector3 buffPosition) { buffPosition = Vector3.Zero; if (!Settings.MoveToGroundBuffs) { return(false); } // Don't try to move to the buff if the grid is flagged for avoidance where the buff is. if (_lastBuffPosition != Vector3.Zero && Core.Avoidance.Grid.IsLocationInFlags(_lastBuffPosition, AvoidanceFlags.Avoidance)) { _lastBuffPosition = Vector3.Zero; return(false); } if (_lastBuffPosition != Vector3.Zero && !Core.Grids.CanRayWalk(Player.Position, _lastBuffPosition)) { _lastBuffPosition = Vector3.Zero; return(false); } if (_lastBuffPosition != Vector3.Zero && Player.Position.Distance(_lastBuffPosition) > 2f && !_groundBuffWalkTimer.IsFinished) { Core.Logger.Log($"Moving to buff: {_lastBuffPosition} - Distance: {Player.Position.Distance(_lastBuffPosition)}"); return(true); } _lastBuffPosition = Vector3.Zero; Vector3 bestBuffedPosition; var bestClusterPoint = TargetUtil.GetBestClusterPoint(); if (TargetUtil.BestBuffPosition(40f, bestClusterPoint, false, out bestBuffedPosition) && bestBuffedPosition != Vector3.Zero) { Core.Logger.Log($"Found buff: {_lastBuffPosition} - Distance: {Player.Position.Distance(_lastBuffPosition)}"); buffPosition = bestBuffedPosition; if (bestBuffedPosition != Vector3.Zero) { _lastBuffPosition = bestBuffedPosition; _groundBuffWalkTimer.Reset(); return(true); } } return(false); }
/// <summary> /// Walk towards a location with positional bonuses e.g. occulus damage bonus / serenity defensive bonus. /// </summary> /// <param name="power">Trinity power configured to move player towards a buffed position</param> /// <param name="maxDistance">maximum distance spot can be from player's current position</param> /// <param name="arriveDistance">how close to get to the middle of the spot before stopping walking</param> /// <returns>if a location was found and should be moved to</returns> public static bool TryMoveToBuffedSpot(out TrinityPower power, float maxDistance, float arriveDistance = 20f) { power = null; if (IsInCombat && !IsCurrentlyKiting && !IsCurrentlyAvoiding) { Vector3 buffedLocation; if (TargetUtil.BestBuffPosition(maxDistance, Player.Position, true, out buffedLocation)) { //var lastPower = SpellHistory.LastPower; var distance = buffedLocation.Distance(Player.Position); Core.Logger.Verbose(LogCategory.Routine, $"Buffed location found Dist={distance}"); if (buffedLocation.Distance(Player.Position) < arriveDistance) { Core.Logger.Log(LogCategory.Routine, $"Standing in Buffed Position {buffedLocation} Dist={distance}"); } else if (!Core.Avoidance.Grid.CanRayWalk(Player.Position, buffedLocation)) { Core.Logger.Log(LogCategory.Routine, $"Unable to straight-line path to Buffed Position {buffedLocation} Dist={distance}"); } else if (!Core.Avoidance.Grid.CanRayWalk(TrinityCombat.Targeting.CurrentTarget.Position, buffedLocation)) { Core.Logger.Log(LogCategory.Routine, $"Can't see target from buffed position {buffedLocation} Dist={distance}"); } else if (Core.Avoidance.Avoider.IsKiteOnCooldown) { Core.Logger.Log(LogCategory.Routine, $"Not moving to buffed location while on kite cooldown"); } //else if (checkPowerRange && lastPower != null && buffedLocation.Distance(Combat.Targeting.CurrentTarget.Position) > lastPower.MinimumRange + Combat.Targeting.CurrentTarget.CollisionRadius + Player.Radius) //{ // Core.Logger.Verbose(LogCategory.Routine, $"Buffed spot outside attack range for power {lastPower.SNOPower} Range={lastPower.MinimumRange} TimeSinceUse={lastPower.TimeSinceUseMs} Dist={distance}"); //} else if (IsKitingEnabled && TargetUtil.AnyMobsInRangeOfPosition(buffedLocation, TrinityCombat.Routines.Current.KiteDistance)) { Core.Logger.Verbose(LogCategory.Routine, $"Moving to buffed spot would trigger kiting away from it."); } else { Core.Logger.Verbose(LogCategory.Routine, $"Moving to Buffed Position {buffedLocation} Dist={distance}"); power = new TrinityPower(SNOPower.Walk, maxDistance, buffedLocation); return(true); } } } return(false); }
protected override bool ShouldBloodRush(out Vector3 position) { position = Vector3.Zero; if (!Skills.Necromancer.BloodRush.CanCast()) { return(false); } if (Skills.Necromancer.BloodRush.TimeSinceUse < 750) { return(false); } if (!AllowedToUse(Settings.BloodRush, Skills.Necromancer.BloodRush)) { return(false); } // Dont move from outside avoidance into avoidance. if (!Core.Avoidance.InCriticalAvoidance(Player.Position) && Core.Avoidance.Grid.IsIntersectedByFlags(Player.Position, position, AvoidanceFlags.CriticalAvoidance)) { return(false); } // Try to Rush to Occulus AoE whenever possible Vector3 bestBuffedPosition; var bestClusterPoint = TargetUtil.GetBestClusterPoint(); if (TargetUtil.BestBuffPosition(50f, bestClusterPoint, false, out bestBuffedPosition) && Player.Position.Distance2D(bestBuffedPosition) > 10f && bestBuffedPosition != Vector3.Zero && (Skills.Necromancer.LandOfTheDead.CanCast() || Skills.Necromancer.LandOfTheDead.IsBuffActive)) { Core.Logger.Log($"Found buff position - distance: {Player.Position.Distance(bestBuffedPosition)} ({bestBuffedPosition})"); position = bestBuffedPosition; return(position != Vector3.Zero); } // Find a safespot with no monsters within range. Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15f, 60f, Player.Position, AvoidCondition); return(position != null); }
protected override bool ShouldDashingStrike(out Vector3 position) { position = Vector3.Zero; if (!Skills.Monk.DashingStrike.CanCast()) { return(false); } if (Skills.Monk.DashingStrike.TimeSinceUse < 750) { return(false); } if (!AllowedToUse(Settings.DashingStrike, Skills.Monk.DashingStrike)) { return(false); } // Dont move from outside avoidance into avoidance. if (!Core.Avoidance.InAvoidance(Player.Position) && Core.Avoidance.Grid.IsLocationInFlags(position, AvoidanceFlags.Avoidance)) { return(false); } // Try to dash to Occulus AoE whenever possible Vector3 bestBuffedPosition; var bestClusterPoint = TargetUtil.GetBestClusterPoint(); if (TargetUtil.BestBuffPosition(60f, bestClusterPoint, false, out bestBuffedPosition) && Player.Position.Distance2D(bestBuffedPosition) > 10f && bestBuffedPosition != Vector3.Zero) { Core.Logger.Log($"Found buff position - distance: {Player.Position.Distance(bestBuffedPosition)} ({bestBuffedPosition})"); position = bestBuffedPosition; return(position != Vector3.Zero); } // Find a safespot with no monsters within range. Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15f, 60f, Player.Position, node => !TargetUtil.AnyMobsInRangeOfPosition(node.NavigableCenter)); return(position != Vector3.Zero); }
protected override bool ShouldTeleport(out Vector3 position) { position = Vector3.Zero; if (!Skills.Wizard.Teleport.CanCast()) { return(false); } if (Skills.Wizard.Teleport.TimeSinceUse < 200) { return(false); } if (!AllowedToUse(Settings.Teleport, Skills.Wizard.Teleport)) { return(false); } // Dont move from outside avoidance into avoidance. if (!Core.Avoidance.InAvoidance(Player.Position) && Core.Avoidance.Grid.IsLocationInFlags(position, AvoidanceFlags.Avoidance)) { return(false); } Vector3 bestBuffedPosition; var hasAPDs = Legendary.AncientParthanDefenders.IsEquipped; var bestClusterPoint = TargetUtil.GetBestClusterPoint(); var teleportPoint = hasAPDs ? bestClusterPoint : TargetUtil.GetSafeSpotPosition(40f); var oculusMobs = hasAPDs ? TargetUtil.NumMobsInRangeOfPosition(teleportPoint, 10f) > 3 : true; var distance = hasAPDs ? 25f : 50f; if (TargetUtil.BestBuffPosition(distance, bestClusterPoint, false, out bestBuffedPosition) && Player.Position.Distance2D(bestBuffedPosition) > 10f && bestBuffedPosition != Vector3.Zero && oculusMobs) { Core.Logger.Log($"Found buff position - distance: {Player.Position.Distance(bestBuffedPosition)} ({bestBuffedPosition})"); position = bestBuffedPosition; return(position != Vector3.Zero); } position = teleportPoint; return(position != Vector3.Zero); }
private bool ShouldWalkToGroundBuff(out Vector3 buffPosition) { buffPosition = Vector3.Zero; if (!Settings.MoveToGroundBuffs || CurrentTarget == null) { return(false); } if (_lastBuffPosition != Vector3.Zero && _lastBuffPosition.Distance2D(CurrentTarget.Position) > 20) { return(false); } if (_lastBuffPosition != Vector3.Zero && Player.Position.Distance2D(_lastBuffPosition) > 9f && !_groundBuffWalkTimer.IsFinished) { Core.Logger.Log($"Moving to buff: {_lastBuffPosition} - Distance: {Player.Position.Distance2D(_lastBuffPosition)}"); return(true); } _lastBuffPosition = Vector3.Zero; Vector3 bestBuffedPosition; var bestClusterPoint = TargetUtil.GetBestClusterPoint(); if (TargetUtil.BestBuffPosition(40f, bestClusterPoint, false, out bestBuffedPosition) && bestBuffedPosition != Vector3.Zero) { Core.Logger.Log($"Found buff: {bestBuffedPosition} - Distance: {Player.Position.Distance2D(bestBuffedPosition)}"); buffPosition = bestBuffedPosition; if (bestBuffedPosition != Vector3.Zero) { _lastBuffPosition = bestBuffedPosition; _groundBuffWalkTimer.Reset(); return(true); } } return(false); }
public TrinityPower GetOffensivePower() { // Ported from Phelon's Cold Garg routine TrinityPower power; TrinityActor target; // Recast gargs if they are being lazy. var myGargs = TargetUtil.FindPets(PetType.Gargantuan); var distGargsToTarget = TargetUtil.Centroid(myGargs.Select(g => g.Position)).Distance(CurrentTarget.Position); if (distGargsToTarget > 30f && Player.PrimaryResourcePct > 0.5f && Skills.WitchDoctor.Gargantuan.CanCast()) { return(Gargantuan(CurrentTarget.Position)); } var bestDpsTarget = TargetUtil.BestAoeUnit(35f, true); Vector3 bestDpsPosition; if (Core.Player.HasBuff(SNOPower.Witchdoctor_SpiritWalk)) { if (Player.CurrentHealthPct < EmergencyHealthPct && TargetUtil.ClosestGlobe(35f, true) != null) { return(Walk(TargetUtil.ClosestGlobe(35f, true).Position)); } if (TargetUtil.BestBuffPosition(35f, bestDpsTarget.Position, false, out bestDpsPosition) && bestDpsPosition.Distance2D(Player.Position) > 6f) { return(Walk(bestDpsPosition)); } } if (TargetUtil.BestBuffPosition(12f, bestDpsTarget.Position, false, out bestDpsPosition) && (TargetUtil.UnitsBetweenLocations(Player.Position, bestDpsPosition).Count < 6 || Legendary.IllusoryBoots.IsEquipped) && bestDpsPosition.Distance2D(Player.Position) > 6f) { return(Walk(bestDpsPosition)); } if (TrySpecialPower(out power)) { return(power); } if (ShouldPiranhas(out target)) { return(Piranhas(target)); } if (ShouldWallOfDeath(out target)) { return(WallOfDeath(target)); } if (ShouldHaunt(out target)) { return(Haunt(target)); } if (TrySecondaryPower(out power)) { return(power); } if (TryPrimaryPower(out power)) { return(power); } if (Settings.KiteVariation == KiteVariation.DistantEmptySpace) { // Stand still for damage buff - defualt Avoider.SafeSpot includes logic // to suppress minor variations in safespot position if less than 12f if (Player.CurrentHealthPct > 0.8f && !TargetUtil.AnyMobsInRange(15f)) { return(Walk(Avoider.SafeSpot)); } return(Walk(TargetUtil.GetLoiterPosition(bestDpsTarget, 25f))); } //KiteVariation.NearTargetCluster return(Walk(Avoider.SafeSpot)); }
public TrinityPower GetOffensivePower() { Vector3 position; var allUnits = Core.Targets.ByType[TrinityObjectType.Unit].Where(u => u.IsUnit && u.RadiusDistance <= 50f).ToList(); var clusterUnits = (from u in allUnits where u.IsUnit && u.Weight > 0 && !u.IsPlayer orderby u.NearbyUnitsWithinDistance(15f) descending, u.Distance, u.HitPointsPct descending select u).ToList(); var bestClusterUnit = clusterUnits.FirstOrDefault(); //10 second 60% damage reduction should always be on to survive if (!HasJeramsRevengeBuff && Player.CurrentHealthPct > 0.4 && !Core.Avoidance.InCriticalAvoidance(Player.Position) && (ZetaDia.Me.IsInCombat || Player.CurrentHealthPct < 0.4) && bestClusterUnit != null && Skills.WitchDoctor.WallOfDeath.CanCast()) { return(WallOfDeath(allUnits.FirstOrDefault())); } if (bestClusterUnit != null) { if (Player.HasBuff(SNOPower.Witchdoctor_Hex)) { Vector3 explodePos = PlayerMover.IsBlocked ? Player.Position : bestClusterUnit.Position; return(ExplodeChicken(explodePos)); } if (!HasJeramsRevengeBuff && ZetaDia.Me.IsInCombat && Skills.WitchDoctor.WallOfDeath.CanCast()) { var target = allUnits.FirstOrDefault(); if (target != null) { return(WallOfDeath(target)); } } // Make sure we approach carefully when our defenses are down var harvestStacks = Skills.WitchDoctor.SoulHarvest.BuffStacks; var bestHarvestCluster = TargetUtil.GetBestClusterPoint(18f, 50f); if (Core.Rift.IsGreaterRift && harvestStacks < 10 && Legendary.LakumbasOrnament.IsEquipped) { if (Skills.WitchDoctor.SpiritWalk.CanCast() && TargetUtil.ClusterExists(18f, 50f, 2)) { return(SpiritWalk()); } if (Skills.WitchDoctor.SpiritWalk.IsBuffActive && bestHarvestCluster != null) { return(Walk(bestHarvestCluster)); } } // SpiritWalk for the invulnerability var shouldBecomeInvulnerable = Core.Avoidance.Avoider.ShouldAvoid || Core.Avoidance.Avoider.ShouldKite || (Player.CurrentHealthPct < 0.9f && Core.Rift.IsGreaterRift); if (Skills.WitchDoctor.SpiritWalk.CanCast() && Settings.SpiritWalk.UseMode == UseTime.Default && TargetUtil.AnyMobsInRange(20f)) { return(SpiritWalk()); } // Spam hex for the 50% damage reduction var closeUnit = HostileMonsters.FirstOrDefault(u => u.Distance < 40f); if (!Player.HasBuff(SNOPower.Witchdoctor_Hex) && Skills.WitchDoctor.Hex.CanCast() && closeUnit != null) { return(Hex(TargetUtil.GetBestClusterPoint(15f, 20f))); } var targetsWithoutLocust = clusterUnits.Where(u => !u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm)).OrderBy(u => u.Distance); var isAnyTargetWithLocust = clusterUnits.Any(u => u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) && u.Distance < 45f); var percentTargetsWithHaunt = TargetUtil.DebuffedPercent(SNOPower.Witchdoctor_Haunt, 8f); var percentTargetsWithLocust = TargetUtil.DebuffedPercent(SNOPower.Witchdoctor_Locust_Swarm, 12f); var isEliteWithoutHaunt = clusterUnits.Any(u => u.IsElite && !u.HasDebuff(SNOPower.Witchdoctor_Haunt) && u.Distance <= 20f); var isElitewithoutLocust = clusterUnits.Any(u => u.IsElite && !u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) && u.Distance <= 20f); var harvestBuffCooldown = Core.Cooldowns.GetBuffCooldown(SNOPower.Witchdoctor_SoulHarvest); var harvestPossibleStackGain = 10 - harvestStacks; var harvestUnitsInRange = allUnits.Count(u => u.Distance < 12f); var interruptForHarvest = Skills.WitchDoctor.SoulHarvest.CanCast() && harvestPossibleStackGain >= harvestUnitsInRange && harvestBuffCooldown?.Remaining.TotalMilliseconds < 500; var interruptForHaunt = percentTargetsWithHaunt < 0.2f || isEliteWithoutHaunt; var needToSwarmElite = isElitewithoutLocust && !((Legendary.VileHive.IsEquipped || Runes.WitchDoctor.Pestilence.IsActive) && isAnyTargetWithLocust); var interruptForLocust = (percentTargetsWithLocust < 0.1f || needToSwarmElite) && Player.PrimaryResource > 300 && Skills.WitchDoctor.LocustSwarm.CanCast(); var interruptForHex = Skills.WitchDoctor.Hex.CanCast(); var interruptForSpiritWalk = Skills.WitchDoctor.SpiritWalk.CanCast() && Settings.SpiritWalk.UseMode == UseTime.Default && shouldBecomeInvulnerable; // continue channelling firebats? if (Player.IsChannelling) { if (!interruptForHaunt && !interruptForLocust && !interruptForHarvest && !interruptForHex && !interruptForSpiritWalk) { return(new TrinityPower(SNOPower.Witchdoctor_Firebats, 30f, Player.Position, 75, 250)); } } // Emergency health situation if (Player.CurrentHealthPct < 0.35) { if (Skills.WitchDoctor.SpiritWalk.CanCast()) { return(SpiritWalk()); } if (TargetUtil.AnyMobsInRange(12f) && Skills.WitchDoctor.SoulHarvest.CanCast()) { return(SoulHarvest()); } if (!HasJeramsRevengeBuff && Skills.WitchDoctor.WallOfDeath.CanCast() && allUnits.Any()) { return(WallOfDeath(allUnits.FirstOrDefault())); } } // Soul harvest for the damage reduction of Okumbas Ornament if (Skills.WitchDoctor.SoulHarvest.CanCast() && (bestClusterUnit.Distance < 12f || harvestStacks < 4 && TargetUtil.AnyMobsInRange(10f)) && harvestStacks < 10) { if (harvestPossibleStackGain <= harvestUnitsInRange) { return(SoulHarvest()); } } // Locust if (Skills.WitchDoctor.LocustSwarm.CanCast() && Skills.WitchDoctor.LocustSwarm.TimeSinceUse > 1000 && targetsWithoutLocust.Any() && (!Runes.WitchDoctor.Pestilence.IsActive || !isAnyTargetWithLocust)) { if ((percentTargetsWithLocust < Settings.LocustPct || needToSwarmElite) && Player.PrimaryResource > 300 && targetsWithoutLocust.Any()) { return(new TrinityPower(SNOPower.Witchdoctor_Locust_Swarm, 10f, targetsWithoutLocust.First().AcdId, 0, 0)); } } if (ShouldBigBadVoodoo(out position)) { return(BigBadVoodoo(position)); } // Piranhas if (Skills.WitchDoctor.Piranhas.CanCast() && Player.PrimaryResource >= 250 && (TargetUtil.ClusterExists(15f, 40f) || TargetUtil.AnyElitesInRange(40f)) && Player.PrimaryResource >= 250) { return(Piranhas(TargetUtil.GetBestClusterUnit())); } // .80 of mobs give or take. Spelltracker check is to prevent repeat casts ont he same target before the projectile arrives. var targetsWithoutHaunt = clusterUnits.Where(u => !u.HasDebuff(SNOPower.Witchdoctor_Haunt) && !SpellTracker.IsUnitTracked(u, SNOPower.Witchdoctor_Haunt)).OrderBy(u => u.Distance); if ((percentTargetsWithHaunt < Settings.HauntPct || isEliteWithoutHaunt) && targetsWithoutHaunt.Any() && Player.PrimaryResource > 100) { var target = targetsWithoutHaunt.First(); return(Haunt(target)); } Vector3 bestBuffedPosition; TargetUtil.BestBuffPosition(16f, bestClusterUnit.Position, true, out bestBuffedPosition); var bestClusterUnitRadiusPosition = MathEx.GetPointAt(bestClusterUnit.Position, bestClusterUnit.CollisionRadius * 1.1f, bestClusterUnit.Rotation); var bestFirebatsPosition = bestBuffedPosition != Vector3.Zero ? bestBuffedPosition : bestClusterUnitRadiusPosition; var distance = bestFirebatsPosition.Distance2D(Player.Position); // Walk into cluster or buffed location. if (distance > 10f && distance < 35f && !PlayerMover.IsBlocked) { if (distance > 20f && Skills.WitchDoctor.SpiritWalk.CanCast()) { return(SpiritWalk()); } //Core.Logger.Warn($"Walking to cluster position. Dist: {bestFirebatsPosition.Distance(Player.Position)}"); return(new TrinityPower(SNOPower.Walk, 3f, bestFirebatsPosition, 0, 0)); } if (Skills.WitchDoctor.Firebats.CanCast()) { var closestUnit = allUnits.OrderBy(u => u.Distance).FirstOrDefault(); if (closestUnit != null) { return(Firebats(closestUnit)); } } } return(Walk(TargetUtil.GetLoiterPosition(CurrentTarget, 15f))); }
public TrinityPower GetOffensivePower() { Vector3 position; TrinityPower power; var allUnits = Core.Targets.ByType[TrinityObjectType.Unit].Where(u => u.IsUnit && u.RadiusDistance <= 50f).ToList(); var clusterUnits = (from u in allUnits where u.IsUnit && u.Weight > 0 && !u.IsPlayer orderby u.NearbyUnitsWithinDistance(15f) descending, u.Distance, u.HitPointsPct descending select u).ToList(); var bestClusterUnit = clusterUnits.FirstOrDefault(); //10 second 60% damage reduction should always be on to survive if (!HasJeramsRevengeBuff && Player.CurrentHealthPct > 0.4 && !Core.Avoidance.InCriticalAvoidance(Player.Position) && (ZetaDia.Me.IsInCombat || Player.CurrentHealthPct < 0.4) && bestClusterUnit != null && Skills.WitchDoctor.WallOfDeath.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Casting Wall of Death on {allUnits.FirstOrDefault()}"); return(WallOfDeath(allUnits.FirstOrDefault())); } if (bestClusterUnit != null) { if (Player.HasBuff(SNOPower.Witchdoctor_Hex)) { Core.Logger.Log(LogCategory.Routine, $"Casting Explode Chicken"); Vector3 explodePos = PlayerMover.IsBlocked ? Player.Position : bestClusterUnit.Position; return(ExplodeChicken(explodePos)); } if (!HasJeramsRevengeBuff && ZetaDia.Me.IsInCombat && Skills.WitchDoctor.WallOfDeath.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Casting Wall of Death on {allUnits.FirstOrDefault()}"); return(WallOfDeath(allUnits.FirstOrDefault())); } if (!Player.HasBuff(SNOPower.Witchdoctor_Hex) && Skills.WitchDoctor.Hex.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Casting Hex"); return(Hex(CurrentTarget.Position)); } var targetsWithoutLocust = clusterUnits.Where(u => !u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm)).OrderBy(u => u.Distance); var isAnyTargetWithLocust = clusterUnits.Any(u => u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) && u.Distance < 45f); var percentTargetsWithHaunt = TargetUtil.DebuffedPercent(SNOPower.Witchdoctor_Haunt, 8f); var percentTargetsWithLocust = TargetUtil.DebuffedPercent(SNOPower.Witchdoctor_Locust_Swarm, 12f); var isEliteWithoutHaunt = clusterUnits.Any(u => u.IsElite && !u.HasDebuff(SNOPower.Witchdoctor_Haunt)); var isElitewithoutLocust = clusterUnits.Any(u => u.IsElite && !u.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm)); var harvestStacks = Skills.WitchDoctor.SoulHarvest.BuffStacks; var harvestBuffCooldown = Core.Cooldowns.GetBuffCooldown(SNOPower.Witchdoctor_SoulHarvest); var harvestPossibleStackGain = 10 - harvestStacks; var harvestUnitsInRange = allUnits.Count(u => u.Distance < 12f); var interruptForHarvest = Skills.WitchDoctor.SoulHarvest.CanCast() && harvestPossibleStackGain >= harvestUnitsInRange && harvestBuffCooldown?.Remaining.TotalMilliseconds < 500; var interruptForHaunt = percentTargetsWithHaunt < 0.2f || isEliteWithoutHaunt; var needToSwarmElite = isElitewithoutLocust && !((Legendary.VileHive.IsEquipped || Runes.WitchDoctor.Pestilence.IsActive) && isAnyTargetWithLocust); var interruptForLocust = (percentTargetsWithLocust < 0.1f || needToSwarmElite) && Player.PrimaryResource > 300 && Skills.WitchDoctor.LocustSwarm.CanCast(); // continue channelling firebats? if (Player.IsChannelling) { if (!interruptForHaunt && !interruptForLocust && !interruptForHarvest) { Core.Logger.Log(LogCategory.Routine, "Continuation of Firebats."); return(new TrinityPower(SNOPower.Witchdoctor_Firebats, 30f, Player.Position, 75, 250)); } if (interruptForHaunt) { Core.Logger.Log(LogCategory.Routine, "Interrupted Firebats to haunt"); } if (interruptForLocust) { Core.Logger.Log(LogCategory.Routine, "Interrupted Firebats to locust"); } if (interruptForHarvest) { Core.Logger.Log(LogCategory.Routine, "Interrupted Firebats to harvest"); } } // Emergency health situation if (Player.CurrentHealthPct < 0.35) { if (Skills.WitchDoctor.SpiritWalk.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Defensive Spirit Walking"); return(SpiritWalk()); } if (TargetUtil.AnyMobsInRange(12f) && Skills.WitchDoctor.SoulHarvest.CanCast()) { Core.Logger.Log(LogCategory.Routine, "Emergency Harvest"); return(SoulHarvest()); } if (!HasJeramsRevengeBuff && Skills.WitchDoctor.WallOfDeath.CanCast() && allUnits.Any()) { Core.Logger.Log(LogCategory.Routine, $"Casting Defensive WallOfDeath on {allUnits.FirstOrDefault()}"); return(WallOfDeath(allUnits.FirstOrDefault())); } } // Locust if (Skills.WitchDoctor.LocustSwarm.CanCast() && Skills.WitchDoctor.LocustSwarm.TimeSinceUse > 1000 && targetsWithoutLocust.Any() && (!Runes.WitchDoctor.Pestilence.IsActive || !isAnyTargetWithLocust)) { if ((percentTargetsWithLocust < Settings.LocustPct || needToSwarmElite) && Player.PrimaryResource > 300 && targetsWithoutLocust.Any()) { Core.Logger.Log(LogCategory.Routine, "Locust"); return(new TrinityPower(SNOPower.Witchdoctor_Locust_Swarm, 10f, targetsWithoutLocust.First().Position, 0, 0)); } } // Soul harvest for the damage reduction of Okumbas Ornament if (Skills.WitchDoctor.SoulHarvest.CanCast() && (bestClusterUnit.Distance < 12f || harvestStacks < 4 && TargetUtil.AnyMobsInRange(10f)) && harvestStacks < 10) { Core.Logger.Log(LogCategory.Routine, $"Harvest State: StackGainPossible={harvestPossibleStackGain} Units={harvestUnitsInRange} BuffRemainingSecs:{harvestBuffCooldown?.Remaining.TotalSeconds:N2}"); if (harvestPossibleStackGain <= harvestUnitsInRange) { Core.Logger.Log(LogCategory.Routine, $"Soul Harvest."); return(SoulHarvest()); } } if (ShouldBigBadVoodoo(out position)) { return(BigBadVoodoo(position)); } // Piranhas if (Skills.WitchDoctor.Piranhas.CanCast() && Player.PrimaryResource >= 250 && (TargetUtil.ClusterExists(15f, 40f) || TargetUtil.AnyElitesInRange(40f)) && Player.PrimaryResource >= 250) { return(Piranhas(TargetUtil.GetBestClusterUnit())); } // .80 of mobs give or take. Spelltracker check is to prevent repeat casts ont he same target before the projectile arrives. var targetsWithoutHaunt = clusterUnits.Where(u => !u.HasDebuff(SNOPower.Witchdoctor_Haunt) && !SpellTracker.IsUnitTracked(u, SNOPower.Witchdoctor_Haunt)).OrderBy(u => u.Distance); if ((percentTargetsWithHaunt < Settings.HauntPct || isEliteWithoutHaunt) && targetsWithoutHaunt.Any() && Player.PrimaryResource > 100) { var target = targetsWithoutHaunt.First(); Core.Logger.Log(LogCategory.Routine, $"Haunt on {target}"); return(Haunt(target)); } Vector3 bestBuffedPosition; TargetUtil.BestBuffPosition(16f, bestClusterUnit.Position, true, out bestBuffedPosition); var bestClusterUnitRadiusPosition = MathEx.GetPointAt(bestClusterUnit.Position, bestClusterUnit.CollisionRadius * 1.1f, bestClusterUnit.Rotation); var bestFirebatsPosition = bestBuffedPosition != Vector3.Zero ? bestBuffedPosition : bestClusterUnitRadiusPosition; var distance = bestFirebatsPosition.Distance(Player.Position); // Walk into cluster or buffed location. if (distance > 10f && !PlayerMover.IsBlocked) { if (distance > 20f && Skills.WitchDoctor.SpiritWalk.CanCast()) { Core.Logger.Log(LogCategory.Routine, $"Spirit Walking"); return(SpiritWalk()); } Core.Logger.Warn($"Walking to cluster position. Dist: {bestFirebatsPosition.Distance(Player.Position)}"); return(new TrinityPower(SNOPower.Walk, 3f, bestFirebatsPosition, 0, 0)); } if (Skills.WitchDoctor.Firebats.CanCast()) { var closestUnit = allUnits.OrderBy(u => u.Distance).FirstOrDefault(); if (closestUnit != null) { Core.Logger.Log(LogCategory.Routine, $"Casting Firebats"); return(Firebats(closestUnit)); } } } //if (IsChannellingFirebats && Player.CurrentHealthPct > 0.5f && TargetUtil.AnyMobsInRange(FireBatsRange)) // return Firebats(); //if (TrySpecialPower(out power)) // return power; //if (TrySecondaryPower(out power)) // return power; //if (TryPrimaryPower(out power)) // return power; return(Walk(TargetUtil.GetLoiterPosition(CurrentTarget, 15f))); }