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.

Harvest and move on

A script that harvests what its character can on each map of a route, then takes the next map, with recipes for doors, suns, zaaps and maps whose side leads to several maps.

This script walks a route of maps. On each map it harvests every resource its character can, then leaves by the next side of the route. It shows how a script finds what can be used on a map, uses it, waits while a harvest lasts, and changes map, with asterobot:movement doing the walking. Recipes at the end cover doors, suns, zaaps and maps whose side leads to several maps.

You need a MITM bot, as Your first bot sets up, a character with a gathering job on a map that has resources it can harvest, and to know how to create a package and run it, as in Create a package.

The script

Create a package named harvest-route, replace the contents of its index.js with this code, and save:

import { botInfo, botWarn, currentMap, session } from "asterobot:bot";
import { record } from "asterobot:gamedata";
import { changeMap, useElement } from "asterobot:movement";
import { send, wait } from "asterobot:protocol";

// The sides to leave by, one map after the other. Write your own route here.
const ROUTE = ["right", "right", "bottom"];

// A harvest action gathers an item, and the skills table says which one.
// Every other action (use, save, open, sit...) gathers none.
function harvests(skillId) {
  return (record("skills", skillId)?.gathered_ressource_item ?? -1) > 0;
}

// The elements of the character's map with a harvest action on offer right
// now, and that action.
function harvestable() {
  return (currentMap()?.elements ?? []).flatMap((element) => {
    const skill = element.skills.find((offer) => harvests(offer.id));

    return skill ? [{ id: element.id, skillId: skill.id }] : [];
  });
}

async function harvestMap() {
  for (const { id, skillId } of harvestable()) {
    try {
      const use = await useElement(id, { skill: skillId });
      botInfo(`Harvesting element ${id} for ${use.duration} ms`);

      // The character stays busy until the game says the harvest is over.
      if (use.duration > 0) {
        await wait(
          (traffic) => traffic.type === "InteractiveUseEndedEvent" && traffic.payload?.elementId === id,
          { timeout: use.duration + 5000 },
        );
      }
    } catch (error) {
      // Harvested by someone else meanwhile, out of reach, or refused by the
      // game: leave it and try the next one.
      botWarn(`Couldn't harvest element ${id}: ${String(error)}`);
    }
  }
}

export default async function behavior(launch) {
  // A new game connection starts with this message. MITM bots never launch
  // with "initial": the Dofus client has already identified their session.
  if (launch.reason === "initial") {
    await send("IdentificationRequest", {
      ticketKey: session.gameToken,
      languageCode: session.language,
    });
  }

  for (const side of ROUTE) {
    await harvestMap();

    try {
      const { mapId, cellId } = await changeMap(side);
      botInfo(`Left by the ${side}: now on map ${mapId}, cell ${cellId}`);
    } catch (error) {
      botWarn(`Couldn't leave by the ${side}: ${String(error)}`);
      return;
    }
  }

  await harvestMap();
  botInfo("Route done");
}

How it works

Finding what to harvest

currentMap() lists the map's interactive elements in elements. Each one has its id and the actions it offers right now in skills, each with the skill id. A resource that was just harvested offers nothing until it regrows, so it's left out on its own.

The elements don't say what they are. The skill does: each skill is a row of the game data's skills table, and a harvest skill's gathered_ressource_item is the item it gathers, such as 289, wheat, for skill 45. Other skills, such as 184, "Use", have -1 there. To harvest only one kind of resource, compare gathered_ressource_item with that item's id instead.

harvestable() reads the map once, when harvestMap() starts. By the time the script gets to an element, another player may have harvested it: useElement() then rejects, and the script moves on.

Using an element

useElement(id, { skill }) does what the Dofus client does when the player clicks the element: the character walks within reach of it, then the game is asked to use it. Naming the skill matters on elements that offer several actions: without it, useElement() rejects with move: the element offers several actions; name the one to use.

The promise resolves as soon as the game accepts the use, with duration, how long the harvest lasts in milliseconds. During that time the character can't use anything else, and useElement() would reject with move: the character is still using an element. So the script waits for InteractiveUseEndedEvent about that element, which the game sends when the harvest is over. Like every wait(), it has a timeout, here the harvest's duration and five seconds more.

Moving on

changeMap(side) walks the character to the exit of that side it reaches soonest, takes it to the next map, and resolves once the new map has loaded, so the next harvestMap() reads the new map's elements. When a side has no exit, or none the character can walk to, it rejects, and the script stops there with a line in the console.

What the script sends

useElement() and changeMap() send the walks, the confirmations a Full socket bot needs, InteractiveUseRequest and MapChangeRequest. On a MITM bot your Dofus client loads each new map. The script itself sends nothing but the identification, on a Full socket bot.

On a Full socket bot

The identification doesn't take a Full socket bot's character into the game: choosing the character and entering the world are a script's too. @asterobot:base does all of it. In a package that depends on base, replace the identification block with await connect() from @asterobot:base, which resolves once the character stands on its map. Base also asks for every new map, and changeMap() would do it itself if nothing did.

Run it

  1. Start waiting on your MITM bot, start Dofus and sign in, and choose a character with a gathering job. Walk it to a map with resources it can harvest.
  2. Change ROUTE to the sides that lead from that map to the next ones you want to harvest.
  3. In the editor, pick the bot and click Run.
  4. On the bot's page, open Console and untick Asterobot under Channels, at the left of the chat box.

Your character walks to each resource and harvests it, then leaves the map, as if you had clicked. Don't click in Dofus while it runs: your own walk would replace the script's. The lines look like this:

Harvesting element 489389 for 3000 ms
Harvesting element 489391 for 3000 ms
Couldn't harvest element 489402: GoError: move: the server refused the use (element 489402)
Left by the right: now on map 154010883, cell 293
Harvesting element 490017 for 3000 ms
Route done

The bot's Map tab shows the same walks live, and its Resources and objects list turns each harvested resource Unavailable.

Recipes

These pieces go inside a script like the one above, which already imports botInfo, botWarn, currentMap, record, changeMap, useElement and wait. Add query from asterobot:gamedata where a recipe uses it.

Go through a door

A door, a staircase or a teleporter is an element whose action takes the character to another map. The game's travel graph says which elements of a map do it, with the action to use and the map they lead to:

const result = await query(
  `SELECT t.transition_id, t.skill_id, destination.map_id
     FROM world_transitions t
     JOIN world_vertices origin ON origin.uid = t.from_uid
     JOIN world_vertices destination ON destination.uid = t.to_uid
    WHERE origin.map_id = ? AND t.type = 32`,
  [Number(currentMap().mapId)],
);

// [elementId, skillId, destinationMapId] for each door of the map, or nothing
// when the bot's game version has no game data.
const doors = result?.rows ?? [];

useElement() resolves once the game accepts the use, and the new map comes a moment later. To go on once it has loaded, wait for its MapComplementaryInformationEvent, started before the use so it can't be missed:

const [elementId, skillId] = doors[0];
const before = currentMap().mapId;

await Promise.all([
  wait(
    (traffic) => traffic.type === "MapComplementaryInformationEvent" && traffic.payload?.mapId !== before,
    { timeout: 30000 },
  ),
  useElement(elementId, { skill: skillId }),
]);

On a Full socket bot, the new map only loads when something asks for it: @asterobot:base does, or your script answers MapCurrentEvent and MapCurrentInstanceEvent itself. Unlike changeMap(), a door's use doesn't do it for you.

Take a sun

An exit drawn as a sun is an element whose only action is skill 339, Point Out an Exit. useElement() walks the character onto it, and resolves on the next map, once that map has loaded:

const sun = currentMap()?.elements.find(
  (element) => element.skills.length === 1 && element.skills[0].id === 339,
);

if (sun) {
  const { mapId } = await useElement(sun.id);
  botInfo(`Through the sun to map ${mapId}`);
}

Use a zaap

A zaap offers two actions: 114, "Use", which opens its destinations, and 44, "Save", which saves it as your respawn point. Name the one you want:

const zaap = currentMap()?.elements.find((element) => element.skills.some((offer) => offer.id === 44));

if (zaap) await useElement(zaap.id, { skill: 44 });

Choosing a destination after Use isn't built in: the game sends the destinations, and travelling to one is a message of its own.

Leave from a given exit

On a few maps, one side leads to different maps depending on the exit cell, such as the top and the bottom of the right side. Pass the cell to leave from:

await changeMap("right", { cell: 475 });

Choose the exit shows how to list a side's exit cells and find which ones lead where.

Try again after a refusal

The game can refuse a walk, for example when another actor got in the way. The error's message says so, and a script can try again:

async function changeMapPatiently(side, tries = 3) {
  for (let attempt = 1; ; attempt++) {
    try {
      return await changeMap(side);
    } catch (error) {
      if (attempt >= tries || !String(error).includes("the server refused the walk")) throw error;
      botWarn(`Walk refused, trying again (${attempt}/${tries})`);
    }
  }
}

asterobot:movement lists every error text these functions reject with.

Ideas to take it further

  • A route from the bot's settings. Declare the route as a setting, as Declare settings shows, so it changes without editing the script.
  • Only one resource. Keep the elements whose skill gathers the item you want, with its id from Item names from game data.
  • Stop when the pods are full. Inventory watcher follows the pods: end the route when they reach the maximum.
  • Loop the route. Make it a circle, such as right, bottom, left, top, and run it until the script is stopped.

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.