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

FollowBot for ExileBuddy (Basic Beta Release)

Thanks for your patience.
I will recode this project under a new name, its atm under development so please stand by.
 
Yeah, but its really complicated right now, because EB changed the complete code of the Bot so none of my snippets are working.
 
Yeah, but its really complicated right now, because EB changed the complete code of the Bot so none of my snippets are working.

The new code added and being added is designed to help developers like you do things a lot easier than before! It's somewhat of an inconvenience, but since EB dev really hasn't taken off community wise, mostly because of how the code is always changing, we can at least progress towards something that will be future proof and work well in the long run.

The future of EB development is going to be coroutine based and not behavior tree based, so now's a good point to start switching over. I'm not sure how possible it'll be to run the coroutine logic from BTs, but we'll try to add some wrappers for compatibility later after all the coroutine stuff is done. The reason for the switch is purely technical; the way this game works, trying to do what we need in a BT is very complex and messy. It requires a ton more code and state tracking, which makes it more likely for things to break and become hard to maintain.

There's a lot going on behind the scenes right now transiting our bot base into something more modular so people can achieve the same things the grindbot can do, without having to have all of the code. Ideally, we provide users with a bunch of high level functions that greatly simplifies tasks, so users can just use those, and not have to worry about the nitty gritty details. This is important, because as bugs are found or logic has to change, we can change that stuff, and your end user code stays the same. We can't really help you rewrite all of your project just yet, because like I mentioned a lot of things are being changed and added, but we'll do our best to help you transition to the new code and add useful functions to our shared code that you can make use of to make life easier.

A few examples are in the Community Developer forum, but a lot of the things you had a lot of code to do before, can easily be wrapped up into common coroutines everyone can make use of. Here's an example of a bot that will do some basic stash orgainization by withdrawing all skill gems not on page 4, and then depositing them on page 4.

OrganizedStashBot.cs
Code:
using System.Collections;
using System.Linq;
using System.Windows;
using Buddy.Coroutines;
using log4net;
using Loki.Bot.v2;
using Loki.Game;
using Loki.Game.GameData;
using Loki.Game.Inventory;
using Loki.Game.NativeWrappers;
using Loki.TreeSharp;
using Loki.Utilities;
using Action = Loki.TreeSharp.Action;

namespace Loki.Bot.Logic.Bots.ApiTest
{
    /// <summary></summary>
    public class OrganizedStashBot
#if DEBUG
 : IBot
#endif
    {
        /// <summary> </summary>
        public OrganizedStashBot()
        {
        }

        /// <summary></summary>
        private static readonly ILog Log = Logger.GetLoggerInstanceForType();

        /// <summary>The main coroutine.</summary>
        private Coroutine _coroutine;

        /// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. </summary>
        public void Dispose()
        {
        }

        /// <summary>Whether or not this bot requires game input to happen. </summary>
        public bool RequiresGameInput
        {
            get { return true; }
        }

        /// <summary> Gets the name of this bot. </summary>
        public string Name
        {
            get { return "OrganizedStashBot"; }
        }

        /// <summary> Gets the description of this bot. </summary>
        public string Description
        {
            get { return "Coroutine testing code."; }
        }

        /// <summary> Gets the configuration window for this bot. </summary>
        public Window ConfigWindow
        {
            get { return null; }
        }

        /// <summary> Gets the logic for this bot. </summary>
        public Composite Logic { get; private set; }

        /// <summary>Which items to pulse when this bot runs.</summary>
        public PulseFlags PulseFlags
        {
            get { return PulseFlags.All; }
        }

        /// <summary> Starts this bot. Do any initialization here. </summary>
        public void Start()
        {
            Logic = new Action(ret => RunStatus.Success);

            _coroutine = new Coroutine(Main());

            _needsStashContents = true;
        }

        /// <summary> Stops this bot. Do any pre-dispose cleanup here. </summary>
        public void Stop()
        {
            if (_coroutine != null)
            {
                _coroutine.Dispose();
                _coroutine = null;
            }
        }

        /// <summary> Pulses the bot. Do any update logic here. </summary>
        public void Pulse()
        {
            // Check to see if the coroutine is finished. If it is, stop the bot.
            if (_coroutine.Status == CoroutineStatus.RanToCompletion || _coroutine.Status == CoroutineStatus.Faulted)
            {
                var msg = string.Format("The bot coroutine has finished {0}",
                    _coroutine.Status == CoroutineStatus.RanToCompletion ? "successfully." : "due to an error.");

                Log.DebugFormat(msg);

                BotMain.Stop(msg);

                return;
            }

            // Otherwise, resume it.
            if (_coroutine.Status == CoroutineStatus.Runnable)
            {
                object value;
                while (_coroutine.Resume(out value) && value != LokiCoroutine.EndTick)
                {
                }
            }
        }

        /// <summary> </summary>
        private bool _needsStashContents = true;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="tab"></param>
        /// <param name="item"></param>
        /// <param name="user"></param>
        /// <returns></returns>
        private bool WithdrawItemsDelegate(InventoryTab tab, InventoryItem item, object user)
        {
            // We only want to move around skill gems for this example.
            if (item.Item.Rarity != Rarity.Gem)
            {
                return false;
            }

            // We want to move all skill gems to "4", so if we are on "4", don't withdraw it.
            if (tab.DisplayName == "4")
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <param name="user"></param>
        /// <returns></returns>
        private bool ShouldStashItem(InventoryItem item, object user)
        {
            // Since we'll eventually change this code for more stuff, process each item type as needed.
            if (item.Item.Rarity == Rarity.Gem)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="tab"></param>
        /// <param name="item"></param>
        /// <param name="user"></param>
        /// <returns></returns>
        private bool BestStashTabForItem(InventoryTab tab, InventoryItem item, object user)
        {
            var name = item.Name;

            // Since we'll eventually change this code for more stuff, process each item type as needed.
            if (item.Item.Rarity == Rarity.Gem)
            {
                // All gems go to tab 4.
                if (tab.DisplayName == "4")
                    return true;
            }
            
            return false;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private IEnumerator Main()
        {
            while (true)
            {
                yield return LokiCoroutine.EndTick;

                // Don't do anything if we are not in town.
                if (!LokiPoe.ObjectManager.Me.IsInTown)
                    continue;

                // If Stash is not open yet, go to it and open it.
                if (!LokiPoe.Gui.IsStashWindowOpen)
                {
                    Log.DebugFormat("[Main] The Stash window is not open. Now moving to Stash to open it.");

                    yield return Coroutines.OpenStashCoroutine();

                    var res = (bool)Coroutine.Current.LastResult;
                    if (!res)
                    {
                        Log.ErrorFormat("[Main] OpenStashCoroutine failed. Stopping the bot.");
                        BotMain.Stop("An unrecoverable error was encountered.");
                        break;
                    }

                    continue;
                }

                // If we haven't requested the stash contents, request them. If we don't actually need to, then the coroutine will finish
                // quickly, as it doesn't have to request the contents again.
                if (_needsStashContents)
                {
                    Log.DebugFormat("[Main] We need to request the contents of Stash.");

                    yield return Coroutines.RequestStashContentsCoroutine();

                    var rsccerr = (Coroutines.RequestStashContentsCoroutineError)Coroutine.Current.LastResult;
                    if (rsccerr != Coroutines.RequestStashContentsCoroutineError.None)
                    {
                        Log.ErrorFormat("[Main] RequestStashContentsCoroutine returned {0}. Stopping the bot.", rsccerr);
                        BotMain.Stop("An unrecoverable error was encountered.");
                        break;
                    }

                    _needsStashContents = false;
                }

                var loop = false;

                // First, we want to withdraw as many items as possible to organize.
                yield return Coroutines.WithdrawItemsCoroutine(WithdrawItemsDelegate);
                var ret = (Coroutines.WithdrawItemsCoroutineError)Coroutine.Current.LastResult;
                if (ret == Coroutines.WithdrawItemsCoroutineError.OpenStashCoroutineFailed)
                {
                    Log.ErrorFormat("[Main] WithdrawItemsCoroutine returned {0}. Stopping the bot.", ret);
                    BotMain.Stop("An unrecoverable error was encountered.");
                    break;
                }

                if (ret == Coroutines.WithdrawItemsCoroutineError.CouldNotFitAllItems)
                {
                    loop = true;
                }

                // Second, we want to stash them where they belong.
                yield return Coroutines.StashItemsCoroutine(ShouldStashItem, BestStashTabForItem);
                var sicerr = (Coroutines.StashItemsCoroutineError)Coroutine.Current.LastResult;
                Log.DebugFormat("[Main] StashItemsCoroutine returned {0}.", sicerr);

                // If we cannot stash any more items where we want, stop the bot so the user can adjust the code.
                if (sicerr == Coroutines.StashItemsCoroutineError.CouldNotFitAllItems)
                {
                    Log.ErrorFormat("[Main] StashItemsCoroutine returned {0}. Stopping the bot.", ret);
                    BotMain.Stop("An unrecoverable error was encountered.");
                    break;
                }

                // We should be done, so break out!
                if (!loop)
                    break;
            }
            // ReSharper disable once FunctionNeverReturns
        }
    }
}

It might look foreign and confusing at first, but that's just from it being new. What matters is the actual code required to do things, looking past error handling:

* To open stash in town: yield return Coroutines.OpenStashCoroutine();

* To request all stash contents once stash is opened: yield return Coroutines.RequestStashContentsCoroutine();

* To withdraw various items from stash (as per a user function): yield return Coroutines.WithdrawItemsCoroutine(WithdrawItemsDelegate);

* To stash various items (as per a user function): yield return Coroutines.StashItemsCoroutine(ShouldStashItem, BestStashTabForItem);

All of those things that would require a lot of code before are now simplified into one line calls where you can specify what the function does with a user function where applicable.

We can easily add in things that wrap party stuff, so you could have something like: yield return Coroutines.JoinParty(ShouldJoinParty); where ShouldJoinParty is a function that would be called for all the pending party invites to check things based on members, names, or any other of the available information.

Some of the new input code not shown yet makes tasks like opening chests or picking up items much simpler as well. For example, without error handling the two calls are simply:

yield return Coroutines.ClickToMoveTo(nearbyItem.Position);
yield return Coroutines.InteractWithObject(nearbyItem);

And that's it. Those same two functions can be used for anything else that requires interaction, so portals, quest objects, area transitions, and anything else other than monsters. Skill stuff is not rewritten yet, but it too will get some new logic down the road.

Anyways, the code is still changing, but it's being developed with users in mind so it'll be well documented and a lot simpler to use compared to what we have had since the first release. We're starting with the actual bot functions first, so a lot of the "fun" things like follow bots and party bots aren't quite ready to be done by us yet, but if you have any requests for being able to do certain things, just post and we can start adding them as we go along. That goes for anyone interested in writing stuff with EB as well. We want to have something that works a lot better than what we have had, and is really easy to use and fun to play with.
 
Can't wait. Been waiting for something just like this.
 
Back
Top