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) ) ) ) ) ) ) ) )); }