Пример #1
0
        /// <summary>
        ///  This behavior SHOULD be called at top of the combat behavior. This behavior won't let the rest of the combat behavior to be called
        /// if you don't have a target. Also it will find a proper target, if the current target is dead or you don't have a target and still in combat.
        /// Tank targeting is also dealed in this behavior.
        /// </summary>
        /// <returns></returns>
        public static Composite EnsureTarget()
        {
            return
                (new Decorator(
                     ret => !SingularSettings.DisableAllTargeting,
                     new PrioritySelector(

                         #region Switch from Current Target if a more important one exists!

                         new PrioritySelector(

                             #region Validate our CurrentTarget - ctx set to null if we need a new one, non-null if ok!

                             ctx =>
            {
                #region Tank Targeting
                // Handle tank targeting - only if in Combat, otherwise we'll choose based upon Targeting list
                if (TankManager.NeedTankTargeting && !SingularSettings.Instance.DisableTankTargetSwitching && Group.MeIsTank && StyxWoW.Me.Combat && TankManager.Instance.FirstUnit != null)
                {
                    if (Me.CurrentTarget != TankManager.Instance.FirstUnit)
                    {
                        if (TankManager.TargetingTimer.IsFinished)
                        {
                            Logger.Write(targetColor, "TankTarget: switching to first unit of TankTargeting");
                            return TankManager.Instance.FirstUnit;
                        }

                        if (!Unit.ValidUnit(Me.CurrentTarget, showReason: false))
                        {
                            Logger.Write(targetColor, "TankTarget: CurrentTarget invalid, switching to first unit of TankTargeting");
                            return TankManager.Instance.FirstUnit;
                        }
                    }

                    return Me.CurrentTarget;                   // pass our currenttarget to skip setting or switching
                }
                #endregion

                #region WORLD_PVP_FIRST_AND_FOREMOST
                if (SingularRoutine.CurrentWoWContext == WoWContext.Normal && SingularSettings.Instance.TargetWorldPvpRegardless)
                {
                    // if on an enemy player, stay there
                    if (Me.GotTarget() && Me.CurrentTarget.IsPlayer && Unit.ValidUnit(Me.CurrentTarget))
                    {
                        return Me.CurrentTarget;
                    }

                    // if attacked in last 15 seconds, go after them
                    if (EventHandlers.TimeSinceAttackedByEnemyPlayer.TotalSeconds < 15)
                    {
                        WoWUnit ganker = EventHandlers.AttackingEnemyPlayer;
                        if (Unit.ValidUnit(ganker))
                        {
                            if (!Me.GotTarget() || !Me.CurrentTarget.IsPlayer || !Unit.ValidUnit(Me.CurrentTarget))
                            {
                                if (ganker != Me.CurrentTarget)
                                {
                                    Logger.Write(targetColor, "Switching to Ganker: " + ganker.SafeName() + " who attacked us first!");
                                    Logger.WriteDebug("Setting BotPoi to Kill {0}", ganker.SafeName());
                                    BotPoi.Current = new BotPoi(ganker, PoiType.Kill);
                                }

                                return ganker;
                            }
                        }
                    }
                }
                #endregion

                #region TOTEM KILLER

                if (SingularRoutine.CurrentWoWContext == WoWContext.Normal && SingularSettings.Instance.TargetCurrentTargetTotems)
                {
                    if (Me.GotTarget() && !Me.CurrentTarget.IsPlayer && Unit.ValidUnit(Me.CurrentTarget))
                    {
                        if (Me.CurrentTarget.IsTotem)
                        {
                            if (Me.CurrentTarget.SummonedByUnit != null && !Me.CurrentTarget.SummonedByUnit.IsPlayer)
                            {
                                return Me.CurrentTarget;
                            }
                        }
                        else if ((DateTime.UtcNow - timePrevTotem).TotalSeconds > 15)
                        {
                            float range = Me.IsMelee() ? 15 : 39;
                            WoWUnit totem = ObjectManager.GetObjectsOfType <WoWUnit>(false, false)
                                            .FirstOrDefault(t => t.IsTotem && guidPrevTotem != t.Guid && t.SummonedByUnitGuid == Me.CurrentTargetGuid && Unit.ValidUnit(t) && t.SpellDistance() < range);
                            if (totem != null)
                            {
                                guidPrevTotem = totem.Guid;
                                timePrevTotem = DateTime.UtcNow;
                                Logger.Write(targetColor, "Switching to Totem: {0} set by {1}", totem.Name, totem.SummonedUnit.SafeName());
                                return totem;
                            }
                        }
                    }
                }

                #endregion

#if ALWAYS_SWITCH_TO_BOTPOI
                WoWUnit unit;
                // Check botpoi (our top priority.)  we switch to BotPoi if a kill type exists and not blacklisted
                // .. if blacklisted, clear the poi to give bot a chance to do something smarter
                // .. if we are already fighting it, we keep fighting it, end of story
                if (BotPoi.Current.Type == PoiType.Kill)
                {
                    if (BotPoi.Current.AsObject == null)
                    {
                        Logger.Write(targetColor, "BotPOI is (null) --- clearing");
                        BotPoi.Clear(string.Format("Singular: (null) object was target (possibly another components error)"));
                    }
                    else
                    {
                        unit = BotPoi.Current.AsObject.ToUnit();
                        if (unit != null && !unit.IsAlive)
                        {
                            Logger.WriteDiagnostic(targetColor, "BotPOI " + unit.SafeName() + " is dead --- clearing");
                            BotPoi.Clear(string.Format("Singular: {0} is dead", unit.SafeName()));
                        }
                        else if (!Unit.ValidUnit(unit, showReason: true))
                        {
                            Logger.Write(targetColor, "BotPOI " + unit.SafeName() + " not valid --- clearing");
                            BotPoi.Clear(string.Format("Singular: {0} invalid target", unit.SafeName()));
                        }
                        else
                        {
                            if (StyxWoW.Me.CurrentTargetGuid != unit.Guid)
                            {
                                Logger.Write(targetColor, "Switching to BotPoi: " + unit.SafeName() + "!");
                            }

                            return unit;
                        }
                    }
                }
#endif
                // Go below if current target is null or dead. We have other checks to deal with that
                if (!StyxWoW.Me.GotTarget() || StyxWoW.Me.CurrentTarget.IsDead)
                {
                    return null;
                }

                // target not aggroed yet or out of range? check for adds in melee pounding us
                if (!Me.IsInGroup() && Me.Combat && ((!StyxWoW.Me.CurrentTarget.Combat && !StyxWoW.Me.CurrentTarget.Aggro && !StyxWoW.Me.CurrentTarget.PetAggro) || StyxWoW.Me.SpellDistance() > 30 || !StyxWoW.Me.CurrentTarget.InLineOfSpellSight))
                {
                    // Look for agrroed mobs next. prioritize by IsPlayer, Relative Distance, then Health
                    var target = ObjectManager.GetObjectsOfType <WoWUnit>(false, false)
                                 .Where(
                        p => p.SpellDistance() < 10 &&
                        Unit.ValidUnit(p) &&
                        (p.Aggro || p.PetAggro) &&
                        p.InLineOfSpellSight
                        )
                                 // .OrderBy(u => CalcDistancePriority(u)).ThenBy(u => u.HealthPercent)
                                 .OrderBy(u => u.HealthPercent)
                                 .FirstOrDefault();

                    if (target != null && target.Guid != Me.CurrentTargetGuid)
                    {
                        // Return the closest one to us
                        Logger.Write(targetColor, "Switching to aggroed mob pounding on me " + target.SafeName() + "!");
                        return target;
                    }
                }

                // check if current target is owned by a player
                WoWUnit pOwner = Unit.GetPlayerParent(Me.CurrentTarget);
                if (pOwner != null)
                {
                    if (!Me.CurrentTarget.CanWeAttack())
                    {
                        Logger.Write(targetColor, "CurrentTarget " + Me.CurrentTarget.SafeName() + " is a non-attackable enemy player pet so clearing target!");
                        Blacklist.Add(Me.CurrentTargetGuid, BlacklistFlags.Pull | BlacklistFlags.Combat, TimeSpan.FromSeconds(60), "Unattackable Enemy Player Pet is CurrentTarget");
                        Me.ClearTarget();
                        return null;
                    }
                    else if (Unit.ValidUnit(pOwner) && !Blacklist.Contains(pOwner, BlacklistFlags.Combat))
                    {
                        Logger.Write(targetColor, "Current target owned by a player.  Switching to " + pOwner.SafeName() + "!");
                        if (BotPoi.Current.Type == PoiType.Kill && BotPoi.Current.Guid == Me.CurrentTarget.Guid)
                        {
                            BotPoi.Clear(string.Format("Singular detected {0} as Player Owned Pet", Me.CurrentTarget.SafeName()));
                        }

                        return pOwner;
                    }
                }

                // no valid BotPoi, so let's check Targeting.FirstUnit which is Bots #1 choice
#if IGNORE_TARGETING_UNLESS_SEARCHING_FOR_NEW_TARGET
#elif BOT_FIRSTUNIT_GETS_PRIORITY
                unit = Targeting.Instance.FirstUnit;
                if (unit != null && unit.IsAlive)
                {
                    if (Blacklist.Contains(unit.Guid, BlacklistFlags.Combat))
                    {
                        Logger.Write(targetColor, "Targeting.FirstUnit " + unit.SafeName() + " is blacklisted!");
                        if (unit == Me.CurrentTarget && (Me.CurrentTarget.Combat && Me.CurrentTarget.IsTargetingMeOrPet))
                        {
                            return unit;
                        }

                        return null;
                    }

                    if (StyxWoW.Me.CurrentTarget != unit)
                    {
                        Logger.Write(targetColor, "Current target is not Bots first choice.  Switching to " + unit.SafeName() + "!");
                    }

                    return unit;
                }
#else
                foreach (var unit in Targeting.Instance.TargetList)
                {
                    if (StyxWoW.Me.CurrentTargetGuid != unit.Guid && unit.IsAlive && !Blacklist.Contains(unit.Guid, BlacklistFlags.Combat))
                    {
                        Logger.Write(targetColor, "Bot has a higher priority target available.  Switching to " + unit.SafeName() + "!");
                        return unit;
                    }
                }
#endif
                // at this point, just check its okay to kill currenttarget
                if (Blacklist.Contains(StyxWoW.Me.CurrentTargetGuid, BlacklistFlags.Combat))
                {
                    Logger.Write(targetColor, "CurrentTarget " + Me.CurrentTarget.SafeName() + " blacklisted and not in combat with so clearing target!");
                    Me.ClearTarget();
                    return null;
                }

                // valid unit? keep it then
                if (Unit.ValidUnit(Me.CurrentTarget, showReason: true))
                {
                    return Me.CurrentTarget;
                }

                if (Me.CurrentTarget.IsPlayer && !Battlegrounds.IsInsideBattleground && Me.CurrentTarget.IsHostile && !Me.CurrentTarget.CanWeAttack())
                {
                    Logger.Write(targetColor, "CurrentTarget " + Me.CurrentTarget.SafeName() + " is a non-attackable enemy player so clearing target!");
                    Blacklist.Add(Me.CurrentTargetGuid, BlacklistFlags.Pull | BlacklistFlags.Combat, TimeSpan.FromSeconds(15), "Unattackable Enemy Player is CurrentTarget");
                    Me.ClearTarget();
                }

                // otherwise, let's get a new one
                Logger.WriteDebug(targetColor, "EnsureTarget: invalid target {0}, so forcing selection of a new one...", !Me.GotTarget() ? "(null)" : Me.CurrentTarget.SafeName());
                return null;
            },

                             #endregion

                             #region Target was selected -- change target if needed, or do nothing if already current target

                             new Decorator(
                                 ret => ret != null,
                                 new Sequence(
                                     CreateClearPendingCursorSpell(RunStatus.Success),
                                     new Decorator(
                                         req => ((WoWUnit)req).Guid != StyxWoW.Me.CurrentTargetGuid,
                                         new Sequence(
                                             new Action(ret => { if (SingularSettings.Debug)
                                                                 {
                                                                     Logger.WriteDebug(targetColor, "EnsureTarget: switching to target {0}", ((WoWUnit)ret).SafeName());
                                                                 }
                                                        }),
                                             new Action(ret => ((WoWUnit)ret).Target()),
                                             new WaitContinue(2, ret => StyxWoW.Me.GotTarget() && StyxWoW.Me.CurrentTarget == (WoWUnit)ret, new ActionAlwaysSucceed()),
                                             new Action(ret => TankManager.TargetingTimer.Reset())  // cheaper to just reset than to check if we need Tank Targeting
                                             )
                                         ),

                                     // fall through to spell priority at this point as we have our target and its valid
                                     new ActionAlwaysFail()
                                     )
                                 ),

                             #endregion

                             #endregion

                             #region Target Invalid (none or dead) - Find a New one if possible

                             new Decorator(
                                 ret => ret == null,
                                 new PrioritySelector(
                                     ctx =>
            {
                // If we have a RaF leader, then use its target.
                var rafLeader = RaFHelper.Leader;
                if (rafLeader != null && rafLeader.IsValid && !rafLeader.IsMe && rafLeader.Combat &&
                    rafLeader.GotTarget() && rafLeader.CurrentTarget.IsAlive && !Blacklist.Contains(rafLeader.CurrentTarget, BlacklistFlags.Combat))
                {
                    Logger.Write(targetColor, "Current target invalid. Switching to Tanks target " + rafLeader.CurrentTarget.SafeName() + "!");
                    return rafLeader.CurrentTarget;
                }

                /*
                 *                                      // if we have BotPoi then try it
                 *                                      if (SingularRoutine.CurrentWoWContext != WoWContext.Normal && BotPoi.Current.Type == PoiType.Kill)
                 *                                      {
                 *                                          var unit = BotPoi.Current.AsObject as WoWUnit;
                 *                                          if (unit == null)
                 *                                          {
                 *                                              Logger.Write(targetColor, "Current Kill POI invalid. Clearing POI!");
                 *                                              BotPoi.Clear("Singular detected null POI");
                 *                                          }
                 *                                          else if (!unit.IsAlive)
                 *                                          {
                 *                                              Logger.Write(targetColor, "Current Kill POI dead. Clearing POI " + unit.SafeName() + "!");
                 *                                              BotPoi.Clear("Singular detected Unit is dead");
                 *                                          }
                 *                                          else if (Blacklist.Contains(unit, BlacklistFlags.Combat))
                 *                                          {
                 *                                              Logger.Write(targetColor, "Current Kill POI is blacklisted. Clearing POI " + unit.SafeName() + "!");
                 *                                              BotPoi.Clear("Singular detected Unit is Blacklisted");
                 *                                          }
                 *                                          else
                 *                                          {
                 *                                              Logger.Write(targetColor, "Current target invalid. Switching to POI " + unit.SafeName() + "!");
                 *                                              return unit;
                 *                                          }
                 *                                      }
                 */
                // Look for agrroed mobs next. prioritize by IsPlayer, Relative Distance, then Health
                var target = Targeting.Instance.TargetList
                             .Where(
                    p => !Blacklist.Contains(p, BlacklistFlags.Combat) &&
                    Unit.ValidUnit(p)
                    // && p.DistanceSqr <= 40 * 40  // dont restrict check to 40 yds
                    && (p.Aggro || p.PetAggro || (p.Combat && p.GotTarget() && (p.IsTargetingMeOrPet || p.IsTargetingMyRaidMember))))
                             .OrderBy(u => u.IsPlayer)
                             .ThenBy(u => CalcDistancePriority(u))
                             .ThenBy(u => u.HealthPercent)
                             .FirstOrDefault();

                if (target != null)
                {
                    // Return the closest one to us
                    Logger.Write(targetColor, "Current target invalid. Switching to aggroed mob " + target.SafeName() + "!");
                    return target;
                }

                /*
                 *                                      // if we have BotPoi then try it
                 *                                      if (SingularRoutine.CurrentWoWContext == WoWContext.Normal && BotPoi.Current.Type == PoiType.Kill)
                 *                                      {
                 *                                          var unit = BotPoi.Current.AsObject as WoWUnit;
                 *                                          if (unit == null)
                 *                                          {
                 *                                              Logger.Write(targetColor, "Current Kill POI invalid. Clearing POI!");
                 *                                              BotPoi.Clear("Singular detected null POI");
                 *                                          }
                 *                                          else if (!unit.IsAlive)
                 *                                          {
                 *                                              Logger.Write(targetColor, "Current Kill POI dead. Clearing POI " + unit.SafeName() + "!");
                 *                                              BotPoi.Clear("Singular detected Unit is dead");
                 *                                          }
                 *                                          else if (Blacklist.Contains(unit, BlacklistFlags.Combat))
                 *                                          {
                 *                                              Logger.Write(targetColor, "Current Kill POI is blacklisted. Clearing POI " + unit.SafeName() + "!");
                 *                                              BotPoi.Clear("Singular detected Unit is Blacklisted");
                 *                                          }
                 *                                      }
                 */
                // now anything in the target list or a Player
                target = Targeting.Instance.TargetList
                         .Where(
                    p => !Blacklist.Contains(p, BlacklistFlags.Combat) &&
                    p.IsAlive
                    // && p.DistanceSqr <= 40 * 40 // don't restrict check to 40 yds
                    )
                         .OrderBy(u => u.IsPlayer)
                         .ThenBy(u => u.DistanceSqr)
                         .FirstOrDefault();

                if (target != null)
                {
                    // Return the closest one to us
                    Logger.Write(targetColor, "Current target invalid. Switching to TargetList mob " + target.SafeName() + "!");
                    return target;
                }

                /*
                 *                                  // Cache this query, since we'll be using it for 2 checks. No need to re-query it.
                 *                                  var agroMob =
                 *                                      ObjectManager.GetObjectsOfType<WoWUnit>(false, false)
                 *                                          .Where(p => !Blacklist.Contains(p, BlacklistFlags.Combat) && p.IsHostile && !p.IsDead
                 *                                                  && !p.Mounted && p.DistanceSqr <= 70 * 70 && p.IsPlayer && p.Combat && (p.IsTargetingMeOrPet || p.IsTargetingMyRaidMember))
                 *                                          .OrderBy(u => u.DistanceSqr)
                 *                                          .FirstOrDefault();
                 *
                 *                                  if (agroMob != null)
                 *                                  {
                 *                                      if (!agroMob.IsPet || agroMob.SummonedByUnit == null)
                 *                                      {
                 *                                          Logger.Write(targetColor, "Current target invalid. Switching to player attacking us " + agroMob.SafeName() + "!");
                 *                                      }
                 *                                      else
                 *                                      {
                 *                                          Logger.Write(targetColor, "Current target invalid. Enemy player pet {0} attacking us, switching to player {1}!", agroMob.SafeName(), agroMob.SummonedByUnit.SafeName());
                 *                                          agroMob = agroMob.SummonedByUnit;
                 *                                      }
                 *
                 *                                      return agroMob;
                 *                                  }
                 */
                // Look for agrroed mobs not in targetlist for some reason next. prioritize by IsPlayer, Relative Distance, then Health
                target = Unit.UnfriendlyUnits()
                         .Where(
                    p => !Blacklist.Contains(p, BlacklistFlags.Combat) &&
                    Unit.ValidUnit(p)
                    // && p.DistanceSqr <= 40 * 40  // dont restrict check to 40 yds
                    && (p.Aggro || p.PetAggro || (p.Combat && p.GotTarget() && (p.IsTargetingMeOrPet || p.IsTargetingMyRaidMember))))
                         .OrderBy(u => u.IsPlayer)
                         .ThenBy(u => CalcDistancePriority(u))
                         .ThenBy(u => u.HealthPercent)
                         .FirstOrDefault();

                if (target != null)
                {
                    // Return the closest one to us
                    Logger.Write(targetColor, "Current target invalid. Switching to Unfriendly mob " + target.SafeName() + " attacking us!");
                    return target;
                }

                // And there's nothing left, so just return null, kthx.
                // ... but show a message about botbase still calling our Combat behavior with nothing to kill
                if (DateTime.UtcNow >= _timeNextInvalidTargetMessage)
                {
                    _timeNextInvalidTargetMessage = DateTime.UtcNow + TimeSpan.FromSeconds(5);
                    Logger.Write(targetColor, "Bot TargetList is empty, no targets available");
                }

                return null;
            },

                                     // Make sure the target is VALID. If not, then ignore this next part. (Resolves some silly issues!)
                                     new Decorator(
                                         ret => ret != null && ((WoWUnit)ret).Guid != StyxWoW.Me.CurrentTargetGuid,
                                         new Sequence(
                                             CreateClearPendingCursorSpell(RunStatus.Success),
                                             new Action(ret => Logger.WriteDebug(targetColor, "EnsureTarget: set target to chosen target {0}", ((WoWUnit)ret).SafeName())),
                                             new Action(ret => ((WoWUnit)ret).Target()),
                                             new WaitContinue(2, ret => StyxWoW.Me.GotTarget() && StyxWoW.Me.CurrentTargetGuid == ((WoWUnit)ret).Guid, new ActionAlwaysSucceed())
                                             )
                                         ),

                                     // fall through... we'll catch whether we targeted or not in next check
                                     new ActionAlwaysFail()
                                     )
                                 )
                             ),

                         #endregion
                         new Decorator(
                             req => !Me.GotTarget() || !Unit.ValidUnit(Me.CurrentTarget),
                             new Action(r =>
            {
                if (_lastTargetMessageGuid != Me.CurrentTargetGuid || _nextTargetMessageTimer.IsFinished)
                {
                    Logger.Write(targetColor, "EnsureTarget: no valid target set by " + SingularRoutine.GetBotName() + " -- skipping " + Dynamics.CompositeBuilder.CurrentBehaviorType.ToString() + " spell priority");
                    _nextTargetMessageTimer.Reset();
                }

                _lastTargetMessageGuid = Me.CurrentTargetGuid;
                return RunStatus.Success;
            })
                             )

                         )
                     ));
        }