Aller au contenu
Afficher dans l'application

Une meilleure façon de naviguer. En savoir plus.

Asterobot

Une application plein écran sur votre écran d'accueil avec notifications push, badges et plus encore.

Pour installer cette application sur iOS et iPadOS
  1. Appuyez sur Icône de partage dans Safari
  2. Faites défiler le menu et appuyez sur Ajouter à l'écran d'accueil.
  3. Appuyez sur Ajouter dans le coin supérieur droit.
Pour installer cette application sur Android
  1. Appuyez sur le menu à trois points (⋮) dans le coin supérieur droit du navigateur.
  2. Appuyez sur Ajouter à l'écran d'accueil ou Installer l'application.
  3. Confirmez en appuyant sur Installer.

@asterobot:base

The official package that does what the Dofus client does by itself, starting as soon as a script loads it, and the standard library packages build on: moving, the Studio's plans, steps and conditions for gathering, fighting, resting, equipment, the marketplace and the bank, and 24 fight profiles.

@asterobot:base is the official package that does what the Dofus client does by itself, so that your package never has to:

  • it identifies the game connection, picks a character and enters the world;
  • it keeps the connection alive the way the client does, with its pings and its answers to the server's checks and polls;
  • it tells the server the character is ready on every map it enters.

It starts itself as soon as a script loads it, and your package carries on from a character standing on its map.

It's also the standard library packages build on:

  • moving, from asterobot:movement, re-exported;
  • playing the plans drawn in Asteroboard's Studio;
  • steps and conditions for gathering, fighting, resting, eating, equipment, the marketplace and the bank, for plans and scripts alike;
  • 24 fight profiles, ready to play.

You don't install it: Asterobot downloads it from asterobot.net by itself, keeps its newest version in every library, and loads that version for every package that depends on it, whatever version the package locked. See Base is always the newest.

Add it to a package

Declare @asterobot:base in your package's dependencies, as Dependencies shows, then import it by that name:

import { connect } from "@asterobot:base";

export default async function behavior() {
  void main();
}

async function main() {
  const map = await connect();
  // The character stands on map.mapId: your script starts here.
}

Importing base is all it takes. It starts as it loads, before your own code runs, and connect() only waits for the world. The entry function doesn't wait for it, so Play returns right away and main() goes on in the background.

A package loads base when one of its files imports it, and so does every package that depends on that one, at any depth. Listing base in dependencies without importing it loads nothing.

@asterobot:base can also be played on its own: it brings a bot into the world and keeps it there. Such a bot plays the newest version of base too, whatever version it was set to.

The four cases

Full socket bot MITM bot
Played on its own Identifies, selects a character, enters the world, then answers everything the server asks Sends nothing, and waits for the person's character to be on a map
As a dependency The same, as soon as the script loads base The same

Base tells the two kinds of bot apart by session.shared, so there's nothing to configure. A package using base is the same code in all four cases.

Base starts at the top level of its index.js. Asterobot also runs that code while it reads a package's settings, without a game session, and base doesn't start then. See Declarations.

Exports

Export Returns What it does
connect() Promise of the map Waits until the character stands on its map.
latency() number The latency the bot reports to the server, in milliseconds.
move(), changeMap(), useElement(), travel(), route(), stopTravel(), trip() Moving, from asterobot:movement, re-exported. After a map change, base asks for the new map, as for every map.
findMaps(), mapAt(), mapInfo(), nearest(), nearestWith(), mapsWithin(), reachable() The map finders of asterobot:movement, re-exported: see Find a map.
run(), stop(), plan() Play a plan drawn in the Studio, and stop it: see Plans.
levelAtLeast(), kamasAtLeast(), lifeBelow(), bagsAbove(), inFight(), hasItem(), jobLevelAtLeast(), onMap(), inSubarea(), inArea(), harvestsAtLeast(), diedInLastFight(), fightsAtLeast(), wears() boolean, or a Promise of one Yes/no questions about the character: see Conditions.
gather(), fightMonsters(), fightSettings(), rest(), eat(), equip(), unequip(), sell(), buy(), deposit(), withdraw() A Promise, nothing for fightSettings() Gathering, fighting, resting, eating, equipment, the marketplace and the bank: see Steps.
steps, conditions objects The declarations of every step and condition above, which the Studio offers in every package that depends on base.

connect()

connect() resolves with what currentMap() returns once the character is placed on its map, at once if it already is. Base itself calls it as it loads, which starts everything. Every later call returns the same promise, so any number of packages on the bot can call it.

When it fails, the reason goes to the bot's Console as an error line, and the promise rejects with it too. Stopping the script, or losing the connection, rejects it without an error line.

What base does by itself

On a Full socket bot, for as long as the script runs:

  • Proof of identity. It answers ServerSessionReadyEvent, ServerVerificationEvent and ServerChallengeEvent with the values of asterobot:verification, as soon as each event arrives. The answers go out with a uid, like the client's. It answers a challenge with a value and one without, and both are checked against the real client.
  • Polls. It answers each SequenceNumberEvent with the number of polls received on the connection, and each BasicLatencyStatsEvent with latency(). The server sends the two together, every 4 minutes, and base answers them in the Dofus client's order: the sequence, then the latency.
  • Pings. With the ping setting on active, it sends a PingRequest on about half of the 5-second ticks once the character has loaded, as a client does while someone uses it.
  • Maps. It answers each MapCurrentEvent, and each MapCurrentInstanceEvent, the form the server uses for an instanced map, with ContextReadyRequest and MapInformationRequest carrying the map's mapId, after the time the Dofus client takes to load the map: 1.2 to 2.4 seconds for the first one, then 0.4 to 1.2 seconds. The server sends nothing of the new map until both requests arrive. On the first map it sends AnomalySubareaInformationRequest too. After a changeMap(), an exit's useElement(), or each map of a travel(), these two requests are base's alone: base tells Asterobot so with answerMapLoads(), and those functions never send them too, however long base takes.

And once, to get into the world:

  • Identifying. A refused ticket makes connect() reject. The ticket is then spent: only a new sign-in brings another.
  • Selecting a character. Base picks the character the character setting names, or the first of the list, after a pause of 1.5 to 3.5 seconds. connect() rejects when no character has that name, naming the ones there are, and when the server refuses the selection.
  • Entering the world. Base sends what the client sends once the character has loaded, then waits for the map.

latency()

The mean of the bot's last 50 ping round trips, in milliseconds, rounded as the Dofus client rounds it, or 0 before the first. The round trips are kept in the bot's memory, so a script started again goes on from the same ones.

Plans

A plan is a file of a package, drawn in Asteroboard's Studio: places on the world map, the order the bot goes round them, and the steps it runs at each. A script plays one with run(), as the index.js that Start with the Studio writes does:

import { run } from "@asterobot:base";
import main from "./plans/main.js";

export { parameters } from "./plans/main.js";   // the plan's settings

export default async function behavior() {
  void run(main);
}
Export Returns What it does
run(plan, options?) Promise Plays a plan, the default export of a plan file. Resolves when its circuit ends or is stopped, rejects with what failed. options is reserved: nothing reads it yet.
stop() boolean Asks the plan being played to stop cleanly. Says whether one was.
plan(data, refs) The plan What a plan file calls as it's imported, never by hand. It never throws, since it also runs while Asterobot reads the package's declarations.

run() first waits for the world, with connect(). It then checks the whole plan, and every plan it plays through interrupts and plan steps: every field, place, package, function, argument and setting. On any problem it rejects before the bot takes a step, listing every problem, each with its block's label and id. Then it walks the circuit, lap after lap.

  • One plan at a time. A run() while another plan plays rejects with base: a plan is already running. The plans a plan plays itself don't count.
  • Stopping cleanly. A stop comes from stop(), from a stop step, or from Asterobot asking the script to stop, such as at the end of a schedule's time slot. The step under way finishes, and a long one sees context.stopping turn true. A wait ends at once, a trip ends at the end of its leg, then run() resolves.
  • A failure rejects run(). Nothing catches a void run(main), so a plan that fails stops the script, and the bot's page shows why. The error names the block, then the cause: base: Astrub farm › 2 Astrub fields (k3f9) › Fight (p2x1) failed 3 times: no monster to fight.
  • Where it is. While a plan plays, the runner keeps where it is in the bot's memory, under @asterobot:base/plan: { plan, stop, step, map, lap, connectionId }.

The Studio explains what a player draws: the order, the failures and retries, the interrupts, the plan's settings and the console lines. The editor explains the plan files, and base's README describes the plan format.

Conditions

Each condition is a function base exports and declares in its conditions, so the Studio offers it in every package that depends on base. From code, each takes its arguments as one object, as the Studio passes them:

import { bagsAbove, levelAtLeast } from "@asterobot:base";

if (levelAtLeast({ level: 50 }) && bagsAbove({ percent: 90 })) {
  // …
}
Condition Arguments True when
levelAtLeast level (1 or more) The character's level is at least level.
kamasAtLeast kamas (0 or more) It carries at least kamas kamas.
lifeBelow percent (1 to 100) Its life is below percent of its maximum.
bagsAbove percent (1 to 100) Its bags are fuller than percent of what they can carry.
inFight A fight is under way.
hasItem item, count (1 by default) It carries at least count of item, worn or not.
wears item It wears item.
jobLevelAtLeast job, level (1 or more) That job is at least level. A job it doesn't have is level 0.
onMap map It stands on that map.
inSubarea subarea It stands in that subarea. Returns a Promise.
inArea area It stands in that area. Returns a Promise.
harvestsAtLeast count (1 or more) It has made at least count harvests with gather() since the script started.
fightsAtLeast count (1 or more) It has fought at least count fights since the script started.
diedInLastFight It died in its last fight. False before any.

Items, jobs, maps, subareas and areas are game data ids, which the Studio searches by name. The conditions read what Asterobot follows: asterobot:character, currentMap() and mapInfo(). The two fight conditions count every fight from the moment base starts, from the fight module's onFight(), whoever started the fight.

A fact the game hasn't told yet is never guessed. The condition throws, naming it, such as the level isn't known yet or the jobs aren't known yet, and in a plan that fails the block it guards. By the time connect() resolves, the game has sent the character, its characteristics and its inventory.

Steps

Each step is a function base exports and declares in its steps. From code, each takes its arguments as one object, as the Studio passes them, and resolves once it's done:

import { fightMonsters, fightSettings, gather, rest } from "@asterobot:base";

fightSettings({ profile: "@asterobot:base/careful" });
await fightMonsters({ monsters: [4109], maxLevel: 30 });
await rest({ upTo: 90, eat: true });
await gather({ resources: [303], bagsAbove: 90 });   // 303 is Ash Wood

A step with nothing to do on the map resolves { nothingToDo: true }, with retryAfter: 60 for gather() and fightMonsters(), since resources grow back and monsters come back. A fight under way when a step starts, or one that comes during gather(), rest() or eat(), such as an aggression, is waited for while the fight module plays it. A step fails with the character died in the fight when the character dies in it.

gather()

Harvests the map's resources that match, one after another, with the job's own action, as the Dofus client does when the player clicks one: the nearest first, or the highest level first. It ends when nothing more matches, after maxHarvests harvests, or once the bags are fuller than bagsAbove. An element whose use fails, taken by someone else or out of reach, is tried once more, then given up.

Argument Default Gathers
resources none What gives these items, such as 289, Wheat. With jobs, what either asks for.
jobs none Every resource of these jobs. Neither resources nor jobs: every resource of the map.
avoid none Never what gives these items.
myLevel true Only what the character's level in the job allows. The server would refuse the rest.
order nearest nearest first, or the resource of the highest level first.
maxHarvests 0 Ends the step after this many harvests on the map. 0: everything that matches.
bagsAbove 0 Ends the step once the bags are fuller than this share, 0 to 100. 0: never.

fightMonsters()

Attacks the map's monster groups that match, the nearest first, one after another, and waits for each fight's end, which the fight module plays. On a Full socket bot it switches the module on while the script runs, when it's off: a fight can't go on without it. On a MITM bot the switch stays the person's. When no fight starts within 5 s, the group walked away: base walks to its new cell and attacks again, and gives a group up after three attacks.

Argument Default Keeps the groups
monsters none With at least one of these monsters. None: any group.
avoid none With none of these monsters.
races none With a monster of one of these races. None: any race.
minLevel 0 Whose level, the sum of its monsters' levels as the game shows it, is at least this.
maxLevel 0 Whose level is at most this. 0: no limit.
maxMonsters 0 With at most this many monsters, 0 to 8. 0: no limit.
maxMonsterLevel 0 With no monster above this level. 0: no limit.
archmonsters avoid Without an archmonster (avoid), with or without one (allow), or only with one (only).
maxFights 0 Ends the step after this many fights on the map. 0: every group that matches.

The groups and their monsters are currentMap()'s actors and their monsters.

fightSettings()

Sets how the fight module plays while the script runs, as asterobot:fight does, and returns at once. What it leaves at keep, or an empty profile, stays as the bot's Fight tab says.

Argument Values Default
module keep, on, off. On a MITM bot the switch stays the person's. keep
profile A fight profile's name, as the Fight Profiles page shows it. empty
speed keep, slow, normal, turbo keep

rest() and eat()

rest() sits the character down with the sit emote, EmotePlayRequest { emoteId: 1 }, until its life is back to upTo percent of its maximum, then stands it up with the same emote. With eat, it eats first. It fails with the character's life isn't coming back when life doesn't change for a minute, and with the character's life isn't known yet before the game tells it.

eat() eats from the bags until life is back to upTo: a stack whose game data effects give life back, such as breads and potions, the one healing most without passing what's missing first. It sends ObjectUseRequest { objectUid }, one at a time, at a person's pace. Food the server doesn't take within 5 s is given up for the rest of the step.

Argument Default What it does
upTo 100 Stops once life reaches this share of its maximum, 1 to 100.
eat false rest() only: eats from the bags first, which is faster.
food none Eats only these items. None: any food that gives life back.

Life is character().life, which follows the life coming back, faster sitting down, and the food is its items().

equip() and unequip()

equip({ items }) puts each item on, one after another at a person's pace, in a free slot it can go in, else in its first slot, which sends the item there back to the bags. One already worn is left as it is. unequip({ items }) takes them off, or everything the character wears with none given. The slots come from the game data, so a ring goes on either hand. Both send the Dofus client's ObjectSetPositionRequest, and the server confirms with ObjectMovementEvent.

They fail for an item that isn't in the bags (item 1439 isn't in the bags), one that can't be worn (item 1439 can't be worn), and one the server didn't move within 5 s (the game didn't move item 1439).

sell() and buy()

Both use the marketplace of the map the character stands on, as a person does in the Dofus client's marketplace window. A plan travels there first, as for any other place: the step doesn't look for a marketplace elsewhere.

import { buy, sell } from "@asterobot:base";

await sell({ items: [289], under: 1, floor: 70 });   // 289 is Wheat
await buy({ items: [468], upTo: 20, maxPrice: 50 });   // 468 is Incarnam Bread

sell() puts the items asked for on sale from the bags. The window opens on its Buy tab, and the step goes to its Sell tab, which lists the lots the account has on sale. For each stack, it asks the item's prices, then puts lots on sale, one after another at a person's pace: the biggest the stack fills first (1000, 100, 10, then 1), or lots of the one size lot asks for. Each size has one price for the stack:

  1. under kamas under the cheapest lot of that size on sale. A lot of the account's own that's already the cheapest keeps its price, so the bot never undercuts itself.
  2. With no lot of that size on sale, the marketplace's average price for that many items.
  3. Never under floor percent of the average price, however cheap others sell.

The marketplace takes its tax, 2% of the price in the capture, when the lot goes on sale. An item the marketplace doesn't take, one above its level cap, and one whose lots aren't all identical, such as equipment with its own stats, stay in the bags, and the Console says so. The step stops putting lots on sale when the character can't pay the tax, or when the marketplace doesn't confirm a lot within 5 s, which happens when the account has as many lots on sale as it can.

Argument Default What it does
items none Sells these items. With jobs, what either asks for.
jobs none Sells every resource these jobs gather. Neither items nor jobs: nothing is sold.
lot 0 0: the biggest lots each stack fills. 1, 10, 100 or 1000: lots of that size only, and what doesn't fill one stays in the bags.
under 1 How many kamas under the cheapest lot of the same size. 0: the same price.
floor 70 Never prices a lot under this share of the average price, 0 to 100. 0: no floor.

buy() buys the items asked for until the character has upTo of each, worn or in the bags. For each item it picks the item's type, then the item, as the window lists them, and buys the lot that costs least per item among those that don't buy more than is missing, never above maxPrice per item, until none is left. An item with its own stats isn't bought.

Argument Default What it does
items none Buys these items.
upTo 1 How many of each item the character should have. It buys what's missing.
maxPrice none The most it pays for one item, in kamas. Required: buy() fails without it, and a plan's check asks for it.

Both resolve { nothingToDo: true } when there's nothing to sell or buy, and when the marketplace sold or bought nothing. They fail with there's no marketplace on this map, and with the marketplace's window didn't open when the server doesn't open it within 5 s. They close the window when they end, even on a failure. The Console says what each item gave, such as base: put on sale Wheat: 1 × 100 at 4999, 3 × 10 at 520 kamas.

deposit() and withdraw()

Both use the bank of the map the character stands on. They talk to its banker, the nearest of currentMap()'s actors with npc: true, pick the dialog's bank reply, and the server takes the bank's fee and opens the bank. A plan travels to the bank's map first, as for any other place.

import { deposit, withdraw } from "@asterobot:base";

await deposit({ keep: [468], kamas: true, keepKamas: 5000 });   // 468 is Incarnam Bread
await withdraw({ items: [468], upTo: 20 });

They move one stack at a time, as a person drags them, about a second apart: ExchangeObjectMoveRequest { objectUid, quantity }, positive to put a stack in, negative with the bank's uid to take one out. Asterobot follows every stack that comes and goes, so items() and Bags above stay right. Kamas move with ExchangeMoveKamaRequest { quantity }.

deposit() puts in every stack in the bags but the items to keep, or only the items and what the jobs gather when given. Worn items stay on. A stack the bank doesn't take within 5 s is left in the bags, and the Console says so.

Argument Default What it does
items none Puts in only these items. With jobs, what either asks for. Neither: everything in the bags.
jobs none Puts in only what these jobs gather.
keep none These stay in the bags, such as food, potions or a spare set.
kamas false Puts in the kamas the character carries above keepKamas too.
keepKamas 0 The kamas left on the character, for zaaps and the bank's fee.

withdraw() takes the items out until the character has upTo of each, worn or in the bags, from as many of the bank's stacks as it takes, and kamas until it carries kamasUpTo, as far as the bank holds them.

Argument Default What it does
items none Takes out these items.
upTo 1 How many of each the character should have. It takes out what's missing.
kamasUpTo 0 Takes kamas out until the character carries this many. 0: none.

Both resolve { nothingToDo: true } when there's nothing to move, without talking to the banker, and when nothing moved. They fail with there's no banker on this map, with the NPC on this map doesn't offer the bank when the nearest NPC's dialog has no bank reply, which leaves that dialog open, and with the banker didn't answer or the bank didn't open when the server doesn't answer within 5 s. They close the window when they end, even on a failure. The Console says what moved, such as base: put in the bank: 37 Manganese, 395 Iron, 700 kamas.

Fight profiles

Base ships 24 fight profiles, as files of its fightprofiles folder. Every bot can play them, whether or not it plays base, by their name @asterobot:base/<name>, such as @asterobot:base/careful or @asterobot:base/ranged-kite. None of them names a spell, so each plays any class. The guide lists them in Ready-made profiles in base.

A script plays one with setProfile() or fightSettings({ profile: "@asterobot:base/careful" }). Their names stay the same from one version of base to the next.

Settings

@asterobot:base has its own card on the bot's Settings tab, whichever package uses it: see Settings in dependencies.

Setting Values Default What it changes
character A character's name Empty The character to play, whatever its capitals. Empty plays the first of the list.
community server, french, english, spanish, portuguese, german server The chat community the character joins on entering the world. server takes the one the server gives.
ping active, idle active active pings like a person using the client, which gives the server a real latency. idle never pings, like a client no one touches, and keeps reporting the last latency it measured: 0 if it never pinged.
bringUp faithful, minimal faithful faithful sends everything the Dofus client sends once the character has loaded: contacts, guild, chat community, the shop's token, the steps of the quests under way and the day's average prices. minimal sends only ContextCreationRequest, the one message needed to reach the map.

Started again on the same connection

Every step records how far it got in the bot's memory, under @asterobot:base/connection, next to session.connectionId. A script started again on the same connection, after Stop then Play or a Run from the editor, carries on from there: it doesn't identify a second time, keeps the character already chosen, and goes on counting the server's polls where the last script left them. After a reconnection, the connection has a new id, and everything starts over.

Reacting to what base answers

A message goes to every handler that listens for it, so base answering a message doesn't take it away from your package. Your own on() and wait() get every message base answers, MapCurrentEvent and MapCurrentInstanceEvent included, on both kinds of bot. This script logs each map change, whichever of the two announces it, then the cell the character lands on:

import "@asterobot:base";
import { currentMap, botInfo, botWarn } from "asterobot:bot";
import { on, wait } from "asterobot:protocol";

async function logArrival(traffic) {
  const mapId = traffic.payload?.mapId;
  if (mapId === undefined) return;

  botInfo("Heading to map", mapId);

  try {
    // Its contents come once the map has loaded, a moment after this.
    await wait(
      (next) => next.type === "MapComplementaryInformationEvent" && next.payload?.mapId === mapId,
      { timeout: 10000 },
    );
    botInfo("On map", mapId, "cell", currentMap()?.cellId);
  } catch (error) {
    // An error that escapes a handler stops the whole script.
    botWarn("Never arrived on map", mapId, String(error));
  }
}

export default async function behavior(launch) {
  // An instanced map is announced with the second message, with the same mapId.
  on("MapCurrentEvent", logArrival);
  on("MapCurrentInstanceEvent", logArrival);
}
  • Don't answer what base answers. On a Full socket bot, sending ContextReadyRequest, MapInformationRequest, or the answers to the proof of identity and the polls yourself would reach the server twice. Anything else is yours: log, store, look up, plan.
  • When MapCurrentEvent or MapCurrentInstanceEvent arrives, the new map hasn't loaded yet. Base says it's ready 0.4 to 2.4 seconds later, and only then does the server send its contents. Until then currentMap() already names the new map, but with no actors and no cellId. To act on it, wait for its MapComplementaryInformationEvent, as above.
  • Ids are BigInts. Compare them with BigInts, such as mapId === 153879301n. findPath() takes a map id as a BigInt, and record() doesn't: pass it Number(mapId).
  • Base's handlers run before yours, because base loads before your code. Nothing depends on it.

On a MITM bot

On a MITM bot, the Dofus client of the person playing does all of this. @asterobot:base then sends nothing and only follows the session. connect() resolves once the person playing stands on a map. A package that acts, walking or fighting, should still check session.shared itself, since the person controls the same character.

Good to know

  • When the character isn't placed on a map within a minute of entering the world, connect() rejects with base: the character's map never arrived within a minute of entering the world.
  • The Dofus client asks again for a quest's step whenever it changes. @asterobot:base asks only once, on entering the world.
  • A Dofus client whose chat channels were changed in its options also sends SubscribeMultipleChannelRequest on entering the world. A bot's options are the defaults, so @asterobot:base doesn't.
  • The day's average prices are asked once per day, UTC, and Asterobot forgets it asked when it restarts.
  • When the server asks to check a game file with FileCheckEvent, the bot's Console says so, and nothing answers it.
  • Load one version of @asterobot:base per script. Two packages that lock different versions of it load both, and each answers the server.

Compte

Navigation

Recherche

Recherche

Configurer les notifications push du navigateur

Chrome (Android)
  1. Appuyez sur l'icône de cadenas à côté de la barre d'adresse.
  2. Tap Autorisations → Notifications.
  3. Ajustez vos préférences.
Chrome (Desktop)
  1. Cliquez sur l'icône représentant un cadenas dans la barre d'adresse..
  2. Select Paramètres du site.
  3. Find Notifications et ajustez vos préférences.