What's new
  • Visit Rebornbuddy
  • Visit Resources
  • Visit API Documentation
  • Visit Downloads
  • Visit Portal
  • Visit Panda Profiles
  • Visit LLamamMagic

Reference: QuestTools

xzjv

Community Developer
Joined
Mar 17, 2014
Messages
1,243
Reaction score
46
There's a lot of new stuff in QuestTools over the last few months so i'm intending to put together some documentation to help out people making profiles and plugins. This is obviously a work in progress.


Custom Conditions

Nesox was successfully convinced of the need to have additional conditions for our IF/When/While tags, so thank him for that when you get a chance. Plugins can declare their own conditions like so:

For example:

Code:
public static class CustomConditions
{
        public static void Initialize()
        {
            ScriptManager.RegisterShortcutsDefinitions((typeof(CustomConditions)));
        }


        public static bool CurrentSceneName(string sceneName)
        {
            return ZetaDia.Me.CurrentScene.Name.ToLowerInvariant().Contains(sceneName.ToLowerInvariant());          
        }
}

QuestTools Conditions

The following conditions are available to use in any profile if you have QuestTools installed. Many of them are not heavily tested and may have issues still, or be deprecated if they aren't found to be useful.

Quick Checks:

  • IsCastingOrLoading()
  • IsVendorWindowOpen()
Keywarden Related:

  • KeyAboveMedian(int actorId)
  • KeyBelowMedian(int actorId)
  • KeyAboveUpperFence(int actorId)
  • KeyBelowLowerFence(int actorId)
  • KeyAboveUpperQuartile(int actorId)
  • KeyBelowLowerQuartile(int actorId)
  • IsKeyOutlier(int actorId)
  • HighestKeyCountId(int id)
  • LowestKeyCountId(int id)
  • IsAnyKeyOutlier()

Rifts:

  • CurrentWave(int waveNumber)

Bot/Hero:
  • CurrentSceneName(string sceneName)
  • CurrentDifficulty(string difficulty)
  • CurrentDifficultyLessThan(string difficulty)
  • CurrentDifficultyGreaterThan(string difficulty)
  • CurrentClass(string actorClass)
  • CurrentHeroLevel(int level)

Inventory:


  • ItemCount(int actorId)
  • ItemCountGreaterThan(int actorId, int amount)
  • ItemCountLessThan(int actorId, int amount)

Actors:
  • ActorIsAlive(int actorId)
  • ActorFound(int actorId)
  • ActorExistsNearMe(int actorId, float range)
  • HasBeenOperated(int actorId)
  • CurrentAnimation(int actorId, string animationName)


Profile Management:

  • ProfileSetting(string key)
  • UsedOnce(string id)



IsCastingOrLoading()


This condition will be true if the bot is not valid, loading a level, casting / channeling or otherwise busy.

Code:
    <!-- Weeping Hollow Waypoint -->
    <While condition="CurrentLevelAreaId != 19954 and CurrentWorldId != 135193>            
        <UseWaypoint waypointNumber="6" questId="1"/>    
        <WaitWhile condition="IsCastingOrLoading()" />    
    </While>

Current Difficulty

  • CurrentDifficulty(string difficulty)
  • CurrentDifficultyLessThan(string difficulty)
  • CurrentDifficultyGreaterThan(string difficulty)

Code:
    <If condition="CurrentDifficultyLessThan('Torment1')">
        <LogMessage quest="1" output="There are no keywardens below Torment Difficulty!" />
        <Command name="SetDifficulty" value="Torment1" />    
        <LeaveGame questId="1" reason="to change difficulty" stayInParty="False" />    
    </If>

Item Count

  • ItemCount(int actorId)
  • ItemCountGreaterThan(int actorId, int amount)
  • ItemCountLessThan(int actorId, int amount)

These conditions returns the sum of both backpack and stash counts for the Item.

Code:
    <If condition="ItemCount(202124) <= 50">
        <LogMessage quest="1" output="You less than 50 of this Item!" />
    </If>

ActorFound

  • ActorFound(int actorId)

Actor found is special because it looks at the QuestTools ActorHistory instead of at the current list of actors in DemonBuddy. If the actor has been seen at all since the last world change it will return True. This means that even if you get knocked out of range of the monster or it uses an ability that makes it disappear the profile can still know it has been found.

Code:
    <When condition="ActorFound(256054) and CurrentLevelAreaId == 109538" name="A4 Keywarden">
        <LogMessage questId="1" output=">Found A4 Keywarden!" />
    </When>

CurrentAnimation

  • CurrentAnimation(int actorId, string animationName)

Returns true if the actor exists and is currently using the specified animation. To discover what animations are available for an actor you can use Trinity debug logging in the debug tab. There is also a debug message every time CurrentAnimation condition is used which shows the animation name.

Code:
    <If condition="CurrentAnimation(364715,'OpenWorld_LootRunObelisk_B_dead_02')">
        <LogMessage output="Rift or Trial Portal is Open" />
    </If>

ProfileSetting

  • ProfileSetting(string key)


Provides way for profiles split over multiple files to be aware of the same data. This can replace the 'Zeta.Bot.Settings.GlobalSettings.Instance.LastProfile.Contains()' hack. Profile settings are reset on bot start event.

Saving a setting: You can choose any string for the name and value parameters.
Code:
    <ProfileSetting name="KeyMode" value="Zerg" />

Reading a setting:
Code:
    <If condition="ProfileSetting('KeyMode') == 'Zerg'">
        <LogMessage questId="1" output=">Mode is set to zerg!" />
    </If>


When Tag


The 'When' tag allows you to execute a special part of your profile whenever a condition is met.

This tag has two main components:

  • The Condition to be satisfied
  • The Child tags embedded within.

When DemonBuddy finds this tag it will do the following:
a) Read and store all the child tags and the condition
b) Continue executing the rest of the profile.
c) Check continuously to see if the condition is met.​

When the condition is evaluated to True, the bot starts executing the stored child tags. It does this with priority over whatever else the bot may have been doing.

A Simple Example: View attachment WhenTest1.xml

This profile goes to StoneFort, then starts exploring, when the Actor (an environment pillar with fire on top of it) comes within range, the When tag will move towards it and then wait for five seconds, then continue exploring.

Code:
<?xml version="1.0" encoding="utf-8"?>
<Profile>
  <Name>WhenTest1</Name>
  <KillMonsters>True</KillMonsters>
  <PickupLoot>True</PickupLoot>
  <GameParams act="OpenWorld" resumeFromSave="False" isPrivate="True" numGames="-1" />
  <Order>
    <!-- Environment a3dun_rmpt_Coal_Piles-845 (157012) -->
    <When condition="ActorExistsAt(157012,4159,4031,9,50)">
      <LogMessage questId="1" output="> Found Environment Actor a3dun_rmpt_Coal_Piles" />    
      <MoveToActor questId="1" pathPrecision="5" actorId="157012" />        
      <WaitTimer questId="1" stepId="2" waitTime="5000" />    
    </When>  
    <While condition="CurrentLevelAreaId != 93173">     
      <UseWaypoint questId="1" waypointNumber="27" />
      <WaitWhile questId="1" condition="IsCastingOrLoading()" />
    </While>    
    <ExploreDungeon questId="1" direction="Any" until="FullyExplored" 
      routeMode="WeightedNearestMinimapUnvisited" boxSize="40" 
      pathPrecision="60" boxTolerance="0.1" />        
  </Order>
</Profile>

Note the order of tags in the profile; the When tag is positioned before the teleport to stonefort and before starting to explore, yet it occurs after arriving at stonefort and while exploring. When tags can be placed anywhere in the profile, but QuestTools will only start checking the condition after the tag has been encountered.

Supported Tags

Currently most of the default and QuestTools tags are supported. If you find one that doesn't work please report it in the Trinity/QuestTools section of the forum.

To be supported your tag must implement the interface IEnhancedProfileBehavior, which defines a set of methods that are needed to prepare a ProfileBehavior to be executed by the BotBehaviorQueue. Take a look at the tags within QuestTools plugin if you need some examples.

Code:
        #region IEnhancedProfileBehavior

        public void Update()
        {
            UpdateBehavior();
        }

        public void Start()
        {
            OnStart();
        }

        public void Done()
        {
            _isDone = true;
        }

        #endregion


Additional Tag Options

Persist:

By default all when tags are cleared when a profile is loaded (or reloaded). Setting persist to True will cause a When tag to be remembered and stay active after loading a new profile.

Repeat:


By default when tags are only executed once. Setting repeat to True will cause a When tag to be executed again and again so long as the condition is met.

Name

Adding a unique name is useful for identification purposes as the name will be shown in logging messages when it's executing.

Another Example: View attachment WhenTest2.xml

In this example the bot will go to Stonefort, and then move towards and away from the waypoint repeatedly, to illustrate some of the above options.

Code:
<?xml version="1.0" encoding="utf-8"?>
<Profile>
  <Name>WhenTest2</Name>
  <KillMonsters>True</KillMonsters>
  <PickupLoot>True</PickupLoot>
  <GameParams act="OpenWorld" resumeFromSave="False" isPrivate="True" numGames="-1" />
  <Order>
    <!-- When close to Waypoint (6442) -->
    <When condition="ActorExistsNearMe(6442,20)" name="MoveAway" repeat="True">            
      <LogMessage questId="1" output="> Moving away from waypoint!" />    
      <SafeMoveTo questId="312429" stepId="2" x="4274" y="4210" z="-25" pathPrecision="5" />            
      <WaitTimer questId="1" stepId="2" waitTime="2000" />    
    </When>  
    <!-- When waypoint (6442) is found within 300 yards  -->
    <When condition="ActorExistsNearMe(6442,300)" name="MoveTowards" repeat="True">        
      <LogMessage questId="1" output="> Oh look there's a waypoint!" />    
      <MoveToActor questId="1" pathPrecision="5" actorId="6442" interactAttempts="1" />        
      <WaitTimer questId="1" stepId="2" waitTime="2000" />    
    </When>          
    <While condition="CurrentLevelAreaId != 93173">    
      <UseWaypoint questId="1" waypointNumber="27" />
      <WaitWhile questId="1" condition="IsCastingOrLoading()" />
    </While>
    <WaitTimer questId="1" stepId="2" waitTime="20000" />            
  </Order>
</Profile>


QuestTools Plugin

Creating plugins is a bit weird in my opinion and more difficult than it needs to be. inheriting your plugin from QuestToolsPlugin instead of IPlugin makes things a little easier and tidier.

Example 1:
Code:
namespace KadalaSpree
{
    public class KadalaPlugin : QuestToolsPlugin
    {
        private KadalaPlugin()
        {
            Name = "Kadala";
            Version = new Version(2, 5, 4);
            Author = "xzjv";
            Description = "Buys stuff from kadala";
            SettingsClass = KadalaSpreeSettings.Instance;
            SettingsXaml = "Kadala.Settings.xaml";
        }

        public override void OnPulse()
        {
            // Do some stuff here
        }
    }
}

Things to note:
  • You don't have to override the methods that you don't care about with empty methods. Just use the ones you need.
  • There is a wrapper for the dirty work of providing a settings page.
    • Specify the file name of your .XAML based UI
    • Specify the instance of your settings class.

Example 2: No Settings Specified

Code:
namespace Arcanum
{
    public class Arcanum : QuestToolsPlugin
    {
        private Arcanum()
        {
            Name = "Arcanum";
            Version = new Version(1, 0, 7);
            Author = "xzjv";
            Description = "Enchanting";
        }


        public override void OnEnabled()
        {
            Logger.Log("{0} v{1} by {2} Enabled!", Name, Version, Author);
            UI.Attach();
        }


        public override void OnDisabled()
        {
            Logger.Log("{0} v{1} by {2} Disabled!", Name, Version, Author);
            UI.Detach();            
        }
    }
}

Take a look at KadalaSpree for a complete example of a plugin using this method. https://www.thebuddyforum.com/demon...plugin-kadalaspree-stand-gambling-plugin.html
 
Last edited:
BotBehaviorQueue

Have you ever been making a plugin and wanted to run a simple behavior on pulse? well now you can. BotBehaviorQueue as the name might suggest, is a queue for behaviors to be run in the 'BotBehavior' hook.

To use it you must Include QuestTools in the plugin (your plugin folder must start with a letter after Q in the alphabet, i prefix all mine with the letter z)
Code:
using QuestTools;
using QuestTools.Helpers;
using QuestTools.ProfileTags.Complex;
using QuestTools.ProfileTags.Movement;

Example calling BotBehaviorQueue.Queue() method.
Code:
        private DateTime _lastPulseTime = DateTime.MinValue;

        public override void OnPulse()
        {
            if (DateTime.UtcNow.Subtract(_lastPulseTime).TotalMilliseconds < 1000)
                return;


            if (!Player.IsValid || !BotBehaviorQueue.IsEnabled)
                return;


            _lastPulseTime = DateTime.UtcNow;


            if (!ShouldGamble) 
                return;


            BotBehaviorQueue.Queue(GambleBehavior, "KadalaSpree");
        }

Overloads:

  • public static void Queue(QuestTools.Helpers.QueueItem item)
  • public static void Queue(System.Collections.Generic.IEnumerable<QueueItem> items)
  • public static void Queue(System.Collections.Generic.IEnumerable<ProfileBehavior> profileBehaviors, [string name = ""])
  • public static void Queue(Zeta.Bot.Profile.ProfileBehavior profileBehavior, QuestTools.Helpers.BotBehaviorQueue.ShouldRunCondition condition)
  • public static void Queue(Zeta.Bot.Profile.ProfileBehavior behavior, [string name = ""])
  • public static void Queue(System.Collections.Generic.IEnumerable<ProfileBehavior> profileBehaviors, QuestTools.Helpers.BotBehaviorQueue.ShouldRunCondition condition, [string name = ""])


Using the Queue


  1. A list of ProfileBehaviors is Queued. These are the tags just like you would use in a profile.
    Code:
            public List<ProfileBehavior> GambleBehavior = new List<ProfileBehavior>
            {       
                new LogMessageTag
                {
                    Output = "Started Gambling Sequence"
                },
                new SafeMoveToTag
                {
                    SetPositionOnStart = ret => ProfileUtils.TownApproachVector
                },
                new MoveToActorTag
                {
                    ActorId = (int)SNOActor.X1_RandomItemNPC,
                },
                new ActionTag
                {
                    IsDoneWhen = ret => !ShouldBuyItems,
                    Action = ret => BuyItems()
                },
                new LogMessageTag
                {
                    Output = "Finished Gambling!"
                },
            };

    Another Example: Ending a Trial on Certain Wave.

    Code:
                    var endTrialSequence = new List<ProfileBehavior>
                    {
                        new SafeMoveToTag()
                        {
                            PathPrecision = 5,
                            PathPointLimit = 250,
                            X = 393,
                            Y = 237,
                            Z = -11
                        },
                        new TownPortalTag(),
                        new CompositeTag()
                        {
                            IsDoneWhen = ret => Zeta.Bot.ConditionParser.IsActiveQuestAndStep(405695,9),
                            Composite = new Action(ret =>
                            {
                                Logger.Log("Waiting for Trial to Finish...");
                                return RunStatus.Success;
                            })
                        }
                    };
                    BotBehaviorQueue.Queue(endTrialSequence);
  2. Duplicate queue attempts are discarded (unique based on tags + name), this means you could queue something every tick OnPulse() without worrying (aside from performance implications)
    .
  3. Use Verbose Logging for debugging. It will spam a lot of information about what is happening in the queue.
    .
  4. Time of ProfileBehavior Properties. When you create a list of ProfileBehaviors with an object initializer like the examples above, all the settings are fixed at that point in time. Anything you want to set dynamically like the position of a monster to be moved to, needs to be set when the tag is actually executed with some sort of delegate.

    For example I modified SafeMoveTo to set the position when the tag starts.
    Code:
                new SafeMoveToTag
                {
                    SetPositionOnStart = ret => ProfileUtils.TownApproachVector
                },

    Alternatively you could use the events on the QueueItem class:


  • [*=1] public QueueItemDelegate OnNodeStart { get; set; }
    [*=1] public QueueItemDelegate OnNodeDone { get; set; }
    [*=1] public QueueItemDelegate OnDone { get; set; }
    [*=1] public QueueItemDelegate OnStart { get; set; }

Example of Queue() with a QueueItem from QuestTools.ProfileTags.Complex.WhenTag

Code:
            BotBehaviorQueue.Queue(new QueueItem
            {
                Condition = ret => ScriptManager.GetCondition(Condition).Invoke(),
                Name = Name,
                Nodes = Body,
                Persist = Persist,
                Repeat = Repeat,
            });

so something like this:

Code:
            BotBehaviorQueue.Queue(new QueueItem
            {
                Name = Name,
                Nodes = Body,
                OnNodeStart = ret =>
                {
                    // Check if tag is the one you need
                        // Do something
                },
                OnStart = ret =>
                {
                    // Do something before any tags are run
                }
            });



Special Tags


Tags created specifically for putting into the BotBehaviorQueue from plugins.

ActionTag


Provides an easy way to execute some arbitrary code. It requires a bool value to be returned, false will loop and true will end.

You can call an anonymous delegate in the constructor like this:
Code:
            new ActionTag(ret =>
            {
                GameUI.CloseVendorWindow();
                return true;
            }),

Or you can specify the properties explicitly:
  • Action - A bool delegate to be run. Same as you would put into the constructor.
  • IsDoneWhen - A bool delegate that when true will cause the tag to end.

Code:
            new ActionTag
            {
                IsDoneWhen = ret => !ShouldBuyItems,
                Action = ret => BuyItems()
            },
IF/WHILE tags


  • EnhancedIfTag
  • EnhancedWhileTag

Provides a way to specify the child nodes and condition.
Code:
        public static ProfileBehavior[] UseWaypointToAct1()
        {
            return new ProfileBehavior[]
            {
                new UseWaypointTag
                {
                    QuestId = 1,
                    WaypointNumber = 0
                },
                new WaitTimerTag
                {
                    QuestId = 1,
                    WaitTime = 4000
                }
            };
        }

        public List<ProfileBehavior> GambleBehavior = new List<ProfileBehavior>
        {       
            new LogMessageTag
            {
                Output = "Started Gambling Sequence"
            },
            new EnhancedIfTag(
                ret => !ProfileUtils.CanPathToLocation(ProfileUtils.TownApproachVector),   
                UseWaypointToAct1()
            ),
        };

CompositeTag


If you have a normal composite that you want to just run, this wrapper tag converts it into a valid profile behavior so that you can include it in your list to be Queued.

Code:
new CompositeTag(ret => Zeta.Bot.CommonBehaviors.CreateUseTownPortal())

You can also create the behavior inline and it handles the behavior normally - so it will loop/end based on RunStatus.

Specifying 'IsDoneWhen' will force it to end when true (basically setting _isDone via delegate).

Code:
    new CompositeTag()    {
        IsDoneWhen = ret => Zeta.Bot.ConditionParser.IsActiveQuestAndStep(405695,9),
        Composite = new Action(ret =>
        {
            Logger.Log("Waiting for Trial to Finish...");
            return RunStatus.Success;
        })
    }
.


Example Projects


KadalaSpree : - https://www.thebuddyforum.com/demon...plugin-kadalaspree-stand-gambling-plugin.html

VaultRunner by SmurfX : - https://www.thebuddyforum.com/demon...ta-vaultrunner-weedrunner-goblin-plugins.html
 
Last edited:
Added a bunch of stuff on BotBehaviorQueue and QuestToolsPlugin
 
These are awesome. It's a pity this post gets lost in time, would be nice if it was a sticky.

I have a small requests.

Could you please change the condition in TownRunTag.GetFreeSlots() to this, otherwise bot just waits while greater rift closing countdown. (Still doesn't solve the entire problem, townrun gets initiated but only identifies items, checking the reason)
Code:
            bool participatingInTieredLootRun = ZetaDia.ActInfo.AllQuests.Any(
                q => q.State == QuestState.InProgress && ((
                    q.QuestSNO == 337492 && (q.QuestStep == 13 || q.QuestStep == 16)) || 
                    q.QuestSNO == 405695
                    ));

Thank you very much :)
 
Last edited:
Back
Top