asterobot:pathfinding computes moves on a map the way the Dofus client does. findPath() gives the route the client would walk from one cell to another, the key cells to send for it and how long the walk lasts. The other functions work with cell positions: where a cell sits on the map's grid, and the distance and the direction between two cells.
It only computes. To walk, call move() from asterobot:movement, or send the walk yourself as Walk along a path shows.
import { findPath, cellToPoint, pointToCell, distance, direction } from "asterobot:pathfinding";
These are the module's only exports. There's no pathfinding object to import.
| Export | Returns | What it does |
|---|---|---|
findPath() |
A path or undefined, right away |
Finds the route from one cell of a map to another |
cellToPoint() |
A point, right away | Gives a cell's position on the grid |
pointToCell() |
A cell or undefined, right away |
Gives the cell at a grid position |
distance() |
A number, right away | Measures the grid distance between two cells |
direction() |
A direction or undefined, right away |
Gives the direction from one cell to another |
None of them returns a promise. Like the functions of asterobot:gamedata, they throw when the top level of a module calls them while Asterobot reads the package's declarations: see Declarations.
Everything here is about moving outside fights. Moves in a fight follow other rules, and this module doesn't cover them.
Cells and directions
A map has 560 cells, numbered 0 to 559. Every function takes cell ids as regular whole numbers: a BigInt or a string is refused.
A direction is a number from 0 to 7:
| Number | Direction on screen |
|---|---|
0 |
East |
1 |
South-east |
2 |
South |
3 |
South-west |
4 |
West |
5 |
North-west |
6 |
North |
7 |
North-east |
findPath()
findPath(mapId, from, to, options?) returns a Path, or undefined.
| Parameter | Type | Description |
|---|---|---|
mapId |
BigInt, number or string | The map's id: a BigInt such as currentMap().mapId, a whole number up to Number.MAX_SAFE_INTEGER, or the id written in digits, such as "154010883". Any map of the bot's game version works, not only the one the character stands on. |
from |
number | The cell the walk starts from |
to |
number | The cell to walk to |
options.occupied |
number[] | The cells other actors stand on, as described below. Default none. |
options.cautious |
boolean | The cautious flag the walk will be sent with. It changes how the character moves, and so the walk's times. Default false. |
options.disableDiagonals |
boolean | true only for a character whose look has no diagonal walking animations. Player characters have them, so leave it false. |
It reads the map from the game data of the bot's game version and returns the route the Dofus client would take. When to can't be reached, the route ends on the reachable cell closest to it instead, and end tells you which. findPath() returns undefined only when the Dofus client itself would find no route at all, which doesn't happen on the game's maps.
Occupied cells
The Dofus client's router makes a cell with an actor on it more expensive to walk onto, so its routes tend to go around actors. To get the route the client would walk, pass in occupied the cell of every actor the server shows on the map except the bot's own character: other players, monster groups, NPCs, merchants, tax collectors, prisms and the like. Items on the ground, marks and glyphs don't count. currentMap() lists the actors:
import { currentMap } from "asterobot:bot";
import { findPath } from "asterobot:pathfinding";
const map = currentMap();
if (map?.cellId !== undefined) {
const others = map.actors.filter((actor) => actor.id !== map.characterId);
const path = findPath(map.mapId, map.cellId, 301, {
occupied: others.map((actor) => actor.cellId),
});
}
The path
| Property | Type | Value |
|---|---|---|
start |
number | The cell the route starts from: from |
end |
number | The cell the route ends on: to when the router reached it, otherwise the reachable cell closest to to |
steps |
array | The route's steps, in walking order. Each one is an object with cell, the cell the character leaves, and direction, the direction it walks from there. |
keyCells |
number[] | What MapMovementRequest carries in keyCells for this route. Send them as they are. Empty when end is start: there's nowhere to walk, and the Dofus client sends nothing. |
gait |
string | "walk" or "run": how the character moves along the route |
duration |
number | How long the walk lasts, in milliseconds: from its start to the moment the Dofus client sends MapMovementConfirmRequest. 0 when end is start. |
stepEnds |
number[] | When each step ends, in milliseconds from the start of the walk. The last one is duration. |
A character walks a route of 3 cells or fewer, the start included, and runs a longer one. With cautious: true it always walks.
duration is the earliest moment a Dofus client can confirm the walk. A real client only ends a step when it draws its next frame, so it confirms a little later: in walks recorded from the Dofus client, the confirmation left 10 to 48 ms after duration. The times are those of a character on foot. A mounted character runs at a speed Asterobot doesn't cover yet, so its duration can be wrong.
Errors
| Error | Type | Cause |
|---|---|---|
pathfinding.findPath mapId must be an integer Number, BigInt or decimal string |
TypeError |
mapId is a fraction, a number above Number.MAX_SAFE_INTEGER, a string that isn't a whole number written in digits, or another type |
pathfinding.findPath from must be an integer cell id, and the same with to |
TypeError |
The cell isn't a whole number in the 32-bit range, or is a BigInt or a string |
pathfinding.findPath options must be an object |
TypeError |
options is set, not to null or undefined, and isn't an object: an array or a number, for example |
pathfinding.findPath options has no property "<name>" |
TypeError |
options has a key other than occupied, cautious and disableDiagonals, such as the misspelled ocupied |
pathfinding.findPath options.occupied must be an array of cell ids |
TypeError |
occupied isn't an array |
pathfinding.findPath options.occupied[<index>] must be an integer cell id |
TypeError |
That item isn't a whole number in the 32-bit range |
pathfinding.findPath options.cautious must be a boolean, and the same with disableDiagonals |
TypeError |
The option is set to something other than true, false, null or undefined |
pathfinding.findPath: map <mapId>, cell <from> to cell <to>: pathfinding: not a map cell, cell ids go from 0 to 559: <cell> |
TypeError |
from or to is a whole number outside 0 to 559. For a cell of occupied, the text ends with occupied cell <cell> instead. |
failed to load map <mapId> for pathfinding: datacenter: no such record: maps/<mapId> |
Error | The bot's game version has no map with that id |
failed to open the datacenter: followed by the reason |
Error | Asterobot can't read the game data of the bot's game version, for example because it isn't extracted. See Extract game data. |
String(error) gives TypeError: or GoError: followed by the text.
cellToPoint()
cellToPoint(cell) returns { x, y }, the cell's position on the map's grid.
cellToPoint(300); // { x: 17, y: -4 }
The grid is the map turned 45 degrees, so both numbers change along a row of the screen. One cell to the east adds 1 to x and 1 to y. One cell to the south adds 1 to x and takes 1 from y.
| Error | Type | Cause |
|---|---|---|
pathfinding.cellToPoint cell must be an integer cell id |
TypeError |
cell isn't a whole number in the 32-bit range, or is a BigInt or a string |
pathfinding.cellToPoint cell: pathfinding: not a map cell, cell ids go from 0 to 559 |
TypeError |
cell is outside 0 to 559 |
pointToCell()
pointToCell(x, y) returns the cell at that grid position, or undefined when the position is off the map.
pointToCell(18, -3); // 301
pointToCell(0, 1); // undefined
It throws a TypeError with pathfinding.pointToCell x and y must be integers when x or y isn't a whole number in the 32-bit range, or is a BigInt or a string.
distance()
distance(a, b) returns the grid distance between two cells, as the Dofus client measures it: how far apart their x are, plus how far apart their y are.
distance(300, 301); // 2
So the next cell to the south-east, south-west, north-east or north-west is 1 away, and the next cell to the east, west, north or south is 2 away.
| Error | Type | Cause |
|---|---|---|
pathfinding.distance a must be an integer cell id, and the same with b |
TypeError |
The cell isn't a whole number in the 32-bit range, or is a BigInt or a string |
pathfinding.distance cells: pathfinding: not a map cell, cell ids go from 0 to 559 |
TypeError |
A cell is outside 0 to 559 |
direction()
direction(from, to) returns the direction from one cell to another, or undefined when the two cells aren't on one line.
direction(300, 301); // 0, east
direction(300, 316); // undefined
Two cells on one line give that line's direction, whatever their distance. The same cell twice gives 1, as it does in the Dofus client.
| Error | Type | Cause |
|---|---|---|
pathfinding.direction from must be an integer cell id, and the same with to |
TypeError |
The cell isn't a whole number in the 32-bit range, or is a BigInt or a string |
pathfinding.direction cells: pathfinding: not a map cell, cell ids go from 0 to 559 |
TypeError |
A cell is outside 0 to 559 |
Walk along a path
A walk takes two exchanges with the game server:
- The script sends
MapMovementRequestwith the path'skeyCells, thecautiousflag and the map's id. The server grants the walk withMapMovementEvent, whosecellslist every cell of the walk. The server sends that message for every actor that walks on the map, so compare itscharacterIdwith the character's. It can also grant a shorter walk than the one asked for. - Once the walk is over, the character confirms it with
MapMovementConfirmRequest, which has no field, and the server answers withMapMovementConfirmResponse. Until then, the server considers the character still walking.
Who sends the confirmation depends on the bot:
| Bot | Who sends MapMovementConfirmRequest |
|---|---|
| Full socket | Your script, duration milliseconds after MapMovementEvent arrived. Nothing else will. |
MITM, where session.shared is true |
The Dofus client, once it has walked the character on screen. Your script only waits for the server's answer. |
import { currentMap, session } from "asterobot:bot";
import { findPath } from "asterobot:pathfinding";
import { send, wait } from "asterobot:protocol";
import { sleep } from "asterobot:timers";
async function walkTo(cell) {
const map = currentMap();
if (map?.cellId === undefined) return;
const others = map.actors.filter((actor) => actor.id !== map.characterId);
const path = findPath(map.mapId, map.cellId, cell, {
occupied: others.map((actor) => actor.cellId),
});
if (!path || path.keyCells.length === 0) return;
await Promise.all([
wait(
(traffic) =>
traffic.type === "MapMovementEvent" && traffic.payload?.characterId === map.characterId,
{ timeout: 5000 },
),
send("MapMovementRequest", { keyCells: path.keyCells, cautious: false, mapId: map.mapId }),
]);
if (session.shared) {
// The Dofus client confirms the walk once it has walked it.
await wait("MapMovementConfirmResponse", { timeout: path.duration + 5000 });
return;
}
await sleep(path.duration);
await Promise.all([
wait("MapMovementConfirmResponse", { timeout: 5000 }),
send("MapMovementConfirmRequest", {}),
]);
}
wait() comes first in each Promise.all, so it's already listening when the message goes out, as Send messages explains.
This sample times the confirmation with the route it asked for. When the server grants a shorter walk, time the walk it granted instead, for example with findPath() from the first of its cells to the last. Start one walk at a time: a walk started before the previous one ends would start from a cell the character hasn't reached yet.
To stop a walk before its end, the Dofus client sends MapMovementCancelRequest with the cell the character stopped on, and the server doesn't answer it. stepEnds tells you which step the character has reached at a given moment.
move() does all of this in one call, shorter walks and both kinds of bots included.
asterobot:bot describes currentMap(), and the Dofus protocol reference lists the fields of the movement messages.
Aucun avis à afficher.