How scripts and Asterobot's fight module share a bot's fights: starting a fight from a script, knowing when it starts and ends, driving the module and taking its decisions, what the module sends and holds back, and what a script must send when the module is off.
Asterobot has a fight module that plays a bot's fights by itself, when the person running the bot switches it on: it places the character, says it's ready, plays every turn and answers everything the Dofus client answers during a fight. How it decides is set by fight profiles. The guide describes it for players: The fight module and Fight profiles.
The module plays fights, it never starts one. Starting fights, and deciding what happens between them, is your script's job.
Who does what
| Your script | The fight module, switched on | The fight module, switched off | |
|---|---|---|---|
| Walking to a group and attacking it | Yes | Never | Never |
| Answering the fight map's load | Yes | Nothing | |
| Placement and ready | Yes, for the parts its profile leaves on | Nothing | |
| Playing the character's turns, and those of the summons it controls | Yes | Nothing | |
| Answering the server during the fight (acknowledgements, ready for the next turn) | Yes | Nothing, but what Hurry the server adds | |
| Everything after the fight | Yes | Nothing | Nothing |
The person running the bot chooses whether the module plays, on the bot's Fight tab. Your script can override that choice while it runs, and the profile and speed too, with asterobot:fight. Write your package so it works either way, or switch the module as it needs it.
Start a fight
A Dofus client attacks a monster group by walking onto the group's cell, then sending AttackMonsterRequest with the group's actor id. The server then removes the group from the map and switches the character into a fight.
To know which actors of the map are monster groups, read the map's contents when they arrive: a monster group's actorInformation.rolePlayActor holds a monsterGroupActor. The ids and cells of the actors standing on the map now are in currentMap().
import { currentMap, botWarn } from "asterobot:bot";
import { on, send, wait } from "asterobot:protocol";
import { move } from "asterobot:movement";
// The actor ids of the monster groups the server placed on the map, read
// each time a map's contents arrive, after every fight included.
const groups = new Set();
on("MapComplementaryInformationEvent", (traffic) => {
groups.clear();
for (const actor of traffic.payload?.actors ?? []) {
if (actor.actorInformation?.rolePlayActor?.monsterGroupActor) groups.add(actor.actorId);
}
});
// Walks onto a monster group and attacks it. Resolves with true once the
// fight has started, false when the group was gone or the attack got no answer.
async function attack() {
const group = currentMap()?.actors.find((actor) => groups.has(actor.id));
if (!group) return false;
await move(group.cellId);
await send("AttackMonsterRequest", { monsterGroupId: group.id });
try {
await wait(
(traffic) => traffic.type === "ContextCreationEvent" && traffic.payload?.context === "FIGHT",
{ timeout: 5000 },
);
return true;
} catch {
// The group walked away while the character walked to it: the server
// doesn't answer an attack from a cell the group left.
botWarn("The attack got no answer");
return false;
}
}
Call attack() once the character stands on a map, for example after @asterobot:base has taken a Full socket bot into the game.
Groups walk around. When one moves while the character walks to it, the server ignores the attack, and the Dofus client walks again and attacks from the group's new cell. Do the same: try again after a failed attack, from a fresh currentMap().
Groups that arrive later come with GameRolePlayShowActorsEvent, in the same actors shape, and ContextRemoveElementEvent removes the ones that leave. A script that waits on a map for groups to come back reads those too.
Know when a fight starts and ends
| What | How |
|---|---|
| A fight starts | ContextCreationEvent with context: "FIGHT". The MapCurrentEvent that follows names the fight's map. |
| The fight ends | ContextCreationEvent with context: "ROLE_PLAY", after FightEndEvent, then the world map the character is now on. After a defeat, that's another map. |
| Whether a fight is under way, at any moment | character().inFight from asterobot:character, true from the first switch to the second. |
To wait for a fight to end, give the wait a long timeout: fights can last many minutes.
await wait(
(traffic) => traffic.type === "ContextCreationEvent" && traffic.payload?.context === "ROLE_PLAY",
{ timeout: 30 * 60_000 },
);
Your script keeps running during the fight, and its handlers keep receiving every message the server sends, fight messages included. travel() waits out a fight that starts on the way, and @asterobot:base leaves the fight's map to the fight module.
While the module plays
- Don't send fight actions. Moves, spells and the end of the turn sent by a script would reach the server alongside the module's, and the turn would go wrong. The module doesn't hold back what a script sends. To decide an action, answer it from a decision hook: the module sends it, checked by the rules engine.
- Your interceptors don't see the clicks it holds. On a relayed session, the module is asked first about every message the Dofus client sends. The clicks it holds back, the moves, spells and turn ends of the person playing, and placement or ready when the profile decides them, go neither to the server nor to your interceptors. Every other message reaches them as usual.
- Its console lines are the bot's. The module writes what it does in the bot's console, such as
The fight module casts a spell, between your script's lines.
When the module is off
With the module off, Asterobot sends nothing during a fight, but what Hurry the server adds when it's on. On a relayed session, the person's Dofus client plays it. On a Full socket bot, nothing answers the server unless your script does, and the fight doesn't move: the server waits for each fighter's answer before the next turn.
A script that plays fights itself sends what the Dofus client sends:
- The fight's map. After
MapCurrentEventnames it,FightPingsRequest(empty) thenContextReadyRequestwithmapId, the fight map's id. Unlike a world map, noMapInformationRequest. - Placement, optionally:
FightPlacementPositionRequestwithcellId, a cell of the character's team fromFightPlacementPossiblePositionsEvent, andentityId, the character's id. - Ready:
FightReadyRequestwithisReady: true. Without it, the fight starts when the placement countdown runs out. - Its turns: when
FightTurnEventnames the character, or a summonFightControlEventgave it to play (itsmasterIdthe character, itsslaveIdthe summon, with the summon's spells),MapMovementRequestto walk,GameActionFightCastRequestwithspellIdandcellto cast (spell 0 for the weapon or bare hands, a fighter aimed at through its cell), andFightTurnFinishRequestto end the turn. - Acknowledgements:
GameActionAcknowledgementRequestwithactionId, the action id of each outermost sequence the character or a summon it controls authored, asSequenceEndEventgives it, andvalid: true. The server doesn't wait for them, but the Dofus client sends them. - Ready for the next turn:
FightTurnReadyRequestwithisReady: truefor everyFightIsTurnReadyEvent. The server waits for this one.
The Dofus protocol reference describes each message and its fields. It's a lot of work to do well, which is what the module is for.
Faster fights
Three switches on the bot's Fight tab work in every fight, whether the module plays it, the person, or your script. Scripts can't change them. What they change for a script:
- Hurry the server: Asterobot also sends
GameActionAcknowledgementRequestfor each outermost sequence nobody else acknowledges, a monster's or an ally's, andFightTurnReadyRequest { isReady: true }every 100 ms from theFightTurnEndEventof the character's turn until the nextFightTurnEvent, 20 at most. Neither is one of the answers listed above, so a script that plays fights keeps sending its own. - Fewer animations, on a relayed session: a
GameActionFightEventwithtargetedAbilityreaches the Dofus client withsilentCast: true, and one withstagingorexecuteScriptdoesn't reach it at all. - Skip walking in fights, on a relayed session: a
MapMovementEventduring a fight reaches the Dofus client with its last cell alone. On every session, the module then answers without waiting for a fight walk to play.
Your script's interceptors see casts and walks as the server sent them, and never see a staging or executeScript that Fewer animations drops. Its handlers receive all of them as the Dofus client does: silent, shortened, or not at all.
Ship fight profiles
A package can ship fight profiles alongside its script: they show on the Fight Profiles page of everyone who installs it, and any bot can play them. See Ship fight profiles, and the Fight profile format for every field.
Drive the module
asterobot:fight is the module for scripts that work with the fight module:
- Switch it, play another profile or change its speed while the script runs, with
enable(),disable(),setProfile()andsetSpeed(). The bot's Fight tab says what the script overrides. - Read the fight under way with
state(): every fighter's life, AP, MP, cell, states and monster, the round and whose turn it is. - Follow every fight with
onFight(): its start, each turn's start and end, the module's actions with their reasons, and its end. - Take its decisions with
onDecision(): where to place, holding the ready, and each action of a turn, from one of the AI's turns to a whole turn of your own. The rules engine checks every action.
The choice of challenges comes later, with the challenges themselves.
That's the end of the protocol chapter. The next one, Game data, covers reading items, monsters, maps and texts from a script.