// 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, float minDistance = 0f) { const float gridSquareSize = 5f; var dhMaxDistance = Math.Max(Trinity.Settings.Combat.DemonHunter.KiteMaxDistance, Trinity.Settings.Combat.DemonHunter.KiteLimit + 5); float maxDistance = Trinity.Player.ActorClass == ActorClass.DemonHunter ? dhMaxDistance : 100f; 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); if (MainGridProvider.Width == 0) { // Do not remove nav server logging, we need to differentiate between legitmate trinity issues and nav server issues. // In this case, if MainGridProvider is empty, then the bot cannot kite. Logger.Log("NavServer Issue: MainGridProvider is empty, unable to avoidance/kite position"); return(Vector3.Zero); } int totalNodes = 0; int nodesCantStand = 0; int nodesZDiff = 0; int nodesGT45Raycast = 0; int nodesAvoidance = 0; int nodesMonsters = 0; int nodesObjects = 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 = WorldToGrid(minWorld); 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; // MainGridProvider will be empty/clear we start receiving navServer data //Point maxPoint = WorldToGrid(maxWorld); 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()); //var monsters = monsterList.ToList(); 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; // 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(aoe => xyz.Distance2D(aoe.Position) <= gridSquareRadius)) { nodesAvoidance++; continue; } timers[2].Stop(); if (monsterList != null && monsterList.Any(m => xyz.Distance(m.Position) - m.Radius - 2 <= minDistance)) { nodesMonsters++; continue; } // Monsters if (shouldKite) { timers[3].Start(); double checkRadius = gridSquareRadius; if (CombatBase.KiteDistance > 0) { checkRadius = gridSquareSize + 10f; } // Any monster standing in this GridPoint if (CacheData.MonsterObstacles.Any(monster => monster.Position.Distance2D(xyz) - monster.Radius <= checkRadius)) { 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.KiteDistance) { gridPoint.Weight = (int)gridPoint.Distance; } } else { gridPoint.Weight = gridPoint.Distance; } // Boss Areas timers[5].Start(); if (UnSafeZone.UnsafeKiteAreas.Any(a => a.WorldId == Trinity.Player.WorldID && a.Position.Distance2DSqr(gridPoint.Position) <= (a.Radius * a.Radius))) { continue; } timers[5].Stop(); if (shouldKite) { /* * 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 distFromPointToMonster = gridPoint.Position.Distance2D(monster.Position); float distFromPointToOrigin = gridPoint.Position.Distance2D(origin); // No Kite Distance Setting if (CombatBase.KiteDistance <= 0) { // higher weight closer to monster if (distFromPointToMonster < distFromPointToOrigin) { gridPoint.Weight += distFromPointToOrigin; } else if (distFromPointToMonster > distFromPointToOrigin) { gridPoint.Weight -= distFromPointToMonster; } } else // Kite Distance is Set { // higher weight further from monster if (distFromPointToMonster < distFromPointToOrigin) { gridPoint.Weight -= distFromPointToOrigin; } else if (distFromPointToMonster > distFromPointToOrigin) { gridPoint.Weight += distFromPointToMonster; } if (PositionCache.Cache.Any(cachePoint => gridPoint.Position.Distance2D(cachePoint.Position) <= gridSquareRadius)) { gridPoint.Weight += distFromPointToOrigin; // always <= max distance, 0-150ish } } } timers[7].Stop(); timers[8].Start(); foreach (CacheObstacleObject avoidance in CacheData.TimeBoundAvoidance) { float distSqrFromPointToAvoidance = gridPoint.Position.Distance2DSqr(avoidance.Position); // position is inside avoidance if (distSqrFromPointToAvoidance < (avoidance.Radius * avoidance.Radius)) { continue; } float distSqrFromPointToOrigin = gridPoint.Position.Distance2DSqr(origin); if (distSqrFromPointToAvoidance < distSqrFromPointToOrigin) { gridPoint.Weight -= distSqrFromPointToOrigin; } else if (distSqrFromPointToAvoidance > distSqrFromPointToOrigin) { gridPoint.Weight += distSqrFromPointToAvoidance; } } 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 (!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.Avoidance, "Kiting grid found {0}, distance: {1:0}, weight: {2:0}", bestPoint.Position, bestPoint.Distance, bestPoint.Weight); Logger.Log(TrinityLogLevel.Debug, LogCategory.Avoidance, "Kiting grid stats Total={0} CantStand={1} ZDiff {2} GT45Raycast {3} Avoidance {4} Monsters {5} Obstacles {14} 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, nodesObjects ); if (double.IsNaN(bestPoint.Position.X)) { Logger.LogVerbose("Somethign went wrong, NaN value for MainFindSafeZone result"); return(Vector3.Zero); } return(bestPoint.Position); }
internal static Vector3 newFindSafeZone(Vector3 origin, bool shouldKite = false, bool isStuck = false, IEnumerable <GilesObject> monsterList = null) { /* * generate 50x50 grid of 5x5 squares within max 100 distance from origin to edge of grid * * all squares start with 0 weight * * check if Center IsNavigable * check Z * check if avoidance is present * check if monsters are present * * final distance tile weight = (Max Dist - Dist)/Max Dist*Max Weight * * end result should be that only navigable squares where no avoidance, monsters, or obstacles are present */ float gridSquareSize = 5f; int maxDistance = 200; int maxWeight = 100; int maxZDiff = 14; int gridTotalSize = (int)(maxDistance / gridSquareSize) * 2; /* If maxDistance is the radius of a circle from the origin, then we want to get the hypotenuse of the radius (x) and tangent (y) as our search grid corner * anything outside of the circle will not be considered */ Vector2 topleft = new Vector2(origin.X - maxDistance, origin.Y - maxDistance); //Make a circle on the corners of the square double gridSquareRadius = Math.Sqrt((Math.Pow(gridSquareSize / 2, 2) + Math.Pow(gridSquareSize / 2, 2))); GridPoint bestPoint = new GridPoint(Vector3.Zero, 0, 0); int nodesNotNavigable = 0; int nodesZDiff = 0; int nodesGT45Raycast = 0; int nodesAvoidance = 0; int nodesMonsters = 0; int pathFailures = 0; for (int x = 0; x < gridTotalSize; x++) { for (int y = 0; y < gridTotalSize; y++) { Vector2 xy = new Vector2(topleft.X + (x * gridSquareSize), topleft.Y + (y * gridSquareSize)); Vector3 xyz = Vector3.Zero; Point p_xy = Point.Empty; if (GilesTrinity.Settings.Combat.Misc.UseNavMeshTargeting) { xyz = new Vector3(xy.X, xy.Y, gp.GetHeight(xy)); } else { xyz = new Vector3(xy.X, xy.Y, origin.Z + 4); } GridPoint gridPoint = new GridPoint(xyz, 0, origin.Distance(xyz)); //if (gridPoint.Distance > maxDistance + gridSquareRadius) // continue; if (GilesTrinity.Settings.Combat.Misc.UseNavMeshTargeting) { p_xy = gp.WorldToGrid(xy); if (!gp.CanStandAt(p_xy)) { nodesNotNavigable++; continue; } } else { // If ZDiff is way too different (up a cliff or wall) if (Math.Abs(gridPoint.Position.Z - origin.Z) > maxZDiff) { nodesZDiff++; continue; } } if (gridPoint.Distance > 45 && Navigator.Raycast(origin, xyz)) { nodesGT45Raycast++; continue; } if (isStuck && gridPoint.Distance > (PlayerMover.iTotalAntiStuckAttempts + 2) * 5) { continue; } /* * Check if a square is occupied already */ // Avoidance if (GilesTrinity.hashAvoidanceObstacleCache.Any(a => Vector3.Distance(xyz, a.Location) - a.Radius <= gridSquareRadius)) { nodesAvoidance++; continue; } // Obstacles if (GilesTrinity.hashNavigationObstacleCache.Any(a => Vector3.Distance(xyz, a.Location) - a.Radius <= gridSquareRadius)) { nodesMonsters++; continue; } // Monsters if (shouldKite) { // Any monster standing in this GridPoint if (GilesTrinity.hashMonsterObstacleCache.Any(a => Vector3.Distance(xyz, a.Location) - a.Radius <= (shouldKite ? gridSquareRadius : gridSquareSize + GilesTrinity.PlayerKiteDistance))) { nodesMonsters++; continue; } if (!hasEmergencyTeleportUp) { // Any monsters blocking in a straight line between origin and this GridPoint foreach (GilesObstacle monster in GilesTrinity.hashMonsterObstacleCache.Where(m => MathEx.IntersectsPath(new Vector3(m.Location.X, m.Location.Y, 0), m.Radius, new Vector3(origin.X, origin.Y, 0), new Vector3(gridPoint.Position.X, gridPoint.Position.Y, 0)) )) { nodesMonsters++; continue; } } int nearbyMonsters = (monsterList != null ? monsterList.Count() : 0); } if (isStuck && UsedStuckSpots.Any(p => Vector3.Distance(p.Position, gridPoint.Position) <= gridSquareRadius)) { continue; } if (!isStuck) { gridPoint.Weight = ((maxDistance - gridPoint.Distance) / maxDistance) * maxWeight; // Low weight for close range grid points if (shouldKite && gridPoint.Distance < GilesTrinity.PlayerKiteDistance) { gridPoint.Weight = (int)gridPoint.Distance; } } else { gridPoint.Weight = gridPoint.Distance; } // Boss Areas if (UnSafeZone.UnsafeKiteAreas.Any(a => a.WorldId == ZetaDia.CurrentWorldId && Vector3.Distance(a.Position, gridPoint.Position) <= a.Radius)) { continue; } if (shouldKite) { // make sure we can raycast to our target if (!NavHelper.CanRayCast(gridPoint.Position, GilesTrinity.LastPrimaryTargetPosition)) { continue; } /* * We want to down-weight any grid points where monsters are closer to it than we are */ foreach (GilesObstacle monster in GilesTrinity.hashMonsterObstacleCache) { float distFromMonster = gridPoint.Position.Distance2D(monster.Location); float distFromOrigin = gridPoint.Position.Distance2D(origin); float distFromOriginToAvoidance = origin.Distance2D(monster.Location); if (distFromOriginToAvoidance < distFromOrigin) { continue; } if (distFromMonster < distFromOrigin) { gridPoint.Weight -= distFromOrigin; } else if (distFromMonster > distFromOrigin) { gridPoint.Weight += distFromMonster; } } foreach (GilesObstacle avoidance in GilesTrinity.hashAvoidanceObstacleCache) { float distFromAvoidance = gridPoint.Position.Distance2D(avoidance.Location); float distFromOrigin = gridPoint.Position.Distance2D(origin); float distFromOriginToAvoidance = origin.Distance2D(avoidance.Location); 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; } } } 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) // melee avoidance use only { var monsterCount = GilesTrinity.GilesObjectCache.Count(u => u.Type == GObjectType.Unit && u.Position.Distance2D(gridPoint.Position) <= gridSquareRadius); if (monsterCount > 0) { gridPoint.Weight *= monsterCount; } } if (gridPoint.Weight > bestPoint.Weight && gridPoint.Distance > 1) { bestPoint = gridPoint; } //if (gridPoint.Weight > 0) // DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Moving, "Kiting grid point {0}, distance: {1:0}, weight: {2:0}", gridPoint.Position, gridPoint.Distance, gridPoint.Weight); } } if (isStuck) { UsedStuckSpots.Add(bestPoint); } DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting grid found {0}, distance: {1:0}, weight: {2:0}", bestPoint.Position, bestPoint.Distance, bestPoint.Weight); DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting grid stats NotNavigable {0} ZDiff {1} GT45Raycast {2} Avoidance {3} Monsters {4} pathFailures {5}", nodesNotNavigable, nodesZDiff, nodesGT45Raycast, nodesAvoidance, nodesMonsters, pathFailures); return(bestPoint.Position); }
// 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); }