public static void ResetEverythingNewGame() { hashUseOnceID = new HashSet <int>(); dictUseOnceID = new Dictionary <int, int>(); iMaxDeathsAllowed = 0; iDeathsThisRun = 0; _hashsetItemStatsLookedAt = new HashSet <string>(); _hashsetItemPicksLookedAt = new HashSet <string>(); _hashsetItemFollowersIgnored = new HashSet <string>(); TownRun._dictItemStashAttempted = new Dictionary <int, int>(); hashRGUIDBlacklist60 = new HashSet <int>(); hashRGUIDBlacklist90 = new HashSet <int>(); hashRGUIDBlacklist15 = new HashSet <int>(); vBacktrackList = new SortedList <int, Vector3>(); iTotalBacktracks = 0; HasMappedPlayerAbilities = false; PlayerMover.iTotalAntiStuckAttempts = 1; PlayerMover.vSafeMovementLocation = Vector3.Zero; PlayerMover.vOldPosition = Vector3.Zero; PlayerMover.iTimesReachedStuckPoint = 0; PlayerMover.TimeLastRecordedPosition = DateTime.Today; PlayerMover.LastGeneratedStuckPosition = DateTime.Today; PlayerMover.iTimesReachedMaxUnstucks = 0; PlayerMover.iCancelUnstuckerForSeconds = 0; PlayerMover._lastCancelledUnstucker = DateTime.Today; NavHelper.UsedStuckSpots = new List <GridPoint>(); // Reset all the caches dictGilesObjectTypeCache = new Dictionary <int, GObjectType>(); dictGilesMonsterAffixCache = new Dictionary <int, MonsterAffixes>(); dictGilesMaxHealthCache = new Dictionary <int, double>(); dictGilesLastHealthCache = new Dictionary <int, double>(); dictGilesLastHealthChecked = new Dictionary <int, int>(); dictGilesBurrowedCache = new Dictionary <int, bool>(); dictGilesActorSNOCache = new Dictionary <int, int>(); dictGilesACDGUIDCache = new Dictionary <int, int>(); dictGilesInternalNameCache = new Dictionary <int, string>(); dictGilesGameBalanceIDCache = new Dictionary <int, int>(); dictGilesDynamicIDCache = new Dictionary <int, int>(); dictGilesVectorCache = new Dictionary <int, Vector3>(); dictGilesGoldAmountCache = new Dictionary <int, int>(); dictGilesQualityCache = new Dictionary <int, ItemQuality>(); dictGilesPickupItem = new Dictionary <int, bool>(); dictSummonedByID = new Dictionary <int, int>(); dictTotalInteractionAttempts = new Dictionary <int, int>(); listProfilesLoaded = new List <string>(); CurrentProfile = ""; FirstProfile = ""; NavHelper.UpdateSearchGridProvider(); GoldInactivity.ResetCheckGold(); global::GilesTrinity.XmlTags.TrinityLoadOnce.UsedProfiles = new List <string>(); GenericCache.ClearCache(); GenericBlacklist.ClearBlacklist(); }
/// <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); }
/// <summary> /// Called when user Enable the plugin. /// </summary> public void OnEnabled() { BotMain.OnStart += TrinityBotStart; BotMain.OnStop += TrinityBotStop; // Set up the pause button // rrrix: removing for next DB beta... //Application.Current.Dispatcher.Invoke(PaintMainWindowButtons()); SetWindowTitle(); if (!Directory.Exists(FileManager.PluginPath)) { DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "Fatal Error - cannot enable plugin. Invalid path: {0}", FileManager.PluginPath); DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "Please check you have installed the plugin to the correct location, and then restart DemonBuddy and re-enable the plugin."); DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, @"Plugin should be installed to \<DemonBuddyFolder>\Plugins\Trinity\"); } else { HasMappedPlayerAbilities = false; IsPluginEnabled = true; // Settings are available after this... LoadConfiguration(); Navigator.PlayerMover = new PlayerMover(); SetUnstuckProvider(); GameEvents.OnPlayerDied += TrinityOnDeath; GameEvents.OnGameJoined += TrinityOnJoinGame; GameEvents.OnGameLeft += TrinityOnLeaveGame; GameEvents.OnItemSold += TrinityOnItemSold; GameEvents.OnItemSalvaged += TrinityOnItemSalvaged; GameEvents.OnItemStashed += TrinityOnItemStashed; GameEvents.OnGameChanged += GameEvents_OnGameChanged; if (NavProvider == null) { NavProvider = new DefaultNavigationProvider(); } // enable or disable process exit events //ZetaDia.Memory.Process.EnableRaisingEvents = false; CombatTargeting.Instance.Provider = new BlankCombatProvider(); LootTargeting.Instance.Provider = new BlankLootProvider(); ObstacleTargeting.Instance.Provider = new BlankObstacleProvider(); if (Settings.Loot.ItemFilterMode != global::GilesTrinity.Settings.Loot.ItemFilterMode.DemonBuddy) { ItemManager.Current = new TrinityItemManager(); } NavHelper.UpdateSearchGridProvider(); // Safety check incase DB "OnStart" event didn't fire properly if (BotMain.IsRunning) { TrinityBotStart(null); if (ZetaDia.IsInGame) { TrinityOnJoinGame(null, null); } } SetBotTPS(); TrinityPowerManager.LoadLegacyDelays(); DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "*******************TRINITY*****************", Description);; DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "ATIVADO: {0} carregado - mod by WAR!", Description);; DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "*******************TRINITY*****************", Description);; } if (StashRule != null) { // reseting stash rules StashRule.reset(); } }
private static void RefreshSetKiting(ref Vector3 vKitePointAvoid, bool NeedToKite, ref bool TryToKite) { using (new PerformanceLogger("RefreshDiaObjectCache.Kiting")) { TryToKite = false; var monsterList = from m in GilesObjectCache where m.Type == GObjectType.Unit && m.Weight > 0 && m.RadiusDistance <= PlayerKiteDistance && (m.IsBossOrEliteRareUnique || ((m.HitPointsPct >= .15 || m.MonsterStyle != MonsterSize.Swarm) && !m.IsBossOrEliteRareUnique) ) select m; if (CurrentTarget != null && CurrentTarget.Type == GObjectType.Unit && PlayerKiteDistance > 0 && CurrentTarget.RadiusDistance <= PlayerKiteDistance) { TryToKite = true; vKitePointAvoid = PlayerStatus.CurrentPosition; } if (monsterList.Count() > 0 && (PlayerStatus.ActorClass != ActorClass.Wizard || IsWizardShouldKite())) { TryToKite = true; vKitePointAvoid = PlayerStatus.CurrentPosition; } // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin! bool shouldKamikazeTreasureGoblins = (!AnyTreasureGoblinsPresent || Settings.Combat.Misc.GoblinPriority <= GoblinPriority.Prioritize); double msCancelledEmergency = DateTime.Now.Subtract(timeCancelledEmergencyMove).TotalMilliseconds; bool shouldEmergencyMove = msCancelledEmergency >= cancelledEmergencyMoveForMilliseconds && NeedToKite; double msCancelledKite = DateTime.Now.Subtract(timeCancelledKiteMove).TotalMilliseconds; bool shouldKite = msCancelledKite >= cancelledKiteMoveForMilliseconds && TryToKite; if (shouldKamikazeTreasureGoblins && (shouldEmergencyMove || shouldKite)) { Vector3 vAnySafePoint = NavHelper.FindSafeZone(false, 1, vKitePointAvoid, true, monsterList); if (LastKitePosition == null) { LastKitePosition = new KitePosition() { PositionFoundTime = DateTime.Now, Position = vAnySafePoint, Distance = vAnySafePoint.Distance(PlayerStatus.CurrentPosition) }; } if (vAnySafePoint != Vector3.Zero && vAnySafePoint.Distance(PlayerStatus.CurrentPosition) >= 1) { if ((DateTime.Now.Subtract(LastKitePosition.PositionFoundTime).TotalMilliseconds > 3000 && LastKitePosition.Position == vAnySafePoint) || (CurrentTarget != null && DateTime.Now.Subtract(lastGlobalCooldownUse).TotalMilliseconds > 1500 && TryToKite)) { timeCancelledKiteMove = DateTime.Now; cancelledKiteMoveForMilliseconds = 1500; DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kite movement failed, cancelling for {0:0}ms", cancelledKiteMoveForMilliseconds); return; } else { LastKitePosition = new KitePosition() { PositionFoundTime = DateTime.Now, Position = vAnySafePoint, Distance = vAnySafePoint.Distance(PlayerStatus.CurrentPosition) }; } if (Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting to: {0} Distance: {1:0} Direction: {2:0}, Health%={3:0.00}, KiteDistance: {4:0}, Nearby Monsters: {5:0} NeedToKite: {6} TryToKite: {7}", vAnySafePoint, vAnySafePoint.Distance(PlayerStatus.CurrentPosition), MathUtil.GetHeading(MathUtil.FindDirectionDegree(Me.Position, vAnySafePoint)), PlayerStatus.CurrentHealthPct, PlayerKiteDistance, monsterList.Count(), NeedToKite, TryToKite); } CurrentTarget = new GilesObject() { Position = vAnySafePoint, Type = GObjectType.Avoidance, Weight = 90000, CentreDistance = Vector3.Distance(PlayerStatus.CurrentPosition, vAnySafePoint), RadiusDistance = Vector3.Distance(PlayerStatus.CurrentPosition, vAnySafePoint), InternalName = "KitePoint" }; //timeCancelledKiteMove = DateTime.Now; //cancelledKiteMoveForMilliseconds = 100; // Try forcing a target update with each kiting //bForceTargetUpdate = true; } //else //{ // // Didn't find any kiting we could reach, so don't look for any more kite spots for at least 1.5 seconds // timeCancelledKiteMove = DateTime.Now; // cancelledKiteMoveForMilliseconds = 500; //} } else if (!shouldEmergencyMove && NeedToKite) { DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Emergency movement cancelled for {0:0}ms", DateTime.Now.Subtract(timeCancelledEmergencyMove).TotalMilliseconds - cancelledKiteMoveForMilliseconds); } else if (!shouldKite && TryToKite) { DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kite movement cancelled for {0:0}ms", DateTime.Now.Subtract(timeCancelledKiteMove).TotalMilliseconds - cancelledKiteMoveForMilliseconds); } } }
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" }; } }
/// <summary> /// This method will add and update necessary information about all available actors. Determines GilesObjectType, sets ranges, updates blacklists, determines avoidance, kiting, target weighting /// and the result is we will have a new target for the Target Handler. Returns true if the cache was refreshed. /// </summary> /// <returns>True if the cache was updated</returns> public static bool RefreshDiaObjectCache(bool forceUpdate = false) { using (new PerformanceLogger("RefreshDiaObjectCache")) { if (DateTime.Now.Subtract(LastRefreshedCache).TotalMilliseconds < Settings.Advanced.CacheRefreshRate && !forceUpdate) { if (!UpdateCurrentTarget()) { return(false); } } LastRefreshedCache = DateTime.Now; using (new PerformanceLogger("RefreshDiaObjectCache.UpdateBlock")) { GenericCache.MaintainCache(); GenericBlacklist.MaintainBlacklist(); using (ZetaDia.Memory.AcquireFrame()) { // Update player-data cache, including buffs GilesPlayerCache.UpdateCachedPlayerData(); if (PlayerStatus.CurrentHealthPct <= 0) { return(false); } if (Settings.Combat.Misc.UseNavMeshTargeting && !gp.CanStandAt(gp.WorldToGrid(PlayerStatus.CurrentPosition.ToVector2()))) { NavHelper.UpdateSearchGridProvider(); } RefreshCacheInit(); // Now pull up all the data and store anything we want to handle in the super special cache list // Also use many cache dictionaries to minimize DB<->D3 memory hits, and speed everything up a lot RefreshCacheMainLoop(); } } // Reduce ignore-for-loops counter if (IgnoreTargetForLoops > 0) { IgnoreTargetForLoops--; } // If we have an avoidance under our feet, then create a new object which contains a safety point to move to // But only if we aren't force-cancelling avoidance for XX time bool bFoundSafeSpot = false; using (new PerformanceLogger("RefreshDiaObjectCache.AvoidanceCheck")) { // Note that if treasure goblin level is set to kamikaze, even avoidance moves are disabled to reach the goblin! if (StandingInAvoidance && (!AnyTreasureGoblinsPresent || Settings.Combat.Misc.GoblinPriority <= GoblinPriority.Prioritize) && DateTime.Now.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= cancelledEmergencyMoveForMilliseconds) { Vector3 vAnySafePoint = NavHelper.FindSafeZone(false, 1, PlayerStatus.CurrentPosition, true); // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach if (vAnySafePoint != vNullLocation) { if (Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Kiting Avoidance: {0} Distance: {1:0} Direction: {2:0}, Health%={3:0.00}, KiteDistance: {4:0}", vAnySafePoint, vAnySafePoint.Distance(Me.Position), MathUtil.GetHeading(MathUtil.FindDirectionDegree(Me.Position, vAnySafePoint)), PlayerStatus.CurrentHealthPct, PlayerKiteDistance); } bFoundSafeSpot = true; CurrentTarget = new GilesObject() { Position = vAnySafePoint, Type = GObjectType.Avoidance, Weight = 20000, CentreDistance = Vector3.Distance(PlayerStatus.CurrentPosition, vAnySafePoint), RadiusDistance = Vector3.Distance(PlayerStatus.CurrentPosition, vAnySafePoint), InternalName = "GilesSafePoint" };; } //else //{ // // Didn't find any safe spot we could reach, so don't look for any more safe spots for at least 2.8 seconds // cancelledEmergencyMoveForMilliseconds = 2800; // timeCancelledEmergencyMove = DateTime.Now; // DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Movement, "Unable to find kite location, canceling emergency movement for {0}ms", cancelledEmergencyMoveForMilliseconds); //} } } /* * Give weights to objects */ // Special flag for special whirlwind circumstances bAnyNonWWIgnoreMobsInRange = false; // Now give each object a weight *IF* we aren't skipping direcly to a safe-spot if (!bFoundSafeSpot) { RefreshDiaGetWeights(); RefreshSetKiting(ref vKitePointAvoid, NeedToKite, ref TryToKite); } // Not heading straight for a safe-spot? // No valid targets but we were told to stay put? if (CurrentTarget == null && ShouldStayPutDuringAvoidance && !StandingInAvoidance) { CurrentTarget = new GilesObject() { Position = PlayerStatus.CurrentPosition, Type = GObjectType.Avoidance, Weight = 20000, CentreDistance = 2f, RadiusDistance = 2f, InternalName = "GilesStayPutPoint" }; DbHelper.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Staying Put During Avoidance"); } using (new PerformanceLogger("RefreshDiaObjectCache.FinalChecks")) { // force to stay put if we want to town run and there's no target if (CurrentTarget == null && ForceVendorRunASAP) { bDontMoveMeIAmDoingShit = true; } // Still no target, let's see if we should backtrack or wait for wrath to come off cooldown... if (CurrentTarget == null) { RefreshDoBackTrack(); } // Still no target, let's end it all! if (CurrentTarget == null) { return(true); } // Ok record the time we last saw any unit at all if (CurrentTarget.Type == GObjectType.Unit) { lastHadUnitInSights = DateTime.Now; // And record when we last saw any form of elite if (CurrentTarget.IsBoss || CurrentTarget.IsEliteRareUnique || CurrentTarget.IsTreasureGoblin) { lastHadEliteUnitInSights = DateTime.Now; } } // Record the last time our target changed if (CurrentTargetRactorGUID != CurrentTarget.RActorGuid) { RecordTargetHistory(); DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Weight, "Found New Target - {0} CurrentTargetRactorGUID: {1} CurrentTarget.RActorGuid: {2}", DateTime.Now, CurrentTargetRactorGUID, CurrentTarget.RActorGuid); dateSincePickedTarget = DateTime.Now; iTargetLastHealth = 0f; } else { // We're sticking to the same target, so update the target's health cache to check for stucks if (CurrentTarget.Type == GObjectType.Unit) { // Check if the health has changed, if so update the target-pick time before we blacklist them again if (CurrentTarget.HitPointsPct != iTargetLastHealth) { DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.Weight, "Keeping Target {0} - CurrentTarget.iHitPoints: {1:0.00} iTargetLastHealth: {2:0.00} ", CurrentTarget.RActorGuid, CurrentTarget.HitPointsPct, iTargetLastHealth); dateSincePickedTarget = DateTime.Now; } // Now store the target's last-known health iTargetLastHealth = CurrentTarget.HitPointsPct; } } } // We have a target and the cached was refreshed return(true); } }
// 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); }
/// <summary> /// This is wired up by Plugin.OnEnabled, and called when the bot is started /// </summary> /// <param name="bot"></param> private static void TrinityBotStart(IBot bot) { // Recording of all the XML's in use this run try { string sThisProfile = Zeta.CommonBot.Settings.GlobalSettings.Instance.LastProfile; if (sThisProfile != CurrentProfile) { listProfilesLoaded.Add(sThisProfile); CurrentProfile = sThisProfile; if (FirstProfile == "") { FirstProfile = sThisProfile; } } } catch { } // Update actors if possible (if already in-game) if (ZetaDia.IsInGame && !ZetaDia.IsLoadingWorld && ZetaDia.Actors != null) { ZetaDia.Actors.Update(); NavHelper.UpdateSearchGridProvider(true); } HasMappedPlayerAbilities = false; if (!bMaintainStatTracking) { ItemStatsWhenStartedBot = DateTime.Now; ItemStatsLastPostedReport = DateTime.Now; bMaintainStatTracking = true; } else { DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "Note: Maintaining item stats from previous run. To reset stats fully, please restart DB."); } UsedProfileManager.RefreshProfileBlacklists(); ReplaceTreeHooks(); PlayerMover.TimeLastRecordedPosition = DateTime.Now; PlayerMover.timeLastRestartedGame = DateTime.Now; GoldInactivity.ResetCheckGold(); if (Zeta.CommonBot.Settings.CharacterSettings.Instance.KillRadius < 20) { DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "WARNING: Low Kill Radius detected, currently set to: {0} (you can change this through Demonbuddy bot settings)", Zeta.CommonBot.Settings.CharacterSettings.Instance.KillRadius); } if (Zeta.CommonBot.Settings.CharacterSettings.Instance.LootRadius < 50) { DbHelper.Log(TrinityLogLevel.Normal, LogCategory.UserInformation, "WARNING: Low Gold Loot Radius detected, currently set to: {0} (you can change this through Demonbuddy bot settings)", Zeta.CommonBot.Settings.CharacterSettings.Instance.LootRadius); } if (StashRule == null) { StashRule = new ItemRules.Interpreter(); } StashRule.readConfiguration(); }
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; } } } }
/// <summary> /// Update the cached data on the player information, including buffs if needed /// </summary> internal static void UpdateCachedPlayerData() { using (new PerformanceLogger("UpdateCachedPlayerData")) { if (DateTime.Now.Subtract(PlayerStatus.LastUpdated).TotalMilliseconds <= 100) { return; } // If we aren't in the game of a world is loading, don't do anything yet if (!ZetaDia.IsInGame || ZetaDia.IsLoadingWorld) { return; } var me = ZetaDia.Me; if (me == null) { return; } try { PlayerStatus.LastUpdated = DateTime.Now; PlayerStatus.IsInTown = me.IsInTown; PlayerStatus.IsDead = me.IsDead; PlayerStatus.IsInGame = ZetaDia.IsInGame; PlayerStatus.IsLoadingWorld = ZetaDia.IsLoadingWorld; PlayerStatus.IsIncapacitated = (me.IsFeared || me.IsStunned || me.IsFrozen || me.IsBlind); PlayerStatus.IsRooted = me.IsRooted; PlayerStatus.CurrentHealthPct = me.HitpointsCurrentPct; PlayerStatus.PrimaryResource = me.CurrentPrimaryResource; PlayerStatus.PrimaryResourcePct = PlayerStatus.PrimaryResource / me.MaxPrimaryResource; PlayerStatus.SecondaryResource = me.CurrentSecondaryResource; PlayerStatus.SecondaryResourcePct = PlayerStatus.SecondaryResource / me.MaxSecondaryResource; PlayerStatus.CurrentPosition = me.Position; PlayerStatus.GoldPickupRadius = me.GoldPickupRadius; PlayerStatus.Coinage = me.Inventory.Coinage; if (PlayerStatus.PrimaryResource >= GilesTrinity.MinEnergyReserve) { PlayerStatus.WaitingForReserveEnergy = false; } if (PlayerStatus.PrimaryResource < 20) { PlayerStatus.WaitingForReserveEnergy = true; } PlayerStatus.MyDynamicID = me.CommonData.DynamicId; PlayerStatus.Level = me.Level; PlayerStatus.ActorClass = me.ActorClass; PlayerStatus.BattleTag = ZetaDia.Service.CurrentHero.BattleTagName; PlayerStatus.LevelAreaId = ZetaDia.CurrentLevelAreaId; if (PlayerStatus.ActorClass == ActorClass.WitchDoctor && HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.Witchdoctor_Hex && s.RuneIndex == 1)) { PlayerStatus.IsHidden = me.IsHidden; } else { PlayerStatus.IsHidden = false; } if (DateTime.Now.Subtract(PlayerStatus.Scene.LastUpdate).TotalMilliseconds > 1000 && GilesTrinity.Settings.Combat.Misc.UseNavMeshTargeting) { int CurrentSceneSNO = -1; CurrentSceneSNO = (int)ZetaDia.Me.SceneId; if (PlayerStatus.SceneId != CurrentSceneSNO) { PlayerStatus.SceneId = CurrentSceneSNO; DbHelper.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Updating Grid Provider", true); NavHelper.UpdateSearchGridProvider(); } } // World ID safety caching incase it's ever unavailable GilesTrinity.CurrentWorldDynamicId = ZetaDia.CurrentWorldDynamicId; PlayerStatus.WorldDynamicID = ZetaDia.CurrentWorldDynamicId; PlayerStatus.WorldID = ZetaDia.CurrentWorldId; GilesTrinity.cachedStaticWorldId = ZetaDia.CurrentWorldId; // Game difficulty, used really for vault on DH's GilesTrinity.iCurrentGameDifficulty = ZetaDia.Service.CurrentHero.CurrentDifficulty; // Refresh player buffs (to check for archon) RefreshBuffs(); } catch (Exception ex) { DbHelper.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Safely handled exception for grabbing player data.{0}{1}", Environment.NewLine, ex); } } }
private static TrinityPower GetDemonHunterPower(bool IsCurrentlyAvoiding, bool UseOOCBuff, bool UseDestructiblePower) { // Pick the best destructible power available if (UseDestructiblePower) { return(GetDemonHunterDestroyPower()); } MinEnergyReserve = 25; // Shadow Power if (!UseOOCBuff && Hotbar.Contains(SNOPower.DemonHunter_ShadowPower) && !PlayerStatus.IsIncapacitated && PlayerStatus.SecondaryResource >= 14 && (PlayerStatus.CurrentHealthPct <= 0.99 || PlayerStatus.IsRooted || ElitesWithinRange[RANGE_25] >= 1 || AnythingWithinRange[RANGE_15] >= 3) && GilesUseTimer(SNOPower.DemonHunter_ShadowPower)) { return(new TrinityPower(SNOPower.DemonHunter_ShadowPower, 0f, vNullLocation, CurrentWorldDynamicId, -1, 1, 1, WAIT_FOR_ANIM)); } // Smoke Screen if ((!UseOOCBuff || Settings.Combat.DemonHunter.SpamSmokeScreen) && Hotbar.Contains(SNOPower.DemonHunter_SmokeScreen) && !GetHasBuff(SNOPower.DemonHunter_ShadowPower) && PlayerStatus.SecondaryResource >= 14 && ( (PlayerStatus.CurrentHealthPct <= 0.90 || PlayerStatus.IsRooted || ElitesWithinRange[RANGE_20] >= 1 || AnythingWithinRange[RANGE_15] >= 3 || PlayerStatus.IsIncapacitated) || Settings.Combat.DemonHunter.SpamSmokeScreen ) && GilesUseTimer(SNOPower.DemonHunter_SmokeScreen)) { return(new TrinityPower(SNOPower.DemonHunter_SmokeScreen, 0f, vNullLocation, CurrentWorldDynamicId, -1, 1, 1, WAIT_FOR_ANIM)); } // Preparation if ( ( ((!UseOOCBuff && !PlayerStatus.IsIncapacitated && AnythingWithinRange[RANGE_40] >= 1) || Settings.Combat.DemonHunter.SpamPreparation) ) && Hotbar.Contains(SNOPower.DemonHunter_Preparation) && PlayerStatus.SecondaryResource <= 10 && //GilesUseTimer(SNOPower.DemonHunter_Preparation) && //PowerManager.CanCast(SNOPower.DemonHunter_Preparation) TrinityPowerManager.CanUse(SNOPower.DemonHunter_Preparation) ) { return(new TrinityPower(SNOPower.DemonHunter_Preparation, 0f, vNullLocation, CurrentWorldDynamicId, -1, 1, 1, WAIT_FOR_ANIM)); } // Evasive Fire if (!UseOOCBuff && Hotbar.Contains(SNOPower.DemonHunter_EvasiveFire) && !PlayerStatus.IsIncapacitated && (((AnythingWithinRange[RANGE_20] >= 1 || CurrentTarget.RadiusDistance <= 20f) && GilesUseTimer(SNOPower.DemonHunter_EvasiveFire)) || DHHasNoPrimary())) { float range = DHHasNoPrimary() ? 70f : 0f; return(new TrinityPower(SNOPower.DemonHunter_EvasiveFire, range, vNullLocation, -1, CurrentTarget.ACDGuid, 1, 1, WAIT_FOR_ANIM)); } // Companion if (!PlayerStatus.IsIncapacitated && Hotbar.Contains(SNOPower.DemonHunter_Companion) && iPlayerOwnedDHPets == 0 && PlayerStatus.SecondaryResource >= 10 && GilesUseTimer(SNOPower.DemonHunter_Companion)) { return(new TrinityPower(SNOPower.DemonHunter_Companion, 0f, vNullLocation, CurrentWorldDynamicId, -1, 2, 1, WAIT_FOR_ANIM)); } // Sentry Turret if (!UseOOCBuff && !PlayerStatus.IsIncapacitated && Hotbar.Contains(SNOPower.DemonHunter_Sentry) && (TargetUtil.AnyElitesInRange(50) || TargetUtil.AnyMobsInRange(50, 2) || TargetUtil.IsEliteTargetInRange(50)) && PlayerStatus.PrimaryResource >= 30 && PowerManager.CanCast(SNOPower.DemonHunter_Sentry)) { return(new TrinityPower(SNOPower.DemonHunter_Sentry, 0f, PlayerStatus.CurrentPosition, CurrentWorldDynamicId, -1, 0, 0, NO_WAIT_ANIM)); } // Marked for Death if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_MarkedForDeath) && PlayerStatus.SecondaryResource >= 3 && (ElitesWithinRange[RANGE_40] >= 1 || AnythingWithinRange[RANGE_40] >= 3 || ((CurrentTarget.IsEliteRareUnique || CurrentTarget.IsTreasureGoblin || CurrentTarget.IsBoss) && CurrentTarget.Radius <= 40 && CurrentTarget.RadiusDistance <= 40f)) && GilesUseTimer(SNOPower.DemonHunter_MarkedForDeath)) { return(new TrinityPower(SNOPower.DemonHunter_MarkedForDeath, 40f, vNullLocation, CurrentWorldDynamicId, CurrentTarget.ACDGuid, 1, 1, WAIT_FOR_ANIM)); } // Vault if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Vault) && !PlayerStatus.IsRooted && !PlayerStatus.IsIncapacitated && // Only use vault to retreat if < level 60, or if in inferno difficulty for level 60's (PlayerStatus.Level < 60 || iCurrentGameDifficulty == GameDifficulty.Inferno) && (CurrentTarget.RadiusDistance <= 10f || AnythingWithinRange[RANGE_6] >= 1) && ((!Hotbar.Contains(SNOPower.DemonHunter_ShadowPower) && PlayerStatus.SecondaryResource >= 16) || (Hotbar.Contains(SNOPower.DemonHunter_ShadowPower) && PlayerStatus.SecondaryResource >= 22)) && //GilesUseTimer(SNOPower.DemonHunter_Vault) && DateTime.Now.Subtract(GilesTrinity.dictAbilityLastUse[SNOPower.DemonHunter_Vault]).TotalMilliseconds >= GilesTrinity.Settings.Combat.DemonHunter.VaultMovementDelay && PowerManager.CanCast(SNOPower.DemonHunter_Vault)) { Vector3 vNewTarget = MathEx.CalculatePointFrom(CurrentTarget.Position, PlayerStatus.CurrentPosition, -15f); return(new TrinityPower(SNOPower.DemonHunter_Vault, 20f, vNewTarget, CurrentWorldDynamicId, -1, 1, 2, WAIT_FOR_ANIM)); } // Rain of Vengeance if (!UseOOCBuff && Hotbar.Contains(SNOPower.DemonHunter_RainOfVengeance) && !PlayerStatus.IsIncapacitated && (AnythingWithinRange[RANGE_25] >= 3 || ElitesWithinRange[RANGE_25] >= 1) && GilesUseTimer(SNOPower.DemonHunter_RainOfVengeance) && PowerManager.CanCast(SNOPower.DemonHunter_RainOfVengeance)) { return(new TrinityPower(SNOPower.DemonHunter_RainOfVengeance, 0f, vNullLocation, CurrentWorldDynamicId, -1, 1, 1, WAIT_FOR_ANIM)); } // Cluster Arrow if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_ClusterArrow) && !PlayerStatus.IsIncapacitated && PlayerStatus.PrimaryResource >= 50 && (ElitesWithinRange[RANGE_50] >= 1 || AnythingWithinRange[RANGE_50] >= 5 || ((CurrentTarget.IsEliteRareUnique || CurrentTarget.IsTreasureGoblin || CurrentTarget.IsBoss) && CurrentTarget.RadiusDistance <= 69f)) && GilesUseTimer(SNOPower.DemonHunter_ClusterArrow)) { return(new TrinityPower(SNOPower.DemonHunter_ClusterArrow, 69f, vNullLocation, CurrentWorldDynamicId, CurrentTarget.ACDGuid, 1, 1, WAIT_FOR_ANIM)); } // Multi Shot if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Multishot) && !PlayerStatus.IsIncapacitated && PlayerStatus.PrimaryResource >= 30 && (ElitesWithinRange[RANGE_40] >= 1 || AnythingWithinRange[RANGE_40] >= 2 || ((CurrentTarget.IsEliteRareUnique || CurrentTarget.IsTreasureGoblin || CurrentTarget.IsBoss) && CurrentTarget.RadiusDistance <= 30f))) { return(new TrinityPower(SNOPower.DemonHunter_Multishot, 40f, CurrentTarget.Position, CurrentWorldDynamicId, -1, 1, 1, WAIT_FOR_ANIM)); } // Fan of Knives if (!UseOOCBuff && Hotbar.Contains(SNOPower.DemonHunter_FanOfKnives) && !PlayerStatus.IsIncapacitated && PlayerStatus.PrimaryResource >= 20 && (AnythingWithinRange[RANGE_15] >= 4 || ElitesWithinRange[RANGE_15] >= 1) && PowerManager.CanCast(SNOPower.DemonHunter_FanOfKnives)) { return(new TrinityPower(SNOPower.DemonHunter_FanOfKnives, 0f, vNullLocation, CurrentWorldDynamicId, -1, 1, 1, WAIT_FOR_ANIM)); } // Strafe spam - similar to barbarian whirlwind routine if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Strafe) && !PlayerStatus.IsIncapacitated && !PlayerStatus.IsRooted && // Only if there's 3 guys in 25 yds AnythingWithinRange[RANGE_25] >= 3 && // Check for energy reservation amounts ((PlayerStatus.PrimaryResource >= 15 && !PlayerStatus.WaitingForReserveEnergy) || PlayerStatus.PrimaryResource >= MinEnergyReserve)) { bool bGenerateNewZigZag = (DateTime.Now.Subtract(lastChangedZigZag).TotalMilliseconds >= 1500 || (vPositionLastZigZagCheck != vNullLocation && PlayerStatus.CurrentPosition == vPositionLastZigZagCheck && DateTime.Now.Subtract(lastChangedZigZag).TotalMilliseconds >= 200) || Vector3.Distance(PlayerStatus.CurrentPosition, vSideToSideTarget) <= 4f || CurrentTarget.ACDGuid != iACDGUIDLastWhirlwind); vPositionLastZigZagCheck = PlayerStatus.CurrentPosition; if (bGenerateNewZigZag) { float fExtraDistance = CurrentTarget.CentreDistance <= 10f ? 10f : 5f; //vSideToSideTarget = FindZigZagTargetLocation(CurrentTarget.vPosition, CurrentTarget.fCentreDist + fExtraDistance); vSideToSideTarget = NavHelper.FindSafeZone(false, 1, CurrentTarget.Position, false); // Resetting this to ensure the "no-spam" is reset since we changed our target location LastPowerUsed = SNOPower.None; iACDGUIDLastWhirlwind = CurrentTarget.ACDGuid; lastChangedZigZag = DateTime.Now; } return(new TrinityPower(SNOPower.DemonHunter_Strafe, 25f, vSideToSideTarget, CurrentWorldDynamicId, -1, 0, 0, WAIT_FOR_ANIM)); } // Spike Trap if (!UseOOCBuff && !PlayerStatus.IsIncapacitated && Hotbar.Contains(SNOPower.DemonHunter_SpikeTrap) && LastPowerUsed != SNOPower.DemonHunter_SpikeTrap && (ElitesWithinRange[RANGE_30] >= 1 || AnythingWithinRange[RANGE_25] > 4 || ((CurrentTarget.IsEliteRareUnique || CurrentTarget.IsTreasureGoblin || CurrentTarget.IsBoss) && CurrentTarget.RadiusDistance <= 35f)) && PlayerStatus.PrimaryResource >= 30 && GilesUseTimer(SNOPower.DemonHunter_SpikeTrap)) { // For distant monsters, try to target a little bit in-front of them (as they run towards us), if it's not a treasure goblin float fExtraDistance = 0f; if (CurrentTarget.CentreDistance > 17f && !CurrentTarget.IsTreasureGoblin) { fExtraDistance = CurrentTarget.CentreDistance - 17f; if (fExtraDistance > 5f) { fExtraDistance = 5f; } if (CurrentTarget.CentreDistance - fExtraDistance < 15f) { fExtraDistance -= 2; } } Vector3 vNewTarget = MathEx.CalculatePointFrom(CurrentTarget.Position, PlayerStatus.CurrentPosition, CurrentTarget.CentreDistance - fExtraDistance); return(new TrinityPower(SNOPower.DemonHunter_SpikeTrap, 40f, vNewTarget, CurrentWorldDynamicId, -1, 1, 1, WAIT_FOR_ANIM)); } // Caltrops if (!UseOOCBuff && Hotbar.Contains(SNOPower.DemonHunter_Caltrops) && !PlayerStatus.IsIncapacitated && PlayerStatus.SecondaryResource >= 6 && (AnythingWithinRange[RANGE_30] >= 2 || ElitesWithinRange[RANGE_40] >= 1) && GilesUseTimer(SNOPower.DemonHunter_Caltrops)) { return(new TrinityPower(SNOPower.DemonHunter_Caltrops, 0f, vNullLocation, CurrentWorldDynamicId, -1, 1, 1, WAIT_FOR_ANIM)); } //skillDict.Add("ElementalArrow", SNOPower.DemonHunter_ElementalArrow); //runeDict.Add("BallLightning", 1); //runeDict.Add("FrostArrow", 0); //runeDict.Add("ScreamingSkull", 2); //runeDict.Add("LightningBolts", 4); //runeDict.Add("NetherTentacles", 3); var hasBallLightning = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 1); var hasFrostArrow = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 0); var hasScreamingSkull = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 2); var hasLightningBolts = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 4); var hasNetherTentacles = HotbarSkills.AssignedSkills.Any(s => s.Power == SNOPower.DemonHunter_ElementalArrow && s.RuneIndex == 3); // Elemental Arrow if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_ElementalArrow) && GilesUseTimer(SNOPower.DemonHunter_ElementalArrow) && !PlayerStatus.IsIncapacitated && ((PlayerStatus.PrimaryResource >= 10 && !PlayerStatus.WaitingForReserveEnergy) || PlayerStatus.PrimaryResource >= MinEnergyReserve)) { // Players with grenades *AND* elemental arrow should spam grenades at close-range instead if (Hotbar.Contains(SNOPower.DemonHunter_Grenades) && CurrentTarget.RadiusDistance <= 18f) { return(new TrinityPower(SNOPower.DemonHunter_Grenades, 18f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 1, WAIT_FOR_ANIM)); } // Now return elemental arrow, if not sending grenades instead return(new TrinityPower(SNOPower.DemonHunter_ElementalArrow, 65f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 1, WAIT_FOR_ANIM)); } // Chakram if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Chakram) && !PlayerStatus.IsIncapacitated && // If we have elemental arrow or rapid fire, then use chakram as a 110 second buff, instead ((!Hotbar.Contains(SNOPower.DemonHunter_ClusterArrow)) || DateTime.Now.Subtract(dictAbilityLastUse[SNOPower.DemonHunter_Chakram]).TotalMilliseconds >= 110000) && ((PlayerStatus.PrimaryResource >= 10 && !PlayerStatus.WaitingForReserveEnergy) || PlayerStatus.PrimaryResource >= MinEnergyReserve)) { return(new TrinityPower(SNOPower.DemonHunter_Chakram, 50f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 1, WAIT_FOR_ANIM)); } // Rapid Fire if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_RapidFire) && !PlayerStatus.IsIncapacitated && ((PlayerStatus.PrimaryResource >= 20 && !PlayerStatus.WaitingForReserveEnergy) || PlayerStatus.PrimaryResource >= MinEnergyReserve)) { // Players with grenades *AND* rapid fire should spam grenades at close-range instead if (Hotbar.Contains(SNOPower.DemonHunter_Grenades) && CurrentTarget.RadiusDistance <= 18f) { return(new TrinityPower(SNOPower.DemonHunter_Grenades, 18f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 0, WAIT_FOR_ANIM)); } // Now return rapid fire, if not sending grenades instead return(new TrinityPower(SNOPower.DemonHunter_RapidFire, 50f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 0, NO_WAIT_ANIM)); } // Impale if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Impale) && !PlayerStatus.IsIncapacitated && (AnythingWithinRange[RANGE_12] <= 3) && ((PlayerStatus.PrimaryResource >= 25 && !PlayerStatus.WaitingForReserveEnergy) || PlayerStatus.PrimaryResource >= MinEnergyReserve) && CurrentTarget.RadiusDistance <= 50f) { return(new TrinityPower(SNOPower.DemonHunter_Impale, 50f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 1, WAIT_FOR_ANIM)); } // Hungering Arrow if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_HungeringArrow) && !PlayerStatus.IsIncapacitated) { return(new TrinityPower(SNOPower.DemonHunter_HungeringArrow, 50f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 0, WAIT_FOR_ANIM)); } // Entangling shot if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_EntanglingShot) && !PlayerStatus.IsIncapacitated) { return(new TrinityPower(SNOPower.DemonHunter_EntanglingShot, 50f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 0, WAIT_FOR_ANIM)); } // Bola Shot if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_BolaShot) && !PlayerStatus.IsIncapacitated) { return(new TrinityPower(SNOPower.DemonHunter_BolaShot, 50f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 1, WAIT_FOR_ANIM)); } // Grenades if (!UseOOCBuff && !IsCurrentlyAvoiding && Hotbar.Contains(SNOPower.DemonHunter_Grenades) && !PlayerStatus.IsIncapacitated) { return(new TrinityPower(SNOPower.DemonHunter_Grenades, 40f, vNullLocation, -1, CurrentTarget.ACDGuid, 0, 1, WAIT_FOR_ANIM)); } // Default attacks if (!UseOOCBuff && !IsCurrentlyAvoiding) { return(new TrinityPower(GetDefaultWeaponPower(), GetDefaultWeaponDistance(), vNullLocation, CurrentWorldDynamicId, CurrentTarget.ACDGuid, 0, 0, WAIT_FOR_ANIM)); } return(new TrinityPower(SNOPower.None, -1, vNullLocation, -1, -1, 0, 0, WAIT_FOR_ANIM)); }