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.

Item names from game data

(0 notes)

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:

  1. On the bot's Inventory tab, with Live inventory on, open a row. Its details show the item's id after item.
  2. In Game Manager, open the bot's game version, go to its Game data tab, then to SQL.
  3. 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

  1. 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.
  2. 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.
  3. 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.
  4. Play. When the quantity of one of your stacks changes, a line such as Blé: 43 (+3) appears.
  5. 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.

Commentaires des utilisateurs

Aucun avis à afficher.

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.