Beispiel #1
0
        protected virtual bool ShouldExplodingPalm(out TrinityActor target)
        {
            target = null;

            if (!Skills.Monk.ExplodingPalm.CanCast())
            {
                return(false);
            }

            if (Player.PrimaryResource < PrimaryEnergyReserve)
            {
                return(false);
            }

            var isBigCluster         = TargetUtil.ClusterExists(WaveOfLightRange, 3);
            var isEliteInRange       = TargetUtil.AnyElitesInRange(WaveOfLightRange);
            var isFarTooMuchResource = Player.PrimaryResourcePct > 0.8f;

            if (isBigCluster || isEliteInRange || isFarTooMuchResource)
            {
                target = TargetUtil.BestExplodingPalmTarget(MeleeAttackRange);
                return(target != null);
            }

            return(false);
        }
Beispiel #2
0
        /// <summary>
        /// Gets the best (non-movement related) avoidance power
        /// </summary>
        /// <returns></returns>
        private static TrinityPower GetCombatAvoidancePower()
        {
            // Defensive Teleport: SafePassage
            if (CanCast(SNOPower.Wizard_Teleport, CanCastFlags.NoTimer) && Runes.Wizard.SafePassage.IsActive && Player.CurrentHealthPct <= 0.50)
            {
                var target = NavHelper.FindSafeZone(false, 1, CurrentTarget.Position, true);
                return(new TrinityPower(SNOPower.Wizard_Teleport, 65f, target));
            }

            // Diamond Skin: Tank mode
            if (CanCast(SNOPower.Wizard_DiamondSkin) && LastPowerUsed != SNOPower.Wizard_DiamondSkin && !GetHasBuff(SNOPower.Wizard_DiamondSkin) &&
                (TargetUtil.AnyElitesInRange(25, 1) || TargetUtil.AnyMobsInRange(25, 1) || Player.CurrentHealthPct <= 0.90 || Player.IsIncapacitated || Player.IsRooted || CurrentTarget.RadiusDistance <= 40f))
            {
                return(new TrinityPower(SNOPower.Wizard_DiamondSkin));
            }

            // Explosive Blast
            if (CanCast(SNOPower.Wizard_ExplosiveBlast, CanCastFlags.NoTimer) && !Player.IsIncapacitated && !Player.IsInTown)
            {
                return(new TrinityPower(SNOPower.Wizard_ExplosiveBlast, 10f));
            }

            // Frost Nova
            if (CanCast(SNOPower.Wizard_FrostNova) && !Player.IsIncapacitated &&
                ((Runes.Wizard.DeepFreeze.IsActive && TargetUtil.AnyMobsInRange(25, 5)) || TargetUtil.AnyMobsInRange(25, 1) || Player.CurrentHealthPct <= 0.7) &&
                CurrentTarget.RadiusDistance <= 25f)
            {
                return(new TrinityPower(SNOPower.Wizard_FrostNova, 20f));
            }


            return(null);
        }
Beispiel #3
0
        protected override bool ShouldWaveOfLight(out TrinityActor target)
        {
            target = null;

            if (!Skills.Monk.WaveOfLight.CanCast())
            {
                return(false);
            }

            if (Player.PrimaryResource < PrimaryEnergyReserve)
            {
                return(false);
            }

            var isBigCluster         = TargetUtil.ClusterExists(Settings.WoLRange, Settings.ClusterSize);
            var isEliteInRange       = TargetUtil.AnyElitesInRange(Settings.WoLEliteRange);
            var isFarTooMuchResource = Player.PrimaryResourcePct > 0.8f;

            if (isBigCluster || isEliteInRange || isFarTooMuchResource)
            {
                target = TargetUtil.GetBestClusterUnit();
                return(target != null);
            }

            if (IsBlocked || IsStuck)
            {
                target = TargetUtil.GetClosestUnit(Settings.WoLRange) ?? CurrentTarget;
            }
            else
            {
                target = TargetUtil.GetBestClusterUnit(Settings.WoLRange) ?? CurrentTarget;
            }
            return(true);
        }
Beispiel #4
0
        protected virtual bool ShouldInnerSanctuary()
        {
            if (!Skills.Monk.InnerSanctuary.CanCast())
            {
                return(false);
            }

            if (HasInstantCooldowns && !Skills.Monk.InnerSanctuary.IsLastUsed)
            {
                return(true);
            }

            if (Core.Buffs.HasBuff(SNOPower.X1_Monk_InnerSanctuary))
            {
                return(false);
            }

            if (Player.CurrentHealthPct > 0.9 && !TargetUtil.AnyElitesInRange(10f))
            {
                return(false);
            }

            if (TargetUtil.BestAoeUnit(25, Player.IsInParty).Distance > 20)
            {
                return(false);
            }

            return(true);
        }
Beispiel #5
0
        protected virtual bool ShouldShadowPower()
        {
            if (!Skills.DemonHunter.ShadowPower.CanCast())
            {
                return(false);
            }

            if (Player.HasBuff(SNOPower.DemonHunter_ShadowPower))
            {
                return(false);
            }

            if (Skills.DemonHunter.ShadowPower.TimeSinceUse < 4500)
            {
                return(false);
            }

            if (TargetUtil.AnyElitesInRange(40f))
            {
                return(true);
            }

            if (Player.CurrentHealthPct < 0.7f)
            {
                return(true);
            }

            if (TargetUtil.NumMobsInRange() > 5)
            {
                return(true);
            }

            return(false);
        }
Beispiel #6
0
        protected virtual bool ShouldLeap(out Vector3 position)
        {
            position = Vector3.Zero;

            if (!Skills.Barbarian.Leap.CanCast())
            {
                return(false);
            }

            position = TargetUtil.GetBestClusterPoint();

            if (Legendary.LutSocks.IsEquipped && Skills.Barbarian.Leap.TimeSinceUse < 2000)
            {
                return(true);
            }

            if (Sets.MightOfTheEarth.IsFullyEquipped)
            {
                return(true);
            }

            if (!TargetUtil.ClusterExists(15f, 50f, 5) && !TargetUtil.AnyElitesInRange(50f))
            {
                return(false);
            }

            return(position != Vector3.Zero);
        }
        protected override bool ShouldTeleport(out Vector3 position)
        {
            position = Vector3.Zero;
            var skill             = Skills.Wizard.Teleport;
            var affixOnPlayer     = Core.Avoidance.InAvoidance(ZetaDia.Me.Position);
            var healthIsLow       = Player.CurrentHealthPct < Settings.TeleportHealthEmergency;
            var archonHealthIsLow = Player.CurrentHealthPct < Settings.ArchonTeleportHealthEmergency;
            var anyElitesinRange  = TargetUtil.AnyElitesInRange(Settings.TeleportEliteKiteRange);
            var anyMobsInRange    = TargetUtil.AnyMobsInRangeOfPosition(Player.Position, Settings.TeleportTrashKiteRange, Settings.TeleportTrashInRangeCount);

            if (!skill.CanCast())
            {
                return(false);
            }

            if (Player.IsChannelling || !Player.IsChannelling)
            {
                if (anyElitesinRange || anyMobsInRange || healthIsLow || affixOnPlayer)
                {
                    Core.Logger.Log(LogCategory.Routine, $"Close Elites: {anyElitesinRange}, Mobs: {anyMobsInRange}, Health: {healthIsLow}, Affix: {affixOnPlayer}");
                }
                return(true);
            }

            Avoider.TryGetSafeSpot(out position, Settings.TeleportKiteMinDistance, Settings.TeleportKiteMaxDistance, ZetaDia.Me.Position, node => !HostileMonsters.Any(m => m.Position.Distance(node.NavigableCenter) < 15f));

            return(position != Vector3.Zero);
        }
Beispiel #8
0
        protected bool IsReasonToUse(SkillSettings settings, Skill skill)
        {
            var routine = Core.Routines.CurrentRoutine;

            if (settings.Reasons.HasFlag(UseReasons.Elites) && TargetUtil.AnyElitesInRange(40f))
            {
                return(true);
            }

            if (settings.Reasons.HasFlag(UseReasons.Trash) && TargetUtil.ClusterExists(routine.TrashRange, routine.TrashRange, routine.ClusterSize))
            {
                return(true);
            }

            if (settings.Reasons.HasFlag(UseReasons.Surrounded) && TargetUtil.NumMobsInRange(25f) >= Math.Max(ClusterSize, 5))
            {
                return(true);
            }

            if (settings.Reasons.HasFlag(UseReasons.Avoiding) && IsCurrentlyAvoiding)
            {
                return(true);
            }

            if (settings.Reasons.HasFlag(UseReasons.Blocked) && PlayerMover.IsBlocked)
            {
                return(true);
            }

            if (settings.Reasons.HasFlag(UseReasons.DumpResource) && Player.PrimaryResourcePct < 0.8f)
            {
                return(true);
            }

            if (settings.Reasons.HasFlag(UseReasons.Goblins) && WeightedUnits.Any(u => u.IsTreasureGoblin))
            {
                return(true);
            }

            if (settings.Reasons.HasFlag(UseReasons.HealthEmergency) && Player.CurrentHealthPct < TrinityCombat.Routines.Current.EmergencyHealthPct)
            {
                return(true);
            }

            if (settings.Reasons.HasFlag(UseReasons.Buff) && settings.BuffCondition != null && settings.BuffCondition())
            {
                return(true);
            }

            return(false);
        }
        protected override bool ShouldLandOfTheDead(out TrinityActor target)
        {
            target = null;
            if (!Skills.Necromancer.LandOfTheDead.CanCast())
            {
                return(false);
            }

            if (!TargetUtil.AnyElitesInRange(LotDRange) && !HasInstantCooldowns)
            {
                return(false);
            }

            target = FindBestTarget();
            return(IsValidTarget(target));
        }
Beispiel #10
0
        protected virtual bool ShouldAvalanche(out Vector3 position)
        {
            position = Vector3.Zero;

            if (!Skills.Barbarian.Avalanche.CanCast())
            {
                return(false);
            }

            if (!TargetUtil.ClusterExists(15f, 50f, 5) && !TargetUtil.AnyElitesInRange(50f))
            {
                return(false);
            }

            position = TargetUtil.GetBestClusterPoint();
            return(position != Vector3.Zero);
        }
Beispiel #11
0
        protected override bool ShouldBlindingFlash()
        {
            if (!Skills.Monk.BlindingFlash.CanCast())
            {
                return(false);
            }

            if (HasInstantCooldowns && Skills.Monk.BlindingFlash.TimeSinceUse > 2500)   //Overlap them by half a second
            {
                return(true);
            }

            var lowHealth          = Player.CurrentHealthPct <= Settings.EmergencyHealthPct;
            var enoughStuffToBlind = TargetUtil.AnyElitesInRange(20, 1) || TargetUtil.AnyMobsInRange(20, 3);
            var blindCurrentTarget = CurrentTarget != null && CurrentTarget.IsElite && CurrentTarget.RadiusDistance <= 15f;

            return(lowHealth || enoughStuffToBlind || blindCurrentTarget);
        }
Beispiel #12
0
        protected virtual bool ShouldBlindingFlash()
        {
            if (!Skills.Monk.BlindingFlash.CanCast())
            {
                return(false);
            }

            if (HasInstantCooldowns && !Skills.Monk.Epiphany.IsLastUsed)
            {
                return(true);
            }

            var lowHealth          = Player.CurrentHealthPct <= 0.4;
            var enoughStuffToBlind = TargetUtil.AnyElitesInRange(15, 1) || TargetUtil.AnyMobsInRange(15, 3);
            var blindCurrentTarget = CurrentTarget != null && CurrentTarget.IsElite && CurrentTarget.RadiusDistance <= 15f;

            return(lowHealth || enoughStuffToBlind || blindCurrentTarget);
        }
Beispiel #13
0
        protected virtual bool ShouldFanOfKnives()
        {
            if (!Skills.DemonHunter.FanOfKnives.CanCast())
            {
                return(false);
            }

            if (Runes.DemonHunter.FanOfDaggers.IsActive && Player.CurrentHealthPct < 0.65 && TargetUtil.AnyMobsInRange(16f))
            {
                return(true);
            }

            if (TargetUtil.NumMobsInRange(20f) >= 4 || TargetUtil.AnyElitesInRange(15f))
            {
                return(true);
            }

            return(false);
        }
        /// <summary>
        /// When Companion should be cast
        /// </summary>
        private static bool CompanionCondition(SkillMeta meta)
        {
            meta.CastFlags = CanCastFlags.NoTimer;

            // Use Spider Slow on 4 or more trash mobs in an area or on Unique/Elite/Champion
            if (Runes.DemonHunter.SpiderCompanion.IsActive && TargetUtil.ClusterExists(25f, 4) && TargetUtil.EliteOrTrashInRange(25f))
            {
                return(true);
            }

            //Use Bat when Hatred is Needed
            if (Runes.DemonHunter.BatCompanion.IsActive && Player.PrimaryResourceMissing >= 60)
            {
                return(true);
            }

            // Use Boar Taunt on 3 or more trash mobs in an area or on Unique/Elite/Champion
            if (Runes.DemonHunter.BoarCompanion.IsActive && ((TargetUtil.ClusterExists(20f, 4) && TargetUtil.EliteOrTrashInRange(20f)) || (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.Distance <= 20f)))
            {
                return(true);
            }

            // Ferrets used for picking up Health Globes when low on Health
            if (Runes.DemonHunter.FerretCompanion.IsActive && Trinity.ObjectCache.Any(o => o.Type == TrinityObjectType.HealthGlobe && o.Distance < 60f) && Player.CurrentHealthPct < EmergencyHealthPotionLimit)
            {
                return(true);
            }

            // Use Wolf Howl on Unique/Elite/Champion - Would help for farming trash, but trash farming should not need this - Used on Elites to reduce Deaths per hour
            if (Runes.DemonHunter.WolfCompanion.IsActive && (TargetUtil.AnyElitesInRange(100f) || TargetUtil.AnyMobsInRange(40, 8)))
            {
                return(true);
            }

            // Companion off CD
            if (Settings.Combat.DemonHunter.CompanionOffCooldown && TargetUtil.AnyMobsInRange(60))
            {
                return(true);
            }

            return(false);
        }
Beispiel #15
0
        protected override bool ShouldBoneSpear(out TrinityActor target)
        {
            target = null;

            if (!Skills.Necromancer.BoneSpear.CanCast())
            {
                return(false);
            }


            if (Skills.Necromancer.LandOfTheDead.IsBuffActive)
            {
                return(false);
            }

            var boneSpearTarget = TargetUtil.AnyElitesInRange(60f) ?
                                  TargetUtil.BestEliteInRange(60f, true) : TargetUtil.LowestHealthTarget(60f);

            target = boneSpearTarget ?? CurrentTarget;
            return(target != null);
        }
Beispiel #16
0
        /// <summary>
        /// Checks for all necessary buffs and combat conditions for starting Archon
        /// </summary>
        /// <returns></returns>
        private static bool ShouldStartArchon()
        {
            bool canCastArchon = (
                CheckAbilityAndBuff(SNOPower.Wizard_MagicWeapon) &&
                (!Hotbar.Contains(SNOPower.Wizard_Familiar) || IsFamiliarActive) &&
                CheckAbilityAndBuff(SNOPower.Wizard_EnergyArmor) &&
                CheckAbilityAndBuff(SNOPower.Wizard_IceArmor) &&
                CheckAbilityAndBuff(SNOPower.Wizard_StormArmor)
                );

            var elitesOnly   = Settings.Combat.Wizard.ArchonElitesOnly && TargetUtil.AnyElitesInRange(Settings.Combat.Wizard.ArchonEliteDistance);
            var trashInRange = !Settings.Combat.Wizard.ArchonElitesOnly && TargetUtil.AnyMobsInRange(Settings.Combat.Wizard.ArchonMobDistance, Settings.Combat.Wizard.ArchonMobCount);

            // With Chantodos set wait until max stacks before using archon
            if (Sets.ChantodosResolve.IsFullyEquipped && CacheData.Buffs.HasBuff(SNOPower.P3_ItemPassive_Unique_Ring_021) && CacheData.Buffs.GetBuff(SNOPower.P3_ItemPassive_Unique_Ring_021).StackCount < 20)
            {
                return(false);
            }

            return(canCastArchon && (elitesOnly || trashInRange || CurrentTarget.IsBoss));
        }
Beispiel #17
0
        protected virtual bool ShouldCompanion()
        {
            if (!Skills.DemonHunter.Companion.CanCast())
            {
                return(false);
            }

            // Use Spider Slow on 4 or more trash mobs in an area or on Unique/Elite/Champion
            if (Runes.DemonHunter.SpiderCompanion.IsActive && TargetUtil.ClusterExists(25f, 4) && TargetUtil.EliteOrTrashInRange(25f))
            {
                return(true);
            }

            // Use Bat when Hatred is Needed
            if (Runes.DemonHunter.BatCompanion.IsActive && Player.PrimaryResourceMissing >= 60)
            {
                return(true);
            }

            // Use Boar Taunt on 3 or more trash mobs in an area or on Unique/Elite/Champion
            if (Runes.DemonHunter.BoarCompanion.IsActive && (TargetUtil.ClusterExists(20f, 4) && TargetUtil.EliteOrTrashInRange(20f) || CurrentTarget != null && CurrentTarget.IsElite && CurrentTarget.Distance <= 20f))
            {
                return(true);
            }

            // Ferrets used for picking up Health Globes when low on Health
            if (Runes.DemonHunter.FerretCompanion.IsActive && Core.Targets.Entries.Any(o => o.Type == TrinityObjectType.HealthGlobe && o.Distance < 60f) && Player.CurrentHealthPct < EmergencyHealthPct)
            {
                return(true);
            }

            // Use Wolf Howl on Unique/Elite/Champion
            if (Runes.DemonHunter.WolfCompanion.IsActive && (TargetUtil.AnyElitesInRange(80f) || TargetUtil.AnyMobsInRange(40, 5)))
            {
                return(true);
            }

            return(false);
        }
        /// <summary>
        /// When Rain of Vengeance should be cast
        /// </summary>
        private static bool RainOfVengeanceCondition(SkillMeta meta)
        {
            meta.CastRange = 90f;
            meta.CastFlags = CanCastFlags.NoTimer;

            if (Legendary.CrashingRain.IsEquipped)
            {
                meta.TargetPositionSelector = skillMeta => TargetUtil.GetBestClusterPoint(30f, 80f);
            }

            if (Settings.Combat.DemonHunter.RainOfVengeanceOffCD || Sets.NatalyasVengeance.IsEquipped)
            {
                return(true);
            }

            if (TargetUtil.ClusterExists(45f, 4) || TargetUtil.AnyElitesInRange(90f))
            {
                return(true);
            }

            return(false);
        }
Beispiel #19
0
        protected virtual bool ShouldSevenSidedStrike(out TrinityActor target)
        {
            target = null;

            if (!Skills.Monk.SevenSidedStrike.CanCast())
            {
                return(false);
            }

            if (!TargetUtil.AnyMobsInRange(45f))
            {
                return(false);
            }

            if (Player.PrimaryResource < PrimaryEnergyReserve)
            {
                return(false);
            }

            if (Skills.Monk.ExplodingPalm.IsActive && !WeightedUnits.Any(u => u.IsUnit && u.Distance < 35f && u.HasDebuff(SNOPower.Monk_ExplodingPalm)))
            {
                return(false);
            }

            var isElitesInRange    = TargetUtil.AnyElitesInRange(15, 1);
            var isSpamRelatedItems = Legendary.Madstone.IsEquipped || Sets.UlianasStratagem.IsMaxBonusActive;
            var isLowHealth        = Player.CurrentHealthPct < 0.55f;
            var isLargerCluster    = TargetUtil.ClusterExists(20f, 20f, LargeClusterSize);

            if (isElitesInRange || isSpamRelatedItems || isLowHealth || isLargerCluster)
            {
                target = TargetUtil.GetBestClusterUnit();
                return(target != null);
            }

            return(false);
        }
Beispiel #20
0
        protected virtual bool ShouldEpiphany()
        {
            if (!Skills.Monk.Epiphany.CanCast())
            {
                return(false);
            }

            if (HasInstantCooldowns && !Skills.Monk.Epiphany.IsLastUsed)
            {
                return(true);
            }

            if (Player.CurrentHealthPct > 0.9 && !TargetUtil.AnyElitesInRange(30f))
            {
                return(false);
            }

            if (TargetUtil.BestAoeUnit(25, Player.IsInParty)?.Distance > 30 && !TargetUtil.AnyElitesInRange(20))
            {
                return(false);
            }

            return(true);
        }
        protected override bool ShouldSimulacrum(out Vector3 position)
        {
            position = Vector3.Zero;
            TrinityActor target;

            if (!Skills.Necromancer.Simulacrum.CanCast())
            {
                return(false);
            }

            if (!TargetUtil.AnyElitesInRange(LotDRange) && !AlwaysSimulacrum && !HasInstantCooldowns)
            {
                return(false);
            }

            target = FindBestTarget();

            if (!IsValidTarget(target))
            {
                return(false);
            }
            position = target.Position;
            return(true);
        }
        public TrinityPower GetBuffPower()
        {
            // ty RobbenHoschi

            if (Skills.Wizard.ExplosiveBlast.CanCast())
            {
                return(ExplosiveBlast());
            }
            if (Skills.Wizard.DiamondSkin.CanCast())
            {
                return(DiamondSkin());
            }
            if (Skills.Wizard.FrostNova.CanCast() && (TargetUtil.ClusterExists(25f, ClusterSize) || TargetUtil.AnyElitesInRange(40f)))
            {
                return(FrostNova());
            }
            return(DefaultBuffPower());
        }
Beispiel #23
0
        public TrinityPower GetOffensivePower()
        {
            var          position = Vector3.Zero;
            TrinityActor target;

            if (IsArchonActive)
            {
                target = TargetUtil.SafeList(true)
                         .FirstOrDefault(a => a.IsBoss && !a.IsShadowClone && a.Distance < 125f) ??
                         TargetUtil.SafeList(true)
                         .Where(a => a.IsElite &&
                                a.EliteType != EliteTypes.Minion &&
                                !a.IsIllusion &&
                                a.Distance < 125f)
                         .OrderBy(a => a.NearbyUnitsWithinDistance(6f))
                         .FirstOrDefault() ??
                         TargetUtil.SafeList(true)
                         .Where(a => a.IsTrashMob &&
                                a.IsInLineOfSight &&
                                !a.IsSummoner &&
                                !a.IsSummoned &&
                                a.Distance < 50f)
                         .OrderByDescending(a => a.NearbyUnitsWithinDistance(6f))
                         .FirstOrDefault() ??
                         CurrentTarget;
            }
            else
            {
                target = TargetUtil.SafeList(true)
                         .FirstOrDefault(a => a.IsBoss &&
                                         !a.IsShadowClone &&
                                         a.Distance < 125f) ??
                         TargetUtil.SafeList(true)
                         .Where(a => a.IsMonster &&
                                a.Distance < 50f &&
                                a.IsInLineOfSight)
                         .OrderBy(a => a.Distance)
                         .FirstOrDefault(a => TrinityGrid.Instance.CanRayWalk(Player.Position, a.Position)) ??
                         CurrentTarget;
            }

            if (target == null)
            {
                return(null);
            }

            if (position != Vector3.Zero)
            {
                return(Teleport(position));
            }

            Skill teleport = Skills.Wizard.Teleport;

            if (Skills.Wizard.ArchonTeleport.CanCast() ||
                Skills.Wizard.Teleport.CanCast())
            {
                teleport = IsArchonActive ? Skills.Wizard.ArchonTeleport : Skills.Wizard.Teleport;
                var needTeleport = !Core.Buffs.HasInvulnerableShrine &&
                                   Core.Avoidance.InAvoidance(Player.Position) ||
                                   !Core.Buffs.HasInvulnerableShrine &&
                                   Player.CurrentHealthPct < 1 &&
                                   teleport.TimeSinceUse > 1500 ||
                                   TargetUtil.AnyElitesInRange(15) &&
                                   teleport.TimeSinceUse > 2000 ||
                                   TargetUtil.AnyBossesInRange(30) ||
                                   TargetUtil.NumMobsInRange() > 3 ||
                                   teleport.TimeSinceUse > 3000;

                if (needTeleport)
                {
                    if (position == Vector3.Zero)
                    {
                        Core.Avoidance.Avoider.TryGetSafeSpot(out position, 30, 45);
                    }

                    return(Teleport(position));
                }
            }

            if (IsArchonActive)
            {
                return(ArchonDisintegrationWave(target));
            }

            if (Skills.Wizard.Archon.CanCast() &&
                TalRashaStacks >= 3 &&
                Player.Summons.HydraCount >= 2)
            {
                return(Archon());
            }

            var hydra      = Skills.Wizard.Hydra;
            var rayOfFrost = Skills.Wizard.RayOfFrost;

            var elite = TargetUtil.SafeList(true)
                        .Where(a => a.IsElite &&
                               a.IsInLineOfSight &&
                               a.EliteType != EliteTypes.Minion &&
                               !a.IsIllusion &&
                               a.Distance < 50f)
                        .OrderByDescending(a => a.Distance)
                        .FirstOrDefault();

            if (Skills.Wizard.Archon.CanCast() &&
                TalRashaStacks < 3)
            {
                if (Player.Summons.HydraCount < 2 ||
                    hydra.TimeSinceUse > 6000)
                {
                    return(Hydra(elite?.Position ?? target.Position));
                }

                if (rayOfFrost.TimeSinceUse > 6000)
                {
                    return(RayOfFrost(target));
                }

                if (teleport.TimeSinceUse > 6000 &&
                    Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50))
                {
                    return(Teleport(position));
                }
            }

            if (Skills.Wizard.Hydra.CanCast() &&
                Player.Summons.HydraCount < 2 &&
                IsInCombat)
            {
                return(new TrinityPower(Skills.Wizard.Hydra, 50, elite ?? target));
            }

            if (Skills.Wizard.RayOfFrost.CanCast() &&
                Player.PrimaryResource > 15)
            {
                return(RayOfFrost(target));
            }

            return(Walk(Player.Position));
        }
        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)));
        }
Beispiel #25
0
        public void MoveTowards(Vector3 vMoveToTarget)
        {
            if (!ZetaDia.IsInGame || !ZetaDia.Me.IsValid || ZetaDia.Me.IsDead || ZetaDia.IsLoadingWorld)
            {
                return;
            }

            if (UISafetyCheck())
            {
                return;
            }

            TimeLastUsedPlayerMover = DateTime.Now;
            vMyCurrentPosition      = GilesTrinity.PlayerStatus.CurrentPosition;
            LastMoveToTarget        = vMoveToTarget;

            // record speed once per second
            if (DateTime.Now.Subtract(lastRecordedPosition).TotalMilliseconds >= 1000)
            {
                // Record our current location and time
                if (!SpeedSensors.Any())
                {
                    SpeedSensors.Add(new SpeedSensor()
                    {
                        Location          = vMyCurrentPosition,
                        TimeSinceLastMove = new TimeSpan(0),
                        Distance          = 0f,
                        WorldID           = GilesTrinity.CurrentWorldDynamicId
                    });
                }
                else
                {
                    SpeedSensor lastSensor = SpeedSensors.OrderByDescending(s => s.Timestamp).FirstOrDefault();
                    SpeedSensors.Add(new SpeedSensor()
                    {
                        Location          = vMyCurrentPosition,
                        TimeSinceLastMove = new TimeSpan(DateTime.Now.Subtract(lastSensor.TimeSinceLastMove).Ticks),
                        Distance          = Vector3.Distance(vMyCurrentPosition, lastSensor.Location),
                        WorldID           = GilesTrinity.CurrentWorldDynamicId
                    });
                }

                lastRecordedPosition = DateTime.Now;
            }
            // Set the public variable
            MovementSpeed = GetMovementSpeed();

            vMoveToTarget = WarnAndLogLongPath(vMoveToTarget);

            // Make sure GilesTrinity doesn't want us to avoid routine-movement
            //if (GilesTrinity.bDontMoveMeIAmDoingShit)
            //    return;
            // Store player current position

            // Store distance to current moveto target
            float DestinationDistance;

            DestinationDistance = vMyCurrentPosition.Distance2D(vMoveToTarget);

            // Do unstuckery things
            if (GilesTrinity.Settings.Advanced.UnstuckerEnabled)
            {
                // See if we can reset the 10-limit unstuck counter, if >120 seconds since we last generated an unstuck location
                // this is used if we're NOT stuck...
                if (iTotalAntiStuckAttempts > 1 && DateTime.Now.Subtract(LastGeneratedStuckPosition).TotalSeconds >= 120)
                {
                    iTotalAntiStuckAttempts  = 1;
                    iTimesReachedStuckPoint  = 0;
                    vSafeMovementLocation    = Vector3.Zero;
                    NavHelper.UsedStuckSpots = new List <GridPoint>();
                    DbHelper.Log(TrinityLogLevel.Normal, LogCategory.Movement, "Resetting unstuck timers", true);
                }

                // See if we need to, and can, generate unstuck actions
                // check if we're stuck
                bool isStuck = UnstuckChecker(vMyCurrentPosition);
                if (DateTime.Now.Subtract(_lastCancelledUnstucker).TotalSeconds > iCancelUnstuckerForSeconds && isStuck)
                {
                    // Record the time we last apparently couldn't move for a brief period of time
                    _lastRecordedAnyStuck = DateTime.Now;
                    // See if there's any stuck position to try and navigate to generated by random mover
                    vSafeMovementLocation = UnstuckHandler(vMyCurrentPosition, LastMoveToTarget);
                    DbHelper.Log(TrinityLogLevel.Normal, LogCategory.Movement, "SafeMovement Location set to {0}", vSafeMovementLocation);
                    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(_lastRecordedAnyStuck).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;
                    DestinationDistance = vMyCurrentPosition.Distance2D(vMoveToTarget);
                }
                // Get distance to current destination
                // 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 && DestinationDistance <= 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.CurrentPosition = vMyCurrentPosition;
                        vSafeMovementLocation = NavHelper.FindSafeZone(true, iTotalAntiStuckAttempts, vMyCurrentPosition);
                        vMoveToTarget         = vSafeMovementLocation;
                    }
                    else
                    {
                        if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                        {
                            DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Clearing old route and trying new path find to: " + LastMoveToTarget.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;
                        }
                        _lastCancelledUnstucker = DateTime.Now;

                        Navigator.Clear();
                        Navigator.MoveTo(LastMoveToTarget, "original destination", false);

                        return;
                    }
                }
            }
            // Is the built-in unstucker enabled or not?
            // if (GilesTrinity.Settings.Advanced.DebugInStatusBar)
            // {
            //    Logging.WriteDiagnostic("[Trinity] Moving toward <{0:0},{1:0},{2:0}> distance: {3:0}", vMoveToTarget.X, vMoveToTarget.Y, vMoveToTarget.Z, fDistanceFromTarget);
            // }
            // See if there's an obstacle in our way, if so try to navigate around it
            Vector3 point = vMoveToTarget;

            foreach (GilesObstacle obstacle in GilesTrinity.hashNavigationObstacleCache.Where(o => vMoveToTarget.Distance2D(o.Location) <= o.Radius))
            {
                if (vShiftedPosition == Vector3.Zero)
                {
                    // Make sure we only shift max once every 6 seconds
                    if (DateTime.Now.Subtract(lastShiftedPosition).TotalMilliseconds >= 6000)
                    {
                        DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Shifting position for Navigation Obstacle {0} {1} at {2}", obstacle.ActorSNO, obstacle.Name, obstacle.Location);
                        GetShiftedPosition(ref vMoveToTarget, ref point, obstacle.Radius + 5f);
                    }
                }
                else
                {
                    if (DateTime.Now.Subtract(lastShiftedPosition).TotalMilliseconds <= iShiftPositionFor)
                    {
                        vMoveToTarget = vShiftedPosition;
                    }
                    else
                    {
                        vShiftedPosition = Vector3.Zero;
                    }
                }
            }



            // don't use special movement within 10 seconds of being stuck
            bool cancelSpecialMovementAfterStuck = DateTime.Now.Subtract(LastGeneratedStuckPosition).TotalMilliseconds > 10000;

            // See if we can use abilities like leap etc. for movement out of combat, but not in town
            if (GilesTrinity.Settings.Combat.Misc.AllowOOCMovement && !GilesTrinity.PlayerStatus.IsInTown && !GilesTrinity.bDontMoveMeIAmDoingShit && cancelSpecialMovementAfterStuck)
            {
                bool bTooMuchZChange = (Math.Abs(vMyCurrentPosition.Z - vMoveToTarget.Z) >= 4f);

                // Whirlwind for a barb, special context only
                if (GilesTrinity.Hotbar.Contains(SNOPower.Barbarian_Whirlwind) && GilesTrinity.GilesObjectCache.Count(u => u.Type == GObjectType.Unit && u.RadiusDistance <= 10f) >= 1 &&
                    GilesTrinity.PlayerStatus.PrimaryResource >= 10)
                {
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_Whirlwind, vMoveToTarget, GilesTrinity.CurrentWorldDynamicId, -1);
                    if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Whirlwind for OOC movement, distance={0}", DestinationDistance);
                    }
                    return;
                }

                // Leap movement for a barb
                if (GilesTrinity.Hotbar.Contains(SNOPower.Barbarian_Leap) &&
                    DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_Leap]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Barbarian_Leap] &&
                    DestinationDistance >= 20f &&
                    PowerManager.CanCast(SNOPower.Barbarian_Leap) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (DestinationDistance > 35f)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f);
                    }
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_Leap, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_Leap] = DateTime.Now;
                    if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Leap for OOC movement, distance={0}", DestinationDistance);
                    }
                    return;
                }
                // Furious Charge movement for a barb
                if (GilesTrinity.Hotbar.Contains(SNOPower.Barbarian_FuriousCharge) && !bTooMuchZChange &&
                    DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Barbarian_FuriousCharge] &&
                    DestinationDistance >= 20f &&
                    PowerManager.CanCast(SNOPower.Barbarian_FuriousCharge) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (DestinationDistance > 35f)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f);
                    }
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_FuriousCharge, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.Barbarian_FuriousCharge] = DateTime.Now;
                    if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Furious Charge for OOC movement, distance={0}", DestinationDistance);
                    }
                    return;
                }
                // Vault for a DH - maximum set by user-defined setting
                if (GilesTrinity.Hotbar.Contains(SNOPower.DemonHunter_Vault) && !bTooMuchZChange &&
                    DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.DemonHunter_Vault]).TotalMilliseconds >= GilesTrinity.Settings.Combat.DemonHunter.VaultMovementDelay &&
                    DestinationDistance >= 18f &&
                    PowerManager.CanCast(SNOPower.DemonHunter_Vault) && !ShrinesInArea(vMoveToTarget) &&
                    // Don't Vault into avoidance/monsters if we're kiting
                    (GilesTrinity.PlayerKiteDistance <= 0 || (GilesTrinity.PlayerKiteDistance > 0 &&
                                                              (!GilesTrinity.hashAvoidanceObstacleCache.Any(a => a.Location.Distance(vMoveToTarget) <= GilesTrinity.PlayerKiteDistance) ||
                                                               (!GilesTrinity.hashAvoidanceObstacleCache.Any(a => MathEx.IntersectsPath(a.Location, a.Radius, GilesTrinity.PlayerStatus.CurrentPosition, vMoveToTarget))) ||
                                                               !GilesTrinity.hashMonsterObstacleCache.Any(a => a.Location.Distance(vMoveToTarget) <= GilesTrinity.PlayerKiteDistance))))
                    )
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (DestinationDistance > 35f)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f);
                    }
                    ZetaDia.Me.UsePower(SNOPower.DemonHunter_Vault, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.DemonHunter_Vault] = DateTime.Now;
                    if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Vault for OOC movement, distance={0}", DestinationDistance);
                    }
                    return;
                }
                // Tempest rush for a monk
                if (GilesTrinity.Hotbar.Contains(SNOPower.Monk_TempestRush) &&
                    (GilesTrinity.Settings.Combat.Monk.TROption == TempestRushOption.MovementOnly || GilesTrinity.Settings.Combat.Monk.TROption == TempestRushOption.Always ||
                     (GilesTrinity.Settings.Combat.Monk.TROption == TempestRushOption.TrashOnly && !TargetUtil.AnyElitesInRange(40f))))
                {
                    Vector3 vTargetAimPoint = vMoveToTarget;
                    //vTargetAimPoint = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, aimPointDistance);
                    //vTargetAimPoint = MathEx.CalculatePointFrom(vMyCurrentPosition, vMoveToTarget, aimPointDistance);

                    //bool canRayCastTarget = GilesTrinity.NavHelper.CanRayCast(vMyCurrentPosition, vTargetAimPoint);
                    bool canRayCastTarget = true;

                    vTargetAimPoint = TargetUtil.FindTempestRushTarget();

                    if (!CanChannelTempestRush &&
                        ((GilesTrinity.PlayerStatus.PrimaryResource >= GilesTrinity.Settings.Combat.Monk.TR_MinSpirit &&
                          DestinationDistance >= GilesTrinity.Settings.Combat.Monk.TR_MinDist) ||
                         DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Monk_TempestRush]).TotalMilliseconds <= 150) &&
                        canRayCastTarget && PowerManager.CanCast(SNOPower.Monk_TempestRush))
                    {
                        CanChannelTempestRush = true;
                    }
                    else if ((CanChannelTempestRush && (GilesTrinity.PlayerStatus.PrimaryResource < 10f)) || !canRayCastTarget)
                    {
                        CanChannelTempestRush = false;
                    }

                    double lastUse = DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Monk_TempestRush]).TotalMilliseconds;

                    if (CanChannelTempestRush)
                    {
                        if (GilesTrinity.GilesUseTimer(SNOPower.Monk_TempestRush))
                        {
                            LastTempestRushPosition = vTargetAimPoint;

                            ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, vTargetAimPoint, GilesTrinity.CurrentWorldDynamicId, -1);
                            GilesTrinity.dictAbilityLastUse[SNOPower.Monk_TempestRush] = DateTime.Now;
                            GilesTrinity.LastPowerUsed = SNOPower.Monk_TempestRush;

                            // simulate movement speed of 30
                            SpeedSensor lastSensor = SpeedSensors.OrderByDescending(s => s.Timestamp).FirstOrDefault();
                            SpeedSensors.Add(new SpeedSensor()
                            {
                                Location          = vMyCurrentPosition,
                                TimeSinceLastMove = new TimeSpan(0, 0, 0, 0, 1000),
                                Distance          = 5f,
                                WorldID           = GilesTrinity.CurrentWorldDynamicId
                            });

                            if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                            {
                                DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Tempest Rush for OOC movement, distance={0:0} spirit={1:0} cd={2} lastUse={3:0} V3={4} vAim={5}",
                                             DestinationDistance, GilesTrinity.PlayerStatus.PrimaryResource, PowerManager.CanCast(SNOPower.Monk_TempestRush), lastUse, vMoveToTarget, vTargetAimPoint);
                            }
                            return;
                        }
                        else
                        {
                            return;
                        }
                    }
                    else
                    {
                        if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                        {
                            DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement,
                                         "Tempest rush failed!: {0:00.0} / {1} distance: {2:00.0} / {3} Raycast: {4} MS: {5:0.0} lastUse={6:0}",
                                         GilesTrinity.PlayerStatus.PrimaryResource,
                                         GilesTrinity.Settings.Combat.Monk.TR_MinSpirit,
                                         DestinationDistance,
                                         GilesTrinity.Settings.Combat.Monk.TR_MinDist,
                                         canRayCastTarget,
                                         GetMovementSpeed(),
                                         lastUse);
                        }

                        GilesTrinity.MaintainTempestRush = false;
                    }

                    // Always set this from PlayerMover
                    GilesTrinity.LastTempestRushLocation = vTargetAimPoint;
                }

                bool hasWormHole = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.Wizard_Teleport && s.RuneIndex == 4);

                // 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.Hotbar.Contains(SNOPower.Wizard_Teleport) &&
                    ((PowerManager.CanCast(SNOPower.Wizard_Teleport) && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Wizard_Teleport]) ||
                     (hasWormHole && WizardTeleportCount < 3 && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport]).TotalMilliseconds >= 250)) &&
                    DestinationDistance >= 10f && !ShrinesInArea(vMoveToTarget))
                {
                    // Reset teleport count if we've already hit the max
                    if (WizardTeleportCount >= 3)
                    {
                        WizardTeleportCount = 0;
                    }

                    // increment the teleport count for wormhole rune
                    WizardTeleportCount++;

                    var maxTeleportRange = 75f;

                    Vector3 vThisTarget = vMoveToTarget;
                    if (DestinationDistance > maxTeleportRange)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, maxTeleportRange);
                    }
                    ZetaDia.Me.UsePower(SNOPower.Wizard_Teleport, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Teleport] = DateTime.Now;
                    if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Teleport for OOC movement, distance={0}", DestinationDistance);
                    }
                    return;
                }
                // Archon Teleport for a wizard
                if (GilesTrinity.Hotbar.Contains(SNOPower.Wizard_Archon_Teleport) &&
                    DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport]).TotalMilliseconds >= GilesTrinity.dictAbilityRepeatDelay[SNOPower.Wizard_Archon_Teleport] &&
                    DestinationDistance >= 20f &&
                    PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (DestinationDistance > 35f)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, vMyCurrentPosition, 35f);
                    }
                    ZetaDia.Me.UsePower(SNOPower.Wizard_Archon_Teleport, vThisTarget, GilesTrinity.CurrentWorldDynamicId, -1);
                    GilesTrinity.dictAbilityLastUse[SNOPower.Wizard_Archon_Teleport] = DateTime.Now;
                    if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Archon Teleport for OOC movement, distance={0}", DestinationDistance);
                    }
                    return;
                }
            }

            if (vMyCurrentPosition.Distance2D(vMoveToTarget) > 1f)
            {
                // Default movement
                ZetaDia.Me.UsePower(SNOPower.Walk, vMoveToTarget, GilesTrinity.CurrentWorldDynamicId, -1);

                if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                {
                    DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Moved to:{0} dir: {1} Speed:{2:0.00} Dist:{3:0} ZDiff:{4:0} Nav:{5} LoS:{6}",
                                 vMoveToTarget, MathUtil.GetHeadingToPoint(vMoveToTarget), MovementSpeed, vMyCurrentPosition.Distance2D(vMoveToTarget),
                                 Math.Abs(vMyCurrentPosition.Z - vMoveToTarget.Z),
                                 GilesTrinity.gp.CanStandAt(GilesTrinity.gp.WorldToGrid(vMoveToTarget.ToVector2())),
                                 !Navigator.Raycast(vMyCurrentPosition, vMoveToTarget)
                                 );
                }
            }
            else
            {
                if (GilesTrinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                {
                    DbHelper.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Reached MoveTowards Destination {0} Current Speed: {1:0.0}", vMoveToTarget, MovementSpeed);
                }
            }
        }
Beispiel #26
0
        public static TrinityPower GetPower()
        {
            TrinityPower power = null;

            // Spirit Walk, always!
            if (CanCast(SNOPower.Witchdoctor_SpiritWalk))
            {
                return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk));
            }

            var hasAngryChicken = (Skills.WitchDoctor.Hex.IsActive && Runes.WitchDoctor.AngryChicken.IsActive) || CacheData.Hotbar.ActivePowers.Contains(SNOPower.Witchdoctor_Hex_ChickenWalk);

            if (hasAngryChicken && Sets.ManajumasWay.IsEquipped && CanCast(SNOPower.Witchdoctor_Hex))
            {
                return(new TrinityPower(SNOPower.Witchdoctor_Hex));
            }

            // Combat Avoidance Spells
            if (!UseOOCBuff && IsCurrentlyAvoiding)
            {
                // Spirit Walk out of AoE
                if (CanCast(SNOPower.Witchdoctor_SpiritWalk))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk));
                }

                // Soul harvest at current location while avoiding
                if (Sets.RaimentOfTheJadeHarvester.IsMaxBonusActive && MinimumSoulHarvestCriteria(Enemies.BestCluster))
                {
                    Skills.WitchDoctor.SoulHarvest.Cast();
                }
            }

            // Incapacitated or Rooted
            if (!UseOOCBuff && (Player.IsIncapacitated || Player.IsRooted))
            {
                // Spirit Walk
                if (CanCast(SNOPower.Witchdoctor_SpiritWalk))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk));
                }
            }

            // Combat Spells with a Target
            if (!UseOOCBuff && !IsCurrentlyAvoiding && CurrentTarget != null)
            {
                if (_bastianGeneratorWaitTimer.IsFinished && ShouldRefreshBastiansGeneratorBuff)
                {
                    if (Hotbar.Contains(SNOPower.Witchdoctor_CorpseSpider))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_CorpseSpider, 50f, CurrentTarget.ACDGuid));
                    }
                    if (Hotbar.Contains(SNOPower.Witchdoctor_PoisonDart))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_PoisonDart, 50f, CurrentTarget.ACDGuid));
                    }
                    if (Hotbar.Contains(SNOPower.Witchdoctor_PlagueOfToads))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_PlagueOfToads, 50f, CurrentTarget.ACDGuid));
                    }
                    if (Hotbar.Contains(SNOPower.Witchdoctor_Firebomb))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_Firebomb, 50f, CurrentTarget.ACDGuid));
                    }
                    _bastianGeneratorWaitTimer.Reset();
                }


                bool hasGraveInjustice = CacheData.Hotbar.PassiveSkills.Contains(SNOPower.Witchdoctor_Passive_GraveInjustice);



                //                Debug.Print(CacheData.Hotbar.GetSkill(SNOPower.Witchdoctor_Hex).RuneIndex.ToString());
                var isChicken = CacheData.Hotbar.ActivePowers.Contains(SNOPower.Witchdoctor_Hex_ChickenWalk);

                bool hasVisionQuest = CacheData.Hotbar.PassiveSkills.Any(s => s == SNOPower.Witchdoctor_Passive_VisionQuest);

                // Set max ranged attack range, based on Grave Injustice, and current target NOT standing in avoidance, and health > 25%
                float rangedAttackMaxRange = 30f;
                if (hasGraveInjustice && !CurrentTarget.IsStandingInAvoidance && Player.CurrentHealthPct > 0.25)
                {
                    rangedAttackMaxRange = Math.Min(Player.GoldPickupRadius + 8f, 30f);
                }

                // Set basic attack range, depending on whether or not we have Bears and whether or not we are a tik tank
                float basicAttackRange = 35f;
                if (hasGraveInjustice)
                {
                    basicAttackRange = rangedAttackMaxRange;
                }
                else if (Hotbar.Contains(SNOPower.Witchdoctor_ZombieCharger) && Player.PrimaryResource >= 150)
                {
                    basicAttackRange = 30f;
                }
                else if (Legendary.TiklandianVisage.IsEquipped && !TikHorrifyCriteria(Enemies.BestLargeCluster))
                {
                    basicAttackRange = 25f;
                }
                else if (Legendary.TiklandianVisage.IsEquipped)
                {
                    basicAttackRange = 1f;
                }



                // Summon Pets  -----------------------------------------------------------------------

                // Hex with angry chicken, is chicken, explode!
                if (isChicken && (TargetUtil.AnyMobsInRange(12f, 1, false) || CurrentTarget.RadiusDistance <= 10f || UseDestructiblePower)) // && CanCast(SNOPower.Witchdoctor_Hex_Explode)
                {
                    //Debug.Print("Attempting to cast HEx Explosion {0}", ZetaDia.Me.UsePower(SNOPower.Witchdoctor_Hex_Explode, ZetaDia.Me.Position,
                    //    ZetaDia.CurrentWorldDynamicId));

                    return(new TrinityPower(SNOPower.Witchdoctor_Hex_Explode));
                }

                bool hasJaunt          = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 1);
                bool hasHonoredGuest   = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 3);
                bool hasUmbralShock    = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 2);
                bool hasSeverance      = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 0);
                bool hasHealingJourney = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritWalk && s.RuneIndex == 4);

                // Spirit Walk for Goblins chasing
                if (CanCast(SNOPower.Witchdoctor_SpiritWalk) &&
                    CurrentTarget.IsTreasureGoblin && CurrentTarget.HitPointsPct < 0.90 && CurrentTarget.RadiusDistance <= 40f)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk));
                }

                // Spirit Walk < 65% Health: Healing Journey
                if (CanCast(SNOPower.Witchdoctor_SpiritWalk) && hasHealingJourney &&
                    Player.CurrentHealthPct <= V.F("WitchDoctor.SpiritWalk.HealingJourneyHealth"))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk));
                }

                // Spirit Walk < 50% Mana: Honored Guest
                if (CanCast(SNOPower.Witchdoctor_SpiritWalk) && hasHonoredGuest &&
                    Player.PrimaryResourcePct <= V.F("WitchDoctor.SpiritWalk.HonoredGuestMana"))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk));
                }

                //bool shouldRefreshVisionQuest = GetTimeSinceLastVisionQuestRefresh() > 4000;
                bool shouldRefreshVisionQuest = !GetHasBuff(SNOPower.Witchdoctor_Passive_VisionQuest) || GetTimeSinceLastVisionQuestRefresh() > 3800;

                // Vision Quest Passive
                if (hasVisionQuest && shouldRefreshVisionQuest)
                {
                    // Poison Darts
                    if (CanCast(SNOPower.Witchdoctor_PoisonDart))
                    {
                        VisionQuestRefreshTimer.Restart();
                        return(new TrinityPower(SNOPower.Witchdoctor_PoisonDart, basicAttackRange, CurrentTarget.ACDGuid));
                    }
                    // Corpse Spiders
                    if (CanCast(SNOPower.Witchdoctor_CorpseSpider))
                    {
                        VisionQuestRefreshTimer.Restart();
                        return(new TrinityPower(SNOPower.Witchdoctor_CorpseSpider, basicAttackRange, CurrentTarget.ACDGuid));
                    }
                    // Plague Of Toads
                    if (CanCast(SNOPower.Witchdoctor_PlagueOfToads))
                    {
                        VisionQuestRefreshTimer.Restart();
                        return(new TrinityPower(SNOPower.Witchdoctor_PlagueOfToads, basicAttackRange, CurrentTarget.ACDGuid));
                    }
                    // Fire Bomb
                    if (CanCast(SNOPower.Witchdoctor_Firebomb))
                    {
                        VisionQuestRefreshTimer.Restart();
                        return(new TrinityPower(SNOPower.Witchdoctor_Firebomb, basicAttackRange, CurrentTarget.ACDGuid));;
                    }
                }

                bool hasVengefulSpirit  = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SoulHarvest && s.RuneIndex == 4);
                bool hasSwallowYourSoul = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SoulHarvest && s.RuneIndex == 3);

                // START Jade Harvester -----------------------------------------------------------------------

                if (Sets.RaimentOfTheJadeHarvester.IsMaxBonusActive)
                {
                    //LogTargetArea("BestLargeCluster", Enemies.BestLargeCluster);
                    //LogTargetArea("BestCluster", Enemies.BestCluster);
                    //LogTargetArea("Nearby", Enemies.Nearby);
                    //LogTargetArea("CloseNearby", Enemies.CloseNearby);

                    // Piranhas
                    if (CanCast(SNOPower.Witchdoctor_Piranhas) && Player.PrimaryResource >= 250 &&
                        (TargetUtil.ClusterExists(15f, 45f) || TargetUtil.AnyElitesInRange(45f)) &&
                        LastPowerUsed != SNOPower.Witchdoctor_Piranhas &&
                        Player.PrimaryResource >= 250)
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_Piranhas, 25f, Enemies.BestCluster.Position));
                    }

                    // Should we move to cluster for harvest
                    if (IdealSoulHarvestCriteria(Enemies.BestLargeCluster))
                    {
                        //LogTargetArea("--- Found a good harvest location...", Enemies.BestLargeCluster);
                        MoveToSoulHarvestPoint(Enemies.BestLargeCluster);
                    }

                    // Is there a slightly better position than right here
                    if (MinimumSoulHarvestCriteria(Enemies.BestCluster) && (Enemies.BestCluster.EliteCount >= 2 || Enemies.BestCluster.UnitCount > 4))
                    {
                        //LogTargetArea("--- Found an average harvest location...", Enemies.BestCluster);
                        MoveToSoulHarvestPoint(Enemies.BestCluster);
                    }

                    // Should we harvest right here?
                    if (MinimumSoulHarvestCriteria(Enemies.CloseNearby))
                    {
                        //LogTargetArea("--- Harvesting (CurrentPosition)", Enemies.CloseNearby);
                        return(new TrinityPower(SNOPower.Witchdoctor_SoulHarvest));
                    }

                    // Locust Swarm
                    if (CanCast(SNOPower.Witchdoctor_Locust_Swarm) && Player.PrimaryResource >= 300 &&
                        !CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_Locust_Swarm, 20f, CurrentTarget.ACDGuid));
                    }

                    // Haunt
                    if (Skills.WitchDoctor.Haunt.CanCast() && Player.PrimaryResource >= 50 &&
                        !CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Haunt))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_Haunt, 45f, CurrentTarget.ACDGuid));
                    }

                    // Acid Cloud
                    if (Skills.WitchDoctor.AcidCloud.CanCast() && Player.PrimaryResource >= 325 &&
                        LastPowerUsed != SNOPower.Witchdoctor_AcidCloud)
                    {
                        Vector3 bestClusterPoint;
                        if (Passives.WitchDoctor.GraveInjustice.IsActive)
                        {
                            bestClusterPoint = TargetUtil.GetBestClusterPoint(15f, Math.Min(Player.GoldPickupRadius + 8f, 30f));
                        }
                        else
                        {
                            bestClusterPoint = TargetUtil.GetBestClusterPoint(15f, 30f);
                        }

                        return(new TrinityPower(SNOPower.Witchdoctor_AcidCloud, rangedAttackMaxRange, bestClusterPoint));
                    }

                    // Spread the love around
                    if (!CurrentTarget.IsTreasureGoblin && CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) &&
                        CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Haunt) && Enemies.Nearby.UnitCount > 3 &&
                        Enemies.Nearby.DebuffedPercent(HarvesterCoreDebuffs) < 0.5)
                    {
                        //var oldTarget = Trinity.CurrentTarget;
                        Trinity.Blacklist3Seconds.Add(CurrentTarget.RActorGuid);
                        Trinity.CurrentTarget = Enemies.BestCluster.GetTargetWithoutDebuffs(HarvesterCoreDebuffs);
                        //Logger.LogNormal("{0} {1} is fully debuffed, switched to {2} {3}", oldTarget.InternalName, oldTarget.ACDGuid, CurrentTarget.InternalName, CurrentTarget.ACDGuid);
                    }

                    // Save mana for locust swarm || piranhas
                    if (!CurrentTarget.HasDebuff(SNOPower.Witchdoctor_Locust_Swarm) && Player.PrimaryResource < 300)
                    {
                        //Logger.LogNormal("Saving mana");
                        return(DefaultPower);
                    }
                }

                // END Jade Harvester -----------------------------------------------------------------------

                // Tiklandian Visage ----------------------------------------------------------------------
                // Constantly casts Horrify and moves the middle of clusters

                if (Legendary.TiklandianVisage.IsEquipped)
                {
                    // Piranhas
                    if (CanCast(SNOPower.Witchdoctor_Piranhas) && Player.PrimaryResource >= 250 &&
                        (TargetUtil.ClusterExists(15f, 45f) || TargetUtil.AnyElitesInRange(45f)) &&
                        LastPowerUsed != SNOPower.Witchdoctor_Piranhas &&
                        Player.PrimaryResource >= 250)
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_Piranhas, 25f, Enemies.BestCluster.Position));
                    }

                    //Cast Horrify before we go into the fray
                    if (CanCast(SNOPower.Witchdoctor_Horrify))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_Horrify));
                    }

                    // Should we move to a better position to fear people
                    if (TikHorrifyCriteria(Enemies.BestLargeCluster))
                    {
                        MoveToHorrifyPoint(Enemies.BestLargeCluster);
                    }
                }

                // END Tiklandian Visage ----------------------------------------------------------------------

                // Sacrifice
                if (CanCast(SNOPower.Witchdoctor_Sacrifice) && Trinity.PlayerOwnedZombieDogCount > 0 &&
                    (TargetUtil.AnyElitesInRange(15, 1) || (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.RadiusDistance <= 9f)))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Sacrifice));
                }

                // Sacrifice for Circle of Life
                bool hasCircleofLife = CacheData.Hotbar.PassiveSkills.Any(s => s == SNOPower.Witchdoctor_Passive_CircleOfLife);
                if (CanCast(SNOPower.Witchdoctor_Sacrifice) && Trinity.PlayerOwnedZombieDogCount > 0 && hasCircleofLife && TargetUtil.AnyMobsInRange(15f))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Sacrifice));
                }

                // Wall of Zombies
                if (CanCast(SNOPower.Witchdoctor_WallOfZombies) &&
                    (TargetUtil.AnyElitesInRange(15, 1) || TargetUtil.AnyMobsInRange(15, 1) ||
                     ((CurrentTarget.IsEliteRareUnique || CurrentTarget.IsTreasureGoblin || CurrentTarget.IsBoss) && CurrentTarget.RadiusDistance <= 25f)))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_WallOfZombies, 25f, CurrentTarget.Position));
                }

                bool hasRestlessGiant     = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Gargantuan && s.RuneIndex == 0);
                bool hasWrathfulProtector = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Gargantuan && s.RuneIndex == 3);

                if (CanCast(SNOPower.Witchdoctor_Gargantuan))
                {
                    // Gargantuan, Recast on Elites or Bosses to trigger Restless Giant
                    if (hasRestlessGiant && (TargetUtil.IsEliteTargetInRange(30f) || Trinity.PlayerOwnedGargantuanCount == 0))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_Gargantuan));
                    }

                    // Gargantuan Wrathful Protector, 15 seconds of smash, use sparingly!
                    if (hasWrathfulProtector && TargetUtil.IsEliteTargetInRange(30f))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_Gargantuan));
                    }

                    // Gargantuan regular
                    if (!hasRestlessGiant && !hasWrathfulProtector && Trinity.PlayerOwnedGargantuanCount == 0)
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_Gargantuan));
                    }
                }

                bool hasSacrifice = Hotbar.Contains(SNOPower.Witchdoctor_Sacrifice);

                // Zombie Dogs for Sacrifice
                if (hasSacrifice && CanCast(SNOPower.Witchdoctor_SummonZombieDog) &&
                    (LastPowerUsed == SNOPower.Witchdoctor_Sacrifice || Trinity.PlayerOwnedZombieDogCount <= 2) &&
                    LastPowerUsed != SNOPower.Witchdoctor_SummonZombieDog)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SummonZombieDog));
                }

                // Hex with angry chicken, check if we want to shape shift and explode
                if (CanCast(SNOPower.Witchdoctor_Hex) && (TargetUtil.AnyMobsInRange(12f, 1, false) || CurrentTarget.RadiusDistance <= 10f) &&
                    hasAngryChicken)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Hex));
                }

                // Hex Spam Cast without angry chicken
                if (CanCast(SNOPower.Witchdoctor_Hex) && !hasAngryChicken &&
                    (TargetUtil.AnyElitesInRange(12) || TargetUtil.AnyMobsInRange(12, 2) || TargetUtil.IsEliteTargetInRange(18f)))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Hex));
                }

                if (CanCast(SNOPower.Witchdoctor_SoulHarvest) && (TargetUtil.AnyElitesInRange(16) || TargetUtil.AnyMobsInRange(16, 2) || TargetUtil.IsEliteTargetInRange(16f)))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SoulHarvest));
                }

                // Mass Confuse, elites only or big mobs or to escape on low health
                if (CanCast(SNOPower.Witchdoctor_MassConfusion) &&
                    (TargetUtil.AnyElitesInRange(12, 1) || TargetUtil.AnyMobsInRange(12, 6) || Player.CurrentHealthPct <= 0.25 || (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.RadiusDistance <= 12f)) &&
                    !CurrentTarget.IsTreasureGoblin)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_MassConfusion, 0f, CurrentTarget.ACDGuid));
                }

                if (!Settings.Combat.WitchDoctor.UseBigBadVoodooOffCooldown)
                {
                    // Big Bad Voodoo, elites and bosses only
                    if (CanCast(SNOPower.Witchdoctor_BigBadVoodoo) &&
                        (TargetUtil.EliteOrTrashInRange(25f) || (CurrentTarget.IsBoss && CurrentTarget.Distance <= 30f)))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_BigBadVoodoo));
                    }
                }
                else
                {
                    // Big Bad Voodo, cast whenever available
                    if (!UseOOCBuff && !Player.IsIncapacitated && CanCast(SNOPower.Witchdoctor_BigBadVoodoo))
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_BigBadVoodoo));
                    }
                }
                // Grasp of the Dead
                if (CanCast(SNOPower.Witchdoctor_GraspOfTheDead) &&
                    (TargetUtil.AnyMobsInRange(30, 2) || TargetUtil.EliteOrTrashInRange(30f)) &&
                    Player.PrimaryResource >= 150)
                {
                    var bestClusterPoint = TargetUtil.GetBestClusterPoint();

                    return(new TrinityPower(SNOPower.Witchdoctor_GraspOfTheDead, 25f, bestClusterPoint));
                }

                // Piranhas
                if (CanCast(SNOPower.Witchdoctor_Piranhas) && Player.PrimaryResource >= 250 &&
                    (TargetUtil.ClusterExists(15f, 45f) || TargetUtil.AnyElitesInRange(45f)) &&
                    Player.PrimaryResource >= 250)
                {
                    var bestClusterPoint = TargetUtil.GetBestClusterPoint();

                    return(new TrinityPower(SNOPower.Witchdoctor_Piranhas, 25f, bestClusterPoint));
                }

                bool hasPhobia            = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 2);
                bool hasStalker           = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 4);
                bool hasFaceOfDeath       = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 1);
                bool hasFrighteningAspect = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 0);
                bool hasRuthlessTerror    = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 3);

                float horrifyRadius = hasFaceOfDeath ? 24f : 12f;

                // Horrify when low on health
                if (CanCast(SNOPower.Witchdoctor_Horrify) && Player.CurrentHealthPct <= EmergencyHealthPotionLimit && TargetUtil.AnyMobsInRange(horrifyRadius, 3))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Horrify));
                }

                // Horrify Buff at 35% health -- Freightening Aspect
                if (CanCast(SNOPower.Witchdoctor_Horrify) && Player.CurrentHealthPct <= 0.35 && hasFrighteningAspect)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Horrify));
                }

                // Spam Horrify
                if (CanCast(SNOPower.Witchdoctor_Horrify) && Settings.Combat.WitchDoctor.SpamHorrify)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Horrify));
                }

                // Fetish Army, elites only
                if (CanCast(SNOPower.Witchdoctor_FetishArmy) &&
                    (TargetUtil.EliteOrTrashInRange(30f) || TargetUtil.IsEliteTargetInRange(30f) || Settings.Combat.WitchDoctor.UseFetishArmyOffCooldown))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_FetishArmy));
                }

                bool hasManitou = Runes.WitchDoctor.Manitou.IsActive;

                // Spirit Barrage Manitou
                if (CanCast(SNOPower.Witchdoctor_SpiritBarrage) && Player.PrimaryResource >= 100 &&
                    TimeSincePowerUse(SNOPower.Witchdoctor_SpiritBarrage) > 18000 && hasManitou)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SpiritBarrage));
                }

                bool hasResentfulSpirit = Runes.WitchDoctor.ResentfulSpirits.IsActive;
                // Haunt
                if (CanCast(SNOPower.Witchdoctor_Haunt) &&
                    Player.PrimaryResource >= 50 &&
                    !SpellTracker.IsUnitTracked(CurrentTarget, SNOPower.Witchdoctor_Haunt) &&
                    LastPowerUsed != SNOPower.Witchdoctor_Haunt)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Haunt, 21f, CurrentTarget.ACDGuid));
                }

                //skillDict.Add("LocustSwarm", SNOPower.Witchdoctor_Locust_Swarm);

                // Locust Swarm
                if (CanCast(SNOPower.Witchdoctor_Locust_Swarm) && Player.PrimaryResource >= 300 &&
                    !SpellTracker.IsUnitTracked(CurrentTarget, SNOPower.Witchdoctor_Locust_Swarm) && LastPowerUsed != SNOPower.Witchdoctor_Locust_Swarm)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Locust_Swarm, 12f, CurrentTarget.ACDGuid));
                }

                // Sacrifice for 0 Dogs
                if (CanCast(SNOPower.Witchdoctor_Sacrifice) &&
                    (Settings.Combat.WitchDoctor.ZeroDogs || !WitchDoctorHasPrimaryAttack))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Sacrifice, 9f));
                }

                var zombieChargerRange = hasGraveInjustice ? Math.Min(Player.GoldPickupRadius + 8f, 11f) : 11f;

                // Zombie Charger aka Zombie bears Spams Bears @ Everything from 11feet away
                if (CanCast(SNOPower.Witchdoctor_ZombieCharger) && Player.PrimaryResource >= 150)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_ZombieCharger, zombieChargerRange, CurrentTarget.Position));
                }

                // Soul Harvest Any Elites or to increase buff stacks
                if (!Sets.RaimentOfTheJadeHarvester.IsMaxBonusActive && CanCast(SNOPower.Witchdoctor_SoulHarvest) &&
                    (TargetUtil.AnyMobsInRange(16f, GetBuffStacks(SNOPower.Witchdoctor_SoulHarvest) + 1, false) || (hasSwallowYourSoul && Player.PrimaryResourcePct <= 0.50) || TargetUtil.IsEliteTargetInRange(16f)))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SoulHarvest));
                }

                // Soul Harvest with VengefulSpirit
                if (!Sets.RaimentOfTheJadeHarvester.IsMaxBonusActive && CanCast(SNOPower.Witchdoctor_SoulHarvest) && hasVengefulSpirit &&
                    TargetUtil.AnyMobsInRange(16, 3))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SoulHarvest));
                }

                bool hasDireBats    = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 0);
                bool hasVampireBats = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 3);
                bool hasPlagueBats  = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 2);
                bool hasHungryBats  = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 1);
                bool hasCloudOfBats = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Firebats && s.RuneIndex == 4);

                int fireBatsChannelCost = hasVampireBats ? 0 : 75;
                int fireBatsMana        = TimeSincePowerUse(SNOPower.Witchdoctor_Firebats) < 125 ? fireBatsChannelCost : 225;

                bool firebatsMaintain =
                    Trinity.ObjectCache.Any(u => u.IsUnit &&
                                            u.IsPlayerFacing(70f) && u.Weight > 0 &&
                                            u.Distance <= V.F("WitchDoctor.Firebats.MaintainRange") &&
                                            SpellHistory.TimeSinceUse(SNOPower.Witchdoctor_Firebats) <= TimeSpan.FromMilliseconds(250d));

                // Fire Bats:Cloud of bats
                if (hasCloudOfBats && (TargetUtil.AnyMobsInRange(8f) || firebatsMaintain) &&
                    CanCast(SNOPower.Witchdoctor_Firebats) && Player.PrimaryResource >= fireBatsMana)
                {
                    var range = Settings.Combat.WitchDoctor.FirebatsRange > 12f ? 12f : Settings.Combat.WitchDoctor.FirebatsRange;

                    return(new TrinityPower(SNOPower.Witchdoctor_Firebats, range, CurrentTarget.ACDGuid));
                }

                // Fire Bats fast-attack
                if (CanCast(SNOPower.Witchdoctor_Firebats) && Player.PrimaryResource >= fireBatsMana &&
                    (TargetUtil.AnyMobsInRange(Settings.Combat.WitchDoctor.FirebatsRange) || firebatsMaintain) && !hasCloudOfBats)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Firebats, Settings.Combat.WitchDoctor.FirebatsRange, CurrentTarget.Position));
                }

                // Acid Cloud
                if (CanCast(SNOPower.Witchdoctor_AcidCloud) && Player.PrimaryResource >= 175)
                {
                    Vector3 bestClusterPoint;
                    if (hasGraveInjustice)
                    {
                        bestClusterPoint = TargetUtil.GetBestClusterPoint(15f, Math.Min(Player.GoldPickupRadius + 8f, 30f));
                    }
                    else
                    {
                        bestClusterPoint = TargetUtil.GetBestClusterPoint(15f, 30f);
                    }

                    return(new TrinityPower(SNOPower.Witchdoctor_AcidCloud, rangedAttackMaxRange, bestClusterPoint));
                }

                bool hasWellOfSouls   = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_SpiritBarrage && s.RuneIndex == 1);
                bool hasRushOfEssence = CacheData.Hotbar.PassiveSkills.Any(s => s == SNOPower.Witchdoctor_Passive_RushOfEssence);

                // Spirit Barrage + Rush of Essence
                if (CanCast(SNOPower.Witchdoctor_SpiritBarrage) && Player.PrimaryResource >= 100 &&
                    hasRushOfEssence && !hasManitou)
                {
                    if (hasWellOfSouls)
                    {
                        return(new TrinityPower(SNOPower.Witchdoctor_SpiritBarrage, 21f, CurrentTarget.ACDGuid));
                    }

                    return(new TrinityPower(SNOPower.Witchdoctor_SpiritBarrage, 21f, CurrentTarget.ACDGuid));
                }

                // Zombie Charger backup
                if (CanCast(SNOPower.Witchdoctor_ZombieCharger) && Player.PrimaryResource >= 150)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_ZombieCharger, zombieChargerRange, CurrentTarget.Position));
                }

                // Regular spirit barage
                if (CanCast(SNOPower.Witchdoctor_SpiritBarrage) && Player.PrimaryResource >= 100 && !hasManitou)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SpiritBarrage, basicAttackRange, CurrentTarget.ACDGuid));
                }

                // Poison Darts fast-attack Spams Darts when mana is too low (to cast bears) @12yds or @10yds if Bears avialable
                if (CanCast(SNOPower.Witchdoctor_PoisonDart))
                {
                    VisionQuestRefreshTimer.Restart();
                    return(new TrinityPower(SNOPower.Witchdoctor_PoisonDart, basicAttackRange, CurrentTarget.ACDGuid));
                }
                // Corpse Spiders fast-attacks Spams Spiders when mana is too low (to cast bears) @12yds or @10yds if Bears avialable
                if (CanCast(SNOPower.Witchdoctor_CorpseSpider))
                {
                    VisionQuestRefreshTimer.Restart();
                    return(new TrinityPower(SNOPower.Witchdoctor_CorpseSpider, basicAttackRange, CurrentTarget.ACDGuid));
                }
                // Toads fast-attacks Spams Toads when mana is too low (to cast bears) @12yds or @10yds if Bears avialable
                if (CanCast(SNOPower.Witchdoctor_PlagueOfToads))
                {
                    VisionQuestRefreshTimer.Restart();
                    return(new TrinityPower(SNOPower.Witchdoctor_PlagueOfToads, basicAttackRange, CurrentTarget.ACDGuid));
                }
                // Fire Bomb fast-attacks Spams Bomb when mana is too low (to cast bears) @12yds or @10yds if Bears avialable
                if (CanCast(SNOPower.Witchdoctor_Firebomb))
                {
                    VisionQuestRefreshTimer.Restart();
                    return(new TrinityPower(SNOPower.Witchdoctor_Firebomb, basicAttackRange, CurrentTarget.ACDGuid));
                }

                //Hexing Pants Mod
                if (Legendary.HexingPantsOfMrYan.IsEquipped && CurrentTarget.IsUnit &&
                    //!CanCast(SNOPower.Witchdoctor_Piranhas) &&
                    CurrentTarget.RadiusDistance > 10f)
                {
                    return(new TrinityPower(SNOPower.Walk, 10f, CurrentTarget.Position));
                }

                if (Legendary.HexingPantsOfMrYan.IsEquipped && CurrentTarget.IsUnit &&
                    //!CanCast(SNOPower.Witchdoctor_Piranhas) &&
                    CurrentTarget.RadiusDistance < 10f)
                {
                    Vector3 vNewTarget = MathEx.CalculatePointFrom(CurrentTarget.Position, Player.Position, -10f);
                    return(new TrinityPower(SNOPower.Walk, 10f, vNewTarget));
                }
            }

            // Buffs
            if (UseOOCBuff)
            {
                // Spirit Walk OOC
                if (CanCast(SNOPower.Witchdoctor_SpiritWalk) && Settings.Combat.Misc.AllowOOCMovement)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SpiritWalk));
                }


                //Spam fear at all times if Tiklandian Visage is ewquipped and fear spam is selected to keep fear buff active
                if (CanCast(SNOPower.Witchdoctor_Horrify) && Settings.Combat.WitchDoctor.SpamHorrify && Legendary.TiklandianVisage.IsEquipped)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Horrify));
                }

                bool hasStalker = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Horrify && s.RuneIndex == 4);
                // Horrify Buff When not in combat for movement speed -- Stalker
                if (CanCast(SNOPower.Witchdoctor_Horrify) && hasStalker)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Horrify));
                }

                // Zombie Dogs non-sacrifice build
                if (CanCast(SNOPower.Witchdoctor_SummonZombieDog) &&
                    ((Legendary.TheTallMansFinger.IsEquipped && Trinity.PlayerOwnedZombieDogCount < 1) ||
                     (!Legendary.TheTallMansFinger.IsEquipped && Trinity.PlayerOwnedZombieDogCount <= 2)))
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_SummonZombieDog));
                }

                bool hasRestlessGiant     = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Gargantuan && s.RuneIndex == 0);
                bool hasWrathfulProtector = CacheData.Hotbar.ActiveSkills.Any(s => s.Power == SNOPower.Witchdoctor_Gargantuan && s.RuneIndex == 3);

                if (CanCast(SNOPower.Witchdoctor_Gargantuan) && !hasRestlessGiant && !hasWrathfulProtector && Trinity.PlayerOwnedGargantuanCount == 0)
                {
                    return(new TrinityPower(SNOPower.Witchdoctor_Gargantuan));
                }
            }

            // Default Attacks
            if (IsNull(power))
            {
                power = DefaultPower;
            }

            return(power);
        }
        public TrinityPower GetOffensivePower()
        {
            // 锁定奥拉什
            TrinityActor target;
            Vector3      position = Vector3.Zero;

            if (IsArchonActive)
            {
                target = TargetUtil.BestRangedAoeUnit(75, 75) ?? CurrentTarget;


                if (Skills.Wizard.ArchonTeleport.CanCast())
                {
                    if (!Core.Buffs.HasInvulnerableShrine && Skills.Wizard.Archon.TimeSinceUse > 19000)
                    {
                        Core.Avoidance.Avoider.TryGetSafeSpot(out position, 30, 50, Player.Position);
                    }

                    if (!Core.Buffs.HasInvulnerableShrine && Core.Avoidance.InAvoidance(Core.Player.Position))
                    {
                        Core.Avoidance.Avoider.TryGetSafeSpot(out position, 25, 45, target.Position);

                        if (position == Vector3.Zero)
                        {
                            Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15, 40, Player.Position);
                        }
                    }

                    if ((!Core.Buffs.HasInvulnerableShrine && (Player.CurrentHealthPct >= 1 && Player.ShieldHitpoints < Player.CurrentHealth) && Skills.Wizard.ArchonTeleport.TimeSinceUse > 3000) ||
                        (!Core.Buffs.HasInvulnerableShrine && Player.CurrentHealthPct < 1 && Skills.Wizard.ArchonTeleport.TimeSinceUse > 1500) ||
                        TargetUtil.AnyElitesInRange(10))
                    {
                        Core.Avoidance.Avoider.TryGetSafeSpot(out position, 30, 45, target.Position);
                    }

                    if (target.IsBoss && target.Distance < 30)
                    {
                        Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, target.Position);
                    }

                    if (position != Vector3.Zero)
                    {
                        return(Teleport(position));
                    }
                }

                if (Skills.Wizard.ArchonDisintegrationWave.CanCast())
                {
                    return(ArchonDisintegrationWave(target));
                }

                return(Walk(Avoider.SafeSpot));
            }


            target = TargetUtil.BestRangedAoeUnit(50) ?? CurrentTarget;


            if (Skills.Wizard.Archon.CanCast())
            {
                if (!HasTalRashaStacks)
                {
                    if (TalRashaStacks == 2 && Skills.Wizard.ArcaneTorrent.TimeSinceUse < 6000)
                    {
                        if (Skills.Wizard.Blizzard.TimeSinceUse < 6000)
                        {
                            return(BlackHole(target));
                        }
                        if (Skills.Wizard.BlackHole.TimeSinceUse < 6000)
                        {
                            return(Blizzard(target));
                        }
                    }
                }
                else
                {
                    return(Archon());
                }
            }

            var blizzard = Skills.Wizard.Blizzard;

            if (blizzard.CanCast() && blizzard.TimeSinceUse > 6000)
            {
                return(Blizzard(target));
            }

            var blackhole = Skills.Wizard.BlackHole;

            if ((blackhole.CanCast() && blackhole.TimeSinceUse > 6000))
            {
                return(BlackHole(target));
            }

            if (TalRashaStacks >= 2)
            {
                return(ArcaneTorrent(target));
            }

            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)));
        }
Beispiel #29
0
        public void MoveTowards(Vector3 vMoveToTarget)
        {
            if (Trinity.Settings.Advanced.DisableAllMovement)
            {
                return;
            }

            if (!ZetaDia.IsInGame || !ZetaDia.Me.IsValid || ZetaDia.Me.IsDead || ZetaDia.IsLoadingWorld)
            {
                return;
            }

            if (UISafetyCheck())
            {
                return;
            }

            TimeLastUsedPlayerMover = DateTime.UtcNow;
            LastMoveToTarget        = vMoveToTarget;

            // Set the public variable

            vMoveToTarget = WarnAndLogLongPath(vMoveToTarget);

            // Store player current position

            // Store distance to current moveto target
            float destinationDistance = MyPosition.Distance2D(vMoveToTarget);

            // Do unstuckery things
            if (Trinity.Settings.Advanced.UnstuckerEnabled)
            {
                // See if we can reset the 10-limit unstuck counter, if >120 seconds since we last generated an unstuck location
                // this is used if we're NOT stuck...
                if (TotalAntiStuckAttempts > 1 && DateTime.UtcNow.Subtract(LastGeneratedStuckPosition).TotalSeconds >= 120)
                {
                    TotalAntiStuckAttempts   = 1;
                    TimesReachedStuckPoint   = 0;
                    vSafeMovementLocation    = Vector3.Zero;
                    NavHelper.UsedStuckSpots = new List <GridPoint>();
                    Logger.Log(TrinityLogLevel.Info, LogCategory.Movement, "Resetting unstuck timers", true);
                }

                // See if we need to, and can, generate unstuck actions
                // check if we're stuck
                bool isStuck = UnstuckChecker();

                if (isStuck)
                {
                    // Record the time we last apparently couldn't move for a brief period of time
                    LastRecordedAnyStuck = DateTime.UtcNow;

                    // See if there's any stuck position to try and navigate to generated by random mover
                    vSafeMovementLocation = UnstuckHandler(MyPosition, vMoveToTarget);

                    if (vSafeMovementLocation == Vector3.Zero)
                    {
                        Logger.Log(TrinityLogLevel.Info, LogCategory.Movement, "Unable to find Unstuck point!", vSafeMovementLocation);
                        return;
                    }
                    Logger.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "SafeMovement Location set to {0}", vSafeMovementLocation);
                }
                // See if we can clear the total unstuckattempts if we haven't been stuck in over 6 minutes.
                if (DateTime.UtcNow.Subtract(LastRecordedAnyStuck).TotalSeconds >= 360)
                {
                    TimesReachedMaxUnstucks = 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;
                    destinationDistance = MyPosition.Distance2D(vMoveToTarget);
                }
                // Get distance to current destination
                // 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 && destinationDistance <= 3f)
                {
                    vSafeMovementLocation = Vector3.Zero;
                    TimesReachedStuckPoint++;

                    // Do we want to immediately generate a 2nd waypoint to "chain" anti-stucks in an ever-increasing path-length?
                    if (TimesReachedStuckPoint <= TotalAntiStuckAttempts)
                    {
                        vSafeMovementLocation = NavHelper.FindSafeZone(true, TotalAntiStuckAttempts, MyPosition);
                        vMoveToTarget         = vSafeMovementLocation;
                    }
                    else
                    {
                        if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                        {
                            Logger.Log(TrinityLogLevel.Info, LogCategory.UserInformation, "Clearing old route and trying new path find to: " + LastMoveToTarget.ToString());
                        }
                        // Reset the path and allow a whole "New" unstuck generation next cycle
                        TimesReachedStuckPoint = 0;
                        // And cancel unstucking for 9 seconds so DB can try to navigate
                        CancelUnstuckerForSeconds = (9 * TotalAntiStuckAttempts);
                        if (CancelUnstuckerForSeconds < 20)
                        {
                            CancelUnstuckerForSeconds = 20;
                        }
                        LastCancelledUnstucker = DateTime.UtcNow;

                        Navigator.Clear();
                        Navigator.MoveTo(LastMoveToTarget, "original destination", false);

                        return;
                    }
                }
            }

            // don't use special movement within 10 seconds of being stuck
            bool cancelSpecialMovementAfterStuck = DateTime.UtcNow.Subtract(LastGeneratedStuckPosition).TotalMilliseconds > 10000;

            // See if we can use abilities like leap etc. for movement out of combat, but not in town
            if (Trinity.Settings.Combat.Misc.AllowOOCMovement && !Trinity.Player.IsInTown && !Trinity.DontMoveMeIAmDoingShit && cancelSpecialMovementAfterStuck)
            {
                bool bTooMuchZChange = (Math.Abs(MyPosition.Z - vMoveToTarget.Z) >= 4f);

                // Whirlwind for a barb, special context only
                if (Trinity.Settings.Combat.Barbarian.SprintMode != BarbarianSprintMode.CombatOnly &&
                    Trinity.Hotbar.Contains(SNOPower.Barbarian_Whirlwind) && Trinity.ObjectCache.Any(u => u.IsUnit &&
                                                                                                     MathUtil.IntersectsPath(u.Position, u.Radius + 5f, Trinity.Player.Position, vMoveToTarget)) &&
                    Trinity.Player.PrimaryResource >= V.F("Barbarian.Whirlwind.MinFury") && !Trinity.IsWaitingForSpecial && V.B("Barbarian.Whirlwind.UseForMovement"))
                {
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_Whirlwind, vMoveToTarget, Trinity.CurrentWorldDynamicId, -1);
                    SpellHistory.RecordSpell(SNOPower.Barbarian_Whirlwind);
                    if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Whirlwind for OOC movement, distance={0}", destinationDistance);
                    }
                    return;
                }

                // Leap movement for a barb
                if (Trinity.Settings.Combat.Barbarian.UseLeapOOC && Trinity.Hotbar.Contains(SNOPower.Barbarian_Leap) &&
                    PowerManager.CanCast(SNOPower.Barbarian_Leap) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (destinationDistance > 35f)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, 35f);
                    }
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_Leap, vThisTarget, Trinity.CurrentWorldDynamicId, -1);
                    SpellHistory.RecordSpell(SNOPower.Barbarian_Leap);
                    if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Leap for OOC movement, distance={0}", destinationDistance);
                    }
                    return;
                }
                // Furious Charge movement for a barb
                if (Trinity.Settings.Combat.Barbarian.UseChargeOOC && Trinity.Hotbar.Contains(SNOPower.Barbarian_FuriousCharge) && !bTooMuchZChange &&
                    destinationDistance >= 20f &&
                    PowerManager.CanCast(SNOPower.Barbarian_FuriousCharge) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (destinationDistance > 35f)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, 35f);
                    }
                    ZetaDia.Me.UsePower(SNOPower.Barbarian_FuriousCharge, vThisTarget, Trinity.CurrentWorldDynamicId, -1);
                    SpellHistory.RecordSpell(SNOPower.Barbarian_FuriousCharge);
                    if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Furious Charge for OOC movement, distance={0}", destinationDistance);
                    }
                    return;
                }

                bool hasTacticalAdvantage = HotbarSkills.PassiveSkills.Any(s => s == SNOPower.DemonHunter_Passive_TacticalAdvantage);
                int  vaultDelay           = hasTacticalAdvantage ? 2000 : Trinity.Settings.Combat.DemonHunter.VaultMovementDelay;

                // DemonHunter Vault
                if (Trinity.Hotbar.Contains(SNOPower.DemonHunter_Vault) && !bTooMuchZChange && Trinity.Settings.Combat.DemonHunter.VaultMode != DemonHunterVaultMode.CombatOnly &&
                    CombatBase.TimeSincePowerUse(SNOPower.DemonHunter_Vault) > vaultDelay &&
                    destinationDistance >= 18f &&
                    PowerManager.CanCast(SNOPower.DemonHunter_Vault) && !ShrinesInArea(vMoveToTarget) &&
                    // Don't Vault into avoidance/monsters if we're kiting
                    (CombatBase.PlayerKiteDistance <= 0 || (CombatBase.PlayerKiteDistance > 0 &&
                                                            (!CacheData.TimeBoundAvoidance.Any(a => a.Position.Distance(vMoveToTarget) <= CombatBase.PlayerKiteDistance) ||
                                                             (!CacheData.TimeBoundAvoidance.Any(a => MathEx.IntersectsPath(a.Position, a.Radius, Trinity.Player.Position, vMoveToTarget))) ||
                                                             !CacheData.MonsterObstacles.Any(a => a.Position.Distance(vMoveToTarget) <= CombatBase.PlayerKiteDistance))))
                    )
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (destinationDistance > 35f)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, 35f);
                    }
                    ZetaDia.Me.UsePower(SNOPower.DemonHunter_Vault, vThisTarget, Trinity.CurrentWorldDynamicId, -1);
                    SpellHistory.RecordSpell(SNOPower.DemonHunter_Vault);
                    if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Vault for OOC movement, distance={0}", destinationDistance);
                    }
                    return;
                }
                // Tempest rush for a monk
                if (Trinity.Hotbar.Contains(SNOPower.Monk_TempestRush) &&
                    (Trinity.Settings.Combat.Monk.TROption == TempestRushOption.MovementOnly || Trinity.Settings.Combat.Monk.TROption == TempestRushOption.Always ||
                     (Trinity.Settings.Combat.Monk.TROption == TempestRushOption.TrashOnly && !TargetUtil.AnyElitesInRange(40f))))
                {
                    Vector3 vTargetAimPoint = vMoveToTarget;

                    bool canRayCastTarget = true;

                    vTargetAimPoint = TargetUtil.FindTempestRushTarget();

                    if (!CanChannelTempestRush &&
                        ((Trinity.Player.PrimaryResource >= Trinity.Settings.Combat.Monk.TR_MinSpirit &&
                          destinationDistance >= Trinity.Settings.Combat.Monk.TR_MinDist) ||
                         DateTime.UtcNow.Subtract(CacheData.AbilityLastUsed[SNOPower.Monk_TempestRush]).TotalMilliseconds <= 150) &&
                        canRayCastTarget && PowerManager.CanCast(SNOPower.Monk_TempestRush))
                    {
                        CanChannelTempestRush = true;
                    }
                    else if ((CanChannelTempestRush && (Trinity.Player.PrimaryResource < 10f)) || !canRayCastTarget)
                    {
                        CanChannelTempestRush = false;
                    }

                    double lastUse = DateTime.UtcNow.Subtract(CacheData.AbilityLastUsed[SNOPower.Monk_TempestRush]).TotalMilliseconds;

                    if (CanChannelTempestRush)
                    {
                        if (Trinity.SNOPowerUseTimer(SNOPower.Monk_TempestRush))
                        {
                            LastTempestRushPosition = vTargetAimPoint;

                            ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, vTargetAimPoint, Trinity.CurrentWorldDynamicId, -1);
                            SpellHistory.RecordSpell(SNOPower.Monk_TempestRush);

                            // simulate movement speed of 30
                            SpeedSensor lastSensor = SpeedSensors.OrderByDescending(s => s.Timestamp).FirstOrDefault();
                            SpeedSensors.Add(new SpeedSensor()
                            {
                                Location          = MyPosition,
                                TimeSinceLastMove = new TimeSpan(0, 0, 0, 0, 1000),
                                Distance          = 5f,
                                WorldID           = Trinity.CurrentWorldDynamicId
                            });

                            if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                            {
                                Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Tempest Rush for OOC movement, distance={0:0} spirit={1:0} cd={2} lastUse={3:0} V3={4} vAim={5}",
                                           destinationDistance, Trinity.Player.PrimaryResource, PowerManager.CanCast(SNOPower.Monk_TempestRush), lastUse, vMoveToTarget, vTargetAimPoint);
                            }
                            return;
                        }
                        else
                        {
                            return;
                        }
                    }
                    else
                    {
                        if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                        {
                            Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement,
                                       "Tempest rush failed!: {0:00.0} / {1} distance: {2:00.0} / {3} Raycast: {4} MS: {5:0.0} lastUse={6:0}",
                                       Trinity.Player.PrimaryResource,
                                       Trinity.Settings.Combat.Monk.TR_MinSpirit,
                                       destinationDistance,
                                       Trinity.Settings.Combat.Monk.TR_MinDist,
                                       canRayCastTarget,
                                       GetMovementSpeed(),
                                       lastUse);
                        }

                        Trinity.MaintainTempestRush = false;
                    }

                    // Always set this from PlayerMover
                    Trinity.LastTempestRushLocation = vTargetAimPoint;
                }

                // Dashing Strike OOC
                if (CombatBase.CanCast(SNOPower.X1_Monk_DashingStrike) && Trinity.Settings.Combat.Monk.UseDashingStrikeOOC && destinationDistance > 15f)
                {
                    ZetaDia.Me.UsePower(SNOPower.X1_Monk_DashingStrike, vMoveToTarget, Trinity.CurrentWorldDynamicId, -1);
                    SpellHistory.RecordSpell(SNOPower.X1_Monk_DashingStrike);
                    if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Dashing Strike for OOC movement, distance={0}", destinationDistance);
                    }
                }


                bool hasWormHole = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.Wizard_Teleport && s.RuneIndex == 4);
                bool hasCalamity = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.Wizard_Teleport && s.RuneIndex == 0);

                // Teleport for a wizard
                if (!hasCalamity && CombatBase.CanCast(SNOPower.Wizard_Teleport, CombatBase.CanCastFlags.NoTimer) &&
                    CombatBase.TimeSincePowerUse(SNOPower.Wizard_Teleport) > 250 &&
                    destinationDistance >= 10f && !ShrinesInArea(vMoveToTarget))
                {
                    var maxTeleportRange = 75f;

                    Vector3 vThisTarget = vMoveToTarget;
                    if (destinationDistance > maxTeleportRange)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, maxTeleportRange);
                    }
                    ZetaDia.Me.UsePower(SNOPower.Wizard_Teleport, vThisTarget, Trinity.CurrentWorldDynamicId, -1);
                    SpellHistory.RecordSpell(SNOPower.Wizard_Teleport);
                    if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Teleport for OOC movement, distance={0}", destinationDistance);
                    }
                    return;
                }
                // Archon Teleport for a wizard
                if (Trinity.Hotbar.Contains(SNOPower.Wizard_Archon_Teleport) &&
                    DateTime.UtcNow.Subtract(CacheData.AbilityLastUsed[SNOPower.Wizard_Archon_Teleport]).TotalMilliseconds >= CombatBase.GetSNOPowerUseDelay(SNOPower.Wizard_Archon_Teleport) &&
                    destinationDistance >= 20f &&
                    PowerManager.CanCast(SNOPower.Wizard_Archon_Teleport) && !ShrinesInArea(vMoveToTarget))
                {
                    Vector3 vThisTarget = vMoveToTarget;
                    if (destinationDistance > 35f)
                    {
                        vThisTarget = MathEx.CalculatePointFrom(vMoveToTarget, MyPosition, 35f);
                    }
                    ZetaDia.Me.UsePower(SNOPower.Wizard_Archon_Teleport, vThisTarget, Trinity.CurrentWorldDynamicId, -1);
                    SpellHistory.RecordSpell(SNOPower.Wizard_Archon_Teleport);
                    if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                    {
                        Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Using Archon Teleport for OOC movement, distance={0}", destinationDistance);
                    }
                    return;
                }
            }

            if (MyPosition.Distance2D(vMoveToTarget) > 1f)
            {
                // Default movement
                ZetaDia.Me.UsePower(SNOPower.Walk, vMoveToTarget, Trinity.CurrentWorldDynamicId, -1);

                if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Moved to:{0} dir:{1} Speed:{2:0.00} Dist:{3:0} ZDiff:{4:0} CanStand:{5} Raycast:{6}",
                               NavHelper.PrettyPrintVector3(vMoveToTarget), MathUtil.GetHeadingToPoint(vMoveToTarget), MovementSpeed, MyPosition.Distance2D(vMoveToTarget),
                               Math.Abs(MyPosition.Z - vMoveToTarget.Z),
                               Trinity.MainGridProvider.CanStandAt(Trinity.MainGridProvider.WorldToGrid(vMoveToTarget.ToVector2())),
                               !Navigator.Raycast(MyPosition, vMoveToTarget)
                               );
                }
            }
            else
            {
                if (Trinity.Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Movement, "Reached MoveTowards Destination {0} Current Speed: {1:0.0}", vMoveToTarget, MovementSpeed);
                }
            }
        }
Beispiel #30
0
        public TrinityPower GetOffensivePower()
        {
            TrinityPower power;
            var          target   = CurrentTarget;
            Vector3      position = Vector3.Zero;

            if (Skills.Wizard.ArchonTeleport.CanCast() || Skills.Wizard.Teleport.CanCast())
            {
                if (!Core.Buffs.HasInvulnerableShrine && Core.Avoidance.InAvoidance(Core.Player.Position))
                {
                    Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, Player.Position);

                    if (position == Vector3.Zero)
                    {
                        Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15, 50, Player.Position);
                    }
                }
                if (!Core.Buffs.HasInvulnerableShrine)
                {
                    if (IsArchonActive)
                    {
                        if (TargetUtil.AnyElitesInRange(10) && Skills.Wizard.ArchonTeleport.TimeSinceUse > 2000)
                        {
                            Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, CurrentTarget.Position);

                            if (position == Vector3.Zero)
                            {
                                Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50, CurrentTarget.Position);
                            }
                        }
                        if (TargetUtil.AnyMobsInRange(15, 3) && Skills.Wizard.ArchonTeleport.TimeSinceUse > 3000)
                        {
                            Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, CurrentTarget.Position);

                            if (position == Vector3.Zero)
                            {
                                Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50, CurrentTarget.Position);
                            }
                        }
                        if (Player.CurrentHealthPct < 1 && Skills.Wizard.ArchonTeleport.TimeSinceUse > 2000)
                        {
                            Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, Player.Position);

                            if (position == Vector3.Zero)
                            {
                                Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15, 50, Player.Position);
                            }
                        }
                    }
                    if (!IsArchonActive)
                    {
                        if (TargetUtil.AnyElitesInRange(10) && Skills.Wizard.Teleport.TimeSinceUse > 1200)
                        {
                            Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, CurrentTarget.Position);

                            if (position == Vector3.Zero)
                            {
                                Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50, CurrentTarget.Position);
                            }
                        }

                        if (TargetUtil.AnyMobsInRange(15, 3) && Skills.Wizard.Teleport.TimeSinceUse > 1200)
                        {
                            Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, CurrentTarget.Position);

                            if (position == Vector3.Zero)
                            {
                                Core.Avoidance.Avoider.TryGetSafeSpot(out position, 20, 50, CurrentTarget.Position);
                            }
                        }
                        if (Player.CurrentHealthPct < 1 && Skills.Wizard.Teleport.TimeSinceUse > 1200)
                        {
                            Core.Avoidance.Avoider.TryGetSafeSpot(out position, 35, 50, Player.Position);

                            if (position == Vector3.Zero)
                            {
                                Core.Avoidance.Avoider.TryGetSafeSpot(out position, 15, 50, Player.Position);
                            }
                        }
                    }
                }

                if (target.IsBoss && target.Distance < 25 && (Skills.Wizard.ArchonTeleport.TimeSinceUse > 2000 || Skills.Wizard.Teleport.TimeSinceUse > 2000))
                {
                    Core.Avoidance.Avoider.TryGetSafeSpot(out position, 40, 50, CurrentTarget.Position);
                }

                if (position != Vector3.Zero && !TrinityGrid.Instance.IsIntersectedByFlags(ZetaDia.Me.Position, position, AvoidanceFlags.Combat, AvoidanceFlags.CriticalAvoidance))
                {
                    return(Teleport(position));
                }
            }

            if (!IsArchonActive)
            {
                if (Player.CurrentHealthPct <= 0.7f && Skills.Wizard.Archon.CanCast())
                {
                    Core.Logger.Log(LogCategory.Spells, $"Emergency Archon, LOW ON HP {Player.CurrentHealthPct}%!");
                    return(Archon());
                }
                if (Legendary.ConventionOfElements.IsEquipped)
                {
                    if (Skills.Wizard.Archon.CanCast())
                    {
                        if (Core.Buffs.ConventionElement == Element.Fire && TalRashaStacks >= 3)
                        {
                            Core.Logger.Log(LogCategory.Spells, $"Archon conditions met (TalRasha {TalRashaStacks} stacks && We are on {Core.Buffs.ConventionElement}!)");
                            return(Archon());
                        }

                        if (Core.Buffs.ConventionElement == Element.Fire && TalRashaStacks >= 2)
                        {
                            Core.Logger.Log(LogCategory.Spells, $"Archon conditions met (TalRasha {TalRashaStacks} stacks && We are on {Core.Buffs.ConventionElement}!)");
                            return(Archon());
                        }
                    }
                }
                if (!Legendary.ConventionOfElements.IsEquipped)
                {
                    if (Skills.Wizard.Archon.CanCast() && TalRashaStacks >= 2)
                    {
                        Core.Logger.Log(LogCategory.Spells, $"Archon conditions met (TalRasha {TalRashaStacks} stacks!)");
                        return(Archon());
                    }
                }
            }

            if (IsArchonActive)
            {
                target = null;
                var targetElite = TargetUtil.BestEliteInRange(75);
                var targett     = TargetUtil.BestRangedAoeUnit(30, 75, Settings.ClusterSize, true, true);
                var targettt    = TargetUtil.GetBestClusterUnit(30, 75, true, true, false, true) ?? CurrentTarget;
                if (targetElite != null)
                {
                    Core.Logger.Log(LogCategory.Targetting, $"Ladies first!");
                    return(ArchonDisintegrationWave(targetElite));
                }
                else if (targett != null)
                {
                    Core.Logger.Log(LogCategory.Targetting, $"Good hunts are second!");
                    return(ArchonDisintegrationWave(targett));
                }
                else
                {
                    Core.Logger.Log(LogCategory.Targetting, $"Archon Disintegration: Pew Pew Pew!");
                    return(ArchonDisintegrationWave(targettt));
                }
            }

            var lastBlackHolePosition = Vector3.Zero;

            power = null;
            if (Skills.Wizard.BlackHole.CanCast() && Core.Player.PrimaryResource > 18)
            {
                if (Skills.Wizard.BlackHole.CurrentRune == Runes.Wizard.Spellsteal || Skills.Wizard.BlackHole.CurrentRune == Runes.Wizard.AbsoluteZero)
                {
                    if (TargetUtil.NumMobsInRangeOfPosition(target.Position, 15) > Skills.Wizard.BlackHole.BuffStacks)
                    {
                        lastBlackHolePosition = target.Position;
                        {
                            Core.Logger.Log(LogCategory.Targetting, $"Placing BlackHole: Good place for stacks!");
                            return(BlackHole(target));
                        }
                    }
                }

                if (target.Position.Distance2D(lastBlackHolePosition) > 10)
                {
                    lastBlackHolePosition = target.Position;
                    {
                        Core.Logger.Log(LogCategory.Targetting, $"Placing BlackHole: The bomb has been planted!");
                        return(BlackHole(target));
                    }
                }

                if (Skills.Wizard.BlackHole.TimeSinceUse > 2000)
                {
                    lastBlackHolePosition = target.Position;
                    {
                        Core.Logger.Log(LogCategory.Targetting, $"Placing BlackHole: Well placed.");
                        return(BlackHole(target));
                    }
                }
            }

            if (TrySecondaryPower(out power))
            {
                Core.Logger.Log(LogCategory.Targetting, $"OffensivePower SecondaryPower");
                return(power);
            }

            if (TryPrimaryPower(out power))
            {
                Core.Logger.Log(LogCategory.Targetting, $"OffensivePower PrimaryPower");
                return(power);
            }
            return(Walk(CurrentTarget.Position));
        }