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.

Smooth

Administrateurs

Tout ce qui a été publié par Smooth

  1. Smooth a posté un record dans Tutoriels
    Messages name items by number. This script turns those numbers into the names players know, with asterobot:gamedata: when a stack's quantity changes, the console reads Blé: 43 (+3) rather than Stack 812734 (item 289): 43 (+3). A List inventory button also writes the whole inventory by name. It starts from the stacks of the Inventory watcher and leaves kamas and pods out. Read that page first if uid and gid are new to you. Find the names in the game data InventoryContentEvent gives each stack a gid: the id of the item the stack is made of. The item's name is in the game data of the bot's game version, in two tables: items has one row per item. Its id column holds the id the game uses in its messages, the gid. Its _id column is the key Asterobot gives the row, which isn't always the same number, as Lookups explains. _names holds the names of records, one row per name and per language. record_table and record_id point to the record by its key, language holds the language code, and ordinal 0 marks the record's main name. So a query finds the items by id, and joins their names on _id. Try it in the SQL tab before writing any code: On the bot's Inventory tab, with Live inventory on, open a row. Its details show the item's id after item. In Game Manager, open the bot's game version, go to its Game data tab, then to SQL. Type this query, with ids from your inventory in place of 289, 290, and click Run: SELECT i.id, n.name FROM items i JOIN _names n ON n.record_table = 'items' AND n.record_id = i._id AND n.language = 'fr' AND n.ordinal = 0 WHERE i.id IN (289, 290) Each item comes back on its own row, with its French name. SQL queries describes the tab. The script Create a package named item-names, replace the contents of its index.js with this code, and save: import { session, botInfo, botWarn } from "asterobot:bot"; import { query } from "asterobot:gamedata"; import { on, send } from "asterobot:protocol"; // A query returns 5,000 rows at most, so ids are looked up in batches. const BATCH_SIZE = 500; function namesQuery(count) { return ` SELECT i.id, n.name FROM items i JOIN _names n ON n.record_table = 'items' AND n.record_id = i._id AND n.language = ? AND n.ordinal = 0 WHERE i.id IN (${Array(count).fill("?").join(", ")})`; } const stacks = new Map(); // uid => { gid, quantity } const names = new Map(); // gid => name, kept for the whole run async function loadNames(gids) { const missing = [...new Set(gids)].filter((gid) => !names.has(gid)); for (let start = 0; start < missing.length; start += BATCH_SIZE) { const batch = missing.slice(start, start + BATCH_SIZE); const result = await query(namesQuery(batch.length), [session.language, ...batch], { limit: batch.length, }); // undefined rather than a result: this game version has no game data. if (result === undefined) throw new Error("No game data for this bot's game version"); for (const [gid, name] of result.rows) names.set(gid, name); } } function nameOf(gid) { return names.get(gid) ?? `item #${gid}`; } function updateStack(uid, quantity) { const stack = stacks.get(uid); // Only stacks from the last full inventory have an item to name. if (!stack) return; const change = quantity - stack.quantity; stack.quantity = quantity; botInfo(`${nameOf(stack.gid)}: ${quantity} (${change >= 0 ? "+" : ""}${change})`); } /** @type {BehaviorActions} */ export const actions = { listInventory: { label: "List inventory", description: "Writes every stack the character carries to the bot's console.", run() { if (stacks.size === 0) { botInfo("The game hasn't sent the inventory since the script started."); return; } const lines = [...stacks.values()].map((stack) => `${nameOf(stack.gid)} x${stack.quantity}`); for (const line of lines.sort()) botInfo(line); }, }, }; export default async function behavior(launch) { on("InventoryContentEvent", async (traffic) => { // A message Asterobot couldn't decode has no payload, and an error // thrown in a handler stops the whole script. if (!traffic.payload) return; // Filled before the lookup, so quantity updates arriving meanwhile find their stack. stacks.clear(); for (const { item } of traffic.payload.objects) { if (item) stacks.set(item.uid, { gid: item.gid, quantity: item.quantity }); } try { await loadNames([...stacks.values()].map((stack) => stack.gid)); const named = [...stacks.values()].filter((stack) => names.has(stack.gid)).length; botInfo(`Inventory received: ${stacks.size} stacks, ${named} of them named`); } catch (error) { // An error that escapes a handler stops the whole script. botWarn("Couldn't look up item names:", String(error)); } }); on("ObjectsQuantityEvent", (traffic) => { for (const record of traffic.payload?.object ?? []) { updateStack(record.objectUid, record.quantity); } }); on("ObjectQuantityEvent", (traffic) => { const record = traffic.payload?.object; if (record) updateStack(record.objectUid, record.quantity); }); // 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, }); } } How it works One query for the whole inventory Looking items up one at a time would take a find() call per item. A single query() with IN (?, ?, ...) names the whole inventory at once, and like every game data call that returns a promise, it does its work outside your code while the bot keeps handling messages. Game data overview explains the difference. A few details of loadNames(): namesQuery() writes one ? for each id. The values go in the params array in the order of the ? in the SQL: the language first, then the ids. Parameters keep their type, so the ids are compared as numbers. query() returns 200 rows unless you pass a limit, and 5,000 at most. Each batch asks for as many rows as it has ids, and BATCH_SIZE keeps every batch far below that maximum. Rows are arrays in the order of the SELECT, so const [gid, name] reads the id first and the name second. query() resolves to undefined when the bot's game version has no extracted game data. The script throws then, and the handler's catch writes why on the console. Names in the script's language The query asks for names in session.language. For every start made from Asteroboard that's fr, whatever language the bot itself is set to, so the names come in French. Game data overview explains why. To get another language, pass its code instead of session.language. Stacks first, names second The InventoryContentEvent handler fills stacks before it awaits the query. Handlers don't wait for each other, so a quantity update can arrive while the names are still being looked up, and it still finds its stack. Until the query returns, such a stack is written as item # followed by its gid, which is also how an item the game data doesn't know is written. names lasts for the whole run. When the inventory arrives again, only items without a name yet are looked up. Like every variable, it starts empty at the next start. The List inventory button export const actions puts a List inventory button in the package's section of the bot's Settings tab. Its run reads the same stacks and names as the handlers, and writes one line per stack. sort() compares characters one by one, so capitals come before small letters and names starting with an accented letter come after z. Asterobot keeps the last 200 lines of each bot's console, so a large inventory pushes older lines out of what it keeps. Declare actions covers buttons. Run it Check that the bot's game version has game data: in Game Manager, its row should show Ready for bot. A MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play) can run without it, and the script then writes Couldn't look up item names: Error: No game data for this bot's game version. Extracting the data while the script runs isn't enough: start the script again afterwards. As for the Inventory watcher, start waiting on your MITM bot, start Dofus and stop at the character selection screen. Once the bot shows Connected to game server, pick it in the editor and click Run. On the bot's Console tab, keep only Script in Sources, then choose your character in Dofus. A line such as Inventory received: 57 stacks, 57 of them named appears. Play. When the quantity of one of your stacks changes, a line such as Blé: 43 (+3) appears. Open the bot's Settings tab, then Package settings, and click List inventory under Actions. A toast says Action started, then another one says listInventory finished, and the console lists your stacks by name. Ideas to take it further Another language. _names has rows for each language the game data holds. Run SELECT DISTINCT language FROM _names in the SQL tab to see which, then add a string setting with choices and pass values.language to the query instead of session.language. The item's type. Join item_types on item_types.id = items.type_id, then its name through _names in the same way. The SQL tab's Examples on items do something similar. Watch a few items. Add a list setting of item names, and write a warning when one of those stacks reaches a quantity you choose. From a name to an id. search("Blé", { table: "items" }) finds items by name. Each result's id is the row's key: read the row with record() to get its id column, the one messages use. See Lookups. The Dofus protocol reference lists the other messages you can build on, and Game data overview everything else a script can read.
  2. Smooth a posté un record dans Tutoriels
    This script keeps track of what the character carries and writes each change to the bot's console: kamas spent or earned, pods going up or down, and the new quantity of a stack. It listens to the same messages as the bot's Inventory tab, and shows how a script keeps what it learned from one message to the next. You need a MITM bot, as Your first bot sets up, and to know how to create a package and run it, as in Create a package. The messages it follows Message What it carries Fields the script reads InventoryContentEvent The whole inventory, sent once when the character enters the game kamas, and objects, with one entry per stack whose item holds the stack's uid, gid and quantity KamasUpdateEvent The character's kamas quantity, the new balance InventoryWeightEvent The character's pods inventoryWeight, what the inventory weighs, and weightMax, the most it can weigh before the character is overloaded ObjectsQuantityEvent New quantities for stacks the character already has object, a list of entries with objectUid and quantity ObjectQuantityEvent A new quantity for one stack object, one entry with objectUid and quantity These are the messages behind the Live inventory switch of the bot's Inventory tab. That switch only changes what Asteroboard shows: the script receives the messages whether it's on or not. The script Create a package named inventory-watcher, replace the contents of its index.js with this code, and save: import { session, botInfo, botWarn } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; // What the script knows about the inventory. It all starts empty at each // start, and fills in as the game reports things. let kamas; // a BigInt, once known let pods; // { current, max }, once known const stacks = new Map(); // uid => { gid, quantity } function signed(change) { return change >= 0 ? `+${change}` : `${change}`; } function updateStack(uid, quantity) { const stack = stacks.get(uid); if (!stack) { // The last full inventory didn't have this stack, so there's nothing to compare. botInfo(`Stack ${uid}: ${quantity}`); return; } const change = quantity - stack.quantity; stack.quantity = quantity; botInfo(`Stack ${uid} (item ${stack.gid}): ${quantity} (${signed(change)})`); } export default async function behavior(launch) { on("InventoryContentEvent", (traffic) => { // A message Asterobot couldn't decode has no payload, and an error // thrown in a handler stops the whole script. if (!traffic.payload) return; kamas = traffic.payload.kamas; stacks.clear(); for (const { item } of traffic.payload.objects) { // item holds a message, so it's only there when the game set it. if (item) stacks.set(item.uid, { gid: item.gid, quantity: item.quantity }); } botInfo(`Inventory received: ${stacks.size} stacks, ${kamas} kamas`); }); on("KamasUpdateEvent", (traffic) => { const balance = traffic.payload?.quantity; if (balance === undefined) return; // The game sends the new balance, not what changed. const change = kamas === undefined ? "" : ` (${signed(balance - kamas)})`; kamas = balance; botInfo(`Kamas: ${balance}${change}`); }); on("InventoryWeightEvent", (traffic) => { if (!traffic.payload) return; const { inventoryWeight: current, weightMax: max } = traffic.payload; // Not worth a line when neither number moved. if (pods && pods.current === current && pods.max === max) return; const change = pods ? ` (${signed(current - pods.current)})` : ""; pods = { current, max }; if (current > max) { botWarn(`Pods: ${current} / ${max}${change}, overloaded`); } else { botInfo(`Pods: ${current} / ${max}${change}`); } }); on("ObjectsQuantityEvent", (traffic) => { for (const record of traffic.payload?.object ?? []) { updateStack(record.objectUid, record.quantity); } }); on("ObjectQuantityEvent", (traffic) => { const record = traffic.payload?.object; if (record) updateStack(record.objectUid, record.quantity); }); // 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, }); } } How it works What the script remembers kamas, pods and stacks sit at the top of the module, outside the handlers, so every handler reads and changes the same values. They start empty at each start of the script. Messages that arrived before the start are never delivered, and the game only sends the whole inventory as the character enters the game, so until then the script knows nothing about the stacks. Play, stop and reload explains what a new start keeps. Totals, not changes Each of these messages carries a new total: the new balance, the new weight, the stack's new quantity. To say what changed, the script keeps the previous value and subtracts it. The first value it gets has nothing to be compared with, so its line has no change in brackets. Kamas are BigInt values kamas, and quantity in KamasUpdateEvent, are 64-bit fields, so they read as BigInt values such as 125000n. Subtracting a BigInt from another works, and so does comparing a BigInt with 0, which is why signed() serves kamas and pods alike. Arithmetic that mixes a BigInt with a regular number throws a TypeError, though: kamas - 500 fails where kamas - 500n works. Pods and stack quantities are 32-bit fields, and read as regular numbers. Payloads has the rules. Logging a BigInt writes its digits, without the n. uid and gid A stack has two ids. uid names this very stack, and quantity updates point to it as objectUid. gid names the item the stack is made of, and every stack of that item shares it. That's why stacks is keyed by uid. The script keeps the gid anyway: the next tutorial turns it into the item's name. In InventoryContentEvent, each entry of objects wraps its stack. Besides item, it says where the item is equipped and whether it's a favourite. item holds a message, which is only in the payload when the game set it, so the script checks it before reading it. What it doesn't see A stack the character didn't have before, such as a new loot, doesn't come through these messages: Asterobot has no readable name yet for the messages the game uses for it. A stack that leaves the inventory completely can go unnoticed too. The script learns about both with the next InventoryContentEvent, when the character enters the game again. Until then, a quantity update for a stack it doesn't know only gives the uid and the new quantity. Run it The whole inventory only arrives as the character enters the game, so the script has to be running by then. Start waiting on your MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play), start Dofus and sign in. Stop at the character selection screen. If your character is already in the game, go back to that screen. Once the bot's header shows Connected to game server, pick the bot in the editor and click Run. On the bot's page, open Console and keep only Script in Sources. Choose your character in Dofus. A line such as Inventory received: 57 stacks, 125000 kamas appears. Play as usual. When your kamas, your pods or the quantity of one of your stacks change, a line appears. The lines look like this: Inventory received: 57 stacks, 125000 kamas Pods: 1250 / 3000 Kamas: 124500 (-500) Pods: 1262 / 3000 (+12) Stack 812734 (item 289): 43 (+3) The script only reads. Apart from the identification on a Full socket bot, it sends nothing to the game. Ideas to take it further Names instead of ids. Item names from game data turns each gid into the item's name. A warning before the pods are full. Read settings builds one, with a setting for the limit. Kamas earned since the start. Keep the first balance the script receives, and add a button that writes the difference: see Declare actions. Stop a Full socket bot when its inventory is full. Sessions shows disconnect() on the same message, and why a MITM bot must never call it.
  3. Smooth a posté un record dans Tutoriels
    This script writes each chat line the bot receives to the bot's Console tab, as [GUILD] Airelle: anyone up for a dungeon?. It goes further than the handler in React to messages: package settings let people pause it, keep only the channels they choose and leave out some players, their own character included, and staff lines stand out as warnings. The page assumes you've already created a package and run it on a bot. If you haven't, start with Create a package and Identify and run. The script In Library Manager, click New package and name it chat-logger. Replace the contents of index.js with the code below, and save. import { session, botInfo, botWarn } from "asterobot:bot"; import { values } from "asterobot:parameters"; import { on, send } from "asterobot:protocol"; /** @type {BehaviorParameters} */ export const parameters = { enabled: { type: "bool", label: "Log chat", default: true, }, channels: { type: "array", of: "string", label: "Channels", description: "Channel names to log, such as GUILD or PARTY. Leave it empty to log every channel.", }, ignored: { type: "array", of: "string", label: "Ignored characters", description: "Character names whose lines aren't logged. Add your own to leave out what you say.", }, }; export default async function behavior(launch) { on("ChatChannelMessageEvent", (traffic) => { // A message Asterobot couldn't decode has no payload, and an error // thrown in a handler stops the whole script. if (!traffic.payload || !values.enabled) return; const { senderName, content, fromAdmin } = traffic.payload; // A channel Asterobot has no name for reads as a number. const channel = String(traffic.payload.channel); // Read on every line, so an applied change counts from the next one. const channels = values.channels.map((name) => name.trim().toUpperCase()); if (channels.length > 0 && !channels.includes(channel)) return; const sender = senderName.toLowerCase(); if (values.ignored.some((name) => name.trim().toLowerCase() === sender)) return; const line = `[${channel}] ${senderName}: ${content}`; // Staff lines are rare and usually matter, so they show as warnings. if (fromAdmin) { botWarn(line); } else { botInfo(line); } }); // 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, }); } } How it works The settings export const parameters declares three settings. They show on the bot's Settings tab, under Package settings, in the chat-logger section: Setting Type On the bot's page Default enabled bool Log chat, a switch On channels array of string Channels, a list with Add under its rows Empty ignored array of string Ignored characters, a list with Add under its rows Empty A list can't offer a drop-down, because choices only works on string and int settings, so people type the names themselves. The script forgives the usual slips: it trims spaces, compares channel names in capitals, and ignores upper and lower case in character names. Declare settings covers every field used here. Reading the settings on every line The handler reads values.enabled, values.channels and values.ignored each time a chat line arrives. values always holds what was last applied on the bot's page, so a change counts from the next line, with no restart. That's also why turning Log chat off doesn't stop the script: the handler still runs for each line, and returns straight away. Read settings explains values. The handler awaits nothing, so it needs no try/catch. The check at its top keeps it from reading a payload that isn't there. Channel names channel reads as the name of the channel the line was said on. The ones you'll use most: Name Channel GLOBAL The general channel, which the players around the character see GUILD Guild ALLIANCE Alliance PARTY Party TEAM Team SALES Trade, where players advertise what they sell SEEK Recruitment, to look for players or groups COMMUNITY The community channel, split by language The Dofus protocol reference lists every channel. When Asterobot has no name for a channel's value, channel reads as that value's number instead of a name. String() turns it into text, so a number typed in Channels matches it like a name would. Your own lines The game sends your character's lines back to you like everyone else's, so the logger receives them too. session doesn't say which character the bot plays, so the plain way to leave your lines out is to put your character's name in Ignored characters. The same list works for any player you'd rather not read. Staff lines fromAdmin is true on lines the game marks as staff lines. The script writes those with botWarn() instead of botInfo(), so they show at the WARN level among the INFO lines. Run it Test it on a MITM bot (man-in-the-middle: the bot relays the game session of the Dofus client you play). Bring your MITM bot online and enter the game with your character, as Your first bot shows. In the editor, pick that bot and click Run. A toast says Running on followed by the bot's name. On the bot's page, open Console. In Sources, keep only Script. Say something in the Dofus chat. A line such as [GLOBAL] Airelle: hello appears. Then try the settings: Open the bot's Settings tab, then Package settings. In the chat-logger section, click Add under Ignored characters and type your character's name. Not applied appears next to the package's name. Click Apply. A toast says chat-logger settings applied. Say something in the Dofus chat again. This time no line appears. Click Add under Channels, type GUILD and click Apply. From now on, only guild lines are logged. Turn off Log chat and click Apply. Nothing is logged any more, yet the bot's header still shows Stop: the script runs and waits for the switch to come back on. The settings are saved with the bot, so they're still there the next time the script starts. The script runs on a Full socket bot too, but chat only reaches a character that is in the game. This script identifies the bot and nothing more, so on a Full socket bot it needs a connection script that also chooses a character. Ideas to take it further Highlight some words. Add a keywords list setting, and write the lines that contain one of them with botWarn(). Ignore a person rather than a character. senderAccountId stays the same when a player switches characters. It's a 64-bit field, so it reads as a BigInt: compare it with BigInt values, as Payloads explains. Count lines per channel. Keep the counts in a Map, and add a button that writes them to the console: see Declare actions. Answer some lines. Send messages shows how to reply and check that the reply went through. Next, the Inventory watcher follows kamas, pods and item quantities.
  4. Smooth a posté un record dans Reference
    Extension modules are extra modules that Asterobot will be able to offer scripts, next to the nine built-in ones, for abilities those don't cover. Their names will start with asterobot:extension/, and a script will import them like any built-in module. Note Coming soon. No extension module is available to scripts yet. Until then, a script can use everything the built-in modules described in this reference offer. Importing one today A script that imports a module whose name starts with asterobot:extension/ doesn't start. The same goes for any other asterobot: name that isn't a built-in module, such as asterobot:fs. The reason is: resolve "asterobot:extension/<name>" imported by "<module>": built-in module "asterobot:extension/<name>" is not registered As for any import that can't be resolved, the toast after Play or Run doesn't show this text. The bot's Settings tab does: Package settings shows Couldn't read this package's settings with the reason. Error messages lists the other texts you can meet there. The modules a script can import today are: asterobot:protocol, to exchange messages with the game; asterobot:bot, for the session, the bot's console and disconnecting; asterobot:timers, for sleep(); asterobot:gamedata, to read game texts and data; asterobot:console, to write to Asterobot's log; asterobot:runtime, to read package information; asterobot:parameters, to read the package's settings.
  5. Smooth a posté un record dans Reference
    The error texts you can meet while writing and running scripts are grouped here by where they appear. Search for a part of the text you see. In the texts, a word in angle brackets such as <module> stands for something that changes: <module> is the path of a file inside Asterobot, such as library/my-first-script/1.0.0/index.js, <type> a message's name, <field> a field's name. Where you see it What it looks like Section At the top of the bot's page The behavior stopped with an error, with the full text, Copy and Dismiss The script stopped A toast after Play, Run, a load or a version switch Couldn't start the behavior, Couldn't run the package, Couldn't load the selection or Couldn't switch version, without a reason When a package doesn't load Package settings, on the bot's Settings tab, and the Add a bot dialog Couldn't read this package's settings, with the reason When a package doesn't load, and Declarations Inside the script A rejected promise, or an error thrown by a call, that your code can catch Errors a script can catch The bot's Console tab A warning or error line, while the script keeps running Console lines Toasts on Package settings Couldn't apply the settings, Couldn't start the action, or an action's name followed by failed Saving settings and running actions Under the package's manifest, after saving it The reason, or Couldn't save the manifest Saving the manifest The script stopped The behavior stopped with an error appears at the top of the bot's page when a script that had loaded stops because of an error, while starting or later. In Bot Manager, the bot's Behavior column says Failed. The game connection stays open. Fix the cause, then start the script again with Play, or with Run in the editor. The beginning of the text says where the script was: The text starts with The error happened evaluate behavior module "<module>": In the top-level code of the modules, as the script started read declarations of "<module>": While Asterobot read the parameters and actions exports. See Declarations. initialize behavior module "<module>": In the entry function, before it finished Game traffic handler failed: In a handler registered with on() or onTraffic() evaluate protocol handler selector: In a selector function passed to on() evaluate protocol wait selector: In a selector function passed to wait() parameters.onChange handler <number>: In a handler registered with onChange() unhandled Promise rejection: Anywhere: a promise rejected and nothing caught it drain JavaScript Promise jobs: In code that resumed after an await What follows is the error itself: Promise rejected: and the reason, when a promise the start was waiting for rejected. An error thrown by your code: its type and message, such as TypeError: and the message. For an error thrown synchronously, at follows, with the function's name and, in parentheses, the file, the line and the column. A message from one of Asterobot's functions, as listed in Errors a script can catch. For example, a script that sends token instead of ticketKey while starting gets: initialize behavior module "library/my-first-script/1.0.0/index.js": Promise rejected: IdentificationRequest.token: unknown protobuf field These texts can appear after one of the beginnings above, or on their own: Text Cause Fix behavior module "<module>" has no unambiguous default export index.js has no default export Add export default async function behavior(launch) behavior module "<module>" default export is not callable The default export isn't a function Export a function behavior module "<module>" default export must be async and return a Promise The default export returned something other than a promise Make it an async function dynamic import() is not supported by this Asterobot runtime The script called import() as a function Use an import statement at the top of the file JavaScript execution deadline exceeded Code ran for more than 250 ms without an await Split the work into parts separated by an await, such as await sleep(0). See Limits. behavior event-loop queue overflow More than 64 events piled up while the script was busy Keep handlers short, and move slow work out of them unhandled Promise rejection: <reason> A promise rejected with nothing to catch it: an await outside try/catch in an async handler or interceptor, or a promise started without await Wrap the await in try/catch, or add .catch() Game session closed: <reason> The game connection closed while the script was running Connect the bot again, then play the script behavior requested Game disconnect The script called disconnect() Nothing, if that was intended A script stopped with Stop shows no alert. When a package doesn't load When a script can't even be loaded, it never runs, and nothing reaches the Problems alert. The toast is all you get, without the reason: Couldn't start the behavior after Play, Couldn't run the package after Run, Couldn't load the selection after a load from the bot's menu, Couldn't switch version after changing the version. To see the reason, open the bot's Settings tab. Asterobot loads a package the same way to read its settings, so for the bot's package, Package settings shows Couldn't read this package's settings with the text. The package's settings in the Add a bot dialog show it too. For a syntax error, the editor's error badge usually points at the line as well. When the reason comes from the package's code, the text there starts with <package>@<version>: read declarations:. Syntax and imports Text Cause Fix parse module "<module>": <details> That file has a syntax error Fix the syntax. Check the editor's error badge. resolve "<specifier>" imported by "<module>": package "<package>" has no locked dependency for "<specifier>" An import names a package that isn't among the package's dependencies, or is misspelled Fix the name, or add the dependency. See Dependencies. resolve "<specifier>" imported by "<module>": module "<module>" imported by "<module>" is not installed A relative import names a file the package doesn't have Fix the path. Relative imports need the whole file name, extension included, such as ./helper.js. resolve "<specifier>" imported by "<module>": relative import "<specifier>" escapes package root "<root>" A ../ goes above the package's own folder Import only files of the package, or of a dependency by its name resolve "<specifier>" imported by "<module>": built-in module "<specifier>" is not registered The asterobot: module doesn't exist, which is the case for every asterobot:extension/ module today Import one of the nine built-in modules. See Extension modules. resolve "<specifier>" imported by "<module>": package "<package>": no installed version of "<name>" satisfies "<version>" An import such as "name@^2.0.0" asks for a version the script's packages don't include Lock a matching version in the dependencies resolve "<specifier>" imported by "<module>": package "<package>": specifier "<specifier>" has an invalid version "<version>": <details> The part after @ in the import isn't latest, a version or a version range Fix the version resolve "<specifier>" imported by "<module>": absolute module specifier "<specifier>" is forbidden An import starts with / Use a relative path or a package name resolve "<specifier>" imported by "<module>": module specifier "<specifier>" contains a backslash An import path uses \ Use / reachable module count exceeds limit 256 The script loads more than 256 modules See Limits reachable module source exceeds limit of 4194304 bytes The script's modules weigh more than 4 MiB together See Limits module graph depth <depth> exceeds limit 64 at "<module>" Imports chain more than 64 modules deep See Limits Packages and dependencies These texts start with resolve package <name>@<version>:, naming the package being loaded. A problem in one of its dependencies adds dependency "<name>" locked to version "<version>": before the reason, once per level. Text Cause Fix read package <name>@<version> sources: <details> Asterobot couldn't read that package's files, for example because that version isn't installed Install the package or the dependency at that version package <name>@<version> is not installed: parse asterobot.json: <details> The package's asterobot.json isn't valid JSON Fix the file. See The manifest. package "<name>@<version>": package "<name>" requires asterobot <range>, running <version> The package's compatibleVersion doesn't include the Asterobot version you run Update Asterobot, or change compatibleVersion. See Compatibility. package "<name>@<version>": package "<name>" has an invalid compatibleVersion constraint "<range>": <details> compatibleVersion isn't a valid version range Fix it, such as >=1.5.0 <2.0.0 package "<name>@<version>": package source integrity mismatch The files of a package installed from the marketplace no longer match the checksum recorded at install Install that version again package "<name>@<version>": empty package permission permissions in asterobot.json has an empty entry Remove it package "<name>@<version>": duplicate package permission "<permission>" permissions lists the same permission twice Remove the duplicate package "<name>@<version>": entry module "index.js" is missing The package has no index.js at its root Add it. See Package layout. package "<name>@<version>": package contains no modules The package has no .js or .mjs file Add index.js package "<name>@<version>" dependency "<specifier>" targets package named "<name>" version "<version>" A dependency whose name ends with @ and a version is locked to another version Make the version in the name and the locked version the same package <name>@<version> has an invalid name: <details> The package's name breaks the naming rules See Names and versions Messages about downloading or installing packages from the marketplace are covered in Package errors. Errors a script can catch The functions of the built-in modules report problems in two ways. Most return a promise that rejects, which await turns into an error a try/catch can catch. A few throw at once, as noted below. String(error) gives the texts of this section as they are written here, with two exceptions: an error of type TypeError gives TypeError: followed by the text, and the errors thrown by record(), findPath() and packages.info(), and the errors move() rejects with, give GoError: followed by the text. An error your code doesn't catch stops the script, with one of the texts in The script stopped. Messages and timers Text Returned by Cause Fix protocol type must be a non-empty semantic message name send(), request() The message name is missing, empty or not a string Pass the name as a string semantic protobuf message is not mapped: <type> send(), request() Asterobot has no message with that name Check the name in the Dofus protocol reference protocol options must be an object send(), request(), wait() The options aren't an object, such as a bare number Pass { timeout: 5000 } unknown protocol option "<name>" send(), request(), wait() The options have a key other than timeout and to Fix the key timeout/milliseconds must be a finite positive Number send(), request(), wait(), sleep() A timeout that isn't a number above 0, or a sleep() duration that's negative or not a number Pass a number of milliseconds timeout/milliseconds is too large send(), request(), wait(), sleep() A duration too big to be one Pass a smaller number send option "to" must be "server" or "client" send() to isn't a string Use "server" or "client" unknown send target "<value>": expected "server" or "client" send() to is another string Use "server" or "client" protocol selector cannot be empty wait(); thrown by on(), intercept() The selector is "" Pass a name, "*" or a function protocol selector must be a semantic type, '*', or predicate wait(); thrown by on(), intercept() The selector is neither a string nor a function Pass a name, "*" or a function protocol.on handler must be callable, protocol.onTraffic handler must be callable, protocol.intercept handler must be callable Thrown as a TypeError The handler isn't a function Pass a function maximum behavior handlers reached Thrown as a TypeError by on(), onTraffic(), intercept(), onChange() 256 handlers of that kind are registered Remove handlers you no longer need with off() or offChange() protocol wait timed out wait() Nothing matched before the timeout Check the selector, or wait longer context deadline exceeded request() No response came back before the timeout. Most Dofus requests never get one. Use send() and then wait() for the event the game sends mitm relay refuses to identify: this Game session was already authenticated by the real DOFUS client request() The script sent IdentificationRequest on a MITM bot Identify only when launch.reason === "initial" behavior resource limit exceeded: maximum pending operations reached send(), request(), table(), find(), search(), query(), move() 32 operations are pending Await operations before starting more behavior resource limit exceeded: maximum waits reached wait() 128 waits are open Give every wait() a timeout behavior resource limit exceeded: maximum timers reached sleep() 128 sleeps are pending Await sleeps instead of starting many at once On a MITM bot, send() of an IdentificationRequest to the server doesn't reject: see Console lines. Payload errors send() and request() reject with these texts when a payload doesn't fit its message. An interceptor's { payload } gives the same texts on the bot's console, after build replacement payload for <type>:. Each text starts with the path of the field: the message's name, then the field names separated by dots, with [<index>] for an item of a list and ["<key>"] for a value of a map. Text Cause Fix <type>: expected an object The payload is missing, null or undefined Pass {} for a message without fields <path>: expected an object, got <type> The payload, or a nested message, isn't an object Pass an object <path>.<field>: unknown protobuf field The message has no such field Check the field names in the Dofus protocol reference IdentificationRequest.token: unknown protobuf field The script copies an old example that sends token and lang Send ticketKey and languageCode, as in Identify and run <path>: fields "<name>" and "<name>" address the same protobuf field The same field is given under both spellings, such as ticketKey and ticket_key Keep one <path>: oneof <group> selects both "<field>" and "<field>" Two alternatives of the same group are set Set only one <path>.<field>: explicit undefined is not a protobuf value A field is set to undefined Leave the field out <path>.<field>: null is only valid for a message field A field that isn't a nested message is set to null Leave the field out <path>: expected an array A list field got something other than an array Pass an array <path>: invalid array length The value passed for a list has an unusable length Pass a real array <path>: expected a plain object map A map field got something other than an object Pass an object <path>: invalid boolean map key "<key>", and the same with int32, uint32, int64 or uint64 A map key can't be read as the map's key type Use keys such as "true" or "12" <path>: expected boolean A boolean field got another type Pass true or false <path>: expected string A string field got another type Pass a string <path>: expected Uint8Array or ArrayBuffer A bytes field got another type Pass a Uint8Array <path>: expected integral Number A 32-bit integer field got a fraction or something that isn't a number Pass a whole number <path>: Number is outside int32 range The number doesn't fit a signed 32-bit integer Pass a smaller number <path>: expected integral Number in uint32 range An unsigned 32-bit field got a fraction, a negative number or something too big Pass a whole number from 0 to 4294967295 <path>: expected signed 64-bit BigInt or decimal string A 64-bit integer field got a number too big to be exact, a fraction, a string that isn't digits, or another type Pass a BigInt, such as 123456789012n <path>: expected unsigned 64-bit BigInt or decimal string The same, for an unsigned field, or a negative value Pass a positive BigInt <path>: expected Number A floating point field got something that isn't a number Pass a number <path>: unknown enum symbol "<name>" The enum has no value with that name Check the value names in the Dofus protocol reference <path>: expected enum symbol or number An enum field got neither a name nor a whole number Pass the value's name as a string Game data Text Returned by Cause Fix gamedata.text key must be a string or integer Thrown as a TypeError by text() The key is a fraction, a number outside the 32-bit range, a BigInt, undefined or an object Check the value before passing it gamedata.record table must be a non-empty string Thrown as a TypeError by record() The table name is missing or empty Pass the table's name gamedata.record id must be an integer Thrown as a TypeError by record() The id isn't a whole number, is too big, or is a BigInt or a string Pass a regular whole number datacenter: no such table: "<table>" Thrown by record(); table(), find(), search() This game version has no table with that name Check the table's name in the Game page's game data gamedata.table name must be a non-empty string table() The name is missing or empty Pass the table's name gamedata.find table must be a non-empty string find() The name is missing or empty Pass the table's name gamedata.find filter must be a non-empty object find() The filter is missing, empty, an array or not an object Pass { column: value } datacenter: no such column: table "<table>" has no column "<column>" find() A filter column doesn't exist in that table Fix the column's name gamedata.search query must be a non-empty string search() The query is missing or empty Pass a name gamedata.search options.table must be a string search() options.table isn't a string Pass the table's name as a string datacenter: no text for this language: "<language>" search() The game data has no names in the script's language Extract the game data again, or check session.language gamedata.query sql must be a non-empty string query() The SQL is missing or empty Pass the SQL as a string gamedata.query params must be an array query() params isn't an array Pass an array, such as [31] datacenter: empty query query() The SQL holds only spaces Pass a statement datacenter: query is <size> bytes, over the 65536 byte limit query() The SQL text is too long Shorten the query datacenter: followed by the database's message query() The SQL is wrong, names a table or column that doesn't exist, or writes, in which case the message mentions attempt to write a readonly database Fix the query. Try it in the SQL tab. Pathfinding Text Returned by Cause Fix pathfinding.findPath mapId must be an integer Number, BigInt or decimal string Thrown as a TypeError by findPath() The map id is a fraction, a number above Number.MAX_SAFE_INTEGER, a string that isn't digits, or another type Pass currentMap().mapId, or the id as a BigInt pathfinding.findPath from must be an integer cell id, and the same with to Thrown as a TypeError by findPath() The cell isn't a whole number, or is a BigInt or a string Pass a cell id from 0 to 559 pathfinding.findPath options must be an object Thrown as a TypeError by findPath() The options are neither an object, null nor undefined Pass an object, or nothing pathfinding.findPath options has no property "<name>" Thrown as a TypeError by findPath() The options have a key other than occupied, cautious and disableDiagonals Fix the key pathfinding.findPath options.occupied must be an array of cell ids Thrown as a TypeError by findPath() occupied isn't an array Pass an array of cell ids pathfinding.findPath options.occupied[<index>] must be an integer cell id Thrown as a TypeError by findPath() That item isn't a whole number Pass cell ids only pathfinding.findPath options.cautious must be a boolean, and the same with disableDiagonals Thrown as a TypeError by findPath() The option isn't a boolean Pass true or false pathfinding.findPath: map <mapId>, cell <from> to cell <to>: pathfinding: not a map cell, cell ids go from 0 to 559: <cell> Thrown as a TypeError by findPath() from or to is outside 0 to 559. For a cell of occupied, the text ends with occupied cell <cell>. Pass cell ids from 0 to 559 failed to load map <mapId> for pathfinding: datacenter: no such record: maps/<mapId> Thrown by findPath() The bot's game version has no map with that id Check the map id failed to open the datacenter: followed by the reason Thrown by findPath() Asterobot can't read the game data of the bot's game version Extract the game data pathfinding.cellToPoint cell must be an integer cell id, pathfinding.distance a must be an integer cell id and the same with b, pathfinding.direction from must be an integer cell id and the same with to Thrown as a TypeError by cellToPoint(), distance(), direction() The cell isn't a whole number, or is a BigInt or a string Pass a cell id from 0 to 559 pathfinding: not a map cell, cell ids go from 0 to 559, after pathfinding.cellToPoint cell:, pathfinding.distance cells: or pathfinding.direction cells: Thrown as a TypeError by cellToPoint(), distance(), direction() A cell is outside 0 to 559 Pass cell ids from 0 to 559 pathfinding.pointToCell x and y must be integers Thrown as a TypeError by pointToCell() x or y isn't a whole number Pass whole numbers Movement Text Returned by Cause Fix movement.move cell must be an integer cell id move(), as a TypeError The cell isn't a whole number, or is a BigInt or a string Pass a cell id from 0 to 559 movement.move cell: pathfinding: not a map cell, cell ids go from 0 to 559 move(), as a TypeError The cell is outside 0 to 559 Pass a cell id from 0 to 559 movement.move options must be an object move(), as a TypeError The options are neither an object, null nor undefined Pass an object, or nothing movement.move options has no property "<name>" move(), as a TypeError The options have a key other than cautious and timeout Fix the key movement.move options.cautious must be a boolean move(), as a TypeError cautious isn't a boolean Pass true or false movement.move options.timeout: timeout/milliseconds must be a finite positive Number move(), as a TypeError timeout isn't a number above 0 Pass a number of milliseconds move: the bot isn't connected to a game server move() The bot has no game connection Connect the bot first move: the character is already walking; await the move in progress first move() Another walk of the bot is under way, from a script or from Asteroboard Await each move() before starting the next move: the character is still walking move() The character hasn't finished a walk it didn't ask for, such as one the player started on a MITM bot Wait for that walk to end move: the character's map and cell aren't known yet move() Asterobot hasn't seen the character's map yet, or where the map places the character Wait until the character is on a loaded map move: the game has no path from cell <cell> to cell <cell> move() The Dofus client itself would find no route Pick another cell move: the server didn't grant the walk in time move() No MapMovementEvent granted the walk within the timeout, which is also how a refused walk ends Check the cell, or give the server longer with timeout move: another walk of the character started before this one ended move() Another walk of the character started first, or the walk was cancelled Nothing to fix in the call move: the server didn't acknowledge the end of the walk in time move() No MapMovementConfirmResponse came within the walk's time plus the timeout Give the server longer with timeout move: the game session closed during the walk move() The game connection closed before the walk ended Connect the bot again asterobot:movement describes each case. Packages and settings Text Returned by Cause Fix packages.info requires a module or package name Thrown as a TypeError by packages.info() The name is missing, empty or not a string Pass a package name package "<name>" is not installed Thrown by packages.info() No package of the running script has that name, asterobot:parameters included Check the name. See asterobot:runtime. package "<name>" is ambiguous across <count> locked versions Thrown by packages.info() The dependencies lock several versions of that package Nothing to fix in the call: the name can't pick one parameters.onChange handler must be callable Thrown as a TypeError by onChange() The handler isn't a function Pass a function 'Set' on a dynamic object returned false Thrown as a TypeError by assigning to a property of values values is read-only Don't assign to values Could not delete property "<name>" of a dynamic object Thrown as a TypeError by deleting a property of values values is read-only Don't delete from values While the script stops A call made while the script is stopping rejects, or throws, with the reason the script is stopping, and the promises still pending when it stops reject with that reason too. After Stop, the reason is behavior runtime closed. There's nothing to fix: the script is ending. Console lines These lines appear on the bot's Console tab. The script keeps running. Line Level Cause Fix Refused to forward a message that would re-authenticate a relayed session Warning On a MITM bot, the script sent IdentificationRequest to the server. Asterobot didn't forward it, and send() resolved anyway. Identify only when launch.reason === "initial", as in Identify and run interceptor failed: <error> Error An interceptor threw, or ran for more than 250 ms Fix the handler, and keep it quick interceptor selector failed: <error> Error An interceptor's selector function threw Fix the selector build replacement payload for <type>: <error> Error An interceptor returned a { payload } that doesn't fit the message. <error> is one of the payload errors. Fix the payload re-encode <type>: <error> Error Asterobot couldn't rebuild the message with the new fields Return { payload } with the same message's fields cannot replace the payload of <wire name>: this build has no name for it, so there is nothing to build a replacement from - use { raw } instead Error An interceptor returned { payload } for a message without a readable name Return { raw }, or leave that message alone A line is written for each message on which an interceptor fails. When several interceptors fail on the same message, only the first failure is written, and the tenth if there are that many, ending with (10 times). An async interceptor whose promise rejects doesn't write a line here: it stops the script with unhandled Promise rejection: <error>. Saving settings and running actions These appear as toasts on Package settings, on the bot's Settings tab. Toast Text under it Cause Fix Couldn't apply the settings <name>: <reason> A value doesn't fit its declaration. The reasons are listed in Declarations. Nothing from the form is saved. Correct the value, then Apply again Couldn't apply the settings package "<package>" declares no parameter "<name>" The form holds a setting the package doesn't declare any more Click Refresh, then apply again Couldn't apply the settings package "<package>" is not part of this bot's behavior The bot's package changed since the form was loaded Click Refresh Couldn't start the action <argument>: <reason> An argument doesn't fit its declaration Correct the value in the dialog Couldn't start the action the running behavior declares no action "<action>" on package "<package>" The running script doesn't declare that action, for example because you added it in the editor without starting the script again Start the script again Couldn't start the action no behavior is running on this bot The script isn't running Play the script first Couldn't start the action behavior event-loop queue overflow The script has 32 operations pending Try again in a moment The action's name followed by failed The error the action's run threw, or its promise's rejection reason The action failed. The script keeps running. Fix run When the declarations themselves can't be read, Package settings shows Couldn't read this package's settings instead of the form: Declarations lists those texts. Saving the manifest When you save a package's asterobot.json from Asteroboard and Asterobot refuses it, the reason shows under the manifest, or Couldn't save the manifest when there's none. Dependencies explains how to declare them. The first four texts below come after invalid package manifest:. The last three are checked first, for dependencies that have no URL, and come on their own. Text Cause Fix empty dependency specifier A dependency has no name Name the dependency dependency "<specifier>": version embedded in its own specifier: not a valid exact version: <details> The version written after @ in the dependency's name isn't one exact version Write an exact version, such as name@1.2.0 dependency "<specifier>": version "<version>" conflicts with the version already in its own specifier The dependency's name holds one version and its version field another Make them the same, or remove one dependency "<specifier>": invalid version "<version>": <details> The version isn't latest, a version or a version range Fix the version dependency "<specifier>" locks version "<version>" in its specifier but declares "<version>" The version in the name and the declared version differ Make them the same dependency "<specifier>" needs an exact version A dependency without a URL has no exact version Give an exact version dependency "<specifier>" needs <name>@<version> installed, or a URL to fetch it from A dependency without a URL isn't installed at that version Install it, or give its asterobot.net URL
  6. Smooth a posté un record dans Reference
    Asterobot runs every script within fixed limits, so that one script can't hold up Asterobot or the other bots. They're the same for every bot and every package, and a package can't change them. Here is each one, with its value and what happens past it. Running code Limit Value Past the limit Code running without a pause 250 ms The script stops, and the Problems alert shows a text containing JavaScript execution deadline exceeded. The game connection stays open. Function calls nested in each other 1024 The script stops. A try/catch can't catch this. A pause is an await, or the end of the function Asterobot called. Each of these counts as one run, with its own 250 ms: the top level of the modules, when the script starts; the entry function, up to its first await; one call of a handler: from on(), onTraffic(), intercept(), onChange(), a selector function, or an action's run; code resuming after an await, together with any other code resuming at the same moment. The text in the alert says which it was, for example Game traffic handler failed: JavaScript execution deadline exceeded for a handler, or drain JavaScript Promise jobs: JavaScript execution deadline exceeded for code that resumed after an await. The place in your code follows it. Three cases don't stop the script: In an interceptor, the message goes on unchanged, and the bot's Console shows interceptor failed: JavaScript execution deadline exceeded. In an action's run, the toast for the action says it failed, with the same text. While Asterobot reads the package's declarations, reading fails. See Declarations. Handlers, waits and sleeps Limit Value Past the limit Message handlers registered at once with on(), onTraffic() and intercept() together 256 The call throws a TypeError with maximum behavior handlers reached Handlers registered at once with onChange() 256, counted apart from message handlers The call throws a TypeError with maximum behavior handlers reached wait() calls not settled yet 128 The new wait() rejects with behavior resource limit exceeded: maximum waits reached sleep() calls not settled yet 128 The new sleep() rejects with behavior resource limit exceeded: maximum timers reached A handler removed with off() or offChange(), and a wait or a sleep that settled, frees its place. A wait() without a timeout that never matches keeps its place until the script stops. Pending operations Limit Value Past the limit Operations waiting for their result: send(), request(), table(), find(), search() and query() together 32 The new call's promise rejects with behavior resource limit exceeded: maximum pending operations reached text() and record() answer right away and don't count. An operation frees its place as soon as its promise settles. While all 32 places are taken, a click on one of the package's action buttons shows Couldn't start the action with behavior event-loop queue overflow. Settings applied at that moment are saved, but the running script only gets them at its next start. Events waiting for the script Limit Value Past the limit Events waiting to be handled 64 The script stops with behavior event-loop queue overflow The events are the messages that arrive, operations that finish, sleeps and wait timeouts that end, applied settings and clicked actions. A script handles them one at a time, so they pile up while its code is busy, for example in a handler that runs long or awaits nothing. A MITM bot relaying a busy game session receives many messages each second: keep handlers short. A message waiting for an interceptor's decision is the exception. When the queue is full, it goes on unchanged instead of stopping the script. On a MITM bot, Asterobot never slows the game down for what watches it. When the script or the Network tool can't keep up with the messages, Asterobot leaves some out of what they receive, and the game still gets them. A Full socket bot waits instead, and misses nothing. Timeouts Limit Value Past the limit send() and request() 30 seconds unless you pass { timeout } The promise rejects. For request(), with context deadline exceeded. wait() None unless you pass { timeout } With a timeout, the promise rejects with protocol wait timed out. Without one, it waits until the script stops. table(), find(), search() and query() 30 seconds, fixed The promise rejects An interceptor's decision 50 ms The message goes on unchanged, and nothing is written anywhere Packages and modules Limit Value Past the limit Modules the script loads, counting the built-in modules it imports 256 The script doesn't start: reachable module count exceeds limit 256 Total size of those modules 4 MiB (4,194,304 bytes) The script doesn't start: reachable module source exceeds limit of 4194304 bytes Chain of imports, from index.js down 64 modules The script doesn't start: module graph depth <depth> exceeds limit 64 at "<module>" When a script doesn't start for one of these reasons, the toast after Play or Run doesn't say why. The bot's Settings tab does: Package settings shows Couldn't read this package's settings with the text. Only the .js and .mjs files of a package are part of its code. A package archive you import is at most 32 MiB: see Zip files and the command line. Game data Limit Value Past the limit Rows returned by one call of table(), find(), search() or query() 200 by default, 5000 at most A larger limit is lowered to 5000. query() sets truncated to true when rows were left out. Length of the SQL passed to query() 65,536 bytes The promise rejects with datacenter: query is <size> bytes, over the 65536 byte limit Time for one query(), waiting for its turn included 10 seconds The query is interrupted, and the promise rejects Size of a single value a query builds 16 MiB The query fails Queries running at once on one game version, across every bot and Asteroboard 4 The next query waits for its turn, and the wait counts toward its 10 seconds Consoles Limit Value Past the limit Lines Asterobot keeps for each bot's Console 200 A Console tab opened later starts with the last 200 lines only Interceptor failures written to the bot's Console for one message The first, and the tenth When several interceptors fail on the same message, the other failures aren't written. The count starts again with each message. Error messages explains every text on this page, and Async and timing shows how to write scripts that stay within the limits.
  7. Smooth a posté un record dans Reference
    A package puts settings and buttons on a bot's page by declaring them in its script. The complete schema is below. Declare settings and Declare actions walk through it with examples, and asterobot:parameters describes how a script reads the values. Where declarations go A package declares settings and actions as two named exports of its index.js: export const parameters = { maxFights: { type: "int", label: "Stop after", default: 20, min: 1 }, }; export const actions = { recall: { label: "Recall to zaap", async run() { // What the button does. }, }, }; Both exports are optional. Exports with these names in other files of the package are ignored. Every package of the running script can declare its own: the bot's package, and each dependency the script really imports. A dependency listed in asterobot.json that nothing imports declares nothing. Settings and actions appear on the bot's page in the order you write them. These are ordinary JavaScript values, so a default can be computed or imported from another file. How Asterobot reads them Asterobot runs the top level of the package's modules without calling the default export, then reads the two exports. It does so when a bot's Settings tab shows Package settings, when you pick the package in the Add a bot dialog, and each time a script starts. At that moment there's no bot and no game session, so some functions behave differently when the top level of a module calls them: Called at the top level While Asterobot reads declarations send(), request(), wait(), sleep(), disconnect(), currentMap(), and every function of asterobot:gamedata and asterobot:pathfinding Throws, and reading fails with an error that ends with not available while reading a package's declarations move() Rejects with an error that ends with not available while reading a package's declarations on(), onTraffic(), intercept(), off(), onChange(), offChange() Accepted, and does nothing session Holds empty values values An empty object botDebug() and the other console functions of asterobot:bot and asterobot:console Write nothing packages.info() Answers as usual Reading also fails when a top-level await never settles, with package awaits at import time, which cannot be answered without a Game session, and when top-level code runs for more than 250 ms without awaiting, with JavaScript execution deadline exceeded. A rejected promise that nothing catches doesn't make reading fail. So keep the top level of your modules to imports, declarations and quick computations. Settings export const parameters is an object. Each key is a setting's name, the name the script reads in values, and each value is an object with these fields: Field Type Required Description type string Yes "bool", "string", "int", "double", "array" or "map" of string For array and map only The type of the array's items, or of the map's values: "bool", "string", "int" or "double". Refused on the other types. label string No Shown next to the input. Default: the setting's name. description string No A line of help shown with the input placeholder string No Shown in an empty text box or number box unit string No A short word shown after a number box, such as kamas or seconds default depends on type No The value until someone changes it. Without a default, or with null, the setting starts at its type's zero value. choices array No For string and int only: the values allowed. See Choices. min number No The smallest value allowed, for int and double max number No The largest value allowed, for int and double Any other field is ignored without an error, so a misspelled field such as labell silently does nothing. A label, description, placeholder or unit that isn't a string is ignored too, and so is a min or max that isn't a number. Types type The script reads On the bot's page Zero value bool A boolean A switch false string A string A text box "" int A whole number, never a BigInt A number box that steps by 1 0 double A number A number box 0 array An array A list with an input per item, a remove button on each, and Add [] map An object with string keys A list of key and value rows, with Add an entry. A row with an empty key is dropped. {} A setting with choices shows a drop-down list, whatever its type, and its zero value is the first choice. Values that fit The default, and every value applied from the bot's page, has to fit the declaration. Asterobot never converts one type into another: bool takes true or false, nothing else. string takes a string. A number isn't turned into text. int takes a whole, finite number. 3.0 is fine, and 3.5 is refused rather than rounded. double takes a finite number: not NaN, not Infinity. array takes an array whose every item fits of. map takes an object whose every value fits of. With choices, the value has to be one of the choices. With min or max, an int or double value has to be within the bounds, the bounds themselves included. min and max don't apply to a setting that has choices, nor to the items of an array or a map. A saved value that stops fitting after you change a declaration is ignored, and the setting falls back to its default. Choices choices takes bare values, objects with a value and a label, or a mix of both. A bare value is its own label. export const parameters = { slot: { type: "int", label: "Slot", choices: [1, 2, 3], default: 2 }, mode: { type: "string", label: "Strategy", choices: [ { value: "safe", label: "Avoid groups" }, { value: "greedy", label: "Fight everything" }, ], default: "safe", }, }; Every choice's value has to fit the setting's type, and a default has to be one of the choices. Actions export const actions is an object. Each key is an action's name, and each value is an object with these fields: Field Type Required Description label string No The button's text. Default: the action's name. description string No Shown as the button's tooltip, and at the top of the arguments dialog args object No The arguments the bot's page asks for before running the action. Each one is declared exactly like a setting, with the same fields and rules, but its value isn't saved. run function Yes What the button does run(args) receives an object holding every declared argument: the value typed in the dialog, or the argument's default when none was given. An action without args receives {}. run may be async. import { send } from "asterobot:protocol"; export const actions = { say: { label: "Say something", args: { text: { type: "string", label: "Text", default: "Hello" } }, async run({ text }) { await send("ChatChannelMessageRequest", { channel: "GLOBAL", content: text }); }, }, }; Here is what happens around an action: Its button is in the package's section of Package settings, on the bot's Settings tab. It can only be clicked while a script is running. Otherwise it's disabled, with the tooltip The bot has to be playing a behavior before an action can run. An action with arguments opens a dialog titled with its label, with one field per argument, reset to the defaults every time it opens, and Cancel and Run buttons. The arguments are checked against the declarations before anything runs. A value that doesn't fit shows Couldn't start the action with the reason, and run isn't called. Once the action is on its way, a toast says Action started. When run returns, or its promise settles, a second toast says the action's name followed by finished, or followed by failed with the error. A failure toast stays until you close it. An error in run never stops the script. Asterobot finds the actions before it calls the default export, so the buttons work even when the entry function never finishes. A button runs the action of the script that's running. After you change an action in the editor, start the script again to use the new code. Types for the editor The editor knows these types for declarations: BehaviorParameters, BehaviorActions, BehaviorParameter, BehaviorAction, BehaviorChoice, BehaviorParameterType and BehaviorScalarType. Name them in a JSDoc comment to get completion on the fields: /** @type {BehaviorParameters} */ export const parameters = { enabled: { type: "bool", label: "Answer in chat", default: true }, }; Errors When declarations can't be read, Package settings on the bot's Settings tab, and the package's settings in the Add a bot dialog, show Couldn't read this package's settings with the reason. The text starts with the package's name and version and read declarations:, such as: my-first-script@1.0.0: read declarations: package "my-first-script" parameter "enabled": parameter "enabled" default: want a bool, got string In the texts below, <package> is the package's name, <name> a setting's name, <action> an action's name and <argument> an argument's name. Errors that also stop the script from starting These say an export doesn't have the right shape. They also make every start fail, with the Problems alert showing read declarations of "<module>": followed by the same text. Error Cause package "<package>": parameters: must be an object, got <type> parameters is exported, but isn't an object package "<package>": parameters: "<name>" must be an object A setting isn't an object package "<package>": actions: must be an object, got <type> actions is exported, but isn't an object package "<package>": actions: "<action>" must be an object An action isn't an object package "<package>": actions: "<action>" declares no run function An action has no run package "<package>": actions: "<action>": run must be a function An action's run isn't a function package "<package>": actions: "<action>": args: must be an object, got <type> args isn't an object package "<package>": actions: "<action>": args: "<argument>" must be an object An argument isn't an object Errors in a setting or an argument These don't stop a start: the script starts, but with no settings at all. They begin with package "<package>" parameter "<name>": for a setting, or with package "<package>" action "<action>": argument "<argument>": for an action's argument. Error Cause parameter "<name>" declares unknown type "<type>" type is missing or isn't one of the six types parameter "<name>" is a <type> and must declare its element type as `of` An array or map without of parameter "<name>" declares element type "<of>", which is not one of bool/string/int/double of isn't one of the four scalar types parameter "<name>" is a <type> and cannot declare an element type of on a type other than array and map parameter "<name>" declares choices, which only a string or an int may do choices on a type other than string and int choices must be an array, got <type> choices isn't an array choice <index> has no value A choice object without value. Choices count from 0. parameter "<name>" choice <index>: <reason> A choice's value doesn't fit the type parameter "<name>" declares min <min> above max <max> min is greater than max parameter "<name>" default: <reason> The default doesn't fit the declaration parameter has no name A setting's name is the empty string Reasons a value doesn't fit These reasons follow default: or choice <index>: in the errors above. The same texts appear when a value applied from the bot's page, or an action's argument, doesn't fit. Reason Cause want a bool, got <type> A bool got something other than true or false want a string, got <type> A string got something other than a string want an int, got <type> An int got something other than a number want a whole number, got <value> An int got a fraction, or Infinity or NaN want a number, got <type> A double got something other than a number want a finite number, got <value> A double got NaN or Infinity want an array, got <type> An array got something other than an array item <index>: <reason> An array item doesn't fit of want a map, got <type> A map got something other than an object key "<key>": <reason> A map value doesn't fit of <value> is not one of <choices> The value isn't among the choices, listed with commas <value> is below the minimum <min> The value is lower than min <value> is above the maximum <max> The value is higher than max <type> is the name Asterobot gives the value's type: string, bool, int64 for a whole number written in the script, float64 for other numbers and for every number sent from the bot's page, []interface {} for an array and map[string]interface {} for an object. Error messages lists the texts you can meet everywhere else.
  8. Smooth a posté un record dans Reference
    @asterobot:base is an official package of ready-made functions that most bots need. For now it re-exports move(), the built-in function of asterobot:movement that walks the bot's character to a cell of its map. Note Coming soon. @asterobot:base isn't on the marketplace yet, so no script can install it. You don't need it to walk: import move() from asterobot:movement. Add it to a package Once @asterobot:base is in your library, declare it in your package's dependencies, as Dependencies shows, then import move() by that name: import { move } from "@asterobot:base"; It's the same function as the one asterobot:movement exports, and asterobot:movement describes its options, errors and limits.
  9. Smooth a posté un record dans Reference
    asterobot:movement walks the bot's character. Its one function, move(), walks the character to a cell of the map it stands on and resolves once the walk is over. The same call works on a Full socket bot and on a MITM bot (man-in-the-middle: the bot relays the game session of a Dofus client that someone plays). import { move } from "asterobot:movement"; This is the module's only export. move() move(cell, options?) returns Promise<number>. Parameter Type Description cell number The cell to walk to, a whole number from 0 to 559 options.cautious boolean Walk instead of running, whatever the distance. Default false. options.timeout number How many milliseconds the server gets to grant the walk, and then to acknowledge its end once the walk is over. Default 5000. It resolves with the cell the character stands on, once the server has acknowledged the end of the walk. try { const cell = await move(301); botInfo("Standing on cell", cell); } catch (error) { botWarn("Couldn't walk:", String(error)); } How it walks It reads where the character and the other actors stand, as currentMap() does, and plans the route to cell the way findPath() does, around every other actor. When cell can't be reached, the route ends on the reachable cell closest to it. When there's nowhere to walk, move() resolves right away with the character's cell and sends nothing. It sends MapMovementRequest and waits for the server's MapMovementEvent granting the walk. On a Full socket bot, it waits as long as the Dofus client takes to walk the cells the server granted, then sends MapMovementConfirmRequest. On a MITM bot it sends nothing more: the Dofus client walks the character on screen and confirms the walk itself. It resolves when the server answers with MapMovementConfirmResponse. When the server grants a shorter walk than the one asked for, move() times the walk the server granted and resolves with the cell where it ends. On a Full socket bot the confirmation leaves as early as a Dofus client could send it. The path explains how that time is found, and why it can be wrong for a mounted character. One walk at a time A bot walks one walk at a time, whoever asks for it. While a walk is under way, a second move() rejects at once and sends nothing, and so does a walk started with Walk here on the bot's map in Asteroboard. Await each move() before starting the next. move() also refuses to start while the character is still walking a walk it didn't ask for, such as one the player started on a MITM bot. That walk counts until the server acknowledges its end, or for one second at most past the time the Dofus client takes to walk it. The walk belongs to the bot, not to your script. If the script stops during a walk, the promise of move() rejects with the reason the script stopped, and the walk still ends and is confirmed, so the server doesn't keep the character walking. Doing something else during the walk move() doesn't hold up the rest of your script. Other code keeps running while the character walks, and on a Full socket bot the confirmation still leaves on time. To send a chat message while walking, start both together: const [cell] = await Promise.all([ move(301), send("ChatChannelMessageRequest", { channel: "GLOBAL", content: "Hello" }), ]); Don't keep the promise of move() in a variable to await later. If it rejects before your code reaches the await, nothing catches it and the script stops with unhandled Promise rejection:. Await it, put it in a Promise.all, or give it a .catch(). Until the end of the walk is acknowledged, the server considers the character still walking. Wait for move() to resolve before an action that needs the character standing, such as using an element of the map. Errors A mistake in the arguments rejects the promise with a TypeError, and nothing is sent: Error Cause movement.move cell must be an integer cell id cell isn't a whole number in the 32-bit range, or is a BigInt or a string movement.move cell: pathfinding: not a map cell, cell ids go from 0 to 559 cell is outside 0 to 559 movement.move options must be an object options is set, not to null or undefined, and isn't an object movement.move options has no property "<name>" options has a key other than cautious and timeout movement.move options.cautious must be a boolean cautious is set to something other than true, false, null or undefined movement.move options.timeout: timeout/milliseconds must be a finite positive Number timeout isn't a number above 0 A walk that is refused or fails rejects it with an error whose message is one of these texts: Text Cause move: the bot isn't connected to a game server The bot has no game connection. Nothing is sent. move: the character is already walking; await the move in progress first Another walk of the bot is under way, from a script or from Asteroboard. Nothing is sent. move: the character is still walking The character hasn't finished a walk it didn't ask for. Nothing is sent. move: the character's map and cell aren't known yet Asterobot hasn't seen the character's map yet, or where the map places the character, for example right after the character enters the game. Nothing is sent. move: the game has no path from cell <cell> to cell <cell> The Dofus client itself would find no route. Nothing is sent. move: the server didn't grant the walk in time No MapMovementEvent granted the walk within timeout. A walk the server refuses ends this way too. move: another walk of the character started before this one ended A new walk of the character started first, or its walk was cancelled, for example because the player clicked elsewhere on a MITM bot move: the server didn't acknowledge the end of the walk in time No MapMovementConfirmResponse came within the walk's time plus timeout move: the game session closed during the walk The game connection closed before the walk ended behavior resource limit exceeded: maximum pending operations reached 32 operations are pending, and a walk counts as one until it ends. Nothing is sent. It also rejects with the texts of findPath() when the game data can't be read, such as failed to load map <mapId> for pathfinding: datacenter: no such record: maps/<mapId>. asterobot:pathfinding lists them. String(error) gives TypeError: or GoError: followed by the text. Limits move() walks on the map the character stands on. It doesn't change maps. It's made for moving outside fights, like asterobot:pathfinding. Walk along a path shows the messages move() exchanges, for a script that walks by hand.
  10. Smooth a posté un record dans Reference
    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 MapMovementRequest with the path's keyCells, the cautious flag and the map's id. The server grants the walk with MapMovementEvent, whose cells list every cell of the walk. The server sends that message for every actor that walks on the map, so compare its characterId with 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 with MapMovementConfirmResponse. 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.
  11. Smooth a posté un record dans Reference
    asterobot:parameters gives a script the current values of its package's settings: the ones declared with export const parameters and shown on the bot's Settings tab, under Package settings. Declarations describes how to declare them, and Read settings shows how to use them. import { values, onChange, offChange } from "asterobot:parameters"; Whose settings Each package gets its own copy of this module. Any file of a package that imports it reads that package's settings, declared in that package's index.js, and nothing else. A dependency reads its own settings, never those of the package that uses it, and no package can read another package's settings. Settings in dependencies explains how that looks on the bot's page. values values is a read-only object with one property per declared setting. Declared type Value in values bool A boolean string A string int A whole number. It's a regular number, not a BigInt, unlike 64-bit fields in game messages. double A number array An array of values of the of type map An object whose keys are strings and whose values have the of type The values are the ones saved for this bot, on top of the declared defaults. Settings are saved under the package's name, whatever its version, so they carry over when the bot moves to a new version. A saved value that no longer fits the declaration, because the setting was renamed, removed or given another type, is ignored, and the default applies. values is live. When someone clicks Apply on the bot's page, the running script reads the new values from its next read on, without restarting. A package's settings are applied all at once, so a script never reads half of an edit. Don't assign to values or delete from it. Package code always runs as a module, which JavaScript makes strict, so Asterobot's refusal throws: assigning a property throws a TypeError with 'Set' on a dynamic object returned false, and deleting one throws Could not delete property "<name>" of a dynamic object. Either way the setting keeps the value set on the bot's page. Object.keys(values) lists the settings, in no particular order, and "name" in values tells whether a setting exists. When Asterobot can't read the package's declarations, the bot's page shows Couldn't read this package's settings, and the script starts with no settings at all: values is empty, and every read returns undefined. While Asterobot reads the declarations themselves, values is an empty object too, so a declaration can't depend on a setting's value. onChange() onChange(handler) returns a handle, { id }, where id is a BigInt. Parameter Type Description handler function Called with { name, value } for each setting whose value changed When settings of this package are applied to the running script, handler runs once for each setting whose value actually changed, with the setting's name and its new value, of the type shown in the table above. Settings applied with the value they already had don't call it, and arrays and maps count as changed only when their content differs. It doesn't run when the script starts, and it never runs for another package's settings. With several handlers, the order they run in isn't defined. Settings applied while no script runs are saved, and the next start reads them from values. In the rare case where the script already has 32 operations pending when someone applies settings, the settings are saved, but the running script only gets them at its next start. See Limits. An error in a handler stops the script: What happened Text in the Problems alert The handler threw parameters.onChange handler <number>: <error> An async handler's promise rejected unhandled Promise rejection: <error> onChange() throws a TypeError at once when: Error Cause parameters.onChange handler must be callable handler isn't a function maximum behavior handlers reached 256 onChange() handlers are already registered. This count is separate from the message handlers of asterobot:protocol. If the script is already stopping, onChange() throws the reason it's stopping. onChange(({ name, value }) => { botInfo(`Setting ${name} is now ${value}`); }); offChange() offChange(handle) returns undefined. It removes the handler that onChange() registered, given the handle it returned. It never throws: a handle that was already removed, or anything else, is ignored. It doesn't remove handlers registered with on(), onTraffic() or intercept(): use off() for those. Types Type Definition ParameterScalar A boolean, a string or a number ParameterValue A ParameterScalar, a read-only array of them, or a read-only object of them ParameterChange { name: string, value: ParameterValue }, what onChange() handlers receive ParameterSubscription { id: bigint }, the handle onChange() returns When a value applied from the bot's page doesn't fit its declaration, nothing is saved and the page shows Couldn't apply the settings with the reason. Error messages lists those reasons.
  12. Smooth a posté un record dans Reference
    asterobot:runtime lets a script read facts about the packages it's made of: its own package, the packages it depends on, and the built-in asterobot: modules. Nothing in it can change a package. import { packages } from "asterobot:runtime"; packages is the module's only export. It's a frozen object with one function, info(). There's no flat info export, so it can't be confused with info() from asterobot:console. packages.info() packages.info(name) returns a frozen PackageInfo object. Parameter Type Description name string A package name, such as "my-first-script" or "@alice:pathfinder", or the name of a built-in module, such as "asterobot:protocol" The names it knows are: every package in the running script: the bot's package and, all the way down, the packages its dependencies lock; the built-in modules asterobot:protocol, asterobot:bot, asterobot:timers, asterobot:gamedata, asterobot:console and asterobot:runtime. asterobot:parameters isn't one of them: each package has its own copy of that module, and asking for it throws package "asterobot:parameters" is not installed. An inline script, loaded as code rather than as a library package, is the package api-script with the version local. packages.info() throws when: Error Type Cause packages.info requires a module or package name TypeError name is missing, empty or not a string. Despite the wording, a module path isn't accepted. package "<name>" is not installed Error No package in the running script has that name package "<name>" is ambiguous across <count> locked versions Error The script's dependencies lock more than one version of that package, so the name alone doesn't say which const self = packages.info("my-first-script"); botInfo(`${self.name} ${self.version}, ${self.provenance}`); On a local package, this writes my-first-script 1.0.0, local to the bot's console. packages.info() also answers while Asterobot reads the package's declarations, so a declaration can use it. PackageInfo Property Type Value name string The package's name version string The package's version. "embedded" for a built-in module. publisher string The part before the colon in @publisher:name: "asterobot" for official packages and built-in modules, "" for a local package provenance string "local", "official", "community", or "builtin" for a built-in module integrity string The checksum Asterobot recorded when it installed the package from the marketplace, starting with sha256-. "" when there's none, as for a local package, and "embedded" for a built-in module. permissions array of strings The permissions listed in the package's asterobot.json. An empty array when it lists none, and always for a built-in module. compatibleVersion string The Asterobot versions the package declared it works with, such as ">=1.5.0 <2.0.0", or "" The type in the editor also lists "builtin-native" as a possible provenance. Asterobot never returns it. A package's provenance comes from its name: a name starting with @asterobot: is official, another name starting with @ is community, and a name without @ is local. Names and versions explains the rules, The manifest describes asterobot.json, and Compatibility covers compatibleVersion. Permissions permissions only reports what a package declares. Asterobot doesn't check it today: any package can use every function of every built-in module, whatever it lists. Note Coming soon. Asterobot will enforce the permissions a package declares. Until then, treat the list as information for the people who install your package. Permissions has the details.
  13. Smooth a posté un record dans Reference
    asterobot:console writes to Asterobot's own log, the one shown in Server > Console and in the terminal Asterobot runs in. It's for a script's diagnostics. To show something to the person watching a bot, use botInfo() and the other functions of asterobot:bot instead: they write to that bot's own console. import { console, debug, log, info, warn, error } from "asterobot:console"; Scripts have no global console. The console here is an object this module exports, so it has to be imported like the rest. Exports Export Level of the line debug(...values) debug log(...values) info. log is the same function as info. info(...values) info warn(...values) warn error(...values) error console A frozen object with the same five functions: console.debug(), console.log(), console.info(), console.warn() and console.error() Each function takes any number of values, writes them as one line joined with spaces, and returns undefined. They never throw. info("Route computed in", elapsed, "ms"); console.warn("No zaap found for map", mapId); Where the lines appear Lines go to Server > Console and to the terminal. By default, debug lines are left out of both: set Server level to DEBUG on Server > Console to see them there. In Server > Console, a line doesn't say which bot wrote it. When several bots run scripts, write the bot's name or something that identifies it into the text yourself. How values print Values are turned into text the same way as for botInfo(): strings, numbers and booleans print as JavaScript would, a BigInt prints without its n, undefined and null print <nil>, an array prints as [1 2 3], an object as map[name:value], and an Error as map[]. Log String(error) for errors. The table on asterobot:bot has every case. At a module's top level Code at the top level of a module also runs while Asterobot reads the package's declarations, and what these functions write at that moment is thrown away. The same code runs again when the script starts, and its lines appear then. Logging and debugging compares the two consoles and when to use each.
  14. Smooth a posté un record dans Reference
    asterobot:gamedata reads the texts and the data of the game version the bot uses: items, monsters, maps, spells and everything else Asterobot extracts from the game files. It only reads. Game data overview, Lookups and SQL show how to use it. import { text, record, table, find, search, query } from "asterobot:gamedata"; These are the module's only exports. There's no gamedata object to import. Export Returns What it reads text() A string or undefined, right away One game text, by id or key record() An object or undefined, right away One row of a table, by id table() A promise of an array The rows of a table, a page at a time find() A promise of an array The rows whose columns equal given values search() A promise of an array Records found by their name query() A promise of a result The answer to a read-only SQL query What the data is The data comes from the game version the bot is set to. text() and search() answer in the language the script was started with, session.language, which is fr for every start made from Asteroboard today. Asterobot looks for the data when the script starts. When that game version has no extracted game data at that moment, record() returns undefined, and table(), find(), search() and query() resolve with undefined rather than an array, until the script starts again after the data is extracted. When Asterobot can't load the game's texts in that language, text() returns undefined for every key. Extract game data explains how to get the data. Table and column names, such as monsters or name_id, are the ones of the extracted data, and they can change between game versions. Browse them on the Game page's game data (see Game data), and try queries in the SQL tab. In the rows these functions return, numbers are regular JavaScript numbers, not BigInt. A packed column arrives as an array of objects, and a JSON column as the value it holds. record(), table() and find() leave empty columns out of their objects. table(), find(), search() and query() share these rules: They reject after 30 seconds, and there's no option to change that. Each call counts as one of the 32 operations a script can have pending at once, along with send() and request(). Past that, the promise rejects with behavior resource limit exceeded: maximum pending operations reached. limit defaults to 200 rows and can't go above 5000. A limit or offset that isn't a whole number greater than 0 is ignored. text() text(key) returns a string, or undefined. Parameter Type Description key number or string A text id, as found in columns such as name_id, or a text key such as "ui.common.classic" It returns the text in the script's language, or undefined when no text has that id or key. When the game's texts are loaded, text() throws a TypeError with gamedata.text key must be a string or integer for any other key: a fraction, a number outside the 32-bit range, a BigInt, undefined or an object. Check a value exists before passing it. record() record(table, id) returns an object, or undefined. Parameter Type Description table string The table's name, such as "monsters" id number The record's id: a whole number, not a BigInt It returns the row with that id, or undefined when there's none. Error Type Cause gamedata.record table must be a non-empty string TypeError table is missing, empty or not a string gamedata.record id must be an integer TypeError id isn't a whole number, is too big to be exact in JavaScript, or is a BigInt or a string datacenter: no such table: "<table>" Error This game version has no table with that name. String(error) gives GoError: datacenter: no such table: "<table>". const monster = record("monsters", 31); if (monster) botInfo("Monster 31 is", text(monster.name_id)); table() table(name, options?) returns Promise<object[]>. Parameter Type Description name string The table's name options.limit number How many rows to return. Default 200, at most 5000. options.offset number How many rows to skip first. Default 0. It resolves with the rows, in the order of their ids. A call without options returns the first 200 rows, not the whole table. The promise rejects with gamedata.table name must be a non-empty string when name isn't a non-empty string, and with datacenter: no such table: "<name>" for a table this game version doesn't have. const page = await table("items", { limit: 50, offset: 100 }); find() find(name, filter, options?) returns Promise<object[]>. Parameter Type Description name string The table's name filter object Column names and the values they must equal. A row matches when every column equals its value. options.limit number How many rows to return. Default 200, at most 5000. offset is ignored. Error Cause gamedata.find table must be a non-empty string name isn't a non-empty string gamedata.find filter must be a non-empty object filter is missing, empty, an array, or not an object datacenter: no such table: "<name>" This game version has no table with that name datacenter: no such column: table "<name>" has no column "<column>" A filter column doesn't exist in the table const spots = await find("maps__interactive_elements", { gfx_id: 1018 }); search() search(query, options?) returns Promise<Match[]>. Parameter Type Description query string The name, or part of it options.table string Only search this table's records, such as "monsters". Without it, every named record is searched. options.limit number How many matches to return. Default 200, at most 5000. Each word of query matches the beginning of a word, so a partial name works. Shorter names come first. Without options.table, the results take one match from each table in turn, so the first results show every kind of thing that has that name. A query made only of spaces resolves with an empty array. Error Cause gamedata.search query must be a non-empty string query isn't a non-empty string gamedata.search options.table must be a string options.table is there but isn't a string datacenter: no such table: "<table>" This game version has no table with that name datacenter: no text for this language: "<language>" The extracted data has no names in the script's language const [hit] = await search("Bouftou Royal", { table: "monsters" }); if (hit) botInfo(hit.name, "is monster", hit.id); query() query(sql, params?, options?) returns Promise<QueryResult>. Parameter Type Description sql string One or more SQL statements params array Values for the ? placeholders, in order options.limit number How many rows to return. Default 200, at most 5000. The game data is opened read-only, so any statement that writes fails, and attaching another database is refused. Every statement in sql runs, and the result is the last one's: "SELECT 1; SELECT 2" answers 2. A query runs for at most 10 seconds, and its text can't be longer than 65,536 bytes. Pass values through params instead of writing them into the SQL: a number written into the text arrives as text, and doesn't equal the number stored in a column. In rows, an empty value is null. Error Cause gamedata.query sql must be a non-empty string sql isn't a non-empty string gamedata.query params must be an array params is there but isn't an array datacenter: empty query sql holds only spaces datacenter: query is <size> bytes, over the 65536 byte limit The SQL text is too long datacenter: followed by the database's message The SQL is wrong, names a table or column that doesn't exist, or tries to write, in which case the message mentions attempt to write a readonly database The promise also rejects when the query runs for more than 10 seconds. const { columns, rows, truncated } = await query( "SELECT _id, name_id FROM monsters WHERE _id = ?", [31], ); Types Type Shape Row An object whose keys are the table's column names Window { limit?: number, offset?: number }, the options of table() and find() SearchOptions { table?: string, limit?: number } QueryOptions { limit?: number } Match { table: string, id: number, nameId: number, name: string }: the record's table and id, to pass to record(), the id of the text its name comes from, and the name in the script's language QueryResult { columns: string[], rows: unknown[][], truncated: boolean }. columns lists the column names in order, and they can repeat, which is why each row is an array aligned with them. truncated is true when the query had more rows than limit.
  15. Smooth a posté un record dans Reference
    asterobot:timers has a single export, sleep(). Scripts have no setTimeout() or setInterval(), so pauses, delays and repeated tasks are all built with it. import { sleep } from "asterobot:timers"; sleep() sleep(milliseconds) returns Promise<void>. Parameter Type Description milliseconds number How long to wait. 0 and fractions such as 2.5 are accepted. The promise resolves once the time has passed and the script gets to it: when other events are waiting to be handled, it resolves after them. While a script awaits sleep(), its message handlers keep running. The promise rejects with: Error Cause timeout/milliseconds must be a finite positive Number milliseconds is negative, NaN or Infinity, or isn't a number at all. A BigInt such as 1000n and a string such as "1000" are refused too. timeout/milliseconds is too large milliseconds is too big to be a duration, more than about 292 years behavior resource limit exceeded: maximum timers reached 128 calls to sleep() are already pending. See Limits. When the script stops while a sleep() is pending, its promise rejects with the reason the script stopped, such as behavior runtime closed after Stop. Repeating a task Start the loop without awaiting it, so the entry function can finish. A start only counts as successful once that function has returned, which matters for launch.reason. import { session, botInfo, botWarn } from "asterobot:bot"; import { send } from "asterobot:protocol"; import { sleep } from "asterobot:timers"; async function everyMinute() { while (true) { await sleep(60000); botInfo("Still running"); } } export default async function behavior(launch) { // Not awaited, and caught: a rejection nobody catches stops the script. everyMinute().catch((error) => botWarn("Timer loop stopped:", String(error))); // 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, }); } } When you click Stop, the pending sleep() rejects, so the console shows Timer loop stopped: behavior runtime closed as the script ends. Don't poll in a tight loop to wait for a message: await wait(selector, { timeout }) from asterobot:protocol resolves as soon as the message arrives. And never keep the script busy without an await: code that runs for more than 250 ms in one go stops the script. Async and timing explains why.
  16. Smooth a posté un record dans Reference
    asterobot:bot gives a script what belongs to its own bot: the facts about the game session, the map its character stands on, a way to close the game connection, and the bot's console. The launch argument of the entry function is described on this page too, because session repeats its two values. import { session, currentMap, disconnect, botDebug, botInfo, botWarn, botError } from "asterobot:bot"; These are the module's only exports. There's no bot object to import. The launch object Asterobot starts a script by calling the default export of the package's index.js with one argument, launch: import { session, botInfo } from "asterobot:bot"; import { send } from "asterobot:protocol"; export default async function behavior(launch) { botInfo("Launch:", launch.reason, launch.generation); // 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, }); } } On the first start of a Full socket bot, the console shows Launch: initial 1. Property Type Value reason string "initial", "resume" or "reload", as described below generation BigInt Which start this is on the current game connection: 1n for the first, then one more for each start. A start whose script fails while starting still counts. A start that fails before any code runs, such as one with a syntax error, doesn't. A new game connection counts from 1n again. reason When a script starts with it "initial" On a Full socket bot's game connection, as long as no script has started successfully on it yet. A start is successful once the default export's function has finished. "resume" Every later start on the same connection, such as Play after Stop. On a MITM bot, every start, the first one included. "reload" When a running script is replaced without being stopped first. No button in Asteroboard does that. The default export must be a function that returns a promise, usually an async function. The entry function explains what to do for each reason. session session is a frozen object. Its values are set when the script starts and don't change while it runs. Property Type Value gameToken string The ticket the login server issued for this game connection. A Full socket script sends it as ticketKey in IdentificationRequest. serverId number The game server chosen when signing in, fixed for the whole connection. On a MITM bot, the server the Dofus client connected to. It says nothing about where the character is. language string The language code the script was started with. Right now every start made from Asteroboard, with Play, Run or a load from the bot's menu, gives fr, whatever language the bot itself is set to. text() and search() from asterobot:gamedata use this language too. launchReason string The same value as launch.reason behaviorGeneration BigInt The same value as launch.generation shared boolean true on a MITM bot, where a person plays the same character through their Dofus client. false on a Full socket bot. Every function behaves the same whatever shared says. It exists so that a script that acts on its own, moving or fighting, can decide to stay passive while someone else is playing. Caution Never log session.gameToken or show it to anyone. It's the ticket to the bot's game session, and a stranger who has it could take that session over. While Asterobot reads a package's declarations, there's no session yet. session then holds empty values: "" for the strings, 0 for serverId, "initial", 1n and false. See Declarations. currentMap() currentMap() returns the map the bot's character stands on, or undefined until the character has entered a map. import { currentMap, botInfo } from "asterobot:bot"; const map = currentMap(); if (map?.cellId !== undefined) { botInfo("Map", map.mapId, "cell", map.cellId, "actors", map.actors.length); } For a character on cell 300 of map 154010883, next to a monster group, the console shows Map 154010883 cell 300 actors 2. Property Type Value mapId BigInt The map's id characterId BigInt The id of the bot's character, the one MapMovementEvent gives in characterId when the character walks. Absent until the character is selected. cellId number The cell the character stands on. Absent while the map doesn't place the character yet. actors array Every actor the server placed on the map, the character included, sorted by id. Each one is an object with id, a BigInt, and cellId, a number. Asterobot follows the map itself, for the whole game session, from the messages the bot sends and receives: MapInformationRequest when the character arrives on a map, MapComplementaryInformationEvent for the actors already there, then GameRolePlayShowActorsEvent, MapMovementEvent and ContextRemoveElementEvent for the actors that come, walk and leave. So a script started or reloaded in the middle of a session reads where the character is, not only what it saw since it started. Each call returns a new object holding the values of that moment. An actor's cellId is the cell its walk ends on from the moment the server grants the walk, before the actor gets there. When the character's own walk stops early, for example because the player clicked elsewhere on a MITM bot, the MapMovementCancelRequest sent for it moves the character to the cell where it stopped. Fights aren't followed. disconnect() disconnect() returns undefined. It closes the bot's game connection and returns right away, without waiting for the connection to close. The script stops as a result, and the bot's page shows The behavior stopped with an error with the text behavior requested Game disconnect. Calling disconnect() again, or while the script is already stopping, does nothing. Warning On a MITM bot, the connection is the game session of the person playing. disconnect() closes it for their Dofus client too. Check session.shared first if that matters to your package. Asterobot doesn't connect the bot again by itself. Disconnect, delete and restart describes what happens next from the player's side. botDebug(), botInfo(), botWarn() and botError() Each takes any number of values and returns undefined: botInfo("Kamas:", traffic.payload?.kamas); botWarn("Reply not delivered:", String(error)); The line goes to the bot's Console tab as a Script line, at the level the function names: debug, info, warn or error. Lines of every level show there, debug included. The values are joined with spaces. These functions never throw. Asterobot turns each value into text on its own, and only strings, numbers and booleans come out the way JavaScript would print them: Value Printed as A string As it is A number 42, 1.5 A BigInt Its digits, without the n: 1500 A boolean true, false undefined or null <nil> An array Items separated by spaces, in brackets: [1 2 3] An object map[ then its properties sorted by name: map[kamas:1500 name:Airelle] An Error map[], so log String(error) instead To print an object readably, build the text yourself, for example with a template string. JSON.stringify() throws on any value that holds a BigInt, with the message Do not know how to serialize a BigInt, and message payloads often do. When you open a bot's Console tab, it starts with the last 200 lines Asterobot kept for that bot. Code at the top level of a module also runs while Asterobot reads the package's declarations. What these functions write at that moment is thrown away. To write to Asterobot's own log instead, use asterobot:console. Logging and debugging compares the two and suggests a way to track down a problem.
  17. Smooth a posté un record dans Reference
    asterobot:protocol is how a script exchanges messages with the game: it sends them, waits for them, reacts to them, and changes or blocks them on their way through. Listening, Waiting, Sending and Intercepting explain how to use each part. import { send, request, wait, on, onTraffic, intercept, off } from "asterobot:protocol"; These are the module's only exports. There's no protocol object to import. Export What it does send() Sends a message, and resolves once it's sent request() Sends a request, and resolves with the response matched to it wait() Resolves with the next inbound message that matches on() Calls a handler for every inbound message that matches onTraffic() Calls a handler for every message, in both directions intercept() Decides whether a message goes on unchanged, is dropped or is replaced off() Removes a handler registered with on(), onTraffic() or intercept() Message names and fields are the ones in the Dofus protocol reference. send() send(type, payload, options?) returns Promise<void>. Parameter Type Description type string The message's name, such as "ChatChannelMessageRequest" payload object The message's fields. Pass {} for a message you send without fields: leaving payload out is an error. options.timeout number Milliseconds before the promise rejects. Default: 30000. options.to "server" or "client" Where the message goes. Default: "server". send() builds the message and writes it. The promise resolves as soon as the message is written: it doesn't wait for an answer, so use wait() when you need to know what the game did. With to: "server", the message goes to the game server as if the Dofus client had sent it. onTraffic() handlers see it go out, and no interceptor is asked about it. With to: "client", the message is delivered as an event from the server: On a MITM bot, Asterobot writes it to the Dofus client, which acts on it as if the server had sent it. On a Full socket bot there's no Dofus client, so the message only reaches the script's own on(), onTraffic() and wait(). Either way it arrives as an inbound message, so the same script works on both kinds of bot. On a MITM bot, an IdentificationRequest sent to the server is never forwarded, because the Dofus client has already identified the session. The promise resolves anyway, and the bot's Console shows the warning Refused to forward a message that would re-authenticate a relayed session. The promise rejects with: Error Cause protocol type must be a non-empty semantic message name type is missing, empty or not a string semantic protobuf message is not mapped: <type> Asterobot has no message with that name. Check the spelling, and see After a game update. <type>: expected an object payload is missing, null or undefined <type>.<field>: unknown protobuf field The message has no field with that name, for example IdentificationRequest.token: unknown protobuf field Another text starting with <type> A field's value doesn't fit. Payload errors lists them all. protocol options must be an object options isn't an object, for example send(type, payload, 5000) unknown protocol option "<name>" options has a key other than timeout and to timeout/milliseconds must be a finite positive Number timeout isn't a number greater than 0 timeout/milliseconds is too large timeout is too big to be a duration send option "to" must be "server" or "client" to isn't a string unknown send target "<value>": expected "server" or "client" to is a string other than those two behavior resource limit exceeded: maximum pending operations reached 32 operations are already pending. See Limits. It also rejects when the message can't be written before the timeout, or when the game connection closes. await send("ChatChannelMessageRequest", { channel: "GLOBAL", content: "Hello" }); request() request(type, payload, options?) returns Promise<ProtocolTraffic>. Parameter Type Description type string The request's name payload object Its fields, as for send() options.timeout number Milliseconds before the promise rejects. Default: 30000. options.to is accepted and ignored: a request always goes to the server. request() sends the message as a request carrying a new uid, and resolves with the response the server sends back with the same uid, as a traffic object whose kind is "response". Only a few Dofus requests get such a response. The game answers most of them with an event, or not at all, and request() then waits for its whole timeout before rejecting. For those, send the message with send() and wait() for the event. The promise rejects with the same argument, payload and limit errors as send(), apart from the two about to, and with: Error Cause context deadline exceeded No response came back before the timeout mitm relay refuses to identify: this Game session was already authenticated by the real DOFUS client The script sent IdentificationRequest on a MITM bot. The request isn't forwarded. wait() wait(selector, options?) returns Promise<ProtocolTraffic>. Parameter Type Description selector string or function A message name, "*" for any message, or a function that receives a traffic object and returns true for the message you want options.timeout number Milliseconds before the promise rejects. There's no default: without a timeout, wait() waits until the script stops. options.to is accepted and ignored. wait() looks at inbound messages that arrive after the call, and resolves with the first one that matches. When several waits match the same message, they all resolve with it. For each message, the handlers registered with on() and onTraffic() run before the waits are checked. Since it only sees what arrives after the call, start the wait before you send the message it waits for, for example with Promise.all(): the answer can arrive before await send() has resolved. Waiting shows how. A selector function runs for every inbound message until the wait settles. If it throws, the whole script stops, with evaluate protocol wait selector: <error>. The promise rejects with: Error Cause protocol wait timed out The timeout passed before a message matched protocol selector cannot be empty selector is "" protocol selector must be a semantic type, '*', or predicate selector is neither a string nor a function protocol options must be an object, unknown protocol option "<name>", timeout/milliseconds must be a finite positive Number, timeout/milliseconds is too large The options are wrong, as for send() behavior resource limit exceeded: maximum waits reached 128 waits are already open const echo = await wait( (traffic) => traffic.type === "ChatChannelMessageEvent" && traffic.payload?.content === "Hello", { timeout: 5000 }, ); on() on(selector, handler) returns a handle, { id }, where id is a BigInt. Parameter Type Description selector string or function A message name, "*" for any message, or a function that returns true for the messages to handle handler function Called with the traffic object of each matching inbound message. It may be async. on() sends nothing and returns right away. From then on, handler runs for every inbound message that matches, until you pass the handle to off() or the script stops. Outbound messages never reach it, whatever the selector. Handlers run one message at a time, in the order they were registered, onTraffic() handlers included. A handler registered while a message is being handled starts with the next message. An async handler returns at its first await, and the next handler doesn't wait for it to finish. An error in a handler stops the script: What happened Text in the Problems alert The handler threw Game traffic handler failed: <error> An async handler's promise rejected unhandled Promise rejection: <error> The selector function threw evaluate protocol handler selector: <error> on() throws a TypeError at once, instead of returning, when: Error Cause protocol selector cannot be empty selector is "" protocol selector must be a semantic type, '*', or predicate selector is neither a string nor a function protocol.on handler must be callable handler isn't a function maximum behavior handlers reached 256 handlers from on(), onTraffic() and intercept() are already registered If the script is already stopping, on() throws the reason it's stopping. const handle = on("ChatChannelMessageEvent", (traffic) => { if (!traffic.payload) return; botInfo(`${traffic.payload.senderName}: ${traffic.payload.content}`); }); onTraffic() onTraffic(handler) returns a handle, { id }. It works like on("*", handler), except that it receives messages in both directions: what the game server sends; what the Dofus client sends, on a MITM bot; what the script sends with send() and request(); what someone sends from the bot's Network tool. A message dropped by an interceptor never reaches it. onTraffic() throws a TypeError with protocol.onTraffic handler must be callable when handler isn't a function, and with maximum behavior handlers reached past the limit. Errors in the handler stop the script, with the same texts as for on(). intercept() intercept(selector, handler) or intercept(handler) returns a handle, { id }. Parameter Type Description selector string or function Optional. A message name, "*", or a function that returns true for the messages to decide about. With only one argument, handler is asked about every message. handler function Called with the traffic object, and returns a decision. It must answer synchronously. An interceptor is asked about the messages the bot carries: every inbound message from the game server, and on a MITM bot every outbound message from the Dofus client. It's never asked about: a message the script sends, nor one sent from the bot's Network tool, although both still reach onTraffic(); on a MITM bot, the Dofus client's first message, and the responses to the script's own request() calls; a message Asterobot can't read at all. A MITM bot forwards such a message without showing it to the script, and a Full socket bot closes its game connection when it receives one. For each message, interceptors are asked in the order they were registered. One whose selector doesn't match, or that returns no decision, leaves the message to the next. The first decision wins, and the interceptors after it aren't asked. As soon as one interceptor is registered, every message the bot carries waits for the script, whatever the selectors say, because Asterobot checks them inside the script. Remove an interceptor with off() once you no longer need it. Return value What happens undefined, null, or any value not listed below No decision. The next interceptor is asked, and when none decides, the message goes on unchanged. "drop" The message stops here. On a MITM bot the other side never receives it, and on every bot on(), onTraffic() and wait() never see it. On a Full socket bot, dropping the response to a request() makes that request() wait for its timeout. { payload } The message goes on, rebuilt from these fields. It keeps its name, its kind and its uid. A field you leave out of the object is left out of the message, and the fields Asterobot has no name for are lost. { raw } The message is replaced by these bytes, a Uint8Array or an ArrayBuffer. They stand for the whole message as it travels on the connection, envelope included, not for traffic.rawAny. raw is used when both raw and payload are present. After a replacement, the script's handlers and waits see the new message. A MITM bot forwards raw bytes even when Asterobot can't read them, and its handlers then see the original message. On a Full socket bot, bytes Asterobot can't read change nothing. An async handler returns a promise, and a promise isn't a decision: the message goes on before the promise settles. If that promise rejects, nothing catches it, and the script stops with unhandled Promise rejection: <error>. The message waits at most 50 ms for a decision, counted from when it arrives, so time the script spends on other work counts too. Past that, it goes on unchanged and nothing is written anywhere: the handler keeps running, and its answer is ignored. On a MITM bot, the person playing feels a slow interceptor as lag. Intercepting explains these rules with examples. When a handler throws, or its selector function throws, the script doesn't stop. The message goes to the next interceptor, or on unchanged, and the bot's Console gets an error line for that message. When several interceptors fail on the same message, the line is written for the first failure only, and again at the tenth, ending with (10 times). Console line Cause interceptor failed: <error> The handler threw, or ran for more than 250 ms interceptor selector failed: <error> The selector function threw build replacement payload for <type>: <error> The payload doesn't fit the message. <error> is one of the payload errors. re-encode <type>: <error> Asterobot couldn't rebuild the message with the new fields cannot replace the payload of <wire name>: this build has no name for it, so there is nothing to build a replacement from - use { raw } instead { payload } was returned for a message without a readable name intercept() throws a TypeError, like on(), for a bad selector, with protocol.intercept handler must be callable when handler isn't a function, and with maximum behavior handlers reached past the limit. intercept("ChatChannelMessageRequest", (traffic) => { if (!traffic.payload) return; return { payload: { ...traffic.payload, content: traffic.payload.content.toUpperCase() } }; }); On a MITM bot, this turns everything the player types in chat into capital letters before the server receives it. off() off(handle) returns undefined. It removes a handler registered with on(), onTraffic() or intercept(), given the handle they returned. A handler removed while a message is being handled doesn't run for that message if its turn hasn't come yet. off() never throws: a handle that was already removed, or anything else, is ignored. It doesn't remove what onChange() from asterobot:parameters registered: use offChange() for that. The traffic object Handlers, selector functions and interceptors receive a traffic object, and wait() and request() resolve with one. Traffic explains its properties in depth. Property Type Value sequence BigInt The message's position in the bot's traffic. A dropped message leaves a gap. On a MITM bot, the object request() resolves with has 0n. direction string "inbound" from the server towards the client, "outbound" from the client towards the server kind string "request", "event" or "response". The type also lists "unknown", which scripts never receive, because a message Asterobot can't read isn't delivered. uid number The number that pairs a request with its response. -1 on events, and on messages sent with send(). type string The message's readable name, or "" when Asterobot has no name for it wireType string The name the message has on the connection, which changes between game versions typeUrl string The full type identifier of the message's content payload object, or absent The message's fields, when Asterobot could read them unknown boolean true when there's no payload decodeError string, or absent What went wrong while reading the message's content. It can be there along with a payload. rawAny Uint8Array The message's content as bytes, without its envelope. Always present, possibly empty. Payload values A payload's field names are the camelCase names, such as senderName. When sending, the names from the protocol definitions, such as sender_name, work too, but not both for the same field. Payloads covers every case. Field type A script reads A script can send 64-bit integer A BigInt A BigInt, a string of digits, or a whole number no bigger than Number.MAX_SAFE_INTEGER either way 32-bit integer A number A whole number within the field's range Floating point A number A number Boolean A boolean A boolean String A string A string Bytes A Uint8Array A Uint8Array or an ArrayBuffer Enum The value's name, such as "GLOBAL", or a number for a value without a name The name or the number List An array An array Map An object with string keys A plain object Nested message An object An object, or null to leave it out Group of alternatives (oneof) Only the field that's set One field of the group at most A nested message, an optional field or an alternative that the message doesn't set is absent from payload. Other fields are always there, with 0, "", false or an empty array when they're not set. When sending, a field you leave out isn't sent. Setting a field to undefined is an error, and null is only accepted for a nested message. The editor completes message names and payload fields, and knows the types ProtocolTraffic, ProtocolOptions, ProtocolSelector, ProtocolSubscription and ProtocolDecision.
  18. Smooth a posté un record dans Publication
    Every package installed from the marketplace gets a name of the form @publisher:name, built from its file on asterobot.net. You don't type that name anywhere, but you choose what it's built from. The name at first install The first time someone installs a file, Asterobot builds the package's name from @, the file author's asterobot.net name, :, then the file's title. Both parts go through the same conversion: Letters are put in lowercase. Every run of characters other than the letters a to z and the digits 0 to 9 becomes a single hyphen, and hyphens at the start or at the end are dropped. Spaces, punctuation and accented letters all count as other characters. Author Title Package name Alice Chat Tools @alice:chat-tools Some_Author Auto Harvest 2! @some-author:auto-harvest-2 Alice Récolte auto @alice:r-colte-auto The version is the file's version on asterobot.net. In Asteroboard, the preview shows the full name and version after Will install as, before anything is installed. A title with no letter from a to z and no digit leaves nothing to build a name from, and neither does an author name written only with other characters. Such a file can't be previewed or installed. And since people type your package's name in their import statements when they depend on it, a title that converts to a readable name, such as Harvest Helper, serves them better than one full of accents or symbols. Official packages A file the Asterobot team marks as official on asterobot.net installs under @asterobot: followed by its converted title, whoever uploaded it. It shows the Official badge, and the preview's Author line says asterobot. A package imported from a .zip, or installed with library install, takes the name typed for it, which can start with @asterobot: too. It then shows Official without coming from the marketplace. Only a package installed from the marketplace has a Source row on its page: see Trusting a package. The name sticks Asterobot remembers which file each marketplace package came from. When someone installs or updates that file again, the package keeps the name it already has on their Asterobot, even if you've changed the title since. Updates keep reaching them. The name is only built again for someone who didn't have the package yet. So after you rename the file, new users get your package under the new name, while earlier users keep the old one. Bots, saved settings and dependencies all refer to a package by its name, so for Asterobot these are two different packages, and a package that depends on yours by one of the names gets its own copy under that name. The same happens with two files that have the same author and title: they install under the same name. Choose the title before you publish, keep it, and publish every new version on the same file. When the author or the official mark changes Each time Asterobot reads the file again, it checks the publisher part of the name: the file's current author, or asterobot when the file is marked official. When it no longer matches the name a package has: Someone who installs or updates the file again gets it as a new package, under a name built from the new author or mark. Their bots and saved settings stay with the package under the old name. A package that depends on yours under the old name can't be installed any more, because Asterobot refuses to fetch a dependency whose file now has another publisher. A change of your asterobot.net name has the same effect, since it's the publisher part of every name built from your files. A change of title alone is never refused. Names typed by hand Only an installation from the marketplace builds a name from a file. Import a .zip and library install use the name typed for them, which can have the same @publisher:name shape. Such a package isn't tied to any file: it has no Source row, Check for updates skips it, and Asterobot records no checksum of its files. Names and versions gives the rules every name follows. The marketplace describes the marketplace from the website's side.
  19. Smooth a posté un record dans Publication
    A package on asterobot.net changes by getting new versions on the same file. The people who installed it see the update the next time they check for updates, and choose when to move their bots to it. Prepare the new version In Library Manager, open your package's menu and choose Copy to a new version, with a higher version such as 1.0.1 or 1.1.0. Names and versions says which number to raise. Make your changes in the copy, and test it on your bots. Check what your changes do to the settings people saved: a renamed setting loses its values, and a new default doesn't reach the bots that already saved one. See Read settings. Go through Before you publish again. Export the new version, which gives a file such as my-bot-1.0.1.zip. Upload it to the same file On your file's page on asterobot.net, upload a new version of the file: Replace the attachment with the new .zip, so the file still has exactly one attachment. Set the file's version to the new version, written exactly as in your library. Say what changed, especially anything people have to do, such as checking a setting. Keep the title, since a new title gives new users a different package name: see How your package is named. Always update the existing file, never submit a new one for a new version. Asterobot checks for updates against the file a package was installed from, so people who have your package never see a new file as an update. And since a package's name comes from its author and title, a second file with the same title installs under the same name as the first, which only adds confusion. How people get the update Nothing changes on anyone's Asterobot by itself. When someone clicks Check for updates in Library Manager, Asterobot compares each package they installed from the marketplace with the current version of its file: The file's current version is They see Higher than theirs, both being semantic versions Update to followed by the version Lower than theirs Nothing: Asterobot never offers to go back The same as theirs Nothing Different, when either version isn't a semantic version Update to, even for an older version Update to installs the new version next to the old one. Their bots stay on the old version until they move them, one by one, and keep the settings they saved. The old version stays until they remove it. Update packages describes their side. Packages that were installed as dependencies of another package are checked the same way. Packages that depend on yours A package that depends on yours locks one exact version of it. Which version it gets depends on what its author declared, at the moment someone installs that package: The dependency's version The version installed An exact version, such as 1.0.0 That version: your file's current version when it's the same, otherwise the same version from your file's history A range, such as ^1.0.0 The highest matching version, among your file's current version and its history latest Your file's current version Afterwards, the installed package keeps the version it got. Your update doesn't change it, even when the same person installs your new version with Update to. It moves to a newer version of yours when its author publishes an update of their own package, or when someone installs that package again and its declaration picks your newer version. Keep older versions available Asterobot fetches an older version of your package from your file's version history on asterobot.net. A package that locks your 1.0.0, while your file's current version is 1.1.0, can only be installed as long as 1.0.0 is still in that history. When it isn't, installing that package fails. So keep your previous versions in the file's history for as long as other packages may lock them. Never reuse a version number Once a version is published, never publish different code under the same number, not even to fix a mistake a minute later. Asterobot tells versions apart by their number alone: People who already installed that version aren't offered the new code, since Check for updates sees the same version. They only get it if they happen to install that version again. People who install it afterwards get the new code under the same number. Two people with 1.0.1 then run different code, and nobody can tell from the version which one they have. The same goes for packages that lock that exact version: installed before, they keep the old code, installed after, they get the new one. Asterobot can't notice it. It records a checksum of a package's files when it installs the package from the marketplace, and refuses the package if those files change on that computer afterwards. That protects what was downloaded, but the checksum comes from the download itself, so it can't tell that the file on asterobot.net changed. Publish the fix as a new version, such as 1.0.2, instead. That one reaches everyone through Check for updates. When a version goes wrong If you publish a broken version, don't set the file back to an older version number: the people who installed the broken one wouldn't be offered the older one, since Asterobot never offers to go back. Publish a fixed version with a higher number, and say in the description which version to avoid. Next, How your package is named.
  20. Smooth a posté un record dans Publication
    The marketplace is the Library category of asterobot.net, and any asterobot.net member can publish a package there. Publishing means submitting a file on the website with your exported .zip attached. You need an asterobot.net account, as Create an asterobot.net account explains, and the .zip of your package, exported once you've gone through Before you publish. What Asterobot reads from your file When someone pastes the link of your file in Asteroboard, Asterobot reads a few things from the file on asterobot.net: On asterobot.net What Asterobot does with it The file's title Turns it into the second part of the package's name, in lowercase with hyphens: Chat Tools gives chat-tools. Your asterobot.net name, as the file's author Turns it into the first part of the name, the publisher: @alice:chat-tools. The official mark, set by the Asterobot team Replaces the publisher with asterobot, and shows Official instead of Community. The file's version Uses it as the package's version. The file's attachment Installs it as the package. The file must have exactly one attachment: your .zip. Who can download the file Decides who can install the package. Asterobot doesn't read the description, the screenshots or anything else on the file's page, and doesn't check which category the file is in. Those are for the people who look for packages, and they look in the Library category. How your package is named goes into the naming rules. Submit the file Sign in on asterobot.net and open the Library category: https://asterobot.net/files/category/10-library/. Start submitting a new file in that category. Attach your exported .zip, and nothing else. Give the file its title. Choose it with care: it becomes part of the package's name, and a title changed later gives new users a different name. Set the file's version to your package's version, written the same way, such as 1.0.0. A file without a version can't be installed. Write the description: what the package does, whether it works on Full socket bots, MITM bots or both, its settings and actions, the packages it depends on and the permissions it declares. Submit the file. Anything else you fill in on the website is for the people reading your file's page: it doesn't change how Asterobot installs the package. Check that it installs Install your package on your own Asterobot, the way anyone would: Copy the address of your file's page. In Library Manager, click Download package, paste the address in File ID or URL, then click Preview. Check the card: your title, the Community badge, your name after Author, and after Will install as, the name and version you expect, such as @alice:chat-tools@1.0.0. Click Install. Package installed appears, and the package has its own row in Library Manager. Start it on a bot. Remove it from your library afterwards if you don't need it. If Preview says File not found, or you don't have permission to view it., check that your file is visible on asterobot.net, that your Asterobot is signed in, and that the title and your asterobot.net name each contain at least one letter from a to z or a digit. When the installation itself fails, Asteroboard only says Couldn't install the package. The causes on your side: Cause Fix The file has more than one attachment, or none Keep your .zip as the file's only attachment Asterobot doesn't find index.js in the archive Export the package again from Library Manager and attach that archive. See Zip files and the command line. The archive is larger than 32 MiB Leave out what the package doesn't need The file has no version, or its version contains / or \ Set a version such as 1.0.0 The archive's asterobot.json isn't valid JSON, or a dependency's version isn't valid Fix the manifest, export again and upload the new archive as a new version A dependency has no URL, its URL points to a file whose author doesn't match the dependency's name, or the version it locks isn't available See Dependencies When a dependency is what failed, your package itself may already be installed, without that dependency. Remove it before trying again. Share the link The address of your file's page, such as https://asterobot.net/files/file/12-chat-tools/, is all people need. They paste it in Download package, or in Download one wherever they choose a package for a bot, as Install from the marketplace shows. The number at the start of the last part of the address, 12 here, is the file's ID, which works on its own too. Asterobot only reads that number, so an address that still has an old title in it keeps working after you rename the file. Other authors use the same address as the URL of a dependency on your package. Who can install it Asterobot downloads the file with the asterobot.net account it's signed in with. When that account isn't allowed to download your file, the preview says You don't have permission to download this file. and Install stays disabled. Who can download a file is decided on asterobot.net, not in Asterobot. The marketplace describes the website's side. Next, when you change your package, Publish an update.
  21. Smooth a posté un record dans Publication
    Once your package is on asterobot.net, people install it on their own Asterobot, with their own bots and their own settings, and a version they installed stays as it is. So go through this list before you upload anything. Each item links to the page that explains it. It runs on both kinds of bot The script identifies a Full socket bot on its initial launch, with the lines from Identify and run. Without them, a Full socket bot never gets into the game. You tested it on a MITM bot (man-in-the-middle: the bot relays the game session of a Dofus client someone plays), and on a Full socket bot when what the package does allows it. Every await inside a handler sits in try/catch, and every wait() has a timeout. An error that escapes a handler stops the script: see Errors. The editor's error badge shows no error. The script says so on the bot's console when something it expects doesn't happen, and never writes session.gameToken anywhere. Settings and actions Package settings, on a bot set to your package, shows your cards and not Couldn't read this package's settings. Every setting has a label, a sensible default, a description when the label isn't enough, and a unit for a number that has one. See Declare settings. The keys of your settings are the ones you'll keep. Renaming a setting in a later version loses the values people saved: see Read settings. Each action works while the script runs, and its key reads well in the toasts that end with finished and failed. Dependencies and manifest Every dependency is published, has the URL of its file on asterobot.net, its marketplace name and an exact version. See Dependencies. If the package declares a compatibleVersion, its page shows Compatible on your Asterobot. See Compatibility. The permissions list what the package really uses. See Permissions. The manifest has no sourceId and no integrity. A package created, copied or imported in Asteroboard never has them. Version and name The version is a semantic version, three numbers such as 1.0.0. See Names and versions. You never published different code under this version. See Publish an update. The title you'll give the file, and your asterobot.net name, contain letters from a to z or digits, since the package's name is built from them. See How your package is named. The archive You exported the version you tested, with Export in Library Manager. See Zip files and the command line. The archive weighs less than 32 MiB. You imported the exported .zip under another local name, such as my-bot-check, and that package starts on a bot. That's the code people will install. Remove the check package afterwards. Next, Publish on asterobot.net.
  22. A package leaves Asterobot and comes back as a .zip file: that's what Export produces, what Import a .zip reads, and what you upload to publish on asterobot.net. The command line installs packages from a folder instead, which lets you write them in the code editor you already know. What goes in a .zip The simplest archive, and the one Export produces, has the package's files at its root: index.js asterobot.json lib/format.js The rules: index.js has to be at the root of the archive. Only .js and .mjs files are read, in any subfolder, following the paths described in Package layout. Other files, such as a README or images, are left out and not kept. asterobot.json is optional, next to index.js. Its sourceId and integrity are removed at import, as The manifest explains. An archive can't be larger than 32 MiB, whether it's imported or installed from the marketplace. The archive doesn't carry the package's name and version. They come from the import dialog, or from the file on asterobot.net. Asterobot also accepts an archive whose files all sit in a single folder, such as my-bot/index.js, which is what compressing a folder produces. That only works when the archive lists nothing but the folder's contents. With a file or a second folder next to it, or with an entry for the folder itself, which some archiving tools add, Asterobot doesn't find index.js and the import fails. To avoid surprises, compress the files of the package rather than the folder that holds them. Export In Library Manager, open a package's menu, or right-click its row, and choose Export. The package's page has the same menu. Your browser downloads the archive. The file is named after the package and its version, with the @ removed and the : turned into -: version 1.2.0 of @alice:chat-tools gives alice-chat-tools-1.2.0.zip, and my-bot 1.0.0 gives my-bot-1.0.0.zip. The archive holds every file of the package at its root: its modules, and asterobot.json when it has one. The manifest is exported as it is. For a package installed from the marketplace, that includes its sourceId and integrity, which an import removes but library install keeps. Every package can be exported, local or not. Import a .zip Share your package walks through the dialog, and Manage your library lists its messages. The details that matter to an author: Version fills in by itself when the file's name ends with a hyphen and a version starting with a digit, as in my-bot-1.2.0.zip, and only while Version is empty. A version with its own hyphen defeats it: my-bot-1.2.0-rc.1.zip suggests nothing. Any valid name works, local or @publisher:name. Only a local name gives a package you can edit. An installed package with the same name and version is replaced. The dialog warns you, but, unlike New package, it doesn't disable the button. Importing never installs the package's dependencies. Install them first, or the package won't start. When the import fails, the toast Couldn't import the package gives no reason. Unzip the archive and install the folder with library install: the command prints the reason for most failures. The command line Three commands of the library group manage the library from a terminal. Command line explains how to run Asterobot's commands on each system. library install asterobot library install --path ./my-bot --name my-bot --version 1.0.1 Flag Required What it takes --path Yes The folder holding the package's .js and .mjs files, and its asterobot.json if it has one --name Yes The package's name: a local name, or @publisher:name --version Yes The package's version What it does: It copies every .js and .mjs file of the folder and of all its subfolders, whether index.js imports it or not, and asterobot.json from the root of the folder. Other files are ignored. Keep the folder to your package's own files. It replaces the package if that name and version are already installed. It refuses a folder that contains a symbolic link anywhere, with module source "<path>" is a symbolic link. It keeps the sourceId and integrity of the folder's asterobot.json, when the file has them. It doesn't download dependencies. On success, it logs Package my-bot@1.0.1 installed. On failure, the message contains failed to install package: followed by the reason, such as package my-bot@1.0.1 entry "index.js" is missing from its sources. library list asterobot library list Prints a table with one row per installed version, sorted by name, then by version: the name, the version, the provenance and the publisher, which is empty for a local package. NAME VERSION PROVENANCE PUBLISHER @alice:chat-tools 1.2.0 community alice my-bot 1.0.0 local my-bot 1.0.1 local library remove asterobot library remove --name my-bot --version 1.0.0 Both flags are required. The command removes that version and logs Package removed. Unlike Library Manager, it doesn't warn about the packages that depend on it or the bots set to it. A version that isn't installed fails with a message containing package my-bot@1.0.0: package is not installed. Asteroboard shows what these commands changed the next time it loads the library, for example after you reload its page. Work with your own code editor Only Asteroboard's editor completes the asterobot: modules and the game's messages. If you'd still rather write the code in the editor you're used to, keep the package in a folder of your own and install it with library install after each change: Create a folder with index.js, your other modules and, if the package needs one, its asterobot.json without sourceId or integrity. To start from a package you already have, export it, unzip it, and remove those two keys from its manifest if they're there. Edit the files. Install the folder, under the same name and version for as long as you work on that version: asterobot library install --path ./my-bot --name my-bot --version 1.0.1 The first time, set a bot to that package and version with Load a new package, reloading Asteroboard's page first if the package isn't listed. Then click Play, and Stop then Play after each new install: every start reads the files installed at that moment. Go back to step 2. While you work this way, don't save that package from Asteroboard's editor: saving replaces the installed files with what the editor holds, and Run saves first. When the version is done, move on to a new --version for the next changes. You can also edit the files of a local package directly in the library, inside Asterobot's data folder, and the next start reads them. The same caution about Asteroboard's editor applies. Never change the files of a package installed from the marketplace there: its integrity check then refuses it. Files and folders says where the data folder is. Next, the Publishing chapter starts with what to check before you publish.
  23. Smooth a posté un record dans Paquets
    The editor is where you write a package's code, in Asteroboard. Create a package gave a first tour of it. Some of its parts aren't obvious: saving replaces the package's files, Run does four things in one click, and a clean error badge doesn't mean the script will run. Open it In Library Manager, open a package's menu, or right-click its row, and choose Edit code. On a package's page, click Edit code at the top. New package and Copy to a new version open the editor on the package they create. Edit code is only offered for local packages. When the editor can't load a package's files, for example because the package was removed in the meantime, it shows Couldn't load this package's code instead of the files and the code. The top bar Item What it does Box icon Opens the package's page, where its manifest is edited. Name and version The package open in the editor. Unsaved Shown while the files differ from what was last saved. Incompatible Shown when the package's range of Asterobot versions doesn't include the one you run. See Compatibility. Error count A badge such as 2 error(s), shown when the editor finds errors. Resting the pointer on it says "Across every file in this package, not only the open one." IntelliSense Grey while the editor loads its typings, green once it completes and checks the asterobot: modules and the game's messages, orange when the typings couldn't be loaded. Resting the pointer on it says how many game messages it knows, or Couldn't load the typings - editing works, completion doesn't. Bot picker The bot that Run starts the package on. It lists the bots connected to a game server, and only appears when there's at least one. Game Opens or closes the Game panel next to the code. Save Saves every file. Disabled while nothing has changed. Run Saves, then starts the package on the bot in the picker. Disabled when no bot is connected to a game, with the tooltip No bot is connected to a game. Files The Files column lists the package's modules: index.js first, then the others in alphabetical order, each with its folders in its path, such as lib/format.js. Click a file to show it. Each file keeps its own cursor, scroll position, selection, folded blocks and undo history while you move between files. A dot after a file's name marks unsaved changes in it, and a number next to it counts its errors. The plus button at the top of the column opens Add a file. Type a Path, such as lib/helper.js, then click Add a file or press Enter. The dialog refuses a path that doesn't end in .js or .mjs, one that starts with / or contains .. or \, and one already used. The new file is empty, opens right away, and only exists in the package once you save. Package layout gives every path rule, including one the dialog doesn't check. Resting the pointer on a file shows a trash icon. It removes the file from the list at once, without asking, and saving then deletes it from the package. index.js has no trash icon. The other files see a new file as soon as you add it, before you save: an import of it completes and isn't marked as an error. Jumping to a function's definition, or renaming it, works across the files of the package the same way. Saving Click Save, or press Ctrl+S (Cmd+S on a Mac). Saving replaces the package's files with exactly the files the editor holds. A file you removed from the list is deleted, and so is any file that isn't in the list, such as one added to the package from somewhere else while the editor was open. The manifest isn't touched. What else to know about saving: Unsaved also goes away when you undo your changes back to what was last saved. Removing a file counts as a change, even when every other file is unchanged. Nothing asks you to save when you leave the editor: unsaved changes are lost. The editor reads the files when it opens. If the package changes elsewhere in the meantime, in another tab or from the command line, the editor doesn't show it, and saving from the editor overwrites that change. With the same package open in two tabs, the last save wins. A script that's already running keeps the code it started with. The next start uses what you saved: see Play, stop and reload. Saving doesn't check the declarations of settings and actions. Package settings, on a bot set to the package, does: see Declare settings. When saving fails, a toast says Couldn't save, without a reason. The usual causes are a file path Asterobot refuses, such as ./lib/helper.js, a package that was removed in the meantime, or a package that isn't local. Run Run does in one click what you'd otherwise do in four steps: It saves the package. If saving fails, it stops there, with Couldn't save. It stops the script the picked bot is running, if there's one. It sets the bot's package to this package, at this version. It starts the script. A toast then says Running on followed by the bot's name, or Couldn't run the package when a step failed. The bot keeps this package afterwards, so Play on its page starts it again. Identify and run shows where the output goes and what to check when the script doesn't start. The Game panel Game opens a panel next to the code, with two tabs: Atlas, the map viewer, and Datacenter, the game data browser. Drag the line between the code and the panel to share the width differently. Closing the panel only hides it: when you open it again, each tab is where you left it. The panel shows one game version: The game version Asterobot is tested against, when it's downloaded and its game data extracted. A badge shows the version number, and resting the pointer on it explains that Atlas and Datacenter answer from that version, the one bots meet. Otherwise, the most recent game version with extracted game data. The badge then reads Incompatible version, and resting the pointer on it explains the difference: fine for writing a script, since most IDs carry over between neighbouring versions, but not the version Asterobot connects bots with, and map IDs, table columns and game texts can differ. When no game version has extracted game data, the panel says No extracted game version. Extract one, as Extract game data shows, then open the panel again. Names and texts in the panel use the language picked for game data on the game's page. Map viewer and Game data describe both tools. Completion and error checking The editor loads its typings from the Asterobot it's connected to: the asterobot: modules, and a type for each game message that version of Asterobot knows. With them, it completes functions, message names and payload fields, and checks your code the way TypeScript checks JavaScript. JavaScript support describes that check, the warnings for the three constructs scripts don't support, and the JSDoc types you can use. The error badge and the numbers in Files count errors only, in every file, open or not. Warnings aren't counted. Neither errors nor warnings stop you from saving or running. Two mistakes are worth looking for in the editor before anywhere else: a syntax error, and an import of a file that doesn't exist. When a script can't even load because of one of them, Run only says Couldn't run the package, while the editor shows the line, and Package settings, on the bot's Settings tab, gives the full reason. On the other hand, code with no error in the editor can still fail when it runs: the editor knows the shape of messages, not what the game will send. Marketplace packages are read-only A package installed from the marketplace, or any package with a @publisher:name name, can't be edited. Library Manager doesn't offer Edit code or Copy to a new version for it, Asterobot refuses to save its files, so Couldn't save appears if you reach the editor for it anyway, and its Manifest card is read-only. The package stays exactly as it was installed, which the integrity check described in The manifest relies on. When you need another author's code in your package, depend on their package, as Dependencies explains. When you really need to change it, export it and import the .zip under a local name: the imported package is yours to edit. It has no link to the marketplace any more, so no updates and no Source row, and bots save its settings under its new name. See Zip files and the command line. Next, Zip files and the command line.
  24. Smooth a posté un record dans Paquets
    A package can list permissions in its manifest: short names for what it uses. For now the list is information for the people who install your package, and Asterobot doesn't limit anything based on it. Note Coming soon. Asterobot will enforce the permissions a package declares. Until then, any package can use every function of every built-in module, whatever it lists. Declare them Open your package's page and find the Manifest card. Under Permissions, type a name in the box that reads e.g. gamedata, then click Add or press Enter. The name appears as a badge. Add the other permissions the same way. The button on a badge removes it. Click Save manifest. In the file, the list is the permissions key: { "permissions": [ "gamedata" ] } The rules: Each permission is a string. Asterobot doesn't define permission names yet, so it accepts any text and shows it as you wrote it. The card leaves out empty names and names already in the list. In a file, an empty entry or a name listed twice stops the package from starting, with empty package permission or duplicate package permission "<permission>". The list comes along when the package is copied, exported or imported. Where they show On the package's page, the Manifest card shows each permission as a badge, or "No permissions declared." when there's none. For a package installed from the marketplace, the card is read-only, so the people who install yours read your list there. A script reads the list with packages.info(), from asterobot:runtime, for its own package and for each of its dependencies. This one writes its own list to the bot's console when it starts, assuming the package is named my-bot: import { session, botInfo } from "asterobot:bot"; import { send } from "asterobot:protocol"; import { packages } from "asterobot:runtime"; export default async function behavior(launch) { const self = packages.info("my-bot"); botInfo(`${self.name} ${self.version} declares: ${self.permissions.join(", ") || "no permission"}`); // 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, }); } } permissions is always an array, empty when the package lists nothing. asterobot:runtime describes the other properties, and the errors packages.info() throws for a name the script doesn't load. What to list Since nothing checks the list today, it's worth exactly as much as it's honest. Name what your package really does, with words people understand, such as gamedata for a package that reads game data, the example the card itself gives. The names Asterobot will check aren't defined yet: expect to update your list, in a new version of your package, once they are. Whatever the list says, people deciding whether to run your package should know what any package can do with their bot. Trusting a package explains it from their side. Next, The editor.
  25. Smooth a posté un record dans Paquets
    compatibleVersion says which versions of Asterobot your package works with. When someone runs an Asterobot outside that range, the package is marked Incompatible and doesn't start. It's about Asterobot's version, not the game's: a Dofus update changes nothing here. After a game update covers what a game update changes for scripts. Declare it On your package's page, type the range in Compatible with, in the Manifest card, then click Save manifest. In the file, it's the compatibleVersion key: { "compatibleVersion": ">=1.5.0 <2.0.0" } Without a range, the package works with every version of Asterobot and nothing is checked. To find the version you run, see Updating, or run asterobot --version. Write the range A range is one or more conditions on the version of Asterobot: Range Matches >=1.5.0 1.5.0 and every later version >=1.5.0 <2.0.0 From 1.5.0 up to 2.0.0, without 2.0.0. A space or a comma between two conditions means both must hold. ^1.5.0 The same as >=1.5.0 <2.0.0: this version and the later ones with the same first number ^0.4.0 From 0.4.0 up to 0.5.0, without 0.5.0. When the first number is 0, the second one has to stay the same. ~1.5.0 From 1.5.0 up to 1.6.0, without 1.6.0 1.5.x Every 1.5 version 1.5.0 - 1.8.0 From 1.5.0 to 1.8.0, both included. The hyphen needs a space on each side. <1.6.0 || >=1.7.0 Either condition: here, every version except the 1.6 ones !=1.6.2 Every version except 1.6.2 1.5.0 Only 1.5.0 A version with a suffix after a hyphen, such as 1.6.0-rc.1, only matches a range that has such a suffix itself, as in >=1.6.0-0. Without one, the range leaves those versions out, even when their numbers are inside it. Where it shows Where What you see Library Manager, Compatibility column Compatible or Incompatible, only for a package that declares a range The package's page The same badge next to the provenance badge, and a Compatible with row with the range The top bar of the editor Incompatible, when the Asterobot you run isn't in the range Package pickers, in Add a bot, Load a behavior selection and the bot's Settings tab (incompatible) after the package's name and version. The package can still be picked. The bot's header The package's name in orange, and incompatible versions in orange in its version menu A script packages.info(name).compatibleVersion, described in asterobot:runtime What it blocks Asterobot checks the range each time it loads the package: at every start, whether from Play, Run or Load a package and play it, and each time it reads the package's settings. When the Asterobot you run isn't in the range: The script doesn't start, and the toast gives no reason. Package settings, on the bot's Settings tab, and the Add a bot dialog show Couldn't read this package's settings with the reason: resolve package my-bot@1.0.0: package "my-bot@1.0.0": package "my-bot" requires asterobot >=1.5.0 <2.0.0, running 1.4.2 A package that depends on an incompatible package doesn't start either, even when its own range is fine. The reason then names the dependency. Nothing else is blocked. Installing from the marketplace, importing, copying, editing and saving all work on an incompatible package, and it stays in the library. After Asterobot is updated and started again, the badges and the checks follow the new version. Not checked when you save Asterobot doesn't check that a range is written correctly when you click Save manifest, nor when it installs a package. A range it can't read is saved as it is, and then: The package shows Incompatible everywhere, whatever version you run. Every start fails, and Package settings shows a reason containing has an invalid compatibleVersion constraint, followed by the range and what's wrong with it. So after saving a range, look at the badge at the top of the package's page. If it says Incompatible while the Asterobot you run should match, read the range again: >=1.5.0 and <2.0.0 or 1.5.0+, for example, aren't ranges Asterobot can read. When to declare a range Declare a lower bound when your package needs something a given Asterobot version brought: a function, a message name, a fix. Use the first version that has it. Add an upper bound only when you know a later version breaks your package. A bound that's too tight marks your package Incompatible for people whose Asterobot would have run it fine. A new range reaches the people who installed your package only with a new version of it: see Publish an update. Next, Permissions.

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.