Пример #1
0
        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));
        }
Пример #2
0
        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);
        }
Пример #3
0
        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);
        }
Пример #4
0
        /// <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);
        }
Пример #5
0
        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);
        }
Пример #7
0
        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);
        }
Пример #8
0
        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);
        }
Пример #9
0
        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);
        }
Пример #10
0
        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);
        }
Пример #11
0
        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));
        }
Пример #12
0
        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)));
        }