public static Composite CreateRestoDruidPull() { return(new PrioritySelector( HealerManager.CreateStayNearTankBehavior(), new Decorator( req => !HealerManager.Instance.TargetList.Any(h => h.IsAlive && !h.IsMe && h.Distance < 40), new PrioritySelector( Helpers.Common.EnsureReadyToAttackFromLongRange(), Spell.WaitForCastOrChannel(), new Decorator( req => !Spell.IsGlobalCooldown(), new PrioritySelector( Helpers.Common.CreateInterruptBehavior(), Movement.WaitForFacing(), Movement.WaitForLineOfSpellSight(), Spell.Buff("Moonfire"), Spell.Cast("Wrath"), Movement.CreateMoveToUnitBehavior(on => Me.CurrentTarget, 35f, 30f) ) ) ) ) )); }
public static Composite CreateHolyHeal() { return(new PrioritySelector( CreateHolyDiagnosticOutputBehavior("HEAL"), HealerManager.CreateStayNearTankBehavior(), // Group heals new Decorator( ret => Unit.NearbyGroupMembers.Any(m => m.IsAlive && !m.IsMe), CreateHolyHealOnlyBehavior(false, true) ), // Solo heals new Decorator( ret => !Unit.NearbyGroupMembers.Any(m => m.IsAlive && !m.IsMe), new PrioritySelector( Spell.Cast("Desperate Prayer", ret => Me, ret => Me.Combat && Me.HealthPercent < PriestSettings.DesperatePrayerHealth), // keep heal buffs on if glyphed Spell.BuffSelf("Prayer of Mending", ret => Me.Combat && Me.HealthPercent <= 90), Spell.BuffSelf("Renew", ret => Me.Combat && Me.HealthPercent <= 95), Common.CreatePsychicScreamBehavior(), Spell.Cast("Flash Heal", ctx => Me, ret => Me.HealthPercent <= PriestSettings.ShadowHeal), Spell.Cast("Flash Heal", ctx => Me, ret => !Me.Combat && Me.PredictedHealthPercent(includeMyHeals: true) <= 90) ) ))); }
public static Composite CreateMistweaverCombatBehaviorBattlegrounds() { return(new Decorator( ret => !Unit.NearbyGroupMembers.Any(m => m.IsAlive && !m.IsMe), new PrioritySelector( CreateMistWeaverDiagnosticOutputBehavior(on => MyHealTarget), CancelSoothingMistAsNeeded(), HealerManager.CreateMeleeHealerMovementBehavior(), new Decorator( req => HealerManager.AllowHealerDPS(), new PrioritySelector( CreateMistweaverMoveToEnemyTarget(), new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector( new Decorator( req => Me.GotTarget() && Me.CurrentTarget.IsWithinMeleeRange, Helpers.Common.CreateAutoAttack() ), Helpers.Common.CreateInterruptBehavior(), Spell.Cast("Paralysis", onu => Unit.NearbyUnfriendlyUnits .FirstOrDefault(u => u.IsCasting && u.Distance.Between(9, 20) && Me.IsSafelyFacing(u))), Spell.Cast("Leg Sweep", ret => Spell.UseAOE && SingularRoutine.CurrentWoWContext == WoWContext.Normal && Me.CurrentTarget.IsWithinMeleeRange), Spell.Cast(SpinningCraneKick, ret => Spell.UseAOE && Unit.NearbyUnfriendlyUnits.Count(u => u.Distance <= 8) >= MonkSettings.SpinningCraneKickCnt), Spell.Cast("Rising Sun Kick"), Spell.Cast("Blackout Kick", req => Me.GetAuraStacks("Teachings of the Monastery") >= 3), Spell.Cast("Tiger Palm") ) ), Spell.Cast("Roll", ret => MovementManager.IsClassMovementAllowed && !MonkSettings.DisableRoll && Me.CurrentTarget.Distance > minDistRollAllowed) ) ), new Decorator( ret => Unit.NearbyGroupMembers.Any(m => m.IsAlive && !m.IsMe), new PrioritySelector( CreateMistweaverMonkHealing(selfOnly: false), Helpers.Common.CreateInterruptBehavior() ) ) ) )); }
public static Composite CreateDiscOutOfCombatHeal() { return(new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector(ctx => HealerManager.FindLowestHealthTarget(), Spell.Cast("Flash Heal", on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < 95), Spell.Cast("Plea", mov => false, on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < 100) ) )); }
public static Composite CreateRestoDruidHealBehavior() { return(new PrioritySelector( CreateRestoDiagnosticOutputBehavior(), HealerManager.CreateStayNearTankBehavior(), new Decorator( ret => HealerManager.Instance.TargetList.Any(h => h.Distance < 40 && h.IsAlive && !h.IsMe), new PrioritySelector( CreateRestoDruidHealOnlyBehavior() ) ) )); }
public static Composite CreateRestoShamanCombatBehaviorBattlegrounds() { return(new PrioritySelector( Spell.WaitForCastOrChannel(), CreateRestoDiagnosticOutputBehavior(on => HealerManager.Instance.FirstUnit), new Decorator( ret => !Spell.IsGlobalCooldown() && HealerManager.Instance.TargetList.Any(t => !t.IsMe && t.IsAlive), new PrioritySelector( HealerManager.CreateStayNearTankBehavior(), CreateRestoShamanHealingOnlyBehavior(selfOnly: false) ) ), new Decorator( ret => !Spell.IsGlobalCooldown() && HealerManager.AllowHealerDPS(), new PrioritySelector( Helpers.Common.EnsureReadyToAttackFromMediumRange(), Spell.WaitForCastOrChannel(), new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector( Helpers.Common.CreateInterruptBehavior(), Totems.CreateTotemsBehavior(), Movement.WaitForFacing(), Movement.WaitForLineOfSpellSight(), Dispelling.CreatePurgeEnemyBehavior("Purge"), Common.CastElementalBlast(), Spell.Buff("Flame Shock", 3, on => Me.CurrentTarget, req => true), Spell.Cast("Lava Burst"), Spell.Cast("Frost Shock"), Spell.Cast("Chain Lightning", ret => Spell.UseAOE && Unit.UnfriendlyUnitsNearTarget(10f).Count() >= 2 && !Unit.UnfriendlyUnitsNearTarget(12f).Any(u => u.IsCrowdControlled())), Spell.Cast("Lightning Bolt") ) ) ) ) )); }
public static Composite CreateRestoDruidCombat() { return(new PrioritySelector( HealerManager.CreateStayNearTankBehavior(), new Decorator( req => HealerManager.AllowHealerDPS(), new PrioritySelector( Helpers.Common.EnsureReadyToAttackFromLongRange(), Spell.WaitForCastOrChannel(), new Decorator( req => !Spell.IsGlobalCooldown(), new PrioritySelector( Helpers.Common.CreateInterruptBehavior(), Movement.WaitForFacing(), Movement.WaitForLineOfSpellSight(), Common.CastForm(ShapeshiftForm.Moonkin, req => Common.HasTalent(DruidTalents.BalanceAffinity) && Me.Shapeshift != ShapeshiftForm.Moonkin && !Utilities.EventHandlers.IsShapeshiftSuppressed), Spell.Cast("Moonfire", on => Unit.NearbyUnitsInCombatWithUsOrOurStuff.FirstOrDefault(u => u.GetAuraTimeLeft("Moonfire").TotalSeconds < 1.8 && u.TimeToDeath(int.MaxValue) > 4)), Spell.Cast("Sunfire", on => Unit.NearbyUnitsInCombatWithUsOrOurStuff.FirstOrDefault(u => u.GetAuraTimeLeft("Sunfire").TotalSeconds < 1.8 && u.TimeToDeath(int.MaxValue) > 4)), Spell.Cast( "Starsurge", on => Unit.UnitsInCombatWithUsOrOurStuff(40).Where(u => !u.IsCrowdControlled() && u.InLineOfSpellSight).OrderByDescending(u => (uint)u.HealthPercent).FirstOrDefault(), req => true, cancel => HealerManager.CancelHealerDPS() ), Spell.Cast( "Lunar Strike", on => Unit.UnitsInCombatWithUsOrOurStuff(40).Where(u => !u.IsCrowdControlled() && u.InLineOfSpellSight).OrderByDescending(u => (uint)u.HealthPercent).FirstOrDefault(), req => true, cancel => HealerManager.CancelHealerDPS() ), Spell.Cast( "Solar Wrath", on => Unit.UnitsInCombatWithUsOrOurStuff(40).Where(u => !u.IsCrowdControlled() && u.InLineOfSpellSight).OrderByDescending(u => (uint)u.HealthPercent).FirstOrDefault(), req => true, cancel => HealerManager.CancelHealerDPS() ) ) ) ) ) )); }
public static Composite CreateHolyCombat() { return(new PrioritySelector( new Decorator( ret => HealerManager.AllowHealerDPS(), new PrioritySelector( Helpers.Common.EnsureReadyToAttackFromMediumRange(), Spell.WaitForCastOrChannel(), new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector( Helpers.Common.CreateInterruptBehavior(), Movement.WaitForFacing(), Movement.WaitForLineOfSpellSight(), Dispelling.CreatePurgeEnemyBehavior("Dispel Magic"), Spell.Buff("Shadow Word: Pain", true), Spell.Buff("Holy Word: Chastise"), Spell.Buff("Shadow Word: Pain", true, on => { WoWUnit unit = Unit.NearbyUnfriendlyUnits .FirstOrDefault( u => (u.TaggedByMe || u.Aggro) && u.Guid != Me.CurrentTargetGuid && u.IsTargetingMeOrPet && !u.HasMyAura("Shadow Word: Pain") && !u.IsCrowdControlled() ); return unit; }), Common.CreateHolyFireBehavior(), Spell.Cast("Smite", mov => true, on => Me.CurrentTarget, req => true, cancel => HealerManager.CancelHealerDPS()) ) ) ) ) )); }
/// <summary> /// non-combat heal to top people off and avoid lifebloom, buffs, etc. /// </summary> /// <param name="selfOnly"></param> /// <returns></returns> public static Composite CreateRestoNonCombatHeal(bool selfOnly = false) { return(new PrioritySelector( ctx => selfOnly || !Me.IsInGroup() ? StyxWoW.Me : HealerManager.FindLowestHealthTarget(), // HealerManager.Instance.FirstUnit, new Decorator( req => req != null && ((WoWUnit)req).Combat && ((WoWUnit)req).PredictedHealthPercent(includeMyHeals: true) < SingularSettings.Instance.IgnoreHealTargetsAboveHealth, CreateRestoDruidHealOnlyBehavior() ), new Decorator( req => req != null && !((WoWUnit)req).Combat && ((WoWUnit)req).PredictedHealthPercent(includeMyHeals: true) < SingularSettings.Instance.IgnoreHealTargetsAboveHealth, new Sequence( new Action(on => Logger.WriteDebug("NonCombatHeal on {0}: health={1:F1}% predicted={2:F1}% +mine={3:F1}", ((WoWUnit)on).SafeName(), ((WoWUnit)on).HealthPercent, ((WoWUnit)on).PredictedHealthPercent(), ((WoWUnit)on).PredictedHealthPercent(includeMyHeals: true))), new PrioritySelector( // BUFFS First Spell.Buff("Rejuvenation", 1, on => (WoWUnit)on, req => ((WoWUnit)req).PredictedHealthPercent(includeMyHeals: true) < 95), Spell.Buff("Regrowth", 1, on => (WoWUnit)on, req => !glyphRegrowth && ((WoWUnit)req).PredictedHealthPercent(includeMyHeals: true) < 80), // Direct Heals After Spell.Cast("Healing Touch", on => (WoWUnit)on, req => ((WoWUnit)req).PredictedHealthPercent(includeMyHeals: true) < 65), Spell.Cast("Regrowth", on => (WoWUnit)on, req => ((WoWUnit)req).PredictedHealthPercent(includeMyHeals: true) < 75), // if Moving, spread Rejuv around on those that need to be topped off new Decorator( req => Me.IsMoving, new PrioritySelector( ctx => HealerManager.Instance.TargetList.FirstOrDefault(h => h.HealthPercent < 95 && !h.HasMyAura("Rejuvenation") && Spell.CanCastHack("Rejuvenation", (WoWUnit)ctx, skipWowCheck: true)), Spell.Buff("Rejuvenation", on => (WoWUnit)on) ) ) ) ) ) )); }
//private static WoWUnit _moveToHealTarget = null; //private static WoWUnit _lastMoveToTarget = null; public static Composite CreateHolyHealOnlyBehavior(bool selfOnly, bool moveInRange) { if (SingularRoutine.CurrentWoWContext == WoWContext.Normal) { return(new ActionAlwaysFail()); } HealerManager.NeedHealTargeting = true; PrioritizedBehaviorList behavs = new PrioritizedBehaviorList(); int cancelHeal = Math.Max(SingularSettings.Instance.IgnoreHealTargetsAboveHealth, Math.Max(PriestSettings.HolyHeal.Heal, PriestSettings.HolyHeal.Renew)); Logger.WriteDebugInBehaviorCreate("Priest Healing: will cancel cast of direct heal if health reaches {0:F1}%", cancelHeal); if (SingularSettings.Instance.DispelDebuffs != RelativePriority.None) { int dispelPriority = (SingularSettings.Instance.DispelDebuffs == RelativePriority.HighPriority) ? 999 : -999; behavs.AddBehavior(dispelPriority, "Dispel", null, Common.CreatePriestDispelBehavior()); } #region Save the Group if (PriestSettings.HolyHeal.DivineHymn != 0) { behavs.AddBehavior(HealthToPriority(PriestSettings.HolyHeal.DivineHymn) + 300, "Divine Hymn @ " + PriestSettings.HolyHeal.DivineHymn + "% MinCount: " + PriestSettings.HolyHeal.CountDivineHymn, "Divine Hymn", new Decorator( ret => StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid, new PrioritySelector( context => HealerManager.GetBestCoverageTarget("Divine Hymn", PriestSettings.HolyHeal.DivineHymn, 0, 40, PriestSettings.HolyHeal.CountDivineHymn), new Decorator( ret => ret != null, Spell.Cast("Divine Hymn", mov => false, on => (WoWUnit)on, req => true, cancel => false) ) ) ) ); } if (PriestSettings.HolyHeal.GuardianSpirit != 0) { behavs.AddBehavior(HealthToPriority(PriestSettings.HolyHeal.GuardianSpirit) + 300, "Guardian Spirit @ " + PriestSettings.HolyHeal.GuardianSpirit + "%", "Guardian Spirit", Spell.Cast("Guardian Spirit", mov => false, on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < PriestSettings.HolyHeal.GuardianSpirit ) ); } if (PriestSettings.LightOfTuure != 0) { behavs.AddBehavior(HealthToPriority(PriestSettings.LightOfTuure) + 300, "Light of T'uure @ " + PriestSettings.LightOfTuure + "%", "Light of T'uure", Spell.Cast("Light of T'uure", mov => false, on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < PriestSettings.LightOfTuure && !((WoWUnit)req).HasAura("Light of T'uure") && Group.Tanks.Contains(((WoWUnit)req)) ) ); } #endregion #region AoE Heals int maxDirectHeal = Math.Max(PriestSettings.HolyHeal.FlashHeal, PriestSettings.HolyHeal.Heal); // instant, so cast this first ALWAYS if (PriestSettings.HolyHeal.HolyWordSanctify != 0) { behavs.AddBehavior(398, "Holy Word: Sanctify @ " + PriestSettings.HolyHeal.HolyWordSanctify + "% MinCount: " + PriestSettings.HolyHeal.CountHolyWordSanctify, "Holy Word: Sanctify", new Decorator( ret => true, new PrioritySelector( context => HealerManager.GetBestCoverageTarget("Holy Word: Sanctify", PriestSettings.HolyHeal.HolyWordSanctify, 40, 10, PriestSettings.HolyHeal.CountHolyWordSanctify), new Decorator( ret => ret != null, Spell.CastOnGround("Holy Word: Sanctify", on => (WoWUnit)on, req => true, false) ) ) ) ); } if (PriestSettings.HolyHeal.PrayerOfMending != 0) { behavs.AddBehavior(397, "Prayer of Mending @ " + PriestSettings.HolyHeal.PrayerOfMending + "%", "Prayer of Mending", Spell.Cast("Prayer of Mending", mov => true, on => (WoWUnit)on, req => !((WoWUnit)req).IsMe && ((WoWUnit)req).HealthPercent <PriestSettings.HolyHeal.PrayerOfMending, cancel => ((WoWUnit)cancel).HealthPercent> cancelHeal ) ); } /* * behavs.AddBehavior(HealthToPriority(PriestSettings.HolyHeal.HolyLevel90Talent) + 200, "Divine Star", * "Divine Star", * new Decorator( * ret => StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid, * new Decorator( * ret => * Clusters.GetClusterCount(Me, * HealerManager.Instance.TargetList.Where( * u => u.HealthPercent < PriestSettings.HolyHeal.CountLevel90Talent).ToList(), * ClusterType.PathToUnit, 4) >= PriestSettings.HolyHeal.CountLevel90Talent, * Spell.Cast("Divine Star", on => (WoWUnit)on, req => true) * ) * ) * ); */ if (PriestSettings.HolyHeal.HolyLevel90Talent != 0) { behavs.AddBehavior(397, "Halo @ " + PriestSettings.HolyHeal.HolyLevel90Talent + "% MinCount: " + PriestSettings.HolyHeal.CountLevel90Talent, "Halo", new Decorator( ret => StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid, new Decorator( ret => ret != null && HealerManager.Instance.TargetList.Count( u => u.IsAlive && u.HealthPercent < PriestSettings.HolyHeal.HolyLevel90Talent && u.Distance < 30) >= PriestSettings.HolyHeal.CountLevel90Talent && Unit.UnfriendlyUnits(30).Any(u => u.Combat && !u.IsCrowdControlled()), Spell.Cast("Halo", req => true) ) ) ); } if (PriestSettings.HolyHeal.BindingHeal != 0) { behavs.AddBehavior(396, "Binding Heal @ " + PriestSettings.HolyHeal.BindingHeal + "% MinCount: 2", "Binding Heal", Spell.Cast("Binding Heal", mov => true, on => (WoWUnit)on, req => !((WoWUnit)req).IsMe && ((WoWUnit)req).HealthPercent < PriestSettings.HolyHeal.BindingHeal && Me.HealthPercent <PriestSettings.HolyHeal.BindingHeal, cancel => ((WoWUnit)cancel).HealthPercent> cancelHeal ) ); } if (PriestSettings.HolyHeal.CircleOfHealing != 0) { behavs.AddBehavior(395, "Circle of Healing @ " + PriestSettings.HolyHeal.CircleOfHealing + "% MinCount: " + PriestSettings.HolyHeal.CountCircleOfHealing, "Circle of Healing", new Decorator( ret => StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid, new PrioritySelector( context => HealerManager.GetBestCoverageTarget("Circle of Healing", PriestSettings.HolyHeal.CircleOfHealing, 40, 30, PriestSettings.HolyHeal.CountCircleOfHealing), Spell.Cast("Circle of Healing", on => (WoWUnit)on) ) ) ); } if (PriestSettings.HolyHeal.PrayerOfHealing != 0) { behavs.AddBehavior(394, "Prayer of Healing @ " + PriestSettings.HolyHeal.PrayerOfHealing + "% MinCount: " + PriestSettings.HolyHeal.CountPrayerOfHealing, "Prayer of Healing", new Decorator( ret => StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid, new PrioritySelector( context => HealerManager.GetBestCoverageTarget("Prayer of Healing", PriestSettings.HolyHeal.PrayerOfHealing, 40, 20, PriestSettings.HolyHeal.CountPrayerOfHealing), Spell.Cast("Prayer of Healing", on => (WoWUnit)on) ) ) ); } #endregion /* * HolyWordSanctify Holy Word: Sanctify * HolyWordSerenity Holy Word: Serenity * CircleOfHealing Circle of Healing * PrayerOfHealing Prayer of Healing * DivineHymn Divine Hymn * GuardianSpirit Guardian Spirit * VoidShift Void Shift */ #region Direct Heals if (maxDirectHeal != 0) // Nuke a Flash Heal { behavs.AddBehavior(400, "Surge of Light @ " + maxDirectHeal + "%", "Flash Heal", Spell.Cast("Flash Heal", mov => false, on => (WoWUnit)on, req => Me.HasActiveAura("Surge of Light") && ((WoWUnit)req).HealthPercent < 85 ) ); } if (PriestSettings.HolyHeal.HolyWordSerenity != 0) { behavs.AddBehavior(HealthToPriority(1) + 4, "Holy Word: Serenity @ " + PriestSettings.HolyHeal.HolyWordSerenity + "%", "Holy Word: Serenity", Spell.CastHack("Holy Word: Serenity", on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < PriestSettings.HolyHeal.HolyWordSerenity ) ); } var cmt = ""; var flashHealHealth = PriestSettings.HolyHeal.FlashHeal; if (!SpellManager.HasSpell("Heal")) { flashHealHealth = Math.Max(flashHealHealth, PriestSettings.HolyHeal.Heal); cmt = "(Adjusted for Heal)"; } if (flashHealHealth != 0) { behavs.AddBehavior(HealthToPriority(PriestSettings.HolyHeal.FlashHeal), "Flash Heal @ " + flashHealHealth + "% " + cmt, "Flash Heal", Spell.Cast("Flash Heal", mov => true, on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent <flashHealHealth, cancel => ((WoWUnit)cancel).HealthPercent> cancelHeal ) ); } if (PriestSettings.HolyHeal.Heal != 0) { behavs.AddBehavior(HealthToPriority(PriestSettings.HolyHeal.Heal), "Heal @ " + PriestSettings.HolyHeal.Heal + "%", "Heal", Spell.Cast("Heal", mov => true, on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent <PriestSettings.HolyHeal.Heal, cancel => ((WoWUnit)cancel).HealthPercent> cancelHeal ) ); } #endregion #region Tank - Low Priority Buffs if (PriestSettings.HolyHeal.Renew != 0) { behavs.AddBehavior(HealthToPriority(102), "Tank - Buff Renew @ " + PriestSettings.HolyHeal.Renew + "%", "Renew", // roll Renew on Tanks Spell.Cast("Renew", on => { WoWUnit unit = HealerManager.GetBestTankTargetForHOT("Renew", PriestSettings.HolyHeal.Renew); if (unit != null && Spell.CanCastHack("Renew", unit, skipWowCheck: true)) { Logger.WriteDebug("Buffing RENEW ON TANK: {0}", unit.SafeName()); return(unit); } return(null); }) ); } #endregion #region Lowest Priority Healer Tasks if (SingularRoutine.CurrentWoWContext == WoWContext.Instances) { behavs.AddBehavior(HealthToPriority(103), "Lightwell", "Lightwell", new Throttle(TimeSpan.FromSeconds(20), new Sequence( new Action(r => { _lightwellTarget = Group.Tanks.FirstOrDefault(t => { if (t.IsAlive && t.Combat && t.GotTarget() && t.CurrentTarget.IsBoss()) { if (t.Distance < 40 && t.SpellDistance(t.CurrentTarget) < 15) { Logger.WriteDiagnostic("Lightwell: found target {0}", t.SafeName()); return(true); } } return(false); }); return(_lightwellTarget == null ? RunStatus.Failure : RunStatus.Success); }), Spell.CastOnGround("Lightwell", location => WoWMathHelper.CalculatePointFrom(Me.Location, _lightwellTarget.Location, (float)Math.Min(10.0, _lightwellTarget.Distance / 2)), req => _lightwellTarget != null, false, desc => string.Format("10 yds from {0}", _lightwellTarget.SafeName()) ) ) ) ); } #endregion behavs.OrderBehaviors(); if (selfOnly == false && CompositeBuilder.CurrentBehaviorType == BehaviorType.Combat) { behavs.ListBehaviors(); } return(new PrioritySelector( ctx => selfOnly ? StyxWoW.Me : HealerManager.FindLowestHealthTarget(), // HealerManager.Instance.FirstUnit, Spell.WaitForCastOrChannel(), new Decorator( ret => !Spell.IsGlobalCooldown() && ret != null, behavs.GenerateBehaviorTree() ), new Decorator( ret => moveInRange, Movement.CreateMoveToUnitBehavior( ret => Battlegrounds.IsInsideBattleground ? (WoWUnit)ret : Group.Tanks.Where(a => a.IsAlive).OrderBy(a => a.Distance).FirstOrDefault(), 35f ) ) )); }
public static Composite EnhancementCombat() { return(new PrioritySelector( //new Decorator(ret => AdvancedAI.PvPRot, // EnhancementShamanPvP.CreateESPvPCombat), Spell.Cast("Healing Stream Totem", ret => Me.HealthPercent < 80 && !Totems.Exist(WoWTotemType.Water)), Spell.Cast("Healing Tide Totem", ret => HealerManager.GetCountWithHealth(55) > 6 && !Totems.Exist(WoWTotemType.Water)), //burst new Decorator(ret => AdvancedAI.Burst, new PrioritySelector( Spell.Cast("Stormlash Totem", ret => !Me.HasAura("Stormlash Totem")), Spell.Cast("Elemental Mastery"), Spell.Cast("Fire Elemental Totem"), Spell.Cast("Feral Spirit"), Spell.Cast("Ascendance", ret => !Me.HasAura("Ascendance")))), //new Decorator(ret => Unit.UnfriendlyUnits(10).Count() >= 3, // CreateAoe()), Spell.Cast("Searing Totem", ret => Me.GotTarget && Me.CurrentTarget.SpellDistance() < Totems.GetTotemRange(WoWTotem.Searing) - 2f && !Totems.Exist(WoWTotemType.Fire)), Spell.Cast("Unleash Elements", ret => SpellManager.HasSpell("Unleashed Fury")), Spell.Cast("Elemental Blast", ret => Me.HasAura("Maelstrom Weapon", 1)), new Decorator(ret => Me.HasAura("Maelstrom Weapon", 5), new PrioritySelector( Spell.Cast("Chain Lightning", ret => Unit.UnfriendlyUnitsNearTarget(10f).Count() >= 2), Spell.Cast("Lightning Bolt"))), //StormBlast new Decorator(ret => (Me.HasAura("Ascendance") && !WoWSpell.FromId(115356).Cooldown), new Action(ret => Lua.DoString("RunMacroText('/cast Stormblast')"))), Spell.Cast("Stormstrike"), Spell.Cast("Flame Shock", ret => Me.CachedHasAura("Unleash Flame") && !Me.CurrentTarget.HasMyAura("Flame Shock")), Spell.Cast("Lava Lash"), Spell.Cast("Flame Shock", ret => (Me.CachedHasAura("Unleash Flame") && Me.CurrentTarget.CachedGetAuraTimeLeft("Flame Shock") < 10) || !Me.CurrentTarget.HasMyAura("Flame Shock")), Spell.Cast("Unleash Elements"), new Decorator(ret => Me.HasAura("Maelstrom Weapon", 3) && !Me.HasAura("Ascendance"), new PrioritySelector( Spell.Cast("Chain Lightning", ret => Unit.UnfriendlyUnitsNearTarget(10f).Count() >= 2), Spell.Cast("Lightning Bolt"))), // need to make it at <2 Spell.Cast("Ancestral Swiftness", ret => !Me.HasAura("Maelstrom Weapon")), Spell.Cast("Lighting Bolt", ret => Me.HasAura("Ancestral Swiftness")), Spell.Cast("Earth Shock"), Spell.Cast("Earth Elemental Totem", ret => Me.CurrentTarget.IsBoss && SpellManager.Spells["Fire Elemental Totem"].CooldownTimeLeft.Seconds >= 50))); //need more gear //new Decorator(ret => Me.HasAura("Maelstrom Weapon", 1) && !Me.HasAura("Ascendance"), // new PrioritySelector( // Spell.Cast("Chain Lightning", ret => Unit.UnfriendlyUnitsNearTarget(10f).Count() >= 2), // Spell.Cast("Lightning Bolt") // ) // ) // ); }
public static Composite CreateDpsShamanOffHealBehavior() { HealerManager.NeedHealTargeting = true; PrioritizedBehaviorList behavs = new PrioritizedBehaviorList(); int cancelHeal = (int)Math.Max(SingularSettings.Instance.IgnoreHealTargetsAboveHealth, ShamanSettings.OffHealSettings.HealingSurge); bool moveInRange = (SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds); Logger.WriteDebugInBehaviorCreate("Shaman Healing: will cancel cast of direct heal if health reaches {0:F1}%", cancelHeal); /* * int dispelPriority = (SingularSettings.Instance.DispelDebuffs == RelativePriority.HighPriority) ? 999 : -999; * if (SingularSettings.Instance.DispelDebuffs != RelativePriority.None) * behavs.AddBehavior(dispelPriority, "Cleanse Spirit", null, Dispelling.CreateDispelBehavior()); */ #region Save the Group behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.OffHealSettings.AncestralSwiftness) + 500, String.Format("Oh Shoot Heal @ {0}%", ShamanSettings.OffHealSettings.AncestralSwiftness), null, new Decorator( ret => (Me.Combat || ((WoWUnit)ret).Combat) && ((WoWUnit)ret).PredictedHealthPercent() < ShamanSettings.OffHealSettings.AncestralSwiftness, new PrioritySelector( Spell.HandleOffGCD(Spell.BuffSelf("Ancestral Swiftness")), Spell.Cast("Healing Surge", on => (WoWUnit)on) ) ) ); #endregion #region AoE Heals behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.OffHealSettings.HealingStreamTotem) + 300, string.Format("Healing Stream Totem @ {0}%", ShamanSettings.OffHealSettings.HealingStreamTotem), "Healing Stream Totem", new Decorator( ret => StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid, Spell.Cast( "Healing Stream Totem", on => (!Me.Combat || Totems.Exist(WoWTotemType.Water)) ? null : HealerManager.Instance.TargetList.FirstOrDefault(p => p.PredictedHealthPercent() < ShamanSettings.OffHealSettings.HealingStreamTotem && p.Distance <= Totems.GetTotemRange(WoWTotem.HealingStream)) ) ) ); behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.OffHealSettings.HealingRain) + 200, string.Format("Healing Rain @ {0}% Count={1}", ShamanSettings.OffHealSettings.HealingRain, ShamanSettings.OffHealSettings.MinHealingRainCount), "Healing Rain", Spell.CastOnGround("Healing Rain", on => Restoration.GetBestHealingRainTarget(), req => HealerManager.Instance.TargetList.Count() > 1, false) ); #endregion #region Single Target Heals behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.OffHealSettings.HealingSurge), string.Format("Healing Surge @ {0}%", ShamanSettings.OffHealSettings.HealingSurge), "Healing Surge", Spell.Cast("Healing Surge", mov => true, on => (WoWUnit)on, req => ((WoWUnit)req).PredictedHealthPercent(includeMyHeals: true) < ShamanSettings.OffHealSettings.HealingSurge, cancel => ((WoWUnit)cancel).HealthPercent > cancelHeal ) ); #endregion behavs.OrderBehaviors(); if (Singular.Dynamics.CompositeBuilder.CurrentBehaviorType == BehaviorType.Heal) { behavs.ListBehaviors(); } return(new PrioritySelector( ctx => HealerManager.FindLowestHealthTarget(), // HealerManager.Instance.FirstUnit, new Decorator( ret => ret != null && (Me.Combat || ((WoWUnit)ret).Combat || ((WoWUnit)ret).PredictedHealthPercent() <= 99), new PrioritySelector( new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector( Totems.CreateTotemsBehavior(), /* * Spell.Cast("Earth Shield", * ret => (WoWUnit)ret, * ret => ret is WoWUnit && Group.Tanks.Contains((WoWUnit)ret) && Group.Tanks.All(t => !t.HasMyAura("Earth Shield"))), */ behavs.GenerateBehaviorTree(), new Decorator( ret => moveInRange, new Sequence( new Action(r => _moveToHealUnit = (WoWUnit)r), new PrioritySelector( Movement.CreateMoveToLosBehavior(on => _moveToHealUnit), Movement.CreateMoveToUnitBehavior(on => _moveToHealUnit, 30f, 25f) ) ) ) ) ) ) ) )); }
public static Composite CreateMistweaverCombatBehaviorInstances() { return(new PrioritySelector( CancelSoothingMistAsNeeded(), CreateMistWeaverDiagnosticOutputBehavior(on => MyHealTarget), new Decorator( req => true, new PrioritySelector( // HealerManager.CreateMeleeHealerMovementBehavior( Common.CreateMonkCloseDistanceBehavior( min => 30, on => (WoWUnit) on)), HealerManager.CreateStayNearTankBehavior(Common.CreateMonkCloseDistanceBehavior(min => 30, on => (WoWUnit)on)), /* * new Decorator( * unit => MovementManager.IsClassMovementAllowed * && !MonkSettings.DisableRoll * && (unit as WoWUnit).SpellDistance() > 10 * && Me.IsSafelyFacing(unit as WoWUnit, 5f), * Spell.Cast("Roll") * ) * ), */ new Decorator( ret => Me.Combat && HealerManager.AllowHealerDPS(), new PrioritySelector( CreateMistweaverMoveToEnemyTarget(), new Decorator( req => Me.GotTarget() && Me.CurrentTarget.IsAlive, Movement.CreateFaceTargetBehavior() ), Spell.WaitForCastOrChannel(), #region Spinning Crane Kick progress handler new Decorator( req => Me.HasActiveAura("Spinning Crane Kick"), // don't wait for Rushing Jade Wind since we can cast new PrioritySelector( new Action(r => { Logger.WriteFile(SpinningCraneKick + ": in progress with {0} ms left", (long)Me.GetAuraTimeLeft(SpinningCraneKick).TotalMilliseconds); return RunStatus.Failure; }), new Decorator( req => { if (Me.GetAuraTimeLeft(SpinningCraneKick).TotalMilliseconds < 333) { return false; } int countFriendly = Unit.NearbyGroupMembersAndPets.Count(u => u.SpellDistance() <= 8); if (countFriendly >= 3) { return false; } if (HealerManager.CancelHealerDPS()) { Logger.Write(LogColor.Cancel, "/cancel {0} since only {1} friendly targets hit and cannot DPS", SpinningCraneKick, countFriendly); return true; } int countEnemy = Unit.NearbyUnfriendlyUnits.Count(u => u.SpellDistance() <= 8); if ((countFriendly + countEnemy) < 3) { Logger.Write(LogColor.Cancel, "/cancel {0} since only {1} friendly and {2} enemy targets hit", SpinningCraneKick, countFriendly, countEnemy); return true; } return false; }, new Sequence( new Action(r => Me.CancelAura(SpinningCraneKick)), new Wait(1, until => !Me.HasActiveAura(SpinningCraneKick), new ActionAlwaysFail()) ) ), // dont go past here if SCK active new ActionAlwaysSucceed() ) ), #endregion new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector( new Decorator( req => Me.GotTarget() && Me.CurrentTarget.IsWithinMeleeRange, Helpers.Common.CreateAutoAttack() ), Helpers.Common.CreateInterruptBehavior(), Spell.Cast("Leg Sweep", ret => Spell.UseAOE && SingularRoutine.CurrentWoWContext == WoWContext.Normal && Me.CurrentTarget.IsWithinMeleeRange), Spell.Cast( SpinningCraneKick, ret => Spell.UseAOE && HealerManager.AllowHealerDPS() && Unit.NearbyUnfriendlyUnits.Count(u => u.SpellDistance() <= 8) >= MonkSettings.SpinningCraneKickCnt ), Spell.Cast("Rising Sun Kick"), Spell.Cast("Blackout Kick", req => Me.GetAuraStacks("Teachings of the Monastery") >= 3), Spell.Cast("Tiger Palm") ) ), Spell.Cast("Roll", req => MovementManager.IsClassMovementAllowed && !MonkSettings.DisableRoll && Me.CurrentTarget.Distance > minDistRollAllowed ) ) ) ) ), new Decorator( ret => !Spell.IsGlobalCooldown() && Unit.NearbyGroupMembers.Any(m => m.IsAlive && !m.IsMe), new PrioritySelector( CreateMistweaverMonkHealing(selfOnly: false), new Decorator( req => true, Helpers.Common.CreateInterruptBehavior() ) ) ), Spell.WaitForCastOrChannel() )); }
public static Composite CreateDiscHealOnlyBehavior() { if (SingularRoutine.CurrentWoWContext == WoWContext.Normal) { return(new ActionAlwaysFail()); } HealerManager.NeedHealTargeting = true; PrioritizedBehaviorList behavs = new PrioritizedBehaviorList(); int cancelHeal = (int)Math.Max(SingularSettings.Instance.IgnoreHealTargetsAboveHealth, PriestSettings.DiscHeal.Heal); cancelHeal = (int)Math.Max(cancelHeal, PriestSettings.DiscHeal.FlashHeal); Logger.WriteDebugInBehaviorCreate("Priest Healing: will cancel cast of direct heal if health reaches {0:F1}%", cancelHeal); if (SingularSettings.Instance.DispelDebuffs != RelativePriority.None) { int dispelPriority = (SingularSettings.Instance.DispelDebuffs == RelativePriority.HighPriority) ? 999 : -999; behavs.AddBehavior(dispelPriority, "Dispel", null, Common.CreatePriestDispelBehavior()); } #region Save the Group behavs.AddBehavior(HealthToPriority(PriestSettings.DiscHeal.PainSuppression) + PriEmergencyBase, "Pain Suppression @ " + PriestSettings.DiscHeal.PainSuppression + "%", "Pain Suppression", new Decorator( req => Me.Combat, Spell.Cast("Pain Suppression", mov => false, on => (WoWUnit)on, req => ((WoWUnit)req).IsPlayer && ((WoWUnit)req).HealthPercent < PriestSettings.DiscHeal.PainSuppression ) ) ); behavs.AddBehavior(HealthToPriority(PriestSettings.DiscHeal.PowerWordBarrier) + PriEmergencyBase, "Power Word: Barrier @ " + PriestSettings.DiscHeal.PowerWordBarrier + "%", "Power Word: Barrier", new Decorator( ret => Me.Combat && (StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid), new PrioritySelector( context => GetBestPowerWordBarrierTarget(), new Decorator( ret => ret != null, new PrioritySelector( new Sequence( new Action(r => Logger.WriteDebug("PW:B - attempting cast")), Spell.CastOnGround("Power Word: Barrier", on => (WoWUnit)on, req => true, false) ), new Action(ret => { if (ret != null) { if (!((WoWUnit)ret).IsValid) { Logger.WriteDebug("PW:B - FAILED - Healing Target object became invalid"); } else if (((WoWUnit)ret).Distance > 40) { Logger.WriteDebug("PW:B - FAILED - Healing Target moved out of range"); } else if (!Spell.CanCastHack("Power Word: Barrier")) { Logger.WriteDebug("PW:B - FAILED - Spell.CanCastHack() said NO to Power Word: Barrier"); } else if (GameWorld.IsInLineOfSpellSight(StyxWoW.Me.GetTraceLinePos(), ((WoWUnit)ret).Location)) { Logger.WriteDebug("PW:B - FAILED - Spell.CanCastHack() unit location not in Line of Sight"); } else if (Spell.IsSpellOnCooldown("Power Word: Barrier")) { Logger.WriteDebug("PW:B - FAILED - Power Word: Barrier is on cooldown"); } else { Logger.WriteDebug("PW:B - Something FAILED with Power Word: Barrier cast sequence (target={0}, h={1:F1}%, d={2:F1} yds, spellmax={3:F1} yds, cooldown={4})", ((WoWUnit)ret).SafeName(), ((WoWUnit)ret).HealthPercent, ((WoWUnit)ret).Distance, Spell.ActualMaxRange("Power Word: Barrier", (WoWUnit)ret), Spell.IsSpellOnCooldown("Power Word: Barrier") ); } } return(RunStatus.Failure); }) ) ) ) ) ); #endregion #region Tank Buffing behavs.AddBehavior(HealthToPriority(99) + PriHighBase, "Power Word: Shield - Tank", "Power Word: Shield", new Decorator( req => Me.Combat, Spell.Buff( "Power Word: Shield", on => Group.Tanks .FirstOrDefault(u => u.IsAlive && CanWePwsUnit(u) && Spell.CanCastHack("Power Word: Shield", u)) ) ) ); behavs.AddBehavior(HealthToPriority(99) + PriHighBase, "Plea - Tank Attonement", "Plea", new Decorator( req => Me.Combat, Spell.Cast( "Plea", on => Group.Tanks .FirstOrDefault(u => u.IsAlive && !u.HasActiveAura("Atonement") && Spell.CanCastHack("Plea", u)) ) ) ); #endregion #region Save the Highest Priority Targets const int SaveEssential = 30; behavs.AddBehavior(HealthToPriority(SaveEssential) + PriSaveEssential, "Save Highest Priority Target below " + SaveEssential + "%", "Shadow Mend", new Decorator( req => Me.Combat, new PrioritySelector( ctx => HealerManager.FindHighestPriorityTarget(), Spell.HandleOffGCD(Spell.Buff("Power Infusion", on => Me, req => req != null, gcd: HasGcd.No)), Spell.Buff( "Power Word: Shield", on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < PriestSettings.DiscHeal.PowerWordShield && CanWePwsUnit((WoWUnit)req) ), Spell.Cast( "Plea", on => (WoWUnit)on, req => !((WoWUnit)req).HasActiveAura("Atonement") ), Spell.Cast( "Shadow Mend", on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < PriestSettings.DiscHeal.ShadowMend ) ) ) ); #endregion #region AoE Heals if (PriestSettings.DiscHeal.DiscLevel90Talent != 0) { behavs.AddBehavior(HealthToPriority(PriestSettings.DiscHeal.DiscLevel90Talent) + PriAoeBase, "Halo @ " + PriestSettings.DiscHeal.DiscLevel90Talent + "% MinCount: " + PriestSettings.DiscHeal.CountLevel90Talent, "Halo", new Decorator( ret => SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds || SingularRoutine.CurrentWoWContext == WoWContext.Instances, new Decorator( ret => ret != null && HealerManager.Instance.TargetList.Count(u => u.IsAlive && u.HealthPercent < PriestSettings.DiscHeal.DiscLevel90Talent && u.Distance < 30) >= PriestSettings.DiscHeal.CountLevel90Talent && Spell.CanCastHack("Halo", (WoWUnit)ret), new PrioritySelector( Spell.HandleOffGCD(Spell.BuffSelf("Archangel", req => ((WoWUnit)req) != null && Me.HasAura("Evangelism", 5))), Spell.CastOnGround("Halo", on => (WoWUnit)on, req => true) ) ) ) ); } if (PriestSettings.DiscHeal.DiscLevel90Talent != 0) { behavs.AddBehavior(HealthToPriority(PriestSettings.DiscHeal.DiscLevel90Talent) + PriAoeBase, "Cascade @ " + PriestSettings.DiscHeal.DiscLevel90Talent + "% MinCount: " + PriestSettings.DiscHeal.CountLevel90Talent, "Cascade", new Decorator( ret => StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid, new PrioritySelector( context => HealerManager.GetBestCoverageTarget("Cascade", PriestSettings.DiscHeal.DiscLevel90Talent, 40, 30, PriestSettings.DiscHeal.CountLevel90Talent), new Decorator( ret => ret != null && Spell.CanCastHack("Cascade", (WoWUnit)ret), new PrioritySelector( Spell.HandleOffGCD(Spell.BuffSelf("Archangel", req => Me.HasAura("Evangelism", 5))), Spell.Cast("Cascade", mov => true, on => (WoWUnit)on, req => true) ) ) ) ) ); } if (PriestSettings.DiscHeal.PowerWordRadiance != 0) { behavs.AddBehavior(99 + PriAoeBase, "Power Word: Radiance", "Power Word: Radiance", new Decorator( ret => StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid, new PrioritySelector( context => HealerManager.GetBestCoverageTarget("Power Word: Radiance", PriestSettings.DiscHeal.PowerWordRadiance, 40, 30, PriestSettings.DiscHeal.CountPowerWordRadiance, o => o != null && !((WoWUnit)o).HasActiveAura("Atonement")), CastBuffsBehavior("Power Word: Radiance"), Spell.Cast("Power Word: Radiance", on => (WoWUnit)on) ) ) ); } #endregion #region Direct Heals if (PriestSettings.DiscHeal.Plea != 0) { behavs.AddBehavior(100 + PriSingleBase, "Atonement Plea @ " + PriestSettings.DiscHeal.Plea + "%", "Plea", new Decorator( req => !((WoWUnit)req).HasActiveAura("Atonement"), new PrioritySelector( CastBuffsBehavior("Plea"), Spell.Cast("Plea", mov => false, on => (WoWUnit)on, req => true, cancel => false ) ) ) ); } if (HasReflectiveShield) { behavs.AddBehavior(100 + PriSingleBase, "Power Word: Shield Self (Glyph of Reflective Shield)", "Power Word: Shield", Spell.BuffSelf("Power Word: Shield", req => CanWePwsUnit(Me) && Unit.NearbyUnitsInCombatWithUsOrOurStuff.Any()) ); } if (PriestSettings.DiscHeal.PowerWordShield != 0) { behavs.AddBehavior(99 + PriSingleBase, "Power Word: Shield @ " + PriestSettings.DiscHeal.PowerWordShield + "%", "Power Word: Shield", Spell.Buff("Power Word: Shield", on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < PriestSettings.DiscHeal.PowerWordShield && CanWePwsUnit((WoWUnit)req)) ); } #endregion behavs.OrderBehaviors(); behavs.ListBehaviors(); return(new PrioritySelector( ctx => HealerManager.FindHighestPriorityTarget(), Spell.WaitForCastOrChannel(), new Decorator( ret => !Spell.IsGlobalCooldown() && ret != null, behavs.GenerateBehaviorTree() ) )); }
public static Composite CreateFeralHeal() { return(new PrioritySelector( CreateFeralDiagnosticOutputBehavior("Combat"), Spell.BuffSelf("Survival Instincts", req => Me.HealthPercent < DruidSettings.SurvivalInstinctsHealth), Spell.Cast( "Regrowth", on => { int pstime = (int)Me.GetAuraTimeLeft("Predatory Swiftness").TotalMilliseconds; if (pstime < 150) { return null; } WoWUnit unit = null; if (Common.HasTalent(DruidTalents.Bloodtalons) && (Me.ComboPoints >= 4 || pstime < 1650)) { unit = Unit.GroupMembers .Where(u => u.IsAlive && Spell.CanCastHack("Regrowth", u)) .OrderBy(u => (int)u.HealthPercent) .FirstOrDefault(); if (unit == null && Spell.CanCastHack("Regrowth", Me)) { unit = Me; } if (unit != null) { Logger.Write(LogColor.Hilite, "^Bloodtalons: buffing bleeds"); } return unit; } // raids: don't waste a GCD on healing without a buff if (SingularRoutine.CurrentHealContext == HealingContext.Raids) { return null; } // else: find lowest health unit unit = HealerManager.NeedHealTargeting ? HealerManager.FindHighestPriorityTarget() : Unit.GroupMembers .Where(u => u.IsAlive && Spell.CanCastHack("Regrowth", u)) .OrderBy(u => (int)u.HealthPercent) .FirstOrDefault(); if (unit != null) { if (unit.HealthPercent < DruidSettings.PredSwiftnessHealingTouchHealth) { return unit; } if (unit.HealthPercent < 90 && pstime < 1650) { return unit; } unit = null; } return unit; } ) )); }
//private static WoWUnit _moveToHealTarget = null; //private static WoWUnit _lastMoveToTarget = null; // temporary lol name ... will revise after testing public static Composite CreateHealingOnlyBehavior(bool selfOnly, bool moveInRange) { BehaviorType behaveType = Dynamics.CompositeBuilder.CurrentBehaviorType; if (SingularRoutine.CurrentWoWContext == WoWContext.Normal) { return(new ActionAlwaysFail()); } HealerManager.NeedHealTargeting = true; PrioritizedBehaviorList behavs = new PrioritizedBehaviorList(); int cancelHeal = (int)Math.Max(SingularSettings.Instance.IgnoreHealTargetsAboveHealth, Math.Max(DruidSettings.Heal.HealingTouch, DruidSettings.Heal.Regrowth)); int maxDirectHeal = Math.Max(DruidSettings.Heal.HealingTouch, DruidSettings.Heal.Regrowth); Logger.WriteDebugInBehaviorCreate("Druid Healing: will cancel cast of direct heal if health reaches {0:F1}%", cancelHeal); #region Cleanse if (SingularSettings.Instance.DispelDebuffs != RelativePriority.None) { int dispelPriority = (SingularSettings.Instance.DispelDebuffs == RelativePriority.HighPriority) ? 999 : -999; behavs.AddBehavior(dispelPriority, "Nature's Cure", "Nature's Cure", Dispelling.CreateDispelBehavior()); } #endregion #region Save the Group // Tank: Rebirth if (Helpers.Common.CombatRezTargetSetting != CombatRezTarget.None) { behavs.AddBehavior(799, "Rebirth Tank/Healer", "Rebirth", Helpers.Common.CreateCombatRezBehavior("Rebirth", filter => true, requirements => true) ); } if (DruidSettings.Heal.HeartOfTheWild != 0) { behavs.AddBehavior(795, "Heart of the Wild @ " + DruidSettings.Heal.HeartOfTheWild + "% MinCount: " + DruidSettings.Heal.CountHeartOfTheWild, "Heart of the Wild", new Decorator( ret => Me.IsInGroup(), Spell.BuffSelf( "Heart of the Wild", req => ((WoWUnit)req).HealthPercent < DruidSettings.Heal.HeartOfTheWild && DruidSettings.Heal.CountHeartOfTheWild <= HealerManager.Instance.TargetList .Count(p => p.IsAlive && p.HealthPercent <= DruidSettings.Heal.HeartOfTheWild && p.Location.DistanceSquared(((WoWUnit)req).Location) <= 30 * 30) ) ) ); } if (DruidSettings.Heal.Tranquility != 0) { behavs.AddBehavior(798, "Tranquility @ " + DruidSettings.Heal.Tranquility + "% MinCount: " + DruidSettings.Heal.CountTranquility, "Tranquility", new Decorator( ret => Me.IsInGroup(), Spell.Cast( "Tranquility", mov => true, on => (WoWUnit)on, req => HealerManager.Instance.TargetList.Count(h => h.IsAlive && h.HealthPercent < DruidSettings.Heal.Tranquility && h.SpellDistance() < 40) >= DruidSettings.Heal.CountTranquility, cancel => false ) ) ); } if (DruidSettings.Heal.Swiftmend != 0) { behavs.AddBehavior(797, "Swiftmend Direct @ " + DruidSettings.Heal.Swiftmend + "%", "Swiftmend", new Decorator( ret => (!Spell.IsSpellOnCooldown("Swiftmend") || Spell.GetCharges("Force of Nature") > 0) && ((WoWUnit)ret).PredictedHealthPercent(includeMyHeals: true) < DruidSettings.Heal.Swiftmend && (Me.IsInGroup()) && Spell.CanCastHack("Rejuvenation", (WoWUnit)ret, skipWowCheck: true), new Sequence( new DecoratorContinue( req => !((WoWUnit)req).HasAnyAura("Rejuvenation", "Regrowth"), new PrioritySelector( Spell.Buff("Rejuvenation", on => (WoWUnit)on), Spell.Cast("Regrowth", on => (WoWUnit)on, req => !glyphRegrowth, cancel => false) ) ), new Wait(TimeSpan.FromMilliseconds(500), until => ((WoWUnit)until).HasAnyAura("Rejuvenation", "Regrowth"), new ActionAlwaysSucceed()), new Wait(TimeSpan.FromMilliseconds(1500), until => !Spell.IsGlobalCooldown() && !Spell.IsCastingOrChannelling(), new ActionAlwaysSucceed()), new PrioritySelector( Spell.Cast("Force of Nature", on => (WoWUnit)on, req => Spell.GetCharges("Force of Nature") > 1), Spell.Cast("Swiftmend", on => (WoWUnit)on) ) ) ) ); } #endregion #region Tank Buffing // Priority Buff: buff Mastery: Harmony if (Me.Level >= 80 && DruidSettings.Heal.BuffHarmony) { behavs.AddBehavior(100 + PriHighBase, "Buff Harmony w/ Healing Touch", "Healing Touch", new Sequence( Spell.Cast( "Healing Touch", mov => true, on => (WoWUnit)on, req => { if (Me.GetAuraTimeLeft("Harmony").TotalMilliseconds > 1500) { return(false); } if (((WoWUnit)req).HealthPercent < maxDirectHeal) { return(false); } if (Spell.DoubleCastContains(Me, "Harmony")) { return(false); } if (!Spell.CanCastHack("Healing Touch", (WoWUnit)req)) { return(false); } Logger.Write(LogColor.Hilite, "^Harmony: buffing with Healing Touch"); return(true); }, cancel => Me.GetAuraTimeLeft("Harmony").TotalMilliseconds > 1500 && ((WoWUnit)cancel).HealthPercent > cancelHeal ), new Action(r => Spell.UpdateDoubleCast("Harmony", Me)) ) ); } // Tank: Lifebloom behavs.AddBehavior(99 + PriHighBase, "Lifebloom - Tank", "Lifebloom", Spell.Buff("Lifebloom", on => GetLifebloomTarget(), req => Me.Combat) ); // Tank: Rejuv if Lifebloom not trained yet if (DruidSettings.Heal.Rejuvenation != 0) { behavs.AddBehavior(98 + PriHighBase, "Rejuvenation - Tank", "Rejuvenation", Spell.Buff("Rejuvenation", on => { WoWUnit unit = GetBestTankTargetFor("Rejuvenation"); if (unit != null && Spell.CanCastHack("Rejuvenation", unit, skipWowCheck: true)) { Logger.WriteDebug("Buffing Rejuvenation ON TANK: {0}", unit.SafeName()); return(unit); } return(null); }) ); } if (DruidSettings.Heal.Ironbark != 0) { if (SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds) { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.Ironbark) + PriHighBase, "Ironbark @ " + DruidSettings.Heal.Ironbark + "%", "Ironbark", Spell.Buff("Ironbark", on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < DruidSettings.Heal.Ironbark) ); } else { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.Ironbark) + PriHighBase, "Ironbark - Tank @ " + DruidSettings.Heal.Ironbark + "%", "Ironbark", Spell.Buff("Ironbark", on => Group.Tanks.FirstOrDefault(u => u.IsAlive && u.HealthPercent < DruidSettings.Heal.Ironbark && !u.HasActiveAura("Ironbark"))) ); } } if (DruidSettings.Heal.CenarionWard != 0) { if (SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds) { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.CenarionWard) + PriHighBase, "Cenarion Ward @ " + DruidSettings.Heal.CenarionWard + "%", "Cenarion Ward", Spell.Buff("Cenarion Ward", on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < DruidSettings.Heal.CenarionWard) ); } else { behavs.AddBehavior(100 + PriHighBase, "Cenarion Ward - Tanks", "Cenarion Ward", Spell.Buff("Cenarion Ward", on => GetLifebloomTarget(), req => Me.Combat) ); } } if (DruidSettings.Heal.NaturesVigil != 0) { if (SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds) { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.NaturesVigil) + PriHighBase, "Nature's Vigil @ " + DruidSettings.Heal.NaturesVigil + "%", "Nature's Vigil", Spell.Buff("Nature's Vigil", on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < DruidSettings.Heal.NaturesVigil) ); } else { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.NaturesVigil) + PriHighBase, "Nature's Vigil - Tank @ " + DruidSettings.Heal.NaturesVigil + "%", "Nature's Vigil", Spell.Buff("Nature's Vigil", on => Group.Tanks.FirstOrDefault(u => u.IsAlive && u.HealthPercent < DruidSettings.Heal.NaturesVigil && !u.HasActiveAura("Nature's Vigil"))) ); } } if (DruidSettings.Heal.TreeOfLife != 0) { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.TreeOfLife) + PriHighBase, "Incarnation: Tree of Life @ " + DruidSettings.Heal.TreeOfLife + "% MinCount: " + DruidSettings.Heal.CountTreeOfLife, "Incarnation: Tree of Life", Spell.BuffSelf("Incarnation: Tree of Life", req => ((WoWUnit)req).HealthPercent < DruidSettings.Heal.TreeOfLife && DruidSettings.Heal.CountTreeOfLife <= HealerManager.Instance.TargetList.Count(h => h.IsAlive && h.HealthPercent < DruidSettings.Heal.TreeOfLife)) ); } #endregion #region AoE Heals if (DruidSettings.Heal.Efflorescence != 0) { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.Efflorescence) + PriAoeBase, "Efflorescence @ " + DruidSettings.Heal.Efflorescence, "Efflorescence", new Decorator( ret => Me.IsInGroup(), CreateMushroomSetBehavior() ) ); } if (DruidSettings.Heal.WildGrowth != 0) { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.WildGrowth) + PriAoeBase, "Wild Growth @ " + DruidSettings.Heal.WildGrowth + "% MinCount: " + DruidSettings.Heal.CountWildGrowth, "Wild Growth", new Decorator( ret => Me.IsInGroup(), new PrioritySelector( // ctx => HealerManager.GetBestCoverageTarget("Wild Growth", Settings.Heal.WildGrowth, 40, 30, Settings.Heal.CountWildGrowth), Spell.Buff( "Wild Growth", on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < DruidSettings.Heal.WildGrowth && DruidSettings.Heal.CountWildGrowth <= HealerManager.Instance.TargetList .Count(p => p.IsAlive && p.HealthPercent <= DruidSettings.Heal.WildGrowth && p.Location.DistanceSquared(((WoWUnit)req).Location) <= 30 * 30)) ) ) ); } #endregion #region Direct Heals // Regrowth above ToL: Lifebloom so we use Clearcasting procs behavs.AddBehavior(200 + PriSingleBase, "Regrowth on Clearcasting", "Regrowth", new Sequence( CastRegrowth( on => { if (Spell.DoubleCastContains(Me, "Clearcasting")) { return(null); } double clearLeft = Me.GetAuraTimeLeft("Clearcasting").TotalMilliseconds; // ignore if less than regrowth cast time left if (clearLeft < Spell.GetSpellCastTime("Regrowth").TotalMilliseconds) { return(null); } WoWUnit target = (WoWUnit)on; double healthPercent = target == null ? 0.0 : target.HealthPercent; // clearLeft > 3000, so clear target if not needed now and try again next pass if (target != null) { // still have enough time remaining on Clearcasting buff, so hold free Regrowth a bit longer to see if greater need arises if (healthPercent > maxDirectHeal) { target = null; } else if (!Spell.CanCastHack("Regrowth", target)) { target = null; } } if (target != null) { Logger.Write(LogColor.Hilite, "^Clearcasting: Regrowth at Health {0:F1}%", healthPercent); } return(target); }, req => true, cancel => ((WoWUnit)cancel).HealthPercent > cancelHeal && Me.GetAuraTimeLeft("Clearcasting").TotalMilliseconds > 4000 && !((WoWUnit)cancel).GetAuraTimeLeft("Lifebloom").TotalMilliseconds.Between(Me.CurrentCastTimeLeft.TotalMilliseconds, 8750) ), // add double cast entry to make sure we don't try to reuse immediately new Action(r => Spell.UpdateDoubleCast("Clearcasting", Me, 500)) ) ); behavs.AddBehavior(198 + PriSingleBase, "Rejuvenation @ " + DruidSettings.Heal.Rejuvenation + "%", "Rejuvenation", new PrioritySelector( Spell.Buff("Rejuvenation", 1, on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent < DruidSettings.Heal.Rejuvenation ) ) ); if (DruidSettings.Heal.HealingTouch != 0) { // roll 3 Rejuvs if Glyph of Rejuvenation equipped if (glyphRejuv) { // make priority 1 higher than Noursh (-1 here due to way HealerManager.HealthToPriority works) behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.HealingTouch - 1) + PriSingleBase, "Roll 3 Rejuvenations for Glyph", "Rejuvenation", new PrioritySelector( Spell.Buff("Rejuvenation", 1, on => { // iterate through so we can stop at either 3 with rejuv or first without int cntHasAura = 0; foreach (WoWUnit h in HealerManager.Instance.TargetList) { if (h.IsAlive) { if (!h.HasKnownAuraExpired("Rejuvenation", 1)) { cntHasAura++; if (cntHasAura >= 3) { return(null); } } else { if (h.InLineOfSpellSight) { return(h); } } } } return(null); }, req => true ) ) ); } } int regrowthInstead = 0; bool healingTouchKnown = SpellManager.HasSpell("Healing Touch"); if (DruidSettings.Heal.HealingTouch != 0) { string whyRegrowth = ""; if (SpellManager.HasSpell("Regrowth")) { if (!healingTouchKnown) { regrowthInstead = Math.Max(DruidSettings.Heal.Regrowth, DruidSettings.Heal.HealingTouch); whyRegrowth = "Regrowth (since Healing Touch unknown) @ "; } } if (regrowthInstead != 0) { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.HealingTouch) + PriSingleBase, whyRegrowth + regrowthInstead + "%", "Regrowth", new PrioritySelector( Spell.Cast( sp => (Me.Combat || !healingTouchKnown) ? "Regrowth" : "Healing Touch", mov => true, on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent <regrowthInstead, cancel => ((WoWUnit)cancel).HealthPercent> cancelHeal ) ) ); } else { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.HealingTouch) + PriSingleBase, "Healing Touch @ " + DruidSettings.Heal.HealingTouch + "%", "Healing Touch", new PrioritySelector( Spell.Cast("Healing Touch", mov => true, on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent <DruidSettings.Heal.HealingTouch, cancel => ((WoWUnit)cancel).HealthPercent> cancelHeal ) ) ); } } if (DruidSettings.Heal.Regrowth != 0 && regrowthInstead == 0) { behavs.AddBehavior(HealerManager.HealthToPriority(DruidSettings.Heal.Regrowth) + PriSingleBase, "Regrowth @ " + DruidSettings.Heal.Regrowth + "%", "Regrowth", new PrioritySelector( Spell.Cast("Regrowth", mov => true, on => (WoWUnit)on, req => ((WoWUnit)req).HealthPercent <DruidSettings.Heal.Regrowth, cancel => ((WoWUnit)cancel).HealthPercent> cancelHeal ) ) ); } #endregion #region Lowest Priority Healer Tasks behavs.AddBehavior(3, "Rejuvenation while Moving @ " + SingularSettings.Instance.IgnoreHealTargetsAboveHealth + "%", "Rejuvenation", new Decorator( req => Me.IsMoving, Spell.Buff("Rejuvenation", on => HealerManager.Instance.TargetList.FirstOrDefault(h => h.IsAlive && h.HealthPercent < SingularSettings.Instance.IgnoreHealTargetsAboveHealth && !h.HasMyAura("Rejuvenation") && Spell.CanCastHack("Rejuvenation", h, true)), req => true ) ) ); #endregion behavs.OrderBehaviors(); if (selfOnly == false && Singular.Dynamics.CompositeBuilder.CurrentBehaviorType == BehaviorType.Heal) { behavs.ListBehaviors(); } return(new PrioritySelector( ctx => selfOnly ? StyxWoW.Me : HealerManager.FindHighestPriorityTarget(), // HealerManager.Instance.FirstUnit, Spell.WaitForCastOrChannel(), new Decorator( ret => !Spell.IsGlobalCooldown() && ret != null, behavs.GenerateBehaviorTree() ), new Decorator( ret => moveInRange, Movement.CreateMoveToUnitBehavior( ret => Battlegrounds.IsInsideBattleground ? (WoWUnit)ret : Group.Tanks.Where(a => a.IsAlive).OrderBy(a => a.Distance).FirstOrDefault(), 35f ) ) )); }
public static Composite ShadowCombat() { return(new PrioritySelector( //new Decorator(ret => Me.IsCasting && (Me.ChanneledSpell == null || Me.ChanneledSpell.Id != MindFlay), new Action(ret => { return RunStatus.Success; })), new Decorator(ret => !Me.Combat || Me.Mounted || !Me.CurrentTarget.IsAlive || !Me.GotTarget /*|| Me.ChanneledSpell.Name == "Hymn of Hope"*/, new ActionAlwaysSucceed()), new Decorator(ret => Me.ChanneledSpell != null, new PrioritySelector( new Decorator(ret => Me.ChanneledSpell.Name == "Hymn of Hope", Spell.WaitForCastOrChannel()))), Hymn(), MassDispel(), new Decorator(ret => AdvancedAI.Burst, new PrioritySelector( Spell.Cast("Lifeblood"), new Action(ret => { Item.UseHands(); return RunStatus.Failure; }), new Action(ret => { Item.UseTrinkets(); return RunStatus.Failure; }), new Decorator(ret => Me.CurrentTarget.IsBoss(), new PrioritySelector( Spell.Cast("Shadowfiend", ret => Spell.GetSpellCooldown("Shadowfiend").TotalMilliseconds < 10), Spell.Cast("Mindbender", ret => Spell.GetSpellCooldown("Mindbender").TotalMilliseconds < 10), Spell.Cast("Power Infusion", ret => SpellManager.HasSpell("Power Infusion") && Spell.GetSpellCooldown("Power Infusion").TotalMilliseconds < 10))))), Spell.Cast("Void Shift", on => VoidTank), Spell.Cast("Prayer of Mending", on => Me, ret => Me.HealthPercent <= 85), new Throttle(1, Spell.Cast("Vampiric Embrace", ret => HealerManager.GetCountWithHealth(55) > 4)), new Decorator( ret => Me.GotTarget && Me.ChanneledSpell != null, new PrioritySelector( new Decorator( ret => Me.ChanneledSpell.Name == "Mind Flay" && CMF && !SpellManager.HasSpell("Solace and Insanity"), new Sequence( new Action(ret => Logging.WriteDiagnostic("/cancel Mind Flay on {0} @ {1:F1}%", Me.CurrentTarget.SafeName(), Me.CurrentTarget.HealthPercent)), new Action(ret => SpellManager.StopCasting()), new WaitContinue(TimeSpan.FromMilliseconds(500), ret => Me.ChanneledSpell == null, new ActionAlwaysSucceed()))))), new Decorator(ret => Unit.UnfriendlyUnitsNearTarget(10f).Count() > 2 && AdvancedAI.Aoe, CreateAOE()), //Spell.Cast("Shadow Word: Pain", ret => Orbs == 3 && Me.CurrentTarget.CachedHasAura("Shadow Word: Pain") && Me.CurrentTarget.CachedGetAuraTimeLeft("Shadow Word: Pain") <= 6), //Spell.Cast("Vampiric Touch", ret => Orbs == 3 && Me.CurrentTarget.CachedHasAura("Vampiric Touch") && Me.CurrentTarget.CachedGetAuraTimeLeft("Shadow Word: Pain") <= 6), //Spell.Cast("Devouring Plague", ret => Orbs == 3 && Me.CurrentTarget.CachedGetAuraTimeLeft("Shadow Word: Pain") >= 6 && Me.CurrentTarget.CachedGetAuraTimeLeft("Vampiric Touch") >= 6), Spell.Cast("Devouring Plague", ret => Orbs == 3), Spell.Cast("Mind Blast", ret => Orbs < 3), new Throttle(2, new PrioritySelector( Spell.Cast("Shadow Word: Death", ret => Orbs < 3))), //new Throttle(2, // new PrioritySelector( // Spell.Cast("Mind Flay", ret => Me.CurrentTarget.CachedHasAura("Devouring Plague")))), Spell.Cast("Mind Flay", on => Me.CurrentTarget, ret => SpellManager.HasSpell("Solace and Insanity") && Me.CurrentTarget.CachedHasAura("Devouring Plague")), new Throttle(1, new PrioritySelector( Spell.Cast("Shadow Word: Pain", ret => !Me.CurrentTarget.CachedHasAura("Shadow Word: Pain") || Me.CurrentTarget.HasAuraExpired("Shadow Word: Pain", 3)))), new Throttle(TimeSpan.FromMilliseconds(1500), new PrioritySelector( Spell.Cast("Vampiric Touch", ret => !Me.CurrentTarget.CachedHasAura("Vampiric Touch") || Me.CurrentTarget.HasAuraExpired("Vampiric Touch", 4)))), Spell.Cast("Mind Spike", ret => Me.HasAura(87160)), Spell.Cast("Halo", ret => Me.CurrentTarget.Distance < 30), Spell.Cast("Cascade"), Spell.Cast("Divine Star"), //new Throttle(1, // new PrioritySelector( Spell.Cast("Mind Flay", on => Me.CurrentTarget, ret => !CMF), Spell.Cast("Shadow Word: Death", ret => Me.IsMoving), Spell.Cast("Shadow Word: Pain", ret => Me.IsMoving))); }
public static Composite CreateRestoShamanCombatBehaviorInstances() { #if OLD_APPROACH return(new PrioritySelector( ctx => HealerManager.Instance.TargetList.Any(t => !t.IsMe && t.IsAlive), Safers.EnsureTarget(), Movement.CreateFaceTargetBehavior(), Spell.WaitForCastOrChannel(), new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector( CreateRestoDiagnosticOutputBehavior(on => Me.CurrentTarget), new Decorator( ret => (bool)ret, new PrioritySelector( HealerManager.CreateStayNearTankBehavior(), CreateRestoShamanHealingOnlyBehavior(selfOnly: false), Helpers.Common.CreateInterruptBehavior(), Dispelling.CreatePurgeEnemyBehavior("Purge"), Totems.CreateTotemsBehavior(), Spell.Cast("Lightning Bolt", ret => TalentManager.HasGlyph("Telluric Currents")) ) ), new Decorator( ret => !((bool)ret), new PrioritySelector( CreateRestoDiagnosticOutputBehavior(on => HealerManager.Instance.FirstUnit), Spell.Cast("Elemental Blast"), Spell.Buff("Flame Shock", true, on => Me.CurrentTarget, req => true, 3), Spell.Cast("Lava Burst"), Spell.Cast("Frost Shock"), Spell.Cast("Chain Lightning", ret => Spell.UseAOE && Unit.UnfriendlyUnitsNearTarget(10f).Count() >= 2 && !Unit.UnfriendlyUnitsNearTarget(12f).Any(u => u.IsCrowdControlled())), Spell.Cast("Lightning Bolt") ) ) ) ) )); #else return(new PrioritySelector( Spell.WaitForCastOrChannel(), CreateRestoDiagnosticOutputBehavior(on => HealerManager.FindLowestHealthTarget()), HealerManager.CreateStayNearTankBehavior(), new Decorator( ret => Me.Combat && HealerManager.AllowHealerDPS(), new PrioritySelector( Helpers.Common.EnsureReadyToAttackFromMediumRange(), Movement.CreateFaceTargetBehavior(), Spell.WaitForCastOrChannel(), new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector( Helpers.Common.CreateInterruptBehavior(), Totems.CreateTotemsBehavior(), Movement.WaitForFacing(), Movement.WaitForLineOfSpellSight(), Common.CastElementalBlast(cancel: c => HealerManager.CancelHealerDPS()), Spell.Buff("Flame Shock", 3, on => Me.CurrentTarget, req => true), Spell.Cast("Lava Burst", on => Me.CurrentTarget, req => true, cancel => HealerManager.CancelHealerDPS()), Spell.Cast("Frost Shock"), Spell.Cast("Chain Lightning", on => Me.CurrentTarget, req => Spell.UseAOE && Unit.UnfriendlyUnitsNearTarget(10f).Count() >= 2 && !Unit.UnfriendlyUnitsNearTarget(12f).Any(u => u.IsCrowdControlled()), cancel => HealerManager.CancelHealerDPS()), Spell.Cast("Lightning Bolt", on => Me.CurrentTarget, req => true, cancel => HealerManager.CancelHealerDPS()) ) ) ) ), new Decorator( ret => Unit.NearbyGroupMembers.Any(m => m.IsAlive && !m.IsMe), new PrioritySelector( CreateRestoShamanHealingOnlyBehavior(selfOnly: false), Helpers.Common.CreateInterruptBehavior(), Dispelling.CreatePurgeEnemyBehavior("Purge"), Totems.CreateTotemsBehavior(), new Decorator( req => TalentManager.HasGlyph("Telluric Currents"), new PrioritySelector( Safers.EnsureTarget(), Movement.CreateFaceTargetBehavior(), Spell.Cast("Lightning Bolt", mov => true, on => Unit.NearbyUnitsInCombatWithUsOrOurStuff .Where(u => u.IsAlive && u.SpellDistance() < 40 && Me.IsSafelyFacing(u)) .OrderByDescending(u => u.HealthPercent) .FirstOrDefault(), req => !HealerManager.Instance.TargetList.Any(h => h.IsAlive && h.SpellDistance() < 40 && h.HealthPercent < ShamanSettings.RestoHealSettings.TelluricHealthCast), cancel => HealerManager.Instance.TargetList.Any(h => h.IsAlive && h.SpellDistance() < 40 && h.HealthPercent < ShamanSettings.RestoHealSettings.TelluricHealthCancel) ) ) ) ) ) )); #endif }
public static bool UpdateDiagnosticCastingState(bool retVal = false) { if (SingularSettings.Debug && SingularSettings.DebugSpellCasting) { if (SingularSettings.DebugSpellCasting) { if (_lastIsGCD != Spell.IsGlobalCooldown()) { _lastIsGCD = !_lastIsGCD; Logger.WriteDebug("CastingState: GCD={0} GCDTimeLeft={1}", _lastIsGCD.ToYN(), (int)Spell.GcdTimeLeft.TotalMilliseconds); } if (_lastIsCasting != Spell.IsCasting()) { _lastIsCasting = !_lastIsCasting; Logger.WriteDebug("CastingState: Casting={0} CastTimeLeft={1}", _lastIsCasting.ToYN(), (int)Me.CurrentCastTimeLeft.TotalMilliseconds); } if (_lastIsChanneling != Spell.IsChannelling()) { _lastIsChanneling = !_lastIsChanneling; Logger.WriteDebug("ChannelingState: Channeling={0} ChannelTimeLeft={1}", _lastIsChanneling.ToYN(), (int)Me.CurrentChannelTimeLeft.TotalMilliseconds); } } if (_lastIsInVehicle != Me.InVehicle) { _lastIsInVehicle = !_lastIsInVehicle; Logger.WriteDebug("VehicleState: InVehicle={0} ***", _lastIsInVehicle.ToYN()); } if (_lastIsInCinematic != InCinematic()) { _lastIsInCinematic = !_lastIsInCinematic; Logger.WriteDebug("CinematicState: InCinematic={0} ***", _lastIsInCinematic.ToYN()); } WoWSpell pending = Spell.GetPendingCursorSpell; if (_lastIsSpellPending != (pending != null)) { _lastIsSpellPending = !_lastIsSpellPending; _lastSpellPending = pending.Name; Logger.WriteDebug("PendingState: Pending Target={0}{1}", _lastIsSpellPending.ToYN(), !_lastIsSpellPending ? "" : ", Spell=" + _lastSpellPending); } if (SingularSettings.DebugSpellCasting) { /// Special: provide diagnostics if healer if (HealerManager.NeedHealTargeting && (_nextAbcWarning < DateTime.UtcNow) && !Me.IsCasting && !Me.IsChanneling && !Spell.IsGlobalCooldown(LagTolerance.No)) { WoWUnit low = HealerManager.FindLowestHealthTarget(); if (low != null) { float lh = low.PredictedHealthPercent(); if (!SingularSettings.Instance.HealerCombatAllow && lh < 70) { Logger.WriteDebug("Healer ABC Warning: no cast in progress detected, low health {0} {1:F1}% @ {2:F1} yds", low.SafeName(), lh, low.SpellDistance()); _nextAbcWarning = DateTime.UtcNow + TimeSpan.FromSeconds(1); } } } } } return(retVal); }
public static Composite CreateRestoShamanHealingOnlyBehavior(bool selfOnly = false) { HealerManager.NeedHealTargeting = true; PrioritizedBehaviorList behavs = new PrioritizedBehaviorList(); int cancelHeal = (int)Math.Max(SingularSettings.Instance.IgnoreHealTargetsAboveHealth, Math.Max(ShamanSettings.RestoHealSettings.HealingWave, ShamanSettings.RestoHealSettings.HealingSurge)); bool moveInRange = false; if (!selfOnly) { moveInRange = (SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds); } Logger.WriteDebugInBehaviorCreate("Shaman Healing: will cancel cast of direct heal if health reaches {0:F1}%", cancelHeal); int dispelPriority = (SingularSettings.Instance.DispelDebuffs == RelativePriority.HighPriority) ? 999 : -999; if (SingularSettings.Instance.DispelDebuffs != RelativePriority.None) { behavs.AddBehavior(dispelPriority, "Purify Spirit", null, Dispelling.CreateDispelBehavior()); } #region Save the Group behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.SpiritLinkTotem) + 600, "Spirit Link Totem", "Spirit Link Totem", new Decorator( ret => Me.Combat && (StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid), Spell.CastOnGround("Spirit Link Totem", on => (WoWUnit)on, ret => HealerManager.Instance.TargetList.Count( p => p.PredictedHealthPercent() < ShamanSettings.RestoHealSettings.SpiritLinkTotem && p.Distance <= Totems.GetTotemRange(WoWTotem.SpiritLink)) >= ShamanSettings.RestoHealSettings.MinSpiritLinkCount ) ) ); #endregion #region AoE Heals behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.HealingTideTotem) + 400, "Healing Tide Totem", "Healing Tide Totem", new Decorator( ret => (Me.Combat || ((WoWUnit)ret).Combat) && (StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid) && (!Totems.Exist(WoWTotem.Cloudburst) || (Totems.GetTotem(WoWTotem.Cloudburst).Expires - DateTime.UtcNow).TotalMilliseconds < 1500), Spell.Cast( "Healing Tide Totem", on => Me, req => Me.Combat && HealerManager.Instance.TargetList.Count(p => p.PredictedHealthPercent() < ShamanSettings.RestoHealSettings.HealingTideTotem && p.Distance <= Totems.GetTotemRange(WoWTotem.HealingTide)) >= ShamanSettings.RestoHealSettings.MinHealingTideCount ) ) ); behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.HealingStreamTotem) + 300, "Healing Stream Totem", "Healing Stream Totem", new Decorator( ret => Me.Combat && (StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid), Spell.Cast( "Healing Stream Totem", on => { if (Totems.Exist(WoWTotemType.Water)) { return(null); } if (Spell.IsSpellOnCooldown(Totems.ToSpellId(WoWTotem.HealingStream))) { return(null); } // if tank in group, make sure we are near the tank WoWUnit tank = HealerManager.TankToStayNear; if (tank != null) { if (!HealerManager.IsTankSettledIntoFight(tank)) { return(null); } if (tank.Distance > Totems.GetTotemRange(WoWTotem.HealingStream)) { return(null); } } WoWUnit unit = HealerManager.Instance.TargetList .FirstOrDefault( p => p.PredictedHealthPercent() < ShamanSettings.RestoHealSettings.HealingStreamTotem && p.Distance <= Totems.GetTotemRange(WoWTotem.HealingStream) ); return(unit); } ) ) ); behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.GiftoftheQueen) + 300, "Gift of the Queen", "Gift of the Queen", new Decorator( ret => Me.Combat && (StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid), new PrioritySelector( context => GetBestGiftoftheQueenTarget(), new Decorator( ret => ret != null, Spell.CastOnGround("Gift of the Queen", on => (WoWUnit)on, req => true, false) ) ) ) ); behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.CloudburstTotem) + 300, "Cloudburst Totem", "Cloudburst Totem", new Decorator( ret => Me.Combat && (StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid), Spell.Cast( "Cloudburst Totem", on => { if (Totems.Exist(WoWTotemType.Water)) { return(null); } if (Spell.IsSpellOnCooldown(Totems.ToSpellId(WoWTotem.Cloudburst))) { return(null); } if (Unit.ValidUnit(Me.CurrentTarget) && (Me.CurrentTarget.TimeToDeath() < 20 || Unit.UnitsInCombatWithUsOrOurStuff().Count() < 3)) { return(null); } // if tank in group, make sure we are near the tank WoWUnit tank = HealerManager.TankToStayNear; if (tank != null) { if (!HealerManager.IsTankSettledIntoFight(tank)) { return(null); } if (tank.Distance > Totems.GetTotemRange(WoWTotem.Cloudburst)) { return(null); } } WoWUnit unit = HealerManager.Instance.TargetList .Where( p => p.HealthPercent < ShamanSettings.RestoHealSettings.CloudburstTotem && p.Distance <= Totems.GetTotemRange(WoWTotem.Cloudburst) ) .OrderBy(p => (int)p.HealthPercent) .FirstOrDefault(); return(unit); } ) ) ); behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.HealingRain) + 200, "Healing Rain", "Healing Rain", new Decorator( ret => Me.Combat && (StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid), new PrioritySelector( context => GetBestHealingRainTarget(), new Decorator( ret => ret != null, Spell.CastOnGround("Healing Rain", on => (WoWUnit)on, req => true, false) ) ) ) ); behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.ChainHeal) + 150, "Chain Heal", "Chain Heal", new Decorator( ret => Me.Combat && (StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid), new PrioritySelector( ctx => GetBestChainHealTarget(), new Decorator( ret => ret != null, new Sequence( new DecoratorContinue( req => ((WoWUnit)req).HasAuraExpired("Riptide", TimeSpan.FromMilliseconds(ChainHealCastTime), true), new Sequence( Spell.Cast("Riptide", on => (WoWUnit)on, req => true, cancel => false), new Wait(TimeSpan.FromMilliseconds(1500), until => !Spell.IsGlobalCooldown(LagTolerance.No), new ActionAlwaysSucceed()), new Action(r => TidalWaveRefresh()) ) ), new WaitContinue(TimeSpan.FromMilliseconds(1500), until => !Spell.IsGlobalCooldown(LagTolerance.No), new ActionAlwaysSucceed()), Spell.Cast("Chain Heal", on => (WoWUnit)on), new Action(r => TidalWaveRefresh()) ) ) ) ) ); #endregion #region Single Target Heals behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.HealingWave), "Healing Wave", "Healing Wave", new Decorator(ret => ((WoWUnit)ret).PredictedHealthPercent() < ShamanSettings.RestoHealSettings.HealingWave, new Sequence( new WaitContinue(TimeSpan.FromMilliseconds(1500), until => !Spell.IsGlobalCooldown(), new ActionAlwaysSucceed()), new WaitContinue(2, until => !Spell.IsGlobalCooldown(), new ActionAlwaysSucceed()), Spell.Cast("Healing Wave", mov => true, on => (WoWUnit)on, req => true, cancel => ((WoWUnit)cancel).HealthPercent > cancelHeal), new Action(r => TidalWaveConsume()) ) ) ); behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.HealingSurge), "Healing Surge", "Healing Surge", new Decorator(ret => ((WoWUnit)ret).PredictedHealthPercent() < ShamanSettings.RestoHealSettings.HealingSurge, new Sequence( new WaitContinue(TimeSpan.FromMilliseconds(1500), until => !Spell.IsGlobalCooldown(), new ActionAlwaysSucceed()), new WaitContinue(2, until => !Spell.IsGlobalCooldown(), new ActionAlwaysSucceed()), Spell.Cast("Healing Surge", mov => true, on => (WoWUnit)on, req => true, cancel => ((WoWUnit)cancel).HealthPercent > cancelHeal), new Action(r => TidalWaveConsume()) ) ) ); #endregion #region Healing Cooldowns behavs.AddBehavior(HealerManager.HealthToPriority(ShamanSettings.RestoHealSettings.Ascendance) + 100, "Ascendance", "Ascendance", new Decorator( ret => ShamanSettings.UseAscendance && (StyxWoW.Me.GroupInfo.IsInParty || StyxWoW.Me.GroupInfo.IsInRaid), Spell.BuffSelf( "Ascendance", ret => HealerManager.Instance.TargetList.Count(p => p.PredictedHealthPercent() < ShamanSettings.RestoHealSettings.Ascendance) >= ShamanSettings.RestoHealSettings.MinAscendanceCount ) ) ); #endregion behavs.OrderBehaviors(); if (selfOnly == false && CompositeBuilder.CurrentBehaviorType == BehaviorType.Combat) { behavs.ListBehaviors(); } return(new PrioritySelector( ctx => selfOnly ? StyxWoW.Me : HealerManager.FindLowestHealthTarget(), // HealerManager.Instance.FirstUnit, CreateRestoDiagnosticOutputBehavior(ret => (WoWUnit)ret), new Decorator( ret => ret != null && (Me.Combat || ((WoWUnit)ret).Combat || ((WoWUnit)ret).PredictedHealthPercent() <= 99), new PrioritySelector( new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector( Totems.CreateTotemsBehavior(), // roll Riptide on Tanks otherwise new Sequence( Spell.Cast("Riptide", on => { WoWUnit unit = GetBestRiptideTankTarget(); if (unit != null && Spell.CanCastHack("Riptide", unit, skipWowCheck: true)) { Logger.WriteDebug("Buffing RIPTIDE ON TANK: {0}", unit.SafeName()); return unit; } return null; }), new Action(r => TidalWaveRefresh()) ), // cast Riptide if we are a low level CreateRestoShamanBuffRiptideLowLevel(), // cast Riptide if we need Tidal Waves -- skip if Ancestral Swiftness is CreateRestoShamanBuffTidalWaves(), behavs.GenerateBehaviorTree(), // cast Riptide if we need Tidal Waves -- skip if Ancestral Swiftness is new Decorator( ret => { int rollCount = HealerManager.Instance.TargetList.Count( u => u.IsAlive && u.HasMyAura("Riptide")); // Logger.WriteDebug("GetBestRiptideTarget: currently {0} group members have my Riptide", rollCount); return rollCount < ShamanSettings.RestoHealSettings.RollRiptideCount; }, new Sequence( Spell.Cast("Riptide", on => { // if tank needs Riptide, bail out on Rolling as they have priority if (GetBestRiptideTankTarget() != null) { return null; } // get the best target from all wowunits in our group WoWUnit unit = GetBestRiptideTarget(); if (unit != null) { Logger.WriteDebug(Color.White, "ROLLING RIPTIDE on: {0}", unit.SafeName()); } return unit; }), new Action(r => TidalWaveRefresh()) ) ), new Decorator( ret => moveInRange, new Sequence( new Action(r => _moveToHealUnit = (WoWUnit)r), new PrioritySelector( Movement.CreateMoveToLosBehavior(on => _moveToHealUnit), Movement.CreateMoveToUnitBehavior(on => _moveToHealUnit, 40f, 34f) ) ) ) ) ) ) ) )); }
public static Composite CreateMonkOffHealBehavior() { HealerManager.NeedHealTargeting = SingularSettings.Instance.DpsOffHealAllowed; PrioritizedBehaviorList behavs = new PrioritizedBehaviorList(); int cancelHeal = (int)Math.Max(SingularSettings.Instance.IgnoreHealTargetsAboveHealth, MonkSettings.OffHealSettings.Effuse); bool moveInRange = (SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds); Logger.WriteDebugInBehaviorCreate("Monk Healing: will cancel cast of direct heal if health reaches {0:F1}%", cancelHeal); /* * int dispelPriority = (SingularSettings.Instance.DispelDebuffs == RelativePriority.HighPriority) ? 999 : -999; * if (SingularSettings.Instance.DispelDebuffs != RelativePriority.None) * behavs.AddBehavior(dispelPriority, "Cleanse Spirit", null, Dispelling.CreateDispelBehavior()); */ #region Save the Group #endregion #region AoE Heals behavs.AddBehavior( Mistweaver.HealthToPriority(MonkSettings.MistHealSettings.ChiWave) + 400, String.Format("Chi Wave on {0} targets @ {1}%", MonkSettings.OffHealSettings.CountChiWaveTalent, MonkSettings.OffHealSettings.ChiWaveTalent), "Chi Wave", CreateClusterHeal("Chi Wave", ClusterType.Cone, MonkSettings.OffHealSettings.ChiWaveTalent, MonkSettings.OffHealSettings.CountChiWaveTalent, 40) ); behavs.AddBehavior( Mistweaver.HealthToPriority(MonkSettings.MistHealSettings.ChiBurstTalent) + 400, String.Format("Chi Burst on {0} targets @ {1}%", MonkSettings.OffHealSettings.CountChiBurstTalent, MonkSettings.OffHealSettings.ChiBurstTalent), "Chi Burst", CreateClusterHeal("Chi Burst", ClusterType.Cone, MonkSettings.OffHealSettings.ChiBurstTalent, MonkSettings.OffHealSettings.CountChiBurstTalent, 40) ); #endregion #region Single Target Heals behavs.AddBehavior(Mistweaver.HealthToPriority(MonkSettings.OffHealSettings.Effuse), string.Format("Effuse @ {0}%", MonkSettings.OffHealSettings.Effuse), "Effuse", Spell.Cast("Effuse", mov => true, on => (WoWUnit)on, req => ((WoWUnit)req).PredictedHealthPercent(includeMyHeals: true) < MonkSettings.OffHealSettings.Effuse, cancel => ((WoWUnit)cancel).HealthPercent > cancelHeal ) ); #endregion behavs.OrderBehaviors(); if (Singular.Dynamics.CompositeBuilder.CurrentBehaviorType == BehaviorType.Heal) { behavs.ListBehaviors(); } return(new PrioritySelector( ctx => HealerManager.FindLowestHealthTarget(), // HealerManager.Instance.FirstUnit, new Decorator( ret => ret != null && (Me.Combat || ((WoWUnit)ret).Combat || ((WoWUnit)ret).PredictedHealthPercent() <= 99), new PrioritySelector( new Decorator( ret => !Spell.IsGlobalCooldown(), new PrioritySelector( behavs.GenerateBehaviorTree(), new Decorator( ret => moveInRange, new Sequence( new Action(r => _moveToHealUnit = (WoWUnit)r), new PrioritySelector( Movement.CreateMoveToLosBehavior(on => _moveToHealUnit), Movement.CreateMoveToUnitBehavior(on => _moveToHealUnit, 30f, 25f) ) ) ) ) ) ) ) )); }