private static double GetAvoidanceHealth(int actorSNO = -1)
 {
     // snag our SNO from cache variable if not provided
     if (actorSNO == -1)
     {
         actorSNO = CurrentCacheObject.ActorSNO;
     }
     try
     {
         if (actorSNO != -1)
         {
             return(AvoidanceManager.GetAvoidanceHealthBySNO(CurrentCacheObject.ActorSNO, 1));
         }
         else
         {
             return(AvoidanceManager.GetAvoidanceHealthBySNO(actorSNO, 1));
         }
     }
     catch (Exception ex)
     {
         Logger.Log(TrinityLogLevel.Info, LogCategory.Avoidance, "Exception getting avoidance radius for sno={0}", actorSNO);
         Logger.Log(TrinityLogLevel.Info, LogCategory.Avoidance, ex.ToString());
         // 100% unless specified
         return(1);
     }
 }
        private static double GetAvoidanceRadius(int actorSNO = -1, float radius = -1f)
        {
            if (actorSNO == -1)
            {
                actorSNO = CurrentCacheObject.ActorSNO;
            }

            if (radius == -1f)
            {
                radius = 20f;
            }

            try
            {
                return(AvoidanceManager.GetAvoidanceRadiusBySNO(actorSNO, radius));
            }
            catch (Exception ex)
            {
                Logger.Log(TrinityLogLevel.Info, LogCategory.Avoidance, "Exception getting avoidance radius for sno={0} radius={1}", actorSNO, radius);
                Logger.Log(TrinityLogLevel.Info, LogCategory.Avoidance, ex.ToString());
                return(radius);
            }
        }
        private static bool RefreshAvoidance(bool AddToCache)
        {
            AddToCache = true;

            try
            {
                CurrentCacheObject.Animation = CurrentCacheObject.Object.CommonData.CurrentAnimation;
            }
            catch (Exception ex)
            {
                Logger.LogDebug(LogCategory.CacheManagement, "Error reading CurrentAnimation for AoE sno:{0} raGuid:{1} name:{2} ex:{3}",
                                CurrentCacheObject.ActorSNO, CurrentCacheObject.RActorGuid, CurrentCacheObject.InternalName, ex.Message);
            }

            float customRadius;

            if (DataDictionary.DefaultAvoidanceCustomRadius.TryGetValue(CurrentCacheObject.ActorSNO, out customRadius))
            {
                CurrentCacheObject.Radius = customRadius;
            }

            double minAvoidanceHealth = GetAvoidanceHealth(CurrentCacheObject.ActorSNO);
            double minAvoidanceRadius = GetAvoidanceRadius(CurrentCacheObject.ActorSNO, CurrentCacheObject.Radius);

            // Are we allowed to path around avoidance?
            if (Settings.Combat.Misc.AvoidanceNavigation)
            {
                MainGridProvider.AddCellWeightingObstacle(CurrentCacheObject.ActorSNO, (float)minAvoidanceRadius);
            }

            AvoidanceType avoidanceType = AvoidanceManager.GetAvoidanceType(CurrentCacheObject.ActorSNO);

            // Beast Charge should set aoe position as players current position!
            if (avoidanceType == AvoidanceType.BeastCharge)
            {
                CurrentCacheObject.Position = Trinity.Player.Position;
            }

            // Monks with Serenity up ignore all AOE's
            if (Player.ActorClass == ActorClass.Monk && Hotbar.Contains(SNOPower.Monk_Serenity) && GetHasBuff(SNOPower.Monk_Serenity))
            {
                // Monks with serenity are immune
                minAvoidanceHealth *= V.F("Monk.Avoidance.Serenity");
                Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring avoidance as a Monk with Serenity");
            }
            // Witch doctors with spirit walk available and not currently Spirit Walking will subtly ignore ice balls, arcane, desecrator & plague cloud
            if (Player.ActorClass == ActorClass.Witchdoctor && Hotbar.Contains(SNOPower.Witchdoctor_SpiritWalk) && GetHasBuff(SNOPower.Witchdoctor_SpiritWalk))
            {
                if (avoidanceType == AvoidanceType.IceBall || avoidanceType == AvoidanceType.Arcane || avoidanceType == AvoidanceType.Desecrator || avoidanceType == AvoidanceType.PlagueCloud)
                {
                    // Ignore ICE/Arcane/Desc/PlagueCloud altogether with spirit walk up or available
                    minAvoidanceHealth *= V.F("WitchDoctor.Avoidance.SpiritWalk");
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring avoidance as a WitchDoctor with Spirit Walk");
                }
            }
            // Remove ice balls if the barbarian has wrath of the berserker up, and reduce health from most other SNO avoidances
            if (Player.ActorClass == ActorClass.Barbarian &&
                Settings.Combat.Barbarian.IgnoreAvoidanceInWOTB &&
                Hotbar.Contains(SNOPower.Barbarian_WrathOfTheBerserker) &&
                GetHasBuff(SNOPower.Barbarian_WrathOfTheBerserker))
            {
                switch (avoidanceType)
                {
                case AvoidanceType.IceBall:
                    minAvoidanceHealth *= V.F("Barbarian.Avoidance.WOTB.IceBall");
                    break;

                case AvoidanceType.Arcane:
                    minAvoidanceHealth *= V.F("Barbarian.Avoidance.WOTB.Arcane");
                    break;

                case AvoidanceType.Desecrator:
                    minAvoidanceHealth *= V.F("Barbarian.Avoidance.WOTB.Desecrator");
                    break;

                case AvoidanceType.Belial:
                    minAvoidanceHealth = V.F("Barbarian.Avoidance.WOTB.Belial");
                    break;

                case AvoidanceType.PoisonTree:
                    minAvoidanceHealth = V.F("Barbarian.Avoidance.WOTB.PoisonTree");
                    break;

                case AvoidanceType.BeastCharge:
                    minAvoidanceHealth = V.F("Barbarian.Avoidance.WOTB.BeastCharge");
                    break;

                default:
                    minAvoidanceHealth *= V.F("Barbarian.Avoidance.WOTB.Other");
                    break;
                }
            }

            if (minAvoidanceHealth == 0)
            {
                AddToCache = false;
                Logger.Log(TrinityLogLevel.Verbose, LogCategory.Avoidance, "Ignoring Avoidance! Name={0} SNO={1} radius={2:0} health={3:0.00} dist={4:0}",
                           CurrentCacheObject.InternalName, CurrentCacheObject.ActorSNO, minAvoidanceRadius, minAvoidanceHealth, CurrentCacheObject.Distance);
                return(AddToCache);
            }

            // Add it to the list of known avoidance objects, *IF* our health is lower than this avoidance health limit
            if (minAvoidanceHealth >= Player.CurrentHealthPct)
            {
                float avoidanceRadius = (float)GetAvoidanceRadius(CurrentCacheObject.ActorSNO, CurrentCacheObject.Radius);

                TimeSpan aoeExpiration;
                DataDictionary.AvoidanceSpawnerDuration.TryGetValue(CurrentCacheObject.ActorSNO, out aoeExpiration);

                CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(CurrentCacheObject.Position, avoidanceRadius, CurrentCacheObject.ActorSNO, CurrentCacheObject.InternalName)
                {
                    Expires    = DateTime.UtcNow.Add(aoeExpiration),
                    ObjectType = GObjectType.Avoidance,
                    Rotation   = CurrentCacheObject.Rotation
                });

                // Is this one under our feet? If so flag it up so we can find an avoidance spot
                if (CurrentCacheObject.Distance <= minAvoidanceRadius)
                {
                    _standingInAvoidance = true;

                    // Note if this is a travelling projectile or not so we can constantly update our safe points
                    if (DataDictionary.AvoidanceProjectiles.Contains(CurrentCacheObject.ActorSNO))
                    {
                        _isAvoidingProjectiles = true;
                        Logger.Log(TrinityLogLevel.Verbose, LogCategory.Avoidance, "Is standing in avoidance for projectile Name={0} SNO={1} radius={2:0} health={3:0.00} dist={4:0}",
                                   CurrentCacheObject.InternalName, CurrentCacheObject.ActorSNO, minAvoidanceRadius, minAvoidanceHealth, CurrentCacheObject.Distance);
                    }
                    else
                    {
                        Logger.Log(TrinityLogLevel.Verbose, LogCategory.Avoidance, "Is standing in avoidance Name={0} SNO={1} radius={2:0} health={3:0.00} dist={4:0}",
                                   CurrentCacheObject.InternalName, CurrentCacheObject.ActorSNO, minAvoidanceRadius, minAvoidanceHealth, CurrentCacheObject.Distance);
                    }
                }
            }

            return(AddToCache);
        }
Exemple #4
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);
        }
        // thanks to Main for the super fast can-stand-at code
        internal static Vector3 MainFindSafeZone(Vector3 origin, bool shouldKite = false, bool isStuck = false, IEnumerable <TrinityCacheObject> monsterList = null, bool avoidDeath = false)
        {
            MainGridProvider.Update();
            Navigator.Clear();

            const float gridSquareSize = 10f;
            const float maxDistance    = 55f;
            const int   maxWeight      = 100;

            double gridSquareRadius = Math.Sqrt((Math.Pow(gridSquareSize / 2, 2) + Math.Pow(gridSquareSize / 2, 2)));

            GridPoint bestPoint = new GridPoint(Vector3.Zero, 0, 0);

            int totalNodes       = 0;
            int nodesCantStand   = 0;
            int nodesZDiff       = 0;
            int nodesGT45Raycast = 0;
            int nodesAvoidance   = 0;
            int nodesMonsters    = 0;
            int pathFailures     = 0;
            int navRaycast       = 0;
            int pointsFound      = 0;

            int worldId = Trinity.Player.WorldID;

            Stopwatch[] timers = Enumerable.Range(0, 12).Select(i => new Stopwatch()).ToArray();

            Vector2 minWorld;

            minWorld.X = origin.X - maxDistance;
            minWorld.Y = origin.Y - maxDistance;

            Point minPoint = MainGridProvider.WorldToGrid(minWorld);

            minPoint.X = Math.Max(minPoint.X, 0);
            minPoint.Y = Math.Max(minPoint.Y, 0);

            Vector2 maxWorld;

            maxWorld.X = origin.X + maxDistance;
            maxWorld.Y = origin.Y + maxDistance;

            Point maxPoint = MainGridProvider.WorldToGrid(maxWorld);

            maxPoint.X = Math.Min(maxPoint.X, MainGridProvider.Width - 1);
            maxPoint.Y = Math.Min(maxPoint.Y, MainGridProvider.Height - 1);

            Point originPos = MainGridProvider.WorldToGrid(origin.ToVector2());

            using (new PerformanceLogger("MainFindSafeZoneLoop"))
            {
                for (int y = minPoint.Y; y <= maxPoint.Y; y++)
                {
                    int searchAreaBasis = y * MainGridProvider.Width;
                    for (int x = minPoint.X; x <= maxPoint.X; x++)
                    {
                        totalNodes++;

                        timers[0].Start();

                        int dx = originPos.X - x;
                        int dy = originPos.Y - y;
                        //if (dx * dx + dy * dy > radius * 2.5 * radius * 2.5)
                        //    continue;

                        // Ignore out of range
                        if (dx * dx + dy * dy > (maxDistance / 2.5f) * (maxDistance / 2.5f))
                        {
                            continue;
                        }
                        // extremely efficient CanStandAt
                        if (!MainGridProvider.SearchArea[searchAreaBasis + x])
                        {
                            nodesCantStand++;
                            continue;
                        }

                        Vector2 xy  = MainGridProvider.GridToWorld(new Point(x, y));
                        Vector3 xyz = Vector3.Zero;

                        if (Trinity.Settings.Combat.Misc.UseNavMeshTargeting)
                        {
                            xyz = new Vector3(xy.X, xy.Y, MainGridProvider.GetHeight(xy));
                        }
                        else
                        {
                            xyz = new Vector3(xy.X, xy.Y, origin.Z + 4);
                        }

                        GridPoint gridPoint = new GridPoint(xyz, 0, origin.Distance(xyz));

                        timers[0].Stop();


                        if (isStuck && gridPoint.Distance > (PlayerMover.TotalAntiStuckAttempts + 2) * 5)
                        {
                            continue;
                        }

                        /*
                         * Check if a square is occupied already
                         */
                        // Avoidance
                        timers[2].Start();
                        if (CacheData.TimeBoundAvoidance.Any(a => xyz.Distance2DSqr(a.Position) - (a.Radius * a.Radius) <= gridSquareRadius * gridSquareRadius))
                        {
                            nodesAvoidance++;
                            continue;
                        }
                        timers[2].Stop();

                        timers[9].Start();
                        // Obstacles
                        if (CacheData.NavigationObstacles.Any(a => xyz.Distance2DSqr(a.Position) - (a.Radius * a.Radius) <= gridSquareRadius * gridSquareRadius))
                        {
                            nodesMonsters++;
                            continue;
                        }
                        timers[9].Stop();

                        timers[10].Start();
                        if (CacheData.NavigationObstacles.Any(a => a.Position.Distance2DSqr(Trinity.Player.Position) < maxDistance * maxDistance &&
                                                              MathUtil.IntersectsPath(a.Position, a.Radius, Trinity.Player.Position, gridPoint.Position)))
                        {
                            pathFailures++;
                        }
                        timers[10].Stop();

                        // Monsters
                        if (shouldKite)
                        {
                            timers[3].Start();
                            double checkRadius = gridSquareRadius;

                            if (CombatBase.PlayerKiteDistance > 0)
                            {
                                checkRadius = gridSquareSize + CombatBase.PlayerKiteDistance;
                            }

                            // Any monster standing in this GridPoint
                            if (CacheData.MonsterObstacles.Any(a => Vector3.Distance(xyz, a.Position) + a.Radius <= checkRadius))
                            {
                                nodesMonsters++;
                                continue;
                            }

                            if (!hasEmergencyTeleportUp)
                            {
                                // Any monsters blocking in a straight line between origin and this GridPoint
                                foreach (CacheObstacleObject monster in CacheData.MonsterObstacles.Where(m =>
                                                                                                         MathEx.IntersectsPath(new Vector3(m.Position.X, m.Position.Y, 0), m.Radius, new Vector3(origin.X, origin.Y, 0), new Vector3(gridPoint.Position.X, gridPoint.Position.Y, 0))
                                                                                                         ))
                                {
                                    nodesMonsters++;
                                    continue;
                                }
                            }
                            timers[3].Stop();
                        }

                        timers[4].Start();
                        if (isStuck && UsedStuckSpots.Any(p => Vector3.Distance(p.Position, gridPoint.Position) <= gridSquareRadius))
                        {
                            continue;
                        }
                        timers[4].Stop();

                        // set base weight
                        if (!isStuck && !avoidDeath)
                        {
                            // e.g. ((100 - 15) / 100) * 100) = 85
                            // e.g. ((100 - 35) / 100) * 100) = 65
                            // e.g. ((100 - 75) / 100) * 100) = 25
                            gridPoint.Weight = ((maxDistance - gridPoint.Distance) / maxDistance) * maxWeight;

                            // Low weight for close range grid points
                            if (shouldKite && gridPoint.Distance < CombatBase.PlayerKiteDistance)
                            {
                                gridPoint.Weight = (int)gridPoint.Distance;
                            }
                        }
                        else
                        {
                            gridPoint.Weight = gridPoint.Distance;
                        }

                        // Boss Areas
                        timers[5].Start();
                        if (UnSafeZone.UnsafeKiteAreas.Any(a => a.WorldId == ZetaDia.CurrentWorldId && Vector3.Distance(a.Position, gridPoint.Position) <= a.Radius))
                        {
                            continue;
                        }
                        timers[5].Stop();

                        if (shouldKite)
                        {
                            // make sure we can raycast to our target
                            //if (!DataDictionary.StraightLinePathingLevelAreaIds.Contains(Trinity.Player.LevelAreaId) &&
                            //    !NavHelper.CanRayCast(gridPoint.Position, Trinity.LastPrimaryTargetPosition))
                            //{
                            //    navRaycast++;
                            //    continue;
                            //}

                            /*
                             * We want to down-weight any grid points where monsters are closer to it than we are
                             */
                            timers[7].Start();
                            foreach (CacheObstacleObject monster in CacheData.MonsterObstacles)
                            {
                                float distFromMonster           = gridPoint.Position.Distance2D(monster.Position);
                                float distFromOrigin            = gridPoint.Position.Distance2D(origin);
                                float distFromOriginToAvoidance = origin.Distance2D(monster.Position);
                                if (distFromOriginToAvoidance < distFromOrigin)
                                {
                                    continue;
                                }

                                if (distFromMonster < distFromOrigin)
                                {
                                    gridPoint.Weight -= distFromOrigin;
                                }
                                else if (distFromMonster > distFromOrigin)
                                {
                                    gridPoint.Weight += distFromMonster;
                                }
                            }
                            timers[7].Stop();

                            timers[8].Start();
                            foreach (CacheObstacleObject avoidance in CacheData.TimeBoundAvoidance)
                            {
                                float distFromAvoidance         = gridPoint.Position.Distance2D(avoidance.Position);
                                float distFromOrigin            = gridPoint.Position.Distance2D(origin);
                                float distFromOriginToAvoidance = origin.Distance2D(avoidance.Position);

                                float health = AvoidanceManager.GetAvoidanceHealthBySNO(avoidance.ActorSNO, 1f);
                                float radius = AvoidanceManager.GetAvoidanceRadiusBySNO(avoidance.ActorSNO, 1f);

                                // position is inside avoidance
                                if (PlayerStatus.CurrentHealthPct < health && distFromAvoidance < radius)
                                {
                                    continue;
                                }

                                // closer to avoidance than it is to player
                                if (distFromOriginToAvoidance < distFromOrigin)
                                {
                                    continue;
                                }

                                if (distFromAvoidance < distFromOrigin)
                                {
                                    gridPoint.Weight -= distFromOrigin;
                                }
                                else if (distFromAvoidance > distFromOrigin)
                                {
                                    gridPoint.Weight += distFromAvoidance;
                                }
                            }
                            timers[8].Stop();
                        }
                        else if (isStuck)
                        {
                            // give weight to points nearer to our destination
                            gridPoint.Weight *= (maxDistance - PlayerMover.LastMoveToTarget.Distance2D(gridPoint.Position)) / maxDistance * maxWeight;
                        }
                        else if (!shouldKite && !isStuck && !avoidDeath) // melee avoidance use only
                        {
                            timers[9].Start();
                            var monsterCount = Trinity.ObjectCache.Count(u => u.IsUnit && u.Position.Distance2D(gridPoint.Position) <= 2.5f);
                            if (monsterCount > 0)
                            {
                                gridPoint.Weight *= monsterCount;
                            }
                            timers[9].Stop();
                        }

                        pointsFound++;

                        if (gridPoint.Weight > bestPoint.Weight && gridPoint.Distance > 1)
                        {
                            bestPoint = gridPoint;
                        }
                    }
                }
            }


            if (isStuck)
            {
                UsedStuckSpots.Add(bestPoint);
            }

            string times = "";

            for (int t = 0; t < timers.Length; t++)
            {
                if (timers[t].IsRunning)
                {
                    timers[t].Stop();
                }
                times += string.Format("{0}/{1:0.0} ", t, timers[t].ElapsedMilliseconds);
            }

            Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Kiting grid found {0}, distance: {1:0}, weight: {2:0}", bestPoint.Position, bestPoint.Distance, bestPoint.Weight);
            Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement,
                       "Kiting grid stats Total={0} CantStand={1} ZDiff {2} GT45Raycast {3} Avoidance {4} Monsters {5} pathFailures {6} navRaycast {7} "
                       + "pointsFound {8} shouldKite={9} isStuck={10} avoidDeath={11} monsters={12} timers={13}",
                       totalNodes,
                       nodesCantStand,
                       nodesZDiff,
                       nodesGT45Raycast,
                       nodesAvoidance,
                       nodesMonsters,
                       pathFailures,
                       navRaycast,
                       pointsFound,
                       shouldKite,
                       isStuck,
                       avoidDeath,
                       monsterList == null ? 0 : monsterList.Count(),
                       times
                       );
            return(bestPoint.Position);
        }
        private static bool RefreshAvoidance()
        {
            try
            {
                CurrentCacheObject.Animation = c_diaObject.CommonData.CurrentAnimation;
            }
            catch (Exception ex)
            {
                Logger.LogDebug(LogCategory.CacheManagement, "Error reading CurrentAnimation for AoE sno:{0} raGuid:{1} name:{2} ex:{3}",
                                CurrentCacheObject.ActorSNO, CurrentCacheObject.RActorGuid, CurrentCacheObject.InternalName, ex.Message);
            }

            float customRadius;

            if (DataDictionary.DefaultAvoidanceCustomRadius.TryGetValue(CurrentCacheObject.ActorSNO, out customRadius) ||
                DataDictionary.DefaultAvoidanceAnimationCustomRadius.TryGetValue((int)CurrentCacheObject.Animation, out customRadius))
            {
                CurrentCacheObject.Radius = customRadius;
            }

            double minAvoidanceHealth = GetAvoidanceHealth(CurrentCacheObject.ActorSNO);
            double minAvoidanceRadius = GetAvoidanceRadius(CurrentCacheObject.ActorSNO, CurrentCacheObject.Radius + 5);

            // Add Navigation cell weights to path around avoidance
            MainGridProvider.AddCellWeightingObstacle(CurrentCacheObject.ActorSNO, (float)minAvoidanceRadius);

            AvoidanceType avoidanceType = AvoidanceManager.GetAvoidanceType(CurrentCacheObject.ActorSNO);

            // Beast Charge should set aoe position as players current position!
            var avoidAtPlayerPosition = DataDictionary.AvoidAnimationAtPlayer.Contains((int)CurrentCacheObject.Animation);

            if (avoidAtPlayerPosition)
            {
                CurrentCacheObject.Position = Player.Position;
            }

            // Monks with Serenity up ignore all AOE's
            if (Player.ActorClass == ActorClass.Monk && Hotbar.Contains(SNOPower.Monk_Serenity) && GetHasBuff(SNOPower.Monk_Serenity))
            {
                // Monks with serenity are immune
                minAvoidanceHealth *= V.F("Monk.Avoidance.Serenity");
                Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring avoidance as a Monk with Serenity");
            }
            // Witch doctors with spirit walk available and not currently Spirit Walking will subtly ignore ice balls, arcane, desecrator & plague cloud
            if (Player.ActorClass == ActorClass.Witchdoctor && Hotbar.Contains(SNOPower.Witchdoctor_SpiritWalk) && GetHasBuff(SNOPower.Witchdoctor_SpiritWalk))
            {
                if (avoidanceType == AvoidanceType.IceBall || avoidanceType == AvoidanceType.Arcane || avoidanceType == AvoidanceType.Desecrator || avoidanceType == AvoidanceType.PlagueCloud)
                {
                    // Ignore ICE/Arcane/Desc/PlagueCloud altogether with spirit walk up or available
                    minAvoidanceHealth *= V.F("WitchDoctor.Avoidance.SpiritWalk");
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring avoidance as a WitchDoctor with Spirit Walk");
                }
            }
            // Remove ice balls if the barbarian has wrath of the berserker up, and reduce health from most other SNO avoidances
            if (Player.ActorClass == ActorClass.Barbarian &&
                Settings.Combat.Barbarian.IgnoreAvoidanceInWOTB &&
                Hotbar.Contains(SNOPower.Barbarian_WrathOfTheBerserker) &&
                GetHasBuff(SNOPower.Barbarian_WrathOfTheBerserker))
            {
                switch (avoidanceType)
                {
                case AvoidanceType.IceBall:
                    minAvoidanceHealth *= V.F("Barbarian.Avoidance.WOTB.IceBall");
                    break;

                case AvoidanceType.Arcane:
                    minAvoidanceHealth *= V.F("Barbarian.Avoidance.WOTB.Arcane");
                    break;

                case AvoidanceType.Desecrator:
                    minAvoidanceHealth *= V.F("Barbarian.Avoidance.WOTB.Desecrator");
                    break;

                case AvoidanceType.Belial:
                    minAvoidanceHealth = V.F("Barbarian.Avoidance.WOTB.Belial");
                    break;

                case AvoidanceType.PoisonTree:
                    minAvoidanceHealth = V.F("Barbarian.Avoidance.WOTB.PoisonTree");
                    break;

                case AvoidanceType.BeastCharge:
                    minAvoidanceHealth = V.F("Barbarian.Avoidance.WOTB.BeastCharge");
                    break;

                case AvoidanceType.MoltenCore:
                    minAvoidanceHealth = V.F("Barbarian.Avoidance.WOTB.MoltenCore");
                    break;

                default:
                    minAvoidanceHealth *= V.F("Barbarian.Avoidance.WOTB.Other");
                    break;
                }
            }

            // Item based immunity
            switch (avoidanceType)
            {
            case AvoidanceType.PoisonTree:
            case AvoidanceType.PlagueCloud:
            case AvoidanceType.PoisonEnchanted:
            case AvoidanceType.PlagueHand:

                if (Legendary.MarasKaleidoscope.IsEquipped)
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring Avoidance {0} because MarasKaleidoscope is equipped", avoidanceType);
                    minAvoidanceHealth = 0;
                }
                break;

            case AvoidanceType.AzmoFireball:
            case AvoidanceType.DiabloRingOfFire:
            case AvoidanceType.DiabloMeteor:
            case AvoidanceType.ButcherFloorPanel:
            case AvoidanceType.Mortar:
            case AvoidanceType.MageFire:
            case AvoidanceType.MoltenTrail:
            case AvoidanceType.MoltenBall:
            case AvoidanceType.ShamanFire:

                if (Legendary.TheStarOfAzkaranth.IsEquipped)
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring Avoidance {0} because TheStarofAzkaranth is equipped", avoidanceType);
                    minAvoidanceHealth = 0;
                }
                break;

            case AvoidanceType.FrozenPulse:
            case AvoidanceType.IceBall:
            case AvoidanceType.IceTrail:

                // Ignore if both items are equipped
                if (Legendary.TalismanOfAranoch.IsEquipped)
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring Avoidance {0} because TalismanofAranoch is equipped", avoidanceType);
                    minAvoidanceHealth = 0;
                }
                break;

            case AvoidanceType.Orbiter:
            case AvoidanceType.Thunderstorm:

                if (Legendary.XephirianAmulet.IsEquipped)
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring Avoidance {0} because XephirianAmulet is equipped", avoidanceType);
                    minAvoidanceHealth = 0;
                }
                break;

            case AvoidanceType.Arcane:
                if (Legendary.CountessJuliasCameo.IsEquipped)
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring Avoidance {0} because CountessJuliasCameo is equipped", avoidanceType);
                    minAvoidanceHealth = 0;
                }
                break;
            }

            // Set based immunity
            if (Sets.BlackthornesBattlegear.IsMaxBonusActive)
            {
                var blackthornsImmunity = new HashSet <AvoidanceType>
                {
                    AvoidanceType.Desecrator,
                    AvoidanceType.MoltenBall,
                    AvoidanceType.MoltenCore,
                    AvoidanceType.MoltenTrail,
                    AvoidanceType.PlagueHand
                };

                if (blackthornsImmunity.Contains(avoidanceType))
                {
                    Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring Avoidance {0} because BlackthornesBattlegear is equipped", avoidanceType);
                    minAvoidanceHealth = 0;
                }
            }

            if (minAvoidanceHealth == 0)
            {
                Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Ignoring Avoidance! Name={0} SNO={1} radius={2:0} health={3:0.00} dist={4:0}",
                           CurrentCacheObject.InternalName, CurrentCacheObject.ActorSNO, minAvoidanceRadius, minAvoidanceHealth, CurrentCacheObject.Distance);
                return(false);
            }

            //Logger.LogDebug(LogCategory.Avoidance, "{0} Distance={1:0} {2}! {3} ({4})",
            //    (avoidanceType == AvoidanceType.None) ? CurrentCacheObject.Animation.ToString() : avoidanceType.ToString(),
            //    CurrentCacheObject.Distance,
            //    minAvoidanceHealth >= Player.CurrentHealthPct ? "Adding" : "Ignoring",
            //    CurrentCacheObject.InternalName, CurrentCacheObject.ActorSNO);

            // Add it to the list of known avoidance objects, *IF* our health is lower than this avoidance health limit
            if (minAvoidanceHealth >= Player.CurrentHealthPct)
            {
                float avoidanceRadius = (float)GetAvoidanceRadius(CurrentCacheObject.ActorSNO, CurrentCacheObject.Radius);

                TimeSpan aoeExpiration;
                DataDictionary.AvoidanceSpawnerDuration.TryGetValue(CurrentCacheObject.ActorSNO, out aoeExpiration);

                CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(CurrentCacheObject.Position, avoidanceRadius, CurrentCacheObject.ActorSNO, CurrentCacheObject.InternalName)
                {
                    Expires    = DateTime.UtcNow.Add(aoeExpiration),
                    ObjectType = TrinityObjectType.Avoidance,
                    Rotation   = CurrentCacheObject.Rotation
                });

                // Is this one under our feet? If so flag it up so we can find an avoidance spot
                if (CurrentCacheObject.Distance <= minAvoidanceRadius)
                {
                    _currentAvoidance = CurrentCacheObject;
                    _currentAvoidance.AvoidanceRadius = avoidanceRadius;
                    _currentAvoidance.AvoidanceHealth = minAvoidanceHealth;
                    _currentAvoidanceName             = CurrentCacheObject.InternalName;
                    _standingInAvoidance = true;

                    // Note if this is a travelling projectile or not so we can constantly update our safe points
                    if (DataDictionary.AvoidanceProjectiles.Contains(CurrentCacheObject.ActorSNO))
                    {
                        Logger.Log(TrinityLogLevel.Verbose, LogCategory.Avoidance, "Is standing in avoidance for projectile Name={0} SNO={1} radius={2:0} health={3:0.00} dist={4:0}",
                                   CurrentCacheObject.InternalName, CurrentCacheObject.ActorSNO, minAvoidanceRadius, minAvoidanceHealth, CurrentCacheObject.Distance);
                    }
                    else
                    {
                        Logger.Log(TrinityLogLevel.Verbose, LogCategory.Avoidance, "Is standing in avoidance Name={0} SNO={1} radius={2:0} health={3:0.00} dist={4:0}",
                                   CurrentCacheObject.InternalName, CurrentCacheObject.ActorSNO, minAvoidanceRadius, minAvoidanceHealth, CurrentCacheObject.Distance);
                    }
                }
            }

            return(true);
        }