/// <summary> /// Finds an optimal position for using Monk Tempest Rush out of combat /// </summary> /// <returns></returns> internal static Vector3 FindTempestRushTarget() { Vector3 target = PlayerMover.LastMoveToTarget; Vector3 myPos = ZetaDia.Me.Position; if (GilesTrinity.CurrentTarget != null && NavHelper.CanRayCast(myPos, target)) { target = GilesTrinity.CurrentTarget.Position; } float distance = target.Distance2D(myPos); if (distance < 30f) { double direction = MathUtil.FindDirectionRadian(myPos, target); target = MathEx.GetPointAt(myPos, 40f, (float)direction); } return(target); }
private static void RefreshDoBackTrack() { // See if we should wait for [playersetting] milliseconds for possible loot drops before continuing run if (DateTime.Now.Subtract(lastHadUnitInSights).TotalMilliseconds <= Settings.Combat.Misc.DelayAfterKill || DateTime.Now.Subtract(lastHadEliteUnitInSights).TotalMilliseconds <= Settings.Combat.Misc.DelayAfterKill) { CurrentTarget = new GilesObject() { Position = PlayerStatus.CurrentPosition, Type = GObjectType.Avoidance, Weight = 20000, CentreDistance = 2f, RadiusDistance = 2f, InternalName = "GilesWaitForLootDrops" }; DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Behavior, "Waiting for loot to drop, delay: {0}ms", Settings.Combat.Misc.DelayAfterKill); } // Now see if we need to do any backtracking if (CurrentTarget == null && iTotalBacktracks >= 2 && Settings.Combat.Misc.AllowBacktracking && !PlayerStatus.IsInTown) // Never bother with the 1st backtrack position nor if we are in town { // See if we're already within 18 feet of our start position first if (Vector3.Distance(PlayerStatus.CurrentPosition, vBacktrackList[1]) <= 18f) { vBacktrackList = new SortedList <int, Vector3>(); iTotalBacktracks = 0; } // See if we can raytrace to the final location and it's within 25 feet if (iTotalBacktracks >= 2 && Vector3.Distance(PlayerStatus.CurrentPosition, vBacktrackList[1]) <= 25f && NavHelper.CanRayCast(PlayerStatus.CurrentPosition, vBacktrackList[1])) { vBacktrackList = new SortedList <int, Vector3>(); iTotalBacktracks = 0; } if (iTotalBacktracks >= 2) { // See if we can skip to the next backtracker location first if (iTotalBacktracks >= 3) { if (Vector3.Distance(PlayerStatus.CurrentPosition, vBacktrackList[iTotalBacktracks - 1]) <= 10f) { vBacktrackList.Remove(iTotalBacktracks); iTotalBacktracks--; } } CurrentTarget = new GilesObject() { Position = vBacktrackList[iTotalBacktracks], Type = GObjectType.Backtrack, Weight = 20000, CentreDistance = Vector3.Distance(PlayerStatus.CurrentPosition, vBacktrackList[iTotalBacktracks]), RadiusDistance = Vector3.Distance(PlayerStatus.CurrentPosition, vBacktrackList[iTotalBacktracks]), InternalName = "GilesBacktrack" }; } } else { vBacktrackList = new SortedList <int, Vector3>(); iTotalBacktracks = 0; } // End of backtracking check //TODO : If this code is obselete remove it (Check that) // Finally, a special check for waiting for wrath of the berserker cooldown before engaging Azmodan if (CurrentTarget == null && Hotbar.Contains(SNOPower.Barbarian_WrathOfTheBerserker) && Settings.Combat.Barbarian.WaitWOTB && !GilesUseTimer(SNOPower.Barbarian_WrathOfTheBerserker) && ZetaDia.CurrentWorldId == 121214 && (Vector3.Distance(PlayerStatus.CurrentPosition, new Vector3(711.25f, 716.25f, 80.13903f)) <= 40f || Vector3.Distance(PlayerStatus.CurrentPosition, new Vector3(546.8467f, 551.7733f, 1.576313f)) <= 40f)) { bDontSpamOutofCombat = true; Logging.Write("[Trinity] Waiting for Wrath Of The Berserker cooldown before continuing to Azmodan."); CurrentTarget = new GilesObject() { Position = PlayerStatus.CurrentPosition, Type = GObjectType.Avoidance, Weight = 20000, CentreDistance = 2f, RadiusDistance = 2f, InternalName = "GilesWaitForWrath" }; } // And a special check for wizard archon if (CurrentTarget == null && Hotbar.Contains(SNOPower.Wizard_Archon) && !GilesUseTimer(SNOPower.Wizard_Archon) && Settings.Combat.Wizard.WaitArchon && ZetaDia.CurrentWorldId == 121214 && (Vector3.Distance(PlayerStatus.CurrentPosition, new Vector3(711.25f, 716.25f, 80.13903f)) <= 40f || Vector3.Distance(PlayerStatus.CurrentPosition, new Vector3(546.8467f, 551.7733f, 1.576313f)) <= 40f)) { DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "Waiting for Wizard Archon cooldown before continuing to Azmodan."); CurrentTarget = new GilesObject() { Position = PlayerStatus.CurrentPosition, Type = GObjectType.Avoidance, Weight = 20000, CentreDistance = 2f, RadiusDistance = 2f, InternalName = "GilesWaitForArchon" }; } // And a very sexy special check for WD BigBadVoodoo if (CurrentTarget == null && Hotbar.Contains(SNOPower.Witchdoctor_BigBadVoodoo) && !PowerManager.CanCast(SNOPower.Witchdoctor_BigBadVoodoo) && ZetaDia.CurrentWorldId == 121214 && (Vector3.Distance(PlayerStatus.CurrentPosition, new Vector3(711.25f, 716.25f, 80.13903f)) <= 40f || Vector3.Distance(PlayerStatus.CurrentPosition, new Vector3(546.8467f, 551.7733f, 1.576313f)) <= 40f)) { DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "Waiting for WD BigBadVoodoo cooldown before continuing to Azmodan."); CurrentTarget = new GilesObject() { Position = PlayerStatus.CurrentPosition, Type = GObjectType.Avoidance, Weight = 20000, CentreDistance = 2f, RadiusDistance = 2f, InternalName = "GilesWaitForVoodooo" }; } }
// 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 = 9f; Vector3 myPos = PlayerStatus.CurrentPosition; float distanceToTarget = origin.Distance2D(myPos); Vector3 zigZagPoint = origin; using (new PerformanceLogger("FindZigZagTargetLocation")) { using (new PerformanceLogger("FindZigZagTargetLocation.CheckObjectCache")) { bool useTargetBasedZigZag = false; float maxDistance = 25f; int minTargets = 2; if (GilesTrinity.PlayerStatus.ActorClass == ActorClass.Monk) { maxDistance = 20f; minTargets = 3; useTargetBasedZigZag = (GilesTrinity.Settings.Combat.Monk.TargetBasedZigZag); } if (GilesTrinity.PlayerStatus.ActorClass == ActorClass.Barbarian) { useTargetBasedZigZag = (GilesTrinity.Settings.Combat.Barbarian.TargetBasedZigZag); } int eliteCount = ObjectCache.Count(u => u.Type == GObjectType.Unit && u.IsBossOrEliteRareUnique); bool shouldZigZagElites = ((GilesTrinity.CurrentTarget.IsBossOrEliteRareUnique && eliteCount > 1) || eliteCount == 0); if (useTargetBasedZigZag && shouldZigZagElites && !AnyTreasureGoblinsPresent && ObjectCache.Where(o => o.Type == GObjectType.Unit).Count() >= minTargets) { var clusterPoint = TargetUtil.GetBestClusterPoint(ringDistance, ringDistance, false); if (clusterPoint.Distance2D(PlayerStatus.CurrentPosition) >= minDistance) { DbHelper.Log(LogCategory.Movement, "Returning ZigZag: BestClusterPoint {0} r-dist={1} t-dist={2}", clusterPoint, ringDistance, clusterPoint.Distance2D(PlayerStatus.CurrentPosition)); return(clusterPoint); } IEnumerable <GilesObject> zigZagTargets = from u in ObjectCache where u.Type == GObjectType.Unit && u.RadiusDistance < maxDistance && !GilesTrinity.hashAvoidanceObstacleCache.Any(a => Vector3.Distance(u.Position, a.Location) < AvoidanceManager.GetAvoidanceRadiusBySNO(a.ActorSNO, a.Radius) && PlayerStatus.CurrentHealthPct <= AvoidanceManager.GetAvoidanceHealthBySNO(a.ActorSNO, 1)) select u; if (zigZagTargets.Count() >= minTargets) { zigZagPoint = zigZagTargets.OrderByDescending(u => u.CentreDistance).FirstOrDefault().Position; if (NavHelper.CanRayCast(zigZagPoint) && zigZagPoint.Distance2D(PlayerStatus.CurrentPosition) >= minDistance) { DbHelper.Log(LogCategory.Movement, "Returning ZigZag: TargetBased {0} r-dist={1} t-dist={2}", zigZagPoint, ringDistance, zigZagPoint.Distance2D(PlayerStatus.CurrentPosition)); return(zigZagPoint); } } } } Random rndNum = new Random(int.Parse(Guid.NewGuid().ToString().Substring(0, 8), NumberStyles.HexNumber)); using (new PerformanceLogger("FindZigZagTargetLocation.RandomZigZagPoint")) { float highestWeightFound = float.NegativeInfinity; Vector3 bestLocation = Vector3.Zero; // 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 = GilesTrinity.gp.GetHeight(zigZagPoint.ToVector2()); // Make sure we're actually zig-zagging our target, except if we're kiting bool intersectsPath = MathUtil.IntersectsPath(CurrentTarget.Position, CurrentTarget.Radius, myPos, zigZagPoint); if (GilesTrinity.PlayerKiteDistance <= 0 && !intersectsPath) { continue; } // if we're kiting, lets not actualy run through monsters if (GilesTrinity.PlayerKiteDistance > 0 && GilesTrinity.hashMonsterObstacleCache.Any(m => m.Location.Distance(zigZagPoint) <= GilesTrinity.PlayerKiteDistance)) { continue; } // Ignore point if any AoE in this point position if (GilesTrinity.hashAvoidanceObstacleCache.Any(m => m.Location.Distance(zigZagPoint) <= m.Radius && PlayerStatus.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(PlayerStatus.CurrentPosition, 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.Type == GObjectType.Unit && u.Position.Distance2D(zigZagPoint) <= Math.Max(u.Radius, 10f)); if (monsterCount > 0) { pointWeight *= monsterCount; } DbHelper.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 (GilesTrinity.Settings.Combat.Misc.UseNavMeshTargeting) { bestLocation = new Vector3(zigZagPoint.X, zigZagPoint.Y, GilesTrinity.gp.GetHeight(zigZagPoint.ToVector2())); } else { bestLocation = new Vector3(zigZagPoint.X, zigZagPoint.Y, zigZagPoint.Z + 4); } } } } DbHelper.Log(LogCategory.Movement, "Returning ZigZag: RandomXY {0} r-dist={1} t-dist={2}", bestLocation, ringDistance, bestLocation.Distance2D(PlayerStatus.CurrentPosition)); return(bestLocation); } } }
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); }
private static void Monk_MaintainTempestRush() { if (!Monk_TempestRushReady()) { return; } if (PlayerStatus.IsInTown || Zeta.CommonBot.Logic.BrainBehavior.IsVendoring) { return; } if (TownRun.IsTryingToTownPortal()) { return; } if (TimeSinceUse(SNOPower.Monk_TempestRush) > 150) { return; } bool shouldMaintain = false; bool nullTarget = CurrentTarget == null; if (!nullTarget) { // maintain for everything except items, doors, interactables... stuff we have to "click" on switch (CurrentTarget.Type) { case GObjectType.Unit: case GObjectType.Gold: case GObjectType.Avoidance: case GObjectType.Barricade: case GObjectType.Destructible: case GObjectType.Globe: { if (Settings.Combat.Monk.TROption == TempestRushOption.TrashOnly && (TargetUtil.AnyElitesInRange(40f) || CurrentTarget.IsBossOrEliteRareUnique)) { shouldMaintain = false; } else { shouldMaintain = true; } } break; } } else { shouldMaintain = true; } if (Settings.Combat.Monk.TROption != TempestRushOption.MovementOnly && GilesUseTimer(SNOPower.Monk_TempestRush) && shouldMaintain) { Vector3 target = LastTempestRushLocation; string locationSource = "LastLocation"; //if (CurrentTarget != null && GilesNavHelper.CanRayCast(PlayerStatus.CurrentPosition, CurrentTarget.Position)) //{ // locationSource = "Current Target Position"; // target = CurrentTarget.Position; //} if (target.Distance2D(ZetaDia.Me.Position) <= 1f) { // rrrix edit: we can't maintain here return; //locationSource = "ZigZag"; //target = FindZigZagTargetLocation(target, 23f); } if (target == Vector3.Zero) { return; } float DestinationDistance = target.Distance2D(ZetaDia.Me.Position); //target = MathEx.CalculatePointFrom(target, PlayerStatus.CurrentPosition, aimPointDistance); target = TargetUtil.FindTempestRushTarget(); if (DestinationDistance > 10f && NavHelper.CanRayCast(ZetaDia.Me.Position, target)) { Monk_TempestRushStatus(String.Format("Using Tempest Rush to maintain channeling, source={0}, V3={1} dist={2:0}", locationSource, target, DestinationDistance)); var usePowerResult = ZetaDia.Me.UsePower(SNOPower.Monk_TempestRush, target, CurrentWorldDynamicId, -1); if (usePowerResult) { dictAbilityLastUse[SNOPower.Monk_TempestRush] = DateTime.Now; } } } }