Register a free account today to become a member! Once signed in, you'll be able to participate on this site by adding your own topics and posts, as well as connect with other members through your own private inbox!
Been tinkering with the ones that come with the bot but I would like to completely write my own as I don't like the pace of combat or the skills combos it uses. Thanks!
To get started, you'll want to make sure you've read and understand the existing guides in the Guides section for when it comes to setting up a project and understanding the API. There's no general guide for writing a CR, because it's pretty much just understanding how to use the API, and then using it to do whatever you need. I'll go over a few of the CR structure aspects that might need some more explanation to get you pointed in the right direction though.
This uses the ExampleRoutine targeting setup and subscribes AreaChanged and PlayerDied event handlers. All the GUI code was removed, and it just uses an empty settings class. That CR, if you were to run it, would simply not do anything once a combat target was detected.
There's really only two aspects of the interface that need to be over-viewed.
SetConfiguration/GetConfiguration give you the means to pass data to and from the CR from outside without requiring knowing the actual object type. This is so you can have bot code that configures the CR in a generic way. If the CR doesn't require or use a specific setting, the CR's code simply wouldn't process it, so it makes life a lot easier.
Logic is the coroutine the bot would execute to invoke CR functionality. It contains a string so the calling code and specify the type of logic that needs to be executed, without forcing everything to be executed at once, like the old design did. This means if you have a bot implementation that wants to break down buff, regen, and combat phases into 3 distinct sets, it can. You'd just code your CR to only perform each specific type of logic per name.
Right now, BasicGrindBot only has one group to execute, and that's "combat", and named so. All combat logic is handled there, so if you were to add any other strings, BasicGrindBot would never execute them, because it's not coded to. Likewise, the only setting passed via SetConfiguration is the leash range, which is used by the bot to limit combat to the area around it.
The current design of BasicGrindBot is such that you are always in combat. In the past, our bot implementation tried to determine if you were in combat or not, but that lead to all sorts of problems and the lack of flexibility to handle a lot of specific mechanics, such as being able t o cast Desecrate and then raise zombies, stop moving if you are punctured, being able to do pre-combat logic before starting boss fights, and so on.
When Logic is called, you return true to signal the CR logic is running, and no other tasks should execute (see BasicGrindBot Guide's Task section for more information). If you return false, other tasks will execute, so if you did execute logic, you'll most likely see bugs.
At this point, once you understand most of that, you're ready to code your CR. The way you go about this, is you imagine your basic combat scenario in PoE, and start looking at what the CR would need to do to perform combat.
To start out, let's say you have your bestTarget. It would be useful to first know how far away it is from you, so you know if you need to move closer to it, or if it's out of LoS. If you check ExampleRoutine, you'll see the canSee, pathDistance, and blockedByDoor values being stored for that reason. You can pretty much c/p that stuff for your CR.
At this point, if you can't see the target, or it's too far from your desired combat range, or it's blocked by a door, you'll want to move towards it. In ExampleRoutine, it move towards it by simple calling PlayerMover.MoveTowards on it's position. This is because it's the fastest, most fail-safe manner to get closer to a mob, without requiring a bunch of extra logic. A more advanced CR might want to find a way to get into optimal combat range, while keeping distance and not moving on top of the mob itself. You can do this by a lot of calcs using things from ExilePather to do raycasts and Utility functions to calculate points in the direction of. I covered some of this in this reply, so have a look at that.
Once you're in range of the target and can see it, you can now cast a skill. LokiPoe.InGameState.SkillBarPanel contains the API functions for working with the skill bar, so you'll want to check out the exposed functionality from there. You basically have 3 types of skill use functions, Use (self-centered), UseAt (directed towards a position), UseOn (directed towards a target, and requires that target to be highlighted). Which ones you want to use when, just comes down to the skill you are going to use.
So, for example, let's say you have Freezing Pulse in Slot 1, and you want to code a CR that simply uses FP on everything within 15 units. Your logic would simply be:
Code:
public async Task<bool> Logic(string type)
{
if (type == "combat")
{
// Update targeting.
CombatTargeting.Update();
// This is pretty important. Otherwise, components can go invalid and exceptions are thrown.
var bestTarget = CombatTargeting.Targets<Monster>().FirstOrDefault();
if (bestTarget == null)
return false;
var myPos = LokiPoe.Me.Position;
var cachedPosition = bestTarget.Position;
var canSee = ExilePather.CanObjectSee(LokiPoe.Me, bestTarget);
var pathDistance = ExilePather.PathDistance(myPos, cachedPosition);
var blockedByDoor = Utility.ClosedDoorBetween(LokiPoe.Me, bestTarget);
if (!canSee || blockedByDoor || pathDistance > 15)
{
if (!PlayerMover.MoveTowards(cachedPosition))
{
Log.ErrorFormat("[Logic] MoveTowards failed for {0}.", cachedPosition);
}
return true;
}
var err1 = LokiPoe.InGameState.SkillBarPanel.UseAt(1, false, cachedPosition);
if (err1 != LokiPoe.InGameState.UseError.None)
{
Log.ErrorFormat("[Logic] Use returned {0} for {1}.", err1, 1);
}
return true;
}
return false;
}
And that's it. Your bot would now run around casting FP on everything as you'd expect. That logic doesn't check for mana or if you can use the skill, so if you couldn't, then it'd just sit around waiting until I could cast it. This is now where you get into the Skill API to get info about the skills. If you were to use this logic instead,
Code:
var skill = LokiPoe.InGameState.SkillBarPanel.Slot1;
if (skill != null && skill.CanUse(true))
{
var err1 = LokiPoe.InGameState.SkillBarPanel.UseAt(1, false, cachedPosition);
if (err1 != LokiPoe.InGameState.UseError.None)
{
Log.ErrorFormat("[Logic] Use returned {0} for {1}.", err1, 1);
}
}
You'd get log info messages about why you couldn't cast it, since the CanUse function has params to help debug these things.
That's about all you need to know for the basics of the CR. From here, you just use our API to get the information you need to put together more complex logic. If you wanted to cast a skill on a target if it was under a status effect, you'd check out the exposed properties in Actor to do that, and then setup your logic with a basic if statement. That way, if the best target is burning, you'd cast a specific skill, and then return true.
You'll have to explore the stuff exposed in the API using the VS Object Browser, but we have as much exposed as possible. Some things you'll have to code around, because they aren't exposed in this game like you'd expect or be used to from other games. If you can't find something, feel free to ask and I'll point you in the right direction or tell you that it just doesn't exist in the API.