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); } }
// 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); }