using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using CommonBehaviors.Actions;
using Singular.Dynamics;
using Singular.Helpers;
using Styx;
using Styx.CommonBot;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;
using Rest = Singular.Helpers.Rest;
using Singular.Settings;
using Singular.Managers;
using Styx.Common.Helpers;
namespace Singular.ClassSpecific.Monk
{
public enum SphereType
{
Chi = 3145, // created by After Life
Life = 3319, // created by After Life
Healing = 2866 // created by Healing Sphere spell
}
public class Common
{
private static MonkSettings MonkSettings { get { return SingularSettings.Instance.Monk(); } }
private static LocalPlayer Me { get { return StyxWoW.Me; } }
public static bool HasTalent(MonkTalents tal) { return TalentManager.IsSelected((int)tal); }
[Behavior(BehaviorType.Initialize, WoWClass.Monk)]
public static Composite CreateMonkInitialize()
{
return null;
}
[Behavior(BehaviorType.LossOfControl, WoWClass.Monk, (WoWSpec)int.MaxValue, WoWContext.Normal | WoWContext.Battlegrounds)]
public static Composite CreateMonkLossOfControlBehavior()
{
return new Decorator(
ret => !Spell.IsGlobalCooldown() && !Spell.IsCastingOrChannelling(),
new PrioritySelector(
Spell.BuffSelf("Nimble Brew", ret => Me.Stunned || Me.Fleeing || Me.HasAuraWithMechanic( WoWSpellMechanic.Horrified )),
Spell.BuffSelf("Dampen Harm", ret => Me.Stunned && Unit.NearbyUnitsInCombatWithMeOrMyStuff.Any()),
Spell.BuffSelf("Tiger's Lust", ret => Me.Rooted && !Me.HasAuraWithEffect( WoWApplyAuraType.ModIncreaseSpeed)),
Spell.BuffSelf("Life Cocoon", req => Me.Stunned && TalentManager.HasGlyph("Life Cocoon") && Unit.NearbyUnitsInCombatWithMeOrMyStuff.Any())
)
);
}
[Behavior(BehaviorType.CombatBuffs, WoWClass.Monk, (WoWSpec)int.MaxValue, WoWContext.All, 2)]
public static Composite CreateMonkCombatBuffs()
{
UnitSelectionDelegate onunitRop;
if (SingularRoutine.CurrentWoWContext == WoWContext.Battlegrounds)
onunitRop = on => Unit.UnfriendlyUnits(8).Any(u => u.CurrentTargetGuid == Me.Guid && u.IsPlayer) ? Me : Group.Healers.FirstOrDefault( h => h.SpellDistance() < 40 && Unit.UnfriendlyUnits((int) h.Distance2D + 8).Any( u => u.CurrentTargetGuid == h.Guid && u.SpellDistance(h) < 8));
else // Instances and Normal - just protect self
onunitRop = on => Unit.UnfriendlyUnits(8).Count(u => u.CurrentTargetGuid == Me.Guid) > 1 ? Me : null;
return new PrioritySelector(
PartyBuff.BuffGroup( "Legacy of the White Tiger", req => Me.Specialization == WoWSpec.MonkBrewmaster || Me.Specialization == WoWSpec.MonkWindwalker),
PartyBuff.BuffGroup("Legacy of the Emperor", req => Me.Specialization == WoWSpec.MonkMistweaver),
new Decorator(
req => !Unit.IsTrivial(Me.CurrentTarget),
new PrioritySelector(
// check our individual buffs here
Spell.Buff("Disable", ret => Me.GotTarget() && Me.CurrentTarget.IsPlayer && Me.CurrentTarget.ToPlayer().IsHostile && !Me.CurrentTarget.HasAuraWithEffect(WoWApplyAuraType.ModDecreaseSpeed)),
Spell.Buff("Ring of Peace", onunitRop)
)
),
CreateChiBurstBehavior()
);
}
[Behavior(BehaviorType.Rest, WoWClass.Monk, WoWSpec.MonkBrewmaster)]
[Behavior(BehaviorType.Rest, WoWClass.Monk, WoWSpec.MonkWindwalker)]
public static Composite CreateMonkRest()
{
return new PrioritySelector(
new Decorator(
ret => !StyxWoW.Me.HasAnyAura("Drink", "Food", "Refreshment"),
new PrioritySelector(
// pickup free heals from Life Spheres
new Decorator(
ret => Me.HealthPercent < 95 && AnySpheres(SphereType.Life, SingularSettings.Instance.SphereDistanceAtRest ),
CreateMoveToSphereBehavior(SphereType.Life, SingularSettings.Instance.SphereDistanceAtRest)
),
// pickup free chi from Chi Spheres
new Decorator(
ret => Me.CurrentChi < Me.MaxChi && AnySpheres(SphereType.Chi, SingularSettings.Instance.SphereDistanceAtRest),
CreateMoveToSphereBehavior(SphereType.Chi, SingularSettings.Instance.SphereDistanceAtRest)
)
)
),
Common.CreateMonkDpsHealBehavior(),
// Rest up! Do this first, so we make sure we're fully rested.
Rest.CreateDefaultRestBehaviour( "Surging Mist", "Resuscitate")
);
}
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))),
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(
3,
Spell.Cast("Tigereye Brew", ctx => Me, ret => Me.HealthPercent < MonkSettings.TigereyeBrewHealth && Me.HasAura("Tigereye Brew", 1))
)
),
// 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
)
);
}
private static WoWUnit _moveToHealUnit = null;
public static Composite CreateMonkOffHealBehavior()
{
HealerManager.NeedHealTargeting = SingularSettings.Instance.DpsOffHealAllowed;
PrioritizedBehaviorList behavs = new PrioritizedBehaviorList();
int cancelHeal = (int)Math.Max(SingularSettings.Instance.IgnoreHealTargetsAboveHealth, MonkSettings.OffHealSettings.SurgingMist);
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.SurgingMist),
string.Format("Surging Mist @ {0}%", MonkSettings.OffHealSettings.SurgingMist),
"Surging Mist",
Spell.Cast("Surging Mist",
mov => true,
on => (WoWUnit)on,
req => ((WoWUnit)req).PredictedHealthPercent(includeMyHeals: true) < MonkSettings.OffHealSettings.SurgingMist,
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)
)
)
)
)
)
)
)
);
}
public static Composite CreateClusterHeal( string spell, ClusterType ct, int health, int range, int minCount)
{
return new Decorator(
req => (req as WoWUnit).HealthPercent < health,
new PrioritySelector(
ctx => Clusters.GetBestUnitForCluster(HealerManager.Instance.TargetList.Where(u => u.HealthPercent <= health), ct, range),
new Decorator(
req =>
{
if (req == null)
return false;
if (!Spell.CanCastHack(spell, (WoWUnit)req))
return false;
if (!((WoWUnit)req).InLineOfSpellSight)
return false;
int count = Clusters.GetClusterCount(
(WoWUnit)req,
HealerManager.Instance.TargetList.Where(u => u.HealthPercent <= health),
ct,
range
);
if (count < minCount)
return false;
Logger.Write( LogColor.Hilite, "^Casting {0} on {1} in attempt to hit {2} members below {3}%", spell, ((WoWUnit)req).SafeName(), count, health);
return true;
},
new Sequence(
new DecoratorContinue(
req => !Me.IsSafelyFacing((WoWUnit)req, 5f),
new PrioritySelector(
new Sequence(
new Action(r => Logger.WriteDiagnostic("{0}: trying to face {1}", spell, ((WoWUnit)r).SafeName())),
new Action(r => ((WoWUnit)r).Face()),
new Wait(TimeSpan.FromMilliseconds(1500), until => Me.IsSafelyFacing((WoWUnit)until, 5f), new ActionAlwaysSucceed()),
new Action(r => Logger.Write("{0}: succeeded at facing {1}", spell, ((WoWUnit)r).SafeName()))
),
new Action(r =>
{
Logger.Write("{0}: failed at facing {1}", spell, ((WoWUnit)r).SafeName());
return RunStatus.Failure;
})
)
),
Spell.Cast(spell, mov => true, on => (WoWUnit)on, req => true, cancel => false)
)
)
)
);
}
///
/// a SpellManager.CanCast replacement to allow checking whether a spell can be cast
/// without checking if another is in progress, since Monks need to cast during
/// a channeled cast already in progress
///
/// name of the spell to cast
/// unit spell is targeted at
///
public static bool CanCastLikeMonk(string name, WoWUnit unit)
{
WoWSpell spell;
if (!SpellManager.Spells.TryGetValue(name, out spell))
{
return false;
}
uint latency = SingularRoutine.Latency * 2;
TimeSpan cooldownLeft = spell.CooldownTimeLeft;
if (cooldownLeft != TimeSpan.Zero && cooldownLeft.TotalMilliseconds >= latency)
return false;
if (spell.IsMeleeSpell)
{
if (!unit.IsWithinMeleeRange)
{
Logger.WriteDebug("CanCastSpell: cannot cast wowSpell {0} @ {1:F1} yds", spell.Name, unit.Distance);
return false;
}
}
else if (spell.IsSelfOnlySpell)
{
;
}
else if (spell.HasRange)
{
if (unit == null)
{
return false;
}
if (unit.Distance < spell.MinRange)
{
Logger.WriteDebug("SpellCast: cannot cast wowSpell {0} @ {1:F1} yds - minimum range is {2:F1}", spell.Name, unit.Distance, spell.MinRange);
return false;
}
if (unit.Distance >= spell.MaxRange)
{
Logger.WriteDebug("SpellCast: cannot cast wowSpell {0} @ {1:F1} yds - maximum range is {2:F1}", spell.Name, unit.Distance, spell.MaxRange);
return false;
}
}
if (Me.IsMoving && spell.CastTime > 0)
{
Logger.WriteDebug("CanCastSpell: wowSpell {0} is not instant ({1} ms cast time) and we are moving", spell.Name, spell.CastTime);
return false;
}
if (!spell.CanCast)
{
Logger.WriteDebug("CanCastSpell: wowSpell {0} cannot be cast according to WoW", spell.Name);
return false;
}
return true;
}
///
/// Creates a behavior to cast a spell by name, with special requirements, on a specific unit. Returns
/// RunStatus.Success if successful, RunStatus.Failure otherwise.
///
///
/// Created 5/2/2011.
///
/// The name.
///
/// The on unit.
/// The requirements.
/// .
public static Composite CastLikeMonk(string name, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements)
{
return new PrioritySelector(
new Decorator(ret => requirements != null && onUnit != null && requirements(ret) && onUnit(ret) != null && name != null && CanCastLikeMonk(name, onUnit(ret)),
new PrioritySelector(
new Sequence(
// cast the spell
new Action(ret =>
{
wasMonkSpellQueued = (Spell.GcdActive || Me.IsCasting || Me.ChanneledSpell != null);
Logger.Write(Color.Aquamarine, string.Format("*{0} on {1} at {2:F1} yds at {3:F1}%", name, onUnit(ret).SafeName(), onUnit(ret).Distance, onUnit(ret).HealthPercent));
Spell.CastPrimative(name, onUnit(ret));
}),
// if spell was in progress before cast (we queued this one) then wait in progress one to finish
new WaitContinue(
new TimeSpan(0, 0, 0, 0, (int) SingularRoutine.Latency << 1),
ret => !wasMonkSpellQueued || !(Spell.GcdActive || Me.IsCasting || Me.ChanneledSpell != null),
new ActionAlwaysSucceed()
),
// wait for this cast to appear on the GCD or Spell Casting indicators
new WaitContinue(
new TimeSpan(0, 0, 0, 0, (int) SingularRoutine.Latency << 1),
ret => Spell.GcdActive || Me.IsCasting || Me.ChanneledSpell != null,
new ActionAlwaysSucceed()
)
)
)
)
);
}
private static bool wasMonkSpellQueued = false;
// delay casting instant ranged abilities if we just cast Roll/FSK
public readonly static WaitTimer RollTimer = new WaitTimer(TimeSpan.FromMilliseconds(1500));
public static Composite CreateMonkCloseDistanceBehavior( SimpleIntDelegate minDist = null, UnitSelectionDelegate onUnit = null, SimpleBooleanDelegate canReq = null)
{
/*
new Decorator(
unit => (unit as WoWUnit).SpellDistance() > 10
&& Me.IsSafelyFacing(unit as WoWUnit, 5f),
Spell.Cast("Roll")
)
*/
bool hasFSKGlpyh = TalentManager.HasGlyph("Flying Serpent Kick");
bool hasTigersLust = HasTalent(MonkTalents.TigersLust);
if (minDist == null)
minDist = min => Me.Combat ? 10 : 12;
if (onUnit == null)
onUnit = on => Me.CurrentTarget;
if (canReq == null)
canReq = req => true;
return new Throttle( 1,
new PrioritySelector(
ctx => onUnit(ctx),
new Decorator(
req => {
if (!MovementManager.IsClassMovementAllowed)
return false;
if (!canReq(req))
return false;
float dist = Me.SpellDistance(req as WoWUnit);
if ( dist <= minDist(req))
return false;
if ((req as WoWUnit).IsAboveTheGround())
return false;
float facingPrecision = (req as WoWUnit).SpellDistance() < 15 ? 6f : 4f;
if (!Me.IsSafelyFacing(req as WoWUnit, facingPrecision))
return false;
bool isObstructed = Movement.MeshTraceline(Me.Location, (req as WoWUnit).Location);
if (isObstructed == true)
return false;
return true;
},
new PrioritySelector(
Spell.BuffSelf(
"Tiger's Lust",
req => hasTigersLust
&& !Me.HasAuraWithEffect(WoWApplyAuraType.ModIncreaseSpeed)
&& Me.HasAuraWithEffect(WoWApplyAuraType.ModRoot, WoWApplyAuraType.ModDecreaseSpeed)
),
new Sequence(
Spell.Cast(
"Flying Serpent Kick",
on => (WoWUnit) on,
ret => TalentManager.CurrentSpec == WoWSpec.MonkWindwalker
&& !Me.Auras.ContainsKey("Flying Serpent Kick")
&& ((ret as WoWUnit).SpellDistance() > 25 || Spell.IsSpellOnCooldown("Roll"))
),
/* wait until in progress */
new PrioritySelector(
new Wait(
TimeSpan.FromMilliseconds(750),
until => Me.Auras.ContainsKey("Flying Serpent Kick"),
new Action( r => Logger.WriteDebug("CloseDistance: Flying Serpent Kick detected towards {0} @ {1:F1} yds in progress", (r as WoWUnit).SafeName(), (r as WoWUnit).SpellDistance()))
),
new Action( r => {
Logger.WriteDebug("CloseDistance: failure - did not see Flying Serpent Kick aura appear - lag?");
return RunStatus.Failure;
})
),
/* cancel when in range */
new Wait(
TimeSpan.FromMilliseconds(2500),
until => {
if (!Me.Auras.ContainsKey("Flying Serpent Kick"))
{
Logger.WriteDebug("CloseDistance: Flying Serpent Kick completed on {0} @ {1:F1} yds and {2} behind me", (until as WoWUnit).SafeName(), (until as WoWUnit).SpellDistance(), (until as WoWUnit).IsBehind(Me) ? "IS" : "is NOT");
return true;
}
if (!hasFSKGlpyh)
{
SpellFindResults sfr;
SpellManager.FindSpell("Flying Serpent Kick", out sfr);
if (((until as WoWUnit).IsWithinMeleeRange || (until as WoWUnit).SpellDistance() < 8f))
{
Logger.Write(LogColor.Cancel, "/cancel Flying Serpent Kick in melee range of {0} @ {1:F1} yds", (until as WoWUnit).SafeName(), (until as WoWUnit).SpellDistance());
//Spell.CastPrimative("Flying Serprent Kick");
// Lua.DoString("CastSpellByID(" + sfr.Original.Id + ")");
return true;
}
else if ((until as WoWUnit).IsBehind(Me))
{
Logger.Write(LogColor.Cancel, "/cancel Flying Serpent Kick flew past {0} @ {1:F1} yds", (until as WoWUnit).SafeName(), (until as WoWUnit).SpellDistance());
//Spell.CastPrimative("Flying Serprent Kick");
//Lua.DoString("CastSpellByID(" + sfr.Original.Id + ")");
return true;
}
}
return false;
},
new PrioritySelector(
new Decorator(
req => !Me.Auras.ContainsKey("Flying Serpent Kick"),
new ActionAlwaysSucceed()
),
new Sequence(
new Action( r => {
if (hasFSKGlpyh)
{
Logger.WriteDebug("CloseDistance: FSK is glyphed, should not be here - notify developer!");
}
else
{
Logger.WriteDebug("CloseDistance: casting Flying Serpent Kick to cancel");
Spell.CastPrimative(101545);
}
}),
/* wait until cancel takes effect */
new PrioritySelector(
new Wait(
TimeSpan.FromMilliseconds(450),
until => !Me.Auras.ContainsKey("Flying Serpent Kick"),
new Action( r => Logger.WriteDebug("CloseDistance: Flying Serpent Kick complete, landed {0:F1} yds from {1}", (r as WoWUnit).SpellDistance(), (r as WoWUnit).SafeName()))
),
new Action( r => {
Logger.WriteDebug("CloseDistance: error - Flying Serpent Kick was not removed - lag?");
})
)
)
)
)
),
Spell.BuffSelf("Tiger's Lust", req => hasTigersLust ),
new Sequence(
Spell.Cast("Roll", on => (WoWUnit)on, req => !MonkSettings.DisableRoll && MovementManager.IsClassMovementAllowed),
new PrioritySelector(
new Wait(
TimeSpan.FromMilliseconds(500),
until => Me.Auras.ContainsKey("Roll"),
new Action(r => Logger.WriteDebug("CloseDistance: Roll in progress"))
),
new Action( r => {
Logger.WriteDebug("CloseDistance: failure - did not detect Roll in progress aura- lag?");
return RunStatus.Failure;
})
),
new Wait(
TimeSpan.FromMilliseconds(950),
until => !Me.Auras.ContainsKey("Roll"),
new Action(r => Logger.WriteDebug("CloseDistance: Roll has ended"))
)
)
)
)
)
);
}
public static WoWObject FindClosestSphere(SphereType typ, float range)
{
range *= range;
return ObjectManager.ObjectList
.Where(o => o.Type == WoWObjectType.AreaTrigger && o.Entry == (uint)typ && o.DistanceSqr < range && !Blacklist.Contains(o.Guid, BlacklistFlags.Combat))
.OrderBy( o => o.DistanceSqr )
.FirstOrDefault();
}
public static bool AnySpheres(SphereType typ, float range)
{
WoWObject sphere = FindClosestSphere(typ, range);
return sphere != null && sphere.Distance < 20;
}
public static WoWPoint FindSphereLocation(SphereType typ, float range)
{
WoWObject sphere = FindClosestSphere(typ, range);
return sphere != null ? sphere.Location : WoWPoint.Empty;
}
private static WoWGuid guidSphere = WoWGuid.Empty;
private static WoWPoint locSphere = WoWPoint.Empty;
private static DateTime timeAbortSphere = DateTime.Now;
public static Composite CreateMoveToSphereBehavior(SphereType typ, float range)
{
return new Decorator(
ret => SingularSettings.Instance.MoveToSpheres && !MovementManager.IsMovementDisabled,
new PrioritySelector(
// check we haven't gotten out of range due to fall / pushback / port / etc
new Decorator(
ret => guidSphere.IsValid&& Me.Location.Distance(locSphere) > range,
new Action(ret => { guidSphere = WoWGuid.Empty; locSphere = WoWPoint.Empty; })
),
// validate the sphere we are moving to
new Action(ret =>
{
WoWObject sph = FindClosestSphere(typ, range);
if (sph == null)
{
guidSphere = WoWGuid.Empty; locSphere = WoWPoint.Empty;
return RunStatus.Failure;
}
if (sph.Guid == guidSphere)
return RunStatus.Failure;
guidSphere = sph.Guid;
locSphere = sph.Location;
timeAbortSphere = DateTime.Now + TimeSpan.FromSeconds(5);
Logger.WriteDebug("MoveToSphere: Moving {0:F1} yds to {1} Sphere {2} @ {3}", sph.Distance, typ, guidSphere, locSphere);
return RunStatus.Failure;
}),
new Decorator(
ret => DateTime.Now > timeAbortSphere,
new Action( ret => {
Logger.WriteDebug("MoveToSphere: blacklisting timed out {0} sphere {1} at {2}", typ, guidSphere, locSphere);
Blacklist.Add(guidSphere, BlacklistFlags.Combat, TimeSpan.FromMinutes(5));
})
),
// move to the sphere if out of range
new Decorator(
ret => guidSphere.IsValid && Me.Location.Distance(locSphere) > 1,
Movement.CreateMoveToLocationBehavior(ret => locSphere, true, ret => 0f)
),
// pause briefly until its consumed
new Wait(
1,
ret => {
WoWObject sph = FindClosestSphere(typ, range);
return sph == null || sph.Guid != guidSphere ;
},
new Action( r => { return RunStatus.Failure; } )
),
// still exist? black list it then
new Decorator(
ret => {
WoWObject sph = FindClosestSphere(typ, range);
return sph != null && sph.Guid == guidSphere ;
},
new Action( ret => {
Logger.WriteDebug("MoveToSphere: blacklisting unconsumed {0} sphere {1} at {2}", typ, guidSphere, locSphere);
Blacklist.Add(guidSphere, BlacklistFlags.Combat, TimeSpan.FromMinutes(5));
})
)
)
);
}
/*
public static Sequence CreateHealingSphereBehavior( int sphereBelowHealth)
{
// healing sphere keeps spell on cursor for up to 3 casts... need to stop targeting after 1
return new Sequence(
Spell.CastOnGround("Healing Sphere",
on => Me,
ret => Me.HealthPercent < sphereBelowHealth
&& (Me.PowerType != WoWPowerType.Mana)
&& !Common.AnySpheres(SphereType.Healing, 1f),
false),
new WaitContinue( TimeSpan.FromMilliseconds(500), ret => Spell.GetPendingCursorSpell != null, new ActionAlwaysSucceed()),
new Action(ret => Lua.DoString("SpellStopTargeting()")),
new WaitContinue(
TimeSpan.FromMilliseconds(750),
ret => Me.Combat || (Spell.GetSpellCooldown("Healing Sphere") == TimeSpan.Zero && !Common.AnySpheres(SphereType.Healing, 1f)),
new ActionAlwaysSucceed()
)
);
}
*/
public static Composite CreateChiBurstBehavior()
{
if ( !HasTalent(MonkTalents.ChiBurst))
return new ActionAlwaysFail();
if (TalentManager.CurrentSpec == WoWSpec.MonkMistweaver && SingularRoutine.CurrentWoWContext != WoWContext.Normal)
{
return new Decorator(
req => !Spell.IsSpellOnCooldown("Chi Burst") && !Me.CurrentTarget.IsBoss()
&& 3 <= Clusters.GetPathToPointCluster(Me.Location.RayCast(Me.RenderFacing, 40f), HealerManager.Instance.TargetList.Where(m => Me.IsSafelyFacing(m, 25)), 5f).Count(),
Spell.Cast("Chi Burst",
mov => true,
ctx => Me,
ret => Me.HealthPercent < MonkSettings.ChiWavePct,
cancel => false
)
);
}
return new Decorator(
req => !Spell.IsSpellOnCooldown("Chi Burst") && !Me.CurrentTarget.IsBoss()
&& 3 <= Clusters.GetPathToPointCluster( Me.Location.RayCast(Me.RenderFacing, 40f), Unit.NearbyUnfriendlyUnits.Where( m => Me.IsSafelyFacing(m,25)), 5f).Count(),
Spell.Cast("Chi Burst",
mov => true,
ctx => Me,
ret => Me.HealthPercent < MonkSettings.ChiWavePct,
cancel => false
)
);
}
///
/// selects best target, favoring healing multiple group members followed by damaging multiple targets
///
///
private static WoWUnit BestChiBurstTarget()
{
WoWUnit target = null;
if (Me.IsInGroup())
target = Clusters.GetBestUnitForCluster(
Unit.NearbyGroupMembers.Where(m => m.IsAlive && m.HealthPercent < 80),
ClusterType.PathToUnit,
40f);
if (target == null || target.IsMe)
target = Clusters.GetBestUnitForCluster(
Unit.NearbyUnitsInCombatWithMeOrMyStuff,
ClusterType.PathToUnit,
40f);
if (target == null)
target = Me;
return target;
}
public static WoWUnit BestExpelHarmTarget()
{
if (HealerManager.NeedHealTargeting)
return HealerManager.Instance.TargetList.FirstOrDefault( t => t.SpellDistance() < 40 && t.InLineOfSpellSight) ?? Me;
return Unit.NearbyGroupMembers.FirstOrDefault(t => t.SpellDistance() < 40 && t.InLineOfSpellSight) ?? Me;
}
public static Composite CastTouchOfDeath()
{
return Spell.Cast("Touch of Death", ret => Me.HasAura("Death Note") || Me.CurrentTarget.HealthPercent < 10);
}
}
public enum MonkTalents
{
#if PRE_WOD
Celerity = 1,
TigersLust,
Momumentum,
ChiWave,
ZenSphere,
ChiBurst,
PowerStrikes,
Ascension,
ChiBrew,
RingOfPeace,
ChargingOxWave,
LegSweep,
HealingElixirs,
DampenHarm,
DiffuseMagic,
RushingJadeWind,
InvokeXuenTheWhiteTiger,
ChiTorpedo
#else
Celerity = 1,
TigersLust,
Momentum,
ChiWave,
ZenSphere,
ChiBurst,
PowerStrikes,
Ascension,
ChiBrew,
RingOfPeace,
ChargingOxWave,
LegSweep,
HealingElixirs,
DampenHarm,
DiffuseMagic,
RushingJadeWind,
InvokeXuenTheWhiteTiger,
ChiTorpedo,
SoulDance,
BreathOfTheSerpent = SoulDance,
HurricaneStrike = SoulDance,
ChiExplosion,
Serenity,
PoolOfMists = Serenity
#endif
}
}