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);
     }
 }
Exemple #2
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);
        }