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

How to edit Default Routine to Support Dual Totem

tozededao

Community Developer
Joined
Jan 15, 2010
Messages
1,225
Reaction score
5
How to edit Default Routine to Support Dual Totem - .cs added as attachment

UPDATED FOR #896
http://pastebin.com/x3QCtLFJ or Attachment


Important



If you are using Shockwave Totem and you want it to be cast right on monsters position.

Code:
var err1 = LokiPoe.InGameState.SkillBarPanel.UseAt(_totemSlot, true, Utility.CalculatePointAtDistanceAfterStart(myPos, cachedPosition,cachedDistance -3));


If you are using any other totem and you want it to keep some distance from enemies

Code:
var err1 = LokiPoe.InGameState.SkillBarPanel.UseAt(_totemSlot, true, Utility.CalculatePointAtDistanceAfterStart(myPos, cachedPosition,cachedDistance / 2));


Credits to Ben's Templar CR and Pushedx.
 

Attachments

Last edited:
Looks good! I started working on some example logic for a smaller routine for dual totem last night based on what you mentioned, but I ran into a fun client issue with casting totems. In short, it's possible to try and cast a totem too fast, and the client simply doesn't do anything. If you don't use the previous Beta's style of checking for casting, the totems will never cast in the correct place.

Here's that basic CR, which you should be able to see the casting behavior is correct for sandwiching mobs between totems.

Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Controls;
using Buddy.Coroutines;
using log4net;
using Loki.Bot;
using Loki.Bot.Pathfinding;
using Loki.Bot.v3;
using Loki.Game;
using Loki.Game.Objects;
using Loki.Utilities;

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

        private int _currentLeashRange = -1;
        private bool _needsUpdate;

        // Do not implement a ctor and do stuff in it.

        #region Implementation of IBase

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

        #endregion

        #region Implementation of IDisposable

        /// <summary> </summary>
        public void Dispose()
        {
        }

        #endregion

        #region Implementation of IAuthored

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

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

        /// <summary>
        /// The author of this object.
        /// </summary>
        public string Author
        {
            get { return "Bossland GmbH"; }
        }

        /// <summary>
        /// The version of this routone.
        /// </summary>
        public Version Version
        {
            get { return new Version(0, 0, 1, 1); }
        }

        #endregion

        #region Implementation of IRunnable

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

            _needsUpdate = true;

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

        /// <summary> The routine tick callback. Do any update logic here. </summary>
        public void Tick()
        {
            if (!LokiPoe.IsInGame)
                return;

            if (_needsUpdate)
            {
                _needsUpdate = false;
            }
        }

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

            GameEventManager.AreaChanged -= GameEventManagerOnAreaChanged;
        }

        #endregion

        #region Implementation of IConfigurable

        /// <summary> The bot's settings control. This will be added to the Exilebuddy Settings tab.</summary>
        public UserControl Control
        {
            get { return null; }
        }

        /// <summary>The settings object. This will be registered in the current configuration.</summary>
        public JsonSettings Settings
        {
            get { return null; }
        }

        #endregion

        #region Implementation of IRoutine

        /// <summary>
        /// Sends data to the routine with the associated name.
        /// </summary>
        /// <param name="name">The name of the configuration.</param>
        /// <param name="param">The data passed for the configuration.</param>
        public void SetConfiguration(string name, params object[] param)
        {
            if (name == "leash")
            {
                _currentLeashRange = (int)param[0];
            }
        }

        /// <summary>
        /// Requests data from the routine with the associated name.
        /// </summary>
        /// <param name="name">The name of the configuration.</param>
        /// <returns>Data from the routine.</returns>
        public object GetConfiguration(string name)
        {
            return null;
        }

        /// <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)
        {
            if (type == "combat")
            {
               
                var myPos = LokiPoe.Me.Position;

                // TODO: _currentLeashRange of -1 means we need to use a cached location system to prevent back and forth issues of mobs despawning.

                var monsters =
                    LokiPoe.ObjectManager.GetObjectsByType<Monster>()
                        .Where(
                            m =>
                                !AreaStateCache.Current.IsBlacklisted(m.Id) &&
                                !m.CannotDie &&
                                m.Distance < (_currentLeashRange != -1 ? _currentLeashRange : 50) &&
                                m.IsActive)
                        .OrderBy(m => m.Distance).ToList();

                // No monsters, we can execute non-critical combat logic, like buffs, auras, etc...
                // For this example, just going to continue executing bot logic.
                if (monsters.Count == 0)
                {
                    return false;
                }

                // This is pretty important. Otherrwise, components can go invalid and exceptions are thrown.
                var bestTarget = monsters.First();
                var cachedPosition = bestTarget.Position;
                var cachedId = bestTarget.Id;
                var cachedName = bestTarget.Name;
                
                var canSee = ExilePather.CanObjectSee(LokiPoe.Me, bestTarget);
                var pathDistance = ExilePather.PathDistance(myPos, cachedPosition);
                var blockedByDoor = Utility.ClosedDoorBetween(LokiPoe.Me, bestTarget);

                if (pathDistance.CompareTo(float.MaxValue) == 0)
                {
                    Log.ErrorFormat(
                        "[Logic] Could not determine the path distance to the best target. Now blacklisting it.");
                    AreaStateCache.Current.Blacklist(cachedId, TimeSpan.FromMinutes(1), "Unable to pathfind to.");
                    return true;
                }

                // Prevent combat loops from happening.
                if (pathDistance > 30)
                    return false;

                if (!canSee || blockedByDoor)
                {
                    Log.InfoFormat(
                        "[Logic] Now moving towards the monster {0} because [canSee: {1}][pathDistance: {2}][blockedByDoor: {3}]",
                        cachedName, canSee, pathDistance, blockedByDoor);

                    if (!Functions.MoveTowards(cachedPosition))
                    {
                    }

                    return true;
                }

                var totemSkill = LokiPoe.InGameState.SkillBarPanel.Slot2;
                var totmes = totemSkill.DeployedObjects.ToList();

                var oldestTotem = totmes.Count > 0 ? totmes[0] : null;
                var newestTotem = totmes.Count > 1 ? totmes[1] : null;

                var frontPosition = ExilePather.WalkablePositionFor(Utility.CalculatePointAtDistanceBeforeEnd(myPos, cachedPosition, 10), 5);
                var backPosition = ExilePather.WalkablePositionFor(Utility.CalculatePointAtDistanceAfterEnd(myPos, cachedPosition, 10), 5);

                if (oldestTotem == null || newestTotem == null || oldestTotem.Position.Distance(frontPosition) > 15 ||
                    newestTotem.Position.Distance(backPosition) > 15)
                {
                    await Coroutines.FinishCurrentAction();
                    await Coroutine.Sleep(Utility.LatencySafeValue(100));

                    while (!LokiPoe.Me.HasCurrentAction)
                    {
                        LokiPoe.InGameState.SkillBarPanel.UseAt(totemSkill.Slot, true, frontPosition);
                        await Coroutine.Sleep(Utility.LatencySafeValue(25));
                    }
                    while (LokiPoe.Me.HasCurrentAction)
                    {
                        await Coroutine.Sleep(Utility.LatencySafeValue(25));
                    }
                    await Coroutine.Sleep(Utility.LatencySafeValue(100));

                    while (!LokiPoe.Me.HasCurrentAction)
                    {
                        LokiPoe.InGameState.SkillBarPanel.UseAt(totemSkill.Slot, true, backPosition);
                        await Coroutine.Sleep(Utility.LatencySafeValue(25));
                    }
                    while (LokiPoe.Me.HasCurrentAction)
                    {
                        await Coroutine.Sleep(Utility.LatencySafeValue(25));
                    }
                    await Coroutine.Sleep(Utility.LatencySafeValue(100));
                }

                return true;
            }

            return false;
        }

        #endregion

        #region Override of Object

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

        #endregion
        
        private void GameEventManagerOnAreaChanged(object sender, AreaChangedEventArgs areaChangedEventArgs)
        {
        }

        private void GameEventManagerOnPlayerDied(object sender, PlayerDiedEventArgs playerDiedEventArgs)
        {
        }
    }
}

One thing that doesn't account for, is CanCast, as well as having a cast timeout in case the client decides where you want to cast at is not a valid location, which also has to be handled. Combat range has to be lowered some, because if you try casting behind mobs too far, the cursor will be off-screen or overlap the gui itself.
 
The bouncing is amazing, I'm gonna use it!

I'm using it just on monster.Rarity > Rarity.Magic since Magic and White monsters always come in packs and there is no need to sandwich them.

Tomorrow I'm gonna try to create some logic to kill clusters of monsters in order to position right in the mdidle of the pack so with the Area of Effect reaches the maximum number of monsters.

I'm curious about monsters.First, does it return the closest monster? the thoughest monster?
 
I'm curious about monsters.First, does it return the closest monster? the thoughest monster?

monsters.First returns the first element in the sequence. monsters is generated by:

Code:
var monsters =
                    LokiPoe.ObjectManager.GetObjectsByType<Monster>()
                        .Where(
                            m =>
                                !AreaStateCache.Current.IsBlacklisted(m.Id) &&
                                !m.CannotDie &&
                                m.Distance < (_currentLeashRange != -1 ? _currentLeashRange : 50) &&
                                m.IsActive)
                        .OrderBy(m => m.Distance).ToList();

So, simply the closest. A targeting weight priority simple we be added back to the CR soon so users can customize weights like before. That way you can do things like, add weight based on # of surrounding mobs, or if it's a necro make it a top priority, etc... CR stuff is just low priority right now as I'm trying to work out some of the larger issues being reported that stem from the API itself.
 
Updated the thread and I need help from you pushedx.

I am trying to add Ice Spear with power charge on crit so I can keep up my power charges in order to deal more damage even though I'm dual totem I can still cast Ice Spear to gain Power Charges.

I've added

Code:
private int _iceSpearSlot = -1;

Code:
_iceSpearSlot = -1;

Code:
var icespear = LokiPoe.InGameState.SkillBarPanel.Skills.FirstOrDefault(s => s.Name == "Ice Spear");
                if (IsCastableHelper(icespear))
                          {
                              _iceSpearSlot = icespear.Slot;
                          }

Code:
private int _maxPowerCharges = -1;


        private int MaxPowerCharges
        {
            get
            {
                // This is so we avoid looking up this mostly static stat, every time.
                if (_maxPowerCharges == -1)
                {
                    _maxPowerCharges = LokiPoe.ObjectManager.Me.GetStat(StatType.MaxPowerCharges);
                }
                return _maxPowerCharges;
            }
        }


        private int PowerCharges
        {
            get
            {
                Aura aura = LokiPoe.ObjectManager.Me.Auras.FirstOrDefault(a => a.InternalName == "power_charge");
                if (aura != null)
                {
                    return aura.Charges;
                }
                return 0;
            }
        }


        private TimeSpan PowerChargeTimeLeft
        {
            get
            {
                Aura aura = LokiPoe.ObjectManager.Me.Auras.FirstOrDefault(a => a.InternalName == "power_charge");
                if (aura != null)
                {
                    return aura.TimeLeft;
                }
                return TimeSpan.Zero;
            }
        }


Now here's the logic I want it to execute, it will always drop 1 totem near our target no matter what, if we dont have the max number of Power Charges up, or they are finishing soon we cast Ice Spear so we gain them, and only after we have the previous condition we drop the second totem.


Here is what I was using:

Code:
if (_totemSlot != -1)
                {
                    var skill = LokiPoe.InGameState.SkillBarPanel.Slot(_totemSlot);
                    var currentTotems = skill.DeployedObjects;
                    int minimumTotemDistance = 15;
                    bool targetHasTotemNear = currentTotems.Any(o => o.Position.Distance(bestTarget.Position) < minimumTotemDistance);
                    int numberTotemsNearTarget = currentTotems.Count(o => o.Position.Distance(bestTarget.Position) < minimumTotemDistance);
[B]                    int numberTotemsThatHaveAnAliveMonsterNear = currentTotems.Count(o => o.Position.Distance(CombatTargeting.Targets<Monster>().Any.Position) < minimumTotemDistance);
                    //Code in bold is wrong
                    //Here's where I need help.  Using numberTotemsNearTarget works, but it makes bot unnefective because we have a totem that is currently damaging monsters, but since                       //it is out of range from our BestTarget it recasts the totem instead of charging up the Power Charges, so instead of using numberTotemsNearTarget I want to know the
                    //number of totems that are actually damaging any momsters, because if they are I won't bother recasting it


[/B]
                    
                    








                    if (skill.CanUse() && numberTotemsNearTarget < 1 )
                    {
                        var err1 = LokiPoe.InGameState.SkillBarPanel.UseAt(_totemSlot, true, Utility.CalculatePointAtDistanceAfterStart(myPos, cachedPosition, cachedDistance - 3));






                        //_totemStopwatch.Restart();


                        if (err1 == LokiPoe.InGameState.UseError.None)
                            return true;


                        Log.ErrorFormat("[Logic] UseAt returned {0} for {1}.", err1, skill.Name);
                    }
                    else
                    {
                        // keep powercharges up
                        if (_iceSpearSlot != -1)
                        {
                            // See if we can use the skill.
                            var skillTest = LokiPoe.InGameState.SkillBarPanel.Slot(_iceSpearSlot);
                            if (skillTest.CanUse())
                            {
                                if (PowerChargeTimeLeft.TotalSeconds <= 5 || PowerCharges < MaxPowerCharges)
                                {
                                    var err1 = LokiPoe.InGameState.SkillBarPanel.UseAt(_iceSpearSlot, true, cachedPosition);
                                    if (err1 == LokiPoe.InGameState.UseError.None)
                                    {
                                        await Coroutine.Sleep(Utility.LatencySafeValue(500));


                                        await Coroutines.FinishCurrentAction(false);


                                        return true;
                                    }


                                    Log.ErrorFormat("[Logic] Use returned {0} for {1}.", err1, skill.Name);
                                }
                            }
                        }
                        




                        if (skill.CanUse() && numberTotemsNearTarget < 2)
                        {
                            var err1 = LokiPoe.InGameState.SkillBarPanel.UseAt(_totemSlot, true, Utility.CalculatePointAtDistanceAfterStart(myPos, cachedPosition, cachedDistance - 3));






                            //_totemStopwatch.Restart();


                            if (err1 == LokiPoe.InGameState.UseError.None)
                                return true;


                            Log.ErrorFormat("[Logic] UseAt returned {0} for {1}.", err1, skill.Name);
                        }


                    }
                }

Without the bold code it is working, I don't know how to count the number of totems who have an alive monster within the minimum distance set by minimumTotemDistance


 
Last edited:
Once you have a Skill, you can access DeployedObjects to get the current objects created from the skill.

If you look at the code for the DualTotemRoutine, that's this portion of it:
Code:
var totemSkill = LokiPoe.InGameState.SkillBarPanel.Slot2;
var totems = totemSkill.DeployedObjects.ToList();

var oldestTotem = totems.Count > 0 ? totems[0] : null;
var newestTotem = totems.Count > 1 ? totems[1] : null;

In this case, skill is your totemSkill, so you don't have to use .Slot2.

Now, you can just check the two totem objects and see how many mobs are around them.
Code:
if(oldestTotem != null)
{
   var mobsAroundTotem = Utility.NumberOfMobsNear(oldestTotem, 20);
   if(mobsAroundTotem == 0)
   {
       // You should recast the totem, since it's most likely not attacking anything.
   }
}

As I mentioned in the other thread about general dual totem stuff, you have to consider the game's casting mechanics for totems. If you have only one totem up, then the next totem you cast will be where you cast it. If you have two totems up, the oldestTotem, is going to be replaced with the new cast, so you shouldn't care about checking newestTotem, as it'd take two casts to replace it.

While it is possible to check the CurrentAction of a totem to see if it's casting or not, you should probably use the mobs around check instead, because of the nature of the game and the fact the APi works out of process. Between attacks, there is a time where the Actor doesn't have a current action, so if you processed based on that, then you'd be recasting at times you shouldn't be, depending on when the API actually read the current data from the client.
 
i dont really see the difference in this routine? still spams totems
 
Ok this is working fine but all it does is cast totems? Is there anyway i can cast my curses?
 
Ok this is working fine but all it does is cast totems? Is there anyway i can cast my curses?

The default behavior of ExampleRoutine is to cast curses that are on your skill bar under this scenario:
Code:
// Handle curse logic - curse magic+ and packs of 4+, but only cast within MaxRangeRange.
                var checkCurses = myPos.Distance(cachedPosition) < ExampleRoutineSettings.Instance.MaxRangeRange &&
                                  (cachedRarity >= Rarity.Magic || Utility.NumberOfMobsNear(bestTarget, 20) >= 3);
 
Back
Top