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
  • Adhésion

  • Dernière visite

Tout ce qui a été publié par Smooth

  1. Excellent je valide! Et j'ai encore mieux. Vous pourrez même utilisez des proxies HTTP pour la connection Socket!! Je vais étendre le système de proxy afin que chaque compte puisse disposer de son propre proxy, qui sera utilisé pour l'authentification Chromium, l'échange de jetons, les codes HAAPI/Zaap, et par défaut pour les bots connectés à ce compte. J'ajouterai également la prise en charge des proxys HTTP et SOCKS5, y compris les proxys authentifiés, tout en m'assurant que Chromium ne se rabatte jamais sur l'adresse IP directe de la machine en cas de défaillance du proxy. Pour les bots, le proxy peut soit hériter du proxy du compte, soit utiliser un proxy spécifique, soit se connecter directement. Vous pourrez ainsi facilement faire partager le même proxy à plusieurs comptes ou utiliser un proxy différent/moins cher pour la connexion au jeu. J’apporterai également les modifications nécessaires à l’interface utilisateur du gestionnaire de proxys et du gestionnaire de comptes, notamment concernant le type de proxy, les affectations de comptes/bots et la protection contre la suppression d’un proxy encore en cours d’utilisation.
  2. Smooth a posté un record dans Notes de version Asteroboard
    0.2.5 - 02/10/2026✨ AjoutéNouveau boutton dans le sniffer réseau qui permet de rapidement rejouer un message client ou serveur 🔄 ModifiéMise à jour des dépendances
  3. Asterobot, le nouveau bot dofus gratuit est ouvertAujourd'hui, j'ouvre Asterobot à tout le monde, et je suis fier de pouvoir enfin écrire cette phrase. J'ai passé des années sur ce projet. À le construire, bien sûr, mais surtout à réfléchir longuement à sa structure : comment un bot doit se connecter au jeu, comment un script doit lui parler, comment une seule interface peut montrer tout ce qu'un bot voit. Beaucoup de ces idées ont été réécrites plus d'une fois avant de me sembler justes. Chaque partie qui parle au jeu a été écrite de zéro et vérifiée par rapport à ce que fait vraiment le client Dofus. Aujourd'hui, il est entre vos mains, et ça compte beaucoup pour moi. Asterobot est un framework de bots pour Dofus 3. Il tourne sur votre propre ordinateur, connecte des bots au jeu de deux façons différentes, exécute les paquets JavaScript que vous leur donnez, et décortique le jeu pour que vous puissiez voir comment il fonctionne. Vous pilotez tout depuis Asteroboard, dans votre navigateur. À partir d'aujourd'hui, toute personne qui a un compte asterobot.net peut le télécharger. J'espère que vous serez nombreux à l'apprécier : les joueurs qui veulent automatiser une partie de leur jeu, les développeurs qui veulent écrire leurs propres scripts, et tous les curieux qui veulent savoir ce que Dofus fait vraiment derrière l'écran. Cet article est long, parce que je préfère vous montrer comment chaque partie fonctionne plutôt que de vous donner une liste de fonctionnalités. Si vous voulez essayer tout de suite, la dernière section met en route votre premier bot en quatre étapes. Trois parties, un seul système, un bot dofus en mode Client/Serveur!Utiliser Asterobot, c'est utiliser trois choses ensemble : un programme qui fait le travail, une interface pour le piloter, et un site autour des deux. Savoir qui fait quoi rend tout le reste plus facile à suivre. Asterobot est le programme. C'est un seul fichier pour votre système, sans installateur : vous le téléchargez, vous le décompressez et vous le lancez. Tout se passe à l'intérieur. Vos bots et leurs connexions à Dofus, les comptes Ankama que vous enregistrez, les versions de Dofus qu'il télécharge, les paquets de votre bibliothèque et les scripts qu'ils exécutent vivent tous dans ce seul programme. Ses réglages tiennent dans un seul config.toml, et le reste de ses données se trouve dans les dossiers de votre profil utilisateur. Asterobot n'a pas de fenêtre à lui : il tourne dans un terminal, et tant que ce terminal est ouvert, Asterobot tourne. Asteroboard est l'interface, sur asteroboard.asterobot.net. La page vient d'internet, mais tout ce qu'elle affiche vient de votre propre Asterobot. Votre navigateur se connecte directement à l'adresse d'Asterobot, 127.0.0.1:8080 par défaut, et Asteroboard affiche ce qu'Asterobot lui transmet et lui envoie ce sur quoi vous cliquez. Ouvrez cette adresse dans un navigateur et Asterobot vous renvoie vers Asteroboard avec l'adresse jointe : il n'y a rien à configurer. Dès lors, chaque changement arrive en direct dans la page. Asteroboard a aussi une page pour Asterobot lui-même, Serveur, avec son état, son journal en direct avec des niveaux réglables, et chaque réglage avec sa description, modifiable depuis le navigateur. Comme les bots vivent dans Asterobot et pas dans la page, fermer Asteroboard n'arrête rien. Fermez l'onglet, revenez plus tard ou depuis un autre appareil, et tout est là où vous l'avez laissé : les bots toujours connectés, les scripts toujours en cours, les téléchargements toujours en route. La console d'un bot reprend même avec les 200 dernières lignes qu'Asterobot a gardées pour lui. Vous pouvez installer Asteroboard comme une application depuis votre navigateur, et il s'ouvre alors dans sa propre fenêtre. Une fois chargé, il continue de fonctionner sans connexion internet tant qu'il atteint votre Asterobot, et il vérifie toutes les 30 minutes s'il existe une nouvelle version de lui-même. Il est disponible en anglais, en français et en espagnol, et suit la langue de votre navigateur tant que vous n'en choisissez pas une. asterobot.net est le site : votre compte, les téléchargements, le marché où les joueurs partagent des paquets, le forum, la référence du protocole Dofus et la documentation. Asterobot se connecte avec votre compte asterobot.net, mais il ne demande jamais votre mot de passe. Il reçoit un code de huit caractères que vous confirmez sur le site, depuis n'importe quel appareil, ou en scannant un QR code affiché dans son terminal. Une même idée traverse les trois : rien n'est caché. De la connexion jusqu'au plus petit message échangé avec le jeu, vous voyez ce que fait votre bot, et un script peut agir sur tout cela. Connexion Deux façons de connecter un botUn bot peut rejoindre Dofus de deux façons. En mode MITM, il vous accompagne sur la partie que vous jouez. En mode Full socket, il se connecte et parle tout seul aux serveurs du jeu, sans aucun client de jeu. Un bot n'appartient pas à un mode : le mode, c'est ce qu'il fait quand vous le mettez en ligne. Un bot qui a un compte Ankama peut faire les deux, l'un après l'autre, puisqu'un compte n'a qu'une session de jeu à la fois. Un bot sans compte ne peut qu'attendre votre jeu. Mode MITMMITM vient de l'anglais man-in-the-middle, « homme du milieu ». Vous lancez Dofus depuis le launcher Ankama et vous jouez comme n'importe quel autre jour, et Asterobot se place entre votre jeu et les serveurs d'Ankama, en faisant passer tout ce qui s'échange dans les deux sens. Le bot qui reçoit votre session voit tout, et un script qui tourne dessus peut lire, envoyer, modifier ou bloquer les messages au passage. C'est la façon la plus simple de commencer : aucun compte Ankama à enregistrer, et aucun script nécessaire pour entrer en jeu, puisque votre propre Dofus s'en est déjà chargé. Voici comment votre jeu finit par passer par Asterobot. Quand vous cliquez sur Commencer l'attente sur un bot, Asterobot modifie une seule ligne de votre dossier Dofus : la ligne ConfigUrl de Dofus_Data/StreamingAssets/version, qui indique à Dofus où récupérer ses paramètres de connexion. Le reste du fichier n'est pas touché. Quand Dofus démarre et demande ces paramètres, Asterobot répond avec les vrais paramètres d'Ankama, modifiés à un seul endroit : le serveur de connexion, c'est désormais Asterobot. Votre jeu se connecte à travers lui, et Asterobot transmet tout à Ankama. Quand Ankama envoie ensuite votre jeu vers un serveur de jeu, Asterobot remplace cette adresse par celle de son propre port de session. Ce port n'accepte qu'une session dont le ticket est passé par la connexion d'Asterobot, et chaque ticket ne sert qu'une fois. Vous vous connectez vous-même, dans le jeu : Asterobot ne voit donc jamais votre identifiant ni votre mot de passe Ankama, et les comptes protégés par Ankama Shield ou par la double authentification fonctionnent comme d'habitude. La seule chose dont il a besoin, c'est de sa propre copie de la version exacte de Dofus à laquelle vous jouez : il compare les numéros de version avant qu'un bot commence à attendre, et refuse quand ils diffèrent. Tous les clients Dofus passent par les deux mêmes ports, l'un pour la connexion et l'autre pour la session de jeu (5555 et 5556 par défaut), quel que soit le nombre de bots. Asterobot distingue les sessions par ce qu'elles envoient, et associe chacune à un bot en attente selon le compte Ankama avec lequel elle s'est connectée. Un bot sans compte se rattache au premier compte qui se présente, et le garde. Une partie qu'aucun bot n'attend passe directement et se joue normalement : vos autres comptes ne sont pas concernés pendant que l'un d'eux est relayé. Quand vous quittez Dofus, le bot se remet à attendre votre prochaine session. Asterobot remet la ligne d'origine quand plus aucun bot n'attend, quand il s'arrête, et même à son démarrage suivant s'il a été arrêté brutalement. Les deux ports ne s'ouvrent que pendant qu'un bot attend, et n'écoutent que sur votre propre ordinateur, sauf si vous en décidez autrement. Avec quelques réglages, vous pouvez jouer sur un PC pendant qu'Asterobot relaie depuis un autre, et suivre le tout depuis un troisième. Un tunnel SSH peut tout transporter sans ouvrir un seul port. Mode Full socketEn mode Full socket, un bot fait tout seul ce que fait le client Dofus. Personne au clavier, aucune fenêtre Dofus ouverte : un seul ordinateur peut faire tourner autant de ces bots que vous avez de comptes Ankama. Quand vous cliquez sur Connecter, la connexion passe par trois étapes, et la console du bot raconte chacune d'elles. D'abord la connexion Ankama, qui donne au bot un ticket à usage unique pour le jeu. La connexion automatique pilote un vrai Chrome, Chromium ou Edge, sans fenêtre par défaut et avec son propre profil de navigateur pour chaque compte. Vous pouvez aussi vous connecter à la main depuis Asteroboard, ce qui ne demande aucun mot de passe enregistré. Quand une connexion cesse de fonctionner, Asterobot la renouvelle. Vient ensuite le serveur de connexion : le bot s'identifie avec le ticket et son identifiant de machine, reçoit la liste des serveurs du compte et en choisit un. Enfin, il ouvre sa propre connexion à ce serveur de jeu. Chaque échange a une limite de 30 secondes, pour que rien ne reste bloqué en silence, et quand quelque chose échoue, la raison complète s'affiche en haut de la page du bot. Chaque bot reçoit son propre identifiant de machine de 64 caractères quand vous l'ajoutez, la valeur que le client Dofus envoie pour l'ordinateur sur lequel il tourne, et le garde d'une connexion à l'autre. Le serveur qu'il choisit dépend de vous : celui de votre dernier personnage joué, un serveur que vous imposez, ou une carte Choisissez un serveur à chaque fois, avec l'état de chaque serveur, son type, son badge mono-compte et le nombre de personnages que vous y avez. Quand vous connectez dix bots d'un coup, leurs connexions Ankama se mettent en file et passent une par une, au lieu de se marcher dessus. Le rôle d'Asterobot s'arrête là, sur une connexion ouverte avec le serveur de jeu. Identifier le bot et choisir un personnage reviennent à un script, appelé script de connexion. C'est voulu : à partir de ce moment, tout ce que le jeu attend relève de la logique de jeu, et la logique de jeu a sa place dans les paquets, où vous pouvez la lire, la modifier et la remplacer. En écrire un tient en quelques lignes, montrées dans la documentation développeurs. Des scripts de connexion prêts à l'emploi existent désormais : le premier officiel, @asterobot:base, identifie le bot, choisit un personnage, entre dans le monde et maintient la connexion. Il est disponible dans la bibliothèque depuis octobre 2026. Le mode MITM, lui, n'a besoin d'aucun script de connexion. Suivre un bot pendant qu'il joueChaque bot a sa propre page dans Asteroboard, remplie en direct pendant qu'il joue. Sa connexion et son paquet sont dans l'en-tête, avec les boutons pour le connecter, le mettre en attente, le lancer et l'arrêter. Une ligne au-dessus des onglets garde en vue le niveau, l'expérience, les points de vie, l'énergie, les pods et les kamas, quel que soit l'onglet ouvert. C'est sur l'onglet Console que vous passerez le plus de temps. C'est un seul fil pour ce qu'Asterobot fait avec le bot, ce qu'écrit son script et le chat du jeu. Vous pouvez le filtrer par source, y chercher et le copier, et la zone de saisie en bas parle dans le chat du jeu en tant que votre personnage. Quand une connexion ou un script échoue, l'erreur complète s'affiche en haut de la page, sélectionnable, avec un bouton Copier, prête à coller dans un rapport de bug. Tout cela est construit à partir des propres messages du jeu. Asterobot ne transmet à Asteroboard que ceux que vous demandez, et quatre interrupteurs décident de ce qu'un bot envoie : personnage, chat, inventaire et carte. Quand votre navigateur ne suit pas, Asterobot saute des messages plutôt que de ralentir le bot. Le bot et son script ne manquent jamais rien : seule la page en manque. Personnage NouveauL'onglet Personnage est votre fiche de personnage, exactement comme le jeu l'envoie. Progression affiche le niveau et le niveau oméga, l'expérience jusqu'au niveau suivant, l'énergie, et les points de vie détaillés en base, vitalité, bonus, érosion et ce qui a été perdu. Chaque caractéristique est détaillée elle aussi, de sa valeur de base jusqu'aux points additionnels, à l'équipement, aux dons d'alignement, au contexte et aux effets temporaires, le tout regroupé en primaires, secondaires et dommages. Les résistances en pourcentage et les résistances fixes de chaque élément sont côte à côte. La carte Équipement passe en revue chaque emplacement, de l'arme et du chapeau jusqu'au familier, au compagnon et aux Dofus, avec un lien direct vers l'inventaire. InventaireL'onglet Inventaire affiche vos objets, vos kamas et vos pods, mis à jour pendant que vous jouez. Chaque objet affiche les effets que le jeu a tirés pour cette pile précise, bonus en vert et malus en rouge, avec sa description, son niveau et son poids. La barre de pods passe à l'orange à 90 % du maximum et au rouge dès que le personnage est surchargé : vous le voyez venir. Vous pouvez parcourir les catégories, chacune avec son nombre d'objets, chercher dans les noms, les types, les effets et les identifiants, trier, et choisir vos colonnes. Un clic droit détruit un objet, ou une partie d'une pile : Asteroboard envoie la requête du jeu lui-même, au nom de votre personnage. Chaque objet s'ouvre aussi sur sa fiche dans les données du jeu. Carte en direct NouveauL'onglet Carte montre où se trouve votre personnage. Sa position est épinglée sur la carte du monde, et sa carte est dessinée cellule par cellule à côté, avec le personnage sur sa cellule et tous ceux qui l'entourent : joueurs, monstres et PNJ, tenus à jour à mesure qu'ils arrivent, se déplacent et repartent. Chaque ressource et chaque objet de la carte est listé, disponible ou non, avec son bonus de récolte depuis sa dernière repousse, et qui l'a utilisé en dernier, et quand. La vue suit le bot de carte en carte, ou reste là où vous naviguez. Double-cliquez sur une cellule, ou faites un clic droit dessus et choisissez Marcher ici, et le personnage y va, par exactement le chemin que prendrait le client Dofus en contournant tous ceux qui sont sur la carte. Sur un bot MITM, vous voyez votre propre personnage y marcher dans le jeu. Sniffer réseauDofus et ses serveurs se parlent en échangeant des messages : une ligne de chat, un personnage qui se déplace, le contenu d'un inventaire. Ce que le client envoie est une requête, ce que le serveur envoie est un événement, et les quelques requêtes qui attendent une réponse directe en reçoivent une, associée à elles par un numéro appelé uid. Un bot parle au jeu de la même façon, et l'outil Réseau, dans l'onglet Outils d'un bot, vous montre chacun de ces messages. Dofus 3 rend cela plus difficile qu'il n'y paraît. Sur le réseau, ses messages ne portent pas de noms comme ChatChannelMessageEvent. Ils portent des noms courts et brouillés, qui changent à chaque version. Asterobot les retraduit, pour que vous lisiez les vrais noms, et vos scripts les utilisent aussi. Les messages qu'il ne sait pas encore nommer s'affichent quand même, sous leur nom brouillé : quand le schéma du jeu les décrit, leurs champs sont tout de même décodés, et sinon, vous obtenez les octets bruts. Un bot échange beaucoup de messages, donc le panneau Trafic commence vide. Vous cochez les messages que vous voulez suivre, par leur nom, et le choix est enregistré avec le bot. Il ne change que ce qu'affiche Asteroboard : un script reçoit toujours tout. Chaque ligne a la couleur de son type, vert pour les événements, rouge pour les requêtes et bleu pour les réponses. Elle s'ouvre sur ses champs en JSON, y compris ceux à leur valeur par défaut, avec les nombres de 64 bits gardés exacts sous forme de texte, un horodatage à la milliseconde, le numéro de séquence du message et son uid. Pour s'y retrouver, la zone de recherche comprend un petit langage : de simples mots, ou name:, kind:, direction:, uid:, payload: et d'autres, avec - pour exclure. direction:inbound -kind:event garde ce que le serveur a envoyé et qui n'est pas un événement, et un nombre seul retrouve un identifiant de personnage dans n'importe quel champ. Vous pouvez aussi envoyer des messages. Envoyer un message de jeu construit un formulaire à partir des types des champs du message, affiche le JSON exact, et le vérifie par rapport à la définition du jeu avant que quoi que ce soit ne parte. Il part vers le serveur au nom de votre personnage. Sur un bot MITM, Vers le client inverse le sens : le message arrive à votre propre Dofus comme si le serveur l'avait envoyé, ce qui est le moyen le plus rapide de voir comment le client réagit à quelque chose. La référence du protocole Dofus sur asterobot.net explique chaque message et ses champs. La propre copie de Dofus d'AsterobotAsterobot ne prend rien au Dofus auquel vous jouez. Il télécharge sa propre copie du jeu depuis les serveurs de téléchargement d'Ankama, une version à la fois, et travaille à partir de celle-ci. Le Dofus que vous avez installé avec le launcher reste le vôtre : la seule fois où Asterobot y touche, c'est pour cette fameuse ligne du mode MITM. Par défaut, cette copie n'est pas le jeu entier, seulement ce dont un bot a besoin : les textes du jeu, les tables de données, les données des cartes, le graphe de déplacement du monde et quelques jeux d'images. Une expression régulière décide des fichiers téléchargés, et une expression vide prend tout. C'est toujours la build Windows, même sous macOS et Linux, parce que deux fichiers qu'un bot lit se trouvent dans un dossier qui porte le nom de la cible de build Windows d'Unity. Rien n'exécute le jeu : elle ne coûte donc que de l'espace disque. Ankama livre le jeu en morceaux, et Asterobot stocke chaque morceau une seule fois, nommé d'après son contenu, quelle que soit la version d'où il vient. Un morceau qu'il a déjà n'est jamais retéléchargé : après un patch, seul ce qui a changé est téléchargé. Réessayer reprend un téléchargement échoué là où il s'est arrêté, et Vérifier les fichiers compare chaque fichier à la liste d'Ankama et ne retélécharge que ceux qui ne correspondent pas. Vous pouvez garder plusieurs versions côte à côte, chacune avec ses bots, et tout faire aussi en ligne de commande, avec les commandes gamemanager. Le jeu, décortiquéTélécharger n'est que la moitié du travail. Asterobot extrait ensuite les fichiers du jeu : il lit directement les fichiers Unity de Dofus et construit une base SQLite pour chaque version. Pour Dofus 3.6.11.15, cela fait 17 419 cartes, 234 tables et environ 2,9 millions de lignes. Tout ce que montre la page Jeu dans Asteroboard lit cette base, et rien de tout cela n'a besoin d'un bot. Les fichiers du jeu vont d'un numéro vers un texte, jamais dans l'autre sens : un monstre connaît le numéro de son nom, mais rien ne vous dit quel monstre s'appelle Bouftou. Asterobot construit donc l'autre sens, 497 135 noms en français, anglais, allemand, espagnol et portugais, indexés pour la recherche plein texte. Ouvrez un enregistrement dans Données de jeu et chaque colonne qui pointe vers une autre table devient un lien, qui affiche le nom de ce qu'elle désigne. Asterobot vérifie chaque lien pendant l'extraction, pour qu'un lien ne mène jamais nulle part. À côté des tables du jeu lui-même, il dérive celles que le jeu n'a pas, comme les cartes où se trouve chaque élément interactif, chaque ligne renvoyant vers son parent. Console SQLL'onglet SQL vous permet de demander n'importe quoi à cette base. Elle est en lecture seule par conception : toute instruction qui écrit échoue, attacher une autre base est refusé, et chaque requête dispose de 10 secondes et affiche jusqu'à 500 lignes. La complétion connaît les tables, les colonnes, les mots-clés SQL et, entre apostrophes, les noms des enregistrements : tapez 'Bouf et elle propose Bouftou. Le menu Exemples contient des requêtes prêtes à l'emploi sur les monstres, les objets, la récolte, l'artisanat, la navigation, les cartes, les quêtes et les textes, chacune avec des notes sur les tables qui contiennent quoi et les pièges à éviter. Une requête qui marche ici marche aussi dans un script, paramètres compris. Carte du mondeLa carte du monde montre où tout se trouve dans le Monde des Douze, et au-delà. Cherchez des coordonnées, un identifiant de carte, ou le nom d'une région, d'un donjon, d'un point d'intérêt, d'un monstre ou d'un objet, dans la langue de votre choix. Choisissez un monstre et les régions où il vit s'allument. Choisissez un objet et vous voyez où il se récolte et où vivent les monstres qui le lâchent. Les contours des régions, les points d'intérêt par catégorie, les zaaps, les donjons, une grille de coordonnées et une carte des niveaux sont des calques que vous activez ou désactivez. Cliquez sur une région et son panneau donne son niveau, son zaap, ses monstres avec leur rayon d'agression, ce qu'on peut y récolter et avec quel métier, et ce que lâchent ses monstres. Visionneuse de carteLa visionneuse de carte dessine n'importe quelle carte cellule par cellule, comme un bot la voit, dans six vues : sol praticable, placement de combat, ligne de vue, zones, hauteur et sorties, avec dix calques à activer ou désactiver. Ses sorties viennent du graphe que le jeu utilise lui-même pour planifier ses trajets, que vous marchiez, preniez une porte ou un escalier, ou utilisiez un parchemin, avec leurs conditions écrites comme le jeu les écrit. Les portes, les caves et les téléporteurs sont marqués sur leurs cellules, tout comme les objectifs de quête présents sur la carte, étape par étape. Les flèches du clavier vous emmènent à la carte suivante, et une flèche en contour vous signale qu'une carte voisine existe mais que rien n'y mène. Pointez une cellule pour lire chacun de ses indicateurs, tels qu'extraits du jeu, et les éléments interactifs qui s'y trouvent. Quand vous voulez garder une carte, enregistrez-la en PNG, exactement telle qu'elle est dessinée, avec la vue et les calques que vous avez choisis. Textes et musiqueL'onglet Textes retrouve n'importe quel texte du jeu par son numéro ou sa clé, dans les cinq langues, avec des statistiques sur le fichier de textes de chaque langue. Et il y a la bande originale : l'onglet Musique télécharge les quatre banques audio qui contiennent la musique de Dofus, environ 900 Mo, sans les sons de combat et d'interface, et la joue avec des albums, une file d'attente et la lecture en boucle. Un petit lecteur reste dans la barre du haut pendant que vous parcourez le reste d'Asteroboard. Les paquets et le marchéUn bot seul se connecte à Dofus et ne fait rien de plus. Pas de combat, pas de récolte, aucune réponse aux autres joueurs. Ce qu'il fait une fois en jeu vient d'un paquet : un petit programme JavaScript que vous installez dans votre bibliothèque et que vous donnez au bot. Pas besoin d'écrire de code pour en utiliser un. Le marché, la catégorie Librairie d'asterobot.net, rassemble les paquets que les joueurs partagent, et tout le monde peut en publier un. Pour installer un paquet, collez son lien dans Gestionnaire de bibliothèque > Télécharger un paquet. Une carte d'aperçu affiche son auteur ainsi que le nom et la version exacts sous lesquels il s'installera, et Installer l'ajoute avec tous les paquets dont il dépend, puisqu'un paquet peut s'appuyer sur d'autres, qui peuvent eux-mêmes s'appuyer sur d'autres. Chaque version s'installe à côté des autres, et chaque bot joue une version précise. C'est voulu : une mise à jour ne change jamais ce que joue un bot tant que vous ne l'y faites pas passer. Asterobot prend aussi l'empreinte d'un paquet du marché quand il l'installe, et vérifie cette empreinte à chaque chargement du paquet. Si un seul fichier a changé, le paquet est refusé. Un badge vous indique qui a publié chaque paquet, Officiel ou Communautaire, et sa page renvoie au fichier dont il vient. N'importe quel paquet peut être exporté en .zip, et le .zip d'un ami importé de la même façon. Avant de lancer un paquet, sachez ce à quoi il a accès. Il joue avec votre personnage et peut envoyer tout ce que le client Dofus peut envoyer : se déplacer, combattre, parler, échanger, vendre, détruire des objets. Il lit tout ce que le jeu envoie au bot, et sur un bot MITM, il peut modifier ou bloquer les messages dans les deux sens. Il ne peut pas lire les fichiers de votre ordinateur, accéder à internet, voir votre mot de passe Ankama ni toucher à vos autres bots. Regardez ce qu'il fait avant de le lancer, et gardez sa console ouverte la première fois. Réglages et actions des paquetsUn paquet peut afficher ses propres réglages sur la page du bot, sans que son auteur écrive la moindre interface. Il les déclare dans un seul objet exporté, et Asteroboard dessine le formulaire : interrupteurs, texte, nombres entiers et décimaux avec leur minimum, leur maximum et leur unité, listes déroulantes, listes et dictionnaires. Quand vous cliquez sur Appliquer, Asterobot vérifie chaque valeur par rapport à la déclaration, puis transmet les nouvelles valeurs au script en cours sans le redémarrer. Chaque bot garde ses propres valeurs, d'un redémarrage à l'autre et d'une version du paquet à l'autre. Les actions fonctionnent de la même façon, sous forme de boutons. Chacune exécute une partie du script, avec une boîte de dialogue pour ses arguments et une notification quand elle se termine ou échoue. Une action qui échoue n'arrête jamais le script. Écrire vos propres scriptsTout ce que fait un paquet passe par le SDK JavaScript. Un script joue à Dofus comme le fait le client Dofus : il envoie les messages que le client enverrait, et réagit à ce que le serveur renvoie. Il n'y a pas de fonction « combattre » ou « récolter » intégrée, et c'est tout l'intérêt. Ce sont des paquets, écrits à partir des propres messages du jeu, de quelques lignes qui répondent à une commande de chat jusqu'à des comportements complets construits à partir d'autres paquets. Les scripts sont écrits en JavaScript moderne : modules ES, async/await, classes, et BigInt pour les identifiants 64 bits du jeu. Chaque bot exécute sa propre copie du script dans un moteur intégré avec sa propre boucle d'événements, juste à côté de sa connexion au jeu : deux bots qui jouent le même paquet ne partagent rien. Un script n'a ni fichiers ni réseau, et du code qui tourne plus de 250 ms sans rien attendre arrête le script, pour qu'un script ne puisse pas bloquer le reste d'Asterobot. Les messages arrivent à un script de la même façon sur un bot Full socket et sur un bot MITM : vous écrivez votre logique une seule fois. Le script sait aussi pourquoi il a démarré : une nouvelle connexion de jeu, une session qu'il reprend, ou un rechargement après une modification de son code. Sur une nouvelle connexion Full socket, il identifie lui-même le bot. Sur un bot MITM, le client Dofus l'a déjà fait, et les mêmes lignes sautent cette étape. Celui-ci écrit dans sa console chaque ligne de chat que reçoit le bot, dans les deux modes : import { session, botInfo } from "asterobot:bot"; import { on, send } from "asterobot:protocol"; export default async function behavior(launch) { on("ChatChannelMessageEvent", (traffic) => { // Un message qu'Asterobot n'a pas pu décoder n'a pas de payload, et une // erreur levée dans un gestionnaire arrête tout le script. if (!traffic.payload) return; const { channel, senderName, content } = traffic.payload; botInfo(`[${channel}] ${senderName}: ${content}`); }); // Une nouvelle connexion de jeu commence par ce message. Les bots MITM ne se // lancent jamais avec "initial" : le client Dofus a déjà identifié leur session. if (launch.reason === "initial") { await send("IdentificationRequest", { ticketKey: session.gameToken, languageCode: session.language, }); } }on() écoute, wait() attend le prochain message qui correspond, send() envoie, et request() envoie puis se résout avec la réponse qui porte le même uid. Les intercepteurs vont un cran plus loin. Un intercepteur est consulté sur chaque message avant qu'il ne poursuive sa route, et le laisse passer, le modifie ou le bloque, en moins de 50 ms. S'il échoue ou répond en retard, le message continue sans changement : un script ne peut jamais bloquer une session. Celui-ci, sur un bot MITM, transforme les lignes de chat que vous tapez en commençant par ! en commandes pour votre script, et elles n'atteignent jamais le serveur : intercept("ChatChannelMessageRequest", (traffic) => { const content = traffic.payload?.content; if (typeof content !== "string" || !content.startsWith("!")) return undefined; botInfo("Commande :", content); return "drop"; });Un script lit les données du jeu avec record(), find(), search() et query(), les mêmes tables et les mêmes colonnes que sur la page Jeu. Il sait où se trouvent son personnage et les acteurs autour de lui. findPath() renvoie le chemin que renvoie le propre routeur du client Dofus, règle pour règle, y compris pour les cellules qu'il ne peut pas atteindre, avec la durée du trajet, et move() le parcourt et se résout quand le serveur confirme la fin du déplacement. Voici ce que vous apporte chaque module intégré : Module Ce qu'il apporte à votre script asterobot:protocol Écouter les messages, en attendre un, envoyer, faire une requête et intercepter asterobot:gamedata Enregistrements, noms, textes et requêtes SQL sur les données du jeu asterobot:bot La session, la position du personnage et des acteurs autour de lui, la console du bot asterobot:pathfinding Le chemin que prendrait le client Dofus jusqu'à une cellule, et sa durée asterobot:movement Marcher jusqu'à une cellule et attendre la fin du déplacement, dans les deux modes asterobot:parameters Les réglages que l'on modifie sur la page du bot, en direct asterobot:timers, asterobot:console, asterobot:runtime Attendre, le journal d'Asterobot lui-même, et les informations de votre paquet L'éditeur de paquetsVous écrivez vos paquets dans Asteroboard lui-même. L'éditeur charge ses types depuis votre Asterobot : chaque fonction asterobot: et un type par message du jeu qu'il connaît, si bien que les noms de messages et les champs des payloads se complètent pendant que vous tapez. Votre JavaScript est vérifié au fil de l'écriture, types JSDoc compris, et les erreurs sont comptées dans tous les fichiers du paquet, ouverts ou non. Un paquet peut avoir plusieurs fichiers, avec des imports entre eux, l'accès à la définition et le renommage dans tous les fichiers. Lancer enregistre le paquet, arrête ce que jouait le bot, charge votre code et le démarre. Modifiez une ligne, cliquez, regardez la console. Un panneau Jeu garde la visionneuse de carte et le navigateur des données du jeu à côté du code, pour les identifiants dont votre script a besoin. Quand il est prêt, exportez-le et publiez-le sur le marché : tout membre d'asterobot.net peut le faire. Où tourne AsterobotAsterobot existe en six builds : Windows, macOS et Linux, chacun en x86-64 et en ARM64. Cela couvre aussi bien un portable Snapdragon qu'un Mac avec une puce Apple, un Raspberry Pi 4 ou 5, ou un serveur ARM. C'est du Go pur, sans la moindre ligne de C, compilé pour les six cibles depuis une seule machine, et la version Linux tourne sur n'importe quelle distribution, sans rien à installer à côté. Chaque version est livrée avec un checksums.txt qui contient le SHA-256 de chaque archive, pour que vous puissiez vérifier que le fichier que vous lancez est bien celui qui a été publié. Pour les serveurs, il existe des images pour amd64 et arm64 sur Docker Hub, sous asterobot/asterobot. Elles sont construites à partir de zéro autour du même binaire, tournent sous un utilisateur sans privilèges, et gardent tout dans un seul volume /data, ou trois si vous voulez le cache du jeu sur un autre disque. Contrôle à distancePar défaut, chaque port n'écoute que sur votre propre ordinateur. Sous Linux et macOS, les fichiers qui contiennent vos connexions et vos mots de passe ne sont lisibles que par votre utilisateur, et Asterobot les restreint de nouveau s'il les trouve ouverts. À partir de là, vous ouvrez autant que nécessaire. Pour atteindre un Asterobot sur une autre machine, un tunnel SSH transporte son port : c'est chiffré, rien de nouveau n'est ouvert sur internet, et le mode MITM continue de fonctionner à travers lui. Pour suivre vos bots depuis votre téléphone sur votre réseau domestique, activez TLS et l'authentification basique, et Asterobot crée son propre certificat si vous n'en fournissez pas. Le mode MITM a toutefois besoin que les deux restent désactivés : pendant que vous relayez une partie, c'est le tunnel qu'il vous faut. Asterobot n'a besoin d'aucun bureau : son journal est sur la page Serveur, et il peut afficher sa connexion sous forme de QR code dans le terminal. Proxys SOCKS5Chaque bot peut se connecter depuis sa propre adresse. Enregistrez vos proxys une fois dans le Gestionnaire de proxys et attribuez-en un à chaque bot. Plusieurs bots peuvent partager le même, et un changement s'applique à la connexion suivante sans toucher à une session en cours. En mode Full socket, la connexion au serveur de connexion et celle au serveur de jeu passent toutes les deux par le proxy, et en mode MITM, c'est la connexion de jeu. La connexion HTTPS à Ankama part toujours de votre propre adresse. Tester envoie une vraie requête HTTPS par un proxy et affiche l'adresse que l'autre côté a vue, et Tout tester vérifie tous les proxys d'un coup. Un bot dont le proxy manque refuse de se connecter plutôt que de se connecter depuis votre propre adresse, et un proxy encore utilisé ne peut pas être supprimé. Assistants IA MCP pour les assistants IAAsterobot parle MCP, le protocole qu'utilisent les assistants IA pour piloter des programmes, sur /mcp de son propre port. Connectez Claude Code, Codex, Cursor ou n'importe quel client MCP, et vous pouvez lui demander d'ajouter un bot sur votre deuxième compte et de le connecter, de trouver les tables qui contiennent les monstres et leurs butins, ou d'écrire un script qui salue votre guilde. Avec Claude Code, il suffit d'une commande : claude mcp add --transport http asterobot http://127.0.0.1:8080/mcpL'assistant dispose d'une centaine d'outils, un pour chaque chose que sait faire Asteroboard, générés au démarrage à partir de la même description d'API que celle sur laquelle Asteroboard est construit. Chaque appel passe par les mêmes vérifications : un assistant ne peut jamais faire ce qu'Asteroboard ne pourrait pas faire. Il suit ce qui se passe avec watch-events, qui attend jusqu'à 30 secondes l'événement suivant et lui dit combien il en a manqué, lit le journal d'Asterobot avec read-logs, et demande la forme exacte de n'importe quelle action avec describe-operation. Une réponse de plus de 100 000 octets est raccourcie par la fin de sa plus grande liste, et l'assistant apprend comment affiner sa demande. Quelques réglages par défaut sont là pour vous protéger. Arrêter Asterobot et réinitialiser tous les réglages restent cachés tant que vous ne les autorisez pas, les secrets reviennent sous la forme [redacted], et vous pouvez limiter un assistant aux parties d'Asterobot que vous choisissez. Gardez en tête qu'un assistant agit en votre nom : tout ce que vous pouvez faire depuis Asteroboard, il peut le faire, y compris sur vos comptes Ankama. Asterobot est le premier bot Dofus intégrant nativement le MCP. Il vous permet de contrôler entièrement votre bot Dofus, Asterobot ou Asteroboard, directement depuis une IA. Automatisez vos actions, pilotez votre bot et interagissez avec votre environnement de jeu simplement en utilisant le langage naturel. Et même générer des scripts! Quand Dofus se met à jourAnkama patche Dofus régulièrement, et les messages qu'échange le jeu changent à chaque patch. Asterobot est construit pour suivre. La version de Dofus avec laquelle il fonctionne n'est pas figée dans le programme : quand Ankama patche le jeu, la prise en charge de la nouvelle build peut arriver jusqu'à votre Asterobot depuis asterobot.net, sans nouveau téléchargement. Et vos scripts ne voient pas les noms brouillés changer. Ils continuent d'écrire ChatChannelMessageEvent, quel que soit le nom brouillé du jour. Chaque version d'Asterobot est testée avec une version de Dofus, et chaque version que vous téléchargez est comparée à celle-ci : Compatible ou Version différente, dans le Gestionnaire de jeux, sur la page Jeu et sur chaque bot. Après un patch, téléchargez la nouvelle version à côté de l'ancienne, passez vos bots dessus un par un, et supprimez l'ancienne version quand plus rien ne l'utilise. En mode MITM, gardez vos bots sur la version à laquelle vous jouez, car un bot refuse d'attendre une partie d'une autre version. DocumentationTout ce que décrit cet article est documenté en détail. Le guide va du téléchargement d'Asterobot jusqu'à son installation sur un serveur, avec chaque réglage et chaque erreur que vous pouvez rencontrer, en français et en anglais. La documentation développeurs, en anglais pour l'instant, vous accompagne dans votre premier script pas à pas, avec des tutoriels et la référence de chaque fonction. La FAQ donne des réponses courtes sur les comptes, les bots, les paquets et le réseau, chacune avec un lien vers l'explication complète. Et la référence du protocole Dofus liste chaque message de Dofus 3 qu'Asterobot connaît, avec ses champs. D'abord stable, ensuite plus grandPour l'instant, l'objectif est simple : s'assurer que tout est stable, sur autant d'ordinateurs, de systèmes et de configurations que possible. Utilisez Asterobot, poussez-le dans ses retranchements, essayez de le casser, et dites-moi ce qui n'a pas marché sur le forum ou sur Discord, avec votre version d'Asterobot, la façon dont votre bot se connecte et le texte exact de l'erreur. Chaque rapport aide. Une fois que tout tient, davantage de messages Dofus recevront un nom lisible, pour que les scripts et l'outil Réseau comprennent une plus grande partie du jeu. Côté paquets, les scripts de connexion prêts à l'emploi arrivent en premier, pour qu'un bot Full socket entre en jeu sans une ligne de code, suivis d'autres paquets sur lesquels construire. De nouvelles fonctionnalités arriveront dans Asterobot comme dans Asteroboard, et vos retours les façonneront : dites-moi sur le forum ce que vous aimeriez voir. Chaque changement est listé dans les journaux des modifications d'Asterobot et d'Asteroboard. Votre premier bot !Quatre étapes, et aucun mot de passe Ankama à donner. Téléchargez Asterobot: Récupérez l'archive pour votre système et décompressez-la où vous voulez. Lancez-le et connectez-vous: Asteroboard s'ouvre dans votre navigateur. Confirmez le code de connexion avec votre compte asterobot.net. Ajoutez un bot MITM: Téléchargez la version de Dofus à laquelle vous jouez dans le Gestionnaire de jeux, puis ajoutez un bot qui attend votre client de jeu. Jouez: Lancez Dofus comme d'habitude. Votre chat, votre inventaire et les messages de votre partie s'affichent dans Asteroboard. Télécharger Asterobot Lire le guide Forum Discord État des services Merci de m'avoir lu jusqu'ici, et merci de donner sa chance à Asterobot. J'ai hâte de voir les bots que vous ferez tourner et les paquets que vous partagerez. On se retrouve sur le forum et sur Discord. Smooth
  4. Smooth a posté un fichier dans Librairie
    • 125 téléchargements
    • Version 0.2.0
    @asterobot:baseRead in English · Changelog @asterobot:base est la bibliothèque standard dont se sert chaque paquet. D’abord, il fait tout ce que le client Dofus officiel fait de lui-même, pour qu’aucun autre paquet n’ait jamais à le faire. Il donne à un bot un personnage connecté, dans le monde, prêt à tout sur sa carte, et l’y maintient : il identifie la connexion de jeu avec le ticket qu’Asterobot a obtenu à la connexion ; il choisit un personnage et attend qu’il soit chargé ; il entre dans le monde et suit le personnage de carte en carte ; tant que le script tourne, il garde la connexion en vie comme le fait le client. Il répond à la preuve d’identité du serveur et à ses relances, et il envoie des pings. Ensuite, il donne à chaque paquet ce qui vient après : se déplacer : marcher, changer de carte, les portes et les zaaps, les voyages vers n’importe quelle carte, et les fonctions qui choisissent où aller ; les plans : il joue les plans dessinés dans le Studio d’Asteroboard, en une ligne, await run(plan) (voir Plans) ; l’état du personnage : niveau, kamas, vie, sacs, combats, objets, métiers et carte, sous forme de conditions qu’un plan ou un script pose (voir Conditions sur l’état du jeu) ; le gameplay : récolter, combattre avec le module de combat d’Asterobot, se reposer et manger, l’équipement, vendre et acheter à l’hôtel des ventes du jeu, et la banque, sous forme d’étapes qu’un plan joue ou qu’un script appelle (voir Récolter, Combattre, Se reposer et manger, L’équipement, L’hôtel des ventes et La banque) ; des profils de combat : 24 façons de combattre toutes prêtes, pour n’importe quelle classe, que chaque bot peut jouer (voir Les profils de combat). D’autre gameplay arrivera dans base avec de nouvelles captures, pour qu’un plan nomme @asterobot:base pour tout cela. Il est pensé pour être la seule dépendance de chaque paquet. Un paquet importe base et oublie la connexion : base démarre tout seul, sur les deux sortes de bots, qu’il soit joué seul ou tiré par d’autres paquets. C’est du JavaScript simple au-dessus des modules intégrés d’Asterobot, et il ne fait lui-même aucune cryptographie : la preuve d’identité est calculée dans Asterobot, en Go (voir La preuve d’identité). Tout ce qu’il envoie, et quand, vient de captures « homme du milieu » du vrai client, et base s’améliore à chaque nouvelle capture (voir Améliorer base à partir d’une nouvelle capture). SommaireDémarrage rapide Comment base fonctionne dans tous les cas Ce qui se passe, étape par étape API Réglages Plans Le format des plans Conditions sur l’état du jeu Récolter Combattre Se reposer et manger L’équipement L’hôtel des ventes La banque Les profils de combat La preuve d’identité Garder la connexion en vie Entrer dans le monde Suivre les cartes Mémoire, et reprise en cours de route Réagir à ce que base répond Sur un bot MITM Tous les messages que base envoie Tous les messages que base lit Lignes de console et erreurs Délais et bruit Ce qu’il faut à base dans Asterobot Différences connues avec le vrai client Améliorer base à partir d’une nouvelle capture Fichiers Démarrage rapideL’installerDepuis ce dépôt, dans la bibliothèque d’Asterobot : asterobot library install --path ./base --name @asterobot:base --version 0.0.1 Réinstallez-le après chaque modification, sous la même version tant que vous travaillez dessus : chaque démarrage lit les fichiers installés à ce moment-là. Le jouer seulChoisissez @asterobot:base comme paquet d’un bot et cliquez sur Lancer. La Console du bot affiche alors base: playing <name> une fois le personnage sélectionné, et base: in the world, on map <id> une fois qu’il se trouve sur sa carte. Le bot reste dans le monde jusqu’à ce que vous l’arrêtiez. L’utiliser depuis votre propre paquetDéclarez-le dans le asterobot.json de votre paquet : { "dependencies": { "@asterobot:base": { "version": "0.0.1" } } } L’importer suffit. Attendez le monde là où votre code en a besoin : import { connect } from "@asterobot:base"; import { botInfo } from "asterobot:bot"; export default async function behavior() { // Pas attendu ici, pour que Lancer rende la main tout de suite. void main(); } async function main() { const map = await connect(); botInfo("On map", map.mapId, "cell", map.cellId); // ... votre script } connect() se résout avec la carte sur laquelle se trouve le personnage, tout de suite s’il y est déjà, et se rejette si le personnage ne peut pas y arriver (voir Lignes de console et erreurs). Elle renvoie la même promesse à chaque appel pendant une exécution du script, donc chaque paquet du bot peut l’appeler. Comment base fonctionne dans tous les casUn paquet qui utilise base doit se comporter de la même façon sur un bot Full socket, où Asterobot est le client, et sur un bot MITM, où une personne joue avec le client officiel et où Asterobot relaie la session. Il doit aussi fonctionner que base soit le paquet joué par le bot, ou une dépendance plusieurs niveaux plus bas. Base gère lui-même ces quatre cas. Les quatre cas Bot Full socket Bot MITM Base joué seul S’identifie, sélectionne, entre dans le monde, puis répond à tout ce que le serveur demande N’envoie rien. Attend que le personnage de la personne soit sur une carte. Base en dépendance, à n’importe quelle profondeur Pareil, dès que le script charge base Pareil Base décide lui-même de la colonne qui s’applique, d’après session.shared : Asterobot le définit sur un bot MITM. Il n’y a rien à configurer, et un paquet qui utilise base est le même code dans les quatre cas. Sur un bot Full socket, personne d’autre n’est là pour répondre au serveur, donc base fait tout ce que ferait le client officiel. Sur un bot MITM, le client officiel répond déjà. Une seconde réponse de base mettrait deux réponses sur le réseau là où le serveur en attend une, donc base n’envoie absolument rien. Il suit quand même la session, si bien que connect() se résout dès que le personnage de la personne se trouve sur une carte. Il démarre tout seulLe index.js de base appelle connect() à son niveau le plus haut, pendant le chargement du module. Assez tôt. Les modules se chargent avant le code qui les importe, donc les gestionnaires de base sont en place avant que le code de tout autre paquet ne tourne, et avant que le serveur ne puisse parler. La première vérification du serveur arrive juste après qu’il a accepté le ticket. À n’importe quelle profondeur. Dans une chaîne comme my-bot → @alice:harvest → base, l’auteur de my-bot ne sait peut-être même pas que base est là, et il tourne quand même. Un paquet charge base quand l’un de ses fichiers l’importe ; le lister dans dependencies sans l’importer ne charge rien. Jamais pendant la lecture des réglages. Asterobot exécute aussi le niveau le plus haut de chaque module quand il lit les réglages d’un paquet, sans bot ni session de jeu. Base reconnaît ce moment, où session.connectionId vaut 0n (ce qui n’arrive jamais pendant une vraie exécution), et ne démarre pas. C’est le seul endroit où base fait quelque chose que le guide du développeur déconseille, un effet de bord au niveau le plus haut d’un module. C’est voulu : c’est ce qui permet à chaque paquet d’oublier base. Un seul point d’entréeBase exporte connect() pour attendre le monde, ainsi que latency() et toutes les fonctions d’asterobot:movement : move(), changeMap(), useElement(), les voyages (travel(), route(), stopTravel(), trip()) et les fonctions qui trouvent les cartes. Il exporte aussi ce qui joue les plans, run(), stop() et plan(), les conditions sur l’état du jeu, et les étapes de gameplay avec leurs conditions. Les étapes de la connexion elles-mêmes (l’identification, la sélection, l’entrée dans le monde et les réponses au serveur) sont internes. Base les exécute déjà, et un second point d’entrée vers les mêmes étapes ferait la course avec sa propre exécution : une seconde identification considérerait le ticket comme accepté avant que le serveur ait répondu. Écrire un paquet au-dessus de baseImportez base, et faites await connect() avant d’agir. Elle se résout tout de suite si le personnage est déjà sur sa carte. Ne refaites pas ce que fait base. Les pings, les relances, la preuve d’identité et l’échange de carte ont déjà leur réponse. Les envoyer à nouveau ferait répondre le bot deux fois. Vérifiez session.shared avant d’agir. Sur un bot MITM, la personne qui joue contrôle le même personnage. Un paquet qui marche ou combat devrait y rester passif, ou demander d’abord. Déplacez-vous avec ce que base réexporte. move(), changeMap(), useElement(), travel() et les autres sont celles d’asterobot:movement : le bot fait un déplacement à la fois, et après un changement de carte, base charge la nouvelle carte comme il le fait pour chaque carte. Fixez une seule version de base. Deux versions chargées côte à côte répondraient toutes les deux au serveur. Ce qui se passe, étape par étapeLes attentes sont celles que le vrai client a prises dans la capture, à partir de 100 ms. Base ajoute un peu de bruit autour de chacune, et envoie tout le reste directement, dans l’ordre du client (voir Délais et bruit). Quand Base Serveur Tout de suite IdentificationRequest { ticketKey, languageCode } AuthenticationTicketAcceptedEvent, puis une rafale : TimeEvent, ServerSettingsEvent, informations de compte et de confiance, ServerSessionReadyEvent Tout de suite CharacterListRequest Tout de suite ClientChallengeInitRequest { challengeKey }, avec un uid QueueStatusEvent, tant que dure la file d’attente du serveur, puis CharacterListEvent 1,5 à 3,5 s plus tard, comme une personne qui clique CharacterSelectionRequest { characterId } CharacterSelectionEvent, puis CharacterLoadingCompleteEvent. C’est t0 ci-dessous. Tout de suite, à chaque fois SequenceNumberRequest { number } SequenceNumberEvent t0 + 1085 ms cinq réglages sociaux, tous vides, puis les maisons de guilde, les informations de guilde, la réinitialisation hebdomadaire de guilde t0 + 1349 ms ContextCreationRequest, puis le jeton de boutique, l’avertissement de connexion de guilde désactivé, SetCommunityChannelRequest { community } +231 ms l’étape de chaque quête en cours, les prix moyens (une fois par jour), puis les amis, les connaissances, le conjoint, les maisons de guilde MapCurrentEvent { mapId } 1,2 à 2,4 s plus tard, sur la première carte AnomalySubareaInformationRequest, ContextReadyRequest { mapId }, MapInformationRequest { mapId } MapComplementaryInformationEvent : connect() se résout Ensuite, tant que le script tourne : Tous les Base Serveur 10 s environ en moyenne PingRequest { quiet: true } PongEvent 4 minutes SequenceNumberRequest { number }, puis BasicLatencyStatsRequest { latency } avec un uid, les deux tout de suite SequenceNumberEvent et BasicLatencyStatsEvent, ensemble De temps en temps, à plusieurs minutes d’intervalle ClientIdRequest { id }, puis ClientChallengeProofRequest { proof }, chacun tout de suite, les deux avec un uid ServerVerificationEvent, puis ServerChallengeEvent { value }, la valeur étant parfois absente Nouvelle carte ContextReadyRequest + MapInformationRequest, 0,4 à 1,2 s plus tard MapCurrentEvent ou MapCurrentInstanceEvent Une heure après chaque clé BakApiTokenRequest BakApiKeyEvent APIimport { connect, latency, move, changeMap, useElement, travel, route, stopTravel, trip, findMaps, mapAt, mapInfo, nearest, nearestWith, mapsWithin, reachable } from "@asterobot:base"; import { run, stop, plan, conditions, levelAtLeast, kamasAtLeast, lifeBelow, bagsAbove, inFight, hasItem, jobLevelAtLeast, onMap, inSubarea, inArea } from "@asterobot:base"; import { steps, gather, harvestsAtLeast, fightMonsters, fightSettings, diedInLastFight, fightsAtLeast } from "@asterobot:base"; import { rest, eat, equip, unequip, wears, sell, buy, deposit, withdraw } from "@asterobot:base"; Export Renvoie Ce qu’il fait connect() Promesse de la carte Attend que le personnage soit sur sa carte. Base l’exécute déjà pendant son chargement. latency() nombre La latence que le bot déclare au serveur, en millisecondes. move() Promesse de la cellule d’arrivée Fait marcher le personnage vers une cellule de sa carte : celle d’asterobot:movement, réexportée. Sa référence est dans le guide du développeur d’Asterobot. changeMap() Promesse de la carte et de la cellule Emmène le personnage sur la carte suivante par un côté : celle d’asterobot:movement, réexportée. Base demande le contenu de la nouvelle carte, comme pour chaque carte. useElement() Promesse de ce qu’a fait l’utilisation Utilise une porte, un zaap, une ressource ou une sortie de la carte : celle d’asterobot:movement, réexportée. travel() Promesse de la carte et de la cellule Emmène le personnage sur n’importe quelle carte, aussi loin soit-elle, carte après carte : celle d’asterobot:movement, réexportée. Base demande le contenu de chaque nouvelle carte, comme pour chaque carte. route(), stopTravel(), trip() Le chemin que prendrait travel(), l’arrêt d’un voyage, et le voyage où il en est : celles d’asterobot:movement, réexportées. findMaps(), mapAt(), mapInfo(), nearest(), nearestWith(), mapsWithin(), reachable() Les fonctions qui trouvent les cartes où voyager : celles d’asterobot:movement, réexportées. run(), stop(), plan() Jouer un plan dessiné dans le Studio d’Asteroboard, l’arrêter, et ce qu’appelle un fichier de plan pendant son import : voir Plans. levelAtLeast(), kamasAtLeast(), lifeBelow(), bagsAbove(), inFight(), hasItem(), jobLevelAtLeast(), onMap(), inSubarea(), inArea() booléen, ou promesse d’un booléen L’état du personnage, sous forme de questions oui/non. conditions les déclare pour le Studio : voir Conditions sur l’état du jeu. gather(), harvestsAtLeast() Une promesse, un booléen Récolter les ressources de la carte, et combien de récoltes ont été faites. steps et conditions les déclarent pour le Studio : voir Récolter. fightMonsters(), fightSettings(), diedInLastFight(), fightsAtLeast() Une promesse, rien, des booléens Combattre : attaquer les groupes de monstres de la carte, régler le module de combat, et ce qu’ont donné les combats. steps et conditions les déclarent pour le Studio : voir Combattre. rest(), eat() Une promesse Récupérer de la vie, en s’asseyant et en mangeant ce que contiennent les sacs. steps les déclare pour le Studio : voir Se reposer et manger. equip(), unequip(), wears() Une promesse, une promesse, un booléen Mettre des objets, les retirer, et savoir si le personnage en porte un. steps et conditions les déclarent pour le Studio : voir L’équipement. sell(), buy() Une promesse Vendre ce que contiennent les sacs et acheter à l’hôtel des ventes du jeu. steps les déclare pour le Studio : voir L’hôtel des ventes. deposit(), withdraw() Une promesse Mettre en banque ce que contiennent les sacs, et en retirer des objets et des kamas. steps les déclare pour le Studio : voir La banque. steps, conditions des objets Les déclarations de toutes les étapes et conditions ci-dessus, que le Studio propose dans chaque paquet qui dépend de base. Tout le reste de la connexion se fait tout seul, donc base n’en exporte rien de plus. Un second point d’entrée vers les mêmes étapes ferait la course avec sa propre exécution. connect()const map = await connect(); Le premier appel, que base fait pendant son chargement, fait ceci dans l’ordre : Il enregistre les étapes clés de la session dans la mémoire du bot (voir Mémoire). Sur un bot Full socket, il commence à répondre au serveur : la preuve d’identité, les deux relances et les pings, et chaque carte (voir La preuve d’identité, Garder la connexion en vie et Suivre les cartes). Il identifie la connexion, sélectionne un personnage et entre dans le monde (voir les étapes). Elle se résout avec ce que renvoie currentMap() d’asterobot:bot une fois le personnage placé : { mapId, characterId, cellId, actors, elements }, elements étant les éléments interactifs de la carte et ce que chacun propose à cet instant. La même promesse à chaque fois pendant une exécution du script : chaque appel suivant l’attend simplement. Un échec est journalisé. La raison va dans la Console du bot sous forme de ligne d’erreur, et la promesse se rejette avec elle. S’arrêter n’est pas un échec. Une promesse rejetée parce que le script s’arrête ou que la connexion s’est fermée n’écrit rien dans la Console. Les étapesL’identification. Base envoie IdentificationRequest avec session.gameToken et session.language, puis attend jusqu’à 30 s la réponse du serveur. Il n’envoie jamais un second IdentificationRequest sur une même connexion. Un ticket refusé arrête tout : le ticket est consommé, et seule une nouvelle connexion au compte en apporte un autre. La sélection d’un personnage. Base joue le personnage que nomme le réglage character, ou le premier de la liste. Quand un combat attend l’un des personnages du compte, après une déconnexion en plein combat, le serveur choisit lui-même ce personnage, juste après la liste (CharacterForceSelectionEvent). Base répond alors aussitôt, comme le client (CharacterForceSelectionReadyRequest), joue ce personnage, et prévient dans la Console du bot quand le réglage character en nomme un autre. L’attente de la liste n’a pas de limite : elle arrive après la file d’attente du serveur, quelle que soit sa longueur. La réponse à la sélection est attendue jusqu’à 30 s, et le chargement jusqu’à 60 s. Elle échoue quand : le compte n’a aucun personnage sur le serveur ; aucun personnage ne porte le nom donné par le réglage (le message liste ceux qui existent) ; le serveur refuse la sélection. L’entrée dans le monde. Base envoie ce que le client envoie après le chargement (voir Entrer dans le monde), puis attend jusqu’à 60 s que le personnage soit placé sur sa carte. Les réponses au serveur tournent dès le départ, et tant que le script tourne. latency()La moyenne des 50 derniers allers-retours de ping du bot, en millisecondes, ou 0 avant le premier : arrondie au pair le plus proche, comme le fait le Math.Round du client ; puis ramenée à un entier signé sur 16 bits, comme le fait la conversion du client. Les allers-retours sont gardés dans la mémoire du bot, donc ils survivent à un redémarrage du script et à une reconnexion, comme dans un client qui reste ouvert. RéglagesBase a sa propre carte dans l’onglet Réglages du bot, quel que soit le paquet qui l’utilise. Réglage Valeurs Par défaut Ce qu’il change character Le nom d’un personnage Vide Le personnage à jouer, quelles que soient ses majuscules. Vide joue le premier de la liste, comme le fait la sélection automatique du client. community server, french, english, spanish, portuguese, german server La communauté de discussion que rejoint SetCommunityChannelRequest : 0 à 4 dans cet ordre. server envoie la community de ServerSettingsEvent, ou 0 s’il n’en a pas. ping active, idle active active envoie des pings comme une personne qui utilise le client, ce qui donne au serveur une vraie latence. idle n’en envoie jamais, comme un client que personne ne touche. Il continue de déclarer la dernière latence mesurée, ou 0 s’il n’a jamais envoyé de ping. bringUp faithful, minimal faithful faithful envoie tout ce que le client envoie après le chargement. minimal n’envoie que ContextCreationRequest, le seul message nécessaire pour atteindre la carte, et aucun jeton de boutique horaire. Les réglages sont lus au moment où ils servent, donc un changement s’applique à partir de la prochaine exécution de l’étape. Un changement de ping s’applique dès le battement suivant. PlansUn plan est un fichier d’un paquet, dessiné dans le Studio d’Asteroboard : des lieux sur la carte du monde, l’ordre dans lequel le bot en fait le tour, et ce qu’il fait à chacun, avec les fonctions d’un paquet ou la logique propre au Studio (attendre, attendre que, seulement si, réessayer…). Base le joue. Le format des plans décrit ce que contient le fichier. Jouer un planimport { run } from "@asterobot:base"; import main from "./plans/main.js"; // Les réglages du plan, dans l'onglet Réglages du bot. export { parameters } from "./plans/main.js"; export default async function behavior() { // Pas attendu ici, pour que Lancer rende la main tout de suite. void run(main); } C’est l’index.js qu’écrit le Studio quand il crée un paquet. Un script écrit à la main peut appeler run() n’importe où, autant de fois qu’il veut, un plan à la fois. Export Renvoie Ce qu’il fait run(plan, options?) Promesse Joue un plan : l’export par défaut d’un fichier de plan. Se résout quand le circuit se termine ou est arrêté, se rejette quand le plan échoue (voir Échecs). options est réservé : rien ne le lit encore. stop() booléen Demande au plan en cours de s’arrêter proprement. Dit s’il y en avait un. plan(data, refs) Le plan Appelée par un fichier de plan pendant son import, jamais à la main. Elle garde le plan et ce à quoi il fait référence, et ne lève jamais d’erreur : elle s’exécute pendant qu’Asterobot lit les déclarations du paquet sans bot, et une erreur à ce moment empêcherait le Studio de lire sa propre palette. run(plan) : Attend le monde, avec connect(). Vérifie tout le plan, et chaque plan qu’il joue par ses interruptions et ses étapes plan : le format, chaque sorte et chaque champ, chaque lieu choisi et chaque requête qui restreint la recherche (voir Les lieux), chaque paquet et chaque fonction qu’un appel nomme, chaque argument selon les règles des réglages, chaque argument omis qui n’a rien avec quoi jouer (sa valeur par défaut, sinon le zéro de son type, enfreint ses propres règles, ou il a un ref sans valeur par défaut), le nom et la valeur par défaut de chaque réglage, chaque $setting et la valeur par défaut du réglage qu’il nomme, chaque plan auquel il fait référence, au moins un arrêt. Au moindre problème, elle se rejette tout de suite en listant tous les problèmes, chacun avec le libellé et l’identifiant de son bloc, avant que le bot ne fasse un seul pas. Parcourt le circuit, tour après tour, jusqu’à sa fin. Un plan à la fois. run() pendant qu’un autre plan est joué se rejette tout de suite avec base: a plan is already running. Les plans qu’un plan joue lui-même, pour une interruption ou une étape plan, ne comptent pas. S’arrêter proprement. Un arrêt vient de stop(), d’une étape stop, ou d’Asterobot qui demande au script de s’arrêter, par exemple à la fin d’une plage du planning. L’étape en cours se termine alors (une étape longue voit context.stopping passer à vrai et peut finir plus tôt), un wait ou un waitUntil se termine tout de suite, un voyage s’arrête à la fin de son tronçon, et run() se résout, même s’il attendait encore le monde. Asterobot attend cela avant d’arrêter le script, dans le temps que le planning laisse pour finir. Un échec rejette run(). Rien n’attrape un void run(main), donc un plan qui échoue arrête le script, et la page du bot dit pourquoi. Attrapez-le pour faire autre chose. Un tourL’arrêt suivant : le suivant dans l’ordre où ils ont été dessinés (drawn), ou le plus proche qui a encore du travail (nearest). Si son when est faux, il est sauté pour ce tour. Son lieu donne ses cartes, visitées de la plus proche à la plus lointaine depuis l’endroit où se trouve le bot. Une carte qu’aucun voyage n’atteint est sautée, avec une ligne de console ; l’arrêt échoue quand aucune ne peut être atteinte. Sur chaque carte : travel() jusqu’à elle, sauf si le bot y est déjà, puis les interruptions, qui sont aussi la vérification avant la première étape ; puis chaque étape à son tour : son when, l’étape, son onFailure si elle échoue, et les interruptions de nouveau avant l’étape suivante. Quand une étape a emmené le bot ailleurs, comme une étape plan, il revient sur la carte avant l’étape suivante ; après la dernière, il continue directement. Après chaque arrêt, repeat.until, s’il y en a un. Le dernier arrêt d’un tour compte d’abord le tour, donc « jusqu’à 3 tours » se termine juste à la fin du troisième. À la fin d’un tour, once termine le circuit, et forever et until commencent le suivant. Un tour dure au moins une seconde : un circuit sans rien à rejoindre et sans rien qui attende, un seul arrêt sur la carte du bot, tournerait sinon sans jamais laisser le bot faire autre chose. Les cartes en attente. Une étape qui se résout avec { nothingToDo: true, retryAfter } laisse la carte de cet arrêt de côté pendant retryAfter secondes, ou jusqu’au tour suivant sans valeur. Les deux ordres sautent une carte en attente. nearest choisit la carte la plus proche d’un arrêt qui n’est pas en attente, et un tour se termine une fois que chaque arrêt a été joué ou est en attente. Quand toutes les cartes restantes sont en attente, le runner attend que la première ait de nouveau du travail plutôt que de tourner à vide. Les interruptions sont vérifiées dans l’ordre à l’arrivée sur chaque carte et avant chaque étape, jamais pendant une étape ou un voyage. C’est la vérification à l’arrivée qui permet d’interrompre aussi un circuit fait uniquement de points de passage. La première dont la condition est vraie joue son plan, imbriqué. Pendant que ce plan tourne, la même interruption n’est plus vérifiée, même depuis l’intérieur de ce plan, donc « sacs pleins : banque » ne peut pas relancer le trajet vers la banque depuis ce même trajet ; les interruptions propres au plan imbriqué s’appliquent bien. Ensuite : resume revient sur la carte où était le circuit, et reprend avec l’étape qu’il allait jouer ; stop termine le plan auquel appartient l’interruption. Avec resume, le plan de l’interruption doit rendre sa condition fausse : le trajet de banque vide les sacs. Quand la condition est encore vraie juste après le plan, le plan s’arrête avec une erreur qui nomme l’interruption, plutôt que de le rejouer environ une fois par seconde tant que le plan tourne. Un chance dans la condition ne compte pas contre elle, puisque la vérification suivante tire à nouveau. Les réglages d’un planUn plan peut déclarer des réglages, exactement sous la forme des réglages d’un paquet, et n’importe quel argument d’une fonction qu’il appelle peut prendre la valeur d’un réglage au lieu d’une valeur fixe : { "$setting": "monsters" }. Le réglage doit exister et avoir le type de l’argument. Le nom d’un réglage est un nom qu’un script pourrait écrire : des lettres, des chiffres et _, sans chiffre au début. Jamais __proto__, que l’objet littéral du fichier du plan lirait autrement, si bien que le réglage disparaîtrait. Un réglage déclare une valeur par défaut quand le zéro de son type enfreint ses propres règles (un int d’au moins 1 commencerait à 0), ou quand c’est une valeur seule avec un ref (0 serait un identifiant que personne n’a choisi) : la page du bot le fait commencer à cette valeur. Un argument lié convient à la valeur par défaut de son réglage. Tant que personne n’a réglé une autre valeur sur la page du bot, l’argument prend la valeur par défaut du réglage, sinon le zéro de son type, donc cette valeur doit passer les règles de l’argument lui-même : un réglage qui vaut 150 par défaut ne peut pas tenir lieu de pourcentage. Ils deviennent des réglages du paquet, dans l’onglet Réglages du bot, dès que l’entrée du paquet les réexporte. Le Studio propose d’écrire la ligne : export { parameters } from "./plans/main.js"; // un seul plan, rien d'autre à déclarer // ou, fusionnés avec ceux de l'entrée : import { parameters as mainSettings } from "./plans/main.js"; export const parameters = { ...mainSettings, myOwnSetting: { type: "int", default: 3 } }; Lus en direct. Le runner lit un réglage chaque fois qu’un argument s’en sert, donc une valeur changée sur la page du bot s’applique dès l’étape suivante. Sans réexport, le plan utilise la valeur par défaut de chaque réglage, et la Console le dit en une ligne avant que le bot ne bouge, en les nommant tous. Plusieurs plans d’un même paquet partagent les valeurs du bot : deux plans qui déclarent le même nom utilisent la même valeur, et doivent le déclarer avec le même type. Une valeur qui enfreint les règles de l’argument lui-même, comme 150 pour un pourcentage, fait échouer le bloc qui s’en sert. ÉchecsUne seule règle pour tout : un voyage, une étape ou une condition qui échoue arrête le plan, sauf si le plan dit autre chose. Les étapes et les arrêts ont chacun un onFailure : onFailure Alors absent, ou { "kind": "stop" } Le plan s’arrête avec l’erreur { "kind": "skip" } Une ligne de console, puis l’étape suivante (pour une étape) ou l’arrêt suivant (pour un arrêt) { "kind": "retry", "times": n, "wait": s, "then": "stop" \| "skip" } Réessayé jusqu’à n fois (1 à 10), à s secondes d’intervalle (5 par défaut). S’il échoue encore, then (stop par défaut). Un arrêt est réessayé en entier : sa condition, son lieu et toutes ses cartes. Ce qui fait échouer quoi : Ce qui arrive Ce qui échoue L’étape d’un paquet lève une erreur ou se rejette, un waitUntil dépasse son délai, le plan d’une étape plan échoue L’étape Le when d’une étape, ou la condition d’un waitUntil, échoue L’étape Le lieu d’un arrêt est introuvable, ou aucune de ses cartes n’est atteignable, un voyage vers l’une de ses cartes échoue, ou son when échoue L’arrêt. Une étape qui échoue ne regarde qu’elle-même, jamais son arrêt. La condition d’une interruption échoue, ou le plan qu’elle joue, ou avec resume sa condition est encore vraie après ce plan, ou repeat.until échoue Le plan, qui s’arrête Une condition échoue quand elle lève une erreur ou se rejette, comme le fait une condition sur l’état du jeu à propos d’un fait que le jeu n’a pas encore dit, ou quand elle répond autre chose que true ou false. Un when simplement faux saute son étape ou son arrêt, ce qui n’est pas un échec. Quand le plan s’arrête, le runner écrit l’erreur dans la Console, et run() se rejette avec elle. L’erreur nomme le bloc, par son libellé et son identifiant, puis la cause : base: Astrub farm › 2 Astrub fields (k3f9) › Fight (p2x1) failed 3 times: no monster to fight base: Astrub farm › 3 map 191105026 (m8q2) failed: travel: no known way leads to map 191105026 base: Astrub farm › interrupt Bank when full (i7w0) failed: condition Bags above (c0b2): the bags' weight isn't known yet base: Astrub farm › interrupt Bank when full (i7w0) is still true after its plan plans/bank.js ran: that plan must make it false, or it would run again and again Les lignes de console d’un planElles restent en anglais, telles que base les écrit : Niveau Ligne Quand info base: <plan> › <n> <stop> Un arrêt arrive : son numéro sur le circuit, puis son libellé, ou son lieu s’il n’en a pas info base: <plan> › <n> <stop> › <step> Une étape commence : une fonction par son libellé, une étape logique par ce qu’elle fait (wait 5–15 s, wait until, play plans/bank.js, stop) info base: <plan> › <n> <stop> › <message> Une étape log info base: <plan> › interrupt <label>: playing <path> La condition d’une interruption est vraie warn base: <block> failed, trying again in <s> s (<i> of <n>): <cause> Une étape ou un arrêt est réessayé warn base: <block> failed, skipped: <cause> Une étape ou un arrêt est sauté, avec <n> times après failed quand il a d’abord été réessayé warn base: <plan> › <n> <stop> (<id>): no trip reaches map <id>, skipped Les cartes d’un lieu qu’aucun voyage n’atteint, toutes sur une ligne warn base: <plan>: settings <name>, <name> aren't among the package's settings, so their defaults are used: re-export the plan's settings from the package's entry Une fois, avant de bouger, en nommant chaque réglage que l’entrée ne réexporte pas, de tous les plans que l’exécution peut jouer (setting <name> isn't one of the package's settings, so its default is used pour un seul) error base: <block> failed: <cause> Le plan s’arrête error base: <plan> can't be run: puis - <problème>, une ligne chacun La vérification a trouvé des problèmes <plan> est le libellé du plan, sinon son chemin dans le paquet, sinon plan quand son fichier ne donne pas son chemin. <block> est l’endroit où se trouve le bloc, avec les identifiants : <plan> › <n> <stop> (<id>) › <step> (<id>). Où en est le runnerPendant qu’un plan est joué, le runner garde l’endroit où il en est dans la mémoire du bot, sous @asterobot:base/plan : { plan: "plans/bank.js", stop: "k3f9", step: "p2x1", map: 191105026, lap: 2, connectionId: 7n } plan est le chemin du plan joué dans son paquet, null quand son fichier ne le donne pas. stop et step sont des identifiants de blocs ; step vaut null à l’arrivée sur une carte, avant sa première étape. Elle est écrite à l’arrivée sur chaque carte, au début de chaque étape, et quand un plan joué depuis là (celui d’une interruption, d’une étape plan) se termine, et retirée une fois que run() s’est terminée. Le Studio en direct d’Asteroboard la lira pour montrer où est le bot. Rien ne la lit encore. Le format des plansLe format 1 : ce que le Studio écrit, et ce que le runner lit. Le Studio écrit tout le fichier. Il est documenté ici pour qu’un runner et un Studio s’accordent dessus, et pour qu’un plan puisse se lire sans le Studio. Le fichierUn plan est un module .js, où qu’il soit dans le paquet. C’est sa première ligne qui en fait un plan : // asterobot-studio-plan v1 // Made in Asteroboard's Studio. Edit it there: changes made by hand are lost the next time the Studio saves it. import * as runner from "@asterobot:base"; import { values } from "asterobot:parameters"; import * as p0 from "@asterobot:farm"; import * as q0 from "./bank.js"; const data = /* studio:plan */ { "format": 1, "runner": "@asterobot:base", … } /* studio:end */; export const parameters = data.settings ?? {}; export default runner.plan(data, { path: "plans/astrub.js", packages: { "@asterobot:base": runner, "@asterobot:farm": p0 }, plans: { "plans/bank.js": () => q0.default }, settings: values, }); Le JSON entre /* studio:plan */ et /* studio:end */ est le plan. Tout le reste est généré à partir de lui. path : le chemin du fichier lui-même dans le paquet, qui nomme le plan dans les lignes de console quand il n’a pas de libellé, et dans où en est le runner. Un fichier sans lui reste un plan, dont le chemin est inconnu. packages : un import d’espace de noms par paquet qu’un call nomme, celui du runner compris. plans : un import par autre plan que le plan joue. Ils sont lus au dernier moment, () => q0.default, pour que deux plans qui se jouent l’un l’autre (ferme → banque → ferme) puissent s’importer mutuellement. Dans le JSON, les chemins sont relatifs au paquet (plans/bank.js) ; l’import est relatif au fichier (./bank.js). settings : values d’asterobot:parameters, qui dans ce fichier sont les réglages du paquet lui-même, pour que le runner lise les réglages du plan dans le bon paquet. parameters : les réglages du plan, déclarés comme ceux d’un paquet, pour que l’entrée les réexporte (voir Les réglages d’un plan). Le plan{ "format": 1, "runner": "@asterobot:base", "label": "Astrub farm", "settings": { "monsters": { "type": "array", "of": "int", "ref": "monsters", "label": "Monsters to fight", "default": [31, 34] } }, "circuit": { "order": "drawn", "repeat": { "kind": "forever" }, "stops": [ { "id": "k3f9", "label": "Astrub fields", "place": { "kind": "subarea", "id": 95 }, "steps": [ { "id": "p2x1", "kind": "call", "package": "@asterobot:farm", "step": "fight", "args": { "monsters": { "$setting": "monsters" } }, "when": { "id": "t5h2", "kind": "time", "from": "08:00", "to": "20:00" }, "onFailure": { "kind": "retry", "times": 2, "then": "skip" } }, { "id": "w4t6", "kind": "wait", "seconds": 5, "upTo": 15 } ] }, { "id": "m8q2", "place": { "kind": "map", "mapId": 191105026 }, "when": { "id": "n1t3", "kind": "not", "of": { "kind": "call", "id": "c5r1", "package": "@asterobot:base", "condition": "levelAtLeast", "args": { "level": 50 } } }, "steps": [] } ] }, "interrupts": [ { "id": "i7w0", "label": "Bank when full", "when": { "kind": "call", "id": "c0b2", "package": "@asterobot:base", "condition": "bagsAbove", "args": { "percent": 90 } }, "run": { "plan": "plans/bank.js" }, "then": "resume" } ] } Champ Signification format 1. Un runner refuse un format qu’il ne connaît pas runner Le paquet dont plan() et run() jouent le plan : @asterobot:base label Facultatif. Affiché dans le Studio et dans les lignes de console settings Facultatif. Les réglages du plan, par nom, chacun sous la forme d’un réglage de paquet (voir Les réglages d’un plan) circuit.order drawn : les arrêts dans leur ordre. nearest : l’arrêt le plus proche qui a encore du travail circuit.repeat { "kind": "once" }, { "kind": "forever" }, ou { "kind": "until", "until": <condition> } circuit.stops Les arrêts, dans l’ordre où ils ont été dessinés. Au moins un interrupts Facultatif. Vérifiées dans cet ordre Les quatre circuits du Studio sont des préréglages d’order et de repeat, et toute autre combinaison est valable aussi, comme un balayage jusqu’au niveau 50 : Circuit order repeat Boucle drawn forever Aller simple drawn once Balayage nearest forever Jusqu’à drawn until Les identifiants. Chaque bloc qu’une personne peut sélectionner (un arrêt, une étape, une condition, une interruption) a un id : 4 caractères parmi a-z et 0-9, unique dans le plan, donné par le Studio et jamais réutilisé. Les erreurs et la position du runner nomment les blocs par lui. Les arrêtsChamp Signification id, label Le libellé est facultatif place Où est l’arrêt (voir Les lieux) go Comment le bot s’y rend. Absent vaut { "kind": "travel" }, la seule sorte du format 1 when Condition facultative. Fausse quand l’arrêt arrive : l’arrêt est sauté pour ce tour onFailure Ce qui se passe quand l’arrêt échoue (voir Échecs) steps Jouées dans l’ordre, sur chaque carte du lieu. Vide pour un arrêt qui n’est qu’un point de passage Les lieuxkind Champs Cartes map mapId Cette carte maps mapIds, au moins une Ces cartes : une zone peinte dans le Studio subarea id findMaps({ subArea: id }), quand l’arrêt arrive area id findMaps({ area: id }), quand l’arrêt arrive query query : les champs de la requête de findMaps() (subArea, area, monster, resource, zaap, x, y, worldMap) findMaps(query), quand l’arrêt arrive Un lieu est choisi. Un map nomme son mapId, un subarea ou un area son id : un lieu que le Studio a passé à l’une de ces sortes n’a pas d’identifiant tant qu’on n’en a pas choisi un, et le runner refuse le plan plutôt que de deviner. 0 est un identifiant comme un autre : la zone 0 est Amakna. Une requête restreint la recherche : elle nomme au moins l’un de subArea, area, monster, resource, x, y, worldMap. Un zaap seul, ou rien du tout, prendrait toutes les cartes du jeu. Les identifiants tiennent dans ce que lit Asterobot. Les nombres d’une requête et l’id d’un subarea ou d’un area sont des nombres entiers qui tiennent sur 32 bits : de 0 à 2147483647, et à partir de -2147483648 pour x et y. Les identifiants de carte sont des nombres JSON, entiers et au-dessus de 0 : ceux de Dofus y tiennent largement. Un lieu subarea, area ou query suit les données de jeu de la version que joue le bot. Les étapesChaque étape a un id et un kind, et peut avoir un when, une condition vérifiée juste avant elle (fausse, elle saute l’étape, ce qui n’est pas un échec), et un onFailure. kind Champs Fait Échoue quand call package, step, args Joue une étape que donne un paquet. Un argument absent prend sa valeur par défaut déclarée ; un argument peut valoir { "$setting": "<nom>" } La fonction lève une erreur ou se rejette wait seconds (0 ou plus), upTo (facultatif, au moins seconds) Attend seconds, ou un temps tiré au hasard entre seconds et upTo Jamais waitUntil until (une condition), timeout (en secondes, facultatif) Vérifie la condition tout de suite, puis chaque seconde, jusqu’à ce qu’elle soit vraie Le délai passe avant, ou la condition échoue plan plan (un chemin dans le paquet) Joue ce plan ici, puis continue Le plan échoue log message Écrit le message dans la Console Jamais stop Termine tout le run(), plans imbriqués compris, comme une fin normale Jamais L’étape d’un paquet est une fonction run(args, context), éventuellement asynchrone, qui se résout avec rien (fait sur cette carte) ou avec { nothingToDo: true, retryAfter } (rien à faire sur cette carte, peut-être de nouveau dans retryAfter secondes). context vaut { map, stop, lap, stopping } : la carte où se trouve le bot, l’arrêt joué (son JSON), les tours faits à partir de 0, et si un arrêt est demandé. Le guide du développeur d’Asterobot explique comment un paquet déclare ses étapes et ses conditions pour le Studio. Le runner lit les déclarations d’un paquet comme Asterobot les lit : un default à null n’est pas une valeur par défaut (le zéro du type, ou le premier choix), un ref à null ou "" n’est pas un ref, un min ou un max qui n’est pas un nombre n’est pas une borne, un label qui n’est pas du texte n’en est pas un. Une déclaration veut donc dire la même chose pour Asterobot, le Studio et le runner. Les conditionskind Champs Vraie quand call id, package, condition, args La condition du paquet le dit. Les arguments comme pour une étape call all of : des conditions, au moins une Chacune l’est any of : des conditions, au moins une Au moins une l’est not of : une condition Elle ne l’est pas elapsed minutes (0 ou plus) Ce temps s’est écoulé depuis le début du run() de plus haut niveau laps count (1 ou plus) Le circuit a été fait au moins ce nombre de fois time from, to ("HH:MM") L’horloge de la machine qui fait tourner Asterobot indique de from, compris, à to, exclu. Un from après son to passe minuit chance percent (0 à 100) Un tirage au hasard, nouveau à chaque vérification, tombe sous percent Les conditions gardent un arrêt (when), une étape (when, un waitUntil), le circuit (repeat.until) et une interruption (when). La condition d’un paquet est une fonction run(args, context) qui renvoie un booléen ou une promesse d’un booléen. Les interruptionsChamp Signification id, label Comme pour un arrêt when Une condition run { "plan": "<chemin dans le paquet>" } : le plan à jouer then resume : retour là où en était le circuit. stop : le plan se termine Ce que ce runner ne connaît pasUn kind, un order, un repeat.kind, un then ou un champ que ce runner ne connaît pas, ou un paquet, une étape ou une condition que le fichier de plan ne lui donne pas : le runner refuse le plan avant de bouger, en nommant le bloc, et en disant que le plan demande un @asterobot:base plus récent ou un paquet manquant. Le format ne grandit que par des ajouts qui ne changent pas la lecture des plans existants ; tout le reste est un nouveau format. Conditions sur l’état du jeuL’état du personnage, sous forme de questions oui/non. Chacune est une fonction que base exporte, utilisable depuis le code, et déclarée dans les conditions de base, donc le Studio la propose dans chaque paquet qui dépend de base : Condition Arguments Vraie quand levelAtLeast level (int, 1 ou plus) Le niveau du personnage vaut au moins level kamasAtLeast kamas (int, 0 ou plus) Il porte au moins kamas kamas lifeBelow percent (int, 1 à 100) Sa vie est sous percent de son maximum bagsAbove percent (int, 1 à 100) Ses sacs sont remplis à plus de percent de ce qu’ils peuvent porter inFight Un combat est en cours hasItem item (int, un objet), count (int, 1 ou plus, 1 par défaut) Il porte au moins count exemplaires de item, équipés ou non jobLevelAtLeast job (int, un métier), level (int, 1 ou plus) Ce métier vaut au moins level. Un métier qu’il n’a pas est au niveau 0 onMap map (int, une carte) Il se trouve sur cette carte inSubarea subarea (int, une sous-zone) Il se trouve dans cette sous-zone inArea area (int, une zone) Il se trouve dans cette zone Le contraire de chacune est un not autour d’elle. Un objet, un métier, une carte, une sous-zone et une zone sont demandés avec un ref vers leur table de données de jeu (items, jobs, maps, sub_areas, areas), donc le Studio propose une recherche par nom. Elles lisent ce qu’Asterobot suit du personnage, asterobot:character, et de sa carte, currentMap() et mapInfo(). Un fait que le jeu n’a pas encore dit n’est jamais deviné : la condition lève une erreur qui le nomme, comme the level isn't known yet ou the jobs aren't known yet. Dans un plan, c’est l’échec du bloc que la condition garde, que son onFailure traite. Quand connect() se résout, le jeu a déjà envoyé le personnage, ses caractéristiques et son inventaire, donc cela concerne surtout un plan lancé plus tôt, ou une liste de métiers que le jeu n’a pas encore envoyée. Depuis le code, chacune prend ses arguments en un seul objet, comme le Studio les passe : import { bagsAbove, levelAtLeast } from "@asterobot:base"; if (levelAtLeast({ level: 50 }) && bagsAbove({ percent: 90 })) { // … } inSubarea() et inArea() renvoient une promesse, puisqu’elles cherchent la carte dans les données de jeu. RécolterRécolter, pour les plans comme pour les scripts : récolter les ressources de la carte avec l’action du métier, comme le client de jeu quand le joueur en clique une. Base déclare l’étape dans son export steps et la condition dans ses conditions, donc le Studio les propose dans chaque paquet qui dépend de base. Depuis le code, elles prennent leurs arguments en un seul objet, comme le Studio les passe : import { gather } from "@asterobot:base"; // 303, c'est le Bois de Frêne. await gather({ resources: [303], bagsAbove: 90 }); gatherRécolte les ressources de la carte qui conviennent, l’une après l’autre : Un combat en cours, comme l’agression d’un monstre de la carte, est attendu d’abord, joué par le module de combat, comme fightMonsters en attend un. La récolte continue ensuite. Elle choisit la ressource suivante que ses arguments laissent passer : la plus proche d’abord, à la distance en cellules depuis le personnage, ou celle du plus haut niveau d’abord. Elle utilise l’élément avec la compétence qui la récolte, comme le fait useElement() : le personnage marche jusqu’à portée de l’élément et le serveur commence la récolte. Elle attend ensuite la fin de la récolte, jusqu’à 5 s après la durée donnée par le serveur. Un élément dont l’utilisation échoue, pris par quelqu’un d’autre entre-temps ou hors de portée, est essayé une fois de plus, puis abandonné. Elle s’arrête quand plus rien ne convient, après maxHarvests récoltes, ou dès que les sacs sont plus remplis que bagsAbove. Quand rien ne convenait, il n’y avait rien à faire sur cette carte, et le plan n’y revient pas avant 60 s, le temps que les ressources repoussent. Elle échoue quand le personnage meurt dans un combat, avec the character died in the fight, et, sans jamais deviner, quand le jeu n’a pas encore dit les niveaux de métier ou le poids des sacs dont elle a besoin. Argument Type Par défaut Récolte resources array d’int, des objets aucun Ce qui donne ces objets, comme 289, le Blé. Avec jobs, ce que l’un ou l’autre demande jobs array d’int, des métiers aucun Chaque ressource de ces métiers. Ni resources ni jobs : chaque ressource de la carte avoid array d’int, des objets aucun Jamais ce qui donne ces objets myLevel bool true Seulement ce que le niveau du personnage dans le métier permet. Le serveur refuserait le reste order nearest, level nearest La plus proche d’abord, ou la ressource du plus haut niveau d’abord, la plus proche à égalité maxHarvests int, 0 ou plus 0 Termine l’étape après ce nombre de récoltes sur la carte. 0 : tout ce qui convient bagsAbove int, 0 à 100 0 Termine l’étape dès que les sacs sont plus remplis que cette part de ce qu’ils peuvent porter. 0 : jamais Ce qu’offre une carte, c’est ce qu’Asterobot en suit : les éléments de currentMap() et les compétences que chacun offre en ce moment, un élément récolté n’offrant plus rien jusqu’à ce qu’il repousse. La table skills des données du jeu dit quel objet chaque compétence récolte, pour quel métier, à partir de quel niveau, et maps__interactive_elements où se trouve chaque élément. La condition de récolteCondition Arguments Vraie quand harvestsAtLeast count (int, 1 ou plus) Le personnage a fait au moins count récoltes avec gather depuis le démarrage du script CombattreCombattre, pour les plans comme pour les scripts : attaquer les groupes de monstres de la carte, que le module de combat d’Asterobot joue, régler la façon dont le module joue tant que le plan tourne, et ce qu’ont donné les combats. Base déclare les deux étapes dans son export steps et les deux conditions dans ses conditions, donc le Studio les propose dans chaque paquet qui dépend de base. Depuis le code, chacune prend ses arguments en un seul objet, comme le Studio les passe : import { fightMonsters, fightSettings } from "@asterobot:base"; fightSettings({ profile: "Prudent" }); await fightMonsters({ monsters: [4109], maxLevel: 30 }); fightMonstersAttaque les groupes de monstres de la carte qui conviennent, le plus proche d’abord, l’un après l’autre, et attend la fin de chaque combat : Un combat en cours, celui de l’étape ou un autre venu autrement, comme une agression, est attendu d’abord. Elle choisit le groupe le plus proche que ses arguments laissent passer, à la distance en cellules depuis le personnage. Sur un bot Full socket, elle active le module de combat, tant que le script tourne, quand il est désactivé : un combat ne peut pas avancer sans lui. Sur un bot MITM, l’interrupteur reste celui de la personne, et module désactivé, elle joue le combat elle-même. Elle marche jusqu’à la cellule du groupe et envoie AttackMonsterRequest avec l’identifiant du groupe, comme le client de jeu. Quand le serveur ne lance aucun combat dans les 5 s, le groupe s’est déplacé ou n’est plus là : base marche de nouveau et attaque depuis sa nouvelle cellule, et renonce à un groupe après trois attaques. Une fois le combat fini, elle attend jusqu’à 10 s le contenu de la carte, qui revient avec les groupes, et passe au suivant. Elle s’arrête quand plus aucun groupe ne convient, ou après maxFights combats. Quand aucun groupe ne convenait, il n’y avait rien à faire sur cette carte, et le plan n’y revient pas avant 60 s, le temps que des groupes reviennent. Elle échoue quand le personnage meurt dans un combat, avec the character died in the fight : il n’est plus sur la carte, et le onFailure de l’étape décide de la suite. Argument Type Par défaut Garde les groupes monsters array d’int, des monstres aucun Avec au moins un de ces monstres. Aucun : n’importe quel groupe avoid array d’int, des monstres aucun Sans aucun de ces monstres races array d’int, des races de monstres aucune Avec un monstre de l’une de ces races. Aucune : n’importe quelle race minLevel int, 0 ou plus 0 Dont le niveau, la somme des niveaux de ses monstres comme le jeu l’affiche, vaut au moins celui-ci maxLevel int, 0 ou plus 0 Dont le niveau vaut au plus celui-ci. 0 : sans limite maxMonsters int, 0 à 8 0 Avec au plus ce nombre de monstres. 0 : sans limite maxMonsterLevel int, 0 ou plus 0 Sans monstre au-dessus de ce niveau. 0 : sans limite archmonsters avoid, allow, only avoid Sans archimonstre, avec ou sans, ou seulement avec un. Un archimonstre est un monstre que les données du jeu marquent ainsi, le isMiniBoss du client maxFights int, 0 ou plus 0 Termine l’étape après ce nombre de combats sur la carte. 0 : chaque groupe qui convient Les groupes et leurs monstres sont ce qu’Asterobot suit de la carte : les acteurs de currentMap() et leurs monsters. Les races et les archimonstres viennent de la table monsters des données du jeu. fightSettingsRègle la façon dont le module de combat joue tant que le script tourne, comme le fait asterobot:fight. Ce qu’elle laisse à keep, ou un profil vide, reste comme le disent les réglages du bot, et ce qu’elle règle dure jusqu’à l’arrêt du script. Argument Valeurs Par défaut Règle module keep, on, off keep L’interrupteur du module. Sur un bot MITM, l’interrupteur reste celui de la personne profile Le nom d’un profil de combat vide Le profil que le bot joue, par son nom sur la page Profils de combat. Vide garde celui en vigueur speed keep, slow, normal, turbo keep La vitesse Les conditions de combatCondition Arguments Vraie quand diedInLastFight Le personnage est mort dans son dernier combat. Fausse avant le premier fightsAtLeast count (int, 1 ou plus) Le personnage a livré au moins count combats depuis le démarrage du script Toutes deux suivent chaque combat du bot à partir des événements du module de combat (onFight), dès que base démarre, quel que soit celui qui a lancé le combat. Se reposer et mangerRécupérer de la vie, pour les plans comme pour les scripts : s’asseoir, comme un joueur avec l’émote assis, et manger ce que contiennent les sacs. Base déclare les deux étapes dans son export steps, donc le Studio les propose dans chaque paquet qui dépend de base. Depuis le code, chacune prend ses arguments en un seul objet, comme le Studio les passe : import { rest } from "@asterobot:base"; // Manger ce que contiennent les sacs, puis s'asseoir pour le reste, jusqu'à 90 % de la vie. await rest({ upTo: 90, eat: true }); La vie est ce qu’Asterobot suit du personnage, character().life, qui compte la régénération que le serveur lance entre deux caractéristiques, plus rapide assis. Les objets sont ses piles, items(). restS’assoit jusqu’à ce que la vie revienne à une part de son maximum : Rien à faire quand la vie y est déjà. Avec eat, il mange d’abord, comme eat. Il s’assoit avec l’émote du client de jeu, EmotePlayRequest { emoteId: 1 }, l’émote 1 des données de jeu, S’asseoir, que le serveur confirme avec EmotePlayEvent. Une émote que le serveur ne confirme pas dans les 5 s n’est pas renvoyée, puisque la même émote relève le personnage : la vie revient tout de même debout, plus lentement. Une fois la vie à upTo, il se relève avec la même émote, au moins 1,2 s après s’être assis, le temps de recharge de l’émote. Le serveur confirme avec l’émote 0. Un combat qui survient entre-temps, et qui relève le personnage, est attendu comme fightMonsters en attend un, puis il se rassoit. Elle échoue quand le personnage meurt dans un combat, avec the character died in the fight ; quand la vie ne change plus pendant une minute, avec the character's life isn't coming back ; et, sans jamais deviner, tant que le jeu n’a pas dit la vie, avec the character's life isn't known yet. Argument Type Par défaut Ce qu’il fait upTo int, de 1 à 100 100 Se relève dès que la vie atteint cette part de son maximum eat bool false Mange d’abord ce que contiennent les sacs, ce qui est plus rapide food array de int, des objets aucun Ne mange que ces objets. Aucun : toute nourriture qui rend de la vie eatMange ce que contiennent les sacs jusqu’à ce que la vie revienne à une part de son maximum : Il choisit la nourriture : une pile des sacs dont les effets dans les données de jeu rendent de la vie (action 110 : pains, potions, viandes, poissons, boissons), seulement les objets de food quand ils sont donnés. Celle qui soigne le plus sans dépasser ce qui manque passe d’abord, sinon celle qui soigne le moins. Il en utilise une, comme le client de jeu pour un objet sans cible : ObjectUseRequest { objectUid }. Il attend que la pile diminue, puis que la vie monte avec les caractéristiques suivantes. Une nourriture que le serveur ne prend pas dans les 5 s est abandonnée pour le reste de l’étape. Il attend de 0,6 à 1,2 s entre deux, au rythme d’une personne. Rien à faire quand la vie y est déjà, ou quand les sacs n’ont rien à manger. Un combat entre-temps est attendu, comme dans rest. Elle prend upTo et food, comme rest. L’équipementMettre et retirer des objets, pour les plans comme pour les scripts, comme le joueur les glisse dans un emplacement ou de nouveau dans les sacs. Base déclare les deux étapes dans son export steps et la condition dans ses conditions : import { equip, unequip, wears } from "@asterobot:base"; // 1439 et 852 : Mauvaise Pioche et Petit Anneau Magique. if (!wears({ item: 1439 })) await equip({ items: [1439, 852] }); Les emplacements où un objet peut aller viennent de ses données de jeu : les possible_positions du super-type de son type, donc un anneau va à l’une ou l’autre main et un Dofus dans l’un des six emplacements de Dofus. La requête est celle du client de jeu, ObjectSetPositionRequest { objectUid, position, quantity: 1 }, que le serveur confirme avec ObjectMovementEvent ; un objet déjà dans l’emplacement retourne dans les sacs avec le sien. equipMet les items, l’un après l’autre au rythme d’une personne, chacun dans un emplacement libre où il peut aller, sinon dans son premier emplacement, ce qui renvoie l’objet qui s’y trouvait dans les sacs. Un objet déjà porté reste comme il est. Elle échoue pour un objet qui n’est pas dans les sacs, avec item 1439 isn't in the bags, un objet qui ne se porte pas, avec item 1439 can't be worn, et un objet que le serveur n’a pas déplacé dans les 5 s, avec the game didn't move item 1439. Rien à faire quand chacun est déjà porté. unequipRetire les items, de nouveau dans les sacs, l’un après l’autre au rythme d’une personne, ou tout ce que porte le personnage si aucun n’est donné. Elle échoue pour un objet que le serveur n’a pas déplacé dans les 5 s, comme equip. Rien à faire quand aucun n’est porté. La condition d’équipementCondition Arguments Vraie quand wears item (int, un objet) Le personnage porte cet objet L’hôtel des ventesVendre ce que contiennent les sacs et acheter, à l’hôtel des ventes du jeu, pour les plans comme pour les scripts, comme le fait un joueur dans la fenêtre d’hôtel des ventes du client de jeu. Base déclare les deux étapes dans son export steps : import { buy, sell } from "@asterobot:base"; // 289 est le Blé, 468 le Pain d'Incarnam. await sell({ items: [289], under: 1, floor: 70 }); await buy({ items: [468], upTo: 20, maxPrice: 50 }); Les deux utilisent l’hôtel des ventes de la carte où se tient le personnage : son comptoir, l’élément interactif qui offre la compétence 355, que le client utilise de n’importe où sur la carte. Sa range vaut 63 dans les données de jeu, donc useElement() ne marche pas jusqu’à lui. Un plan va d’abord sur la carte de l’hôtel des ventes, comme pour n’importe quel autre lieu. Les deux échouent avec there's no marketplace on this map quand aucun élément n’offre la compétence, et avec the marketplace's window didn't open quand le serveur ne l’ouvre pas dans les 5 s. Elles ferment la fenêtre en finissant, même sur un échec, avec DialogLeaveRequest, dont le client envoie l’unique champ à sa valeur zéro, KIO_EUZF. Ce que dit la fenêtre en s’ouvrant, ExchangeBidBuyerStartedEvent.sellingConditions, décide de ce que chaque étape y fait : les types d’objets que l’hôtel des ventes prend, son maxItemLevel, ses tailles de lot quantities (1, 10, 100 et 1000 dans chaque hôtel des ventes de la table auction_houses des données de jeu), sa taxPercentage (2), et npcContextualId (-1), l’identifiant de PNJ du comptoir. sellMet en vente les objets demandés, depuis les sacs : Rien à faire quand les sacs n’en contiennent aucun : la fenêtre n’est pas ouverte. Elle ouvre la fenêtre, qui s’ouvre sur son onglet Achat, puis passe à l’onglet Vente comme une personne le clique, 0,7 à 1,1 s plus tard : InventoryUpdateMergedInventoryTypeRequest { mergedInventoryType: MERGED_INVENTORY_TYPE_BID_HOUSE }, puis NpcGenericActionRequest { npcActionId: 5, npcId: -1, npcMapId }, dans la même frame. Le serveur ferme l’onglet Achat, ExchangeLeaveEvent, et ouvre ExchangeBidSellerStartedEvent, avec les lots du compte en vente. Pour chaque pile, elle passe un objet d’un type que l’hôtel des ventes ne prend pas, ou au-dessus de son niveau maximum. Elle choisit l’objet comme le client, dans une seule frame : elle cesse de suivre l’objet suivi avant, ExchangeBidHouseSearchRequest { objectGid, follow: false }, suit celui-ci, { follow: true }, et demande ses prix, ExchangeBidHousePriceRequest { objectGid }. ExchangeBidPriceEvent répond avec le lot le moins cher de chaque taille, minimalPrices, le prix moyen d’un objet, averagePrice, et allIdentical. Un objet dont les lots ne sont pas tous identiques, comme un équipement qui a ses propres caractéristiques, reste dans les sacs : ses caractéristiques font son prix. Elle fixe le prix de chaque taille une fois pour la pile, comme ci-dessous, et met les lots en vente l’un après l’autre : ExchangeObjectMovePricedRequest { objectUid, quantity, price }, avec l’uid de la pile, la taille du lot et le prix du lot entier. Le serveur prend sa taxe et confirme avec ExchangeBidHouseItemAddedEvent. Une personne tape le prix d’un lot, 2 à 5 s, puis clique de nouveau pour le lot suivant de la même taille, 0,6 à 1 s ; l’objet suivant vient 0,7 à 1,1 s après le dernier lot d’une pile. Elle ferme la fenêtre 1,5 à 5 s après le dernier lot. Les lots sont les plus gros que chaque pile remplit, 1000, puis 100, 10 et 1, ou autant d’une seule taille que lot en demande : ce qui n’en remplit pas un reste dans les sacs. Le prix d’un lot est : under kamas sous le lot le moins cher de sa taille en vente. Un lot du compte qui est déjà le moins cher, listé par ExchangeBidSellerStartedEvent ou mis en vente par cette étape, garde son prix : base ne se coupe jamais l’herbe sous le pied. Sans lot de cette taille en vente, le prix moyen pour autant d’objets. Jamais sous floor pour cent du prix moyen pour autant d’objets, aussi bas que vendent les autres. Elle arrête de mettre des lots en vente quand le personnage ne peut pas payer la taxe, la taxPercentage du prix arrondie, avec base: not enough kamas for the marketplace's tax on <item> (<tax> kamas), et quand le serveur ne confirme pas un lot dans les 5 s, avec base: the marketplace took no more lots of <item>: it may be full, or the account may have as many lots on sale as it can. Aucune capture n’a encore ce refus. Elle écrit ce qu’elle a mis en vente de chaque objet, comme base: put on sale Bakélélite: 1 × 10 at 129993, 2 × 1 at 13871 kamas, et se résout en { nothingToDo: true } quand elle n’a rien mis en vente. Argument Type Par défaut Ce qu’il fait items array de int, des objets aucun Vend ces objets. Avec jobs, ce que l’un ou l’autre demande jobs array de int, des métiers aucun Vend chaque ressource que récoltent ces métiers, d’après la table skills des données de jeu. Ni items ni jobs : rien n’est vendu lot int : 0, 1, 10, 100 ou 1000 0 0 : les plus gros lots que chaque pile remplit. Sinon, des lots de cette taille seulement under int, 0 ou plus, kamas 1 De combien sous le lot le moins cher de la même taille. 0 : le même prix floor int, de 0 à 100 70 Jamais sous cette part du prix moyen. 0 : pas de plancher buyAchète les objets demandés jusqu’à ce que le personnage ait upTo de chacun, portés ou dans les sacs, comme itemCount() les compte : Rien à faire quand il en a assez de chacun : la fenêtre n’est pas ouverte. Elle échoue sans maxPrice, avec buying needs the most to pay per item, maxPrice : base n’achète jamais à n’importe quel prix. Elle ouvre la fenêtre, sur son onglet Achat. Pour chaque objet, elle passe un objet d’un type que l’hôtel des ventes ne prend pas. Elle choisit le type de l’objet comme une personne, 1,5 à 4 s plus tard, ExchangeBidHouseTypeRequest { typeId, follow: true }, auquel ExchangeTypesExchangerDescriptionForUserEvent répond avec les objets de ce type en vente. Puis l’objet, 1,2 à 2,5 s plus tard : elle cesse de suivre l’objet suivi avant et suit celui-ci, auquel ExchangeTypesItemsExchangerDescriptionForUserEvent répond avec ses annonces. Une ressource a une seule annonce, avec le prix du lot le moins cher de chaque taille. Un objet qui a ses propres caractéristiques en a une par objet, et n’est pas acheté. Elle achète des lots l’un après l’autre, à 1,5 à 3 s d’intervalle : le lot qui coûte le moins par objet parmi ceux qui n’achètent pas plus que ce qui manque et coûtent au plus maxPrice par objet, le plus gros à égalité. ExchangeBidHouseBuyRequest { objectUid, price, quantity } nomme l’uid de l’annonce, le prix du lot entier tel qu’affiché et sa taille, et ExchangeBidHouseBuyResultEvent répond avec bought. Les prix de l’annonce suivent ExchangeBidHouseInListUpdatedEvent, qui arrive à mesure que les lots se vendent, ses propres achats compris. Une fois un objet fini, elle cesse de le suivre deux fois, 0,1 à 0,3 s plus tard, comme le client. Elle ferme la fenêtre 0,3 à 1 s après le dernier objet. Elle arrête d’acheter un objet quand aucun lot ne convient, avec base: no lot of <item> at <maxPrice> kamas each or less fits the <n> missing, et quand le serveur ne vend pas le lot, avec base: the marketplace didn't sell the lot of <size> <item>: someone may have bought it first. Elle arrête tout quand le personnage ne peut pas payer un lot. Elle écrit ce qu’elle a acheté de chaque objet, comme base: bought Incarnam Bread: 2 × 10 at 450 kamas, et se résout en { nothingToDo: true } quand elle n’a rien acheté. Argument Type Par défaut Ce qu’il fait items array de int, des objets aucun Achète ces objets upTo int, 1 ou plus 1 Combien de chacun le personnage doit avoir. Elle achète ce qui manque maxPrice int, 1 ou plus, kamas aucun Le plus qu’elle paie pour un objet. Obligatoire : la vérification d’un plan le demande La capture de l’hôtel des ventesUne capture du client 3.7.12.12 du 2026-10-29, heures en UTC : deux lots de 1 et un de 10 de Bakélélite (749) à l’hôtel des ventes des ressources, 1261, sur la carte 212601350 ; un objet qui a ses propres caractéristiques (6716) à l’hôtel des ventes 1265, sur la carte 212601345 ; et deux lots de 10 achetés à 1265, sur la carte 73532928. Heure Message Ce qu’il portait 13:07:42.652 → InteractiveUseRequest élément 522694, le comptoir, instance de compétence 8731 13:07:42.682 ← ExchangeBidBuyerStartedEvent hôtel des ventes 1261, tailles 1, 10, 100, 1000, taxe 2 %, niveau maximum 200 13:07:43.442 → InventoryUpdateMergedInventoryTypeRequest, NpcGenericActionRequest MERGED_INVENTORY_TYPE_BID_HOUSE ; action 5, PNJ -1, carte 212601350 13:07:43.474 ← ExchangeLeaveEvent, ExchangeBidSellerStartedEvent l’onglet Achat se ferme ; les lots du compte en vente 13:07:43.479 → ExchangeBidHouseSearchRequest ×2, ExchangeBidHousePriceRequest 749 plus suivi, suivi ; ses prix 13:07:43.509 ← ExchangeBidPriceEvent moins cher 13872, 129994, 1399999, aucun ; moyenne 13010 ; identiques 13:07:47.215 → ExchangeObjectMovePricedRequest pile 2003241, 1 à 13871 13:07:47.245 ← KamasUpdateEvent, ExchangeBidHouseItemAddedEvent, ObjectQuantityEvent 277 kamas de taxe ; le lot ; 11 restants 13:07:48.010 → ExchangeObjectMovePricedRequest pile 2003241, 1 à 13871 13:07:59.409 → ExchangeObjectMovePricedRequest pile 2003241, 10 à 129993, son dernier : ObjectDeletedEvent 13:08:04.526 → DialogLeaveRequest 13:08:56.395 ← ExchangeBidPriceEvent objet 6716 : moins cher 99998, moyenne 1172559, pas identiques 13:12:34.888 → ExchangeBidHouseTypeRequest type 326 13:12:36.659 → ExchangeBidHouseSearchRequest 33326 suivi 13:12:36.690 ← ExchangeTypesItemsExchangerDescriptionForUserEvent annonce 73541 : 500, 4500, 49896, aucun 13:12:38.939 → ExchangeBidHouseBuyRequest annonce 73541, 10 à 4500 13:12:38.970 ← ExchangeBidHouseInListUpdatedEvent, KamasUpdateEvent, ObjectAddedEvent, ExchangeBidHouseBuyResultEvent les nouveaux prix de l’annonce ; les kamas ; la pile ; acheté 13:12:39.256 → ExchangeBidHouseSearchRequest ×2 33326 plus suivi, deux fois 13:12:42.440 → DialogLeaveRequest La taxe est la taxPercentage du prix, arrondie : 277 kamas pour 13871, 2600 pour 129993, 2400 pour 119999. Chaque fois que la fenêtre s’ouvre ou se ferme, le serveur renvoie tout l’inventaire, InventoryContentEvent. La banqueMettre en banque ce que contiennent les sacs et en retirer des objets et des kamas, pour les plans comme pour les scripts, comme le fait un joueur dans la fenêtre de banque du client de jeu. Base déclare les deux étapes dans son export steps : import { deposit, withdraw } from "@asterobot:base"; // Tout sauf le pain, puis 20 pains pour se reposer. 468 est le Pain d'Incarnam. await deposit({ keep: [468] }); await withdraw({ items: [468], upTo: 20 }); Les deux utilisent le banquier de la carte où se tient le personnage : le plus proche des acteurs de currentMap() qui ont npc: true. Un plan va d’abord sur la carte de la banque, comme pour n’importe quel autre lieu. Pour ouvrir la banque : Une personne clique le banquier une fois la carte affichée, 0,6 à 1 s plus tard : NpcGenericActionRequest { npcActionId: 3, npcId, npcMapId }, l’action générique qui parle. Le serveur répond avec la première réplique du dialogue, NpcDialogQuestionEvent, et ses réponses. La réponse de la banque est celle dont les actions comptent l’action 196. Ses dialogParams portent le prix de la banque. Base la choisit 0,6 à 0,9 s plus tard, comme une personne lit la réplique : NpcDialogReplyRequest { replyId }. Le serveur termine le dialogue, prend le prix, et ouvre la banque : ExchangeStartedWithStorageEvent, puis StorageInventoryContentEvent avec les piles et les kamas de la banque, que base suit avec StorageObjectUpdateEvent, StorageObjectRemovedEvent et StorageKamasUpdateEvent tant que la fenêtre est ouverte. Les deux échouent avec there's no banker on this map quand aucun acteur n’est un PNJ, avec the banker didn't answer et the bank didn't open quand le serveur ne répond pas dans les 5 s, et avec the NPC on this map doesn't offer the bank quand le dialogue du PNJ le plus proche n’a pas de réponse de banque. Ce dialogue reste ouvert : quitter le dialogue d’un PNJ n’est pas encore dans une capture. Elles déplacent une pile à la fois, comme une personne les glisse, 1,2 à 3 s après l’ouverture de la banque, puis à 0,5 à 1,5 s d’intervalle : ExchangeObjectMoveRequest { objectUid, quantity }, avec l’uid des sacs et une quantité positive pour mettre une pile en banque, l’uid de la banque et une quantité négative pour en retirer une. Asterobot voit chaque pile arriver et partir, donc items() et Bags above restent justes. Elles ferment la fenêtre 0,4 à 1,2 s après le dernier déplacement, avec DialogLeaveRequest, comme l’hôtel des ventes. Chacune écrit ce qui a bougé, comme base: put in the bank: 37 Manganese, 395 Iron, 700 kamas. depositMet en banque chaque pile des sacs sauf les objets à garder, keep, ou seulement les items et ce que récoltent les jobs quand ils sont donnés. Les objets portés restent portés. Le serveur confirme une pile avec ObjectDeletedEvent, ou ObjectQuantityEvent pour une partie. Une pile qu’il ne confirme pas dans les 5 s reste, avec base: the bank didn't take <item>, et les autres continuent. Avec kamas, elle met en banque les kamas que le personnage porte au-delà de keepKamas une fois le prix de la banque payé, avec ExchangeMoveKamaRequest { quantity }, que StorageKamasUpdateEvent confirme. Rien à faire quand il n’y a rien à mettre en banque : le banquier n’est pas sollicité. Argument Type Par défaut Ce qu’il fait items array de int, des objets aucun Ne met en banque que ces objets. Avec jobs, ce que l’un ou l’autre demande. Ni l’un ni l’autre : tout ce que contiennent les sacs jobs array de int, des métiers aucun Ne met en banque que ce que récoltent ces métiers keep array de int, des objets aucun Ces objets restent dans les sacs, comme la nourriture, les potions ou un équipement de rechange kamas bool false Met aussi des kamas en banque keepKamas int, 0 ou plus 0 Les kamas laissés sur le personnage, pour les zaaps et le prix de la banque withdrawRetire de la banque les items jusqu’à ce que le personnage en ait upTo de chacun, portés ou dans les sacs, comme itemCount() les compte, dans autant de piles de la banque qu’il le faut. Le serveur confirme avec StorageObjectRemovedEvent, ou StorageObjectUpdateEvent pour une partie d’une pile, et les sacs reçoivent la pile avec ObjectAddedEvent. Un objet que la banque n’a pas est écrit base: no <item> in the bank. Avec kamasUpTo, elle retire des kamas jusqu’à ce que le personnage en porte autant une fois le prix payé, dans la limite de ceux de la banque, avec un ExchangeMoveKamaRequest négatif. Rien à faire quand le personnage a déjà assez de tout. Argument Type Par défaut Ce qu’il fait items array de int, des objets aucun Retire ces objets upTo int, 1 ou plus 1 Combien de chacun le personnage doit avoir. Elle retire ce qui manque kamasUpTo int, 0 ou plus 0 Retire des kamas jusqu’à ce que le personnage en porte autant. 0 : aucun Les captures de la banqueDeux captures du client 3.7.12.12 du 2026-10-29, heures en UTC. La première est la visite à la banque de la capture de l’hôtel des ventes, sur la carte 217059328 : Heure Message Ce qu’il portait 13:08:16.497 ← MapComplementaryInformationEvent le banquier, acteur -20000 sur la cellule 289, un PNJ 13:08:17.227 → NpcGenericActionRequest action 3, PNJ -20000, carte 217059328 13:08:17.259 ← NpcDialogCreationEvent, NpcDialogQuestionEvent réplique 47810, prix 468 ; réponses 63535, avec l’action 196, et 63536 13:08:17.894 → NpcDialogReplyRequest réponse 63535 13:08:17.925 ← DialogLeaveEvent, TextInformationEvent, KamasUpdateEvent, ExchangeStartedWithStorageEvent, StorageInventoryContentEvent le dialogue se termine ; 468 kamas payés ; la banque s’ouvre avec ses piles et ses kamas 13:08:19.498 → ExchangeMoveKamaRequest -249819, tous les kamas de la banque 13:08:21.274 → ExchangeObjectMoveRequest pile 386568, 12 en banque 13:08:21.304 ← StorageObjectUpdateEvent, ObjectDeletedEvent la pile de la banque pour cet objet, désormais 18 ; la pile des sacs disparue 13:08:36.356 → ExchangeObjectMoveRequest pile de la banque 386564, -1 retiré 13:08:36.386 ← ObjectAddedEvent, StorageObjectRemovedEvent l’objet dans les sacs ; la pile de la banque disparue 13:08:45.519 → DialogLeaveRequest Six autres objets sont sortis à 0,5 à 0,65 s d’intervalle, puis deux à 2,6 et 3,2 s. La deuxième capture, une visite à la banque à part, a des piles mises en banque entières et en partie : Heure Message Ce qu’il portait 13:58:55.296 → ExchangeObjectMoveRequest 1 en banque, d’une pile de 37 13:58:55.326 ← ObjectQuantityEvent, InventoryWeightEvent 36 restants 13:58:56.794 → ExchangeObjectMoveRequest les 36 restants en banque 13:58:56.825 ← ObjectDeletedEvent la pile des sacs disparue 13:59:05.001 → ExchangeObjectMoveRequest une pile de 395 en banque 13:59:07.008 → ExchangeObjectMoveRequest 20 retirés 13:59:07.038 ← ObjectAddedEvent une pile de 20 dans les sacs 13:59:08.240 → ExchangeObjectMoveRequest les 20 de nouveau en banque La fenêtre de la banque a aussi des boutons qui déplacent plusieurs piles à la fois. En retirer plusieurs envoie ExchangeObjectTransferListToInventoryRequest { objectsUid } (13:58:52.773 dans la deuxième capture), et le serveur répond avec un message qu’Asterobot ne lit pas encore, isl. En mettre plusieurs en banque a ses propres requêtes, comme ExchangeObjectTransferListFromInventoryRequest, dont les réponses ne sont pas lues non plus. Base déplace une pile à la fois à la place, pour qu’Asterobot voie chaque pile. Les profils de combatBase fournit 24 profils de combat pour le module de combat d’Asterobot, prêts à jouer. Une fois base installé, ils sont sur la page Profils de combat avec la source Paquet, et dans la liste Profil de chaque bot, sous le nom @asterobot:base/<clé> : @asterobot:base/careful, @asterobot:base/ranged-kite… Un bot en joue un qu’il joue base lui-même ou non, et chaque bot le joue dans la version de base la plus récente installée : mettre base à jour les met à jour dès le combat suivant. Chacun est indépendant de la classe : aucun ne nomme de sort, donc chacun joue n’importe quelle classe à n’importe quel niveau, avec les sorts et l’arme du personnage. Quand son but n’est pas un rôle, un profil ne donne pas de rôle, donc chaque classe garde celui que le jeu lui donne en premier (un Féca soutient, un Eniripsa soigne), ni de portée, donc il combat à la portée que font ses sorts. Ce qu’un profil ne donne pas est la valeur du profil par défaut. Clé Ce qu’il fait Ce qu’il règle careful Prudent : ne finit jamais un tour là où les ennemis peuvent le blesser gravement, et se cache dès que sa vie est basse risk 15, poids safety 2 ; sous 35 % de vie, se replie à couvert all-in Tout pour l’attaque : inflige le plus de dégâts possible et ignore le danger, mort comprise risk 100 ; poids damage 1,2, kill 1,5, safety 0,25 melee-charge Se place près, fonce sur l’ennemi le plus proche et frappe au contact range melee, placement close, positioning charge ranged-kite Se place loin, frappe à distance, puis sort de la portée des ennemis range ranged, placement far, positioning kite, risk 40 ranged-hide Se place loin, frappe à distance, puis finit son tour hors de la vue des ennemis range ranged, placement far, positioning hide, risk 30 hold-position Ne bouge que pour atteindre un lancer, pour garder taclés les ennemis à côté de lui positioning hold, risk 60 finish-the-weakest Achève d’abord l’ennemi qui a le moins de vie focus lowestLife, poids kill 2 kill-the-most-dangerous Tue d’abord l’ennemi qui peut le blesser le plus là où il se tient focus mostDangerous, poids kill 1,5 healer Soigneur : soigne d’abord l’allié le plus faible, puis lui-même, et frappe quand personne n’a besoin de soin role healer, allies protect ; un allié sous 60 % est soigné d’abord, puis lui-même sous 40 % bodyguard Garde du corps : protège ses alliés, boucliers et boosts pour le plus faible, et se place pour les abriter allies protect, risk 70, poids buff 1,5 ; un allié sous 50 % est boosté d’abord support Affaiblit les ennemis et booste ses alliés avant de frapper role support, allies help, risk 40 tank Passe devant, encaisse les coups pour l’équipe et tacle les ennemis au contact role tank, range melee, placement close, positioning charge, allies protect, risk 85, poids control 2 crowd-control Contrôle : pousse les ennemis, leur retire PA et PM et leur donne des états avant de les frapper risk 50, poids control 3 summoner Invocateur : invoque dès qu’il peut et reste derrière ses invocations, hors de la vue des ennemis positioning hide, risk 35, poids summon 2,5 trapper Poseur de pièges : pose pièges et glyphes là où les ennemis vont passer, puis reste hors de leur portée range mid, positioning kite, risk 40, poids mark 2,5 earth-strength Préfère ses sorts de terre, pour un personnage force elements earth fire-intelligence Préfère ses sorts de feu, pour un personnage intelligence elements fire water-chance Préfère ses sorts d’eau, pour un personnage chance elements water air-agility Préfère ses sorts d’air, pour un personnage agilité elements air weapon-fighter Frappe avec son arme dès qu’il peut, au contact weapon.use prefer, range melee, placement close, positioning charge spells-only Ne frappe jamais avec l’arme ni à mains nues : ses PA vont à ses sorts weapon.use never team-player Joueur d’équipe : ne blesse jamais un allié avec ses zones, et soigne et booste ses alliés dès que ça aide allies protect ; poids allyHarm 10, heal 1,5, buff 1,5 carried Un bas niveau dans un groupe fort : se place loin, reste hors de danger et de vue, et ne frappe que sans risque placement far, positioning hide, risk 0, poids safety 3 ; sous 50 % de vie, se replie à couvert boss-fight Pour les boss et les donjons : prudent, tue d’abord l’ennemi le plus dangereux et garde le groupe à l’abri focus mostDangerous, allies protect, risk 25 ; sous 40 % de vie, se replie à couvert Ce que fait chaque réglage est dans le guide d’Asterobot, Combats > Réglages du profil, et le format dans son guide du développeur, Fight profile format. Pour en jouer un, choisissez-le dans la liste Profil d’un bot, dans son onglet Combat ; depuis un plan, avec l’étape Fight settings et son nom (@asterobot:base/careful) ; depuis un script, avec setProfile("@asterobot:base/careful") d’asterobot:fight. Pour l’adapter, sur la page Profils de combat, copiez-le avec Copier dans vos propres profils pour le modifier, ou faites un profil qui l’extends et ne donne que ce qui change : { "version": 1, "name": "Cra prudent", "extends": "@asterobot:base/careful", "elements": ["air"] }. Celui qui l’étend suit les mises à jour de base. Leurs clés ne changent pas. Un bot réglé sur l’un d’eux le garde d’une mise à jour de base à l’autre. Pour en garder un exactement tel quel, copiez-le dans vos propres profils. Ce sont les fichiers JSON de fightprofiles/, un par profil, chacun nommé d’après sa clé et tel que la page Profils de combat exporte un profil : c’est ainsi qu’un paquet fournit des profils. Ce sont des données, jamais exécutées. La preuve d’identitéUne fois la session ouverte, le serveur de jeu demande au client de prouver qu’il détient une clé secrète. C’est une preuve d’identité de Schnorr en trois échanges : ServerSessionReadyEvent -> ClientChallengeInitRequest challengeKey = G^x mod P ServerVerificationEvent -> ClientIdRequest id = G^k mod P ServerChallengeEvent { c } -> ClientChallengeProofRequest proof = (k + c*x) mod Q P est le nombre premier de 768 bits de la RFC 2409, section 6.1, « Oakley group 1 ». G = 2, et Q = (P - 1) / 2. x est le secret du client, tiré une fois par processus client. k est un nonce, tiré à nouveau pour chaque ServerVerificationEvent. Le serveur vérifie que G^proof = id × challengeKey^c (mod P). Base ne fait aucun de ces calculs. Il appelle publicKey(), commitment() et proof(value) d’asterobot:verification, qui s’adressent au code Go d’Asterobot (ankama/game/network/verification), et envoie les chaînes qu’elles renvoient. Les secrets n’entrent jamais dans JavaScript. x et k restent en Go. Un script ne voit que ce qui part sur le réseau. x appartient au bot. Il est tiré une fois à la création du bot et gardé toute sa vie, d’un script à l’autre et d’une reconnexion à l’autre, comme un processus client le garde. La même clé publique est publiée à chaque connexion. k est remplacé à chaque engagement. Un second ServerVerificationEvent avant le défi remplace le nonce, et la preuve répond au plus récent, comme le fait le client. Un défi absent reçoit aussi une réponse. Quand ServerChallengeEvent ne porte aucune valeur, proof(undefined) calcule le défi lui-même, comme le fait le client : le SHA-256 de G, de la clé publique, de l’engagement et de l’empreinte de l’appareil envoyée à la connexion du bot. Les deux cas arrivent : le défi d’une capture portait une valeur, celui d’une autre était vide. La part de base, c’est l’enveloppe et l’ordre : Chaque réponse part dès que son événement arrive. Le client répond à sa trame suivante, 6 à 55 ms plus tard selon sa charge, ce qui reste du bruit sous celui du réseau. Le serveur attend chaque réponse avant son étape suivante, donc les trois ne peuvent pas se croiser. Extrait d’une capture : 00:14:08.961 IN ServerVerificationEvent 00:14:08.968 OUT ClientIdRequest #10 00:14:08.997 IN ServerChallengeEvent (no value) 00:14:09.003 OUT ClientChallengeProofRequest #11 Les trois réponses portent un uid, comme celles du client, envoyées avec send(..., { correlated: true }). Rien n’y répond jamais. Les calculs sont couverts par les tests de ankama/game/network/verification dans Asterobot. Ils vérifient le groupe d’après le texte de la RFC, et l’équation du serveur sur chaque branche. Ils vérifient aussi deux vrais contrôles auxquels le client Dofus a répondu, tirés de captures d’un même lancement du jeu : Contrôle Connexion Défi uids La preuve du client 1 c3 aucun : le client le calcule #10, #11 se vérifie avec le défi qu’Asterobot calcule 2 c7 une valeur de 255 bits venue du serveur #12, #13 se vérifie avec cette valeur Le groupe, la preuve et les deux branches sont donc confirmés face au vrai client, pas seulement lus dans son code. Les deux contrôles se vérifient avec la même clé, envoyée sur c3 : le client garde son secret pendant tout le lancement, d’une connexion à l’autre, comme Asterobot en garde un par bot d’une reconnexion à l’autre. Garder la connexion en vieLe serveur relance le client avec deux événements à la fois, dans la même milliseconde et tous deux vides : SequenceNumberEvent et BasicLatencyStatsEvent. Extrait d’une capture du client : 23:57:26.192 IN SequenceNumberEvent 23:57:26.192 IN BasicLatencyStatsEvent 23:57:26.192 OUT SequenceNumberRequest { number: 3 } no uid 23:57:26.200 OUT BasicLatencyStatsRequest { latency: 44 } #5 SequenceNumberEventLe client répond tout de suite avec SequenceNumberRequest { number }, envoyé sans uid : number est le nombre de relances que cette connexion a reçues, celle à laquelle il répond comprise, donc la première réponse vaut 1 ; le compte est gardé dans la mémoire du bot, par connexion. Un script relancé sur la même connexion continue de compter. Le serveur a envoyé chaque relance, il peut donc vérifier le nombre exactement. BasicLatencyStatsEventLe client répond avec BasicLatencyStatsRequest { latency } une trame après la séquence, 8 à 30 ms dans les captures. Il porte un uid, et rien n’y répond. Base envoie latency() juste après le SequenceNumberRequest de la même relance, dans l’ordre du client. Les paires arrivent toutes les 4 minutes, selon l’horloge du serveur plutôt qu’à partir de la connexion : à la seconde 26 d’une minute dont le reste de la division par 4 vaut 1, dans deux sessions. La première arrive 0 à 4 minutes après le chargement du personnage. Un SequenceNumberEvent de plus arrive seul, sans question de latence, pendant le chargement du personnage ; il compte comme la première relance, donc 7 paires plus tard la réponse est number: 8. Chaque paire consomme un uid, un contrôle en consomme deux, et les requêtes numérotées du jeu les leurs : chaque requête numérotée prend l’uid suivant. Dans une session, ClientChallengeInitRequest était #2, puis : Relance Heure Réponse de latence 3 23:57:26 #5 (contrôle) 00:14:08 ClientIdRequest #10, ClientChallengeProofRequest #11 9 00:21:26 #13 Les relances 2 et 3 seules donneraient #3 et #4 : une autre requête numérotée est partie avant la relance 3 dans cette session. Base puise dans le même compteur, donc les mêmes requêtes reçoivent les mêmes numéros. PingsUn vrai client n’envoie de pings que lorsqu’une personne l’utilise : toutes les 5 secondes avec son option de ping rapide, sur les battements où il y a eu une action. Avec ping sur active, base reproduit cela : un battement de 5 secondes qui envoie PingRequest { quiet: true } environ une fois sur deux ; seulement une fois le personnage chargé ; chaque PongEvent qui répond à un ping en cours ajoute son aller-retour aux échantillons dont latency() fait la moyenne, et un pong sans ping en cours est ignoré. Entrer dans le mondeCe que le client envoie après CharacterLoadingCompleteEvent, dans l’ordre. Le client espace certains de ces messages d’une trame ou deux, 10 à 90 ms ; base envoie chaque groupe d’un seul tenant et ne garde que les attentes de 100 ms et plus : Quand Messages Avec minimal t0 + 1085 ms ContactWarnOnAchievementCompleteSetRequest, FriendSetWarnOnConnectionRequest, FriendSetWarnOnLevelGainRequest, ContactWarnOnPermanentDeathSetRequest, FriendSetStatusShareRequest, tous vides : le client rejoue ses options sociales, et un bot n’en a aucune Attend pareil, n’envoie rien ensuite GuildInformationRequest { informationType: INFO_HOUSES }, GuildInformationRequest { informationType: INFO_GENERAL }, GuildWeeklyNextResetRequest Sauté t0 + 1349 ms ContextCreationRequest Envoyé ensuite BakApiTokenRequest, GuildMemberWarnOnConnectionStopRequest, SetCommunityChannelRequest { community } Sauté +231 ms QuestStepInformationRequest { questId } pour chaque quête en cours, deux fois : un passage par identifiant, puis le même passage à nouveau Sauté ensuite ObjectAveragePricesRequest, seulement s’il n’a pas encore été demandé aujourd’hui (UTC) Sauté ensuite FriendListRequest, AcquaintanceListRequest, SpouseInformationRequest, GuildInformationRequest { informationType: INFO_HOUSES } Sauté Les étapes de quête suivent le journal. Le serveur envoie QuestsEvent, le journal de quêtes du personnage, pendant son chargement. Une fois dans le monde, le client demande l’étape de chaque quête en cours, chaque quête deux fois. Dans les captures : 1631, 1631 avec une quête en cours, et 1631, 1637, 1631, 1637 avec deux. Seul ContextCreationRequest est nécessaire pour atteindre la carte. Le reste est ce qui fait ressembler le bot à un client. Le premier écart se mesure à partir du chargement lui-même. Un script relancé en retard n’attend pas une seconde fois la seconde entière. Le jeton de boutique reste frais. Avec faithful, base envoie de nouveau BakApiTokenRequest une heure après chaque BakApiKeyEvent, comme le fait le client. Suivre les cartesChaque MapCurrentEvent { mapId } nomme la carte sur laquelle le serveur a mis le personnage. Pour une carte instanciée, le serveur envoie MapCurrentInstanceEvent { mapId, instantiateMapId } à la place, et base le traite de la même façon avec son mapId : instantiateMapId ne sert à rien, comme dans le client. Il arrive avec un TimeEvent, l’horloge du serveur, auquel rien ne répond. Le client charge l’asset de la carte depuis ses propres fichiers, puis dit qu’il est prêt : Il attend un temps aléatoire entre 1,2 et 2,4 s pour la première carte de la session, et entre 0,4 et 1,2 s ensuite. Dans les captures, une première carte a pris 2,4 s une fois et 1,197 s une autre, et les cartes suivantes 1,19 s et 407 ms, une carte déjà chargée auparavant allant plus vite. Sur la première carte seulement, il envoie AnomalySubareaInformationRequest. Il envoie ContextReadyRequest { mapId }, puis MapInformationRequest { mapId }, l’un après l’autre, avec l’identifiant qu’a donné MapCurrentEvent ou MapCurrentInstanceEvent. Le serveur répond avec MapComplementaryInformationEvent, un aller-retour plus tard, et le suivi de carte d’Asterobot place le personnage : currentMap() a son cellId. Le serveur n’envoie rien de la nouvelle carte avant l’arrivée de ces deux requêtes. Dans une capture, les 1,19 s entre MapCurrentEvent et les requêtes ne portaient aucun message entrant : 21:19:44.838 IN MapChangeOrientationEvent, MapCurrentEvent, TimeEvent 21:19:46.028 OUT ContextReadyRequest + MapInformationRequest same map id 21:19:46.058 IN MapComplementaryInformationEvent Le client déclenche les deux requêtes à partir d’un même événement interne, son moteur de rendu qui démarre sur la carte chargée, donc il n’envoie jamais l’une sans l’autre. Base non plus. Quand un MapCurrentEvent ou un MapCurrentInstanceEvent plus récent arrive alors qu’une carte est encore en chargement, seule la carte la plus récente est annoncée. La carte d’un combat n’est pas touchée. Quand un combat commence, le serveur nomme la carte où il se déroule, et le client y répond autrement (FightPingsRequest, puis ContextReadyRequest). Le module de combat d’Asterobot y répond tant qu’il est activé, et rien ne doit le faire quand il est désactivé : base n’annonce donc jamais une carte nommée pendant un combat. La carte nommée une fois le combat fini est annoncée comme les autres. Avec changeMap() et useElement(). Elles se résolvent une fois le contenu de la nouvelle carte arrivé, donc elles attendent que base le demande. Base le dit à Asterobot avec answerMapLoads(), et Asterobot n’envoie alors jamais lui-même les deux requêtes, quel que soit le temps que prend base : sur un bot qui utilise base, elles partent une seule fois, de base, au rythme du client, même sur un PC lent. Sur un bot dont aucun script ne charge les cartes, Asterobot les envoie lui-même, au rythme du client lui aussi. Mémoire, et reprise en cours de routeLes variables propres d’un script disparaissent après Arrêter, un rechargement ou Lancer depuis l’éditeur, alors que le bot et sa connexion de jeu continuent. Un script relancé sur la même connexion ne doit pas tout recommencer : le serveur n’accepte pas un second IdentificationRequest ; le personnage est déjà sélectionné ; le serveur a compté chaque relance. Base garde donc ce qu’il sait dans la mémoire du bot, que tous les scripts du bot partagent et qui survit à chacun d’eux. Clé Ce qu’elle contient @asterobot:base/connection L’état de la connexion de jeu actuelle, à côté de session.connectionId. Une valeur enregistrée pour une autre connexion, d’avant une reconnexion du bot, est ignorée. @asterobot:base/latency { samples } : les 50 derniers allers-retours de ping, en millisecondes. @asterobot:base/prices { day } : le jour UTC du dernier ObjectAveragePricesRequest. @asterobot:base/plan Où en est le plan joué, tant qu’il l’est : voir Où en est le runner. L’état de la connexion a ces champs. Chacun est absent tant que son étape n’a pas eu lieu. Champ Défini quand id Toujours : le session.connectionId auquel il appartient identified IdentificationRequest va être envoyé. Il est défini avant l’envoi, donc un script arrêté entre les deux n’en envoie jamais un second. accepted, refused Le serveur a répondu au ticket serverCommunity ServerSettingsEvent est arrivé characterId Le serveur a sélectionné un personnage activeQuests QuestsEvent est arrivé : les identifiants des quêtes en cours loadedAt CharacterLoadingCompleteEvent est arrivé (Date.now()) bringUp Les requêtes d’après le chargement ont commencé à partir contextCreated ContextCreationRequest va être envoyé anomalyAsked AnomalySubareaInformationRequest va être envoyé pendingMapId La dernière carte nommée par MapCurrentEvent ou MapCurrentInstanceEvent readyMapId La dernière carte pour laquelle ContextReadyRequest est parti sequence Le nombre de SequenceNumberEvent arrivés bakKeyAt Le dernier BakApiKeyEvent est arrivé Ce que fait un script relancé sur la même connexion, selon l’endroit où le précédent s’est arrêté : Le script précédent s’est arrêté Le nouveau Avant d’envoyer IdentificationRequest S’identifie Après l’avoir envoyé, avant la réponse Ne le renvoie pas. Il considère le ticket comme accepté, puisque la connexion est toujours ouverte, et avertit dans la Console. Avant qu’un personnage soit sélectionné Demande la liste et sélectionne Après la sélection, avant la fin du chargement Attend le chargement Après le chargement, avant que les réglages sociaux partent Envoie tout ce qui suit le chargement Après le début de l’envoi des réglages sociaux, avant ContextCreationRequest Saute les groupes sociaux et de guilde, puis envoie ContextCreationRequest et tout ce qui suit Après ContextCreationRequest N’envoie plus rien de ce qui suit le chargement. Les groupes que le script précédent n’a pas atteints sont sautés, pas envoyés en retard. Pendant le chargement d’une carte Annonce cette carte N’importe où après Se résout tout de suite avec la carte actuelle Après une reconnexion, session.connectionId est nouveau, l’état de connexion enregistré est ignoré, et tout recommence. La page du bot affiche ces clés dans Outils > Mémoire. Réagir à ce que base répondUn message va à chaque gestionnaire qui l’écoute, donc le fait que base réponde à un message ne le retire pas à votre paquet. Vos propres on() et wait() reçoivent chaque message auquel base répond, MapCurrentEvent compris, sur les deux sortes de bots. Ce script note chaque changement de carte, puis la cellule où arrive le personnage : import "@asterobot:base"; import { currentMap, botInfo, botWarn } from "asterobot:bot"; import { on, wait } from "asterobot:protocol"; export default async function behavior(launch) { on("MapCurrentEvent", async (traffic) => { const mapId = traffic.payload?.mapId; if (mapId === undefined) return; botInfo("Heading to map", mapId); try { // Son contenu arrive une fois la carte chargée, un instant après ceci. await wait( (next) => next.type === "MapComplementaryInformationEvent" && next.payload?.mapId === mapId, { timeout: 10000 }, ); botInfo("On map", mapId, "cell", currentMap()?.cellId); } catch (error) { // Une erreur qui s'échappe d'un gestionnaire arrête tout le script. botWarn("Never arrived on map", mapId, String(error)); } }); } Ne répondez pas à ce à quoi base répond. Sur un bot Full socket, envoyer vous-même ContextReadyRequest, MapInformationRequest, ou les réponses à la preuve d’identité et aux relances atteindrait le serveur deux fois. Tout le reste vous appartient : noter, enregistrer, consulter, planifier. Quand MapCurrentEvent arrive, la nouvelle carte n’est pas encore chargée. Base dit qu’il est prêt 0,4 à 2,4 s plus tard, et c’est seulement alors que le serveur envoie son contenu. D’ici là, currentMap() nomme déjà la nouvelle carte, mais sans acteurs ni cellId. Pour agir dessus, attendez son MapComplementaryInformationEvent, comme ci-dessus. Les identifiants sont des BigInt. Comparez-les avec des BigInt, comme mapId === 153879301n. findPath() prend un identifiant de carte en BigInt, et record() d’asterobot:gamedata non : passez-lui Number(mapId). Les cartes instanciées sont annoncées avec MapCurrentInstanceEvent au lieu de MapCurrentEvent. Écoutez les deux pour voir chaque changement de carte. Les gestionnaires de base passent avant les vôtres, parce que base se charge avant votre code. Rien n’en dépend. Sur un bot MITMSur un bot MITM, une personne joue le personnage avec son propre client Dofus, qui fait lui-même tout ce qui précède. Base n’envoie alors absolument rien : il ne répond ni à la preuve d’identité, ni aux relances, ni aux cartes, et n’envoie jamais de ping : le client s’en charge ; il ne s’identifie pas, puisque le client l’a déjà fait ; il prend le personnage déjà sélectionné, ou attend que la personne en sélectionne un ; connect() se résout dès que le personnage se trouve sur une carte, sans limite de temps. Il suit quand même la session, donc un paquet construit sur base lit les mêmes choses sur les deux sortes de bots et attend le monde de la même façon. Un paquet qui agit, qui marche ou qui combat, devrait vérifier lui-même session.shared, puisque la personne qui joue contrôle le même personnage. La personne qui joue est aussi protégée : le signalement de mod loader du client Dofus n’arrive jamais chez Ankama. Le client envoie un message, YoshiRequest, quand il trouve un mod loader dans son dossier d’installation (MelonLoader, BepInEx, dotnet, ou les DLL qu’un mod loader génère). Sur un bot MITM, Asterobot empêche ce message d’atteindre le serveur de jeu dès le début de la session, avant Play, avec ou sans base, et la Console du bot affiche Kept the game client's mod-loader report from the game server à chaque fois. Base n’a rien à faire pour cela, et un bot Full socket n’envoie jamais ce message. Tous les messages que base envoieMessage Quand uid Réglage IdentificationRequest Une fois par connexion aucun CharacterListRequest Après l’acceptation du ticket aucun CharacterSelectionRequest Après la liste aucun character CharacterForceSelectionReadyRequest CharacterForceSelectionEvent : un combat attend l’un des personnages du compte aucun ClientChallengeInitRequest ServerSessionReadyEvent oui ClientIdRequest ServerVerificationEvent oui ClientChallengeProofRequest ServerChallengeEvent oui SequenceNumberRequest SequenceNumberEvent aucun BasicLatencyStatsRequest BasicLatencyStatsEvent oui PingRequest Le battement de ping aucun ping Les cinq réglages sociaux Après le chargement aucun bringUp GuildInformationRequest ×3, GuildWeeklyNextResetRequest Après le chargement aucun bringUp ContextCreationRequest Après le chargement aucun BakApiTokenRequest Après le chargement, puis toutes les heures aucun bringUp GuildMemberWarnOnConnectionStopRequest Après le chargement aucun bringUp SetCommunityChannelRequest Après le chargement aucun bringUp, community QuestStepInformationRequest Après le chargement, deux fois pour chaque quête en cours aucun bringUp ObjectAveragePricesRequest Après le chargement, une fois par jour aucun bringUp AttackMonsterRequest L’étape fightMonsters, une fois sur la cellule du groupe aucun EmotePlayRequest L’étape rest : pour s’asseoir, puis pour se relever aucun ObjectUseRequest L’étape eat, et rest avec eat : une fois par nourriture mangée aucun ObjectSetPositionRequest Les étapes equip et unequip : une fois par objet déplacé aucun InventoryUpdateMergedInventoryTypeRequest, NpcGenericActionRequest L’étape sell : passer à l’onglet Vente de l’hôtel des ventes aucun ExchangeBidHouseSearchRequest Les étapes sell et buy : cesser de suivre et suivre chaque objet aucun ExchangeBidHousePriceRequest L’étape sell : les prix de chaque objet aucun ExchangeObjectMovePricedRequest L’étape sell : une fois par lot mis en vente aucun ExchangeBidHouseTypeRequest L’étape buy : le type de chaque objet aucun ExchangeBidHouseBuyRequest L’étape buy : une fois par lot acheté aucun NpcGenericActionRequest, NpcDialogReplyRequest Les étapes deposit et withdraw : parler au banquier, et choisir la réponse de la banque aucun ExchangeObjectMoveRequest Les étapes deposit et withdraw : une fois par pile déplacée aucun ExchangeMoveKamaRequest Les étapes deposit et withdraw : une fois, quand des kamas bougent aucun DialogLeaveRequest Les étapes sell, buy, deposit et withdraw : fermer la fenêtre aucun FriendListRequest, AcquaintanceListRequest, SpouseInformationRequest Après le chargement aucun bringUp AnomalySubareaInformationRequest Première carte de la connexion aucun ContextReadyRequest, MapInformationRequest Chaque MapCurrentEvent et MapCurrentInstanceEvent aucun « oui » signifie un uid qui lui est propre, comme le client l’envoie. GuildWeeklyNextResetRequest n’en a pas, bien que le serveur y réponde avec une Response : cette réponse revient elle aussi avec l’uid -1. Tous les messages que base litMessage Ce que base en fait AuthenticationTicketAcceptedEvent, AuthenticationTicketRefusedEvent Termine l’identification ServerSettingsEvent Garde community pour le réglage server ServerSessionReadyEvent, ServerVerificationEvent, ServerChallengeEvent Répond à la preuve d’identité QueueStatusEvent Écrit la position dans la file d’attente dans la Console CharacterListEvent Choisit le personnage CharacterForceSelectionEvent Le serveur a choisi le personnage qu’un combat attend : base lui répond et joue ce personnage QuestsEvent Garde les identifiants des quêtes en cours, dans activeQuests. Le serveur l’envoie pendant le chargement du personnage, sans qu’on le demande. CharacterSelectionEvent, CharacterLoadingCompleteEvent Terminent la sélection du personnage SequenceNumberEvent, BasicLatencyStatsEvent, PongEvent Maintien de la connexion BakApiKeyEvent Planifie le prochain jeton de boutique MapCurrentEvent Annonce la carte MapCurrentInstanceEvent Annonce une carte instanciée MapComplementaryInformationEvent Résout connect() ; après un combat, l’étape fightMonsters continue une fois qu’il est arrivé ContextCreationEvent L’étape fightMonsters : le combat que son attaque a lancé InteractiveUseEndedEvent L’étape gather : la fin d’une récolte EmotePlayEvent L’étape rest : la réponse du serveur quand le personnage s’assoit et se relève ExchangeBidBuyerStartedEvent Les étapes sell et buy : la fenêtre de l’hôtel des ventes s’est ouverte, avec ses conditions de vente ExchangeBidSellerStartedEvent L’étape sell : l’onglet Vente, avec les lots du compte en vente ExchangeBidPriceEvent L’étape sell : les prix d’un objet ExchangeBidHouseItemAddedEvent L’étape sell : un lot en vente ExchangeTypesExchangerDescriptionForUserEvent, ExchangeTypesItemsExchangerDescriptionForUserEvent L’étape buy : les objets d’un type en vente, et les annonces d’un objet ExchangeBidHouseInListUpdatedEvent, ExchangeBidHouseBuyResultEvent L’étape buy : les prix d’une annonce à mesure que les lots se vendent, et si un lot a été acheté NpcDialogQuestionEvent Les étapes deposit et withdraw : la réplique du banquier et ses réponses StorageInventoryContentEvent, StorageObjectUpdateEvent, StorageObjectRemovedEvent, StorageKamasUpdateEvent Les étapes deposit et withdraw : ce que contient la banque tant qu’elle est ouverte, et une pile ou des kamas déplacés ObjectDeletedEvent, ObjectQuantityEvent L’étape deposit : une pile mise en banque ExchangeLeaveEvent Les étapes sell, buy, deposit et withdraw : la fenêtre s’est fermée TrustStatusEvent Avertit quand la session n’est pas certifiée SystemMessageDisplayEvent Avertit quand le serveur met fin à la session FileCheckEvent Avertit : base n’y répond pas Lignes de console et erreursLes lignes que base écrit dans la Console du bot à propos de la connexion. Elles restent en anglais, telles que base les écrit. Les lignes propres à un plan sont dans Les lignes de console d’un plan. Niveau Ligne Signification info base: playing <name> Le serveur a sélectionné le personnage info base: in the world, on map <id> connect() s’est résolue info base: waiting in the server's queue, position <n> of <total> Le serveur est plein warn base: resuming after an identification whose answer was not seen Un script s’est arrêté entre le ticket et sa réponse. Voir Mémoire. warn base: the server does not certify this session (TrustStatusEvent) Bon à savoir ; base continue warn base: the server is ending the session (system message <id>) Le serveur s’apprête à déconnecter le bot warn base: the server asked to check <file>, which base does not answer FileCheckEvent. Il n’a jamais été vu dans une capture. warn base: couldn't send <type>: <reason> Un envoi a échoué. Base continue. warn base: <message> failed: <reason> Un gestionnaire a échoué. Base continue. error La raison avec laquelle connect() s’est rejetée Voir ci-dessous Ce avec quoi connect() et les étapes se rejettent : Erreur Cause base: the server refused this connection's ticket; sign the bot in again for a new one AuthenticationTicketRefusedEvent base: this account has no character on this server La liste est vide base: no character is called "<name>" on this server (there are: ...) Le réglage character ne nomme aucun d’entre eux base: the server refused to select <name> CharacterSelectionEvent portait une erreur base: the character's map never arrived within a minute of entering the world Aucun MapComplementaryInformationEvent dans les 60 s qui suivent les requêtes d’après le chargement protocol wait timed out Le serveur n’a pas répondu au ticket en 30 s, à la sélection en 30 s, ou au chargement en 60 s Délais et bruitDeux sortes de délais se trouvent dans une capture, et base les traite à part : Moins de 100 ms : les trames du client. Le client répond à sa trame suivante, donc une réponse arrive 6 à 55 ms après son événement, selon la charge du client. Le serveur le voit à travers l’aller-retour du réseau, 44 ms dans ces captures et plus variable que cela. Base les envoie directement et ne les imite pas. 100 ms et plus : le client qui charge, ou une personne. La seconde qui suit le chargement du personnage, le chargement de la carte, la pause avant de choisir un personnage. Ceux-là sont gardés. Ceux qui ont été mesurés reçoivent un bruit de ±15 % autour de leur valeur, puisqu’un client ne prend jamais deux fois le même temps. Ceux qu’une personne décide, comme regarder la liste des personnages, sont tirés uniformément entre deux bornes. Ce que le serveur voit exactement, c’est l’ordre des messages, puisque TCP le conserve. Base envoie donc dans l’ordre du client, qu’Asterobot conserve : il écrit les trames d’un script dans l’ordre où elles ont été envoyées. Toutes les attentes passent par sleep() d’asterobot:timers, et chacune est annulée quand le script s’arrête. Ce qu’il faut à base dans AsterobotLa mémoire du bot, memory d’asterobot:bot, et session.connectionId. send(..., { correlated: true }), pour les quatre requêtes qui portent un uid et ne reçoivent pas de réponse. asterobot:verification, pour la preuve d’identité. asterobot:movement avec changeMap(), useElement(), les voyages et les fonctions qui trouvent les cartes, que base réexporte à côté de move(). Un Asterobot qui ne les a pas ne peut pas charger base du tout, puisque l’import échoue. asterobot:character, l’état du personnage, pour les conditions sur l’état du jeu. Un Asterobot qui ne l’a pas ne peut pas non plus charger base. onStopRequest() d’asterobot:bot, pour qu’un plan se termine proprement quand une plage du planning se termine. asterobot:fight, les monsters des groupes de monstres dans currentMap(), record() d’asterobot:gamedata et distance() d’asterobot:pathfinding, pour combattre. Un Asterobot qui n’a pas asterobot:fight ne peut pas charger base. find() d’asterobot:gamedata, et les tables skills et maps__interactive_elements des données du jeu, pour récolter. items() d’asterobot:character, pour manger et l’équipement, et la vie qui suit la régénération, pour se reposer. Un Asterobot sans items() ne peut pas charger base. itemCount() d’asterobot:character, text() d’asterobot:gamedata et off() d’asterobot:protocol, pour l’hôtel des ventes, avec la compétence 355 du comptoir dans currentMap().elements. npc: true sur les acteurs de currentMap(), pour le banquier. Un Asterobot plus ancien ne les marque pas, et les étapes de la banque échouent avec there's no banker on this map. Les fichiers fightprofiles/ d’un paquet, pour les profils de combat. Un Asterobot plus ancien laisse ce dossier de côté et n’en liste aucun. Base n’a pas de asterobot.json, donc pas encore de compatibleVersion : une version de développement d’Asterobot se déclare en 0.0.0 et refuserait toute plage. Une fois qu’une version publiée aura ces fonctionnalités, le paquet publié la nommera. Différences connues avec le vrai clientLes canaux de discussion ne sont pas réglés. Un client dont les canaux ont été modifiés dans ses options envoie SubscribeMultipleChannelRequest après SetCommunityChannelRequest, avec l’ensemble complet : dans une capture, Ventes, Recherche, Arène, Communauté et Événement coupés, tous les autres canaux actifs. Une autre capture, d’un client laissé tel quel, n’en a aucun. Les options d’un bot sont celles par défaut, donc base n’en envoie aucun. Les étapes de quête ne sont demandées qu’à l’entrée dans le monde. Le client les redemande aussi quand l’étape d’une quête change. Base ne suit plus les quêtes ensuite. Les prix moyens sont demandés une fois par jour UTC, et Asterobot oublie qu’il les a demandés quand il redémarre. Le client s’en souvient d’un redémarrage à l’autre. FileCheckEvent ne reçoit pas de réponse. Il n’a jamais été vu dans une capture. Le rythme des pings est statistique : environ la moitié des battements de 5 secondes, là où un client envoie un ping sur les battements où une personne l’a vraiment touché. La fenêtre de l’hôtel des ventes se répète moins. Le client choisit un objet dès que l’onglet Vente s’ouvre, puis encore une demi-seconde plus tard, en cessant de suivre puis en suivant le même objet à chaque fois. Base le choisit une fois, et ne cesse jamais de suivre l’objet qu’elle va suivre. En ouvrant un hôtel des ventes qu’il a déjà parcouru, le client redemande le dernier type qu’il montrait, avec deux ExchangeBidHouseTypeRequest ; base commence sans type. La banque déplace une pile à la fois. Les boutons du client qui déplacent plusieurs piles à la fois ne servent pas : Asterobot ne lit pas encore la réponse du serveur. Le dialogue d’un PNJ sans banque reste ouvert. Quitter le dialogue d’un PNJ n’est pas encore dans une capture, donc quand le PNJ le plus proche n’est pas un banquier, les étapes de la banque échouent et laissent son dialogue ouvert. Une seule version de base par script. Deux paquets qui fixent des versions différentes de base les chargent toutes les deux, et les deux répondent au serveur. Fixez la même version partout. Améliorer base à partir d’une nouvelle captureLe premier rôle de base est tout ce que le client Dofus fait de lui-même, pour qu’aucun script qui l’utilise n’ait jamais à penser aux pings, aux contrôles, aux relances ou aux cartes. Chaque nouvelle capture peut l’enrichir, en suivant les mêmes règles : Seulement ce que le client fait toujours : une réponse qu’il envoie à chaque fois à un message du serveur, ou un message qu’il envoie de lui-même à un moment fixe. Ce qu’une personne décide, comme marcher, combattre ou discuter, n’en fait pas partie : c’est le rôle des plans et des fonctions de gameplay. Seulement sur un bot Full socket. Sur un bot MITM, le client Dofus le fait déjà, donc chaque gestionnaire rend la main tout de suite quand session.shared est vrai. Enregistré dans connect(), avant l’identification, puisque le serveur peut parler juste après avoir accepté le ticket. L’ordre du client, pas ses trames. Sous 100 ms, un délai est la fréquence de trame du client : envoyez directement. Envoyez dans l’ordre du client : Asterobot écrit les trames d’un script dans l’ordre où elles ont été envoyées. À partir de 100 ms, un délai est le client qui charge ou une personne qui décide : gardez-le. Utilisez une valeur mesurée avec ±15 % de bruit, ou un tirage uniforme entre deux bornes quand les captures montrent une plage. Ce qui doit survivre à un redémarrage va dans l’état de la connexion (lib/state.js). Quand envoyer deux fois serait une erreur, enregistrez le fait avant d’envoyer. Rien ne s’échappe d’un gestionnaire. Utilisez guarded() et sendQuietly() : une erreur qui s’échappe d’un gestionnaire arrête tout le script, celui de base comme celui qui l’utilise. La preuve va dans ce README, à côté du comportement qu’elle prouve : l’extrait de capture, avec ses heures et ses uids. Fichiersbase/ ├── README.md le README, en anglais ├── README.fr.md ce fichier ├── changelog.md ce qui a changé à chaque version ├── index.js exports, réglages, les primitives du runner, démarrage automatique et fonction d'entrée ├── fightprofiles/ les profils de combat que base fournit, un fichier JSON chacun └── lib/ ├── connect.js connect() : tout, dans l'ordre ├── identify.js l'identification ├── character.js la sélection du personnage ├── world.js l'entrée dans le monde, le suivi des cartes, le jeton de boutique ├── verification.js la preuve d'identité ├── keepalive.js les deux relances, latency(), les pings ├── watch.js enregistre les étapes clés de la session, avertissements de console ├── state.js l'état de la connexion dans la mémoire du bot ├── send.js envoyer depuis les gestionnaires sans arrêter le script ├── timing.js délais mesurés avec bruit ├── conditions.js les conditions sur l'état du jeu ├── fight.js combattre : les étapes et les conditions de combat ├── gather.js récolter : l'étape et la condition de récolte ├── recover.js se reposer et manger : les deux étapes ├── equip.js l'équipement : les deux étapes et la condition ├── market.js l'hôtel des ventes : vendre et acheter, les deux étapes ├── bank.js la banque : mettre en banque et retirer, les deux étapes ├── items.js ce que l'hôtel des ventes et la banque partagent sur les objets └── studio/ le runner, qui joue les plans ├── runner.js run() et stop(), avec les primitives que lui passe index.js ├── plan.js plan(), et la façon dont les lignes nomment les blocs ├── check.js la vérification d'un plan avant de bouger ├── args.js les règles des arguments ├── call.js l'étape ou la condition d'un paquet : arguments, réglages, contexte ├── circuit.js tours, arrêts, cartes, étapes et interruptions ├── steps.js les étapes ├── conditions.js les conditions ├── places.js les cartes d'un arrêt, de la plus proche à la plus lointaine ├── waiting.js les cartes où il n'y a rien à faire pour l'instant └── failures.js onFailure Le runner reçoit tout ce qu’il lui faut d’Asterobot en arguments, donc ses tests, dans test/base à côté de ce dossier, le font tourner sous Node avec des imitations : node --test test/base. Ils sont en dehors du paquet parce qu’Asterobot installe chacun de ses fichiers .js.
    Gratuit
  5. Smooth a posté un record dans Notes de version
    0.2.3 - 01/10/2026✨ AjoutéNouveau module de script asterobot:verification : commitment(), publicKey() et proof() calculent les réponses à la preuve d'identité que le serveur de jeu demande pendant la session. Les secrets restent dans Asterobot : le script ne voit que ce qui part sur le réseau. Le calcul est vérifié sur des réponses du vrai client Dofus tirées de captures, avec et sans valeur de défi. La clé d'un bot ne change pas tant qu'Asterobot tourne : elle survit à Stop, Play et aux reconnexions, comme celle du client Dofus pendant toute sa session. Mémoire partagée d'un bot : memory (get(), set(), delete(), keys()) dans asterobot:bot. Tous les scripts du bot y lisent et y écrivent des données simples, qui survivent à Stop, Play, Reload et aux reconnexions, jusqu'au redémarrage d'Asterobot. Onglet Outils > Mémoire sur la page d'un bot dans Asteroboard : la mémoire du bot clé par clé, mise à jour dès qu'un script la modifie. session.connectionId : le numéro de la connexion de jeu du bot, pour savoir si ce qu'un script a gardé en mémoire concerne la connexion actuelle. Option correlated de send() : le message part avec un uid, comme le client Dofus envoie ses réponses à la preuve d'identité et BasicLatencyStatsRequest, sans attendre de réponse. Protocole : 33 nouveaux noms lisibles. Quatorze messages : MapCurrentEvent, QuestsEvent, QuestObjectiveUpdatedEvent, AchievementsEvent, ArenaUpdatePlayerInformationEvent, ContextCreationEvent, ContextDestroyEvent, JobDescriptionEvent, JobExperiencesUpdateEvent, MapFightCountEvent, NpcsMapQuestStatusUpdateEvent, ShowChallengeEvent, StorageKamasUpdateEvent et TagStoragesRefreshEvent. Dix-neuf types qu'ils contiennent : AchievedAchievement, ArenaRank, Collect, Completion, Craft, Details, FightCommonInformation, JobDescription, JobExperience, LeagueRanking, MapNpcQuest, NpcWithQuest, QuestFinished, QuestObjective, SkillAction, TagInformation, TagObjectData, TagStorageData et Timed. Documentation : nouvelle page sur la mémoire du bot, page de référence asterobot:verification, et page @asterobot:base réécrite pour le paquet officiel qui fait à la place des scripts tout ce que le client Dofus fait seul. Les pages sur Play et Stop, les raisons de lancement, l'envoi et le trafic couvrent la mémoire, session.connectionId, la numérotation des uid et l'ordre des envois. Outil sniffer (pour le développement) : copie d'une valeur ou d'un message entier, et export des messages marqués en texte, JSON, tableau ou hexadécimal, vers un fichier ou le presse-papiers. La recherche trouve aussi les champs et les valeurs décodés, et les filtres proto: et res: sont nouveaux. 🔄 ModifiéMise à jour des dépendances. 🐛 CorrigéLa connexion de jeu d'un bot Full socket continue de compter les uid là où la connexion au serveur de connexion s'est arrêtée, comme le client Dofus : sa première requête numérotée porte #2 et non plus #0. Au changement de carte, currentMap(), la marche du bot et la carte de l'onglet Carte d'Asteroboard passent à la nouvelle carte dès que le serveur la nomme (MapCurrentEvent), au lieu de garder l'ancienne jusqu'à 2,4 s. Un move() demandé avant que la carte soit chargée est refusé au lieu d'être tracé sur la carte quittée. Pour un bot dont la carte en direct était déjà activée, réactivez-la une fois : elle reçoit maintenant aussi MapCurrentEvent.
  6. Smooth a posté un record dans Notes de version Asteroboard
    0.2.4 - 01/10/2026✨ AjoutéInterface en portugais du Brésil, à choisir dans Préférences > Langue. Les données du jeu s'affichent alors en portugais. Bot > Outils > Mémoire : la mémoire partagée du bot. Chaque clé que ses scripts y ont rangée, avec sa valeur et sa dernière modification. En lecture seule, et rafraîchie dès qu'un script la modifie. 🔄 ModifiéLes nombres s'accordent : « 1 joueur », « 3 joueurs » au lieu de « joueur(s) », dans toutes les langues. En français et en portugais, 0 prend le singulier (« 0 joueur »). Les libellés qui nomment un élément du jeu reprennent les termes de DOFUS dans chaque langue : Havre-Sac, emplacements d'équipement (« Dofus ou Trophée »), catégories de l'inventaire, Zaap, Donjon… Traductions relues : typographie française (« guillemets », « 3 h 12 min »), « Temps de fonctionnement » au lieu de « Disponibilité », « cellules » au lieu de « cases », « Monocompte ». L'anglais passe à l'orthographe américaine. Résumé des réglages d'un comportement : « Réglages : 3 · Actions : 2 ». Mise à jour des dépendances : Nuxt UI 4.11.2, Nuxt Content 3.16.1, Vue 3.5.43, Monaco Editor 0.57. 🐛 CorrigéCarte du bot : la carte quittée s'efface dès que le personnage change de carte, sans attendre que le jeu ait chargé la nouvelle. Un clic pendant le changement ne vise plus l'ancienne carte.
  7. Smooth a posté un record dans Notes de version Asteroboard
    0.2.3 - 24/09/2026✨ AjoutéRéglage de la largeur du contenu dans Préférences > Apparence : Étroit, Moyen, Large ou Plein. Il s’applique à toutes les pages et est mémorisé. À propos : les versions d’Asteroboard et d’Asterobot, l’adresse du serveur et son état de connexion, et la version de DOFUS testée. 🔄 ModifiéToutes les pages font la même largeur et occupent tout l’écran. Avant, chaque page fixait la sienne. À propos : page réécrite. Le bouton de version dans la barre latérale ouvre le changelog d’Asteroboard. ❌ SuppriméÀ propos : les trois grandes cartes Aide, Discord et Statut. À propos : la ligne « Asteroboard version X », remplacée par la section Versions.
  8. Smooth a posté a record dans Assistants IA
    Désactiver le point d'accès des assistants, cacher des actions aux assistants ou autoriser celles qui le sont, plafonner la taille des réponses, et lire ce qu'Asterobot dit quand quelque chose ne va pas. Read this page in English Cinq réglages sur Serveur > Config contrôlent ce que reçoivent les assistants. Tous sont lus au démarrage d'Asterobot. Clé Type Valeur par défaut Redémarrage nécessaire mcp.enabled Activé ou désactivé Activé Oui mcp.tags Liste de textes Vide Oui mcp.denyOperations Liste de textes Vide Oui mcp.allowOperations Liste de textes Vide Oui mcp.maxResponseBytes Nombre 100000 Oui mcp.enabled Activé ou désactivé. Par défaut : activé. Redémarrage nécessaire. Désactivez-le et plus rien ne répond sur /mcp : l'adresse n'existe pas, et la console d'Asterobot affiche MCP disabled by configuration au démarrage. Tout le reste d'Asterobot est inchangé. mcp.tags Liste de textes. Par défaut : vide. Redémarrage nécessaire. Ne proposer que les actions de certaines parties d'Asterobot. Chaque entrée est le nom d'un groupe d'actions, et un assistant voit une action quand elle appartient à au moins un groupe que vous avez listé. Les groupes sont AccountManager, BotManager, Config, Events, Galaxy, GameData, GameManager, Library, Logger, ProxyManager, Runtime et Website ; la casse n'a pas d'importance. Vide veut dire tous les groupes. Par exemple, ["GameData", "Library"] donne à un assistant le navigateur de données de jeu et vos paquets, et rien qui touche aux comptes ou aux bots. mcp.denyOperations Liste de textes. Par défaut : vide. Redémarrage nécessaire. Les actions jamais proposées à un assistant, en plus des deux cachées par défaut. Chaque entrée est le nom d'une action tel que l'assistant le verrait, dans n'importe quelle graphie : post-bot-connect et PostBotConnect sont la même action. Demandez à l'assistant de lister ses outils pour obtenir les noms, ou prenez ceux de Ce qu'un assistant peut faire. Important Une entrée qui ne nomme aucune action désactive tout le point d'accès, volontairement : vous vouliez cacher quelque chose, et ce ne serait pas le cas. La console d'Asterobot affiche alors MCP disabled avec l'entrée dans la raison, et les appels à /mcp répondent avec la même raison. Corrigez l'orthographe et redémarrez. mcp.allowOperations Liste de textes. Par défaut : vide. Redémarrage nécessaire. Les deux actions cachées par défaut, delete-runtime (arrêter Asterobot) et delete-config (remettre chaque réglage à sa valeur par défaut), proposées malgré tout. Mêmes règles de graphie que mcp.denyOperations. Une entrée qui ne nomme aucune action est ignorée, avec un avertissement dans la console. mcp.maxResponseBytes Nombre. Par défaut : 100000. Redémarrage nécessaire. La plus grande réponse qu'un outil renvoie entière, en octets. Au-delà, des entrées sont retirées à la fin de la plus grande liste de la réponse, et l'assistant apprend comment restreindre sa requête. La valeur par défaut correspond à peu près à ce que les assistants affichent d'un bloc ; augmentez-la si le vôtre en supporte plus, baissez-la pour garder des réponses courtes. Quand quelque chose ne va pas Asterobot ne refuse jamais de démarrer à cause du point d'accès des assistants. Ce qui peut arriver à la place s'affiche dans la console et le terminal, sur Serveur > Console : Ligne Ce qu'elle veut dire Que faire MCP ready Tout fonctionne. skipped=0 est l'état normal. Rien. MCP skipped an operation avec operation= et err= Une action n'a pas pu être proposée, les autres le sont. Les instructions de l'assistant la nomment aussi. Cela ne devrait pas arriver avec un Asterobot publié. Signalez-le avec la ligne. MCP disabled avec err= Rien ne fonctionne. La raison est un réglage, presque toujours une entrée de mcp.denyOperations qui ne nomme aucune action. Corrigez le réglage et redémarrez. The MCP is reachable from the network without authentication Asterobot écoute sur une adresse réseau sans authentification basique. Pas une erreur, un rappel. Définissez server.basicAuth ou écoutez sur 127.0.0.1, comme l'explique Sécurité. Tant que le point d'accès est désactivé par un réglage, un assistant qui l'appelle reçoit une erreur dont le message commence par MCP unavailable: et donne la même raison que la console. La liste complète des réglages est dans la Référence des réglages.
  9. Comment les appels d'outils d'un assistant atteignent Asterobot, comment il suit ce qui se passe par les événements et le journal, et où les réponses sont coupées. Read this page in English Ce n'est pas vous qui appelez les outils, c'est l'assistant. Savoir comment ils se comportent aide à lire ce qu'il rapporte et à comprendre pourquoi il redemande parfois quelque chose. Comment un appel atteint Asterobot Un appel d'outil est la même requête qu'Asteroboard enverrait, vérifiée de la même façon. Quand l'assistant se trompe dans un argument, la réponse lui dit ce qui ne va pas, avec les mêmes mots que les erreurs d'Asteroboard, et il corrige son appel. Quand Asterobot refuse quelque chose, l'assistant lit le refus : un appel pour déplacer un bot qui n'existe pas répond 404 Not Found: bot not found. Les arguments d'un outil sont les champs que vous rempliriez dans Asteroboard : le nom du bot, la clé du réglage, le message à envoyer. Pour deux actions, envoyer un message sur la connexion d'un bot et copier un paquet, un champ porte le même nom que le bot, et l'assistant passe alors tout le formulaire dans un seul argument body. Il peut demander à Asterobot la forme exacte de n'importe quelle action avec l'outil describe-operation, qui répond aussi pour les actions cachées, en disant pourquoi elles le sont. Trois outils bien à lui En plus des actions d'Asterobot, l'assistant reçoit trois outils dont Asteroboard n'a pas besoin : Outil Ce qu'il fait describe-operation Chaque argument et la forme des réponses d'une action, quand la courte description ne suffit pas. watch-events Les événements qu'Asterobot publie : un bot qui change d'état, un message sur sa connexion, un téléchargement qui avance, un réglage qui change. Chaque appel renvoie les événements après une position, attend jusqu'à 30 secondes le premier quand il n'y en a pas encore, et répond avec la position d'où continuer. read-logs Le journal d'Asterobot lui-même, celui de Serveur > Console, à partir d'un niveau choisi. Asterobot garde les 500 derniers événements de chaque sorte. Quand un assistant revient après qu'autant sont passés, watch-events lui dit combien il en a manqué, pour qu'il regarde à nouveau plutôt que de supposer qu'il ne s'est rien passé. Les actions qui se terminent plus tard Connecter un bot, lancer un paquet ou envoyer un message répond tout de suite ; ce qui en résulte arrive plus tard sous forme d'événement. La réponse à un tel appel nomme l'événement à surveiller et la position d'où le surveiller, pour que l'assistant suive le résultat sans deviner. C'est pourquoi un assistant à qui l'on demande de connecter un bot rapporte en général la connexion un peu plus tard, une fois qu'il a vu l'état du bot changer. Les grandes réponses Une réponse plus grande que mcp.maxResponseBytes, 100 000 octets par défaut, est réduite pour rester lisible : des entrées entières sont retirées à la fin de la plus grande liste qu'elle contient, et l'assistant apprend combien, et quel argument restreint cette action quand elle en a un. Quand il n'y a rien à retirer, la réponse est une erreur qui demande à l'assistant de restreindre sa requête. Les deux réponses concernées en pratique sont le catalogue des messages du jeu, qui prend un argument name pour ne renvoyer qu'un message, et les requêtes SQL, où LIMIT fait l'affaire. Les actions cachées Deux actions sont cachées à tous les assistants sauf si vous les autorisez : arrêter Asterobot et remettre tous les réglages à zéro. Trois ne sont jamais des outils, parce qu'un assistant n'en a pas l'usage : le flux d'événements en direct qu'utilise Asteroboard (l'assistant a watch-events à la place), et l'export ou l'import d'un paquet sous forme de zip. Réglages MCP explique comment cacher d'autres actions, ou autoriser celles qui le sont. Le protocole du jeu L'assistant peut lister chaque message du jeu qu'un bot peut envoyer, avec chacun de ses champs, par le même catalogue qu'utilise l'onglet d'envoi de messages d'un bot, et en envoyer un sur la connexion d'un bot. Asterobot vérifie le message contre la définition du jeu lui-même avant qu'il parte, si bien qu'un champ erroné n'atteint jamais le jeu. Le catalogue donne la forme d'un message, pas son rôle : la référence du protocole Dofus l'a, et vous pouvez indiquer une de ses pages à l'assistant. Ce que l'assistant peut envoyer est ce qu'un script peut envoyer ; voir Envoyer des messages. Suite : Réglages MCP.
  10. Donner à un assistant IA l'adresse de votre Asterobot, avec un mot de passe quand vous en avez défini un, et vérifier qu'il voit les outils. Read this page in English Connecter un assistant, c'est lui donner une adresse. Asterobot n'a besoin d'aucun réglage pour cela : le point d'accès est actif dès qu'Asterobot démarre. L'adresse Le point d'accès est /mcp sur le port d'Asterobot, celui de server.listenAddress : Votre installation Adresse Valeurs par défaut http://127.0.0.1:8080/mcp Un autre port, par exemple 127.0.0.1:9000 http://127.0.0.1:9000/mcp server.tls activé https://127.0.0.1:8080/mcp L'assistant doit tourner sur le même ordinateur qu'Asterobot, ou l'atteindre comme Asteroboard le fait depuis un autre appareil. Un assistant qui tourne sur un site web plutôt que sur votre ordinateur ne peut pas atteindre 127.0.0.1 ; voir Depuis une autre machine. Claude Code Dans un terminal, lancez : claude mcp add --transport http asterobot http://127.0.0.1:8080/mcp Puis démarrez une conversation et posez-lui une question sur Asterobot, par exemple « liste mes bots ». La première fois qu'il utilise un outil qui modifie quelque chose, il vous demande la permission. claude mcp list affiche les serveurs qu'il connaît et s'il les atteint. Autres assistants Tout assistant qui prend en charge MCP en HTTP prend la même adresse, appelée l'URL du serveur. Beaucoup la lisent dans un fichier JSON avec une entrée de ce genre, sous un nom que vous choisissez : { "mcpServers": { "asterobot": { "url": "http://127.0.0.1:8080/mcp" } } } Le fichier exact et sa forme dépendent de l'assistant : cherchez « MCP » et « HTTP » dans sa documentation. Deux choses à savoir quand il pose la question : Le transport est HTTP, parfois appelé streamable HTTP. Ce n'est ni l'ancien transport SSE, ni une commande à exécuter. Asterobot ne garde aucune session entre deux appels. Les assistants conçus pour la version actuelle du protocole fonctionnent ; un assistant très ancien qui exige un identifiant de session du serveur ne se connectera pas. Avec un mot de passe Quand server.basicAuth contient un identifiant et un mot de passe, /mcp les exige aussi, sous la forme d'un en-tête Authorization. Sa valeur est Basic suivi de l'identifiant, deux-points et le mot de passe, encodés en base64. Pour encoder alice:s3cret : echo -n 'alice:s3cret' | base64 [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("alice:s3cret")) Donnez ensuite l'en-tête à l'assistant. Avec Claude Code : claude mcp add --transport http asterobot http://127.0.0.1:8080/mcp --header "Authorization: Basic YWxpY2U6czNjcmV0" Les autres assistants ont une entrée headers à côté de l'URL dans leur fichier de configuration. Depuis une autre machine Un assistant sur un autre ordinateur atteint Asterobot comme Asteroboard le fait depuis un autre appareil, et avec les mêmes précautions : Asterobot sur une machine distante et Sécurité s'appliquent mot pour mot. Un tunnel SSH est le plus simple : il garde Asterobot sur 127.0.0.1, et l'adresse reste http://127.0.0.1:8080/mcp sur la machine où tourne l'assistant. Quand Asterobot écoute sur une adresse réseau sans authentification basique, sa console le dit au démarrage : The MCP is reachable from the network without authentication: set server.basicAuth or listen on loopback C'est la même exposition qu'Asteroboard a déjà dans cette installation, dite une fois à voix haute. Vérifier que ça fonctionne Au démarrage d'Asterobot, sa console et son terminal affichent une ligne sur le point d'accès : Ligne Ce qu'elle veut dire MCP ready avec des compteurs tools=, denied= et skipped= Le point d'accès fonctionne. denied compte les actions cachées, skipped celles qu'Asterobot n'a pas pu proposer. MCP disabled by configuration mcp.enabled est désactivé. Rien ne répond sur /mcp. MCP disabled avec un err= Le point d'accès répond à chaque appel par une erreur qui en donne la raison. Voir Réglages MCP. Demandez ensuite à l'assistant de vérifier qu'Asterobot répond : il a un outil pour cela, get-runtime-ping, dont la réponse est pong. S'il dit qu'il ne peut pas atteindre le serveur, vérifiez l'adresse et le port, et qu'Asterobot tourne. Suite : Outils, événements et limites.
  11. Smooth a posté a record dans Assistants IA
    Laisser un assistant IA comme Claude Code piloter votre Asterobot via MCP, en sachant ce qu'il peut faire, ce qu'il ne peut pas faire, et ce qu'il faut garder en tête avant de le laisser agir. Read this page in English Asterobot peut être piloté par un assistant IA, comme vous le pilotez depuis Asteroboard. L'assistant se connecte à Asterobot, voit tout ce qu'Asteroboard sait faire sous la forme d'une liste d'outils, et les appelle pendant que vous discutez avec lui : « ajoute un bot sur mon deuxième compte et connecte-le », « quelles tables contiennent les monstres et ce qu'ils lâchent », « écris un script qui salue la guilde quand le bot se connecte ». Ce chapitre explique ce qu'un assistant reçoit, comment en connecter un, et à quoi faire attention. Ce qu'est MCP MCP, pour Model Context Protocol, est la façon standard dont les assistants parlent aux programmes. Asterobot le parle à l'adresse /mcp, juste à côté de l'adresse qu'utilise Asteroboard. Tout assistant qui prend en charge MCP en HTTP peut se connecter : Claude Code, Codex, Cursor, un assistant de bureau ou un programme à vous. Rien dans Asterobot n'est lié à l'un d'eux, et il n'y a rien à installer côté Asterobot : c'est actif dès qu'Asterobot démarre. Ce qu'un assistant peut faire Asteroboard fait tout par le port d'Asterobot, et un assistant reçoit le même port. Il peut donc faire ce que vous faites sur chaque page d'Asteroboard : Comptes Ankama : en ajouter un, le connecter automatiquement ou à la main, répondre au code qu'Ankama envoie, rafraîchir ou vérifier une connexion, le déconnecter. Bots : ajouter, modifier et supprimer des bots, les connecter et les déconnecter, répondre à une question de connexion, déplacer le personnage sur sa carte, lire la console d'un bot, choisir les messages que le bot reflète, et envoyer un message sur sa connexion. Paquets : installer un paquet du marché quand vous lui donnez l'identifiant du fichier, créer un paquet, modifier son script et son manifeste, le charger, le lancer, l'arrêter et le recharger, lire et modifier ses réglages, cliquer ses actions, le mettre à jour, le supprimer. Jeu : lister, télécharger, extraire, vérifier et désinstaller des versions de jeu, parcourir les tables de données de jeu, chercher des textes et des noms, et lancer des requêtes SQL. Tout le reste : lire et modifier les réglages, gérer les proxys, connecter et déconnecter Asterobot d'asterobot.net, vérifier qu'Asterobot répond, et suivre ce qui se passe par les événements et le journal. Chaque outil est une action d'Asterobot, nommée d'après ce qu'elle fait : get-bots liste vos bots, post-bot-connect en connecte un, put-config-key modifie un réglage. Il y en a une centaine. L'assistant lit avec chacun une courte description écrite pour lui, vous n'avez donc pas à lui expliquer Asterobot. Quand il lance quelque chose qui prend du temps, comme connecter un bot, la réponse lui dit quel événement attendre, et il peut suivre cet événement avec un outil prévu pour ça. Outils, événements et limites donne les détails. Ce qu'il ne peut pas faire Arrêter Asterobot ou remettre tous les réglages à zéro. Ces deux actions sont cachées aux assistants, sauf si vous les autorisez dans les réglages. Voir Réglages MCP. Exporter ou importer un paquet sous forme de zip. Il faut pour cela un fichier sur votre ordinateur, ce qu'un assistant ne manipule pas. Installer depuis le marché, et créer ou modifier un paquet sur place, fonctionnent. Parcourir le marché. Il peut installer un paquet quand vous lui donnez l'identifiant ou l'adresse du fichier sur asterobot.net, mais il n'a aucun moyen de chercher dans le marché lui-même. Plus qu'un script. Un assistant écrit et lance les mêmes scripts que vous écririez : ils peuvent se déplacer sur une carte et parler au jeu, et ils rencontrent les mêmes limites, décrites dans Ce que les scripts peuvent faire. Ce qu'Asterobot refuse. Quand votre compte asterobot.net ne donne pas à Asterobot l'accès au protocole du jeu, les outils qui en ont besoin répondent que le moteur de jeu n'est pas disponible, exactement comme Asteroboard l'afficherait. Note Bientôt disponible. L'assistant ne peut pas encore lire cette documentation à travers Asterobot. En attendant, il travaille à partir de ce que chaque outil dit de lui-même, de la référence des scripts qu'il peut récupérer, et de ce qu'il sait déjà de Dofus. Vous pouvez coller une page de cette documentation dans la conversation quand il en a besoin. À garder en tête Un assistant agit en votre nom. Tout ce que vous pouvez faire depuis Asteroboard, il peut le faire, sur vos comptes Ankama et avec votre compte asterobot.net. Sécurité liste ce que cela couvre. La plupart des assistants demandent avant de modifier quelque chose. Chaque outil dit s'il se contente de lire. Des assistants comme Claude Code demandent votre permission avant un outil qui modifie quelque chose, et vous laissent autoriser un outil une fois ou pour de bon. Regardez ce qu'un outil s'apprête à faire avant de l'autoriser, comme vous liriez une boîte de dialogue avant de cliquer sur OK. Il lit ce que d'autres écrivent. La console et le trafic d'un bot contiennent les messages de chat des autres joueurs. Un assistant qui les lit pourrait prendre un message écrit pour le piéger pour une instruction. Asterobot ne filtre pas cela. Gardez les demandes de permission actives pour les outils qui modifient des choses, et ne laissez pas un assistant tourner sans surveillance sur un bot dans un chat animé. Les secrets restent cachés. Les réponses des outils ne contiennent jamais website.token ni vos mots de passe d'authentification basique, même par les outils de réglages : ces valeurs sont remplacées par [redacted]. Suite : Connecter un assistant.
  12. Smooth a posté un record dans AI Assistants
    Turn the assistant endpoint off, hide actions from assistants or allow the hidden ones, cap the size of answers, and read what Asterobot says when something is wrong. Five settings on Server > Config control what assistants get. All of them are read when Asterobot starts. Key Type Default Restart needed mcp.enabled On or off On Yes mcp.tags List of text Empty Yes mcp.denyOperations List of text Empty Yes mcp.allowOperations List of text Empty Yes mcp.maxResponseBytes Number 100000 Yes mcp.enabled On or off. Default on. Restart needed. Turn it off and nothing answers at /mcp: the address doesn't exist, and Asterobot's console says MCP disabled by configuration at start. Everything else in Asterobot is unchanged. mcp.tags List of text. Default empty. Restart needed. Offer only the actions of some parts of Asterobot. Each entry is the name of a group of actions, and an assistant sees an action when it belongs to at least one group you listed. The groups are AccountManager, BotManager, Config, Events, Galaxy, GameData, GameManager, Library, Logger, ProxyManager, Runtime and Website; the case doesn't matter. Empty means every group. For example, ["GameData", "Library"] gives an assistant the game data browser and your packages, and nothing that touches accounts or bots. mcp.denyOperations List of text. Default empty. Restart needed. Actions never offered to an assistant, on top of the two hidden by default. Each entry is the name of an action as the assistant would see it, in any spelling: post-bot-connect and PostBotConnect are the same action. Ask the assistant to list its tools to get the names, or use the ones from What an assistant can do. Important An entry that names no action turns the whole endpoint off, on purpose: you wanted something hidden, and it wouldn't be. Asterobot's console then shows MCP disabled with the entry in the reason, and calls to /mcp answer with the same reason. Fix the spelling and restart. mcp.allowOperations List of text. Default empty. Restart needed. The two actions hidden by default, delete-runtime (shut Asterobot down) and delete-config (reset every setting to its default), offered anyway. Same spelling rules as mcp.denyOperations. An entry that names no action is ignored, with a warning in the console. mcp.maxResponseBytes Number. Default 100000. Restart needed. The largest answer a tool returns whole, in bytes. Above it, entries are dropped from the end of the answer's largest list and the assistant is told how to narrow its request. The default is about the size assistants show in one piece; raise it if yours handles more, lower it to keep answers short. When something is wrong Asterobot never fails to start because of the assistant endpoint. What can happen instead shows in the console and terminal, on Server > Console: Line What it means What to do MCP ready Everything serves. skipped=0 is the normal state. Nothing. MCP skipped an operation with operation= and err= One action couldn't be offered, and the others are. The assistant's instructions name it too. This shouldn't happen with a released Asterobot. Report it with the line. MCP disabled with err= Nothing serves. The reason is a setting, almost always an entry of mcp.denyOperations that names no action. Fix the setting and restart. The MCP is reachable from the network without authentication Asterobot listens on a network address with no basic auth. Not an error, a reminder. Set server.basicAuth or listen on 127.0.0.1, as Security explains. While the endpoint is disabled by a setting, an assistant that calls it gets an error whose message starts with MCP unavailable: and gives the same reason as the console. The whole list of settings is in the Settings reference.
  13. How an assistant's tool calls reach Asterobot, how it follows what happens through events and the log, and where the answers are cut. You don't call the tools yourself, the assistant does. Knowing how they behave helps you read what it reports and understand why it sometimes asks for more. How a call reaches Asterobot A tool call is the same request Asteroboard would send, checked the same way. When the assistant gets an argument wrong, the answer tells it what's wrong, in the same words Asteroboard's own errors use, and it fixes its call. When Asterobot refuses something, the assistant reads the refusal: a call to move a bot that doesn't exist answers 404 Not Found: bot not found. The arguments of a tool are the fields you'd fill in Asteroboard: the bot's name, the setting's key, the message to send. For two actions, sending a message on a bot's socket and copying a package, a field has the same name as the bot, so the assistant passes the whole form as one body argument. It can ask Asterobot for the exact shape of any action with the describe-operation tool, which also answers for the hidden actions, saying why they're hidden. Three tools of their own Besides the actions of Asterobot, the assistant gets three tools that Asteroboard has no need for: Tool What it does describe-operation Every argument and the shape of the answers of one action, for when the short description isn't enough. watch-events The events Asterobot publishes: a bot changing state, a message on its socket, a download progressing, a setting changing. Each call returns the events after a position, waits up to 30 seconds for the first one when there's none yet, and answers with the position to continue from. read-logs Asterobot's own log, the one on Server > Console, from a chosen level up. Asterobot keeps the last 500 events of each kind. When an assistant comes back after that many have passed, watch-events tells it how many it missed, so it can look again rather than assume nothing happened. Actions that finish later Connecting a bot, playing a package or sending a message answers right away; what came of it arrives as an event later. The answer to such a call names the event to watch and the position to watch from, so the assistant can follow the outcome without guessing. That's why an assistant told to connect a bot usually reports the connection a little later, once it has seen the bot's state change. Big answers An answer larger than mcp.maxResponseBytes, 100,000 bytes by default, is cut down so it stays readable: whole entries are dropped from the end of the largest list in it, and the assistant is told how many, and which argument narrows that action when it has one. When there's nothing to drop, the answer is an error asking the assistant to narrow its request. The two answers that hit that in practice are the catalogue of game messages, which takes a name argument to return one message, and SQL queries, where LIMIT does the job. Hidden actions Two actions are hidden from every assistant unless you allow them: shutting Asterobot down and resetting every setting. Three are never tools, because an assistant has nothing to do with them: the live event stream Asteroboard uses (the assistant has watch-events instead), and exporting or importing a package as a zip file. MCP settings explains how to hide more actions, or allow the hidden ones. The game protocol The assistant can list every game message a bot can send, with every field, through the same catalogue the Send messages tab of a bot uses, and send one on a bot's socket. Asterobot checks the message against the game's own definition before it goes out, so a wrong field never reaches the game. The catalogue gives the shape of a message, not what it's for: the Dofus protocol reference has that, and you can point the assistant to a page of it. What the assistant can send is what a script can send; see Send messages. Next: MCP settings.
  14. Smooth a posté un record dans AI Assistants
    Give an AI assistant the address of your Asterobot, with a password when you've set one, and check that it sees the tools. Connecting an assistant means giving it one address. Asterobot needs no setting for it: the endpoint is on as soon as Asterobot starts. The address The endpoint is /mcp on Asterobot's port, the one in server.listenAddress: Your setup Address Defaults http://127.0.0.1:8080/mcp Another port, for example 127.0.0.1:9000 http://127.0.0.1:9000/mcp server.tls on https://127.0.0.1:8080/mcp The assistant must run on the same computer as Asterobot, or reach it the way Asteroboard does from another device. An assistant that runs on a website rather than on your computer can't reach 127.0.0.1; see From another machine. Claude Code In a terminal, run: claude mcp add --transport http asterobot http://127.0.0.1:8080/mcp Then start a conversation and ask it something about Asterobot, such as "list my bots". The first time it uses a tool that changes something, it asks for your permission. claude mcp list shows the servers it knows and whether it reaches them. Other assistants Every assistant that supports MCP over HTTP takes the same address, called the server's URL. Many read it from a JSON file with an entry like this one, under a name you choose: { "mcpServers": { "asterobot": { "url": "http://127.0.0.1:8080/mcp" } } } The exact file and its shape depend on the assistant, so check its documentation for "MCP" and "HTTP". Two things to know when it asks: The transport is HTTP, sometimes called streamable HTTP. It isn't the older SSE transport, and it isn't a command to run. Asterobot keeps no session between calls. Assistants built for the current version of the protocol work; a very old one that insists on a session id from the server won't connect. With a password When server.basicAuth holds a login and a password, /mcp requires them too, as an Authorization header. The value is Basic followed by the login, a colon and the password, encoded in base64. To encode alice:s3cret: echo -n 'alice:s3cret' | base64 [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("alice:s3cret")) Then give the assistant the header. With Claude Code: claude mcp add --transport http asterobot http://127.0.0.1:8080/mcp --header "Authorization: Basic YWxpY2U6czNjcmV0" Other assistants have a headers entry next to the URL in their configuration file. From another machine An assistant on another computer reaches Asterobot the way Asteroboard does from another device, and with the same care: Asterobot on a remote machine and Security apply word for word. An SSH tunnel is the simplest: it keeps Asterobot on 127.0.0.1 and the address stays http://127.0.0.1:8080/mcp on the machine where the assistant runs. When Asterobot listens on a network address without basic auth, its console says so at start: The MCP is reachable from the network without authentication: set server.basicAuth or listen on loopback That is the same exposure Asteroboard already has in that setup, said out loud once. Check that it works When Asterobot starts, its console and terminal show one line about the endpoint: Line What it means MCP ready with tools=, denied= and skipped= counts The endpoint serves. denied counts the hidden actions, skipped the ones Asterobot couldn't offer. MCP disabled by configuration mcp.enabled is off. Nothing answers at /mcp. MCP disabled with an err= The endpoint answers every call with an error saying why. See MCP settings. Then ask the assistant to check that Asterobot answers: it has a tool for that, get-runtime-ping, whose answer is pong. If the assistant says it can't reach the server, check the address and the port, and that Asterobot is running. Next: Tools, events and limits.
  15. Smooth a posté un record dans AI Assistants
    Let an AI assistant such as Claude Code drive your Asterobot through MCP, and know what it can do, what it can't, and what to keep in mind before you let it. Asterobot can be driven by an AI assistant, the way you drive it from Asteroboard. The assistant connects to Asterobot, sees everything Asteroboard can do as a list of tools, and calls them while you talk to it: "add a bot on my second account and connect it", "which tables hold the monsters and their drops", "write a script that greets the guild when the bot signs in". This chapter explains what an assistant gets, how to connect one, and what to watch out for. What MCP is MCP, the Model Context Protocol, is the standard way assistants talk to programs. Asterobot speaks it at the address /mcp, right next to the address Asteroboard uses. Any assistant that supports MCP over HTTP can connect: Claude Code, Codex, Cursor, a desktop assistant, or a program of your own. Nothing in Asterobot is tied to one of them, and there's nothing to install on Asterobot's side: it's on as soon as Asterobot starts. What an assistant can do Asteroboard does everything through Asterobot's port, and an assistant gets the same port. So it can do what you do on every page of Asteroboard: Ankama accounts: add one, sign it in automatically or by hand, answer the code Ankama sends, refresh or check a sign-in, disconnect it. Bots: add, change and delete bots, connect and disconnect them, answer a connection question, move the character on its map, read a bot's console, choose which messages the bot mirrors, and send a message on its socket. Packages: install a package from the marketplace when you give it the file's id, create a package, edit its script and manifest, load, play, stop and reload it, read and change its settings, click its actions, update it, delete it. Game: list, download, extract, verify and uninstall game versions, browse the game data tables, look up texts and names, and run SQL queries. Everything else: read and change settings, manage proxies, sign Asterobot in and out of asterobot.net, check that Asterobot answers, and follow what happens through events and the log. Each tool is one action of Asterobot, named after what it does: get-bots lists your bots, post-bot-connect connects one, put-config-key changes a setting. There are about a hundred of them. The assistant reads a short description with each, written for it, so you don't have to explain Asterobot to it. When it starts something that takes time, such as connecting a bot, the answer tells it which event to wait for, and it can watch that event with a tool made for that. Tools, events and limits has the details. What it can't do Shut Asterobot down or reset every setting. These two actions are hidden from assistants unless you allow them in the settings. See MCP settings. Export or import a package as a zip file. Those need a file on your computer, which an assistant doesn't handle. Installing from the marketplace, and creating or editing a package in place, work. Browse the marketplace. It can install a package when you give it the file's id or address from asterobot.net, but it has no way to search the marketplace itself. More than a script can. An assistant writes and runs the same scripts you'd write: they can move on a map and talk to the game, and they meet the same limits, described in What scripts can do. Anything Asterobot refuses. When your asterobot.net account doesn't give Asterobot access to the game protocol, the tools that need it answer that the game engine isn't available, exactly as Asteroboard would show it. Note Coming soon. The assistant can't read this documentation through Asterobot yet. Until then it works from what each tool says about itself, from the script reference it can fetch, and from what it already knows about Dofus. You can paste a page of these docs into the conversation when it needs one. Keep in mind An assistant acts as you. Whatever you can do from Asteroboard, it can do, on your Ankama accounts and with your asterobot.net account. Security lists what that covers. Most assistants ask before changing something. Every tool says whether it only reads. Assistants such as Claude Code ask for your permission before a tool that changes something, and let you allow a tool once or for good. Look at what a tool is about to do before you allow it, the same way you'd read a dialog before clicking OK. It reads what other people write. A bot's console and traffic carry chat messages from other players. An assistant that reads them could take a message written to trick it for an instruction. Asterobot doesn't filter that. Keep the permission prompts on for tools that change things, and don't leave an assistant running unattended on a bot in a busy chat. Secrets stay hidden. Tool answers never contain website.token or your basic auth passwords, even through the settings tools: those values are replaced with [redacted]. Next: Connect an assistant.
  16. Smooth a posté a record dans Installations réseau
    Enregistrez des proxys SOCKS5 dans Asterobot et attribuez-en un à un bot, pour que les connexions qu'il ouvre vers les serveurs de Dofus partent d'une autre adresse. Read this page in English Un proxy fait partir d'une autre adresse que la vôtre les connexions qu'un bot ouvre vers les serveurs de Dofus. Asterobot en tient la liste sur la page Gestionnaire de proxys, et chaque bot en utilise au plus un. Seuls les proxys SOCKS5 fonctionnent ici. Les proxys SOCKS4 et les proxys HTTP ne sont pas pris en charge, parce que ce qui doit passer par le proxy est la connexion brute qu'utilise Dofus lui-même. Ce qui passe par le proxy Lisez ce point avant toute installation. Ce que couvre un proxy découle de ce que fait le bot, pas d'un réglage que vous choisissez. Connexion Mode Full socket Mode MITM Serveur de connexion Par le proxy Directe Serveur de jeu Par le proxy Par le proxy Authentification Ankama en HTTPS Directe Directe Un bot en mode Full socket se connecte et joue tout seul : les deux connexions qu'il ouvre vers les serveurs de Dofus passent donc par son proxy. Un bot en mode MITM, pour « homme du milieu » (man-in-the-middle), c'est-à-dire un bot qui relaie la session d'un client Dofus que vous jouez, n'envoie que la connexion au serveur de jeu par son proxy. La partie connexion d'une session relayée passe par deux ports que tous les bots en attente partagent, et Asterobot les ouvre avant que quoi que ce soit dans le trafic ne dise à quel bot appartient la session. À cet instant, il n'y a aucun bot dont lire le proxy. L'authentification auprès d'Ankama est distincte des deux. Asterobot appelle Ankama en HTTPS pour transformer un compte enregistré en ticket pour le jeu, et cet appel part toujours de votre propre adresse. Important Ankama voit donc un bot en mode Full socket se connecter depuis votre adresse, puis jouer depuis celle du proxy. Vérifiez que cela correspond à ce que vous attendez du proxy avant de compter dessus. Enregistrer un proxy Ouvrez le Gestionnaire de proxys dans la barre latérale. Une liste vide affiche « Aucun proxy enregistré. » Cliquez sur Ajouter un proxy. Remplissez les champs ci-dessous, puis cliquez sur Ajouter. Champ Obligatoire Ce qu'il faut y mettre Nom Non Ce qui vous permettra de reconnaître ce proxy au moment de le choisir sur un bot. Minuscules, chiffres et tirets, en commençant et en finissant par une lettre ou un chiffre. Laissez-le vide et Asterobot en génère un, comme il le fait pour un bot ; le bouton en forme de dé à côté du champ en remplit un pour que vous puissiez le voir et le modifier avant. Il ne pourra plus être modifié ensuite. Adresse Oui L'hôte et le port ensemble, tels que votre fournisseur les donne : proxy.exemple.com:1080. Une adresse IPv6 se met entre crochets, [2001:db8::1]:1080. Le port doit être compris entre 1 et 65535. Identifiant Non L'identifiant que demande le proxy. Laissez-le vide, ainsi que Mot de passe, pour un proxy qui n'exige aucune authentification. Mot de passe Non Le mot de passe qui va avec l'identifiant. Il n'est plus jamais affiché une fois enregistré. Rien n'est contacté pendant que vous tapez, et rien ne l'est quand vous cliquez sur Ajouter. Un proxy hors service, ou dont le mot de passe est faux, est enregistré exactement comme un proxy qui marche. C'est Tester, dans la même fenêtre, qui fait la différence, et l'enregistrement n'y est volontairement pas conditionné : un proxy injoignable sur le moment est souvent celui que vous êtes en train de réparer. Le nom est l'identité définitive du proxy. Les bots le désignent par ce nom, un renommage les casserait donc : pour en changer, ajoutez un second proxy et redirigez les bots vers celui-là. Lire la liste Colonne Ce qu'elle affiche Nom Le nom que vous lui avez donné. Cliquez sur l'en-tête pour trier. Adresse L'hôte et le port, avec SOCKS5 en dessous pour rappeler de quel type de proxy il s'agit. Authentification Avec identifiants quand un identifiant ou un mot de passe est enregistré, Sans identifiants sinon. Quand il y a un identifiant, il est affiché à côté du badge. Utilisé par Les bots réglés pour se connecter par ce proxy, chacun étant un lien vers les Réglages de ce bot. Aucun bot quand personne ne l'utilise. Dernier test Ce qu'a trouvé le dernier test de ce proxy, tant que la page reste ouverte. Un tiret tant que vous n'avez rien demandé. Le champ au-dessus du tableau filtre sur le nom et sur l'hôte : vous pouvez chercher par l'un ou par l'autre. Tester un proxy Un test envoie une vraie requête HTTPS à travers le proxy et rapporte l'adresse depuis laquelle l'autre bout l'a vue arriver. C'est cette adresse la réponse : c'est celle d'où semble venir tout ce qui passe par ce proxy, et la comparer à la vôtre est ce qui vous dit que le proxy transporte vraiment le trafic, au lieu de se contenter d'accepter une négociation. Il y a trois façons de le lancer, et c'est le même test : Tester dans la fenêtre Ajouter un proxy SOCKS5, avant que le proxy ne soit enregistré. De quoi essayer des informations que vous venez de coller. Tester dans le menu du proxy au bout de sa ligne, pour un proxy enregistré. Tout tester, à côté du champ de filtre, pour tous les proxys enregistrés d'un coup. Ils partent ensemble et non les uns après les autres : dix proxys prennent à peu près le temps d'un seul. Les résultats s'affichent dans la colonne Dernier test : l'adresse de sortie et la durée de l'aller-retour en cas de réussite, Échec avec la raison en infobulle sinon. En cas d'échec, vous recevez la raison telle qu'Asterobot l'a obtenue : connexion refusée, mot de passe rejeté (username/password authentication failed) ou hôte introuvable. Asterobot abandonne au bout de 15 secondes. Rien n'est retenu. Les résultats ne vivent que tant que la page reste ouverte, et aucun badge ne survit à un rechargement, parce qu'un proxy qui répondait il y a une minute n'est pas un proxy qui répond maintenant. Note Tester depuis la fenêtre pendant la modification d'un proxy existant n'utilise pas le mot de passe enregistré : Asterobot ne renvoie jamais un mot de passe au navigateur. La fenêtre le signale sous le résultat. Ressaisissez-le si vous voulez que le test le prenne en compte. Ce que contacte le test L'adresse vient d'un service extérieur à Asterobot, défini dans proxymanager.ipCheckUrl sur Serveur > Config. Il vaut au départ https://api.ipify.org?format=json, qui répond votre adresse et rien d'autre. Seuls les tests s'en servent, uniquement quand vous en demandez un, et la requête ne transporte rien sur vous au-delà de l'adresse du proxy lui-même. Tester indique tout de même à qui exploite ce service que ce proxy existe et fonctionne : pointez le réglage vers le vôtre si vous préférez ne pas le dire. Asterobot lit un champ ip dans une réponse JSON, ou la réponse entière quand elle tient sur une ligne, ce qui couvre la plupart de ces services sans rien changer. Attribuer un proxy à un bot Quand vous ajoutez un bot, l'onglet Connexion comporte un champ Proxy, entre Choix du serveur et l'identifiant de machine. Il démarre sur Aucun proxy (connexion directe), ce que faisaient tous les bots avant l'arrivée des proxys. Choisissez plutôt un proxy enregistré, ou utilisez le bouton + pour en enregistrer un sans quitter le formulaire. Un bot existant dispose du même champ sur sa propre page, dans ses Réglages, à la section Connexion. Le modifier ne demande aucune déconnexion : Asterobot lit le proxy au moment d'ouvrir une connexion, le changement s'applique donc à la prochaine connexion du bot et laisse tranquille une session en cours. Cliquez sur Enregistrer comme d'habitude. Plusieurs bots peuvent partager un même proxy. Asterobot ne limite pas leur nombre. Modifier ou supprimer un proxy Modifier, dans le menu du proxy, rouvre la même fenêtre pour tout sauf le nom. Le champ du mot de passe est vide parce qu'Asterobot ne renvoie jamais un mot de passe enregistré : laissez-le vide pour conserver celui que vous aviez saisi, tapez-en un nouveau pour le remplacer, ou cochez Supprimer le mot de passe enregistré pour l'enlever complètement. Comme tout autre changement, une nouvelle adresse atteint un bot à sa prochaine connexion. Supprimer efface le proxy, et refuse tant qu'un bot y fait référence : proxy "fr-datacenter-1" is still used by harvester-one, harvester-two Redirigez d'abord ces bots, puis supprimez-le. Ce refus est voulu : un bot dont le proxy a disparu pourrait sinon continuer à se connecter depuis votre propre adresse, ce qu'un proxy sert précisément à éviter. Quand le proxy d'un bot a disparu Supprimer un proxy utilisé est refusé, ce cas se produit donc surtout si proxies.toml est modifié à la main ou restauré depuis une ancienne copie. Le bot conserve le nom qu'on lui a donné, et Asterobot refuse de le connecter plutôt que de le connecter silencieusement en direct : bot "harvester-one" is configured to connect through proxy "fr-datacenter-1": proxy not found: "fr-datacenter-1" La page Réglages du bot affiche le nom manquant dans le champ Proxy, signalé comme n'étant plus enregistré, avec un avertissement en dessous. Choisissez un autre proxy, ou Aucun proxy (connexion directe), puis enregistrez. Où sont enregistrés les proxys Les proxys vivent dans proxies.toml, dans le dossier de données applicatives d'Asterobot, à côté de bots.toml et accounts.toml. Le mot de passe de chaque proxy s'y trouve en clair. C'est nécessaire : SOCKS5 l'envoie à chaque connexion, Asterobot a donc besoin du mot de passe lui-même et non d'une version brouillée. Sous Linux et macOS, le fichier n'est lisible que par votre propre utilisateur, et Asterobot réapplique cette restriction à chaque démarrage et à chaque enregistrement. Aucun point d'entrée ne redonne un mot de passe enregistré, et c'est pourquoi la fenêtre ne peut pas le pré-remplir. Asterobot lit proxies.toml au démarrage et le réécrit à chaque modification d'un proxy : ne le modifiez donc qu'Asterobot arrêté. Limites SOCKS5 uniquement. Les proxys SOCKS4 et HTTP ne sont pas pris en charge. Un proxy par bot. Asterobot ne fait pas tourner les proxys, n'en choisit pas automatiquement et ne bascule pas sur un second quand le premier échoue. L'authentification Ankama en HTTPS ne passe jamais par un proxy, dans aucun des deux modes. En mode MITM, la partie connexion reste directe, comme expliqué en haut de cette page. Où aller ensuite Mode Full socket explique comment connecter un bot qui se connecte tout seul. Mode MITM explique le relais de la partie que vous jouez. Réglages d'un bot passe en revue tous les champs modifiables ensuite. Sécurité traite de qui peut atteindre votre Asterobot et de ce qu'il peut en faire.
  17. Smooth a posté un record dans Network setups
    Store SOCKS5 proxies in Asterobot and give one to a bot, so the connections it opens to the Dofus servers leave from another address. A proxy makes the connections a bot opens to the Dofus servers leave from another address instead of yours. Asterobot keeps a list of them on the Proxy Manager page, and each bot uses at most one. Only SOCKS5 proxies work here. SOCKS4 proxies and HTTP proxies are not supported, because what has to travel through a proxy is the plain connection Dofus itself uses. What goes through a proxy Read this part before you set anything up. What a proxy covers follows from what the bot is doing, not from a setting you pick. Connection Full socket mode MITM mode Login server Through the proxy Direct Game server Through the proxy Through the proxy Ankama sign-in over HTTPS Direct Direct A Full socket bot signs in and plays on its own, so both connections it opens to the Dofus servers go through its proxy. A bot in MITM mode (man-in-the-middle: the bot relays the session of a Dofus client you play) sends only the game server connection through its proxy. The sign-in half of a relayed session travels over two ports that every waiting bot shares, and Asterobot opens them before anything in the traffic says which bot the session belongs to. At that moment there is no bot to read a proxy from. The sign-in with Ankama is separate from both. Asterobot calls Ankama over HTTPS to turn a stored account into a ticket for the game, and that call always leaves from your own address. Important Ankama therefore sees a Full socket bot sign in from your address and then play from the proxy's. Decide whether that suits what you want the proxy for before you rely on it. Store a proxy Open Proxy Manager in the sidebar. An empty list says "No proxies stored yet." Click Add a proxy. Fill in the fields below and click Add. Field Required What to put in it Name No How you recognise this proxy when picking it on a bot. Lowercase letters, digits and hyphens, starting and ending with a letter or a digit. Leave it blank and Asterobot generates one, the same way it does for a bot; the dice button next to the field fills one in so you can see it and edit it first. It cannot be changed later. Address Yes The host and the port together, as your provider gives them: proxy.example.com:1080. An IPv6 address goes in brackets, [2001:db8::1]:1080. The port has to be between 1 and 65535. Username No The username the proxy asks for. Leave it and Password empty for a proxy that needs no credentials. Password No The password that goes with the username. It is never shown again after you save it. Nothing is contacted while you type, and nothing is contacted when you click Add. A proxy that is down, or whose password is wrong, is stored exactly like a working one. Test, in the same dialog, is what tells the two apart, and storing is deliberately not gated on it: a proxy that happens to be unreachable right now is often the one you are in the middle of fixing. The name is this proxy's identity for good. Bots refer to it by that name, so renaming would break them: to change it, add a second proxy and point the bots at that one instead. Read the list Column What it shows Name The name you gave it. Click the header to sort. Address The host and port, with SOCKS5 underneath as a reminder of what kind of proxy this is. Authentication With credentials when a username or a password is stored, No credentials otherwise. When there is a username, it is shown next to the badge. Used by The bots set to connect through this proxy, each a link to that bot's Settings. No bot when none use it. Last test What the last test of this proxy found, while this page is open. A dash until you ask. The box above the table filters on the name and on the host, so you can search either. Test a proxy A test sends one real HTTPS request through the proxy and reports the address the other end saw it come from. That address is the answer: it is what anything reached through this proxy looks like it comes from, so comparing it with your own address is how you know the proxy is genuinely carrying the traffic, rather than merely completing a handshake. There are three ways to run it, and they are the same test: Test in the Add a SOCKS5 proxy dialog, before the proxy is stored. Use it to try details you have just pasted in. Test in a proxy's menu at the end of its row, for one stored proxy. Test all, next to the filter box, for every stored proxy at once. They run together rather than one after another, so a pool of ten takes about as long as one. Results appear in the Last test column: the exit address and how long the round trip took on success, Failed with the reason in a tooltip otherwise. A failure gives you the reason as Asterobot received it: a refused connection, a rejected password (username/password authentication failed), or a host that does not resolve. Asterobot gives up after 15 seconds. Nothing is remembered. Results live only while the page is open, and no badge survives a reload, because a proxy that answered a minute ago is not a proxy that answers now. Note Testing a proxy in the Add dialog while editing an existing one does not use the password you saved: Asterobot never sends a stored password back to the browser. The dialog says so under the result. Type the password in again if you want the test to include it. What the test contacts The address comes from a service outside Asterobot, set in proxymanager.ipCheckUrl on Server > Config. It starts as https://api.ipify.org?format=json, which answers with your address and nothing else. Only tests use it, only when you ask for one, and the request carries nothing about you beyond the proxy's own address. Testing does tell whoever runs that service that this proxy exists and works, so point the setting at your own if you would rather not say so. Asterobot reads an ip field from a JSON answer, or the whole answer when it is a single line, so most such services work unchanged. Give a proxy to a bot When you add a bot, the Connection tab has a Proxy field between Server selection and the device identifier. It starts on No proxy (connect directly), which is what every bot did before proxies existed. Pick a stored proxy instead, or use the + button to store one without leaving the form. An existing bot has the same field on its own page, under Settings, in the Connection section. Changing it needs no disconnection: Asterobot reads the proxy when it opens a connection, so a change applies at the bot's next connect and leaves a running session alone. Click Save as usual. Several bots can share one proxy. Asterobot puts no limit on how many. Change or remove a proxy Edit, in the proxy's menu, reopens the same dialog for everything except the name. The password field is empty because Asterobot never sends a stored password back: leave it empty to keep the one you saved, type a new one to replace it, or tick Remove the stored password to take it off entirely. Like any other change, a new address reaches a bot at its next connect. Remove deletes the proxy, and refuses while any bot still refers to it: proxy "fr-datacenter-1" is still used by harvester-one, harvester-two Point those bots somewhere else first, then remove it. The refusal is on purpose. A bot whose proxy disappeared could otherwise carry on connecting from your own address, which is the one thing a proxy is there to prevent. When a bot's proxy is missing Removing a proxy in use is refused, so this mostly happens if proxies.toml is edited by hand or restored from an older copy. The bot keeps the name it was given, and Asterobot refuses to connect it rather than quietly connecting it directly: bot "harvester-one" is configured to connect through proxy "fr-datacenter-1": proxy not found: "fr-datacenter-1" The bot's Settings page shows the missing name in the Proxy field, marked as no longer stored, with a warning under it. Pick another proxy, or No proxy (connect directly), and save. Where proxies are stored Proxies live in proxies.toml, in Asterobot's application data folder, next to bots.toml and accounts.toml. The password of each proxy is in that file in plain text. It has to be: SOCKS5 sends it on every connection, so Asterobot needs the password itself rather than a scrambled copy. On Linux and macOS the file is readable only by your own user, and Asterobot applies that again each time it starts and each time it saves. No endpoint ever gives a stored password back, which is why the dialog cannot prefill it. Asterobot reads proxies.toml when it starts and rewrites it whenever you change a proxy, so edit it only while Asterobot is stopped. Limits SOCKS5 only. SOCKS4 and HTTP proxies are not supported. One proxy per bot. Asterobot does not rotate proxies, pick one automatically, or fall back to a second one when the first fails. The Ankama sign-in over HTTPS never goes through a proxy, in either mode. In MITM mode the sign-in stays direct, as described at the top of this page. Where to go next Full socket mode covers connecting a bot that signs in on its own. MITM mode explains relaying the game you play. Bot settings goes through every field you can change on a bot afterwards. Security covers who can reach your Asterobot and what they can do with it.
  18. 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.
  19. 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.
  20. 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.
  21. 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.
  22. 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
  23. 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.
  24. 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.
  25. 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.

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.