/// <summary> /// stays within range of Tank as they move. settings configurable by user. /// </summary> /// <param name="gapCloser">ability to close distance more quickly than running (such as Roll)</param> /// <returns></returns> public static Composite CreateStayNearTankBehavior(Composite gapCloser = null) { int moveNearTank; int stopNearTank; if (gapCloser == null) gapCloser = new ActionAlwaysFail(); if (!SingularSettings.Instance.StayNearTank) return new ActionAlwaysFail(); if (SingularRoutine.CurrentWoWContext != WoWContext.Instances) return new ActionAlwaysFail(); if (IsThisBehaviorCalledDuringCombat()) { moveNearTank = Math.Max(5, SingularSettings.Instance.StayNearTankRangeCombat); stopNearTank = (moveNearTank * 7) / 10; } else { moveNearTank = Math.Max(5, SingularSettings.Instance.StayNearTankRangeRest); stopNearTank = (moveNearTank * 6) / 10; // be slightly more elastic at rest } Logger.WriteDebug("StayNearTank in {0}: will move towards at {1} yds and stop if within {2} yds", Dynamics.CompositeBuilder.CurrentBehaviorType, moveNearTank, stopNearTank); return new PrioritySelector( ctx => HealerManager.TankToStayNear, // no healing needed, then move within heal range of tank new ThrottlePasses( 1, TimeSpan.FromSeconds(5), RunStatus.Failure, new Action( t => { if (SingularSettings.Debug) { WoWUnit tankToStayNear = (WoWUnit)t; if (t != null) { ; } else if (!Group.Tanks.Any()) Logger.WriteDiagnostic(Color.HotPink, "TankToStayNear: no group members with Role=Tank"); else { Logger.WriteDebug(Color.HotPink, "TankToStayNear: {0} tanks in group", Group.Tanks.Count()); int i = 0; foreach (var tank in Group.Tanks.OrderByDescending(gt => gt == RaFHelper.Leader).ThenBy(gt => gt.DistanceSqr)) { Logger.WriteDebug(Color.HotPink, "TankToStayNear[{0}]: {1} Health={2:F1}%, Dist={3:F1}, TankPt={4}, MePt={5}, TankMov={6}, MeMov={7}, LoS={8}, LoSS={9}, Combat={10}, MeCombat={11}, ", i++, tank.SafeName(), tank.HealthPercent, tank.SpellDistance(), tank.Location, Me.Location, tank.IsMoving.ToYN(), Me.IsMoving.ToYN(), tank.InLineOfSight.ToYN(), tank.InLineOfSpellSight.ToYN(), tank.Combat.ToYN(), Me.Combat.ToYN() ); } Logger.WriteDebug(Color.HotPink, "TankToStayNear: current TargetList has {0} units", Targeting.Instance.TargetList.Count()); i = 0; foreach (var target in Targeting.Instance.TargetList) { Logger.WriteDebug(Color.HotPink, "CurrentTargets[{0}]: {1} {2:F1}% @ {3:F1}", i++, target.SafeName(), target.HealthPercent, target.SpellDistance() ); } } } return RunStatus.Failure; }) ), new Decorator( ret => ((WoWUnit)ret) != null, new Sequence( new PrioritySelector( gapCloser, Movement.CreateMoveToLosBehavior(unit => ((WoWUnit)unit)), Movement.CreateMoveToUnitBehavior(unit => ((WoWUnit)unit), moveNearTank, stopNearTank) // , Movement.CreateEnsureMovementStoppedBehavior(stopNearTank, unit => (WoWUnit)unit, "in heal range of tank") ), new ActionAlwaysFail() ) ) ); }
public static Composite CreateHunterCombatBuffs() { Composite feignDeathBehavior = new ActionAlwaysFail(); if ( SingularRoutine.CurrentWoWContext == WoWContext.Normal ) feignDeathBehavior = CreateFeignDeath( req => NeedFeignDeath, () => TimeSpan.FromSeconds(8), cancel => !Unit.NearbyUnfriendlyUnits.Any(u => u.Distance < 25)); if ( SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds ) feignDeathBehavior = CreateFeignDeath(req => NeedFeignDeath, () => TimeSpan.FromSeconds((new Random()).Next(3)), ret => false); if ( SingularRoutine.CurrentWoWContext == WoWContext.Instances && HunterSettings.FeignDeathInInstances ) feignDeathBehavior = CreateFeignDeath(req => Unit.NearbyUnitsInCombatWithMeOrMyStuff.Any( u=> u.Aggro && u.CurrentTargetGuid == Me.Guid), () => TimeSpan.FromMilliseconds(1500), ret => false); return new Decorator( req => !Unit.IsTrivial(Me.CurrentTarget), new PrioritySelector( feignDeathBehavior, // cast Pet survival abilities new Decorator( ret => Me.GotAlivePet && (Pet.HealthPercent < 35 || Pet.HealthPercent < 60 && Unit.NearbyUnfriendlyUnits.Count(u => u.CurrentTargetGuid == Pet.Guid) >= 2), new PrioritySelector( new Decorator(ret => PetManager.CanCastPetAction("Bullheaded"), new Action(ret => PetManager.CastPetAction("Bullheaded"))), new Decorator(ret => PetManager.CanCastPetAction("Last Stand"), new Action(ret => PetManager.CastPetAction("Last Stand"))) ) ), new Throttle(2, Spell.Buff("Mend Pet", onUnit => Pet, ret => Me.GotAlivePet && Pet.HealthPercent < HunterSettings.MendPetPercent)), // don't worry about wrong pet, only missing or dead pet new Decorator( req => !Me.GotAlivePet, Common.CreateHunterCallPetBehavior(true) ), Spell.Buff("Deterrence", ret => (Me.HealthPercent <= HunterSettings.DeterrenceHealth || HunterSettings.DeterrenceCount <= Unit.NearbyUnfriendlyUnits.Count(u => u.Combat && u.CurrentTargetGuid == Me.Guid && !u.IsPet))), Spell.BuffSelf("Aspect of the Hawk", ret => !Me.IsMoving && !Me.HasAnyAura("Aspect of the Hawk", "Aspect of the Iron Hawk")), new Decorator( ret => SingularRoutine.CurrentWoWContext != WoWContext.Battlegrounds, CreateMisdirectionBehavior() ), // don't use Hunter's Mark in Battlegrounds unless soloing someone /* Spell.Buff("Hunter's Mark", ret => Unit.ValidUnit(Target) && !TalentManager.HasGlyph("Marked for Death") && (SingularRoutine.CurrentWoWContext != WoWContext.Battlegrounds || !Unit.NearbyUnfriendlyUnits.Any(u => u.Guid != Target.Guid)) && !Me.CurrentTarget.IsImmune(WoWSpellSchool.Arcane)), */ Spell.BuffSelf("Exhilaration", ret => Me.HealthPercent < 35 || (Pet != null && Pet.HealthPercent < 25)), Spell.Buff("Widow Venom", ret => HunterSettings.UseWidowVenom && Target.IsPlayer && Me.IsSafelyFacing(Target) && Target.InLineOfSpellSight), // Buffs - don't stack Bestial Wrath and Rapid Fire Spell.Buff("Bestial Wrath", true, ret => Spell.GetSpellCooldown("Kill Command") == TimeSpan.Zero && !Me.HasAura("Rapid Fire"), "The Beast Within"), Spell.Cast("Stampede", ret => PartyBuff.WeHaveBloodlust || (!Me.IsInGroup() && SafeArea.AllEnemyMobsAttackingMe.Count() > 2) || (Me.GotTarget && Me.CurrentTarget.IsPlayer && Me.CurrentTarget.ToPlayer().IsHostile)), // Level 75 Talents Spell.Cast("A Murder of Crows"), Spell.Cast("Lynx Rush", ret => Pet != null && Unit.NearbyUnfriendlyUnits.Any(u => Pet.Location.Distance(u.Location) <= 10)), // Level 60 Talents Spell.Cast("Dire Beast"), Spell.Cast("Fervor", ctx => Me.CurrentFocus < 50), // Level 90 Talents Spell.Cast("Glaive Toss", req => Me.IsSafelyFacing(Me.CurrentTarget)), Spell.Cast("Powershot", req => Me.IsSafelyFacing(Me.CurrentTarget)), Spell.Cast("Barrage", req => Me.IsSafelyFacing(Me.CurrentTarget)), // for long cooldowns, spend only when worthwhile new Decorator( ret => Pet != null && Target != null && Target.IsAlive && (Target.IsBoss() || Target.IsPlayer || ScaryNPC || 3 <= Unit.NearbyUnfriendlyUnits.Count(u => u.IsTargetingMeOrPet)), new PrioritySelector( Spell.Buff("Rapid Fire", ret => !Me.HasAura("The Beast Within")), Spell.Cast("Rabid", ret => Me.HasAura("The Beast Within")), Spell.Cast("Readiness", ret => { bool readyForReadiness = true; if (SpellManager.HasSpell("Bestial Wrath")) readyForReadiness = readyForReadiness && Spell.GetSpellCooldown("Bestial Wrath").TotalSeconds.Between(5, 50); if (SpellManager.HasSpell("Rapid Fire")) readyForReadiness = readyForReadiness && Spell.GetSpellCooldown("Rapid Fire").TotalSeconds.Between(30, 165); return readyForReadiness; }) ) ), new Decorator( req => Me.GotAlivePet && PetManager.CanCastPetAction("Reflective Armor Plating") && Unit.NearbyUnfriendlyUnits.Any(u => u.CurrentTargetGuid == Me.Pet.Guid && Me.Pet.IsSafelyFacing(u, 75f)), PetManager.CastAction("Reflective Armor Plating", on => Me.Pet) ), new PrioritySelector( ctx => (!Me.GotAlivePet ? null : Unit.NearbyUnfriendlyUnits .Where(u => u.SpellDistance(Me.Pet) < 30 && Me.Pet.IsSafelyFacing(u)) .OrderBy( u => u.SpellDistance(Me.Pet)) .FirstOrDefault()), new Decorator( req => ((WoWUnit)req) != null && ((WoWUnit)req).SpellDistance(Me.Pet) < 5, new PrioritySelector( PetManager.CastAction("Sting", on => (WoWUnit) on), PetManager.CastAction("Paralyzing Quill", on => (WoWUnit) on), PetManager.CastAction("Lullaby", on => (WoWUnit) on), PetManager.CastAction("Pummel", on => (WoWUnit) on), PetManager.CastAction("Spore Cloud", on => (WoWUnit) on), PetManager.CastAction("Trample", on => (WoWUnit) on), PetManager.CastAction("Horn Toss", on => (WoWUnit) on) ) ), new Decorator( req => ((WoWUnit)req) != null && ((WoWUnit)req).SpellDistance(Me.Pet) < 20, new PrioritySelector( PetManager.CastAction("Sonic Blast", on => (WoWUnit) on), PetManager.CastAction("Petrifying Gaze", on => (WoWUnit) on), PetManager.CastAction("Lullaby", on => (WoWUnit) on), PetManager.CastAction("Bad Manner", on => (WoWUnit) on), PetManager.CastAction("Nether Shock", on => (WoWUnit) on), PetManager.CastAction("Serenity Dust", on => (WoWUnit) on) ) ), new Decorator( req => ((WoWUnit)req) != null && ((WoWUnit)req).SpellDistance(Me.Pet) < 30, new PrioritySelector( PetManager.CastAction("Web", on => (WoWUnit)on), PetManager.CastAction("Web Wrap", on => (WoWUnit)on), PetManager.CastAction("Lava Breath", on => (WoWUnit)on) ) ) ) ) ); }
public static Composite CreateHunterCombatBuffs() { Composite feignDeathBehavior = new ActionAlwaysFail(); if ( SingularRoutine.CurrentWoWContext == WoWContext.Normal ) feignDeathBehavior = CreateFeignDeath( req => NeedFeignDeath, () => TimeSpan.FromSeconds(8), cancel => !Unit.NearbyUnfriendlyUnits.Any(u => u.Distance < 25)); if ( SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds ) feignDeathBehavior = CreateFeignDeath(req => NeedFeignDeath, () => TimeSpan.FromSeconds((new Random()).Next(3)), ret => false); if ( SingularRoutine.CurrentWoWContext == WoWContext.Instances && HunterSettings.FeignDeathInInstances ) feignDeathBehavior = CreateFeignDeath(req => Unit.NearbyUnitsInCombatWithMeOrMyStuff.Any( u=> u.Aggro && u.CurrentTargetGuid == Me.Guid), () => TimeSpan.FromMilliseconds(1500), ret => false); return new Decorator( req => !Unit.IsTrivial(Target), new PrioritySelector( !SpellManager.HasSpell("Enhanced Camouflage") ? (Composite)new ActionAlwaysFail() : (Composite)new Sequence( Spell.BuffSelf("Camouflage", req => Me.HealthPercent <= HunterSettings.ImprovedCamouflageHealth), new Wait( 1, until => Me.HasAura("Camouflage"), new Action(r => { camouflageStart = DateTime.UtcNow; WoWAura aura = Me.GetAllAuras().FirstOrDefault(a => a.Name == "Camouflage"); if (aura != null) camouflageExpires = camouflageStart + aura.TimeLeft; else { Logger.WriteDiagnostic("Camouflage: cast but never saw buff appear"); camouflageExpires = DateTime.MinValue; } })) ), feignDeathBehavior, // cast Pet survival abilities new Decorator( ret => Me.GotAlivePet && (Pet.HealthPercent < 35 || Pet.HealthPercent < 60 && Unit.NearbyUnfriendlyUnits.Count(u => u.CurrentTargetGuid == Pet.Guid) >= 2), new PrioritySelector( new Decorator(ret => PetManager.CanCastPetAction("Bullheaded"), new Action(ret => PetManager.CastPetAction("Bullheaded"))), new Decorator(ret => PetManager.CanCastPetAction("Last Stand"), new Action(ret => PetManager.CastPetAction("Last Stand"))) ) ), new Throttle(2, Spell.Buff("Mend Pet", onUnit => Pet, ret => Me.GotAlivePet && Pet.HealthPercent < HunterSettings.MendPetPct)), // don't worry about wrong pet, only missing or dead pet new Decorator( req => !Me.GotAlivePet, Common.CreateHunterCallPetBehavior(true) ), Spell.Buff("Deterrence", ret => (Me.HealthPercent <= HunterSettings.DeterrenceHealth || HunterSettings.DeterrenceCount <= Unit.NearbyUnfriendlyUnits.Count(u => u.Combat && u.CurrentTargetGuid == Me.Guid && !u.IsPet))), new Decorator( ret => SingularRoutine.CurrentWoWContext != WoWContext.Battlegrounds, CreateMisdirectionBehavior() ), new Decorator( req => Me.GotTarget() && Me.IsSafelyFacing(Target) && Target.InLineOfSpellSight, new PrioritySelector( Spell.BuffSelf("Exhilaration", ret => Me.HealthPercent < 35 || (Pet != null && Pet.HealthPercent < 20)), Spell.Buff("Widow Venom", ret => HunterSettings.UseWidowVenom && Target.IsPlayer && Me.IsSafelyFacing(Target) && Target.InLineOfSpellSight), // Buffs - don't stack Bestial Wrath and Rapid Fire Spell.Buff( "Bestial Wrath", on => Me, req => Spell.GetSpellCooldown("Kill Command") == TimeSpan.Zero && !Me.HasAura("Rapid Fire"), HasGcd.No, "The Beast Within" ), Spell.Cast("Stampede", ret => PartyBuff.WeHaveBloodlust || (!Me.IsInGroup() && SafeArea.AllEnemyMobsAttackingMe.Count() > 2) || (Me.GotTarget() && Target.IsPlayer && Target.ToPlayer().IsHostile)), // Level 75 Talents Spell.Cast("A Murder of Crows"), // Level 60 Talents Spell.Cast("Dire Beast"), // Level 90 Talents Spell.Cast( "Glaive Toss", on => { if (!Me.GotTarget()) return null; WoWPoint loc = WoWPoint.RayCast(Me.Location, WoWMathHelper.CalculateNeededFacing(Me.Location, Target.Location), 40f); IEnumerable<WoWUnit> ienum = Clusters.GetPathToPointCluster(loc, Unit.UnfriendlyUnits(44), 44); int cntCC = 0; int cntTarget = 0; int cntNeutral = 0; WoWUnit target = null; foreach (WoWUnit u in ienum) { cntTarget++; if (u.IsCrowdControlled()) cntCC++; if (!u.Combat && !u.IsTrivial() && !u.Aggro && !u.PetAggro && !(u.IsTargetingMeOrPet || u.IsTargetingMyRaidMember)) cntNeutral++; if (target == null) target = u; if (TargetGuid == u.Guid) target = u; } if (cntNeutral > 0) { Logger.WriteDebug("Glaive Toss: skipping, {0} additional targets would be pulled", cntNeutral); return null; } if (cntCC > 0) { Logger.WriteDebug("Glaive Toss: skipping, {0} crowd controlled targets", cntCC); return null; } if (cntTarget == 0) { Logger.WriteDebug("Glaive Toss: skipping, no targets would be hit"); return null; } return target; } ), Spell.Cast( "Powershot", on => { if (Target == null) return null; int dist = (int)Target.Distance + 4; WoWPoint loc = WoWPoint.RayCast(Me.Location, WoWMathHelper.CalculateNeededFacing(Me.Location, Target.Location), 40f); IEnumerable<WoWUnit> ienum = Clusters.GetPathToPointCluster(loc, Unit.UnfriendlyUnits(dist), dist); int cntCC = 0; int cntTarget = 0; int cntNeutral = 0; WoWUnit target = null; foreach (WoWUnit u in ienum) { cntTarget++; if (u.IsCrowdControlled()) cntCC++; if (!u.Combat && !u.IsTrivial() && !u.Aggro && !u.PetAggro && !(u.IsTargetingMeOrPet || u.IsTargetingMyRaidMember)) cntNeutral++; if (target == null) target = u; if (TargetGuid == u.Guid) target = u; } if (cntNeutral > 0) { Logger.WriteDebug("Powershot: skipping, {0} additional targets would be pulled", cntNeutral); return null; } if (cntCC > 0) { Logger.WriteDebug("Powershot: skipping, {0} crowd controlled targets", cntCC); return null; } if (cntTarget == 0) { Logger.WriteDebug("Powershot: skipping, no targets would be hit"); return null; } return target; } ), Spell.Cast( "Barrage", on => { // Does not require CurrentTarget to be non=null WoWPoint loc = WoWPoint.RayCast(Me.Location, Me.RenderFacing, 30f); IEnumerable<WoWUnit> ienum = Clusters.GetConeCluster(100f, 46f, Unit.UnfriendlyUnits(50)); int cntCC = 0; int cntTarget = 0; int cntNeutral = 0; WoWUnit target = null; foreach (WoWUnit u in ienum) { cntTarget++; if (u.IsCrowdControlled()) cntCC++; if (!u.Combat && !u.IsTrivial() && !u.Aggro && !u.PetAggro && !(u.IsTargetingMeOrPet || u.IsTargetingMyRaidMember)) cntNeutral++; if (target == null) target = u; if (TargetGuid == u.Guid) target = u; } if (cntNeutral > 0) { Logger.WriteDebug("Barrage: skipping, {0} additional targets would be pulled", cntNeutral); return null; } if (cntCC > 0) { Logger.WriteDebug("Barrage: skipping, {0} crowd controlled targets", cntCC); return null; } if (cntTarget == 0) { Logger.WriteDebug("Barrage: skipping, no targets would be hit"); return null; } return target; } ), // for long cooldowns, spend only when worthwhile new Decorator( ret => Pet != null && Target != null && Target.IsAlive && (Target.IsBoss() || Target.IsPlayer || ScaryNPC || 3 <= Unit.NearbyUnfriendlyUnits.Count(u => u.IsTargetingMeOrPet)), new PrioritySelector( Spell.Buff("Rapid Fire", ret => Me.Specialization == WoWSpec.HunterMarksmanship && !Me.HasAura("The Beast Within")) ) ), new Decorator( req => (!Me.GotAlivePet || Me.Pet.HealthPercent < 20) && PetManager.IsPetUseAllowed && Me.GotTarget() && Target.SpellDistance().Between(15, 40), new PrioritySelector( // CreateHunterTrapBehavior("Freezing Trap", true, on => Target, ret => SingularRoutine.CurrentWoWContext != WoWContext.Battlegrounds || Me.FocusedUnit == null), new PrioritySelector( ctx => WoWMathHelper.CalculatePointFrom(Me.Location, Target.Location, 12f + Target.CombatReach), CreateHunterTrapBehavior("Ice Trap", loc => (WoWPoint)loc, ret => SingularRoutine.CurrentWoWContext != WoWContext.Battlegrounds || Me.FocusedUnit == null) ) ) ) ) ), new Decorator( req => Me.GotAlivePet && PetManager.CanCastPetAction("Reflective Armor Plating") && Unit.NearbyUnfriendlyUnits.Any(u => u.CurrentTargetGuid == Me.Pet.Guid && Me.Pet.IsSafelyFacing(u, 75f)), PetManager.CastAction("Reflective Armor Plating", on => Me.Pet) ), new PrioritySelector( ctx => (!Me.GotAlivePet ? null : Unit.NearbyUnfriendlyUnits .Where(u => u.SpellDistance(Me.Pet) < 30 && Me.Pet.IsSafelyFacing(u)) .OrderBy(u => u.SpellDistance(Me.Pet)) .FirstOrDefault()), new Decorator( req => ((WoWUnit)req) != null && ((WoWUnit)req).SpellDistance(Me.Pet) < 5, new PrioritySelector( PetManager.CastAction("Sting", on => (WoWUnit)on), PetManager.CastAction("Paralyzing Quill", on => (WoWUnit)on), PetManager.CastAction("Lullaby", on => (WoWUnit)on), PetManager.CastAction("Pummel", on => (WoWUnit)on), PetManager.CastAction("Spore Cloud", on => (WoWUnit)on), PetManager.CastAction("Trample", on => (WoWUnit)on), PetManager.CastAction("Horn Toss", on => (WoWUnit)on) ) ), new Decorator( req => ((WoWUnit)req) != null && ((WoWUnit)req).SpellDistance(Me.Pet) < 20, new PrioritySelector( PetManager.CastAction("Sonic Blast", on => (WoWUnit)on), PetManager.CastAction("Petrifying Gaze", on => (WoWUnit)on), PetManager.CastAction("Lullaby", on => (WoWUnit)on), PetManager.CastAction("Bad Manner", on => (WoWUnit)on), PetManager.CastAction("Nether Shock", on => (WoWUnit)on), PetManager.CastAction("Serenity Dust", on => (WoWUnit)on) ) ), new Decorator( req => ((WoWUnit)req) != null && ((WoWUnit)req).SpellDistance(Me.Pet) < 30, new PrioritySelector( PetManager.CastAction("Web", on => (WoWUnit)on), PetManager.CastAction("Web Wrap", on => (WoWUnit)on), PetManager.CastAction("Lava Breath", on => (WoWUnit)on) ) ) ) ) ); }
/// <summary> /// /// </summary> /// <param name="gapCloser"></param> /// <returns></returns> public static Composite CreateRangedHealerMovementBehavior(Composite gapCloser = null) { int moveNearTank; int stopNearTank; if (SingularRoutine.CurrentWoWContext != WoWContext.Instances) return new ActionAlwaysFail(); if (!SingularSettings.Instance.StayNearTank) return Movement.CreateMoveBehindTargetBehavior(); if (gapCloser == null) gapCloser = new ActionAlwaysFail(); bool incombat = (Dynamics.CompositeBuilder.CurrentBehaviorType & BehaviorType.InCombat) != (BehaviorType)0; if (incombat) { moveNearTank = Math.Max(5, SingularSettings.Instance.StayNearTankRangeCombat); stopNearTank = Math.Max(moveNearTank / 2, moveNearTank - 5); } else { moveNearTank = Math.Max(5, SingularSettings.Instance.StayNearTankRangeRest); stopNearTank = Math.Max(moveNearTank / 2, moveNearTank - 5); } return new PrioritySelector( ctx => (Me.Combat && Me.GotTarget() && Unit.ValidUnit(Me.CurrentTarget)) ? Me.CurrentTarget : null, new Decorator( ret => ret != null, new Sequence( new PrioritySelector( gapCloser, Movement.CreateMoveToLosBehavior(), Movement.CreateMoveToUnitBehavior(on => Me.CurrentTarget, 30, 25), Movement.CreateEnsureMovementStoppedBehavior(25f), // to account for Mistweaver Monks facing Soothing Mist target automatically new Decorator( req => !Spell.IsChannelling(), Movement.CreateFaceTargetBehavior() ) ), new ActionAlwaysFail() ) ), new Decorator( ret => ret == null, new Sequence( CreateStayNearTankBehavior(gapCloser), new ActionAlwaysFail() ) ) ); }
private static Composite AddCommonBehaviorPrefix( Composite composite, BehaviorType behav) { if (behav == BehaviorType.LossOfControl) { composite = new Decorator( ret => HaveWeLostControl, new PrioritySelector( new Action(r => { if (!StyxWoW.IsInGame) { Logger.WriteDebug(Color.White, "Not in game..."); return RunStatus.Success; } return RunStatus.Failure; }), new ThrottlePasses(1, 1, new Decorator(ret => Me.Fleeing, new Action(r => { Logger.Write( LogColor.Hilite, "FLEEING! (loss of control)"); return RunStatus.Failure; }))), new ThrottlePasses(1, 1, new Decorator(ret => Me.Stunned, new Action(r => { Logger.Write( LogColor.Hilite, "STUNNED! (loss of control)"); return RunStatus.Failure; }))), new ThrottlePasses(1, 1, new Decorator(ret => Me.IsSilenced(), new Action(r => { Logger.Write( LogColor.Hilite, "SILENCED! (loss of control)"); return RunStatus.Failure; }))), new Throttle(1, new PrioritySelector( composite ?? new ActionAlwaysFail(), new Decorator( ret => SingularSettings.Instance.UseRacials, new PrioritySelector( Spell.Cast("Will of the Forsaken", on => Me, ret => Me.Race == WoWRace.Undead && Me.Fleeing), Spell.Cast("Every Man for Himself", on => Me, ret => Me.Race == WoWRace.Human && (Me.Stunned || Me.Fleeing)) ) ), Item.UseEquippedTrinket(TrinketUsage.CrowdControlled), Item.UseEquippedTrinket(TrinketUsage.CrowdControlledSilenced) ) ), new ActionAlwaysSucceed() ) ); } if (behav == BehaviorType.Rest) { Composite combatInRestCheck; if (!SingularSettings.Debug) combatInRestCheck = new ActionAlwaysFail(); else combatInRestCheck = new Throttle( 1, new ThrottlePasses( 1, TimeSpan.FromSeconds(1), RunStatus.Failure, new Decorator( req => Me.IsActuallyInCombat, new Sequence( new PrioritySelector( new ThrottlePasses( 1, 5, new Action(r => Logger.Write(Color.Yellow, "Bot Error: {0} or plugin called Rest behavior while in combat", SingularRoutine.GetBotName())) ), new Action(r => Logger.WriteDebug(Color.Yellow, "Bot Error: {0} or plugin called Rest behavior while in combat", SingularRoutine.GetBotName())) ), new ActionAlwaysFail() ) ) ) ); composite = new LockSelector( new CallWatch("Rest", new Decorator( ret => !Me.IsFlying && AllowBehaviorUsage() && !SingularSettings.Instance.DisableNonCombatBehaviors, new PrioritySelector( // TestDynaWait(), // following is to allow cancelling of kiting in progress new Decorator( ret => Kite.IsKitingActive(), new HookExecutor(HookName("KitingBehavior")) ), new Decorator( req => ShouldWeFightDuringRest(), new PrioritySelector( Safers.EnsureTarget(), new Decorator( req => Unit.ValidUnit(Me.CurrentTarget) && Me.CurrentTarget.SpellDistance() < 45, new PrioritySelector( new ThrottlePasses( 1, TimeSpan.FromSeconds(15), RunStatus.Failure, new Action(r => Logger.WriteDiagnostic(LogColor.Hilite, "Forcing combat from Rest Behavior")), new ActionAlwaysFail() ), SingularRoutine.Instance.HealBehavior, SingularRoutine.Instance.CombatBuffBehavior, SingularRoutine.Instance.CombatBehavior, new ActionAlwaysSucceed() ) ) ) ), // new Action(r => { _guidLastTarget = 0; return RunStatus.Failure; }), new Decorator( req => !OkToCallBehaviorsWithCurrentCastingStatus(allow: LagTolerance.No), new ActionAlwaysSucceed() ), // lost control in Rest -- force a RunStatus.Failure so we don't loop in Rest new Sequence( SingularRoutine.Instance._lostControlBehavior, new ActionAlwaysFail() ), // skip Rest logic if we lost control (since we had to return Fail to prevent Rest loop) new Decorator( req => !HaveWeLostControl, composite ?? new ActionAlwaysFail() ) ) ) ) ); } if (behav == BehaviorType.PreCombatBuffs) { Composite precombatInCombatCheck; if (!SingularSettings.Debug) precombatInCombatCheck = new ActionAlwaysFail(); else precombatInCombatCheck = new Throttle( 1, new ThrottlePasses( 1, TimeSpan.FromSeconds(1), RunStatus.Failure, new Decorator( req => Me.IsActuallyInCombat, new Sequence( new PrioritySelector( new ThrottlePasses( 1, 5, new Action(r => Logger.Write(Color.Yellow, "Bot Error: {0} or plugin called PreCombatBuff behavior while in combat", SingularRoutine.GetBotName())) ), new Action(r => Logger.WriteDebug(Color.Yellow, "Bot Error: {0} or plugin called PreCombatBuff behavior while in combat", SingularRoutine.GetBotName())) ), new ActionAlwaysFail() ) ) ) ); composite = new LockSelector( new CallWatch("PreCombat", new Decorator( // suppress non-combat buffing if standing around waiting on DungeonBuddy or BGBuddy queues ret => !Me.Mounted && !Me.HasAnyAura("Darkflight") && !SingularSettings.Instance.DisableNonCombatBehaviors && AllowNonCombatBuffing(), new PrioritySelector( new Decorator( req => !OkToCallBehaviorsWithCurrentCastingStatus(), Spell.WaitForGcdOrCastOrChannel() ), Helpers.Common.CreateUseTableBehavior(), Helpers.Common.CreateUseSoulwellBehavior(), Item.CreateUseFlasksBehavior(), Item.CreateUseScrollsBehavior(), composite ?? new ActionAlwaysFail() ) ) ) ); } if (behav == BehaviorType.PullBuffs) { composite = new LockSelector( new CallWatch("PullBuffs", new Decorator( ret => AllowBehaviorUsage() && OkToCallBehaviorsWithCurrentCastingStatus(), composite ?? new ActionAlwaysFail() ) ) ); } if (behav == BehaviorType.Pull) { composite = new LockSelector( new CallWatch("Pull", new Decorator( ret => AllowBehaviorUsage(), // && (!Me.GotTarget() || !Blacklist.Contains(Me.CurrentTargetGuid, BlacklistFlags.Combat)), new PrioritySelector( new Decorator( ret => !HotkeyDirector.IsCombatEnabled, new ActionAlwaysSucceed() ), #if BOTS_NOT_CALLING_PULLBUFFS _pullBuffsBehavior, #endif CreateLogTargetChanges(BehaviorType.Pull, "<<< PULL >>>"), Item.CreateThunderLordGrappleBehavior(), composite ?? new ActionAlwaysFail() ) ) ) ); } if (behav == BehaviorType.Heal) { Composite behavHealingSpheres = new ActionAlwaysFail(); if (SingularSettings.Instance.MoveToSpheres) { behavHealingSpheres = new ThrottlePasses( 1, 1, new Decorator( ret => Me.HealthPercent < SingularSettings.Instance.SphereHealthPercentInCombat && Singular.ClassSpecific.Monk.Common.AnySpheres(SphereType.Life, SingularSettings.Instance.SphereDistanceInCombat), Singular.ClassSpecific.Monk.Common.CreateMoveToSphereBehavior(SphereType.Life, SingularSettings.Instance.SphereDistanceInCombat) ) ); } composite = new LockSelector( new CallWatch("Heal", // following must occur before any cast or movement during Combat Generic.CreateCancelShadowmeld(), SingularRoutine.Instance._lostControlBehavior, new Decorator( ret => Kite.IsKitingActive(), new HookExecutor(HookName("KitingBehavior")) ), new Decorator( ret => AllowBehaviorUsage() && OkToCallBehaviorsWithCurrentCastingStatus(), new PrioritySelector( Generic.CreatePotionAndHealthstoneBehavior(), composite ?? new ActionAlwaysFail(), behavHealingSpheres ) ) ) ); } if (behav == BehaviorType.CombatBuffs) { composite = new LockSelector( new CallWatch("CombatBuffs", new Decorator( ret => AllowBehaviorUsage() && OkToCallBehaviorsWithCurrentCastingStatus(), new PrioritySelector( new Decorator(ret => !HotkeyDirector.IsCombatEnabled, new ActionAlwaysSucceed()), Generic.CreateUseTrinketsBehaviour(), Generic.CreatePotionAndHealthstoneBehavior(), Item.CreateUseXPBuffPotionsBehavior(), Generic.CreateRacialBehaviour(), Generic.CreateGarrisonAbilityBehaviour(), composite ?? new ActionAlwaysFail() ) ) ) ); } if (behav == BehaviorType.Combat) { composite = new LockSelector( new CallWatch("Combat", new Decorator( ret => AllowBehaviorUsage(), // && (!Me.GotTarget() || !Blacklist.Contains(Me.CurrentTargetGuid, BlacklistFlags.Combat)), new PrioritySelector( new Decorator( ret => !HotkeyDirector.IsCombatEnabled, new ActionAlwaysSucceed() ), CreatePullMorePull(), CreateLogTargetChanges(BehaviorType.Combat, "<<< ADD >>>"), composite ?? new ActionAlwaysFail() ) ) ) ); } if (behav == BehaviorType.Death) { composite = new LockSelector( new CallWatch("Death", new Decorator( ret => AllowBehaviorUsage(), new PrioritySelector( new Action(r => { ResetCurrentTarget(); return RunStatus.Failure; }), new Decorator( req => !OkToCallBehaviorsWithCurrentCastingStatus(), Spell.WaitForGcdOrCastOrChannel() ), composite ?? new ActionAlwaysFail() ) ) ) ); } return composite; }
public static Composite CreateShamanDpsHealBehavior() { Composite offheal; if (!ShamanSettings.AllowOffHealHeal) offheal = new ActionAlwaysFail(); else { offheal = new Decorator( ret => NeedToOffHealSomeone, Restoration.CreateRestoShamanHealingOnlyBehavior(selfOnly: false) ); } return new Decorator( ret => !Spell.IsGlobalCooldown() && !Spell.IsCastingOrChannelling(), new PrioritySelector( // use predicted health for non-combat healing to reduce drinking downtime and help // .. avoid unnecessary heal casts new Decorator( ret => !Me.Combat, Spell.Cast("Healing Surge", ret => Me, ret => Me.GetPredictedHealthPercent(true) < 85) ), new Decorator( ret => Me.Combat && !Spell.IsGlobalCooldown(), new PrioritySelector( Spell.OffGCD( Spell.BuffSelf("Ancestral Guidance", ret => Me.HealthPercent < ShamanSettings.SelfAncestralGuidance && Me.GotTarget && Me.CurrentTarget.TimeToDeath() > 8 ) ), // save myself if possible new Decorator( ret => (!Me.IsInGroup() || Battlegrounds.IsInsideBattleground) && Me.HealthPercent < ShamanSettings.SelfAncestralSwiftnessHeal, new Sequence( Spell.BuffSelf("Ancestral Swiftness"), new Sequence( Spell.Cast("Greater Healing Wave", ret => Me), Spell.Cast("Healing Surge", ret => Me) ) ) ), // include optional offheal behavior offheal, // use non-predicted health as a trigger for totems new Decorator( ret => !Common.AnyHealersNearby, new PrioritySelector( Spell.BuffSelf( "Healing Tide Totem", ret => !Me.IsMoving && Unit.GroupMembers.Any( p => p.HealthPercent < ShamanSettings.HealingTideTotemPercent && p.Distance <= Totems.GetTotemRange(WoWTotem.HealingTide))), Spell.BuffSelf( "Healing Stream Totem", ret => !Me.IsMoving && !Totems.Exist(WoWTotemType.Water) && Unit.GroupMembers.Any( p => p.HealthPercent < ShamanSettings.SelfHealingStreamTotem && p.Distance <= Totems.GetTotemRange(WoWTotem.HealingTide))), // use actual health for following, not predicted as its a low health value // .. and its okay for multiple heals at that point Spell.Cast( "Healing Surge", mov => true, on => Me, req => Me.GetPredictedHealthPercent(true) <= ShamanSettings.SelfHealingSurge, cancel => Me.HealthPercent > 90 ) ) ) ) ) ) ); }
/// <summary> /// create standard Avoidance behavior (disengage and/or kiting) /// </summary> /// <param name="disengageSpell">spellName to use for disengage</param> /// <param name="disengageDist">distance spellName will jump</param> /// <param name="disengageDir">direction spellName jumps</param> /// <param name="crowdControl">behavior called to crowd control melee enemies or halt use of disengage</param> /// <param name="needDisengage">delegate to check for using disgenage. defaults checking settings for health and # attackers</param> /// <param name="needKiting">delegate to check for using kiting. defaults to checking settings for health and # attackers</param> /// <param name="cancelKiting">delegate to check if kiting should be cancelled. defaults to checking no attackers that aren't crowd controlled</param> /// <returns></returns> public static Composite CreateAvoidanceBehavior( string disengageSpell, int disengageDist, Disengage.Direction disengageDir, Composite crowdControl, SimpleBooleanDelegate needDisengage = null, SimpleBooleanDelegate needKiting = null, SimpleBooleanDelegate cancelKiting = null ) { PrioritySelector pri = new PrioritySelector(); // build default check for whether disengage is needed if (needDisengage == null) { needDisengage = req => { if (disengageSpell == null || disengageDir == Disengage.Direction.None) return false; if (!Kite.IsDisengageWantedByUserSettings() || !MovementManager.IsClassMovementAllowed || !SingularRoutine.IsAllowed(CapabilityFlags.Kiting)) return false; if (Spell.IsSpellOnCooldown(disengageSpell) && (!SingularSettings.Instance.UseRacials || Spell.IsSpellOnCooldown("Rocket Jump"))) return false; bool useDisengage = false; if (SingularRoutine.CurrentWoWContext == WoWContext.Normal) { int countMelee = Unit.UnitsInCombatWithUsOrOurStuff(7).Sum( u => !u.IsPlayer || !u.IsMelee() ? 1 : SingularSettings.Instance.DisengageMobCount); if (countMelee >= SingularSettings.Instance.DisengageMobCount) useDisengage = true; else if (Me.HealthPercent <= SingularSettings.Instance.DisengageHealth && countMelee > 0) useDisengage = true; } else if (SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds) { useDisengage = Unit.UnfriendlyUnits(7).Any(u => u.IsMelee()); } return useDisengage; }; } // standard disengage behavior, with class specific check to make sure parties are crowd controlled if (SingularSettings.Instance.DisengageAllowed) { Composite behavRocketJump = new ActionAlwaysFail(); if (SingularSettings.Instance.UseRacials && SpellManager.HasSpell("Rocket Jump")) behavRocketJump = Disengage.CreateDisengageBehavior("Rocket Jump", 20, Disengage.Direction.Frontwards, null); pri.AddChild( new Decorator( ret => needDisengage(ret), new Sequence( crowdControl, // return Failure if shouldnt disengage, otherwise Success to allow new PrioritySelector( // new Action(r => { Logger.Write( LogColor.Hilite, "^Avoidance: disengaging away from mobs!"); return RunStatus.Failure; } ), Disengage.CreateDisengageBehavior(disengageSpell, disengageDist, disengageDir, null), behavRocketJump ) ) ) ); } // build default needKiting check if (needKiting == null) { needKiting = req => { if (!Kite.IsKitingWantedByUserSettings() || !MovementManager.IsClassMovementAllowed || !SingularRoutine.IsAllowed(CapabilityFlags.Kiting)) return false; bool useKiting = false; int countMelee = Unit.UnitsInCombatWithUsOrOurStuff(7).Count(); if (countMelee >= SingularSettings.Instance.KiteMobCount) useKiting = true; else if (Me.HealthPercent <= SingularSettings.Instance.KiteHealth && countMelee > 0) useKiting = true; return useKiting; }; } if (cancelKiting == null) { cancelKiting = req => { int countAttackers = Unit.UnitsInCombatWithUsOrOurStuff(7).Count(); return (countAttackers == 0); }; } // add check to initiate kiting behavior if (SingularSettings.Instance.KiteAllow) { pri.AddChild( new Decorator( ret => needKiting(ret), new Sequence( crowdControl, Kite.BeginKitingBehavior() ) ) ); } if (!pri.Children.Any()) { return new ActionAlwaysFail(); } return new Decorator(req => MovementManager.IsClassMovementAllowed, pri); }
public static Composite MoveBehaviorInlineToCombat( BehaviorType bt ) { string hookNameOrig = HookName(bt); string hookNameInline = hookNameOrig + "-INLINE"; if (BehaviorType.Combat != Dynamics.CompositeBuilder.CurrentBehaviorType) { Logger.WriteDiagnostic("MoveBehaviorInline: suppressing Inline for {0} behavior", Dynamics.CompositeBuilder.CurrentBehaviorType); return new ActionAlwaysFail(); } if (Instance == null) { StopBot(string.Format("MoveBehaviorInline: PROGRAM ERROR - SingularRoutine.Instance not initialized yet for {0} !!!!", bt)); return null; } if (bt == Dynamics.CompositeBuilder.CurrentBehaviorType) { StopBot(string.Format("MoveBehaviorInline: PROGRAM ERROR - referenced behavior({0}) == current behavior({1}) !!!!", bt, bt)); return null; } // on creation, move hook composite (if exists) from default to inline hook Composite composite = new ActionAlwaysFail(); if (TreeHooks.Instance.Hooks.ContainsKey(hookNameOrig)) { if (TreeHooks.Instance.Hooks[hookNameOrig].Count() == 1) { composite = TreeHooks.Instance.Hooks[hookNameOrig].First().Composite; if (composite == null) { StopBot(string.Format("MoveBehaviorInline: PROGRAM ERROR - not composite for behavior({0}) !!!!", bt)); return null; } TreeHooks.Instance.ReplaceHook(hookNameInline, composite); TreeHooks.Instance.RemoveHook(hookNameOrig, composite); Logger.WriteFile("MoveBehaviorInline: moving {0} behavior within {1} {2}", bt, Dynamics.CompositeBuilder.CurrentBehaviorName, Dynamics.CompositeBuilder.CurrentBehaviorPriority); } } return new HookExecutor(hookNameInline); }
public static Composite CreateMonkDpsHealBehavior() { Composite offheal; if (!SingularSettings.Instance.DpsOffHealAllowed) offheal = new ActionAlwaysFail(); else { offheal = new Decorator( ret => HealerManager.ActingAsOffHealer, CreateMonkOffHealBehavior() ); } return new Decorator( ret => !Spell.IsGlobalCooldown() && !Spell.IsCastingOrChannelling(), new PrioritySelector( new Decorator( ret => !Me.Combat && !Me.IsMoving && Me.HealthPercent <= 85 // not redundant... this eliminates unnecessary GetPredicted... checks && SpellManager.HasSpell("Surging Mist") && Me.PredictedHealthPercent(includeMyHeals: true) < 85, new PrioritySelector( new Sequence( ctx => (float)Me.HealthPercent, new Action(r => Logger.WriteDebug("Surging Mist: {0:F1}% Predict:{1:F1}% and moving:{2}, cancast:{3}", (float)r, Me.PredictedHealthPercent(includeMyHeals: true), Me.IsMoving, Spell.CanCastHack("Surging Mist", Me, skipWowCheck: false))), Spell.Cast( "Surging Mist", mov => true, on => Me, req => true, cancel => Me.HealthPercent > 85 ), new WaitContinue(TimeSpan.FromMilliseconds(500), until => !Me.IsCasting && Me.HealthPercent > (1.1 * ((float)until)), new ActionAlwaysSucceed()), new Action(r => Logger.WriteDebug("Surging Mist: After Heal Attempted: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ), Spell.Buff( "Expel Harm", on => Me, req => Me.HealthPercent < MonkSettings.ExpelHarmHealth) ) ), new Decorator( ret => Me.Combat, new PrioritySelector( // add buff / shield here Spell.HandleOffGCD( new Throttle( 1, Spell.Cast("Tigereye Brew", ctx => Me, ret => Me.HealthPercent < MonkSettings.TigereyeBrewHealth && Me.HasAura("Tigereye Brew", 1)), Spell.BuffSelf("Dampen Harm", req => Me.HealthPercent < MonkSettings.DampenHarmPct || MonkSettings.DampenHarmCount <= Unit.UnfriendlyUnits(40).Count( u => u.IsAlive && u.CurrentTargetGuid == Me.Guid && !u.IsTrivial())) ) ), // save myself if possible new Decorator( ret => (!Me.IsInGroup() || Battlegrounds.IsInsideBattleground) && !Me.IsMoving && Me.HealthPercent < MonkSettings.SurgingMist && Me.PredictedHealthPercent(includeMyHeals: true) < MonkSettings.SurgingMist, new PrioritySelector( new Sequence( ctx => (float)Me.HealthPercent, new Action(r => Logger.WriteDebug("Surging Mist: {0:F1}% Predict:{1:F1}% and moving:{2}, cancast:{3}", (float)r, Me.PredictedHealthPercent(includeMyHeals: true), Me.IsMoving, Spell.CanCastHack("Surging Mist", Me, skipWowCheck: false))), Spell.Cast( "Surging Mist", mov => true, on => Me, req => true, cancel => Me.HealthPercent > 85 ), new WaitContinue(TimeSpan.FromMilliseconds(500), until => !Me.IsCasting && Me.HealthPercent > (1.1 * ((float)until)), new ActionAlwaysSucceed()), new Action(r => Logger.WriteDebug("Surging Mist: After Heal Attempted: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ), new Action(r => Logger.WriteDebug("Surging Mist: After Heal Skipped: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ) ) ) ), offheal ) ); }
public static Composite CreateShamanDpsHealBehavior() { Composite offheal; if (!SingularSettings.Instance.DpsOffHealAllowed) { offheal = new ActionAlwaysFail(); } else { offheal = new Decorator( ret => HealerManager.ActingAsOffHealer, CreateDpsShamanOffHealBehavior() ); } return(new Decorator( ret => !Spell.IsGlobalCooldown() && !Spell.IsCastingOrChannelling(), new PrioritySelector( new Decorator( ret => !Me.Combat && (!Me.IsMoving || Me.HasAura("Maelstrom Weapon", 5)) && Me.HealthPercent <= 85 && // not redundant... this eliminates unnecessary GetPredicted... checks SpellManager.HasSpell("Healing Surge") && Me.PredictedHealthPercent(includeMyHeals: true) < 85, new PrioritySelector( new Sequence( ctx => (float)Me.HealthPercent, new Action(r => Logger.WriteDebug("Healing Surge: {0:F1}% Predict:{1:F1}% and moving:{2}, cancast:{3}", (float)r, Me.PredictedHealthPercent(includeMyHeals: true), Me.IsMoving, Spell.CanCastHack("Healing Surge", Me, skipWowCheck: false))), Spell.Cast( "Healing Surge", mov => true, on => Me, req => true, cancel => Me.HealthPercent > 85 ), new WaitContinue(TimeSpan.FromMilliseconds(500), until => !Me.IsCasting && Me.HealthPercent > (1.1 * ((float)until)), new ActionAlwaysSucceed()), new Action(r => Logger.WriteDebug("Healing Surge: After Heal Attempted: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ), new Action(r => Logger.WriteDebug("Healing Surge: After Heal Skipped: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ) ), new Decorator( ret => Me.Combat, new PrioritySelector( Spell.BuffSelf("Astral Shift", ret => Me.HealthPercent < ShamanSettings.AstralShiftPercent || Common.StressfulSituation), Spell.BuffSelf(WoWTotem.StoneBulwark.ToSpellId(), ret => !Me.IsMoving && (Common.StressfulSituation || Me.HealthPercent < ShamanSettings.StoneBulwarkTotemPercent && !Totems.Exist(WoWTotem.EarthElemental))), Spell.BuffSelf("Shamanistic Rage", ret => Me.HealthPercent < ShamanSettings.ShamanisticRagePercent || Common.StressfulSituation), Spell.HandleOffGCD( Spell.BuffSelf("Ancestral Guidance", ret => Me.HealthPercent < ShamanSettings.SelfAncestralGuidance && Me.GotTarget() && Me.CurrentTarget.TimeToDeath() > 8 ) ), new Decorator( req => !Me.IsMoving && ShamanSettings.SelfHealingStreamTotem > 0 && !Totems.Exist(WoWTotemType.Water), Spell.BuffSelf( WoWTotem.HealingStream.ToSpellId(), req => { int count = Unit.NearbyUnitsInCombatWithMeOrMyStuff.Count(u => !u.IsTrivial()); if (count > 1) { return true; } if (count > 0 && Me.HealthPercent < ShamanSettings.SelfHealingStreamTotem) { if (!Me.GotTarget() || Me.CurrentTarget.IsPlayer || Me.CurrentTarget.TimeToDeath() > 5) { return true; } if (Me.HealthPercent < (ShamanSettings.SelfHealingStreamTotem / 2)) { return true; } } return false; } ) ), // save myself if possible new Decorator( ret => (!Me.IsInGroup() || Battlegrounds.IsInsideBattleground) && (!Me.IsMoving || Me.HasAura("Maelstrom Weapon", 5) || !Spell.IsSpellOnCooldown("Ancestral Swiftness")) && Me.HealthPercent < ShamanSettings.SelfAncestralSwiftnessHeal && Me.PredictedHealthPercent(includeMyHeals: true) < ShamanSettings.SelfAncestralSwiftnessHeal, new PrioritySelector( Spell.HandleOffGCD(Spell.BuffSelf("Ancestral Swiftness")), new PrioritySelector( new Sequence( ctx => (float)Me.HealthPercent, new Action(r => Logger.WriteDebug("Healing Surge: {0:F1}% Predict:{1:F1}% and moving:{2}, cancast:{3}", (float)r, Me.PredictedHealthPercent(includeMyHeals: true), Me.IsMoving, Spell.CanCastHack("Healing Surge", Me, skipWowCheck: false))), Spell.Cast( "Healing Surge", mov => true, on => Me, req => true, cancel => Me.HealthPercent > 85 ), new WaitContinue(TimeSpan.FromMilliseconds(500), until => !Me.IsCasting && Me.HealthPercent > (1.1 * ((float)until)), new ActionAlwaysSucceed()), new Action(r => Logger.WriteDebug("Healing Surge: After Heal Attempted: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ), new Action(r => Logger.WriteDebug("Healing Surge: After Heal Skipped: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ) ) ) ) ), offheal ) )); }
public static Composite CreateShamanDpsHealBehavior() { Composite offheal; if (!SingularSettings.Instance.DpsOffHealAllowed) offheal = new ActionAlwaysFail(); else { offheal = new Decorator( ret => HealerManager.ActingAsOffHealer, CreateDpsShamanOffHealBehavior() ); } return new Decorator( ret => !Spell.IsGlobalCooldown() && !Spell.IsCastingOrChannelling(), new PrioritySelector( new Decorator( ret => !Me.Combat && (!Me.IsMoving || Me.HasAura("Maelstrom Weapon", 5)) && Me.HealthPercent <= 85 // not redundant... this eliminates unnecessary GetPredicted... checks && SpellManager.HasSpell("Healing Surge") && Me.PredictedHealthPercent(includeMyHeals: true) < 85, new PrioritySelector( new Sequence( ctx => (float)Me.HealthPercent, new Action(r => Logger.WriteDebug("Healing Surge: {0:F1}% Predict:{1:F1}% and moving:{2}, cancast:{3}", (float) r, Me.PredictedHealthPercent(includeMyHeals: true), Me.IsMoving, Spell.CanCastHack("Healing Surge", Me, skipWowCheck: false))), Spell.Cast( "Healing Surge", mov => true, on => Me, req => true, cancel => Me.HealthPercent > 85 ), new WaitContinue(TimeSpan.FromMilliseconds(500), until => !Me.IsCasting && Me.HealthPercent > (1.1 * ((float)until)), new ActionAlwaysSucceed()), new Action( r => Logger.WriteDebug("Healing Surge: After Heal Attempted: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ), new Action( r => Logger.WriteDebug("Healing Surge: After Heal Skipped: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ) ), new Decorator( ret => Me.Combat, new PrioritySelector( Spell.BuffSelf("Astral Shift", ret => Me.HealthPercent < ShamanSettings.AstralShiftPercent || Common.StressfulSituation), Spell.BuffSelf(WoWTotem.StoneBulwark.ToSpellId(), ret => !Me.IsMoving && (Common.StressfulSituation || Me.HealthPercent < ShamanSettings.StoneBulwarkTotemPercent && !Totems.Exist(WoWTotem.EarthElemental))), Spell.BuffSelf("Shamanistic Rage", ret => Me.HealthPercent < ShamanSettings.ShamanisticRagePercent || Common.StressfulSituation), Spell.HandleOffGCD( Spell.BuffSelf("Ancestral Guidance", ret => Me.HealthPercent < ShamanSettings.SelfAncestralGuidance && Me.GotTarget() && Me.CurrentTarget.TimeToDeath() > 8 ) ), new Decorator( req => !Me.IsMoving && ShamanSettings.SelfHealingStreamTotem > 0 && !Totems.Exist(WoWTotemType.Water), Spell.BuffSelf( WoWTotem.HealingStream.ToSpellId(), req => { int count = Unit.NearbyUnitsInCombatWithMeOrMyStuff.Count(u => !u.IsTrivial()); if (count > 1) return true; if (count > 0 && Me.HealthPercent < ShamanSettings.SelfHealingStreamTotem) { if (!Me.GotTarget() || Me.CurrentTarget.IsPlayer || Me.CurrentTarget.TimeToDeath() > 5) { return true; } if (Me.HealthPercent < (ShamanSettings.SelfHealingStreamTotem / 2)) { return true; } } return false; } ) ), // save myself if possible new Decorator( ret => (!Me.IsInGroup() || Battlegrounds.IsInsideBattleground) && (!Me.IsMoving || Me.HasAura("Maelstrom Weapon", 5) || !Spell.IsSpellOnCooldown("Ancestral Swiftness")) && Me.HealthPercent < ShamanSettings.SelfAncestralSwiftnessHeal && Me.PredictedHealthPercent(includeMyHeals: true) < ShamanSettings.SelfAncestralSwiftnessHeal, new PrioritySelector( Spell.HandleOffGCD( Spell.BuffSelf("Ancestral Swiftness") ), new PrioritySelector( new Sequence( ctx => (float)Me.HealthPercent, new Action(r => Logger.WriteDebug("Healing Surge: {0:F1}% Predict:{1:F1}% and moving:{2}, cancast:{3}", (float)r, Me.PredictedHealthPercent(includeMyHeals: true), Me.IsMoving, Spell.CanCastHack("Healing Surge", Me, skipWowCheck: false))), Spell.Cast( "Healing Surge", mov => true, on => Me, req => true, cancel => Me.HealthPercent > 85 ), new WaitContinue(TimeSpan.FromMilliseconds(500), until => !Me.IsCasting && Me.HealthPercent > (1.1 * ((float)until)), new ActionAlwaysSucceed()), new Action(r => Logger.WriteDebug("Healing Surge: After Heal Attempted: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ), new Action(r => Logger.WriteDebug("Healing Surge: After Heal Skipped: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ) ) ) ) ), offheal ) ); }
/// <summary> /// /// </summary> /// <param name="gapCloser"></param> /// <returns></returns> public static Composite CreateRangedHealerMovementBehavior(Composite gapCloser = null) { int moveNearTank; int stopNearTank; if (SingularRoutine.CurrentWoWContext != WoWContext.Instances) { return(new ActionAlwaysFail()); } if (!SingularSettings.Instance.StayNearTank) { return(Movement.CreateMoveBehindTargetBehavior()); } if (gapCloser == null) { gapCloser = new ActionAlwaysFail(); } bool incombat = (Dynamics.CompositeBuilder.CurrentBehaviorType & BehaviorType.InCombat) != (BehaviorType)0; if (incombat) { moveNearTank = Math.Max(5, SingularSettings.Instance.StayNearTankRangeCombat); stopNearTank = Math.Max(moveNearTank / 2, moveNearTank - 5); } else { moveNearTank = Math.Max(5, SingularSettings.Instance.StayNearTankRangeRest); stopNearTank = Math.Max(moveNearTank / 2, moveNearTank - 5); } return(new PrioritySelector( ctx => (Me.Combat && Me.GotTarget() && Unit.ValidUnit(Me.CurrentTarget)) ? Me.CurrentTarget : null, new Decorator( ret => ret != null, new Sequence( new PrioritySelector( gapCloser, Movement.CreateMoveToLosBehavior(), Movement.CreateMoveToUnitBehavior(on => Me.CurrentTarget, 30, 25), Movement.CreateEnsureMovementStoppedBehavior(25f), // to account for Mistweaver Monks facing Soothing Mist target automatically new Decorator( req => !Spell.IsChannelling(), Movement.CreateFaceTargetBehavior() ) ), new ActionAlwaysFail() ) ), new Decorator( ret => ret == null, new Sequence( CreateStayNearTankBehavior(gapCloser), new ActionAlwaysFail() ) ) )); }
/// <summary> /// stays within range of Tank as they move. settings configurable by user. /// </summary> /// <param name="gapCloser">ability to close distance more quickly than running (such as Roll)</param> /// <returns></returns> public static Composite CreateStayNearTankBehavior(Composite gapCloser = null) { int moveNearTank; int stopNearTank; if (gapCloser == null) { gapCloser = new ActionAlwaysFail(); } if (!SingularSettings.Instance.StayNearTank) { return(new ActionAlwaysFail()); } if (SingularRoutine.CurrentWoWContext != WoWContext.Instances) { return(new ActionAlwaysFail()); } if (IsThisBehaviorCalledDuringCombat()) { moveNearTank = Math.Max(5, SingularSettings.Instance.StayNearTankRangeCombat); stopNearTank = (moveNearTank * 7) / 10; } else { moveNearTank = Math.Max(5, SingularSettings.Instance.StayNearTankRangeRest); stopNearTank = (moveNearTank * 6) / 10; // be slightly more elastic at rest } Logger.WriteDebug("StayNearTank in {0}: will move towards at {1} yds and stop if within {2} yds", Dynamics.CompositeBuilder.CurrentBehaviorType, moveNearTank, stopNearTank); return(new PrioritySelector( ctx => HealerManager.TankToStayNear, // no healing needed, then move within heal range of tank new ThrottlePasses( 1, TimeSpan.FromSeconds(5), RunStatus.Failure, new Action(t => { if (SingularSettings.Debug) { WoWUnit tankToStayNear = (WoWUnit)t; if (t != null) { ; } else if (!Group.Tanks.Any()) { Logger.WriteDiagnostic(Color.HotPink, "TankToStayNear: no group members with Role=Tank"); } else { Logger.WriteDebug(Color.HotPink, "TankToStayNear: {0} tanks in group", Group.Tanks.Count()); int i = 0; foreach (var tank in Group.Tanks.OrderByDescending(gt => gt == RaFHelper.Leader).ThenBy(gt => gt.DistanceSqr)) { Logger.WriteDebug(Color.HotPink, "TankToStayNear[{0}]: {1} Health={2:F1}%, Dist={3:F1}, TankPt={4}, MePt={5}, TankMov={6}, MeMov={7}, LoS={8}, LoSS={9}, Combat={10}, MeCombat={11}, ", i++, tank.SafeName(), tank.HealthPercent, tank.SpellDistance(), tank.Location, Me.Location, tank.IsMoving.ToYN(), Me.IsMoving.ToYN(), tank.InLineOfSight.ToYN(), tank.InLineOfSpellSight.ToYN(), tank.Combat.ToYN(), Me.Combat.ToYN() ); } Logger.WriteDebug(Color.HotPink, "TankToStayNear: current TargetList has {0} units", Targeting.Instance.TargetList.Count()); i = 0; foreach (var target in Targeting.Instance.TargetList) { Logger.WriteDebug(Color.HotPink, "CurrentTargets[{0}]: {1} {2:F1}% @ {3:F1}", i++, target.SafeName(), target.HealthPercent, target.SpellDistance() ); } } } return RunStatus.Failure; }) ), new Decorator( ret => ((WoWUnit)ret) != null, new Sequence( new PrioritySelector( gapCloser, Movement.CreateMoveToLosBehavior(unit => ((WoWUnit)unit)), Movement.CreateMoveToUnitBehavior(unit => ((WoWUnit)unit), moveNearTank, stopNearTank) // , Movement.CreateEnsureMovementStoppedBehavior(stopNearTank, unit => (WoWUnit)unit, "in heal range of tank") ), new ActionAlwaysFail() ) ) )); }
/// <summary> /// Creates a move behind target behavior. If it cannot fully navigate will move to target location /// </summary> /// <remarks> /// Created 2/12/2011. /// </remarks> /// <param name="requirements">Aditional requirments.</param> /// <returns>.</returns> public static Composite CreateMoveBehindTargetBehavior(SimpleBooleanDelegate requirements, Composite gapCloser = null) { if ( !SingularSettings.Instance.MeleeMoveBehind) return new ActionAlwaysFail(); if (requirements == null) return new ActionAlwaysFail(); if (SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds) return new ActionAlwaysFail(); if (gapCloser == null) gapCloser = new ActionAlwaysFail(); return new Decorator( ret => { if (MovementManager.IsMovementDisabled || !SingularRoutine.IsAllowed(Styx.CommonBot.Routines.CapabilityFlags.MoveBehind) || !requirements(ret) || Spell.IsCastingOrChannelling() || Group.MeIsTank) return false; var currentTarget = Me.CurrentTarget; if (currentTarget == null || Me.IsBehind(currentTarget) || !currentTarget.IsAlive || BossList.AvoidRearBosses.Contains(currentTarget.Entry)) return false; return (currentTarget.Stunned || currentTarget.CurrentTargetGuid != Me.Guid) && requirements(ret); }, new PrioritySelector( ctx => CalculatePointBehindTarget(), new Decorator( req => Navigator.CanNavigateFully(Me.Location, (WoWPoint)req), new Sequence( new Action(ret => Logger.WriteDebug(Color.White, "MoveBehind: behind {0} @ {1:F1} yds", Me.CurrentTarget.SafeName(), Me.CurrentTarget.Distance)), new Action(behindPoint => Navigator.MoveTo((WoWPoint)behindPoint)), new Action(behindPoint => StopMoving.AtLocation((WoWPoint)behindPoint)), new PrioritySelector( new Decorator( req => MovementManager.IsClassMovementAllowed, gapCloser ), new ActionAlwaysSucceed() ) ) ) ) ); }
public static Composite CreateMonkDpsHealBehavior() { Composite offheal; if (!SingularSettings.Instance.DpsOffHealAllowed) { offheal = new ActionAlwaysFail(); } else { offheal = new Decorator( ret => HealerManager.ActingAsOffHealer, CreateMonkOffHealBehavior() ); } return(new Decorator( ret => !Spell.IsGlobalCooldown() && !Spell.IsCastingOrChannelling(), new PrioritySelector( new Decorator( ret => !Me.Combat && !Me.IsMoving && Me.HealthPercent <= 85 && // not redundant... this eliminates unnecessary GetPredicted... checks SpellManager.HasSpell("Effuse") && Me.PredictedHealthPercent(includeMyHeals: true) < 85, new PrioritySelector( new Sequence( ctx => (float)Me.HealthPercent, new Action(r => Logger.WriteDebug("Effuse: {0:F1}% Predict:{1:F1}% and moving:{2}, cancast:{3}", (float)r, Me.PredictedHealthPercent(includeMyHeals: true), Me.IsMoving, Spell.CanCastHack("Effuse", Me, skipWowCheck: false))), Spell.Cast( "Effuse", mov => true, on => Me, req => true, cancel => Me.HealthPercent > 85 ), new WaitContinue(TimeSpan.FromMilliseconds(500), until => !Me.IsCasting && Me.HealthPercent > (1.1 * ((float)until)), new ActionAlwaysSucceed()), new Action(r => Logger.WriteDebug("Effuse: After Heal Attempted: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ) ) ), new Decorator( ret => Me.Combat, new PrioritySelector( // add buff / shield here Spell.HandleOffGCD( new Throttle( 1, Spell.BuffSelf("Dampen Harm", req => Me.HealthPercent < MonkSettings.DampenHarmPct || MonkSettings.DampenHarmCount <= Unit.UnfriendlyUnits(40).Count(u => u.IsAlive && u.CurrentTargetGuid == Me.Guid && !u.IsTrivial())) ) ), // save myself if possible new Decorator( ret => (!Me.IsInGroup() || Battlegrounds.IsInsideBattleground) && !Me.IsMoving && Me.HealthPercent < MonkSettings.Effuse && Me.PredictedHealthPercent(includeMyHeals: true) < MonkSettings.Effuse, new PrioritySelector( new Sequence( ctx => (float)Me.HealthPercent, new Action(r => Logger.WriteDebug("Effuse: {0:F1}% Predict:{1:F1}% and moving:{2}, cancast:{3}", (float)r, Me.PredictedHealthPercent(includeMyHeals: true), Me.IsMoving, Spell.CanCastHack("Effuse", Me, skipWowCheck: false))), Spell.Cast( "Effuse", mov => true, on => Me, req => true, cancel => Me.HealthPercent > 85 ), new WaitContinue(TimeSpan.FromMilliseconds(500), until => !Me.IsCasting && Me.HealthPercent > (1.1 * ((float)until)), new ActionAlwaysSucceed()), new Action(r => Logger.WriteDebug("Effuse: After Heal Attempted: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ), new Action(r => Logger.WriteDebug("Effuse: After Heal Skipped: {0:F1}% Predicted: {1:F1}%", Me.HealthPercent, Me.PredictedHealthPercent(includeMyHeals: true))) ) ) ) ), offheal ) )); }