Пример #1
0
        internal static Vector3 SimpleUnstucker()
        {
            var   myPos    = Trinity.Player.Position;
            float rotation = Trinity.Player.Rotation;

            const double totalPoints = 2 * Math.PI;
            const double start       = 0;
            const double step        = Math.PI / 4;

            const float minDistance  = 10f;
            const float maxDistance  = 25f;
            const float stepDistance = 5f;

            HashSet <GridPoint> gridPoints = new HashSet <GridPoint>();

            int navigationObstacleFail = 0;

            for (double r = start; r <= totalPoints; r += step)
            {
                for (float d = minDistance; d <= maxDistance; d += stepDistance)
                {
                    float   newDirection = (float)(rotation + r);
                    Vector3 newPos       = MathEx.GetPointAt(myPos, d, newDirection);

                    if (!MainGridProvider.CanStandAt(MainGridProvider.WorldToGrid(newPos.ToVector2())))
                    {
                        continue;
                    }

                    // If this hits a known navigation obstacle, skip it
                    if (CacheData.NavigationObstacles.Any(o => MathEx.IntersectsPath(o.Position, o.Radius, myPos, newPos)))
                    {
                        navigationObstacleFail++;
                        continue;
                    }

                    // use distance as weight
                    gridPoints.Add(new GridPoint(newPos, (int)d, d));
                }
            }

            if (!gridPoints.Any())
            {
                Logger.LogDebug(LogCategory.UserInformation, "Unable to generate new unstucker position! navObsticle={0} - trying RANDOM point!", navigationObstacleFail);

                Random random    = new Random();
                int    distance  = random.Next(5, 30);
                float  direction = (float)random.NextDouble();

                return(MathEx.GetPointAt(myPos, distance, direction));
            }
            Navigator.Clear();

            var bestPoint = gridPoints.OrderByDescending(p => p.Weight).FirstOrDefault();

            Logger.LogDebug(LogCategory.UserInformation, "Generated Unstuck position {0} distance={1:0.0} navObsticle={2}",
                            NavHelper.PrettyPrintVector3(bestPoint.Position), bestPoint.Distance, navigationObstacleFail);
            return(bestPoint.Position);
        }
Пример #2
0
 public override string ToString()
 {
     return
         (String.Format("power={0} pos={1} acdGuid={2} preWait={3} postWait={4} timeSinceAssigned={5} timeSinceUse={6} range={7}",
                        SNOPower,
                        NavHelper.PrettyPrintVector3(TargetPosition),
                        TargetACDGUID,
                        WaitTicksBeforeUse,
                        WaitTicksAfterUse,
                        TimeSinceAssigned,
                        TimeSinceUse,
                        MinimumRange));
 }
Пример #3
0
        /// <summary>
        /// Finds an optimal position for using Monk Tempest Rush out of combat
        /// </summary>
        /// <returns></returns>
        internal static Vector3 FindTempestRushTarget()
        {
            Vector3 target = PlayerMover.LastMoveToTarget;
            Vector3 myPos  = ZetaDia.Me.Position;

            if (Trinity.CurrentTarget != null && NavHelper.CanRayCast(myPos, target))
            {
                target = Trinity.CurrentTarget.Position;
            }

            float distance = target.Distance2D(myPos);

            if (distance < 30f)
            {
                double direction = MathUtil.FindDirectionRadian(myPos, target);
                target = MathEx.GetPointAt(myPos, 40f, (float)direction);
            }

            return(target);
        }
Пример #4
0
        /// <summary>
        /// Special handling for whether or not we want to cache an object that's not in LoS
        /// </summary>
        /// <param name="c_diaObject"></param>
        /// <param name="AddToCache"></param>
        /// <returns></returns>
        private static bool RefreshStepIgnoreLoS(bool AddToCache = false)
        {
            try
            {
                if (CurrentCacheObject.Type == GObjectType.Item || CurrentCacheObject.Type == GObjectType.Gold)
                {
                    return(true);
                }

                if (!DataDictionary.AlwaysRaycastWorlds.Contains(Trinity.Player.WorldID))
                {
                    // Bounty Objectives should always be on the weight list
                    if (CurrentCacheObject.IsBountyObjective)
                    {
                        return(true);
                    }

                    // Quest Monsters should get LoS white-listed
                    if (CurrentCacheObject.IsQuestMonster)
                    {
                        return(true);
                    }

                    // Always LoS Units during events
                    if (CurrentCacheObject.Type == GObjectType.Unit && Player.InActiveEvent)
                    {
                        return(true);
                    }
                }
                // Everything except items and the current target
                if (CurrentCacheObject.RActorGuid != LastTargetRactorGUID && CurrentCacheObject.Type != GObjectType.Unknown)
                {
                    if (CurrentCacheObject.Distance < 95)
                    {
                        using (new PerformanceLogger("RefreshLoS.2"))
                        {
                            // Get whether or not this RActor has ever been in a path line with AllowWalk. If it hasn't, don't add to cache and keep rechecking
                            if (!CacheData.HasBeenRayCasted.TryGetValue(CurrentCacheObject.RActorGuid, out c_HasBeenRaycastable) || DataDictionary.AlwaysRaycastWorlds.Contains(Trinity.Player.WorldID))
                            {
                                if (CurrentCacheObject.Distance >= 1f && CurrentCacheObject.Distance <= 5f)
                                {
                                    c_HasBeenRaycastable = true;
                                    if (!CacheData.HasBeenRayCasted.ContainsKey(CurrentCacheObject.RActorGuid))
                                    {
                                        CacheData.HasBeenRayCasted.Add(CurrentCacheObject.RActorGuid, c_HasBeenRaycastable);
                                    }
                                }
                                else if (Settings.Combat.Misc.UseNavMeshTargeting)
                                {
                                    Vector3 myPos = new Vector3(Player.Position.X, Player.Position.Y, Player.Position.Z + 8f);
                                    Vector3 cPos  = new Vector3(CurrentCacheObject.Position.X, CurrentCacheObject.Position.Y, CurrentCacheObject.Position.Z + 8f);
                                    cPos = MathEx.CalculatePointFrom(cPos, myPos, CurrentCacheObject.Radius + 1f);

                                    if (Single.IsNaN(cPos.X) || Single.IsNaN(cPos.Y) || Single.IsNaN(cPos.Z))
                                    {
                                        cPos = CurrentCacheObject.Position;
                                    }

                                    if (!NavHelper.CanRayCast(myPos, cPos))
                                    {
                                        AddToCache      = false;
                                        c_IgnoreSubStep = "UnableToRayCast";
                                    }
                                    else
                                    {
                                        c_HasBeenRaycastable = true;
                                        if (!CacheData.HasBeenRayCasted.ContainsKey(CurrentCacheObject.RActorGuid))
                                        {
                                            CacheData.HasBeenRayCasted.Add(CurrentCacheObject.RActorGuid, c_HasBeenRaycastable);
                                        }
                                    }
                                }
                                else
                                {
                                    if (c_ZDiff > 14f)
                                    {
                                        AddToCache      = false;
                                        c_IgnoreSubStep = "LoS.ZDiff";
                                    }
                                    else
                                    {
                                        c_HasBeenRaycastable = true;
                                        if (!CacheData.HasBeenRayCasted.ContainsKey(CurrentCacheObject.RActorGuid))
                                        {
                                            CacheData.HasBeenRayCasted.Add(CurrentCacheObject.RActorGuid, c_HasBeenRaycastable);
                                        }
                                    }
                                }
                            }
                        }
                        using (new PerformanceLogger("RefreshLoS.3"))
                        {
                            // Get whether or not this RActor has ever been in "Line of Sight" (as determined by Demonbuddy). If it hasn't, don't add to cache and keep rechecking
                            if (!CacheData.HasBeenInLoS.TryGetValue(CurrentCacheObject.RActorGuid, out c_HasBeenInLoS) || DataDictionary.AlwaysRaycastWorlds.Contains(Trinity.Player.WorldID))
                            {
                                // Ignore units not in LoS except bosses
                                if (!CurrentCacheObject.IsBoss && !c_diaObject.InLineOfSight)
                                {
                                    AddToCache      = false;
                                    c_IgnoreSubStep = "NotInLoS";
                                }
                                else
                                {
                                    c_HasBeenInLoS = true;
                                    if (!CacheData.HasBeenInLoS.ContainsKey(CurrentCacheObject.RActorGuid))
                                    {
                                        CacheData.HasBeenInLoS.Add(CurrentCacheObject.RActorGuid, c_HasBeenInLoS);
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        AddToCache      = false;
                        c_IgnoreSubStep = "LoS-OutOfRange";
                    }


                    // always set true for bosses nearby
                    if (CurrentCacheObject.IsBoss || CurrentCacheObject.IsQuestMonster || CurrentCacheObject.IsBountyObjective)
                    {
                        AddToCache      = true;
                        c_IgnoreSubStep = "";
                    }
                    // always take the current target even if not in LoS
                    if (CurrentCacheObject.RActorGuid == LastTargetRactorGUID)
                    {
                        AddToCache      = true;
                        c_IgnoreSubStep = "";
                    }
                }

                // Simple whitelist for LoS
                if (DataDictionary.LineOfSightWhitelist.Contains(CurrentCacheObject.ActorSNO))
                {
                    AddToCache      = true;
                    c_IgnoreSubStep = "";
                }
                // Always pickup Infernal Keys whether or not in LoS
                if (DataDictionary.ForceToItemOverrideIds.Contains(CurrentCacheObject.ActorSNO))
                {
                    AddToCache      = true;
                    c_IgnoreSubStep = "";
                }
            }
            catch (Exception ex)
            {
                AddToCache      = true;
                c_IgnoreSubStep = "IgnoreLoSException";
                Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "{0}", ex);
            }
            return(AddToCache);
        }
Пример #5
0
        private static void Monk_MaintainTempestRush()
        {
            if (!Monk_TempestRushReady())
            {
                return;
            }

            if (Player.IsInTown || Zeta.Bot.Logic.BrainBehavior.IsVendoring)
            {
                return;
            }

            if (TownRun.IsTryingToTownPortal())
            {
                return;
            }

            if (TimeSinceUse(SNOPower.Monk_TempestRush) > 150)
            {
                return;
            }

            bool shouldMaintain = false;
            bool nullTarget     = CurrentTarget == null;

            if (!nullTarget)
            {
                // maintain for everything except items, doors, interactables... stuff we have to "click" on
                switch (CurrentTarget.Type)
                {
                case GObjectType.Unit:
                case GObjectType.Gold:
                case GObjectType.Avoidance:
                case GObjectType.Barricade:
                case GObjectType.Destructible:
                case GObjectType.HealthGlobe:
                case GObjectType.PowerGlobe:
                {
                    if (Settings.Combat.Monk.TROption == TempestRushOption.TrashOnly &&
                        (TargetUtil.AnyElitesInRange(40f) || CurrentTarget.IsBossOrEliteRareUnique))
                    {
                        shouldMaintain = false;
                    }
                    else
                    {
                        shouldMaintain = true;
                    }
                }
                break;
                }
            }
            else
            {
                shouldMaintain = true;
            }

            if (Settings.Combat.Monk.TROption != TempestRushOption.MovementOnly && SNOPowerUseTimer(SNOPower.Monk_TempestRush) && shouldMaintain)
            {
                Vector3 target = LastTempestRushLocation;

                const string locationSource = "LastLocation";

                if (target.Distance2D(ZetaDia.Me.Position) <= 1f)
                {
                    // rrrix edit: we can't maintain here
                    return;
                }

                if (target == Vector3.Zero)
                {
                    return;
                }

                float DestinationDistance = target.Distance2D(ZetaDia.Me.Position);

                target = TargetUtil.FindTempestRushTarget();

                if (DestinationDistance > 10f && NavHelper.CanRayCast(ZetaDia.Me.Position, target))
                {
                    Monk_TempestRushStatus(String.Format("Using Tempest Rush to maintain channeling, source={0}, V3={1} dist={2:0}", locationSource, target, DestinationDistance));

                    var usePowerResult = ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, target, CurrentWorldDynamicId, -1);
                    if (usePowerResult)
                    {
                        CacheData.AbilityLastUsed[SNOPower.Monk_TempestRush] = DateTime.UtcNow;
                    }
                }
            }
        }
Пример #6
0
        private static TrinityPower GetDemonHunterPower(bool IsCurrentlyAvoiding, bool UseOOCBuff, bool UseDestructiblePower)
        {
            // Pick the best destructible power available
            if (UseDestructiblePower)
            {
                return(GetDemonHunterDestroyPower());
            }

            MinEnergyReserve = 25;

            //Kridershot InternalName=x1_bow_norm_unique_09-137 GameBalanceID=1999595351 ItemLink: {c:ffff8000}Kridershot{/c}
            bool hasKridershot = ZetaDia.Me.Inventory.Equipped.Any(i => i.GameBalanceId == 1999595351);

            bool hasPunishment = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 0);

            // Spam Shadow Power
            if (Settings.Combat.DemonHunter.SpamShadowPower && CombatBase.CanCast(SNOPower.DemonHunter_ShadowPower) && !Player.IsIncapacitated &&
                (!GetHasBuff(SNOPower.DemonHunter_ShadowPower) || Player.CurrentHealthPct <= PlayerEmergencyHealthPotionLimit) && // if we don't have the buff or our health is low
                ((!hasPunishment && Player.SecondaryResource >= 14) || (hasPunishment && Player.SecondaryResource >= 39)) &&      // Save some Discipline for Preparation
                (Settings.Combat.DemonHunter.SpamShadowPower && Player.SecondaryResource >= 28))                                  // When spamming Shadow Power, save some Discipline for emergencies

            {
                return(new TrinityPower(SNOPower.DemonHunter_ShadowPower));
            }

            // NotSpam Shadow Power
            if (!UseOOCBuff && !Settings.Combat.DemonHunter.SpamShadowPower && CombatBase.CanCast(SNOPower.DemonHunter_ShadowPower) && !Player.IsIncapacitated &&
                (!GetHasBuff(SNOPower.DemonHunter_ShadowPower) || Player.CurrentHealthPct <= PlayerEmergencyHealthPotionLimit) && // if we don't have the buff or our health is low
                ((!hasPunishment && Player.SecondaryResource >= 14) || (hasPunishment && Player.SecondaryResource >= 39)) &&      // Save some Discipline for Preparation
                (Player.CurrentHealthPct < 1f || Player.IsRooted || TargetUtil.AnyMobsInRange(15)))
            {
                return(new TrinityPower(SNOPower.DemonHunter_ShadowPower));
            }

            // Vengeance
            if (!IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.X1_DemonHunter_Vengeance, CombatBase.CanCastFlags.NoTimer) &&
                ((!Settings.Combat.DemonHunter.VengeanceElitesOnly && TargetUtil.AnyMobsInRange(60, 6)) || TargetUtil.IsEliteTargetInRange(60f)))
            {
                return(new TrinityPower(SNOPower.X1_DemonHunter_Vengeance));
            }

            // Smoke Screen
            if ((!UseOOCBuff || Settings.Combat.DemonHunter.SpamSmokeScreen) && CombatBase.CanCast(SNOPower.DemonHunter_SmokeScreen) &&
                !GetHasBuff(SNOPower.DemonHunter_ShadowPower) && Player.SecondaryResource >= 14 &&
                (
                    (Player.CurrentHealthPct <= 0.50 || Player.IsRooted || TargetUtil.AnyMobsInRange(15) || Player.IsIncapacitated) ||
                    Settings.Combat.DemonHunter.SpamSmokeScreen
                ))
            {
                return(new TrinityPower(SNOPower.DemonHunter_SmokeScreen, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 1, 1));
            }


            int sentryCoolDown = SpellHistory.SpellUseCountInTime(SNOPower.DemonHunter_Sentry, TimeSpan.FromSeconds(24)) >= 4 ? 12 : 6;

            // Sentry Turret
            if (!UseOOCBuff && !Player.IsIncapacitated && CombatBase.CanCast(SNOPower.DemonHunter_Sentry, CombatBase.CanCastFlags.NoTimer) &&
                (TargetUtil.AnyElitesInRange(50) || TargetUtil.AnyMobsInRange(50, 2) || TargetUtil.IsEliteTargetInRange(50)) &&
                Player.PrimaryResource >= 30 &&
                (SpellHistory.TimeSinceUse(SNOPower.DemonHunter_Sentry) > TimeSpan.FromSeconds(sentryCoolDown) || SpellHistory.DistanceFromLastUsePosition(SNOPower.DemonHunter_Sentry) > 7.5))
            {
                return(new TrinityPower(SNOPower.DemonHunter_Sentry, 65f, TargetUtil.GetBestClusterPoint()));
            }

            // Caltrops
            if (!UseOOCBuff && !Player.IsIncapacitated && CombatBase.CanCast(SNOPower.DemonHunter_Caltrops) &&
                Player.SecondaryResource >= 6 && TargetUtil.AnyMobsInRange(40))
            {
                return(new TrinityPower(SNOPower.DemonHunter_Caltrops, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 1, 1));
            }


            // Preperation: restore 30 disc / 45 sec cd
            // Invigoration = 1 : passive + 15 disc / 45 sec cd
            // Punishment   = 0 : Cost: 25 disc, restore 75 hatred / NO COOLDOWN
            // Battle Scars = 3 : instantly restore 40% health + restore 30 disc / 45 sec cd
            // Focused Mind = 2 : restore 45 disc over 15 sec / 45 sec cd
            // Backup plan  = 4 : 30% chance Prep has no cooldown (remove cast timer)

            // Restore 75 hatred for 25 disc - NO COOLDOWN!
            //bool hasPunishment = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 0);
            bool hasInvigoration = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 1);
            bool hasBattleScars  = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 3);
            bool hasFocusedMind  = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 2);
            bool hasBackupPlan   = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_Preparation && s.RuneIndex == 4);

            float preperationTriggerRange = V.F("DemonHunter.PreperationTriggerRange");

            if (((!UseOOCBuff && !Player.IsIncapacitated &&
                  (TargetUtil.AnyMobsInRange(preperationTriggerRange))) || Settings.Combat.DemonHunter.SpamPreparation || hasPunishment) &&
                Hotbar.Contains(SNOPower.DemonHunter_Preparation))
            {
                // Preperation w/ Punishment
                if (hasPunishment && CombatBase.CanCast(SNOPower.DemonHunter_Preparation, CombatBase.CanCastFlags.NoTimer) &&
                    Player.SecondaryResource >= 25 && Player.PrimaryResourceMissing >= 75 && TimeSinceUse(SNOPower.DemonHunter_Preparation) >= 1000)
                {
                    return(new TrinityPower(SNOPower.DemonHunter_Preparation));
                }

                // Preperation w/ Battle Scars - check for health only
                if (hasBattleScars && CombatBase.CanCast(SNOPower.DemonHunter_Preparation) && Player.CurrentHealthPct < 0.6)
                {
                    return(new TrinityPower(SNOPower.DemonHunter_Preparation));
                }

                // no rune || invigoration || focused mind || Backup Plan || Battle Scars (need Disc)
                if ((!hasPunishment) && CombatBase.CanCast(SNOPower.DemonHunter_Preparation) && Player.SecondaryResource <= 15 && TimeSinceUse(SNOPower.DemonHunter_Preparation) >= 1000)
                {
                    return(new TrinityPower(SNOPower.DemonHunter_Preparation));
                }
            }


            // SOURCE: Stede - http://www.thebuddyforum.com/demonbuddy-forum/plugins/trinity/155398-trinity-dh-companion-use-abilities.html#post1445496

            // Companion On-Use Abilities Added in 2.0
            bool hasSpider = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 0);
            bool hasBat    = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 3);
            bool hasBoar   = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 1);
            bool hasFerret = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 4);
            bool hasWolf   = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.X1_DemonHunter_Companion && s.RuneIndex == 2);

            if (!UseOOCBuff && !Player.IsIncapacitated && Hotbar.Contains(SNOPower.X1_DemonHunter_Companion))
            {
                // Use Spider Slow on 4 or more trash mobs in an area or on Unique/Elite/Champion
                if (hasSpider && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) && TargetUtil.ClusterExists(25f, 4) && TargetUtil.EliteOrTrashInRange(25f))
                {
                    return(new TrinityPower(SNOPower.X1_DemonHunter_Companion));
                }

                //Use Bat when Hatred is Needed
                if (hasBat && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) && Player.PrimaryResourceMissing >= 60)
                {
                    return(new TrinityPower(SNOPower.X1_DemonHunter_Companion));
                }

                // Use Boar Taunt on 3 or more trash mobs in an area or on Unique/Elite/Champion
                if (hasBoar && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) && ((TargetUtil.ClusterExists(20f, 4) && TargetUtil.EliteOrTrashInRange(20f)) ||
                                                                                         (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.Distance <= 20f)))
                {
                    return(new TrinityPower(SNOPower.X1_DemonHunter_Companion));
                }

                // Ferrets used for picking up Health Globes when low on Health
                if (hasFerret && ObjectCache.Any(o => o.Type == GObjectType.HealthGlobe && o.Distance < 60f) && Player.CurrentHealthPct < PlayerEmergencyHealthPotionLimit)
                {
                    return(new TrinityPower(SNOPower.X1_DemonHunter_Companion));
                }

                // 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 (hasWolf && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) &&
                    (TargetUtil.AnyMobsInRange(20) || (CurrentTarget.IsBossOrEliteRareUnique && CurrentTarget.RadiusDistance < 55f)))
                {
                    return(new TrinityPower(SNOPower.X1_DemonHunter_Companion));
                }
            }

            //skillDict.Add("EvasiveFire", SNOPower.DemonHunter_EvasiveFire);
            //runeDict.Add("Hardened", 0);
            //runeDict.Add("PartingGift", 2);
            //runeDict.Add("CoveringFire", 1);
            //runeDict.Add("Displace", 4);
            //runeDict.Add("Surge", 3);


            // Companion
            if (!Player.IsIncapacitated && CombatBase.CanCast(SNOPower.X1_DemonHunter_Companion) && TargetUtil.EliteOrTrashInRange(30f) &&
                Player.SecondaryResource >= 10)
            {
                return(new TrinityPower(SNOPower.X1_DemonHunter_Companion, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 2, 1));
            }

            // Marked for Death
            if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_MarkedForDeath) &&
                Player.SecondaryResource >= 3 &&
                (TargetUtil.AnyElitesInRange(40) || TargetUtil.AnyMobsInRange(40, 3) ||

                 ((CurrentTarget.IsEliteRareUnique || CurrentTarget.IsTreasureGoblin || CurrentTarget.IsBoss) &&
                  CurrentTarget.Radius <= 40 && CurrentTarget.RadiusDistance <= 40f)))
            {
                return(new TrinityPower(SNOPower.DemonHunter_MarkedForDeath, 40f, Vector3.Zero, CurrentWorldDynamicId, CurrentTarget.ACDGuid, 1, 1));
            }

            // Vault
            if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_Vault) && !Player.IsRooted && !Player.IsIncapacitated &&
                Settings.Combat.DemonHunter.VaultMode != Config.Combat.DemonHunterVaultMode.MovementOnly &&
                // Only use vault to retreat if < level 60, or if in inferno difficulty for level 60's
                (Player.Level < 60 || Player.GameDifficulty > GameDifficulty.Master) &&
                (CurrentTarget.RadiusDistance <= 10f || TargetUtil.AnyMobsInRange(10)) &&
                // if we have ShadowPower and Disicpline is >= 16
                // or if we don't have ShadoWpower and Discipline is >= 22
                (Player.SecondaryResource >= (Hotbar.Contains(SNOPower.DemonHunter_ShadowPower) ? 22 : 16)) &&
                TimeSinceUse(SNOPower.DemonHunter_Vault) >= Trinity.Settings.Combat.DemonHunter.VaultMovementDelay)
            {
                //Vector3 vNewTarget = MathEx.CalculatePointFrom(CurrentTarget.Position, Player.Position, -15f);
                // Lets find a smarter Vault position instead of just "backwards"
                //Vector3 vNewTarget = NavHelper.FindSafeZone(Trinity.Player.Position, true, false, null, false);
                Vector3 vNewTarget = NavHelper.MainFindSafeZone(Trinity.Player.Position, true, false, null, false);

                return(new TrinityPower(SNOPower.DemonHunter_Vault, 20f, vNewTarget, CurrentWorldDynamicId, -1, 1, 2));
            }

            // Rain of Vengeance
            if (!UseOOCBuff && CombatBase.CanCast(SNOPower.DemonHunter_RainOfVengeance) && !Player.IsIncapacitated &&
                (TargetUtil.ClusterExists(45f, 3) || TargetUtil.EliteOrTrashInRange(45f)))
            {
                var bestClusterPoint = TargetUtil.GetBestClusterPoint(45f, 65f, false, true);

                return(new TrinityPower(SNOPower.DemonHunter_RainOfVengeance, 0f, bestClusterPoint, CurrentWorldDynamicId, -1, 1, 1));
            }

            // Cluster Arrow
            if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_ClusterArrow) && !Player.IsIncapacitated &&
                Player.PrimaryResource >= 50)
            {
                return(new TrinityPower(SNOPower.DemonHunter_ClusterArrow, V.F("DemonHunter.ClusterArrow.UseRange"), CurrentTarget.ACDGuid));
            }

            // Multi Shot
            if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_Multishot) && !Player.IsIncapacitated &&
                Player.PrimaryResource >= 30 &&
                (TargetUtil.AnyMobsInRange(40, 2) || CurrentTarget.IsBossOrEliteRareUnique || CurrentTarget.IsTreasureGoblin))
            {
                return(new TrinityPower(SNOPower.DemonHunter_Multishot, 30f, CurrentTarget.Position, CurrentWorldDynamicId, -1, 1, 1));
            }

            // Fan of Knives
            if (!UseOOCBuff && CombatBase.CanCast(SNOPower.DemonHunter_FanOfKnives) && !Player.IsIncapacitated &&
                (TargetUtil.EliteOrTrashInRange(15) || TargetUtil.AnyTrashInRange(15f, 5, false)))
            {
                return(new TrinityPower(SNOPower.DemonHunter_FanOfKnives, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 1, 1));
            }

            // Strafe spam - similar to barbarian whirlwind routine
            if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_Strafe, CombatBase.CanCastFlags.NoTimer) &&
                !Player.IsIncapacitated && !Player.IsRooted && Player.PrimaryResource >= Settings.Combat.DemonHunter.StrafeMinHatred)
            {
                bool shouldGetNewZigZag =
                    (DateTime.UtcNow.Subtract(Trinity.LastChangedZigZag).TotalMilliseconds >= V.I("Barbarian.Whirlwind.ZigZagMaxTime") ||
                     CurrentTarget.ACDGuid != Trinity.LastZigZagUnitAcdGuid ||
                     CombatBase.ZigZagPosition.Distance2D(Player.Position) <= 5f);

                if (shouldGetNewZigZag)
                {
                    var wwdist = V.F("Barbarian.Whirlwind.ZigZagDistance");

                    CombatBase.ZigZagPosition = TargetUtil.GetZigZagTarget(CurrentTarget.Position, wwdist);

                    Trinity.LastZigZagUnitAcdGuid = CurrentTarget.ACDGuid;
                    Trinity.LastChangedZigZag     = DateTime.UtcNow;
                }

                int postCastTickDelay = TrinityPower.MillisecondsToTickDelay(250);

                return(new TrinityPower(SNOPower.DemonHunter_Strafe, 15f, CombatBase.ZigZagPosition, CurrentWorldDynamicId, -1, 0, postCastTickDelay));
            }

            // Spike Trap
            if (!UseOOCBuff && !Player.IsIncapacitated && CombatBase.CanCast(SNOPower.DemonHunter_SpikeTrap) &&
                LastPowerUsed != SNOPower.DemonHunter_SpikeTrap && Player.PrimaryResource >= 30)
            {
                // For distant monsters, try to target a little bit in-front of them (as they run towards us), if it's not a treasure goblin
                float fExtraDistance = 0f;
                if (CurrentTarget.Distance > 17f && !CurrentTarget.IsTreasureGoblin)
                {
                    fExtraDistance = CurrentTarget.Distance - 17f;
                    if (fExtraDistance > 5f)
                    {
                        fExtraDistance = 5f;
                    }
                    if (CurrentTarget.Distance - fExtraDistance < 15f)
                    {
                        fExtraDistance -= 2;
                    }
                }
                Vector3 vNewTarget = MathEx.CalculatePointFrom(CurrentTarget.Position, Player.Position, CurrentTarget.Distance - fExtraDistance);
                return(new TrinityPower(SNOPower.DemonHunter_SpikeTrap, 35f, vNewTarget, CurrentWorldDynamicId, -1, 1, 1));
            }

            //skillDict.Add("ElementalArrow", SNOPower.DemonHunter_ElementalArrow);
            //runeDict.Add("BallLightning", 1);
            //runeDict.Add("FrostArrow", 0);
            //runeDict.Add("ScreamingSkull", 2);
            //runeDict.Add("LightningBolts", 4);
            //runeDict.Add("NetherTentacles", 3);

            var hasBallLightning   = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 1);
            var hasFrostArrow      = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 0);
            var hasScreamingSkull  = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 2);
            var hasLightningBolts  = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 4);
            var hasNetherTentacles = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 3);

            // Elemental Arrow
            if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_ElementalArrow) &&
                SNOPowerUseTimer(SNOPower.DemonHunter_ElementalArrow) && !Player.IsIncapacitated &&
                ((Player.PrimaryResource >= 10 && !Player.WaitingForReserveEnergy) || Player.PrimaryResource >= MinEnergyReserve || hasKridershot))
            {
                return(new TrinityPower(SNOPower.DemonHunter_ElementalArrow, 65f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 1));
            }

            //skillDict.Add("Chakram", SNOPower.DemonHunter_Chakram);
            //runeDict.Add("TwinChakrams", 0);
            //runeDict.Add("Serpentine", 2);
            //runeDict.Add("RazorDisk", 3);
            //runeDict.Add("Boomerang", 1);
            //runeDict.Add("ShurikenCloud", 4);

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

            // Chakram normal attack
            if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Chakram) && !Player.IsIncapacitated &&
                !hasShurikenCloud &&
                ((Player.PrimaryResource >= 10 && !Player.WaitingForReserveEnergy) || Player.PrimaryResource >= MinEnergyReserve))
            {
                return(new TrinityPower(SNOPower.DemonHunter_Chakram, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 1));
            }

            // Chakram:Shuriken Cloud
            if (!Player.IsInTown && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Chakram) && !Player.IsIncapacitated &&
                hasShurikenCloud && TimeSinceUse(SNOPower.DemonHunter_Chakram) >= 110000 &&
                ((Player.PrimaryResource >= 10 && !Player.WaitingForReserveEnergy) || Player.PrimaryResource >= MinEnergyReserve))
            {
                return(new TrinityPower(SNOPower.DemonHunter_Chakram, 0f, Vector3.Zero, CurrentWorldDynamicId, -1, 2, 2));
            }

            // Rapid Fire
            if (!UseOOCBuff && !IsCurrentlyAvoiding && CombatBase.CanCast(SNOPower.DemonHunter_RapidFire, CombatBase.CanCastFlags.NoTimer) &&
                !Player.IsIncapacitated && Player.PrimaryResource >= 16 &&
                (Player.PrimaryResource >= Settings.Combat.DemonHunter.RapidFireMinHatred || CombatBase.LastPowerUsed == SNOPower.DemonHunter_RapidFire))
            {
                // Players with grenades *AND* rapid fire should spam grenades at close-range instead
                if (Hotbar.Contains(SNOPower.DemonHunter_Grenades) && CurrentTarget.RadiusDistance <= 18f)
                {
                    return(new TrinityPower(SNOPower.DemonHunter_Grenades, 18f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 0));
                }
                // Now return rapid fire, if not sending grenades instead
                return(new TrinityPower(SNOPower.DemonHunter_RapidFire, 40f, CurrentTarget.Position));
            }

            // Impale
            if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Impale) && !Player.IsIncapacitated &&
                (!TargetUtil.AnyMobsInRange(12, 4)) &&
                ((Player.PrimaryResource >= 25 && !Player.WaitingForReserveEnergy) || Player.PrimaryResource >= MinEnergyReserve) &&
                CurrentTarget.RadiusDistance <= 50f)
            {
                return(new TrinityPower(SNOPower.DemonHunter_Impale, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 1));
            }

            // Evasive Fire
            if (!UseOOCBuff && CombatBase.CanCast(SNOPower.X1_DemonHunter_EvasiveFire) && !Player.IsIncapacitated &&
                (TargetUtil.AnyMobsInRange(10f) || DemonHunter_HasNoPrimary()))
            {
                float range = DemonHunter_HasNoPrimary() ? 70f : 0f;

                return(new TrinityPower(SNOPower.X1_DemonHunter_EvasiveFire, range, Vector3.Zero, -1, CurrentTarget.ACDGuid, 1, 1));
            }

            // Hungering Arrow
            if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_HungeringArrow) && !Player.IsIncapacitated)
            {
                return(new TrinityPower(SNOPower.DemonHunter_HungeringArrow, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 0));
            }

            // Entangling shot
            if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.X1_DemonHunter_EntanglingShot) && !Player.IsIncapacitated)
            {
                return(new TrinityPower(SNOPower.X1_DemonHunter_EntanglingShot, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 0));
            }

            // Bola Shot
            if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Bolas) && !Player.IsIncapacitated)
            {
                return(new TrinityPower(SNOPower.DemonHunter_Bolas, 50f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 0, 1));
            }

            // Grenades
            if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Grenades) && !Player.IsIncapacitated)
            {
                return(new TrinityPower(SNOPower.DemonHunter_Grenades, 40f, Vector3.Zero, -1, CurrentTarget.ACDGuid, 5, 5));
            }

            // Default attacks
            return(CombatBase.DefaultPower);
        }
Пример #7
0
        private static void RefreshSetKiting(ref Vector3 vKitePointAvoid, bool NeedToKite)
        {
            using (new PerformanceLogger("RefreshDiaObjectCache.Kiting"))
            {
                bool TryToKite = false;

                List <TrinityCacheObject> kiteMonsterList = new List <TrinityCacheObject>();

                if (CurrentTarget != null && CurrentTarget.IsUnit)
                {
                    switch (CombatBase.KiteMode)
                    {
                    case KiteMode.Never:
                        break;

                    case KiteMode.Elites:
                        kiteMonsterList = (from m in ObjectCache
                                           where m.IsUnit &&
                                           m.RadiusDistance > 0 &&
                                           m.RadiusDistance <= CombatBase.KiteDistance &&
                                           m.IsBossOrEliteRareUnique
                                           select m).ToList();
                        break;

                    case KiteMode.Bosses:
                        kiteMonsterList = (from m in ObjectCache
                                           where m.IsUnit &&
                                           m.RadiusDistance > 0 &&
                                           m.RadiusDistance <= CombatBase.KiteDistance &&
                                           m.IsBoss
                                           select m).ToList();
                        break;

                    case KiteMode.Always:
                        kiteMonsterList = (from m in ObjectCache
                                           where m.IsUnit &&
                                           m.Weight > 0 &&
                                           m.RadiusDistance > 0 &&
                                           m.RadiusDistance <= CombatBase.KiteDistance &&
                                           (m.IsBossOrEliteRareUnique ||
                                            ((m.HitPointsPct >= .15 || m.MonsterSize != MonsterSize.Swarm) && !m.IsBossOrEliteRareUnique))
                                           select m).ToList();
                        break;
                    }
                }
                if (kiteMonsterList.Any())
                {
                    TryToKite       = true;
                    vKitePointAvoid = Player.Position;
                }

                if (CombatBase.KiteDistance > 0 && kiteMonsterList.Count() > 0 && IsWizardShouldKite())
                {
                    TryToKite       = true;
                    vKitePointAvoid = Player.Position;
                }

                // Avoid Death
                if (Settings.Combat.Misc.AvoidDeath &&
                    Player.CurrentHealthPct <= CombatBase.EmergencyHealthPotionLimit && // health is lower than potion limit
                    !SNOPowerUseTimer(SNOPower.DrinkHealthPotion) &&                    // we can't use a potion anymore
                    TargetUtil.AnyMobsInRange(90f, false))
                {
                    Logger.LogNormal("Attempting to avoid death!");
                    NeedToKite = true;

                    kiteMonsterList = (from m in ObjectCache
                                       where m.IsUnit
                                       select m).ToList();
                }

                // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin!
                bool shouldKamikazeTreasureGoblins = (!AnyTreasureGoblinsPresent || Settings.Combat.Misc.GoblinPriority <= GoblinPriority.Prioritize);

                double msCancelledEmergency = DateTime.UtcNow.Subtract(timeCancelledEmergencyMove).TotalMilliseconds;
                bool   shouldEmergencyMove  = msCancelledEmergency >= cancelledEmergencyMoveForMilliseconds && NeedToKite;

                double msCancelledKite = DateTime.UtcNow.Subtract(timeCancelledKiteMove).TotalMilliseconds;
                bool   shouldKite      = msCancelledKite >= cancelledKiteMoveForMilliseconds && TryToKite;

                if (shouldKamikazeTreasureGoblins && (shouldEmergencyMove || shouldKite))
                {
                    Vector3 vAnySafePoint = NavHelper.FindSafeZone(false, 1, vKitePointAvoid, true, kiteMonsterList, shouldEmergencyMove);

                    if (LastKitePosition == null)
                    {
                        LastKitePosition = new KitePosition()
                        {
                            PositionFoundTime = DateTime.UtcNow,
                            Position          = vAnySafePoint,
                            Distance          = vAnySafePoint.Distance(Player.Position)
                        };
                    }

                    if (vAnySafePoint != Vector3.Zero && vAnySafePoint.Distance(Player.Position) >= 1)
                    {
                        if ((DateTime.UtcNow.Subtract(LastKitePosition.PositionFoundTime).TotalMilliseconds > 3000 && LastKitePosition.Position == vAnySafePoint) ||
                            (CurrentTarget != null && DateTime.UtcNow.Subtract(lastGlobalCooldownUse).TotalMilliseconds > 1500 && TryToKite))
                        {
                            timeCancelledKiteMove            = DateTime.UtcNow;
                            cancelledKiteMoveForMilliseconds = 1500;
                            Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Kite movement failed, cancelling for {0:0}ms", cancelledKiteMoveForMilliseconds);
                            return;
                        }
                        else
                        {
                            LastKitePosition = new KitePosition()
                            {
                                PositionFoundTime = DateTime.UtcNow,
                                Position          = vAnySafePoint,
                                Distance          = vAnySafePoint.Distance(Player.Position)
                            };
                        }

                        if (Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                        {
                            Logger.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting to: {0} Distance: {1:0} Direction: {2:0}, Health%={3:0.00}, KiteDistance: {4:0}, Nearby Monsters: {5:0} NeedToKite: {6} TryToKite: {7}",
                                       vAnySafePoint, vAnySafePoint.Distance(Player.Position), MathUtil.GetHeading(MathUtil.FindDirectionDegree(Me.Position, vAnySafePoint)),
                                       Player.CurrentHealthPct, CombatBase.KiteDistance, kiteMonsterList.Count(),
                                       NeedToKite, TryToKite);
                        }
                        CurrentTarget = new TrinityCacheObject()
                        {
                            Position     = vAnySafePoint,
                            Type         = TrinityObjectType.Avoidance,
                            Weight       = 90000,
                            Distance     = Vector3.Distance(Player.Position, vAnySafePoint),
                            Radius       = 2f,
                            InternalName = "KitePoint"
                        };
                    }
                }
                else if (!shouldEmergencyMove && NeedToKite)
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Emergency movement cancelled for {0:0}ms", DateTime.UtcNow.Subtract(timeCancelledEmergencyMove).TotalMilliseconds - cancelledKiteMoveForMilliseconds);
                }
                else if (!shouldKite && TryToKite)
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Kite movement cancelled for {0:0}ms", DateTime.UtcNow.Subtract(timeCancelledKiteMove).TotalMilliseconds - cancelledKiteMoveForMilliseconds);
                }
            }
        }
Пример #8
0
        // Special Zig-Zag movement for whirlwind/tempest
        /// <summary>
        /// Finds an optimal position for Barbarian Whirlwind, Monk Tempest Rush, or Demon Hunter Strafe
        /// </summary>
        /// <param name="origin"></param>
        /// <param name="ringDistance"></param>
        /// <param name="randomizeDistance"></param>
        /// <returns></returns>
        internal static Vector3 GetZigZagTarget(Vector3 origin, float ringDistance, bool randomizeDistance = false)
        {
            var     minDistance      = 20f;
            Vector3 myPos            = Player.Position;
            float   distanceToTarget = origin.Distance2D(myPos);

            Vector3 zigZagPoint = origin;

            bool  useTargetBasedZigZag = false;
            float maxDistance          = 25f;
            int   minTargets           = 2;

            if (Trinity.Player.ActorClass == ActorClass.Monk)
            {
                maxDistance          = 20f;
                minTargets           = 3;
                useTargetBasedZigZag = Trinity.Settings.Combat.Monk.TargetBasedZigZag;
            }
            if (Trinity.Player.ActorClass == ActorClass.Barbarian)
            {
                useTargetBasedZigZag = Trinity.Settings.Combat.Barbarian.TargetBasedZigZag;
            }

            int  eliteCount         = ObjectCache.Count(u => u.IsUnit && u.IsBossOrEliteRareUnique);
            bool shouldZigZagElites = ((Trinity.CurrentTarget.IsBossOrEliteRareUnique && eliteCount > 1) || eliteCount == 0);

            if (useTargetBasedZigZag && shouldZigZagElites && !AnyTreasureGoblinsPresent && ObjectCache.Count(o => o.IsUnit) >= minTargets)
            {
                bool attackInAoe  = Trinity.Settings.Combat.Misc.KillMonstersInAoE;
                var  clusterPoint = TargetUtil.GetBestClusterPoint(ringDistance, ringDistance, false, attackInAoe);
                if (clusterPoint.Distance2D(Player.Position) >= minDistance)
                {
                    Logger.Log(LogCategory.Movement, "Returning ZigZag: BestClusterPoint {0} r-dist={1} t-dist={2}", clusterPoint, ringDistance, clusterPoint.Distance2D(Player.Position));
                    return(clusterPoint);
                }


                var zigZagTargetList = new List <TrinityCacheObject>();
                if (attackInAoe)
                {
                    zigZagTargetList =
                        (from u in ObjectCache
                         where u.IsUnit && u.Distance < maxDistance
                         select u).ToList();
                }
                else
                {
                    zigZagTargetList =
                        (from u in ObjectCache
                         where u.IsUnit && u.Distance < maxDistance && !UnitOrPathInAoE(u)
                         select u).ToList();
                }

                if (zigZagTargetList.Count() >= minTargets)
                {
                    zigZagPoint = zigZagTargetList.OrderByDescending(u => u.Distance).FirstOrDefault().Position;
                    if (NavHelper.CanRayCast(zigZagPoint) && zigZagPoint.Distance2D(Player.Position) >= minDistance)
                    {
                        Logger.Log(LogCategory.Movement, "Returning ZigZag: TargetBased {0} r-dist={1} t-dist={2}", zigZagPoint, ringDistance, zigZagPoint.Distance2D(Player.Position));
                        return(zigZagPoint);
                    }
                }
            }

            Random  rndNum             = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber));
            float   highestWeightFound = float.NegativeInfinity;
            Vector3 bestLocation       = origin;

            // the unit circle always starts at 0 :)
            double min = 0;
            // the maximum size of a unit circle
            double max = 2 * Math.PI;
            // the number of times we will iterate around the circle to find points
            double piSlices = 16;

            // We will do several "passes" to make sure we can get a point that we can least zig-zag to
            // The total number of points tested will be piSlices * distancePasses.Count
            List <float> distancePasses = new List <float>();

            distancePasses.Add(ringDistance * 1 / 2); // Do one loop at 1/2 distance
            distancePasses.Add(ringDistance * 3 / 4); // Do one loop at 3/4 distance
            distancePasses.Add(ringDistance);         // Do one loop at exact distance

            foreach (float distance in distancePasses)
            {
                for (double direction = min; direction < max; direction += (Math.PI / piSlices))
                {
                    // Starting weight is 1
                    float pointWeight = 1f;

                    // Find a new XY
                    zigZagPoint = MathEx.GetPointAt(origin, distance, (float)direction);
                    // Get the Z
                    zigZagPoint.Z = Trinity.MainGridProvider.GetHeight(zigZagPoint.ToVector2());

                    // Make sure we're actually zig-zagging our target, except if we're kiting

                    float targetCircle = CurrentTarget.Radius;
                    if (targetCircle <= 5f)
                    {
                        targetCircle = 5f;
                    }
                    if (targetCircle > 10f)
                    {
                        targetCircle = 10f;
                    }

                    bool intersectsPath = MathUtil.IntersectsPath(CurrentTarget.Position, targetCircle, myPos, zigZagPoint);
                    if (CombatBase.PlayerKiteDistance <= 0 && !intersectsPath)
                    {
                        continue;
                    }

                    // if we're kiting, lets not actualy run through monsters
                    if (CombatBase.PlayerKiteDistance > 0 && CacheData.MonsterObstacles.Any(m => m.Position.Distance(zigZagPoint) <= CombatBase.PlayerKiteDistance))
                    {
                        continue;
                    }

                    // Ignore point if any AoE in this point position
                    if (CacheData.TimeBoundAvoidance.Any(m => m.Position.Distance(zigZagPoint) <= m.Radius && Player.CurrentHealthPct <= AvoidanceManager.GetAvoidanceHealthBySNO(m.ActorSNO, 1)))
                    {
                        continue;
                    }

                    // Make sure this point is in LoS/walkable (not around corners or into a wall)
                    bool canRayCast = !Navigator.Raycast(Player.Position, zigZagPoint);
                    if (!canRayCast)
                    {
                        continue;
                    }

                    float distanceToPoint           = zigZagPoint.Distance2D(myPos);
                    float distanceFromTargetToPoint = zigZagPoint.Distance2D(origin);

                    // Lots of weight for points further away from us (e.g. behind our CurrentTarget)
                    pointWeight *= distanceToPoint;

                    // Add weight for any units in this point
                    int monsterCount = ObjectCache.Count(u => u.IsUnit && u.Position.Distance2D(zigZagPoint) <= Math.Max(u.Radius, 10f));
                    if (monsterCount > 0)
                    {
                        pointWeight *= monsterCount;
                    }

                    //Logger.Log(LogCategory.Movement, "ZigZag Point: {0} distance={1:0} distaceFromTarget={2:0} intersectsPath={3} weight={4:0} monsterCount={5}",
                    //    zigZagPoint, distanceToPoint, distanceFromTargetToPoint, intersectsPath, pointWeight, monsterCount);

                    // Use this one if it's more weight, or we haven't even found one yet, or if same weight as another with a random chance
                    if (pointWeight > highestWeightFound)
                    {
                        highestWeightFound = pointWeight;

                        if (Trinity.Settings.Combat.Misc.UseNavMeshTargeting)
                        {
                            bestLocation = new Vector3(zigZagPoint.X, zigZagPoint.Y, Trinity.MainGridProvider.GetHeight(zigZagPoint.ToVector2()));
                        }
                        else
                        {
                            bestLocation = new Vector3(zigZagPoint.X, zigZagPoint.Y, zigZagPoint.Z + 4);
                        }
                    }
                }
            }
            Logger.Log(LogCategory.Movement, "Returning ZigZag: RandomXY {0} r-dist={1} t-dist={2}", bestLocation, ringDistance, bestLocation.Distance2D(Player.Position));
            return(bestLocation);
        }
Пример #9
0
        /// <summary>
        /// This method will add and update necessary information about all available actors. Determines ObjectType, sets ranges, updates blacklists, determines avoidance, kiting, target weighting
        /// and the result is we will have a new target for the Target Handler. Returns true if the cache was refreshed.
        /// </summary>
        /// <returns>True if the cache was updated</returns>
        public static bool RefreshDiaObjectCache()
        {
            if (ZetaDia.Service.Hero == null)
            {
                Logger.LogError("Hero is null!");
                return(false);
            }

            //if (!ZetaDia.Service.Hero.IsValid)
            //{
            //    Logger.LogError("Hero is invalid!");
            //    return false;
            //}

            if (!ZetaDia.IsInGame)
            {
                return(false);
            }

            if (ZetaDia.IsLoadingWorld)
            {
                return(false);
            }

            if (!ZetaDia.Me.IsValid)
            {
                return(false);
            }

            if (!ZetaDia.Me.CommonData.IsValid)
            {
                return(false);
            }


            using (new PerformanceLogger("RefreshDiaObjectCache"))
            {
                LastRefreshedCache = DateTime.UtcNow;

                /*
                 *  Refresh the Cache
                 */
                using (new PerformanceLogger("RefreshDiaObjectCache.UpdateBlock"))
                {
                    CacheData.Clear();
                    GenericCache.MaintainCache();
                    GenericBlacklist.MaintainBlacklist();


                    if (Player.CurrentHealthPct <= 0)
                    {
                        return(false);
                    }

                    RefreshCacheInit();

                    // Now pull up all the data and store anything we want to handle in the super special cache list
                    // Also use many cache dictionaries to minimize DB<->D3 memory hits, and speed everything up a lot
                    RefreshCacheMainLoop();
                }

                /*
                 * Add Legendary & Set Minimap Markers to ObjectCache
                 */
                RefreshCacheMarkers();

                // Add Team HotSpots to the cache
                ObjectCache.AddRange(GroupHotSpots.GetCacheObjectHotSpots());

                /* Fire Chains Experimental Avoidance */
                if (Settings.Combat.Misc.UseExperimentalFireChainsAvoidance)
                {
                    const float fireChainSize = 5f;
                    foreach (var unit1 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains)))
                    {
                        foreach (var unit2 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains)).Where(unit2 => unit1.RActorGuid != unit2.RActorGuid))
                        {
                            for (float i = 0; i <= unit1.Position.Distance2D(unit2.Position); i += (fireChainSize / 4))
                            {
                                Vector3 fireChainSpot = MathEx.CalculatePointFrom(unit1.Position, unit2.Position, i);

                                if (Player.Position.Distance2D(fireChainSpot) <= fireChainSize)
                                {
                                    Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Fire Chains!");
                                    _standingInAvoidance = true;
                                }
                                CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(fireChainSpot, fireChainSize, -2, "FireChains"));
                            }
                        }
                        if (CacheData.TimeBoundAvoidance.Any(aoe => aoe.ActorSNO == -2))
                        {
                            Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generated {0} avoidance points for FireChains, minDistance={1} maxDistance={2}",
                                       CacheData.TimeBoundAvoidance.Count(aoe => aoe.ActorSNO == -2),
                                       CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2)
                                       .Min(aoe => aoe.Position.Distance2D(Player.Position)),
                                       CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2)
                                       .Max(aoe => aoe.Position.Distance2D(Player.Position)));
                        }
                    }
                }

                /* Beast Charge Experimental Avoidance */
                if (Settings.Combat.Misc.UseExperimentalSavageBeastAvoidance)
                {
                    const float beastChargePathWidth = 10f;
                    List <int>  chargerSnoList       = new List <int>();
                    foreach (var unit1 in ObjectCache.Where(u => objectIsCharging(u)))
                    {
                        Vector3 endPoint = MathEx.GetPointAt(unit1.Position, 90f, unit1.Unit.Movement.Rotation);

                        for (float i = 0; i <= unit1.Position.Distance2D(endPoint); i += (beastChargePathWidth / 4))
                        {
                            Vector3 pathSpot = MathEx.CalculatePointFrom(unit1.Position, endPoint, i);

                            Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement,
                                       "Generating Charge Avoidance: {0} dist: {1}",
                                       pathSpot, pathSpot.Distance2D(unit1.Position));

                            var minWidth = Math.Max(beastChargePathWidth, unit1.Radius) + 5f;

                            if (Player.Position.Distance2D(pathSpot) <= minWidth)
                            {
                                Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Charger!");
                                _standingInAvoidance = true;
                            }
                            CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(pathSpot, minWidth, unit1.ActorSNO,
                                                                                     "Charger"));

                            chargerSnoList.Add(unit1.ActorSNO);
                        }
                        if (CacheData.TimeBoundAvoidance.Any(aoe => chargerSnoList.Contains(aoe.ActorSNO)))
                        {
                            Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement,
                                       "Generated {0} avoidance points for BeastCharge, minDistance={1} maxDistance={2}",
                                       CacheData.TimeBoundAvoidance.Count(aoe => chargerSnoList.Contains(aoe.ActorSNO)),
                                       CacheData.TimeBoundAvoidance.Where(aoe => chargerSnoList.Contains(aoe.ActorSNO))
                                       .Min(aoe => aoe.Position.Distance2D(Player.Position)),
                                       CacheData.TimeBoundAvoidance.Where(aoe => chargerSnoList.Contains(aoe.ActorSNO))
                                       .Max(aoe => aoe.Position.Distance2D(Player.Position)));
                        }
                    }
                }


                // Reduce ignore-for-loops counter
                if (_ignoreTargetForLoops > 0)
                {
                    _ignoreTargetForLoops--;
                }
                // If we have an avoidance under our feet, then create a new object which contains a safety point to move to
                // But only if we aren't force-cancelling avoidance for XX time
                bool hasFoundSafePoint = false;

                using (new PerformanceLogger("RefreshDiaObjectCache.AvoidanceCheck"))
                {
                    if (Settings.Combat.Misc.FleeInGhostMode && Player.IsGhosted)
                    {
                        _standingInAvoidance = true;
                    }

                    // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin!
                    if (_standingInAvoidance && !CombatBase.IsDoingGoblinKamakazi && (!AnyTreasureGoblinsPresent || Settings.Combat.Misc.GoblinPriority <= GoblinPriority.Prioritize) &&
                        DateTime.UtcNow.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= cancelledEmergencyMoveForMilliseconds)
                    {
                        Vector3 safePosition = NavHelper.FindSafeZone(false, 1, Player.Position, true, null, true);

                        // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach
                        if (safePosition != Vector3.Zero)
                        {
                            if (Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                            {
                                Logger.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting Avoidance: {5} {0} Distance: {1:0} Direction: {2:0}, Health%={3:0.00}, KiteDistance: {4:0}",
                                           safePosition, safePosition.Distance(Me.Position), MathUtil.GetHeading(MathUtil.FindDirectionDegree(Me.Position, safePosition)),
                                           Player.CurrentHealthPct, CombatBase.KiteDistance, _currentAvoidanceName);
                            }

                            hasFoundSafePoint = true;
                            var distance = Vector3.Distance(Player.Position, safePosition);
                            Logger.Log(LogCategory.Avoidance, "Found Safe Spot {0}f away", distance);
                            CurrentTarget = new TrinityCacheObject()
                            {
                                Position     = safePosition,
                                Type         = TrinityObjectType.Avoidance,
                                Weight       = 40000,
                                Distance     = distance,
                                Radius       = 2f,
                                InternalName = "SafePoint",
                                IsSafeSpot   = true
                            };
                        }
                    }
                }

                /*
                 *  Set Weights, assign CurrentTarget
                 */

                if (!hasFoundSafePoint)
                {
                    RefreshDiaGetWeights();

                    if (!CombatBase.IsDoingGoblinKamakazi)
                    {
                        RefreshSetKiting(ref KiteAvoidDestination, NeedToKite);
                    }
                }

                // Not heading straight for a safe-spot?
                // No valid targets but we were told to stay put?
                if (CurrentTarget == null && !CombatBase.IsDoingGoblinKamakazi && _shouldStayPutDuringAvoidance && !_standingInAvoidance)
                {
                    CurrentTarget = new TrinityCacheObject()
                    {
                        Position     = Player.Position,
                        Type         = TrinityObjectType.Avoidance,
                        Weight       = 45000,
                        Distance     = 2f,
                        Radius       = 2f,
                        InternalName = "StayPutPoint",
                        IsSafeSpot   = true,
                    };
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Staying Put During Avoidance");
                }

                // Pre-townrun is too far away
                if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP &&
                    TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance"))
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Pre-TownRun position is more than {0} yards away, canceling", V.I("Cache.PretownRun.MaxDistance"));
                    TownRun.PreTownRunPosition = Vector3.Zero;
                    TownRun.PreTownRunWorldId  = -1;
                }

                // Reached pre-townrun position
                if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP &&
                    TownRun.PreTownRunPosition.Distance2D(Player.Position) <= 15f)
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Successfully returned to Pre-TownRun Position");
                    TownRun.PreTownRunPosition = Vector3.Zero;
                    TownRun.PreTownRunWorldId  = -1;
                }

                // After a townrun, make sure to return to original TownRun Location
                if (!Player.IsInTown && CurrentTarget == null && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP)
                {
                    if (TownRun.PreTownRunPosition.Distance2D(Player.Position) > 10f && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance"))
                    {
                        CurrentTarget = new TrinityCacheObject()
                        {
                            Position     = TownRun.PreTownRunPosition,
                            Type         = TrinityObjectType.Avoidance,
                            Weight       = 20000,
                            Distance     = 2f,
                            Radius       = 2f,
                            InternalName = "PreTownRunPosition"
                        };
                        Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Returning to Pre-TownRun Position");
                    }
                }

                using (new PerformanceLogger("RefreshDiaObjectCache.FinalChecks"))
                {
                    if (Settings.WorldObject.EnableBountyEvents && !CombatBase.IsDoingGoblinKamakazi)
                    {
                        bool eventObjectNear = ObjectCache.Any(o => o.Type == TrinityObjectType.CursedChest || o.Type == TrinityObjectType.CursedShrine);

                        if (!Player.InActiveEvent)
                        {
                            EventStartPosition = Vector3.Zero;
                            EventStartTime     = DateTime.MinValue;
                        }

                        // Reset Event time while we have targts
                        if (CurrentTarget != null && Player.InActiveEvent && eventObjectNear)
                        {
                            EventStartTime = DateTime.UtcNow;
                        }

                        if (eventObjectNear)
                        {
                            EventStartPosition = ObjectCache.FirstOrDefault(o => o.Type == TrinityObjectType.CursedChest || o.Type == TrinityObjectType.CursedShrine).Position;
                        }

                        var activeEvent = ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO));

                        const int waitTimeoutSeconds = 90;
                        if (DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds > waitTimeoutSeconds && activeEvent != null)
                        {
                            CacheData.BlacklistedEvents.Add(activeEvent.QuestSNO);
                        }

                        if (CurrentTarget == null && Player.InActiveEvent && EventStartPosition != Vector3.Zero &&
                            DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds < waitTimeoutSeconds &&
                            activeEvent != null && !CacheData.BlacklistedEvents.Contains(activeEvent.QuestSNO))
                        {
                            CurrentTarget = new TrinityCacheObject()
                            {
                                Position     = EventStartPosition,
                                Type         = TrinityObjectType.Avoidance,
                                Weight       = 20000,
                                Distance     = 2f,
                                Radius       = 2f,
                                InternalName = "WaitForEvent"
                            };
                            Logger.Log("Waiting for Event {0} - Time Remaining: {1:0} seconds",
                                       ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO)).Quest,
                                       waitTimeoutSeconds - DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds);
                        }
                    }

                    if (CombatBase.IsDoingGoblinKamakazi && CurrentTarget != null && CurrentTarget.Type != TrinityObjectType.Door && CurrentTarget.Type != TrinityObjectType.Barricade && !CurrentTarget.InternalName.ToLower().Contains("corrupt") && CurrentTarget.Weight != MaxWeight)
                    {
                        Logger.Log("Forcing Target to Goblin '{0} ({1})' Distance={2}", CombatBase.KamakaziGoblin.InternalName, CombatBase.KamakaziGoblin.ActorSNO, CombatBase.KamakaziGoblin.Distance);
                        CurrentTarget = CombatBase.KamakaziGoblin;
                    }

                    if (CombatBase.IsDoingGoblinKamakazi && CurrentTarget == null)
                    {
                        Logger.Log("No Target, Switching to Goblin '{0} ({1})' Distance={2}", CombatBase.KamakaziGoblin.InternalName, CombatBase.KamakaziGoblin.ActorSNO, CombatBase.KamakaziGoblin.Distance);
                        CurrentTarget = CombatBase.KamakaziGoblin;
                    }

                    // Still no target, let's see if we should backtrack or wait for wrath to come off cooldown...
                    if (CurrentTarget == null)
                    {
                        RefreshWaitTimers();
                    }

                    // Still no target, let's end it all!
                    if (CurrentTarget == null)
                    {
                        Events.OnCacheUpdatedHandler.Invoke();
                        return(true);
                    }

                    if (CurrentTarget.IsUnit)
                    {
                        lastHadUnitInSights = DateTime.UtcNow;
                    }

                    if (CurrentTarget.IsBossOrEliteRareUnique)
                    {
                        lastHadEliteUnitInSights = DateTime.UtcNow;
                    }

                    if (CurrentTarget.IsBoss || CurrentTarget.IsBountyObjective)
                    {
                        lastHadBossUnitInSights = DateTime.UtcNow;
                    }

                    if (CurrentTarget.Type == TrinityObjectType.Container)
                    {
                        lastHadContainerInSights = DateTime.UtcNow;
                    }

                    // Record the last time our target changed
                    if (LastTargetRactorGUID != CurrentTarget.RActorGuid)
                    {
                        RecordTargetHistory();

                        Logger.Log(TrinityLogLevel.Verbose, LogCategory.Weight,
                                   "Found New Target {0} dist={1:0} IsElite={2} Radius={3:0.0} Weight={4:0} ActorSNO={5} " +
                                   "Anim={6} TargetedCount={7} Type={8} ",
                                   CurrentTarget.InternalName,
                                   CurrentTarget.Distance,
                                   CurrentTarget.IsEliteRareUnique,
                                   CurrentTarget.Radius,
                                   CurrentTarget.Weight,
                                   CurrentTarget.ActorSNO,
                                   CurrentTarget.Animation,
                                   CurrentTarget.TimesBeenPrimaryTarget,
                                   CurrentTarget.Type
                                   );

                        _lastPickedTargetTime = DateTime.UtcNow;
                        _targetLastHealth     = 0f;
                    }
                    else
                    {
                        // We're sticking to the same target, so update the target's health cache to check for stucks
                        if (CurrentTarget.IsUnit)
                        {
                            // Check if the health has changed, if so update the target-pick time before we blacklist them again
                            if (CurrentTarget.HitPointsPct != _targetLastHealth)
                            {
                                Logger.Log(TrinityLogLevel.Debug, LogCategory.Weight, "Keeping Target {0} - CurrentTarget.HitPoints: {1:0.00} TargetLastHealth: {2:0.00} ",
                                           CurrentTarget.RActorGuid, CurrentTarget.HitPointsPct, _targetLastHealth);
                                _lastPickedTargetTime = DateTime.UtcNow;
                            }
                            // Now store the target's last-known health
                            _targetLastHealth = CurrentTarget.HitPointsPct;
                        }
                    }
                }

                // Store less important objects.
                if (ObjectCache.Count > 1)
                {
                    var setting   = Settings.Advanced.CacheWeightThresholdPct;
                    var threshold = setting > 0 && CurrentTarget != null ? CurrentTarget.Weight * ((double)setting / 100) : 0;

                    var lowPriorityObjects = ObjectCache.DistinctBy(c => c.RActorGuid).Where(c =>
                                                                                             c.Type != TrinityObjectType.Avoidance && c.Type != TrinityObjectType.Unit ||
                                                                                             c.Weight <threshold && c.Distance> 12f && !c.IsElite
                                                                                             ).ToDictionary(x => x.RActorGuid, x => x);

                    Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Cached {0}/{1} ({2:0}%) WeightThreshold={3}",
                               lowPriorityObjects.Count,
                               ObjectCache.Count,
                               lowPriorityObjects.Count > 0 ? ((double)lowPriorityObjects.Count / ObjectCache.Count) * 100 : 0,
                               threshold);

                    CacheData.LowPriorityObjectCache = lowPriorityObjects;
                }

                // We have a target and the cached was refreshed
                Events.OnCacheUpdatedHandler.Invoke();
                return(true);
            }
        }
Пример #10
0
        /// <summary>
        /// This method will add and update necessary information about all available actors. Determines ObjectType, sets ranges, updates blacklists, determines avoidance, kiting, target weighting
        /// and the result is we will have a new target for the Target Handler. Returns true if the cache was refreshed.
        /// </summary>
        /// <returns>True if the cache was updated</returns>
        public static bool RefreshDiaObjectCache(bool forceUpdate = false)
        {
            using (new PerformanceLogger("RefreshDiaObjectCache"))
            {
                if (DateTime.UtcNow.Subtract(LastRefreshedCache).TotalMilliseconds < Settings.Advanced.CacheRefreshRate && !forceUpdate)
                {
                    if (!UpdateCurrentTarget())
                    {
                        return(false);
                    }
                }
                LastRefreshedCache = DateTime.UtcNow;

                using (new PerformanceLogger("RefreshDiaObjectCache.UpdateBlock"))
                {
                    CacheData.Clear();
                    GenericCache.MaintainCache();
                    GenericBlacklist.MaintainBlacklist();

                    // Update player-data cache, including buffs
                    PlayerInfoCache.UpdateCachedPlayerData();

                    if (Player.CurrentHealthPct <= 0)
                    {
                        return(false);
                    }

                    RefreshCacheInit();

                    // Now pull up all the data and store anything we want to handle in the super special cache list
                    // Also use many cache dictionaries to minimize DB<->D3 memory hits, and speed everything up a lot
                    RefreshCacheMainLoop();
                }

                // Add Team HotSpots to the cache
                ObjectCache.AddRange(GroupHotSpots.GetCacheObjectHotSpots());

                /* Fire Chains Experimental Avoidance */
                if (Settings.Combat.Misc.UseExperimentalFireChainsAvoidance)
                {
                    const float fireChainSize = 5f;
                    foreach (var unit1 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains)))
                    {
                        foreach (var unit2 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains)).Where(unit2 => unit1.RActorGuid != unit2.RActorGuid))
                        {
                            for (float i = 0; i <= unit1.Position.Distance2D(unit2.Position); i += (fireChainSize / 4))
                            {
                                Vector3 fireChainSpot = MathEx.CalculatePointFrom(unit1.Position, unit2.Position, i);

                                if (Trinity.Player.Position.Distance2D(fireChainSpot) <= fireChainSize)
                                {
                                    Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Fire Chains!");
                                    _standingInAvoidance = true;
                                }
                                CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(fireChainSpot, fireChainSize, -2, "FireChains"));
                            }
                        }
                        if (CacheData.TimeBoundAvoidance.Any(aoe => aoe.ActorSNO == -2))
                        {
                            Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generated {0} avoidance points for FireChains, minDistance={1} maxDistance={2}",
                                       CacheData.TimeBoundAvoidance.Count(aoe => aoe.ActorSNO == -2),
                                       CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2)
                                       .Min(aoe => aoe.Position.Distance2D(Trinity.Player.Position)),
                                       CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2)
                                       .Max(aoe => aoe.Position.Distance2D(Trinity.Player.Position)));
                        }
                    }
                }

                /* Beast Charge Experimental Avoidance */
                if (Settings.Combat.Misc.UseExperimentalSavageBeastAvoidance)
                {
                    const float beastChargePathWidth = 10f;
                    const int   beastChargerSNO      = 3337;
                    foreach (var unit1 in ObjectCache.Where(u => u.IsFacingPlayer && u.Animation == SNOAnim.Beast_start_charge_02 ||
                                                            u.Animation == SNOAnim.Beast_charge_02 || u.Animation == SNOAnim.Beast_charge_04))
                    {
                        Vector3 endPoint = MathEx.GetPointAt(unit1.Position, 90f, unit1.Unit.Movement.Rotation);

                        for (float i = 0; i <= unit1.Position.Distance2D(endPoint); i += (beastChargePathWidth / 4))
                        {
                            Vector3 pathSpot = MathEx.CalculatePointFrom(unit1.Position, endPoint, i);

                            Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement,
                                       "Generating BeastCharge Avoidance: {0} dist: {1}",
                                       pathSpot, pathSpot.Distance2D(unit1.Position));

                            if (Trinity.Player.Position.Distance2D(pathSpot) <= beastChargePathWidth)
                            {
                                Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Beast Charger!");
                                _standingInAvoidance = true;
                            }
                            CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(pathSpot, beastChargePathWidth, beastChargerSNO,
                                                                                     "BeastCharge"));
                        }
                        if (CacheData.TimeBoundAvoidance.Any(aoe => aoe.ActorSNO == beastChargerSNO))
                        {
                            Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement,
                                       "Generated {0} avoidance points for BeastCharge, minDistance={1} maxDistance={2}",
                                       CacheData.TimeBoundAvoidance.Count(aoe => aoe.ActorSNO == beastChargerSNO),
                                       CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == beastChargerSNO)
                                       .Min(aoe => aoe.Position.Distance2D(Trinity.Player.Position)),
                                       CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == beastChargerSNO)
                                       .Max(aoe => aoe.Position.Distance2D(Trinity.Player.Position)));
                        }
                    }
                }

                /* Poison Experimental Avoidance */



                // Reduce ignore-for-loops counter
                if (_ignoreTargetForLoops > 0)
                {
                    _ignoreTargetForLoops--;
                }
                // If we have an avoidance under our feet, then create a new object which contains a safety point to move to
                // But only if we aren't force-cancelling avoidance for XX time
                bool hasFoundSafePoint = false;

                using (new PerformanceLogger("RefreshDiaObjectCache.AvoidanceCheck"))
                {
                    if (Player.IsGhosted)
                    {
                        _standingInAvoidance = true;
                    }

                    // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin!
                    if (_standingInAvoidance && (!AnyTreasureGoblinsPresent || Settings.Combat.Misc.GoblinPriority <= GoblinPriority.Prioritize) &&
                        DateTime.UtcNow.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= cancelledEmergencyMoveForMilliseconds)
                    {
                        Vector3 vAnySafePoint = NavHelper.FindSafeZone(false, 1, Player.Position, true, null, true);

                        // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach
                        if (vAnySafePoint != Vector3.Zero)
                        {
                            if (Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement))
                            {
                                Logger.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting Avoidance: {0} Distance: {1:0} Direction: {2:0}, Health%={3:0.00}, KiteDistance: {4:0}",
                                           vAnySafePoint, vAnySafePoint.Distance(Me.Position), MathUtil.GetHeading(MathUtil.FindDirectionDegree(Me.Position, vAnySafePoint)),
                                           Player.CurrentHealthPct, CombatBase.PlayerKiteDistance);
                            }

                            hasFoundSafePoint = true;
                            CurrentTarget     = new TrinityCacheObject()
                            {
                                Position     = vAnySafePoint,
                                Type         = GObjectType.Avoidance,
                                Weight       = 20000,
                                Distance     = Vector3.Distance(Player.Position, vAnySafePoint),
                                Radius       = 2f,
                                InternalName = "SafePoint"
                            };;
                        }
                    }
                }

                /*
                 *  Set Weights, assign CurrentTarget
                 */

                if (!hasFoundSafePoint)
                {
                    RefreshDiaGetWeights();

                    RefreshSetKiting(ref KiteAvoidDestination, NeedToKite);
                }
                // Not heading straight for a safe-spot?
                // No valid targets but we were told to stay put?
                if (CurrentTarget == null && _shouldStayPutDuringAvoidance && !_standingInAvoidance)
                {
                    CurrentTarget = new TrinityCacheObject()
                    {
                        Position     = Player.Position,
                        Type         = GObjectType.Avoidance,
                        Weight       = 20000,
                        Distance     = 2f,
                        Radius       = 2f,
                        InternalName = "StayPutPoint"
                    };
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Staying Put During Avoidance");
                }

                // Pre-townrun is too far away
                if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP &&
                    TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance"))
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Pre-TownRun position is more than {0} yards away, canceling", V.I("Cache.PretownRun.MaxDistance"));
                    TownRun.PreTownRunPosition = Vector3.Zero;
                    TownRun.PreTownRunWorldId  = -1;
                }

                // Reached pre-townrun position
                if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP &&
                    TownRun.PreTownRunPosition.Distance2D(Player.Position) <= 15f)
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Successfully returned to Pre-TownRun Position");
                    TownRun.PreTownRunPosition = Vector3.Zero;
                    TownRun.PreTownRunWorldId  = -1;
                }

                // After a townrun, make sure to return to original TownRun Location
                if (!Player.IsInTown && CurrentTarget == null && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP)
                {
                    if (TownRun.PreTownRunPosition.Distance2D(Player.Position) > 10f && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance"))
                    {
                        CurrentTarget = new TrinityCacheObject()
                        {
                            Position     = TownRun.PreTownRunPosition,
                            Type         = GObjectType.Avoidance,
                            Weight       = 20000,
                            Distance     = 2f,
                            Radius       = 2f,
                            InternalName = "PreTownRunPosition"
                        };
                        Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Returning to Pre-TownRun Position");
                    }
                }

                using (new PerformanceLogger("RefreshDiaObjectCache.FinalChecks"))
                {
                    // force to stay put if we want to town run and there's no target
                    if (CurrentTarget == null && ForceVendorRunASAP)
                    {
                        DontMoveMeIAmDoingShit = true;
                    }

                    if (Settings.WorldObject.EnableBountyEvents)
                    {
                        bool eventObjectNear = ObjectCache.Any(o => o.Type == GObjectType.CursedChest || o.Type == GObjectType.CursedShrine);

                        if (!Player.InActiveEvent)
                        {
                            EventStartPosition = Vector3.Zero;
                            EventStartTime     = DateTime.MinValue;
                        }

                        // Reset Event time while we have targts
                        if (CurrentTarget != null && Player.InActiveEvent && eventObjectNear)
                        {
                            EventStartTime = DateTime.UtcNow;
                        }

                        if (eventObjectNear)
                        {
                            EventStartPosition = ObjectCache.FirstOrDefault(o => o.Type == GObjectType.CursedChest || o.Type == GObjectType.CursedShrine).Position;
                        }

                        var activeEvent = ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO));

                        const int waitTimeoutSeconds = 90;
                        if (DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds > waitTimeoutSeconds && activeEvent != null)
                        {
                            CacheData.BlacklistedEvents.Add(activeEvent.QuestSNO);
                        }

                        if (CurrentTarget == null && Player.InActiveEvent && EventStartPosition != Vector3.Zero &&
                            DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds < waitTimeoutSeconds &&
                            activeEvent != null && !CacheData.BlacklistedEvents.Contains(activeEvent.QuestSNO))
                        {
                            CurrentTarget = new TrinityCacheObject()
                            {
                                Position     = EventStartPosition,
                                Type         = GObjectType.Avoidance,
                                Weight       = 20000,
                                Distance     = 2f,
                                Radius       = 2f,
                                InternalName = "WaitForEvent"
                            };
                            Logger.Log("Waiting for Event {0} - Time Remaining: {1:0} seconds",
                                       ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO)).Quest,
                                       waitTimeoutSeconds - DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds);
                        }
                    }

                    // Still no target, let's see if we should backtrack or wait for wrath to come off cooldown...
                    if (CurrentTarget == null)
                    {
                        RefreshDoBackTrack();
                    }

                    // Still no target, let's end it all!
                    if (CurrentTarget == null)
                    {
                        return(true);
                    }


                    if (CurrentTarget.IsUnit)
                    {
                        lastHadUnitInSights = DateTime.UtcNow;
                    }

                    if (CurrentTarget.IsBossOrEliteRareUnique)
                    {
                        lastHadEliteUnitInSights = DateTime.UtcNow;
                    }

                    if (CurrentTarget.IsBoss || CurrentTarget.IsBountyObjective)
                    {
                        lastHadBossUnitInSights = DateTime.UtcNow;
                    }


                    if (CurrentTarget.Type == GObjectType.Container)
                    {
                        lastHadContainerInSights = DateTime.UtcNow;
                    }

                    // Record the last time our target changed
                    if (LastTargetRactorGUID != CurrentTarget.RActorGuid)
                    {
                        RecordTargetHistory();

                        Logger.Log(TrinityLogLevel.Verbose, LogCategory.Weight,
                                   "Found New Target {0} dist={1:0} IsElite={2} Radius={3:0.0} Weight={4:0} ActorSNO={5} " +
                                   "Anim={6} TargetedCount={7} Type={8} ",
                                   CurrentTarget.InternalName,
                                   CurrentTarget.Distance,
                                   CurrentTarget.IsEliteRareUnique,
                                   CurrentTarget.Radius,
                                   CurrentTarget.Weight,
                                   CurrentTarget.ActorSNO,
                                   CurrentTarget.Animation,
                                   CurrentTarget.TimesBeenPrimaryTarget,
                                   CurrentTarget.Type
                                   );

                        _lastPickedTargetTime = DateTime.UtcNow;
                        _targetLastHealth     = 0f;
                    }
                    else
                    {
                        // We're sticking to the same target, so update the target's health cache to check for stucks
                        if (CurrentTarget.IsUnit)
                        {
                            // Check if the health has changed, if so update the target-pick time before we blacklist them again
                            if (CurrentTarget.HitPointsPct != _targetLastHealth)
                            {
                                Logger.Log(TrinityLogLevel.Debug, LogCategory.Weight, "Keeping Target {0} - CurrentTarget.HitPoints: {1:0.00} TargetLastHealth: {2:0.00} ",
                                           CurrentTarget.RActorGuid, CurrentTarget.HitPointsPct, _targetLastHealth);
                                _lastPickedTargetTime = DateTime.UtcNow;
                            }
                            // Now store the target's last-known health
                            _targetLastHealth = CurrentTarget.HitPointsPct;
                        }
                    }
                }
                // We have a target and the cached was refreshed
                return(true);
            }
        }