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.

Bot memory

How the memory a bot's scripts share works, what it keeps and for how long, how to keep state for one game connection or for the whole bot, and when it's emptied.

A script's variables are gone every time it stops, whether through Stop, Run in the editor or a dropped connection. The bot's memory is where a script keeps what it must find again: a count, how far it got, a choice it made. It belongs to the bot, not to the script, and every package the bot runs reads and writes the same one.

This page explains how it behaves and how to use it. asterobot:bot has the reference of its four functions.

A first example

This script counts the chat messages its bot receives. The count goes on across Stop and Play, and across reconnections:

import { memory, session, botInfo } from "asterobot:bot";
import { on, send } from "asterobot:protocol";

const KEY = "@alice:chat-count/total";

export default async function behavior(launch) {
  on("ChatChannelMessageEvent", () => {
    const total = (memory.get(KEY) ?? 0) + 1;

    memory.set(KEY, total);
    botInfo(`${total} chat messages seen by this bot`);
  });

  // 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,
    });
  }
}

Stop the script after three messages and play it again: the next message shows 4 chat messages seen by this bot. A variable in the module would have started again from 0.

How it works

The memory is a set of keys, each naming one value.

Function What it does
memory.get(key) Returns a copy of the value stored under key, or undefined
memory.set(key, value) Stores a copy of value under key, replacing what was there
memory.delete(key) Removes key, and returns whether it was there
memory.keys() Returns every stored key, sorted

All four are synchronous: the memory is held by Asterobot, in the computer's memory, and nothing is written to disk.

Keys

A key is a string of up to 256 bytes. Every package on the bot sees every key, so start yours with your package's name, as in @alice:chat-count/total. Two packages then never pick the same key by accident. Nothing checks it: it's a convention.

Values

A value is plain data: null, a boolean, a number, a string, a BigInt, an array or a plain object, nested at most 32 levels. BigInts stay BigInts, which matters for the ids messages carry, such as map and character ids.

Anything else throws a TypeError that says where in the value the problem is. That includes undefined, a function, NaN, a Map, a Date, a symbol and an instance of a class. To remove a key, use memory.delete(), not undefined. To keep a Date, store date.getTime().

Values are copies

set() stores a copy, and get() returns a new copy each time. Changing what you read changes nothing stored:

import { memory } from "asterobot:bot";

const KEY = "@alice:farm/route";

// Adds a cell to the stored route.
export function addCell(cell) {
  const route = memory.get(KEY) ?? { cells: [] };

  route.cells.push(cell); // Changes this copy only.
  memory.set(KEY, route); // Now it's stored.
}

The keys of an object you read back come sorted by name, whatever order you wrote them in.

How long it lasts

The memory belongs to the bot. It's created with the bot, and it's gone only when the bot is:

What happens The script's variables The bot's memory session.connectionId currentMap()
Stop, then Play Start over Kept Same Kept
Run from the editor, or loading another package Start over Kept Same Kept
The script stops with an error Start over Kept Same Kept
Apply on the bot's Package settings, or a bot setting other than its account or game version changed Kept: the script keeps running Kept Same Kept
The game connection closes, and the bot connects again Start over Kept One more undefined until the character is on a map
A new Dofus session through a MITM bot Start over Kept One more undefined until the character is on a map
The bot's Ankama account or game version is changed Start over Emptied: the bot is rebuilt Counts from 1 again undefined
The bot is deleted Gone Gone
Asterobot restarts Gone Emptied for every bot Counts from 1 again undefined

A bot can't read another bot's memory, and every bot has its own.

Choosing how long your data lives

The memory keeps everything until the bot goes away. Most data belongs to something shorter, so store what it belongs to next to it, and ignore a value that belongs to something else.

For the whole bot

Store the value as it is, as the first example does. It's there on every connection, until the bot is rebuilt or Asterobot restarts.

For one game connection

Much of what a script learns only holds for one game connection: the connection was identified, the character was selected, the server sent three polls. After a reconnection it's wrong, and must be ignored.

session.connectionId says which connection a script runs on: a BigInt that grows by one each time the bot gets a new game connection, and stays the same for every start on one connection. Store it next to the value:

import { memory, session, botInfo } from "asterobot:bot";
import { on, send } from "asterobot:protocol";

const KEY = "@alice:chat-count/connection";

export default async function behavior(launch) {
  on("ChatChannelMessageEvent", () => {
    // Only a count stored on this game connection goes on: after a
    // reconnection, it starts over.
    const stored = memory.get(KEY);
    const count = (stored?.connection === session.connectionId ? stored.count : 0) + 1;

    memory.set(KEY, { connection: session.connectionId, count });
    botInfo(`${count} chat messages on this connection`);
  });

  // 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,
    });
  }
}

launch.reason can't replace the id. "initial" means no start has succeeded on the connection yet: a start that fails half way, after sending something, leaves the next one "initial" on the same connection. See When a start counts.

Keep the id inside the value rather than in the key. A key such as `@alice:chat-count/${session.connectionId}` adds a new key for every connection, and they pile up until the memory is full. With the id inside, each new connection's first set() replaces the old value.

For one character

The same bot can play different characters over time. For data about one character, store its id, from currentMap()?.characterId, next to the value, the same way as the connection's id.

Keeping a connection's state

A script that goes through steps, such as identifying and then waiting for its character, needs to know on each start how far the connection already got. Keep that in one object per connection, under one key, with the connection's id and a field for each step that has happened:

import { memory, session, botInfo } from "asterobot:bot";
import { on, send } from "asterobot:protocol";

const KEY = "@alice:hello/connection";

// The state of the current connection: what's stored, if it was stored on
// this connection, or a fresh state with only the id.
function connectionState() {
  const stored = memory.get(KEY);

  return stored?.id === session.connectionId ? stored : { id: session.connectionId };
}

// Adds fields to the current connection's state.
function updateConnection(changes) {
  memory.set(KEY, { ...connectionState(), ...changes, id: session.connectionId });
}

export default async function behavior(launch) {
  on("CharacterLoadingCompleteEvent", () => {
    updateConnection({ loadedAt: Date.now() });
  });

  const state = connectionState();

  if (state.loadedAt !== undefined) {
    const seconds = Math.round((Date.now() - state.loadedAt) / 1000);

    botInfo(`The character has been loaded for ${seconds} s`);
  }

  // Recorded before sending: a script stopped right after the send must never
  // identify a second time on this connection. A MITM bot's Dofus client has
  // already identified the session.
  if (!state.identified && !session.shared) {
    updateConnection({ identified: true });

    await send("IdentificationRequest", {
      ticketKey: session.gameToken,
      languageCode: session.language,
    });
  }
}

This script uses the state to identify instead of launch.reason. That way it identifies exactly once per connection, even when a start fails half way.

How this state behaves:

  • A field is absent until its step has happened. A fresh state is { id } and nothing else, so state.loadedAt === undefined means "not loaded yet on this connection".
  • Record before you send, when sending twice would be wrong. If the script stops between set() and send(), the next start knows it must not send again. The other order risks sending twice.
  • Handlers keep it up to date, whichever step the script is at, so a script started again later reads what happened while it ran.
  • It resets by itself on a new connection, without being deleted. The stored value keeps the old connection's id, so connectionState() reads it as a fresh state, and the first updateConnection() replaces it.
  • It's empty after Asterobot restarts, like the rest of the memory. The bot comes back disconnected, so there's no connection left for it to describe anyway.

@asterobot:base keeps its own state this way, under @asterobot:base/connection.

Sharing between packages

Every package the bot runs sees the same memory: its own package, its dependencies, and any package the bot plays later. A value one package stores is there for another to read.

// In @alice:scout: remember where the bot saw an ash tree.
import { memory } from "asterobot:bot";

export function sawAshTree(mapId, cellId) {
  const trees = memory.get("@alice:scout/ash-trees") ?? [];

  memory.set("@alice:scout/ash-trees", [...trees, { mapId, cellId }]);
}
// In @bob:harvest: read what the scout found, if anything.
import { memory } from "asterobot:bot";

export function ashTrees() {
  return memory.get("@alice:scout/ash-trees") ?? [];
}

A map id read from a message is a BigInt, and it comes back a BigInt: ashTrees()[0].mapId === 153879301n.

Only rely on keys a package says are meant to be shared, and only read them: the format of another package's key is that package's business, and can change with its next version. The keys of @asterobot:base are its own bookkeeping. Read them if you need to, and never write them.

Writing safely

A bot runs one script at a time, and all its packages run in that one script. The four functions are synchronous. So nothing else can write between your get() and your set(), as long as there's no await between them:

import { memory, botInfo } from "asterobot:bot";
import { sleep } from "asterobot:timers";

const KEY = "@alice:chat-count/total";

export async function countLater() {
  await sleep(1000);

  // Read, change and write with no await in between. Reading before the
  // sleep would lose whatever a handler added during it.
  const total = (memory.get(KEY) ?? 0) + 1;

  memory.set(KEY, total);
  botInfo("Total:", total);
}

set() throws when a value isn't plain data or doesn't fit the limits. In a handler, catch it: an error that escapes a handler stops the whole script.

import { memory, botWarn } from "asterobot:bot";
import { on } from "asterobot:protocol";

// Called from the entry function.
export function rememberLastMessage() {
  on("ChatChannelMessageEvent", (traffic) => {
    try {
      memory.set("@alice:chat/last", traffic.payload?.content ?? null);
    } catch (error) {
      // An error that escapes a handler stops the whole script.
      botWarn("Couldn't store the message:", String(error));
    }
  });
}

Limits

The limits are for the whole bot, all packages together:

Limit Value
Keys 1024
One key 256 bytes
One value 64 KiB
All values together 1 MiB
Levels of arrays and objects in a value 32

A value's size is roughly the size of its JSON text, with every number counted as 8 bytes. memory.delete() frees what a key held. The memory is meant for small pieces of state, not for data sets: Limits gives the exact error each limit throws.

While Asterobot reads your package's settings

Asterobot also runs the top level of your modules when it reads your package's settings, without a bot. At that moment get() returns undefined, keys() returns an empty array, and set() and delete() throw, which makes the reading fail. So call the memory functions from your entry function and your handlers, as the samples on this page do, not from the top level of a module. See Declarations.

Seeing what's stored

On a bot's page, Tools > Memory lists every key with its value and when it last changed, under Key, Value and Last changed. It refreshes by itself when a script changes the memory, and only reads it.

  • An empty memory shows Nothing stored yet.
  • A BigInt shows as its digits followed by n, as in 153879301n, and null shows as a dash.
  • When the list can't be read, the page shows Couldn't read this bot's memory. The refresh button next to the description reads it again.

The asterobot:bot reference has every rule and error of the four functions, and Sessions covers what else changes from one game connection to the next. The next chapter, Game protocol, goes into messages in detail.

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.