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