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

Customizing Combat Routine

cyberbot

Member
Joined
Mar 15, 2014
Messages
220
Reaction score
2
Has the guide to customize the new combat system in the beta been posted?

I really want to be able to modify simple things such as number of mobs, distance requirement etc. like before.

Thanks.
 
Last edited:
ExilebuddyRoutine is not setup like Release's Exile routine is. The design of ExilebuddyRoutine is to allow people to use some simple CR logic to assign skills based on various categories, and not have to actually change any code to do simple things like use skills in a different order, or remove conditions for casting skills because they aren't using anymore. It's basically a really simple routine that offers limited functionality in exchange of ease of use. Exile was meant to do the same, but it turned out to to be the monster it is now due to not building around a GUI (which Beta was rewritten with easier GUI stuff in mind).

As a result, if you need CR logic that does more than ExilebuddyRoutine offers, or want more control, then you will use our API to do so in a custom CR. If you look at Routines\ExampleRoutine\ExampleRoutine.cs, you'll see a simple example of how a custom CR would look in terms of casting skills in a similar manner to how Exile was setup. The only difference is that 95% of the stuff people didn't use in Exile is not present, and won't be. Small, concise CRs are going to be the preferred way to go about customizing combat for EB, as there's too much to do on a broad scale. ExilebuddyRoutine already shows this, as if you wanted to allow for more options to customize using a skills, you have to add even more GUI stuff and logic handlers, and things can quickly bloat out of control.

If you look through the ExampleRoutine code, I think you should find everything you're looking for. You just implement your linear checks of conditions in which you want to use skills, and then use the Cast helper function to cast a spell.

You can refer to ExilebuddyRoutine for various example logic like some of the new kite and avoid surround stuff, but all that code can be added to your own routine pretty easily as well.

When the guide for Routines is added, it'll mostly cover more abstract topics such as ways to design routines rather than how to do X, Y, or Z. If you have any questions on how you'd go about doing something ,feel free to ask! :) The only things we can't really help you with is more advanced logic such as trying to make the routine seem more human like and move around with spatial awareness. Stuff like that is up to the user if you want to go that route, but anything regarding how to use the API or how the IRoutine interface works is fine.
 
I would like to see an example for using curses, which are not shown in the ExampleRoutine.

Also, in the "Aura" and "Buff" types, could you add at least one example as well?

Thanks.
 
I would like to see an example for using curses, which are not shown in the ExampleRoutine.

Also, in the "Aura" and "Buff" types, could you add at least one example as well?

Thanks.

ExilebuddyRoutine contains code for the things you mentioned, but I'll add them to the ExampleRoutine as well. Aura and Buff logic is pretty much a copy/paste and you just have to change a few minor things since you're not using skillids.

Using a curse is the same as using any other skill; you just make a condition and then cast it.
Code:
var isCursable = target.IsCursable;
var isCursedWithTemporalChains = target.IsCursedWithTemporalChains;

// ...

if (isCursable && !isCursedWithTemporalChains)
{
	if (await Cast("Temporal Chains", targetId) == Coroutines.SmartCastCoroutineError.None)
		return true;
}

For aura logic, there's a SmartCastAuraCoroutine coroutine you can use:
Code:
if (type == "aura")
{
	if (!_needsToAura)
		return false;

	foreach (var aura in _auras)
	{
		// This assumes you only have one copy of the skill avaliable.
		var spell = LokiPoe.SpellManager.GetSpellByName(aura);
		if (spell == null)
			continue;

		// We need to cache this since objects might invalidate during coroutine continuation.
		var spellId = spell.Id;
		var spellName = spell.Name;

		// If we already have the aura, don't cast it again.
		if (LokiPoe.Me.HasAura(spellName))
		{
			continue;
		}

		if (!spell.CanCast(true, true))
			return true;

		var res = await Coroutines.SmartCastAuraCoroutine(spellId);
		if (res != Coroutines.SmartCastCoroutineError.None)
			Log.DebugFormat("[Logic] SmartCastCoroutine returned {0} when casting {1}.", res, spellName);

		if (res == Coroutines.SmartCastCoroutineError.None)
			return true;
	}

	_needsToAura = false;

	return false;
}

_auras is a list of aura names you want to cast, and then you have some state tracking logic via _needsToAura so you don't sit around casting aura logic each time. Since auras go away on death, you also want to reset the state flag on player death as well. I also do area changes to handle chickening, which is another case.

For buff logic, there's a SmartCastBuffCoroutine coroutine you can use:
Code:
if (type == "buff")
{
	var ret = false;

	foreach (var buff in _buffs)
	{
		// This assumes you only have one copy of the skill.
		var spell = LokiPoe.SpellManager.GetSpellByName(buff);
		if (spell == null)
			continue;

		// We need to cache this since objects might invalidate during coroutine continuation.
		var spellId = spell.Id;
		var spellName = spell.Name;

		// If we are under the effects of this buff, skip casting it.
		if (LokiPoe.Me.HasAuraFrom(spellName))
			continue;

		if (!spell.CanCast(false, true))
			continue;

		var res = await Coroutines.SmartCastBuffCoroutine(spellId);
		if (res != Coroutines.SmartCastCoroutineError.None)
			Log.DebugFormat("[Logic] SmartCastCoroutine returned {0} when casting {1}.", res, spellName);

		if (res == Coroutines.SmartCastCoroutineError.None)
			ret = true;
	}

	return ret;
}

_buffs is a list of buff names you want to cast. The buff logic runs each tick when something else doesn't execute, so if you want to only cast some things in combat, and some things only out of combat, check to see how I did that in ExilebuddyRoutine. You can also add various timer logic to make sure the routine doesn't spend too much time buffing as well, as that can happen if you use molten shell and are getting attacked by archers.

The ExampleRoutine.cs file will now look like this:
Code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Markup;
using log4net;
using Loki.Bot.Logic.Bots.ExilebuddyBot;
using Loki.Bot.v2;
using Loki.Bot.v2.Settings;
using Loki.Game;
using Loki.Game.GameData;
using Loki.Game.Objects;
using Loki.Utilities;

// ReSharper disable once CheckNamespace

namespace ExampleRoutineNamespace
{
    /// <summary> </summary>
    public class ExampleRoutine : IRoutine
    {
        private static readonly ILog Log = Logger.GetLoggerInstanceForType();

        private bool _needsToAura = true;

        // Do not implement a ctor.

        #region Implementation of IRoutine

        /// <summary>The name of the routine.</summary>
        public string Name
        {
            get { return "ExampleRoutine"; }
        }

        /// <summary>The description of the routine.</summary>
        public string Description
        {
            get { return "An example routine for Exilebuddy."; }
        }

        /// <summary>Initializes this combat routine.</summary>
        public void Initialize()
        {
            Log.DebugFormat("[ExampleRoutine] Initialize");

            // Register the settings manager for this routine with the configuration manager.
            Configuration.Instance.AddSettings(ExampleRoutineSettings.Instance);
        }

        /// <summary> The routine start callback. Do any initialization here. </summary>
        public void OnStart()
        {
            Log.DebugFormat("[ExampleRoutine] OnStart");

            GameEventManager.AreaChanged += GameEventManagerOnAreaChanged;
            GameEventManager.PlayerDied += GameEventManagerOnPlayerDied;
        }

        /// <summary> The routine tick callback. Do any update logic here. </summary>
        public void OnTick()
        {
        }

        /// <summary> The routine stop callback. Do any pre-dispose cleanup here. </summary>
        public void OnStop()
        {
            Log.DebugFormat("[ExampleRoutine] OnStop");

            GameEventManager.PlayerDied -= GameEventManagerOnPlayerDied;
            GameEventManager.AreaChanged -= GameEventManagerOnAreaChanged;
        }

        /// <summary> The bot's settings control. This will be added to the Exilebuddy Settings tab.</summary>
        public UserControl SettingsControl
        {
            get
            {
                using (var fs = new FileStream(@"Routines\ExampleRoutine\SettingsGui.xaml", FileMode.Open))
                {
                    var root = (UserControl) XamlReader.Load(fs);

                    // TODO: Your settings binding here.

                    // TODO: Your settings event handlers here.

                    return root;
                }
            }
        }

        private readonly List<string> _auras = new List<string> {"Haste", "Clarity"};
        private readonly List<string> _buffs = new List<string> { "Molten Shell" };

        /// <summary>
        /// The routine's coroutine logic to execute.
        /// </summary>
        /// <param name="type">The requested type of logic to execute.</param>
        /// <returns></returns>
        // ReSharper disable once CSharpWarnings::CS1998
        public async Task<bool> Logic(string type)
        {
            // Handle aura logic. This is just an example from ExilebuddyRoutine; you do not have to implement aura logic this way.
            if (type == "aura")
            {
                if (!_needsToAura)
                    return false;

                foreach (var aura in _auras)
                {
                    // This assumes you only have one copy of the skill avaliable.
                    var spell = LokiPoe.SpellManager.GetSpellByName(aura);
                    if (spell == null)
                        continue;

                    // We need to cache this since objects might invalidate during coroutine continuation.
                    var spellId = spell.Id;
                    var spellName = spell.Name;

                    // If we already have the aura, don't cast it again.
                    if (LokiPoe.Me.HasAura(spellName))
                    {
                        continue;
                    }

                    if (!spell.CanCast(true, true))
                        return true;

                    var res = await Coroutines.SmartCastAuraCoroutine(spellId);
                    if (res != Coroutines.SmartCastCoroutineError.None)
                        Log.DebugFormat("[Logic] SmartCastCoroutine returned {0} when casting {1}.", res, spellName);

                    if (res == Coroutines.SmartCastCoroutineError.None)
                        return true;
                }

                _needsToAura = false;

                return false;
            }

            // Handle buff logic. This is just an example from ExilebuddyRoutine; you do not have to implement buff logic this way.
            if (type == "buff")
            {
                var ret = false;

                foreach (var buff in _buffs)
                {
                    // This assumes you only have one copy of the skill.
                    var spell = LokiPoe.SpellManager.GetSpellByName(buff);
                    if (spell == null)
                        continue;

                    // We need to cache this since objects might invalidate during coroutine continuation.
                    var spellId = spell.Id;
                    var spellName = spell.Name;

                    // If we are under the effects of this buff, skip casting it.
                    if (LokiPoe.Me.HasAuraFrom(spellName))
                        continue;

                    if (!spell.CanCast(false, true))
                        continue;

                    var res = await Coroutines.SmartCastBuffCoroutine(spellId);
                    if (res != Coroutines.SmartCastCoroutineError.None)
                        Log.DebugFormat("[Logic] SmartCastCoroutine returned {0} when casting {1}.", res, spellName);

                    if (res == Coroutines.SmartCastCoroutineError.None)
                        ret = true;
                }

                return ret;
            }

            // Handle combat logic.
            if (type == "combat")
            {
                var target = BestTarget;

                if (target == null)
                    return false;

                // If the target is too far, try to move towards it some.
                if (!target.IsInLineOfSight || target.Distance > 40)
                {
                    var res = await Coroutines.ClickToMoveTowards(target.Position);
                    if (res != Coroutines.ClickToMoveTowardsError.None)
                    {
                        AreaStateCache.Current.Blacklist(target.Id, TimeSpan.FromMilliseconds(1000), "Could not ClickToMoveTowards the target.");
                    }
                    return true;
                }

                var targetId = target.Id;
                var targetName = target.Name;
                var targetDistance = target.Distance;
                var targetInLos = target.IsInLineOfSight;
                var targetRarity = target.Rarity;
                var mobsNearTarget = Utility.NumberOfMobsNear(target, 24);
                var isCursable = target.IsCursable;
                var isCursedWithTemporalChains = target.IsCursedWithTemporalChains;

                if (isCursable && !isCursedWithTemporalChains)
                {
                    if (await Cast("Temporal Chains", targetId) == Coroutines.SmartCastCoroutineError.None)
                        return true;
                }

                if (targetDistance > 24 && targetInLos)
                {
                    if (await Cast("Leap Slam", targetId) == Coroutines.SmartCastCoroutineError.None)
                        return true;
                }

                if (mobsNearTarget > 2 && targetRarity >= Rarity.Rare)
                {
                    if (await Cast("Ground Slam", targetId) == Coroutines.SmartCastCoroutineError.None)
                        return true;
                }
                
                if (targetRarity >= Rarity.Rare)
                {
                    if (await Cast("Heavy Strike", targetId) == Coroutines.SmartCastCoroutineError.None)
                        return true;
                }

                if (mobsNearTarget > 1)
                {
                    if (await Cast("Ground Slam", targetId) == Coroutines.SmartCastCoroutineError.None)
                        return true;
                }

                if (await Cast("Heavy Strike", targetId) == Coroutines.SmartCastCoroutineError.None)
                    return true;

                if (await Cast("Default Attack", targetId) == Coroutines.SmartCastCoroutineError.None)
                    return true;
            }

            return false;
        }

        #endregion

        private async Task<Coroutines.SmartCastCoroutineError> Cast(string name, int targetId)
        {
            var spell = LokiPoe.SpellManager.GetSpellByName(name);
            if (spell != null)
            {
                return await Coroutines.SmartCastCoroutine(spell.Id, targetId);
            }
            return Coroutines.SmartCastCoroutineError.NoSpell;
        }

        #region Implementation of IDisposable

        /// <summary> </summary>
        public void Dispose()
        {
            // Unregister the settings manager for this routine with the configuration manager.
            Configuration.Instance.RemoveSettings(ExampleRoutineSettings.Instance);
        }

        #endregion

        #region Override of Object

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return Name + ": " + Description;
        }

        #endregion

        /// <summary>Returns a consistent best target from the target list.</summary>
        public Monster BestTarget
        {
            get { return Targeting.Combat.Targets.FirstOrDefault() as Monster; }
        }

        private void GameEventManagerOnPlayerDied(object sender, PlayerDiedEventArgs playerDiedEventArgs)
        {
            _needsToAura = true;
        }

        private void GameEventManagerOnAreaChanged(object sender, AreaChangedEventArgs areaChangedEventArgs)
        {
            _needsToAura = true;
        }

    }
}
 
Back
Top