示例#1
0
        /// <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);
            }
        }