/// <summary> /// Find fresh targets, start main BehaviorTree if needed, cast any buffs needed etc. /// </summary> /// <param name="ret"></param> /// <returns></returns> internal static bool TargetCheck(object ret) { using (new PerformanceLogger("TargetCheck")) { // If we aren't in the game or a world is loading, don't do anything yet if (!ZetaDia.IsInGame || !ZetaDia.Me.IsValid || ZetaDia.IsLoadingWorld) { return(false); } // We keep dying because we're spawning in AoE and next to 50 elites and we need to just leave the game if (DateTime.UtcNow.Subtract(Trinity.LastDeathTime).TotalSeconds < 30 && ZetaDia.Me.Inventory.Equipped.Any() && ZetaDia.Me.Inventory.Equipped.Average(i => i.DurabilityPercent) < 0.05 && !ZetaDia.IsInTown) { Logger.Log("Durability is zero, emergency leave game"); ZetaDia.Service.Party.LeaveGame(true); Thread.Sleep(11000); return(false); } if (ZetaDia.Me.IsDead) { return(false); } if (GoldInactivity.Instance.GoldInactive()) { BotMain.PauseWhile(GoldInactivity.Instance.GoldInactiveLeaveGame); return(false); } if (!HotbarRefreshTimer.IsRunning) { HotbarRefreshTimer.Start(); } if (!HasMappedPlayerAbilities || HotbarRefreshTimer.ElapsedMilliseconds > 1000 || ShouldRefreshHotbarAbilities) { PlayerInfoCache.RefreshHotbar(); // Pick an appropriate health set etc. based on class switch (Player.ActorClass) { case ActorClass.Barbarian: PlayerEmergencyHealthPotionLimit = Settings.Combat.Barbarian.PotionLevel; _playerEmergencyHealthGlobeLimit = Settings.Combat.Barbarian.HealthGlobeLevel; CombatBase.PlayerKiteDistance = Settings.Combat.Barbarian.KiteLimit; CombatBase.PlayerKiteMode = Config.Combat.KiteMode.Never; break; case ActorClass.Crusader: PlayerEmergencyHealthPotionLimit = Settings.Combat.Crusader.PotionLevel; _playerEmergencyHealthGlobeLimit = Settings.Combat.Crusader.HealthGlobeLevel; CombatBase.PlayerKiteDistance = 0; CombatBase.PlayerKiteMode = Config.Combat.KiteMode.Never; break; case ActorClass.Monk: PlayerEmergencyHealthPotionLimit = Settings.Combat.Monk.PotionLevel; _playerEmergencyHealthGlobeLimit = Settings.Combat.Monk.HealthGlobeLevel; // Monks never kite :) CombatBase.PlayerKiteDistance = 0; CombatBase.PlayerKiteMode = Config.Combat.KiteMode.Never; break; case ActorClass.Wizard: PlayerEmergencyHealthPotionLimit = Settings.Combat.Wizard.PotionLevel; _playerEmergencyHealthGlobeLimit = Settings.Combat.Wizard.HealthGlobeLevel; CombatBase.PlayerKiteDistance = Settings.Combat.Wizard.KiteLimit; CombatBase.PlayerKiteMode = Config.Combat.KiteMode.Always; break; case ActorClass.Witchdoctor: PlayerEmergencyHealthPotionLimit = Settings.Combat.WitchDoctor.PotionLevel; _playerEmergencyHealthGlobeLimit = Settings.Combat.WitchDoctor.HealthGlobeLevel; CombatBase.PlayerKiteDistance = Settings.Combat.WitchDoctor.KiteLimit; CombatBase.PlayerKiteMode = Config.Combat.KiteMode.Always; break; case ActorClass.DemonHunter: PlayerEmergencyHealthPotionLimit = Settings.Combat.DemonHunter.PotionLevel; _playerEmergencyHealthGlobeLimit = Settings.Combat.DemonHunter.HealthGlobeLevel; CombatBase.PlayerKiteDistance = Settings.Combat.DemonHunter.KiteLimit; CombatBase.PlayerKiteMode = Settings.Combat.DemonHunter.KiteMode; break; } } // Clear target current and reset key variables used during the target-handling function //CurrentTarget = null; DontMoveMeIAmDoingShit = false; _timesBlockedMoving = 0; IsAlreadyMoving = false; lastMovementCommand = DateTime.MinValue; _isWaitingForPower = false; _isWaitingAfterPower = false; _isWaitingForPotion = false; wasRootedLastTick = false; ClearBlacklists(); using (new PerformanceLogger("TargetCheck.RefreshCache")) { // Refresh Cache if needed RefreshDiaObjectCache(); } // We have a target, start the target handler! if (CurrentTarget != null) { _isWholeNewTarget = true; DontMoveMeIAmDoingShit = true; _shouldPickNewAbilities = true; return(true); } // if we just opened a horadric cache, wait around to open it if (DateTime.UtcNow.Subtract(Composites.LastFoundHoradricCache).TotalSeconds < 5) { return(true); } using (new PerformanceLogger("TargetCheck.OOCPotion")) { // Pop a potion when necessary if (Player.CurrentHealthPct <= PlayerEmergencyHealthPotionLimit) { Trinity.UsePotionIfNeeded(); } } _statusText = "[Trinity] No more targets - DemonBuddy/profile management is now in control"; if (Settings.Advanced.DebugInStatusBar && _resetStatusText) { _resetStatusText = false; BotMain.StatusText = _statusText; } // Nothing to do... do we have some maintenance we can do instead, like out of combat buffing? if (DateTime.UtcNow.Subtract(lastMaintenanceCheck).TotalMilliseconds > 150) { using (new PerformanceLogger("TargetCheck.OOCBuff")) { lastMaintenanceCheck = DateTime.UtcNow; bool isLoopingAnimation = ZetaDia.Me.LoopingAnimationEndTime > 0; if (!isLoopingAnimation && !IsReadyToTownRun && !ForceVendorRunASAP) { BarbarianCombat.AllowSprintOOC = true; DisableOutofCombatSprint = false; powerBuff = AbilitySelector(false, true, false); if (powerBuff.SNOPower != SNOPower.None) { Logger.Log(TrinityLogLevel.Verbose, LogCategory.Behavior, "Using OOC Buff: {0}", powerBuff.SNOPower.ToString()); if (powerBuff.WaitTicksBeforeUse > 0) { BotMain.PauseFor(new TimeSpan(0, 0, 0, 0, (int)powerBuff.WaitBeforeUseDelay)); } ZetaDia.Me.UsePower(powerBuff.SNOPower, powerBuff.TargetPosition, powerBuff.TargetDynamicWorldId, powerBuff.TargetACDGUID); LastPowerUsed = powerBuff.SNOPower; CacheData.AbilityLastUsed[powerBuff.SNOPower] = DateTime.UtcNow; if (powerBuff.WaitTicksAfterUse > 0) { BotMain.PauseFor(new TimeSpan(0, 0, 0, 0, (int)powerBuff.WaitAfterUseDelay)); } } } else if (isLoopingAnimation) { _keepKillRadiusExtendedForSeconds = 20; _timeKeepKillRadiusExtendedUntil = DateTime.UtcNow.AddSeconds(_keepKillRadiusExtendedForSeconds); } } } CurrentTarget = null; if ((Trinity.ForceVendorRunASAP || Trinity.IsReadyToTownRun) && TownRun.TownRunTimerRunning()) { Logger.Log(TrinityLogLevel.Info, LogCategory.UserInformation, "Waiting for town run timer (Target Check)", true); return(true); } // Ok let DemonBuddy do stuff this loop, since we're done for the moment //DbHelper.Log(TrinityLogLevel.Verbose, LogCategory.GlobalHandler, sStatusText); return(false); } }
/// <summary> /// Returns an appropriately selected TrinityPower and related information /// </summary> /// <param name="IsCurrentlyAvoiding">Are we currently avoiding?</param> /// <param name="UseOOCBuff">Buff Out Of Combat</param> /// <param name="UseDestructiblePower">Is this for breaking destructables?</param> /// <returns></returns> internal static TrinityPower AbilitySelector(bool IsCurrentlyAvoiding = false, bool UseOOCBuff = false, bool UseDestructiblePower = false) { using (new PerformanceLogger("AbilitySelector")) { if (!UseOOCBuff && CurrentTarget == null) { return(new TrinityPower()); } // See if archon just appeared/disappeared, so update the hotbar if (ShouldRefreshHotbarAbilities || Trinity.HotbarRefreshTimer.ElapsedMilliseconds > 5000) { PlayerInfoCache.RefreshHotbar(); } // Switch based on the cached character class TrinityPower power = CombatBase.CurrentPower; using (new PerformanceLogger("AbilitySelector.ClassAbility")) { switch (Player.ActorClass) { // Barbs case ActorClass.Barbarian: //power = GetBarbarianPower(IsCurrentlyAvoiding, UseOOCBuff, UseDestructiblePower); power = BarbarianCombat.GetPower(); break; // Crusader case ActorClass.Crusader: power = CrusaderCombat.GetPower(); break; // Monks case ActorClass.Monk: power = GetMonkPower(IsCurrentlyAvoiding, UseOOCBuff, UseDestructiblePower); break; // Wizards case ActorClass.Wizard: power = GetWizardPower(IsCurrentlyAvoiding, UseOOCBuff, UseDestructiblePower); break; // Witch Doctors case ActorClass.Witchdoctor: power = WitchDoctorCombat.GetPower(); break; // Demon Hunters case ActorClass.DemonHunter: power = GetDemonHunterPower(IsCurrentlyAvoiding, UseOOCBuff, UseDestructiblePower); break; } } // use IEquatable to check if they're equal if (CombatBase.CurrentPower == power) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Behavior, "Keeping {0}", CombatBase.CurrentPower.ToString()); return(CombatBase.CurrentPower); } else if (power != null && power.SNOPower != SNOPower.None) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Behavior, "Selected new {0}", power.ToString()); return(power); } else { return(defaultPower); } } }
/// <summary> /// This method will add and update necessary information about all available actors. Determines ObjectType, 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.UtcNow.Subtract(LastRefreshedCache).TotalMilliseconds < Settings.Advanced.CacheRefreshRate && !forceUpdate) { if (!UpdateCurrentTarget()) { return(false); } } LastRefreshedCache = DateTime.UtcNow; using (new PerformanceLogger("RefreshDiaObjectCache.UpdateBlock")) { CacheData.Clear(); GenericCache.MaintainCache(); GenericBlacklist.MaintainBlacklist(); // Update player-data cache, including buffs PlayerInfoCache.UpdateCachedPlayerData(); if (Player.CurrentHealthPct <= 0) { return(false); } 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(); } // Add Team HotSpots to the cache ObjectCache.AddRange(GroupHotSpots.GetCacheObjectHotSpots()); /* Fire Chains Experimental Avoidance */ if (Settings.Combat.Misc.UseExperimentalFireChainsAvoidance) { const float fireChainSize = 5f; foreach (var unit1 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains))) { foreach (var unit2 in ObjectCache.Where(u => u.MonsterAffixes.HasFlag(MonsterAffixes.FireChains)).Where(unit2 => unit1.RActorGuid != unit2.RActorGuid)) { for (float i = 0; i <= unit1.Position.Distance2D(unit2.Position); i += (fireChainSize / 4)) { Vector3 fireChainSpot = MathEx.CalculatePointFrom(unit1.Position, unit2.Position, i); if (Trinity.Player.Position.Distance2D(fireChainSpot) <= fireChainSize) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Fire Chains!"); _standingInAvoidance = true; } CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(fireChainSpot, fireChainSize, -2, "FireChains")); } } if (CacheData.TimeBoundAvoidance.Any(aoe => aoe.ActorSNO == -2)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generated {0} avoidance points for FireChains, minDistance={1} maxDistance={2}", CacheData.TimeBoundAvoidance.Count(aoe => aoe.ActorSNO == -2), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2) .Min(aoe => aoe.Position.Distance2D(Trinity.Player.Position)), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == -2) .Max(aoe => aoe.Position.Distance2D(Trinity.Player.Position))); } } } /* Beast Charge Experimental Avoidance */ if (Settings.Combat.Misc.UseExperimentalSavageBeastAvoidance) { const float beastChargePathWidth = 10f; const int beastChargerSNO = 3337; foreach (var unit1 in ObjectCache.Where(u => u.IsFacingPlayer && u.Animation == SNOAnim.Beast_start_charge_02 || u.Animation == SNOAnim.Beast_charge_02 || u.Animation == SNOAnim.Beast_charge_04)) { Vector3 endPoint = MathEx.GetPointAt(unit1.Position, 90f, unit1.Unit.Movement.Rotation); for (float i = 0; i <= unit1.Position.Distance2D(endPoint); i += (beastChargePathWidth / 4)) { Vector3 pathSpot = MathEx.CalculatePointFrom(unit1.Position, endPoint, i); Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generating BeastCharge Avoidance: {0} dist: {1}", pathSpot, pathSpot.Distance2D(unit1.Position)); if (Trinity.Player.Position.Distance2D(pathSpot) <= beastChargePathWidth) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Avoiding Beast Charger!"); _standingInAvoidance = true; } CacheData.TimeBoundAvoidance.Add(new CacheObstacleObject(pathSpot, beastChargePathWidth, beastChargerSNO, "BeastCharge")); } if (CacheData.TimeBoundAvoidance.Any(aoe => aoe.ActorSNO == beastChargerSNO)) { Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Generated {0} avoidance points for BeastCharge, minDistance={1} maxDistance={2}", CacheData.TimeBoundAvoidance.Count(aoe => aoe.ActorSNO == beastChargerSNO), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == beastChargerSNO) .Min(aoe => aoe.Position.Distance2D(Trinity.Player.Position)), CacheData.TimeBoundAvoidance.Where(aoe => aoe.ActorSNO == beastChargerSNO) .Max(aoe => aoe.Position.Distance2D(Trinity.Player.Position))); } } } /* Poison Experimental Avoidance */ // 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 hasFoundSafePoint = false; using (new PerformanceLogger("RefreshDiaObjectCache.AvoidanceCheck")) { if (Player.IsGhosted) { _standingInAvoidance = true; } // 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.UtcNow.Subtract(timeCancelledEmergencyMove).TotalMilliseconds >= cancelledEmergencyMoveForMilliseconds) { Vector3 vAnySafePoint = NavHelper.FindSafeZone(false, 1, Player.Position, true, null, true); // Ignore avoidance stuff if we're incapacitated or didn't find a safe spot we could reach if (vAnySafePoint != Vector3.Zero) { if (Settings.Advanced.LogCategories.HasFlag(LogCategory.Movement)) { Logger.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)), Player.CurrentHealthPct, CombatBase.PlayerKiteDistance); } hasFoundSafePoint = true; CurrentTarget = new TrinityCacheObject() { Position = vAnySafePoint, Type = GObjectType.Avoidance, Weight = 20000, Distance = Vector3.Distance(Player.Position, vAnySafePoint), Radius = 2f, InternalName = "SafePoint" };; } } } /* * Set Weights, assign CurrentTarget */ if (!hasFoundSafePoint) { RefreshDiaGetWeights(); RefreshSetKiting(ref KiteAvoidDestination, NeedToKite); } // Not heading straight for a safe-spot? // No valid targets but we were told to stay put? if (CurrentTarget == null && _shouldStayPutDuringAvoidance && !_standingInAvoidance) { CurrentTarget = new TrinityCacheObject() { Position = Player.Position, Type = GObjectType.Avoidance, Weight = 20000, Distance = 2f, Radius = 2f, InternalName = "StayPutPoint" }; Logger.Log(TrinityLogLevel.Debug, LogCategory.CacheManagement, "Staying Put During Avoidance"); } // Pre-townrun is too far away if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance")) { Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Pre-TownRun position is more than {0} yards away, canceling", V.I("Cache.PretownRun.MaxDistance")); TownRun.PreTownRunPosition = Vector3.Zero; TownRun.PreTownRunWorldId = -1; } // Reached pre-townrun position if (!Player.IsInTown && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= 15f) { Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Successfully returned to Pre-TownRun Position"); TownRun.PreTownRunPosition = Vector3.Zero; TownRun.PreTownRunWorldId = -1; } // After a townrun, make sure to return to original TownRun Location if (!Player.IsInTown && CurrentTarget == null && TownRun.PreTownRunPosition != Vector3.Zero && TownRun.PreTownRunWorldId == Player.WorldID && !ForceVendorRunASAP) { if (TownRun.PreTownRunPosition.Distance2D(Player.Position) > 10f && TownRun.PreTownRunPosition.Distance2D(Player.Position) <= V.F("Cache.PretownRun.MaxDistance")) { CurrentTarget = new TrinityCacheObject() { Position = TownRun.PreTownRunPosition, Type = GObjectType.Avoidance, Weight = 20000, Distance = 2f, Radius = 2f, InternalName = "PreTownRunPosition" }; Logger.Log(TrinityLogLevel.Debug, LogCategory.UserInformation, "Returning to Pre-TownRun Position"); } } using (new PerformanceLogger("RefreshDiaObjectCache.FinalChecks")) { // force to stay put if we want to town run and there's no target if (CurrentTarget == null && ForceVendorRunASAP) { DontMoveMeIAmDoingShit = true; } if (Settings.WorldObject.EnableBountyEvents) { bool eventObjectNear = ObjectCache.Any(o => o.Type == GObjectType.CursedChest || o.Type == GObjectType.CursedShrine); if (!Player.InActiveEvent) { EventStartPosition = Vector3.Zero; EventStartTime = DateTime.MinValue; } // Reset Event time while we have targts if (CurrentTarget != null && Player.InActiveEvent && eventObjectNear) { EventStartTime = DateTime.UtcNow; } if (eventObjectNear) { EventStartPosition = ObjectCache.FirstOrDefault(o => o.Type == GObjectType.CursedChest || o.Type == GObjectType.CursedShrine).Position; } var activeEvent = ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO)); const int waitTimeoutSeconds = 90; if (DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds > waitTimeoutSeconds && activeEvent != null) { CacheData.BlacklistedEvents.Add(activeEvent.QuestSNO); } if (CurrentTarget == null && Player.InActiveEvent && EventStartPosition != Vector3.Zero && DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds < waitTimeoutSeconds && activeEvent != null && !CacheData.BlacklistedEvents.Contains(activeEvent.QuestSNO)) { CurrentTarget = new TrinityCacheObject() { Position = EventStartPosition, Type = GObjectType.Avoidance, Weight = 20000, Distance = 2f, Radius = 2f, InternalName = "WaitForEvent" }; Logger.Log("Waiting for Event {0} - Time Remaining: {1:0} seconds", ZetaDia.ActInfo.ActiveQuests.FirstOrDefault(q => DataDictionary.EventQuests.Contains(q.QuestSNO)).Quest, waitTimeoutSeconds - DateTime.UtcNow.Subtract(EventStartTime).TotalSeconds); } } // 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); } if (CurrentTarget.IsUnit) { lastHadUnitInSights = DateTime.UtcNow; } if (CurrentTarget.IsBossOrEliteRareUnique) { lastHadEliteUnitInSights = DateTime.UtcNow; } if (CurrentTarget.IsBoss || CurrentTarget.IsBountyObjective) { lastHadBossUnitInSights = DateTime.UtcNow; } if (CurrentTarget.Type == GObjectType.Container) { lastHadContainerInSights = DateTime.UtcNow; } // Record the last time our target changed if (LastTargetRactorGUID != CurrentTarget.RActorGuid) { RecordTargetHistory(); Logger.Log(TrinityLogLevel.Verbose, LogCategory.Weight, "Found New Target {0} dist={1:0} IsElite={2} Radius={3:0.0} Weight={4:0} ActorSNO={5} " + "Anim={6} TargetedCount={7} Type={8} ", CurrentTarget.InternalName, CurrentTarget.Distance, CurrentTarget.IsEliteRareUnique, CurrentTarget.Radius, CurrentTarget.Weight, CurrentTarget.ActorSNO, CurrentTarget.Animation, CurrentTarget.TimesBeenPrimaryTarget, CurrentTarget.Type ); _lastPickedTargetTime = DateTime.UtcNow; _targetLastHealth = 0f; } else { // We're sticking to the same target, so update the target's health cache to check for stucks if (CurrentTarget.IsUnit) { // Check if the health has changed, if so update the target-pick time before we blacklist them again if (CurrentTarget.HitPointsPct != _targetLastHealth) { Logger.Log(TrinityLogLevel.Debug, LogCategory.Weight, "Keeping Target {0} - CurrentTarget.HitPoints: {1:0.00} TargetLastHealth: {2:0.00} ", CurrentTarget.RActorGuid, CurrentTarget.HitPointsPct, _targetLastHealth); _lastPickedTargetTime = DateTime.UtcNow; } // Now store the target's last-known health _targetLastHealth = CurrentTarget.HitPointsPct; } } } // We have a target and the cached was refreshed return(true); } }