Esempio n. 1
0
        public WoWPoint GetSidePoint(float degrees, float distance = 5f)
        {
            float facing = WoWMathHelper.DegreesToRadians(degrees);

            facing = WoWMathHelper.NormalizeRadian(facing);
            return(Location.RayCast(facing, distance));
        }
Esempio n. 2
0
        private bool IsWithinArticulationLimits(WoWUnit vehicle, WoWUnit potentialTarget)
        {
            if ((vehicle == null) || !vehicle.IsValid)
            {
                return(false);
            }

            if ((potentialTarget == null) || !potentialTarget.IsValid)
            {
                return(false);
            }

            double neededAzimuth = Math.Atan((potentialTarget.Location.Z - vehicle.Location.Z)
                                             / vehicle.Location.Distance2D(potentialTarget.Location));
            double neededFacing = WoWMathHelper.CalculateNeededFacing(vehicle.Location, potentialTarget.Location);

            neededFacing = WoWMathHelper.NormalizeRadian((float)neededFacing);

            if ((neededFacing < CannonArticulation_HeadingMin) || (neededFacing > CannonArticulation_HeadingMax))
            {
                return(false);
            }

            if ((neededAzimuth < CannonArticulation_AzimuthMin) || (neededAzimuth > CannonArticulation_AzimuthMax))
            {
                return(false);
            }

            return(true);
        }
Esempio n. 3
0
        public WoWPoint GetFrontPoint(float distance = 5f)
        {
            float facing = RefWoWObject.Rotation;

            facing = WoWMathHelper.NormalizeRadian(facing);
            return(Location.RayCast(facing, distance));
        }
 // The HB API always returns 0 for our facing when we're in a vehicle.
 // To get the actualy value, we must ask the WoWclient directly.
 private float GetVehicleFacing()
 {
     return
         (Query.IsInVehicle()
         ? WoWMathHelper.NormalizeRadian(Lua.GetReturnVal <float>("return GetPlayerFacing();", 0))
         : Me.RenderFacing);
 }
Esempio n. 5
0
        public WoWPoint GetBehindPoint(float distance = 5f)
        {
            float facing = RefWoWObject.Rotation;

            facing += WoWMathHelper.DegreesToRadians(180);
            facing  = WoWMathHelper.NormalizeRadian(facing);
            return(Location.RayCast(facing, distance));
        }
Esempio n. 6
0
        public static WoWPoint CalculatePointBehindTarget()
        {
            float facing = StyxWoW.Me.CurrentTarget.Rotation;

            facing += WoWMathHelper.DegreesToRadians(180); // was 150 ?
            facing  = WoWMathHelper.NormalizeRadian(facing);

            return(StyxWoW.Me.CurrentTarget.Location.RayCast(facing, Spell.MeleeRange - 2f));
        }
        private async Task <bool> UtilityCoroutine_MoveAndUseCatapult()
        {
            if (!Query.IsInVehicle() || !IsViable(SelectedCatapult))
            {
                return(false);
            }

            // Move vehicle into position...
            if (!Navigator.AtLocation(CurrentTask.PositionForLaunch))
            {
                Navigator.MoveTo(CurrentTask.PositionForLaunch);
                return(true);
            }

            // Adjust heading...
            if (!WoWMathHelper.IsFacing(WoWMovement.ActiveMover.Location, GetVehicleFacing(),
                                        CurrentTask.PositionToLand, WoWMathHelper.DegreesToRadians(0.5f)))
            {
                // Handle heading...
                double neededHeading = WoWMathHelper.CalculateNeededFacing(Me.Location, CurrentTask.PositionToLand);
                neededHeading = WoWMathHelper.NormalizeRadian((float)neededHeading);
                QBCLog.Info("Adjusting firing heading");
                Me.SetFacing((float)neededHeading);
                await Coroutine.Sleep(200);

                return(true);
            }

            // Adjust azimuth...
            double currentAzimuth = WoWMathHelper.NormalizeRadian(Lua.GetReturnVal <float>("return VehicleAimGetAngle();", 0));
            double neededAzimuth  = NormalizeAngleToPi(CurrentTask.NeededAzimuth);

            double azimuthChangeRequired = neededAzimuth - currentAzimuth;

            if (Math.Abs(azimuthChangeRequired) >= 0.01)
            {
                QBCLog.Info("Adjusting firing azimuth");
                // NB: VehicleAimIncrement() handles negative values of 'increment' correctly...
                Lua.DoString("VehicleAimIncrement({0})", azimuthChangeRequired);
                await Coroutine.Sleep(200);

                return(true);
            }

            // Fire..
            QBCLog.Info("Firing Catapult");
            Lua.DoString(Lua_LaunchCommand);

            await Coroutine.Wait(3000, () => !Query.IsInVehicle());

            return(true);
        }
Esempio n. 8
0
        public static bool IsFacing2D(WoWPoint me, float myFacingRadians, WoWPoint target, float arcRadians)
        {
            me.Z       = target.Z = 0;
            arcRadians = WoWMathHelper.NormalizeRadian(arcRadians);
            float num  = WoWMathHelper.CalculateNeededFacing(me, target);
            float num2 = WoWMathHelper.NormalizeRadian(num - myFacingRadians);

            if (num2 > 3.1415926535897931)
            {
                num2 = (float)(6.2831853071795862 - num2);
            }
            bool result = (num2 <= arcRadians / 2f);

            return(result);
        }
Esempio n. 9
0
        //if pool is between CurrentPoint and NextPoint then cycle to nextPoint
        public void CycleToNextIfBehind(WoWGameObject pool)
        {
            WoWPoint cp    = CurrentPoint;
            WoWPoint point = GetNextWayPoint();

            point = new WoWPoint(point.X - cp.X, point.Y - cp.Y, 0);
            point.Normalize();
            float angle = WoWMathHelper.NormalizeRadian((float)Math.Atan2(point.Y, point.X - 1));

            if (WoWMathHelper.IsFacing(CurrentPoint, angle, pool.Location) &&
                CurrentPoint != WayPoints[WayPoints.Count - 1])
            {
                CycleToNextPoint();
            }
        }
Esempio n. 10
0
            internal EncounterInfo Update()
            {
                var hostileForces = (from unit in ObjectManager.GetObjectsOfTypeFast <WoWUnit>()
                                     where unit.FactionId == 2334 && unit.IsAlive
                                     let loc = unit.Location
                                               orderby loc.DistanceSqr(_encounterLocaction)
                                               // project WoWUnit.Location to minimize the number of injections.
                                               select new { Location = loc, Unit = unit }).ToList();

                var friendlyForces = (from unit in ObjectManager.GetObjectsOfTypeFast <WoWUnit>()
                                      where unit.FactionId == 2333 && unit.IsAlive
                                      let loc = unit.Location
                                                orderby loc.DistanceSqr(_encounterLocaction)
                                                select new { Location = loc, Unit = unit }).ToList();

                var bestVolleyTarget =
                    hostileForces.OrderByDescending(
                        u => hostileForces.Count(v => v != u && v.Location.DistanceSqr(u.Location) < 10 * 10)).FirstOrDefault();

                VolleyPosition = bestVolleyTarget != null
                    ? bestVolleyTarget.Location.RayCast(
                    WoWMathHelper.NormalizeRadian(bestVolleyTarget.Unit.Rotation),
                    bestVolleyTarget.Location.Distance(_encounterLocaction) *
                    bestVolleyTarget.Unit.MovementInfo.CurrentSpeed * 0.04f)
                    : WoWPoint.Empty;

                var nearbyHostileUnit =
                    hostileForces.FirstOrDefault(u => u.Location.DistanceSqr(_encounterLocaction) <= 30 * 30);

                ChampionRallyPosition = nearbyHostileUnit != null &&
                                        !friendlyForces.Any(
                    u =>
                    u.Unit.Entry == RamkahenChampionId &&
                    u.Location.DistanceSqr(nearbyHostileUnit.Location) < 12 * 12)
                    ? nearbyHostileUnit.Location
                    : WoWPoint.Empty;

                var radianceTargetUnit =
                    friendlyForces.Where(u => u.Unit.HealthPercent < 70).OrderByDescending(u => friendlyForces.Count(v => v.Unit.HealthPercent < 70))
                    .FirstOrDefault() ?? nearbyHostileUnit;

                RadianceTarget = radianceTargetUnit != null ? radianceTargetUnit.Location : WoWPoint.Empty;
                return(this);
            }
        private Vector3 FindNewStationPoint(WoWUnit desiredTarget, double offsetRadians = 0.0)
        {
            if (!Query.IsViable(desiredTarget))
            {
                desiredTarget = FindMobToKill(MobId_ScarletBallista);
            }

            var myLocation        = WoWMovement.ActiveMover.Location;
            var preferredDistance = StyxWoW.Random.Next((int)TargetDistance2DMin + 1, (int)TargetDistance2DMax);
            var targetLocation    = desiredTarget.Location;

            // To evade, we just rotate in a (counter-clockwise) circle around our selected target...
            var escapeHeading = WoWMathHelper.CalculateNeededFacing(targetLocation, myLocation);

            escapeHeading = WoWMathHelper.NormalizeRadian((float)(escapeHeading + offsetRadians + (TAU / 14)));

            var escapePoint = targetLocation.RayCast(escapeHeading, (float)preferredDistance);

            return(new Vector3(escapePoint.X, escapePoint.Y, targetLocation.Z).Add(0.0, 0.0, TargetHeightMinimum + TargetHeightVariance));
        }
Esempio n. 12
0
        // NB: In WoW, larger headings are to left, and larger Azimuths are up
        private void AimAndFireCannon(WoWUnit vehicle, WoWUnit target)
        {
            // Handle heading...
            double   traveltime      = target.Distance / (NurongsCannonShot_FeetPerSecond * 3.0f /*feet to yards*/);
            WoWPoint targetLeadPoint = target.Location.RayCast(target.RenderFacing, (float)(target.MovementInfo.CurrentSpeed * traveltime));
            float    neededHeading   = WoWMathHelper.CalculateNeededFacing(Me.Location, targetLeadPoint);

            neededHeading = WoWMathHelper.NormalizeRadian(neededHeading);
            Me.SetFacing(neededHeading);

            // Handle Azimuth...
            // "Location" is measured at the feet of the toon.  We want to aim for the 'middle' of the toon's
            // height.
            double currentAzimuth = NormalizeAngleToPi(Lua.GetReturnVal <double>("return VehicleAimGetAngle();", 0));
            double neededAzimuth  = Math.Atan((target.Location.Z - vehicle.Location.Z
                                               + (GetBoundingHeight(target) / 2))    // Middle of toon height
                                              / vehicle.Location.Distance2D(target.Location));

            neededAzimuth = NormalizeAngleToPi(neededAzimuth);

            // Execute fire...
            // NB: VehicleAimIncrement() handles negative values of 'increment' correctly...
            Lua.DoString("VehicleAimIncrement({0}); {1}", (neededAzimuth - currentAzimuth), NurongsCannonShot_LuaCommand);
        }
Esempio n. 13
0
        private Composite CreateCombatBehavior()
        {
            // NB: We'll be running right over some hostiles while in the barrel.
            // Even with a PullDistance set to one, HBcore and the CombatRoutine are going to try to pull these mobs.
            // Thus, this behavior runs at "higher than combat" priority, and prevents the CombatRoutine from executing
            // while this behavior is in progress.
            //
            // NB: We need to allow lower BT nodes to run when the behavior is finished; otherwise, HB will not
            // process the change of _isBehaviorDone state.
            return(new Decorator(context => !_isBehaviorDone,
                                 new PrioritySelector(context => _combatContext.Update(),

                                                      // If quest is done, behavior is done...
                                                      new Decorator(context => !UtilIsProgressRequirementsMet(QuestId, QuestRequirementInLog, QuestRequirementComplete),
                                                                    new Action(context =>
            {
                _isBehaviorDone = true;
                QBCLog.Info("Finished");
            })),

                                                      // If not in Keg Bomb Vehicle, move to it and get inside...
                                                      new Decorator(context => !Query.IsInVehicle() && (_combatContext.KegBomb != null),
                                                                    new PrioritySelector(
                                                                        new Decorator(context => _combatContext.KegBomb.Distance > _combatContext.KegBomb.InteractRange,
                                                                                      new Action(context => { Navigator.MoveTo(_combatContext.KegBomb.Location); })),
                                                                        new Decorator(context => Me.IsMoving,
                                                                                      new Action(context => { WoWMovement.MoveStop(); })),
                                                                        new Decorator(context => !Me.IsSafelyFacing(_combatContext.KegBomb),
                                                                                      new Action(context => { _combatContext.KegBomb.Face(); })),
                                                                        new Sequence(
                                                                            // N.B. we need to wait a bit before jumping in vehicle after a round -
                                                                            // due to a bug that prevents bot from leaving vehicle until a relog
                                                                            new WaitContinue(2, context => false, new ActionAlwaysSucceed()),
                                                                            new Action(context =>
            {
                _combatContext.ReInitialize();
                _combatContext.KegBomb.Interact();
                QBCLog.Info("Started barrel roll #{0}", ++_barrelRollCount);
                return RunStatus.Failure;
            })),
                                                                        new Wait(TimeSpan.FromMilliseconds(1000), context => false, new ActionAlwaysSucceed())
                                                                        )),

                                                      // If we are in the vehicle...
                                                      new Decorator(context => Query.IsInVehicle() && (_combatContext.KegBombVehicle != null),
                                                                    new PrioritySelector(
                                                                        // If we've been in the barrel too long, just blow it up...
                                                                        // Blacklist whatever target we were after, and try again
                                                                        new Decorator(context => (_combatContext.BarrelRollingTimer.ElapsedMilliseconds > BarrelRollingTimeBeforeRetrying) &&
                                                                                      !_combatContext.IsKegIgnited,
                                                                                      new Action(context =>
            {
                QBCLog.Warning("We've been in the barrel too long--we're blowing it up to try again");
                IgniteKeg(_combatContext);
                if (_combatContext.SelectedTarget != null)
                {
                    Blacklist.Add(_combatContext.SelectedTarget, BlacklistFlags.Combat, TimeSpan.FromMinutes(3));
                }
            })),

                                                                        // Select target, if not present...
                                                                        new Decorator(context => _combatContext.SelectedTarget == null,
                                                                                      new Action(context => ChooseTarget(_combatContext))),

                                                                        // If we have a target, guide barrel to target...
                                                                        new Decorator(context => _combatContext.SelectedTarget != null,
                                                                                      new Action(context =>
            {
                float neededFacing = WoWMathHelper.CalculateNeededFacing(_combatContext.KegBombVehicle.Location,
                                                                         _combatContext.SelectedTarget.Location);
                neededFacing = WoWMathHelper.NormalizeRadian(neededFacing);

                float neededRotation = Math.Abs(neededFacing - _combatContext.KegBombVehicle.RenderFacing);
                neededRotation = WoWMathHelper.NormalizeRadian(neededRotation);

                // If we need to rotate heading 'too hard' to hit the target, then we missed the target...
                // Blow up the current barrel, blacklist the target, and try again
                if (((neededRotation > (Math.PI / 2)) && (neededRotation < ((Math.PI * 2) - (Math.PI / 2)))) &&
                    !_combatContext.IsKegIgnited)
                {
                    QBCLog.Warning("We passed the selected target--igniting barrel to try again.");
                    IgniteKeg(_combatContext);
                    Blacklist.Add(_combatContext.SelectedTarget, BlacklistFlags.Combat, TimeSpan.FromMinutes(3));
                }

                // Ignite the keg at the appropriate time...
                if (_combatContext.SelectedTarget.Distance <= IgniteDistance)
                {
                    IgniteKeg(_combatContext);
                }

                // Guide the keg to target...
                Me.SetFacing(neededFacing);
                return RunStatus.Success;
            }))
                                                                        ))
                                                      )));
        }
Esempio n. 14
0
        public Vector3 FindLocation(Vector3 ptOrigin)
        {
            DateTime startFind              = DateTime.UtcNow;
            int      countPointsChecked     = 0;
            int      countFailDiff          = 0;
            int      countFailTrace         = 0;
            int      countFailToPointNav    = 0;
            int      countFailRange         = 0;
            int      countFailSafe          = 0;
            int      countFailToPointLoS    = 0;
            int      countFailToMobLoS      = 0;
            TimeSpan spanTrace              = TimeSpan.Zero;
            TimeSpan spanNav                = TimeSpan.Zero;
            double   furthestNearMobDistSqr = 0f;
            Vector3  ptFurthest             = Vector3.Zero;
            float    facingFurthest         = 0f;

            bool    reallyCheckRangeToLineOfSightMob = CheckRangeToLineOfSightMob && Me.GotTarget;
            Vector3 ptAdjOrigin = ptOrigin;
            // ptAdjOrigin.Z += 1f;   // comment out origin adjustment since using GetTraceLinePos()

            Vector3        ptDestination = new Vector3();
            List <Vector3> mobLocations  = new List <Vector3>();
            float          arcIncrement  = ((float)Math.PI * 2) / RaysToCheck;

            mobLocations = AllEnemyMobLocationsToCheck;
            double minSafeDistSqr = MinSafeDistance * MinSafeDistance;

            float baseDestinationFacing;

            if (PreferredDirection == Direction.None && MobToRunFrom != null)
            {
                baseDestinationFacing = Styx.Helpers.WoWMathHelper.CalculateNeededFacing(MobToRunFrom.Location, Me.Location);
            }
            else if (PreferredDirection == Direction.Frontwards)
            {
                baseDestinationFacing = Me.RenderFacing;
            }
            else // if (PreferredDirection == Disengage.Direction.Backwards)
            {
                baseDestinationFacing = Me.RenderFacing + (float)Math.PI;
            }

            L.debugLog("SafeArea: facing {0:F0} degrees, looking for safespot towards {1:F0} degrees", C.DefensiveColor,
                       WoWMathHelper.RadiansToDegrees(Me.RenderFacing),
                       WoWMathHelper.RadiansToDegrees(baseDestinationFacing)
                       );

            for (int arcIndex = 0; arcIndex < RaysToCheck; arcIndex++)
            {
                // rather than tracing around the circle, toggle between clockwise and counter clockwise for each test
                // .. so we favor a position furthest away from mob
                float checkFacing = baseDestinationFacing;
                if ((arcIndex & 1) == 0)
                {
                    checkFacing += arcIncrement * (arcIndex >> 1);
                }
                else
                {
                    checkFacing -= arcIncrement * ((arcIndex >> 1) + 1);
                }

                checkFacing = WoWMathHelper.NormalizeRadian(checkFacing);
                for (float distFromOrigin = MinScanDistance; distFromOrigin <= MaxScanDistance; distFromOrigin += IncrementScanDistance)
                {
                    countPointsChecked++;

                    ptDestination = ptOrigin.RayCast(checkFacing, distFromOrigin);

                    L.debugLog("SafeArea: checking {0:F1} degrees at {1:F1} yds", WoWMathHelper.RadiansToDegrees(checkFacing), distFromOrigin);

                    DateTime start     = DateTime.UtcNow;
                    bool     failTrace = MeshTraceline(Me.Location, ptDestination);
                    spanTrace += DateTime.UtcNow - start;

                    bool failNav;
                    if (DirectPathOnly)
                    {
                        failNav = failTrace;
                        spanNav = spanTrace;
                    }
                    else
                    {
                        start   = DateTime.UtcNow;
                        failNav = false;
                        //failNav = !Navigator.CanNavigateFully(Me.Location, ptDestination);
                        spanNav += DateTime.UtcNow - start;
                    }

                    if (failTrace)
                    {
                        countFailTrace++;
                    }

                    if (failTrace != failNav)
                    {
                        countFailDiff++;
                    }

                    if (failNav)
                    {
                        // L.debugLog( Color.Cyan, "Safe Location failed navigation check for degrees={0:F1} dist={1:F1}", RadiansToDegrees(checkFacing), distFromOrigin);
                        countFailToPointNav++;
                        continue;
                    }

                    Vector3 ptNearest = NearestMobLoc(ptDestination, mobLocations);
                    if (ptNearest == Vector3.Zero)
                    {
                        if (furthestNearMobDistSqr < minSafeDistSqr)
                        {
                            furthestNearMobDistSqr = minSafeDistSqr;
                            ptFurthest             = ptDestination; // set best available if others fail
                            facingFurthest         = checkFacing;
                        }
                    }
                    else
                    {
                        double mobDistSqr = ptDestination.Distance2DSquared(ptNearest);
                        if (furthestNearMobDistSqr < mobDistSqr)
                        {
                            furthestNearMobDistSqr = mobDistSqr;
                            ptFurthest             = ptDestination; // set best available if others fail
                            facingFurthest         = checkFacing;
                        }
                        if (mobDistSqr <= minSafeDistSqr)
                        {
                            countFailSafe++;
                            continue;
                        }
                    }

                    if (reallyCheckRangeToLineOfSightMob && RangeToLineOfSightMob < ptDestination.Distance(LineOfSightMob.Location) - LineOfSightMob.MeleeDistance())
                    {
                        countFailRange++;
                        continue;
                    }

                    if (CheckLineOfSightToSafeLocation)
                    {
                        Vector3 ptAdjDest = ptDestination;
                        ptAdjDest.Z += 1f;
                        if (!Styx.WoWInternals.World.GameWorld.IsInLineOfSight(ptAdjOrigin, ptAdjDest))
                        {
                            // L.debugLog( Color.Cyan, "Mob-free location failed line of sight check for degrees={0:F1} dist={1:F1}", degreesFrom, distFromOrigin);
                            countFailToPointLoS++;
                            continue;
                        }
                    }

                    if (CheckSpellLineOfSightToMob && LineOfSightMob != null)
                    {
                        if (!Styx.WoWInternals.World.GameWorld.IsInLineOfSpellSight(ptDestination, LineOfSightMob.GetTraceLinePos()))
                        {
                            if (!Styx.WoWInternals.World.GameWorld.IsInLineOfSight(ptDestination, LineOfSightMob.GetTraceLinePos()))
                            {
                                // L.debugLog( Color.Cyan, "Mob-free location failed line of sight check for degrees={0:F1} dist={1:F1}", degreesFrom, distFromOrigin);
                                countFailToMobLoS++;
                                continue;
                            }
                        }
                    }

                    L.debugLog("SafeArea: Found mob-free location ({0:F1} yd radius) at degrees={1:F1} dist={2:F1} on point check# {3} at {4}, {5}, {6}", MinSafeDistance, WoWMathHelper.RadiansToDegrees(checkFacing), distFromOrigin, countPointsChecked, ptDestination.X, ptDestination.Y, ptDestination.Z);
                    L.debugLog("SafeArea: processing took {0:F0} ms", (DateTime.UtcNow - startFind).TotalMilliseconds);
                    L.debugLog("SafeArea: meshtrace took {0:F0} ms / fullynav took {1:F0} ms", spanTrace.TotalMilliseconds, spanNav.TotalMilliseconds);
                    L.debugLog("SafeArea: stats for ({0:F1} yd radius) found within {1:F1} yds ({2} checked, {3} nav, {4} not safe, {5} range, {6} pt los, {7} mob los, {8} mesh trace)", MinSafeDistance, MaxScanDistance, countPointsChecked, countFailToPointNav, countFailSafe, countFailRange, countFailToPointLoS, countFailToMobLoS, countFailTrace);
                    return(ptDestination);
                }
            }

            L.debugLog("SafeArea: No mob-free location ({0:F1} yd radius) found within {1:F1} yds ({2} checked, {3} nav, {4} not safe, {5} range, {6} pt los, {7} mob los, {8} mesh trace)", MinSafeDistance, MaxScanDistance, countPointsChecked, countFailToPointNav, countFailSafe, countFailRange, countFailToPointLoS, countFailToMobLoS, countFailTrace);
            if (ChooseSafestAvailable && ptFurthest != Vector3.Zero)
            {
                L.debugLog("SafeArea: choosing best available spot in {0:F1} yd radius where closest mob is {1:F1} yds", MinSafeDistance, Math.Sqrt(furthestNearMobDistSqr));
                L.debugLog("SafeArea: processing took {0:F0} ms", (DateTime.UtcNow - startFind).TotalMilliseconds);
                L.debugLog("SafeArea: meshtrace took {0:F0} ms / fullynav took {1:F0} ms", spanTrace.TotalMilliseconds, spanNav.TotalMilliseconds);
                return(ChooseSafestAvailable ? ptFurthest : Vector3.Zero);
            }

            L.debugLog("SafeArea: processing took {0:F0} ms", (DateTime.UtcNow - startFind).TotalMilliseconds);
            L.debugLog("SafeArea: meshtrace took {0:F0} ms / fullynav took {1:F0} ms", spanTrace.TotalMilliseconds, spanNav.TotalMilliseconds);
            return(Vector3.Zero);
        }
Esempio n. 15
0
        public static Composite CreateDisengageBehavior()
        {
            return
                (new Decorator(
                     ret => IsDisengageNeeded(),
                     new Sequence(
                         new ActionDebugString(ret => "face away from or towards safespot as needed"),
                         new Action(delegate
            {
                if (useRocketJump)
                {
                    needFacing = Styx.Helpers.WoWMathHelper.CalculateNeededFacing(Me.Location, safeSpot);
                }
                else
                {
                    needFacing = Styx.Helpers.WoWMathHelper.CalculateNeededFacing(safeSpot, Me.Location);
                }

                needFacing = WoWMathHelper.NormalizeRadian(needFacing);
                float rotation = WoWMathHelper.NormalizeRadian(Math.Abs(needFacing - Me.RenderFacing));
                Logger.Write(Color.Cyan, "DIS: turning {0:F0} degrees {1} safe landing spot",
                             WoWMathHelper.RadiansToDegrees(rotation), useRocketJump ? "towards" : "away from");
                Me.SetFacing(needFacing);
            }),

                         new ActionDebugString(ret => "wait for facing to complete"),
                         new PrioritySelector(
                             new Wait(new TimeSpan(0, 0, 1), ret => Me.IsDirectlyFacing(needFacing), new ActionAlwaysSucceed()),
                             new Action(ret =>
            {
                Logger.Write(Color.Cyan, "DIS: timed out waiting to face safe spot - need:{0:F4} have:{1:F4}", needFacing, Me.RenderFacing);
                return RunStatus.Failure;
            })
                             ),

                         // stop facing
                         new Action(ret =>
            {
                Logger.Write(Color.Cyan, "DIS: cancel facing now we point the right way");
                WoWMovement.StopFace();
            }),

                         new ActionDebugString(ret => "set time of disengage just prior"),
                         new Sequence(
                             new PrioritySelector(
                                 new Decorator(ret => !useRocketJump, Spell.BuffSelf("Disengage")),
                                 new Decorator(ret => useRocketJump, Spell.BuffSelf("Rocket Jump")),
                                 new Action(ret => Logger.Write(Color.Cyan, "DIS: {0} cast appears to have failed", useRocketJump ? "Rocket Jump" : "Disengage"))
                                 ),
                             new Action(ret =>
            {
                NextDisengageAllowed = DateTime.Now.Add(new TimeSpan(0, 0, 0, 0, 750));
                Logger.Write(Color.Cyan, "DIS: finished {0} cast", useRocketJump ? "Rocket Jump" : "Disengage");
                if (Kite.IsKitingActive())
                {
                    Kite.EndKiting(String.Format("BP: Interrupted by {0}", useRocketJump ? "Rocket Jump" : "Disengage"));
                }
                return RunStatus.Success;
            })
                             )

                         )
                     ));
        }