Пример #1
0
        /// <summary>
        /// <para>Reads the "Quest Behaviors/DATA/AuraIds_OccupiedVehicle.xml" file, and returns an IEnumerable
        /// of all the AuraIds that are represent Vehicles that are occupied.</para>
        /// <para>If the da file has malformed entries (which it should never be), error messages
        /// will be emitted.</para>
        /// </summary>
        /// <returns>the IEnumerable may be empty, but it will never be null.</returns>
        //  7Mar2013-02:28UTC chinajade
        public static IEnumerable <int> GetOccupiedVehicleAuraIds()
        {
            var occupiedVehicleAuraIds = new List <int>();

            string auraDataFileName = GetDataFileFullPath("AuraIds_OccupiedVehicle.xml");

            if (!File.Exists(auraDataFileName))
            {
                QBCLog.Warning("Unable to locate Occupied Vehicle Aura database (in {0}).  Vehicles will be unqualified"
                               + "--this may cause us to follow vehicles occupied by other players.",
                               auraDataFileName);
                return(occupiedVehicleAuraIds);
            }

            XDocument xDoc = XDocument.Load(auraDataFileName, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);

            foreach (var childElement in xDoc.Descendants("Aura"))
            {
                var aura = new SpellType(childElement);

                if (!aura.IsAttributeProblem)
                {
                    occupiedVehicleAuraIds.Add(aura.SpellId);
                }
            }

            return(occupiedVehicleAuraIds);
        }
        // 19Apr2013-05:58UTC chinajade
        public static void DeprecationWarning_Behavior(CustomForcedBehavior cfb, string newBehaviorName, List <Tuple <string, string> > replacementAttributes)
        {
            if (QuestBehaviorCoreSettings.Instance.LogNotifyOn_OnDeprecatedBehaviorUse)
            {
                var oldLoggingContext = QBCLog.BehaviorLoggingContext;

                QBCLog.BehaviorLoggingContext = cfb;

                string attributes =
                    string.Join(" ", replacementAttributes.Select(t => string.Format("{0}=\"{1}\"", t.Item1, t.Item2)));

                QBCLog.Warning(QBCLog.BuildMessageWithContext(cfb.Element,
                                                              "{0}/********************{0}DEPRECATED BEHAVIOR ({1}){0}"
                                                              + "The {1} behavior has been deprecated, but will continue to function as originally designed."
                                                              + "  Please replace the use of the {1} behavior with the {2} behavior.{0}"
                                                              + "The replacement command to accomplish this task is:{0}    {3}",
                                                              Environment.NewLine,
                                                              cfb.GetType().Name,
                                                              newBehaviorName,
                                                              string.Format("<CustomBehavior File=\"{0}\" {1} />", newBehaviorName, attributes))
                               + Environment.NewLine
                               + "********************/"
                               + Environment.NewLine);

                AudibleNotifyOn(QuestBehaviorCoreSettings.Instance.AudibleNotify_OnDeprecatedBehaviorUse);
                QBCLog.BehaviorLoggingContext = oldLoggingContext;
            }
        }
        // 11Mar2013-04:41UTC chinajade
        private double CalculateMuzzleVelocity()
        {
            double launchAngle    = WeaponArticulation.AzimuthGet();
            double muzzleVelocity = 0.0;
            var    spell          = FindVehicleAbilitySpell();

            if ((spell != null) && (spell.InternalInfo.SpellMissile != null))
            {
                IEnumerable <WoWMissile> firedMissileQuery =
                    from missile in WoWMissile.InFlightMissiles
                    where
                    (missile.CasterGuid == Me.TransportGuid) &&
                    (missile.SpellId == spell.Id)
                    select missile;


                WoWMissile launchedMissile = firedMissileQuery.FirstOrDefault();

                /* N.B This has been commented out to fix momentary game 'freeze up' from framelock but has been left here for -
                 * future reference in the event this logic needs to be further repaired.
                 *
                 * // Launch missile, and wait until launch is observed;
                 * MissileWatchingTimer.Reset();
                 *
                 * while ((launchedMissile == null) && !MissileWatchingTimer.IsFinished)
                 * {
                 *  // WoWMissiles are read directly from the games memory and are not stored in the 'ObjectManager'
                 *  // ObjectManager.Update();
                 *  launchedMissile = firedMissileQuery.FirstOrDefault();
                 * }
                 */

                // If we failed to see the missile, report error and move on...
                if (launchedMissile == null)
                {
                    QBCLog.Warning(
                        "Muzzle Velocity not calculated--"
                        + "Unable to locate projectile launched by Vehicle Ability button #{0}",
                        AbilityIndex);
                    return(double.NaN);  // "we don't know"
                }

                // Initial velocity calculation...
                // * Accounts for uneven terrain
                //
                // v0 = sqrt((R^2 * g) / (R * sin(2*theta)  +  2 * h * cos^2(theta)))
                // where, R = range, g = grav const, h = drop height, theta = launch angle
                double R           = launchedMissile.FirePosition.Distance2D(launchedMissile.ImpactPosition);
                double h           = launchedMissile.FirePosition.Z - launchedMissile.ImpactPosition.Z;
                double sinTwoTheta = Math.Sin(2 * launchAngle);
                double cosTheta    = Math.Cos(launchAngle);

                muzzleVelocity = Math.Sqrt(((R * R) * GravityInFpsSqr) / (R * sinTwoTheta + 2 * h * (cosTheta * cosTheta)));
            }

            return(muzzleVelocity);
        }
Пример #4
0
        // 30Apr2013-09:09UTC chinajade
        public static SwimBreathType GetSwimBreathInfo()
        {
            string swimBreathFileName = GetDataFileFullPath("SwimBreath.xml");

            if (!File.Exists(swimBreathFileName))
            {
                QBCLog.Warning("Unable to locate Swim Breath database (in {0}).  Will only be able to catch breath"
                               + " at water's surface.",
                               swimBreathFileName);
                return(null);
            }

            XDocument xDoc = XDocument.Load(swimBreathFileName, LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);

            return(new SwimBreathType(xDoc.Elements("SwimBreath").FirstOrDefault()));
        }
        // 19Apr2013-05:58UTC chinajade
        public void UsageCheck_DeprecatedAttribute(XElement xElement, bool deprecatedAttributeEncounteredPredicate,
                                                   string attributeName, ProvideStringDelegate messageDelegate)
        {
            if (QuestBehaviorCoreSettings.Instance.LogNotifyOn_OnDeprecatedAttributeUse)
            {
                if (deprecatedAttributeEncounteredPredicate)
                {
                    QBCLog.Warning(QBCLog.BuildMessageWithContext(xElement,
                                                                  "DEPRECATED ATTRIBUTE ({1}):{0}{2}",
                                                                  Environment.NewLine,
                                                                  attributeName,
                                                                  UtilFormatMessage(messageDelegate(null))));

                    AudibleNotifyOn(QuestBehaviorCoreSettings.Instance.AudibleNotify_OnDeprecatedAttributeUse);
                }
            }
        }
        // 11Mar2013-04:41UTC chinajade
        public bool WeaponAim(Vector3 selectedLocation)
        {
            AimedLocation = null;
            if (selectedLocation == Vector3.Zero)
            {
                QBCLog.Warning("No target location for WeaponAim!");
                WoWMovement.StopFace();
                return(false);
            }

            if (!UtilAimPreReqsPassed())
            {
                return(false);
            }

            var spell = FindVehicleAbilitySpell();
            // No aiming is required if ability targets terrain using mouse cursor
            //if (spell != null && spell.CanTargetTerrain)
            //{
            //	// make sure target is within range of spell
            //	if (Me.Location.Distance(selectedLocation) > spell.MaxRange)
            //		return false;

            //	AimedLocation = selectedLocation;
            //	return true;
            //}

            // Calculate the azimuth...
            // TODO: Take vehicle rotations (pitch, roll) into account
            var azimuth = CalculateBallisticLaunchAngle(selectedLocation);

            if (!azimuth.HasValue || !WeaponArticulation.AzimuthSet(azimuth.Value))
            {
                return(false);
            }

            // For heading, we just face the location...
            if (!WeaponArticulation.HeadingSet(selectedLocation))
            {
                return(false);
            }

            AimedLocation = selectedLocation;
            return(true);
        }
        // 19Apr2013-05:58UTC chinajade
        public static void UsageCheck_ScheduledForDeprecation(CustomForcedBehavior cfb, string replacementBehaviorName)
        {
            if (QuestBehaviorCoreSettings.Instance.LogNotifyOn_OnScheduledForDeprecation)
            {
                var oldLoggingContext = QBCLog.BehaviorLoggingContext;

                QBCLog.BehaviorLoggingContext = cfb;
                QBCLog.Warning(QBCLog.BuildMessageWithContext(cfb.Element,
                                                              "SCHEDULED FOR DEPRECATION ({1}){0}"
                                                              + "Please replace the behavior with \"{2}\"",
                                                              Environment.NewLine,
                                                              cfb.GetType().Name,
                                                              replacementBehaviorName));

                AudibleNotifyOn(true);
                QBCLog.BehaviorLoggingContext = oldLoggingContext;
            }
        }
        private bool UtilAimPreReqsPassed()
        {
            if (!Query.IsVehicleActionBarShowing())
            {
                QBCLog.Warning("Attempted to aim weapon while not in Vehicle!");
                return(false);
            }

            // Test fire weapon, if needed.  This gets us the muzzle velocity...
            if (NeedsTestFire && IsWeaponReady())
            {
                // Lie about weapon being aimed...
                // NB:  S'ok, for test fire, we're after the measured muzzle velocity.
                AimedLocation = Vector3.Zero;
                WeaponFire();
                return(false);
            }

            return(true);
        }
Пример #9
0
        public bool UseAbility()
        {
            if (!Query.IsVehicleActionBarShowing())
            {
                QBCLog.Warning("Attempted to use {0} while not in Vehicle!", Name);
                return(false);
            }

            if (IsAbilityReady())
            {
                Lua.DoString(_luaCommand_UseAbility);
                ++AbilityUseCount;

                if (LogAbilityUse)
                {
                    QBCLog.DeveloperInfo("{0} ability used (count: {1})", Name, AbilityUseCount);
                }

                return(true);
            }

            return(false);
        }
Пример #10
0
        public static async Task <bool> Interact(WoWObject selectedTarget, System.Action actionOnSuccessfulItemUseDelegate = null)
        {
            // Viable target?
            // NB: Since target may go invalid immediately upon interacting with it,
            // we cache its name for use in subsequent log entries.
            if (!Query.IsViable(selectedTarget))
            {
                QBCLog.Warning("Target is not viable!");
                return(false);
            }

            var targetName = selectedTarget.SafeName;

            // Need to be facing target...
            // NB: Not all items require this, but many do.
            Utility.Target(selectedTarget, true);

            // Notify user of intent...
            QBCLog.DeveloperInfo("Interacting with '{0}'", targetName);

            // Set up 'interrupted use' detection, and interact...
            using (var castMonitor = SpellCastMonitor.Start(null))
            {
                selectedTarget.Interact();

                // NB: The target may not be valid after this point...
                // Some targets will go 'invalid' immediately afer interacting with them.
                // Most of the time this happens, the target is immediately and invisibly replaced with
                // an identical looking target with a different script.
                // We must assume our target is no longer available for use after this point.
                await Coroutine.Sleep(Delay.AfterInteraction);

                // Wait for any casting to complete...
                // NB: Some interactions or item usages take time, and the WoWclient models this as spellcasting.
                // NB: We can't test for IsCasting or IsChanneling--we must instead look for a valid spell being cast.
                //      There are some quests that require actions where the WoWclient returns 'true' for IsCasting,
                //      but there is no valid spell being cast.  We want the behavior to move on immediately in these
                //      conditions.  An example of such an interaction is removing 'tangler' vines in the Tillers
                //      daily quest area.
                var castResult = await castMonitor.GetResult();

                if (castResult != SpellCastResult.Succeeded && castResult != SpellCastResult.NoCastStarted)
                {
                    string reason = castResult == SpellCastResult.UnknownFail
                        ? castMonitor.FailReason
                        : castResult.ToString();

                    QBCLog.DeveloperInfo("Interaction with {0} interrupted.", targetName);

                    QBCLog.Warning("Interaction with {0} interrupted. Reason: {1}", targetName, reason);
                    // Give whatever issue encountered a chance to settle...
                    // NB: --we want the Sequence to fail when delay completes.
                    if (castResult != SpellCastResult.LineOfSight &&
                        castResult != SpellCastResult.OutOfRange &&
                        castResult != SpellCastResult.TooClose)
                    {
                        await Coroutine.Sleep(1500);
                    }
                    return(false);
                }

                QBCLog.DeveloperInfo("Interact with '{0}' succeeded.", targetName);
                if (actionOnSuccessfulItemUseDelegate != null)
                {
                    actionOnSuccessfulItemUseDelegate();
                }

                return(true);
            }
        }
Пример #11
0
        /// <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);
        }
        // 11Mar2013-04:41UTC chinajade
        public bool WeaponAim(WoWObject selectedTarget)
        {
            AimedLocation = null;
            // If target is moving, lead it...
            var wowUnit = selectedTarget as WoWUnit;

            if (Query.IsViable(wowUnit) && wowUnit.IsMoving)
            {
                var projectileFlightTime = CalculateTimeOfProjectileFlight(selectedTarget.Location);
                var anticipatedLocation  = selectedTarget.AnticipatedLocation(projectileFlightTime);

                WoWMovement.StopFace();
                return(WeaponAim(anticipatedLocation));
            }

            if (!Query.IsViable(selectedTarget))
            {
                QBCLog.Warning("No target for WeaponAim!");
                WoWMovement.StopFace();
                return(false);
            }

            if (!UtilAimPreReqsPassed())
            {
                return(false);
            }

            // Show user what we're targeting...
            Utility.Target(selectedTarget);

            var spell = FindVehicleAbilitySpell();
            // Terrain is targeted when firing weapon.
            // Commented out until repairs are made.
            //if (spell != null && spell.CanTargetTerrain)
            //{
            //	// make sure target is within range of spell
            //	if ( Me.Location.Distance(selectedTarget.Location) > spell.MaxRange)
            //		return false;

            //	AimedLocation = selectedTarget.Location;
            //	return true;
            //}

            // Calculate the azimuth...
            // TODO: Take vehicle rotations (pitch, roll) into account
            var azimuth = CalculateBallisticLaunchAngle(selectedTarget.Location);

            //// Debugging--looking for pitch/roll contributions...
            //// NB: It currently looks like the GetWorldMatrix() does not populate enough info to make
            //// this calculation.
            //if (azimuth.HasValue)
            //{
            //    var pitch = StyxWoW.Memory.Read<float>(WoWMovement.ActiveMover.BaseAddress + 0x820 + 0x24);
            //    QBCLog.Debug("{0} {1:F3}/ {2:F3} pitch: {3:F3}", WoWMovement.ActiveMover.Name, azimuth, azimuth - pitch, pitch);

            //    QBCDebug.ShowVehicleArticulationChain(WoWMovement.ActiveMover);
            //}

            if (!azimuth.HasValue || !WeaponArticulation.AzimuthSet(azimuth.Value))
            {
                return(false);
            }

            // For heading, we just face the location...
            if (!WeaponArticulation.HeadingSet(selectedTarget))
            {
                return(false);
            }

            AimedLocation = selectedTarget.Location;
            return(true);
        }
Пример #13
0
        public static async Task <SpellCastResult> CastSpell(
            int spellId,
            WoWObject target = null,
            System.Action actionOnSuccessfulSpellCastDelegate = null)
        {
            // Viable target?
            // If target is null, then assume 'self'.
            // NB: Since target may go invalid immediately upon casting the spell,
            // we cache its name for use in subsequent log entries.
            var selectedObject = target ?? Me;

            if (!Query.IsViable(selectedObject))
            {
                QBCLog.Warning("Target is not viable!");
                return(SpellCastResult.InvalidTarget);
            }

            var targetName = selectedObject.SafeName;

            // Target must be a WoWUnit for us to be able to cast a spell on it...
            var selectedTarget = selectedObject as WoWUnit;

            if (!Query.IsViable(selectedTarget))
            {
                QBCLog.Warning("Target {0} is not a WoWUnit--cannot cast spell on it.", targetName);
                return(SpellCastResult.InvalidTarget);
            }

            // Spell known?
            WoWSpell selectedSpell = WoWSpell.FromId(spellId);

            if (selectedSpell == null)
            {
                QBCLog.Warning("{0} is not known.", Utility.GetSpellNameFromId(spellId));
                return(SpellCastResult.SpellNotKnown);
            }
            var spellName = selectedSpell.Name;

            // Need to be facing target...
            // NB: Not all spells require this, but many do.
            Utility.Target(selectedTarget, true);

            // Wait for spell to become ready...
            if (!SpellManager.CanCast(selectedSpell))
            {
                QBCLog.Warning(
                    "{0} is not usable, yet.  (cooldown remaining: {1})",
                    spellName,
                    Utility.PrettyTime(selectedSpell.CooldownTimeLeft));
                return(SpellCastResult.NotReady);
            }

            // Notify user of intent...
            var message = string.Format("Attempting cast of '{0}' on '{1}'", spellName, targetName);

            message +=
                selectedTarget.IsDead
                    ? " (dead)"
                    : string.Format(" (health: {0:F1})", selectedTarget.HealthPercent);
            QBCLog.DeveloperInfo(message);

            // Set up 'interrupted use' detection, and cast spell...
            using (var castMonitor = SpellCastMonitor.Start(spellId))
            {
                SpellManager.Cast(selectedSpell, selectedTarget);

                // NB: The target or the spell may not be valid after this point...
                // Some targets will go 'invalid' immediately afer interacting with them.
                // Most of the time this happens, the target is immediately and invisibly replaced with
                // an identical looking target with a different script.
                // We must assume our target and spell is no longer available for use after this point.

                await Coroutine.Sleep((int)Delay.AfterItemUse.TotalMilliseconds);

                // If item use requires a second click on the target (e.g., item has a 'ground target' mechanic)...
                await CastPendingSpell(selectedTarget);

                // Wait for any casting to complete...
                // NB: Some interactions or item usages take time, and the WoWclient models this as spellcasting.

                var castResult = await castMonitor.GetResult();

                if (castResult != SpellCastResult.Succeeded)
                {
                    string reason = castResult == SpellCastResult.UnknownFail
                        ? castMonitor.FailReason
                        : castResult.ToString();

                    QBCLog.Warning("Cast of {0} failed. Reason: {1}", spellName, reason);
                    // Give whatever issue encountered a chance to settle...
                    // NB: --we want the Sequence to fail when delay completes.
                    if (castResult != SpellCastResult.LineOfSight &&
                        castResult != SpellCastResult.OutOfRange &&
                        castResult != SpellCastResult.TooClose)
                    {
                        await Coroutine.Sleep(1500);
                    }
                    return(castResult);
                }

                QBCLog.DeveloperInfo("Cast of '{0}' on '{1}' succeeded.", spellName, targetName);

                if (actionOnSuccessfulSpellCastDelegate != null)
                {
                    actionOnSuccessfulSpellCastDelegate();
                }

                return(SpellCastResult.Succeeded);
            }
        }
Пример #14
0
        /// <summary>
        /// <para>Uses the hearthstone.</para>
        /// <para>Dismounts if mounted and stops moving before attempting to cast hearthstone. </para>
        /// <para>Does not yield until hearthstone is casted unless it can't be casted, already in hearthstone area or cast failed. </para>
        /// </summary>
        /// <param name="useGarrisonHearthstone">Use garrison hearthstone if set to <c>true</c>.</param>
        /// <param name="inHearthAreaAction">The action to take if already in hearthstone area.</param>
        /// <param name="noHearthStoneInBagsAction">The action to take if no hearthstone is in bags.</param>
        /// <param name="hearthNotSetAction">The action to take if hearth is not set.</param>
        /// <param name="hearthOnCooldownAction">The action to take if hearth is on cooldown.</param>
        /// <param name="hearthCastedAction">The action to take if hearth is successfully casted.</param>
        /// <param name="hearthCastFailedAction">The action to take if hearth failed to cast. The reason string is passed in argument.</param>
        /// <returns>Returns <c>true</c> if hearth was casted</returns>
        /// <exception cref="Exception">A delegate callback throws an exception.</exception>
        public static async Task <bool> UseHearthStone(
            bool useGarrisonHearthstone            = false,
            Action inHearthAreaAction              = null,
            Action noHearthStoneInBagsAction       = null,
            Action hearthNotSetAction              = null,
            Action hearthOnCooldownAction          = null,
            Action hearthCastedAction              = null,
            Action <string> hearthCastFailedAction = null)
        {
            if (IsInHearthStoneArea(useGarrisonHearthstone))
            {
                if (inHearthAreaAction != null)
                {
                    inHearthAreaAction();
                }
                else
                {
                    QBCLog.DeveloperInfo("Already at hearthstone area");
                }

                return(false);
            }

            var hearthStones = useGarrisonHearthstone
                ? GetHearthStonesByIds(ItemId_GarrisonHearthStoneId)
                : GetHearthStonesByIds(ItemId_HearthStoneId, ItemId_TheInnkeepersDaughter);

            if (!hearthStones.Any())
            {
                if (noHearthStoneInBagsAction != null)
                {
                    noHearthStoneInBagsAction();
                }
                else
                {
                    QBCLog.DeveloperInfo("No hearthstone found in bag");
                }
                return(false);
            }

            if (!useGarrisonHearthstone && Me.HearthstoneAreaId == 0)
            {
                // I can only see this occurring if using the Innkeeper's Daughter hearthtone since the normal hearthstone
                // only shows up in bags if hearth has been set.
                if (hearthNotSetAction != null)
                {
                    hearthNotSetAction();
                }
                else
                {
                    QBCLog.DeveloperInfo("Hearth has not been set");
                }
                return(false);
            }

            var usableHearthstone = hearthStones.FirstOrDefault(i => !i.Effects.First().Spell.Cooldown);

            if (usableHearthstone == null)
            {
                if (hearthOnCooldownAction != null)
                {
                    hearthOnCooldownAction();
                }
                else
                {
                    QBCLog.DeveloperInfo("Hearth is on cooldown");
                }

                return(false);
            }

            // the following coroutines execute sequentially, they do not return until dismounted or movement has stopped.
            await CommonCoroutines.LandAndDismount();

            await CommonCoroutines.StopMoving();

            // Close any frame that can prevent hearthstone use...
            // For example WoW will try to sell to hearthstone if merchant frame is open when hearthstone is used
            await CloseFrames();

            var hearthstoneSpell = usableHearthstone.Effects.First().Spell;

            using (var castMonitor = SpellCastMonitor.Start(hearthstoneSpell.Id))
            {
                QBCLog.DeveloperInfo("Using hearthstone: {0}", hearthstoneSpell.Name);

                usableHearthstone.Use();
                var castResult = await castMonitor.GetResult(12000);

                if (castResult == SpellCastResult.Succeeded)
                {
                    await Coroutine.Wait(2000, () => IsInHearthStoneArea(useGarrisonHearthstone));

                    if (hearthCastedAction != null)
                    {
                        hearthCastedAction();
                    }
                    else
                    {
                        QBCLog.DeveloperInfo("Successfully used hearthstone");
                    }
                    return(true);
                }

                string reason = castResult == SpellCastResult.UnknownFail ? castMonitor.FailReason : castResult.ToString();

                if (hearthCastFailedAction != null)
                {
                    hearthCastFailedAction(reason);
                }
                else
                {
                    QBCLog.Warning("Cast of {0} failed. Reason: {1}", hearthstoneSpell.Name, reason);
                }
                return(false);
            }
        }
Пример #15
0
        /// <summary>
        ///     <para>Uses item defined by ITEMID.</para>
        ///     <para>
        ///         Notes:
        ///         <list type="bullet">
        ///             <item>
        ///                 <description>
        ///                     <para>
        ///                         * It is up to the caller to assure that all preconditions have been met for
        ///                         using the item (i.e., the item is off cooldown, etc).
        ///                     </para>
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <description>
        ///                     <para>
        ///                         * If item use was successful, coroutine returns 'true';
        ///                         otherwise, 'false' is returned (e.g., item is not ready for use,
        ///                         item use was interrupted by combat, etc).
        ///                     </para>
        ///                 </description>
        ///             </item>
        ///         </list>
        ///     </para>
        /// </summary>
        /// <param name="itemId">The item provided should be viable, and ready for use.</param>
        /// <param name="actionOnMissingItemDelegate">This delegate will be called if the item
        /// is missing from our backpack.  This delegate may not be null.</param>
        /// <param name="actionOnFailedItemUseDelegate">If non-null, this delegate will be called
        /// if we attempted to use the item, and it was unsuccessful.  Examples include attemtping
        /// to use the item on an invalid target, or being interrupted or generally unable to use
        /// the item at this time.</param>
        /// <param name="actionOnSuccessfulItemUseDelegate">If non-null, this delegate will be called
        /// once the item has been used successfully.</param>
        /// <returns></returns>
        /// <remarks>20140305-19:01UTC, Highvoltz/chinajade</remarks>
        public static async Task <bool> UseItem(
            int itemId,
            System.Action actionOnMissingItemDelegate,
            System.Action actionOnFailedItemUseDelegate     = null,
            System.Action actionOnSuccessfulItemUseDelegate = null)
        {
            // Waits for global cooldown to end to successfully use the item
            await Coroutine.Wait(500, () => !SpellManager.GlobalCooldown);

            // Is item in our bags?
            var itemToUse = Me.CarriedItems.FirstOrDefault(i => (i.Entry == itemId));

            if (!Query.IsViable(itemToUse))
            {
                QBCLog.Error("{0} is not in our bags.", Utility.GetItemNameFromId(itemId));
                if (actionOnMissingItemDelegate != null)
                {
                    actionOnMissingItemDelegate();
                }
                return(false);
            }
            var itemName = itemToUse.SafeName;

            // Wait for Item to be usable...
            // NB: WoWItem.Usable does not account for cooldowns.
            if (!itemToUse.Usable || (itemToUse.Cooldown > 0))
            {
                TreeRoot.StatusText =
                    string.Format(
                        "{0} is not usable, yet. (cooldown remaining: {1})",
                        itemName,
                        Utility.PrettyTime(itemToUse.CooldownTimeLeft));
                return(false);
            }

            // Notify user of intent...
            QBCLog.DeveloperInfo("Attempting use of '{0}'", itemName);

            // Set up 'interrupted use' detection, and use item...
            using (var castMonitor = SpellCastMonitor.Start(null))
            {
                itemToUse.Use();
                // NB: The target or the item may not be valid after this point...
                // Some targets will go 'invalid' immediately afer interacting with them.
                // Most of the time this happens, the target is immediately and invisibly replaced with
                // an identical looking target with a different script.
                // Some items are consumed when used.
                // We must assume our target and item is no longer available for use after this point.
                await Coroutine.Sleep((int)Delay.AfterItemUse.TotalMilliseconds);

                // Wait for any casting to complete...
                // NB: Some interactions or item usages take time, and the WoWclient models this as spellcasting.
                // NB: We can't test for IsCasting or IsChanneling--we must instead look for a valid spell being cast.
                //      There are some quests that require actions where the WoWclient returns 'true' for IsCasting,
                //      but there is no valid spell being cast.  We want the behavior to move on immediately in these
                //      conditions.  An example of such an interaction is removing 'tangler' vines in the Tillers
                //      daily quest area.
                var castResult = await castMonitor.GetResult();

                if (castResult != SpellCastResult.Succeeded && castResult != SpellCastResult.NoCastStarted)
                {
                    string reason = castResult == SpellCastResult.UnknownFail ? castMonitor.FailReason : castResult.ToString();

                    QBCLog.Warning("Use of {0} interrupted. Reason: {1}", itemName, reason);
                    // Give whatever issue encountered a chance to settle...
                    // NB: --we want the Sequence to fail when delay completes.
                    if (castResult != SpellCastResult.LineOfSight &&
                        castResult != SpellCastResult.OutOfRange &&
                        castResult != SpellCastResult.TooClose)
                    {
                        await Coroutine.Sleep(1500);
                    }

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

            QBCLog.DeveloperInfo("Use of '{0}' succeeded.", itemName);

            if (actionOnSuccessfulItemUseDelegate != null)
            {
                actionOnSuccessfulItemUseDelegate();
            }
            return(true);
        }
Пример #16
0
        /// <summary>
        ///     <para>Uses item defined by ITEMID on target defined by SELECTEDTARGET.</para>
        ///     <para>
        ///         Notes:
        ///         <list type="bullet">
        ///             <item>
        ///                 <description>
        ///                     <para>
        ///                         * It is up to the caller to assure that all preconditions have been met for
        ///                         using the item (i.e., the target is in range, the item is off cooldown, etc).
        ///                     </para>
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <description>
        ///                     <para>
        ///                         * If item use was successful, coroutine returns 'true';
        ///                         otherwise, 'false' is returned (e.g., item is not ready for use,
        ///                         item use was interrupted by combat, etc).
        ///                     </para>
        ///                 </description>
        ///             </item>
        ///             <item>
        ///                 <description>
        ///                     <para>
        ///                         * It is up to the caller to blacklist the target, or select a new target
        ///                         after successful item use.  The actionOnFailedItemUseDelegate argument
        ///                         can facilitate these activities.
        ///                     </para>
        ///                 </description>
        ///             </item>
        ///         </list>
        ///     </para>
        /// </summary>
        /// <param name="selectedTarget">The target provided should be viable.</param>
        /// <param name="itemId">The item provided should be viable, and ready for use.</param>
        /// <param name="actionOnMissingItemDelegate">This delegate will be called if the item
        /// is missing from our backpack.  This delegate may not be null.</param>
        /// <param name="actionOnFailedItemUseDelegate">If non-null, this delegate will be called
        /// if we attempted to use the item, and it was unsuccessful.  Examples include attemtping
        /// to use the item on an invalid target, or being interrupted or generally unable to use
        /// the item at this time.</param>
        /// <param name="actionOnSuccessfulItemUseDelegate">If non-null, this delegate will be called
        /// once the item has been used successfully.</param>
        /// <returns></returns>
        /// <remarks>20140305-19:01UTC, Highvoltz/chinajade</remarks>
        public static async Task <bool> UseItemOnTarget(
            int itemId,
            WoWObject selectedTarget,
            System.Action actionOnMissingItemDelegate,
            System.Action actionOnFailedItemUseDelegate     = null,
            System.Action actionOnSuccessfulItemUseDelegate = null)
        {
            // Waits for global cooldown to end to successfully use the item
            await Coroutine.Wait(500, () => !SpellManager.GlobalCooldown);

            // qualify...
            // Viable target?
            // NB: Since target may go invalid immediately upon using the item,
            // we cache its name for use in subsequent log entries.;
            if (!Query.IsViable(selectedTarget))
            {
                QBCLog.Warning("Target is not viable!");
                if (actionOnFailedItemUseDelegate != null)
                {
                    actionOnFailedItemUseDelegate();
                }
                return(false);
            }
            var targetName = selectedTarget.SafeName;

            // Is item in our bags?
            var itemToUse = Me.CarriedItems.FirstOrDefault(i => (i.Entry == itemId));

            if (!Query.IsViable(itemToUse))
            {
                QBCLog.Error("{0} is not in our bags.", Utility.GetItemNameFromId(itemId));
                if (actionOnMissingItemDelegate != null)
                {
                    actionOnMissingItemDelegate();
                }
                return(false);
            }
            var itemName = itemToUse.SafeName;

            // Need to be facing target...
            // NB: Not all items require this, but many do.
            Utility.Target(selectedTarget, true);

            // Wait for Item to be usable...
            // NB: WoWItem.Usable does not account for cooldowns.
            if (!itemToUse.Usable || (itemToUse.Cooldown > 0))
            {
                TreeRoot.StatusText =
                    string.Format(
                        "{0} is not usable, yet. (cooldown remaining: {1})",
                        itemName,
                        Utility.PrettyTime(itemToUse.CooldownTimeLeft));
                return(false);
            }

            // Notify user of intent...
            var message = string.Format("Attempting use of '{0}' on '{1}'", itemName, targetName);

            var selectedTargetAsWoWUnit = selectedTarget as WoWUnit;

            if (selectedTargetAsWoWUnit != null)
            {
                if (selectedTargetAsWoWUnit.IsDead)
                {
                    message += " (dead)";
                }
                else
                {
                    message += string.Format(" (health: {0:F1})", selectedTargetAsWoWUnit.HealthPercent);
                }
            }
            QBCLog.DeveloperInfo(message);

            // Set up 'interrupted use' detection, and use item...
            // MAINTAINER'S NOTE: Once these handlers are installed, make sure all possible exit paths from the outer
            // Sequence unhook these handlers.  I.e., if you plan on returning RunStatus.Failure, be sure to call
            // UtilityBehaviorSeq_UseItemOn_HandlersUnhook() first.

            // Set up 'interrupted use' detection, and use item...
            using (var castMonitor = SpellCastMonitor.Start(null))
            {
                itemToUse.Use(selectedTarget.Guid);

                // NB: The target or the item may not be valid after this point...
                // Some targets will go 'invalid' immediately afer interacting with them.
                // Most of the time this happens, the target is immediately and invisibly replaced with
                // an identical looking target with a different script.
                // Some items are consumed when used.
                // We must assume our target and item is no longer available for use after this point.
                await Coroutine.Sleep((int)Delay.AfterItemUse.TotalMilliseconds);

                await CastPendingSpell(selectedTarget);


                // Wait for any casting to complete...
                // NB: Some interactions or item usages take time, and the WoWclient models this as spellcasting.
                // NB: We can't test for IsCasting or IsChanneling--we must instead look for a valid spell being cast.
                //      There are some quests that require actions where the WoWclient returns 'true' for IsCasting,
                //      but there is no valid spell being cast.  We want the behavior to move on immediately in these
                //      conditions.  An example of such an interaction is removing 'tangler' vines in the Tillers
                //      daily quest area.
                var castResult = await castMonitor.GetResult();

                if (castResult != SpellCastResult.Succeeded && castResult != SpellCastResult.NoCastStarted)
                {
                    string reason = castResult == SpellCastResult.UnknownFail ? castMonitor.FailReason : castResult.ToString();

                    QBCLog.Warning("Use of {0} interrupted. Reason: {1}", itemName, reason);
                    // Give whatever issue encountered a chance to settle...
                    // NB: --we want the Sequence to fail when delay completes.
                    if (castResult != SpellCastResult.LineOfSight &&
                        castResult != SpellCastResult.OutOfRange &&
                        castResult != SpellCastResult.TooClose)
                    {
                        await Coroutine.Sleep(1500);
                    }

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

            QBCLog.DeveloperInfo("Use of '{0}' on '{1}' succeeded.", itemName, targetName);
            if (actionOnSuccessfulItemUseDelegate != null)
            {
                actionOnSuccessfulItemUseDelegate();
            }
            return(true);
        }