Beispiel #1
0
 /// <summary>
 /// Mounts a vehicle.
 /// </summary>
 /// <param name="vehicleId">The vehicle identifier.</param>
 /// <param name="searchLocation">The search location.</param>
 /// <param name="movementBy">The movement by.</param>
 /// <param name="extraVehicleQualifiers">The extra vehicle qualifiers.</param>
 /// <returns></returns>
 public static async Task <bool> MountVehicle(
     int vehicleId,
     Vector3 searchLocation,
     MovementByType movementBy = MovementByType.FlightorPreferred,
     Func <WoWUnit, bool> extraVehicleQualifiers = null)
 {
     return(await MountVehicle(searchLocation, movementBy, extraVehicleQualifiers, vehicleId));
 }
        // 30May2013-08:11UTC chinajade
        public static bool IsStateMatch_MeshNavigable(WoWObject wowObject, MovementByType movementBy)
        {
            Contract.Requires(wowObject != null, context => "wowObject != null");

            return
                ((movementBy != MovementByType.NavigatorOnly) ||
                 Navigator.CanNavigateFully(StyxWoW.Me.Location, wowObject.Location));
        }
        // 30May2013-08:11UTC chinajade
        public static bool IsStateMatch_MeshNavigable(WoWObject wowObject, MovementByType movementBy)
        {
            Contract.Requires(wowObject != null, context => "wowObject != null");

            return
                ((movementBy != MovementByType.NavigatorOnly)
                 // TODO: Rewrite usages to examine paths after generation instead
                 || /*Navigator.CanNavigateFully(StyxWoW.Me.Location, wowObject.Location)*/ true);
        }
Beispiel #4
0
        public static async Task <bool> MoveTo(
            HuntingGroundsType huntingGrounds,
            MovementByType movementBy = MovementByType.FlightorPreferred)
        {
            Contract.Requires(huntingGrounds != null, context => "huntingGrounds may not be null");
            var destination     = huntingGrounds.CurrentWaypoint().Location;
            var destinationName = String.Format("hunting ground waypoint '{0}'", huntingGrounds.CurrentWaypoint().Name);

            return(await MoveTo(destination, destinationName, movementBy));
        }
        /// <summary>Buys an item from the specified wow object.</summary>
        /// <param name="wowObject">The wow object. Navigates to <paramref name="searchLocation" /> null</param>
        /// <param name="searchLocation">The search location of <paramref name="wowObject" />.</param>
        /// <param name="itemId">The item identifier.</param>
        /// <param name="quantity">The quantity to buy.</param>
        /// <param name="movementBy">The movement type to use.</param>
        /// <param name="navigationFailedAction">
        ///     The action to take if <paramref name="wowObject" /> or
        ///     <paramref name="searchLocation" /> cant be navigated to
        /// </param>
        /// <param name="notFoundAction">
        ///     The action to take if
        ///     <paramref name="wowObject" /> is not found at
        ///     <paramref name="searchLocation" />.
        /// </param>
        /// <param name="noVendorFrameAction">
        ///     The action to take if interaction with
        ///     <paramref name="wowObject" /> didn't open a vendor frame.
        /// </param>
        /// <param name="itemNotFoundAction">The action to take if <paramref name="wowObject"/> does not offer <paramref name="itemId"/> </param>
        /// <param name="insufficientFundsAction">The action to take if toon doesn't have enough funds to buy <paramref name="itemId"/> </param>
        /// <returns></returns>
        /// <exception cref="Exception">A delegate callback throws an exception.</exception>
        public static async Task <bool> BuyItem(
            WoWObject wowObject,
            Vector3 searchLocation,
            int itemId,
            int quantity,
            MovementByType movementBy      = MovementByType.FlightorPreferred,
            Action navigationFailedAction  = null,
            Action notFoundAction          = null,
            Action noVendorFrameAction     = null,
            Action itemNotFoundAction      = null,
            Action insufficientFundsAction = null)
        {
            if (!MerchantFrame.Instance.IsVisible)
            {
                return(await Gossip(
                           wowObject,
                           searchLocation,
                           movementBy,
                           navigationFailedAction,
                           notFoundAction,
                           null,
                           noVendorFrameAction,
                           GossipEntry.GossipEntryType.Vendor));
            }

            var item = MerchantFrame.Instance.GetAllMerchantItems().FirstOrDefault(i => i.ItemId == itemId);

            if (item == null)
            {
                if (itemNotFoundAction != null)
                {
                    itemNotFoundAction();
                }
                return(false);
            }

            if (!MerchantFrame.Instance.BuyItem(item.Index, quantity))
            {
                if (insufficientFundsAction != null)
                {
                    insufficientFundsAction();
                }
                return(false);
            }

            await CommonCoroutines.SleepForRandomUiInteractionTime();

            MerchantFrame.Instance.Close();
            await CommonCoroutines.SleepForLagDuration();

            return(true);
        }
Beispiel #6
0
        /// <summary>Turns in a quest at object </summary>
        /// <param name="wowObject"> The turnin object. </param>
        /// <param name="questId"> The quest Id. If 0 (default) then first completed quest is turned in. </param>
        /// <param name="searchLocation">The search location of <paramref name="wowObject" />.</param>
        /// <param name="movementBy">The movement type to use.</param>
        /// <param name="navigationFailedAction">
        ///     The action to take if <paramref name="wowObject" /> or <paramref name="searchLocation"/> cant be navigated to
        /// </param>
        /// <param name="notFoundAction">
        ///     The action to take if <paramref name="wowObject" /> is not found at
        ///     <paramref name="searchLocation" />.
        /// </param>
        /// <returns><c>true</c> if an action was taken; <c>false</c> otherwise</returns>
        /// <exception cref="Exception">A delegate callback throws an exception.</exception>
        public static async Task <bool> TurninQuest(
            WoWObject wowObject,
            WoWPoint searchLocation,
            uint questId = 0,
            MovementByType movementBy     = MovementByType.FlightorPreferred,
            Action navigationFailedAction = null,
            Action notFoundAction         = null)
        {
            if (wowObject == null)
            {
                if (!Navigator.AtLocation(searchLocation))
                {
                    if (await MoveTo(searchLocation, "Quest turnin search area", movementBy))
                    {
                        return(true);
                    }

                    if (navigationFailedAction != null)
                    {
                        navigationFailedAction();
                    }
                    return(false);
                }

                if (notFoundAction != null)
                {
                    notFoundAction();
                }
                else
                {
                    TreeRoot.StatusText = "Waiting for the WoW object selected for quest turnin to spawn";
                }
                return(true);
            }

            if (!wowObject.WithinInteractRange)
            {
                if (await MoveTo(wowObject.Location, wowObject.SafeName, movementBy))
                {
                    return(true);
                }

                if (navigationFailedAction != null)
                {
                    navigationFailedAction();
                }
                return(false);
            }

            return(await ScriptHelpers.TurninQuest(wowObject, questId));
        }
Beispiel #7
0
 /// <summary>Turns in a quest at object </summary>
 /// <param name="wowObjectId"> The turnin object. </param>
 /// <param name="questId"> The quest Id. If 0 (default) then first completed quest is turned in. </param>
 /// <param name="searchLocation">The search location of <paramref name="wowObjectId" />.</param>
 /// <param name="movementBy">The movement type to use.</param>
 /// <param name="navigationFailedAction">
 ///     The action to take if <paramref name="wowObjectId" /> or <paramref name="searchLocation"/> cant be navigated to
 /// </param>
 /// <param name="notFoundAction">
 ///     The action to take if <paramref name="wowObjectId" /> is not found at
 ///     <paramref name="searchLocation" />.
 /// </param>
 /// <returns><c>true</c> if an action was taken; <c>false</c> otherwise</returns>
 /// <exception cref="Exception">A delegate callback throws an exception.</exception>
 public static async Task <bool> TurninQuest(
     int wowObjectId,
     WoWPoint searchLocation,
     uint questId = 0,
     MovementByType movementBy     = MovementByType.FlightorPreferred,
     Action navigationFailedAction = null,
     Action notFoundAction         = null)
 {
     return(await TurninQuest(
                ObjectManager.ObjectList.Where(o => o.Entry == wowObjectId).OrderBy(o => o.DistanceSqr).FirstOrDefault(),
                searchLocation,
                questId,
                movementBy,
                navigationFailedAction,
                notFoundAction));
 }
Beispiel #8
0
        /// <summary>
        /// Mounts a vehicle
        /// </summary>
        /// <param name="searchLocation">The search location.</param>
        /// <param name="movementBy">The movement type.</param>
        /// <param name="extraVehicleQualifiers">The extra vehicle qualifiers.</param>
        /// <param name="vehicleIds">The vehicle ids.</param>
        /// <returns>
        ///   <c>true</c> if any action was taken; <c>false</c> otherwise
        /// </returns>
        public static async Task <bool> MountVehicle(
            Vector3 searchLocation,
            MovementByType movementBy = MovementByType.FlightorPreferred,
            Func <WoWUnit, bool> extraVehicleQualifiers = null,
            params int[] vehicleIds)
        {
            if (Query.IsInVehicle())
            {
                return(false);
            }

            var vehicle = Query.FindUnoccupiedVehicles(vehicleIds, extraVehicleQualifiers).FirstOrDefault();

            if (vehicle == null)
            {
                if (!Navigator.AtLocation(searchLocation))
                {
                    return(await MoveTo(searchLocation, "Vehicle search area", movementBy));
                }

                await
                    (s_mountVehicleUserUpdateThrottle ??
                    (s_mountVehicleUserUpdateThrottle =
                         new ThrottleCoroutineTask(
                             TimeSpan.FromSeconds(10),
                             async() => QBCLog.Info("Waiting for a vehicle to become available"))));
                return(true);
            }

            if (!vehicle.WithinInteractRange)
            {
                return(await MoveTo(vehicle.Location, vehicle.SafeName, movementBy, vehicle.InteractRange));
            }

            if (await CommonCoroutines.Dismount("Getting inside vehicle"))
            {
                await Coroutine.Sleep(Delay.BeforeButtonClick);
            }
            vehicle.Interact();
            await Coroutine.Sleep(Delay.AfterInteraction);

            return(true);
        }
 /// <summary>
 ///     <para>Gossips with the specified wow object. </para>
 ///     <para>Hearthstone bind popups are automatically accepted</para>
 /// </summary>
 /// <param name="wowObjectId">The wow object identifier.</param>
 /// <param name="searchLocation">The search location of <paramref name="wowObjectId" />.</param>
 /// <param name="movementBy">The movement type to use.</param>
 /// <param name="navigationFailedAction">
 ///     The action to take if <paramref name="wowObjectId" /> or <paramref name="searchLocation"/> cant be navigated to
 /// </param>
 /// <param name="notFoundAction">The action to take if
 ///     <paramref name="wowObjectId" /> is not found at
 ///     <paramref name="searchLocation" />.</param>
 /// <param name="noGossipFrameAction">The action to take if interaction with
 ///     <paramref name="wowObjectId" /> didn't open a gossip frame.</param>
 /// <param name="noMatchingGossipOptionAction">
 ///     <para>The action to take if the passed in gossip type and/or gossip indices </para>
 ///     <para>doesn't match what was offered by <paramref name="wowObjectId" />.</para>
 /// </param>
 /// <param name="gossipEntryType">
 ///     <para>Type gossip entry type to select. If none of this type are found on current page then</para>
 ///     <para> normal gossip types are clicked through in hopes of ending on a page with this gossip type</para>
 /// </param>
 /// <param name="gossipIndexes">The gossip indexes to follow through. Has precedence over
 ///     <paramref name="gossipEntryType" />.</param>
 /// <returns></returns>
 /// <exception cref="Exception">A delegate callback throws an exception.</exception>
 public static async Task <bool> Gossip(
     int wowObjectId,
     WoWPoint searchLocation,
     MovementByType movementBy                   = MovementByType.FlightorPreferred,
     Action navigationFailedAction               = null,
     Action notFoundAction                       = null,
     Action noGossipFrameAction                  = null,
     Action noMatchingGossipOptionAction         = null,
     GossipEntry.GossipEntryType gossipEntryType = GossipEntry.GossipEntryType.Unknown,
     params int[] gossipIndexes)
 {
     return(await Gossip(
                ObjectManager.ObjectList
                .Where(o => o.Entry == wowObjectId)
                .OrderBy(o => o.DistanceSqr).FirstOrDefault(),
                searchLocation,
                movementBy,
                navigationFailedAction,
                notFoundAction,
                noGossipFrameAction,
                noMatchingGossipOptionAction,
                gossipEntryType,
                gossipIndexes));
 }
 /// <summary>Gossips with the specified wow object. Hearthstone bind popups are automatically accepted</summary>
 /// <param name="wowObjectId">The wow object identifier.</param>
 /// <param name="searchLocation">The search location of <paramref name="wowObjectId" />.</param>
 /// <param name="itemId">The item identifier.</param>
 /// <param name="quantity">The quantity to buy.</param>
 /// <param name="movementBy">The movement type to use.</param>
 /// <param name="navigationFailedAction">
 ///     The action to take if <paramref name="wowObjectId" /> or <paramref name="searchLocation"/> cant be navigated to
 /// </param>
 /// <param name="notFoundAction">The action to take if
 ///     <paramref name="wowObjectId" /> is not found at
 ///     <paramref name="searchLocation" />.</param>
 /// <param name="noVendorFrameAction">The action to take if interaction with
 ///     <paramref name="wowObjectId" /> didn't open a vendor frame.
 /// </param>
 /// <param name="itemNotFoundAction">The action to take if <paramref name="wowObjectId"/> does not offer <paramref name="itemId"/> </param>
 /// <param name="insufficientFundsAction">The action to take if toon doesn't have enough funds to buy <paramref name="itemId"/> </param>
 /// <returns></returns>
 /// <exception cref="Exception">A delegate callback throws an exception.</exception>
 public static async Task <bool> BuyItem(
     int wowObjectId,
     Vector3 searchLocation,
     int itemId,
     int quantity,
     MovementByType movementBy      = MovementByType.FlightorPreferred,
     Action navigationFailedAction  = null,
     Action notFoundAction          = null,
     Action noVendorFrameAction     = null,
     Action itemNotFoundAction      = null,
     Action insufficientFundsAction = null)
 {
     return(await BuyItem(
                ObjectManager.ObjectList.FirstOrDefault(o => o.Entry == wowObjectId),
                searchLocation,
                itemId,
                quantity,
                movementBy,
                navigationFailedAction,
                notFoundAction,
                noVendorFrameAction,
                itemNotFoundAction,
                insufficientFundsAction));
 }
        /// <summary>Gossips with the specified wow object. Hearthstone bind popups are automatically accepted</summary>
        /// <param name="wowObject">The wow object. Navigates to <paramref name="searchLocation" /> null </param>
        /// <param name="searchLocation">The search location of <paramref name="wowObject" />.</param>
        /// <param name="movementBy">The movement type to use.</param>
        /// <param name="navigationFailedAction">
        ///     The action to take if <paramref name="wowObject" /> or <paramref name="searchLocation"/> cant be navigated to
        /// </param>
        /// <param name="notFoundAction">
        ///     The action to take if <paramref name="wowObject" /> is not found at
        ///     <paramref name="searchLocation" />.
        /// </param>
        /// <param name="noGossipFrameAction">
        ///     The action to take if interaction with <paramref name="wowObject" /> didn't open a
        ///     gossip frame.
        /// </param>
        /// <param name="noMatchingGossipOptionAction">
        ///     <para>The action to take if the passed in gossip type and/or gossip indices </para>
        ///     <para>doesn't match what was offered by <paramref name="wowObject" />.</para>
        /// </param>
        /// <param name="gossipEntryType">
        ///     <para>Type gossip entry type to select. Ignored if set to Unknown.</para>
        ///		<para>If none of this type are found on current page then</para>
        ///     <para> normal gossip types are clicked through in hopes of ending on a page with this gossip type</para>
        /// </param>
        /// <param name="gossipIndexes">
        ///     The gossip indexes to follow through. Has precedence over
        ///     <paramref name="gossipEntryType" />.
        /// </param>
        /// <exception cref="Exception">A delegate callback throws an exception.</exception>
        public static async Task <bool> Gossip(
            WoWObject wowObject,
            WoWPoint searchLocation,
            MovementByType movementBy                   = MovementByType.FlightorPreferred,
            Action navigationFailedAction               = null,
            Action notFoundAction                       = null,
            Action noGossipFrameAction                  = null,
            Action noMatchingGossipOptionAction         = null,
            GossipEntry.GossipEntryType gossipEntryType = GossipEntry.GossipEntryType.Unknown,
            params int[] gossipIndexes)
        {
            if (wowObject == null)
            {
                if (!Navigator.AtLocation(searchLocation))
                {
                    if (await MoveTo(searchLocation, "Gossip object search area", movementBy))
                    {
                        return(true);
                    }

                    navigationFailedAction?.Invoke();
                    return(false);
                }

                if (notFoundAction != null)
                {
                    notFoundAction();
                }
                else
                {
                    TreeRoot.StatusText = "Waiting for the WoW object selected for gossip to spawn";
                }
                return(true);
            }

            if (!wowObject.WithinInteractRange)
            {
                if (await MoveTo(wowObject.Location, wowObject.SafeName, movementBy))
                {
                    return(true);
                }

                navigationFailedAction?.Invoke();
                return(false);
            }

            if (await CommonCoroutines.Dismount("Gossiping with " + wowObject.SafeName))
            {
                await Coroutine.Sleep(Delay.BeforeButtonClick);
            }

            // If gossip frame is open then we must assume that it doesn't belong to the selected gossip object at this point
            if (GossipFrame.Instance.IsVisible)
            {
                GossipFrame.Instance.Close();
                return(true);
            }

            Func <bool> isFrameReadyForInput =
                () =>
                GossipFrame.Instance.IsVisible &&
                (GossipFrame.Instance.GossipOptionEntries != null ||
                 (!gossipIndexes.Any() && gossipEntryType == GossipEntry.GossipEntryType.Unknown));

            wowObject.Interact();
            var openedGossipFrame = await Coroutine.Wait(3000, isFrameReadyForInput);

            if (!openedGossipFrame)
            {
                QBCLog.Warning("No gossip frame was opened after interacting with {0}", wowObject.SafeName);
                noGossipFrameAction?.Invoke();

                return(false);
            }

            int gossipPage = 1;

            // Click through all the gossip indices
            for (var i = 0; i < gossipIndexes.Length; i++)
            {
                var index = gossipIndexes[i] - 1;

                var gossipEntry =
                    GossipFrame.Instance.GossipOptionEntries.Where(g => g.Index == index)
                    .Select(g => (GossipEntry?)g)
                    .FirstOrDefault();

                if (!gossipEntry.HasValue || gossipEntry.Value.Type == GossipEntry.GossipEntryType.Unknown)
                {
                    QBCLog.Warning("{0} does not provide a gossip at index {1} on page {2}", wowObject.SafeName, index + 1, gossipPage);
                    noMatchingGossipOptionAction?.Invoke();
                    return(false);
                }

                await ClickGossipOption(gossipEntry.Value, gossipPage);

                // make sure frame didn't close before we're done.
                if (!isFrameReadyForInput() && (i < gossipIndexes.Length - 1 || gossipEntryType != GossipEntry.GossipEntryType.Unknown))
                {
                    // This can happen if some external event causes object to stop offering gossip frame, such as NPC getting into combat.
                    // Usually this can be fixed by interacting with object again at a later time. We let the caller handle this.
                    QBCLog.Warning("Gossip frame for {0} closed unexpectedly.", wowObject.SafeName);
                    return(true);
                }
                gossipPage++;
            }

            if (gossipEntryType != GossipEntry.GossipEntryType.Unknown)
            {
                if (!gossipIndexes.Any())
                {
                    while (true)
                    {
                        var gossipEntry = GossipFrame.Instance.GossipOptionEntries.FirstOrDefault(g => g.Type == gossipEntryType);
                        // If no gossip indices were specified then we just click through more gossip,
                        // hopefully it leads to the final gossip type
                        if (gossipEntry.Type != gossipEntryType)
                        {
                            gossipEntry =
                                GossipFrame.Instance.GossipOptionEntries.FirstOrDefault(g => g.Type == GossipEntry.GossipEntryType.Gossip);
                        }

                        if (gossipEntry.Type == GossipEntry.GossipEntryType.Unknown)
                        {
                            QBCLog.Warning("{0} does not provide a {0} gossip type", wowObject.SafeName, gossipEntryType);
                            noMatchingGossipOptionAction?.Invoke();
                            return(false);
                        }

                        await ClickGossipOption(gossipEntry, gossipPage);

                        if (!isFrameReadyForInput() && gossipEntry.Type != gossipEntryType)
                        {
                            // This can happen if some external event causes object to stop offering gossip frame, such as NPC getting into combat.
                            // Usually this can be fixed by interacting with object again at a later time. We let the caller handle this.
                            QBCLog.Warning("Gossip frame for {0} closed unexpectedly.", wowObject.SafeName);
                            return(true);
                        }

                        if (gossipEntry.Type == gossipEntryType)
                        {
                            break;
                        }

                        gossipPage++;
                    }
                }
            }

            // Set hearthstone automatically
            const string setHsPopupName = "CONFIRM_BINDER";

            if (Lua.GetReturnVal <bool>($"return StaticPopup_Visible('{setHsPopupName}')", 0))
            {
                uint hsId = StyxWoW.Me.HearthstoneAreaId;
                Lua.DoString(
                    $"local _,frame = StaticPopup_Visible('{setHsPopupName}') if frame then StaticPopup_OnClick(frame, 1) end");

                if (await Coroutine.Wait(5000, () => StyxWoW.Me.HearthstoneAreaId != hsId))
                {
                    await CommonCoroutines.SleepForRandomReactionTime();

                    var boundLocation = Lua.GetReturnVal <string>("return GetBindLocation()", 0);

                    QBCLog.Info(
                        "You are now bound at {0} Inn in {1}({2})",
                        (Query.IsViable(wowObject) ? wowObject.SafeName : "the"),
                        boundLocation,
                        Me.HearthstoneAreaId);
                }
            }
            return(true);
        }
Beispiel #12
0
        public static async Task <bool> MoveTo(
            WoWPoint destination,
            string destinationName,
            MovementByType movementBy = MovementByType.FlightorPreferred)
        {
            Contract.Requires(destinationName != null, context => "destinationName may not be null");

            if (movementBy == MovementByType.None)
            {
                return(false);
            }

            var activeMover = WoWMovement.ActiveMover;

            if (activeMover == null)
            {
                return(false);
            }

            if (!IsMoveToMessageThrottled)
            {
                if (string.IsNullOrEmpty(destinationName))
                {
                    destinationName = destination.ToString();
                }
                TreeRoot.StatusText = "Moving to " + destinationName;
            }

            switch (movementBy)
            {
            case MovementByType.FlightorPreferred:
                if (await TryFlightor(destination))
                {
                    return(true);
                }

                if (await TryNavigator(destination, destinationName))
                {
                    return(true);
                }

                if (await TryClickToMove(destination, NavType.Fly))
                {
                    return(true);
                }
                break;

            case MovementByType.NavigatorPreferred:
                if (await TryNavigator(destination, destinationName))
                {
                    return(true);
                }

                if (await TryClickToMove(destination, NavType.Run))
                {
                    return(true);
                }
                break;

            case MovementByType.NavigatorOnly:
                if (await TryNavigator(destination, destinationName))
                {
                    return(true);
                }
                break;

            case MovementByType.ClickToMoveOnly:
                var navType = activeMover.MovementInfo.CanFly ? NavType.Fly : NavType.Run;
                if (await TryClickToMove(destination, navType))
                {
                    return(true);
                }
                break;

            case MovementByType.None:
                break;

            default:
                QBCLog.MaintenanceError("Unhandled MovementByType of {0}", movementBy);
                break;
            }
            return(false);
        }
Beispiel #13
0
        /// <summary>
        /// Uses the transport.
        /// </summary>
        /// <param name="transportId">The transport identifier.</param>
        /// <param name="transportStartLoc">The start location.</param>
        /// <param name="transportEndLoc">The end location.</param>
        /// <param name="waitAtLoc">The wait at location.</param>
        /// <param name="boardAtLoc">The stand at location.</param>
        /// <param name="getOffLoc">The get off location.</param>
        /// <param name="movement">The movement.</param>
        /// <param name="destination">The destination.</param>
        /// <param name="navigationFailedAction">
        ///     The action to take if <paramref name="waitAtLoc" /> cant be navigated to
        /// </param>
        /// <returns>returns <c>true</c> until done</returns>
        /// <exception cref="Exception">A delegate callback throws an exception. </exception>
        public static async Task <bool> UseTransport(
            int transportId,
            Vector3 transportStartLoc,
            Vector3 transportEndLoc,
            Vector3 waitAtLoc,
            Vector3 boardAtLoc,
            Vector3 getOffLoc,
            MovementByType movement       = MovementByType.FlightorPreferred,
            string destination            = null,
            Action navigationFailedAction = null)
        {
            if (getOffLoc != Vector3.Zero && Me.Location.DistanceSquared(getOffLoc) < 2 * 2)
            {
                return(false);
            }

            var transportLocation = GetTransportLocation(transportId);

            if (transportLocation != Vector3.Zero &&
                transportLocation.DistanceSquared(transportStartLoc) < 1.5 * 1.5 &&
                waitAtLoc.DistanceSquared(Me.Location) < 2 * 2)
            {
                TreeRoot.StatusText = "Moving inside transport";
                Navigator.PlayerMover.MoveTowards(boardAtLoc);
                await CommonCoroutines.SleepForLagDuration();

                // wait for bot to get on boat.
                await Coroutine.Wait(12000, () => !Me.IsMoving || Navigator.AtLocation(boardAtLoc));
            }

            // loop while on transport to prevent bot from doing anything else
            while (Me.Transport != null && Me.Transport.Entry == transportId)
            {
                if (transportLocation != Vector3.Zero && transportLocation.DistanceSquared(transportEndLoc) < 1.5 * 1.5)
                {
                    TreeRoot.StatusText = "Moving out of transport";
                    Navigator.PlayerMover.MoveTowards(getOffLoc);
                    await CommonCoroutines.SleepForLagDuration();

                    // Sleep until we stop moving.
                    await Coroutine.Wait(12000, () => !Me.IsMoving || Navigator.AtLocation(getOffLoc));

                    return(true);
                }

                // Exit loop if in combat or dead.
                if (Me.Combat || !Me.IsAlive)
                {
                    return(false);
                }

                TreeRoot.StatusText = "Waiting for the end location";
                await Coroutine.Yield();

                // update transport location.
                transportLocation = GetTransportLocation(transportId);
            }

            if (waitAtLoc.DistanceSquared(Me.Location) > 2 * 2)
            {
                if (!await MoveTo(waitAtLoc, destination ?? waitAtLoc.ToString(), movement))
                {
                    navigationFailedAction?.Invoke();
                }
                return(true);
            }
            await CommonCoroutines.LandAndDismount();

            TreeRoot.StatusText = "Waiting for transport";
            return(true);
        }