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