Librairie
Voir tous les scripts disponibles pour Asterobot
3 fichiers
-
FM Intelligente v0.1 — Asterobot 0.6.0
Version de validation, en mode MITM uniquement.
Le moteur de décision choisit une rune à partir des seuils visés, du manque, du gain par rune, de son poids, de sa priorité, de son coût estimé, du stock et du puits retourné par Dofus. Le score est une heuristique : ce n'est pas une formule de taux de succès officielle ni une garantie de résultat.
Fonctionnement et limites
Surveille le résultat des essais (ExchangeCraftResultEvent), les effets de l'objet et le puits (fm_power).
Classe les runes candidates ; déclenche, sous contrôle de l'utilisateur, plusieurs tentatives à la suite avec la rune déjà placée.
Si la meilleure rune change, met en pause et indique celle à placer manuellement : il ne change pas encore de rune automatiquement. En l'absence de capture testée du changement de rune pour cette version, un changement automatique serait imprudent.
S'arrête quand les objectifs sont atteints, quand un maximum est dépassé, si un message de forge est incomplet, si l'atelier est fermé, si les runes manquent, si les tentatives ou le budget sont épuisés ou si le bouton Pause est utilisé.
Pas de réseau extérieur, ni de collecte de compte. Aucune action lors de l'importation ou du lancement tant que l'utilisateur n'appuie pas sur DEMARRER.
Non testé dans une session Dofus réelle : ne jamais commencer avec un équipement coûteux ou une rune d'exotique. Aucune garantie de conformité avec les règles de Dofus.
Importer le ZIP
Ouvrir Asterobot et se connecter à son bot MITM via Asteroboard.
Dans Asteroboard : Library Manager → Import a .zip.
Choisir fm-intelligente-0.1.0.zip sans le décompresser.
Nom : fm-intelligente ; version : 0.1.0 ; cliquer Import.
Sur la page du bot, sélectionner le paquet fm-intelligente puis Play (ou dans l'éditeur cliquer Run en ayant sélectionné le bot).
Onglet Settings / Réglages → remplir Objectifs de FM (JSON), puis Apply.
Dans Dofus, ouvrir le poste de forgemagie ; placer votre objet et une pile de runes correspondant à un objectif ; lancer UNE rune à la main.
Dans l'onglet Settings / Réglages, cliquer VOIR LE RAPPORT pour lire les IDs, valeurs et puits dans la Console du bot.
Quand les valeurs sont vérifiées et que la rune recommandée est bien celle dans l'atelier, cliquer DEMARRER (apres 1 rune manuelle).
Pour arrêter la prochaine tentative : STOP / PAUSE. Il est impossible de retirer une tentative déjà envoyée.
IMPORTANT : lancer le paquet avant d'ouvrir la fenêtre et d'y placer la rune, sinon Asterobot peut ne pas avoir observé les événements de l'échange.
Configurer les objectifs
La valeur Objectifs de FM (JSON) doit être un tableau JSON. Exemple de structure uniquement :
[ { "nom": "Vitalite", "effectId": 111111, "runeGid": 222222, "min": 190, "max": 250, "gain": 10, "poids": 2, "priorite": 2, "cout": 100 }, { "nom": "Sagesse", "effectId": 333333, "runeGid": 444444, "min": 35, "max": 50, "gain": 1, "poids": 3, "priorite": 1, "cout": 200 } ]Les IDs 111111/222222/333333/444444 sont FICTIFS. Il faut impérativement les remplacer. L'exemple ne constitue pas une configuration opérationnelle. Les champs sont :
effectId : identifiant numérique de l'effet Dofus (effects[].action sur l'objet après une première tentative).
runeGid : ID du modèle d'objet de la rune, tel qu'affiché dans l'inventaire / Game > Datacenter, pas l'UID de sa pile.
min : jet minimal à atteindre pour cet effet ; max : plafond à préserver (facultatif).
gain : valeur théorique apportée par la rune, à vérifier pour le type de rune et l'effet.
poids : poids de la rune, à renseigner avec une source fiable ou vos données de FM.
priorite : coefficient de préférence pour cet effet, 1 par défaut.
cout : prix estimé par rune, dans la même unité que budgetKamas, 0 par défaut.
Le script n'infère pas correctement à lui seul toutes les correspondances entre les effets et les runes. Lire le rapport, le Datacenter d'Asteroboard et, si nécessaire, la console Réseau pour obtenir les vrais IDs. Ne jamais reprendre les numéros fictifs de l'exemple.
Notes : seuls les effets de type value_int sont pris en charge. Les jets de dés, valeurs exprimées sous forme de plages et les effets non numériques sont volontairement refusés pour éviter des décisions hasardeuses. Une statistique manquante est traitée comme zéro : ne configurer que les statistiques positives normales dont l'absence signifie réellement zéro.
Boutons et sécurités
VOIR LE RAPPORT : état de l'atelier, rune en place, cible, effet brut de l'objet (avec les action numériques et les valeurs), recommandation.
DEMARRER : arme le script après une première FM manuelle et validée. Les essais suivent un délai (delaiMs, 1800 ms par défaut).
STOP / PAUSE : coupe la série, sans annuler un message déjà transmis au serveur.
maxTentatives : limite d'essais automatisés depuis la dernière exécution du paquet. Défaut 12.
budgetKamas : budget estimé par rapport à vos coûts renseignés dans le JSON ; pas un prélèvement mesuré en HDV. Mettre 0 pour ne pas l'utiliser.
objetUid : identifiant UID précis de l'objet à forgemager, facultatif ; 0 = apprentissage après un résultat d'essai. Pour un équipement de valeur, renseigner explicitement l'UID pour réduire le risque de confusion.
puitsMinimum : arrêt si le puits descend sous ce nombre ; 0 par défaut.
Dépannage
Impossible d'importer : vérifier que index.js est à la racine du ZIP et lib/strategie.js dans son sous-dossier. Ne pas compresser le dossier englobant.
Aucun jet observé : lancer le paquet avant la fenêtre de FM puis faire une rune manuelle.
Rune non détectée : faire glisser la rune après le lancement du paquet. Observer dans la console les GID et UID logués. Utiliser la rune GID dans le JSON.
Aucune rune candidate : vérifier les IDs, les seuils et les stocks items().
Puits absent : Asterobot n'a pas reçu les données attendues. Le script s'arrête volontairement.
L'algorithme veut une autre rune : changer manuellement, effectuer une tentative, lire le rapport et cliquer DÉMARRER.
Le script envoie et n'enchaîne pas : contrôler si Dofus reçoit un ExchangeCraftResultEvent après l'envoi. Une série ne reprend qu'après confirmation serveur.
Documentation officielle consultée
Paquet : https://asterobot.net/docs/devs/first-script/create-a-package-r5/
Import ZIP : https://asterobot.net/docs/guide/en/packages/manage-your-library-r55/
Protocoles : https://asterobot.net/docs/devs/game-protocol/sending-r23/
Résultat FM : https://asterobot.net/docs/dofus-protocol/9_events/exchangecraftresultevent-r489/
Puits : https://asterobot.net/docs/dofus-protocol/8_types/exchangecraftresultwithobjectdescription-r490/
Lancer FM : https://asterobot.net/docs/dofus-protocol/6_requests/exchangereadyrequest-r493/
Items : https://asterobot.net/docs/devs/reference/asterobotcharacter-r67/
- Gratuit
- 6 Téléchargements
-
Paysan est un script de récolte du métier Paysan pour Asterobot : il fauche les céréales du circuit des champs d'Astrub, se bat quand un monstre l'attaque, dépose ses ressources en banque quand les sacs sont pleins, puis reprend sa récolte. Il s'appuie sur @asterobot:base 0.0.3 : le circuit est un plan du Studio, que l'on modifie dans le Studio d'Asteroboard.
Ce que fait le paquet :
il récolte les plants que le niveau du métier permet, la ressource la mieux payée d'abord (prix moyens lus en jeu) ;
il joue les combats de bout en bout, sans jamais les abandonner : sorts, placement, boosts, repli ;
il dépose en banque les ressources et leurs sacs, dans la banque la plus proche ;
il prend un zaap pour aller à la banque et en revenir, seulement quand cela fait gagner du temps ;
il écrit un bilan de récolte dans la console toutes les 30 minutes.
Le paquet a été essayé en jeu avec un Sacrieur et un Cra. Les parties non essayées sont signalées dans « Limites connues ».
Sommaire
Démarrage rapide
Ce que fait chaque étape
Réglages
La récolte
La banque
Le combat
Le zaap
Le bilan de récolte
Messages envoyés et lus
Lignes de la console
Mémoire du bot
Prérequis
Limites connues
Pistes et contribution
Fichiers
Nouveautés de la version 1.3.0
Version précédente
Démarrage rapide
L'installer
Importer Paysan-1.3.0.zip dans la bibliothèque d'Asteroboard (ou le télécharger depuis la Librairie), puis vérifier que @asterobot:base 0.0.3 est installé : c'est la seule dépendance.
{ "dependencies": { "@asterobot:base": { "version": "0.0.3" } } }Le jouer
Connectez le bot, choisissez Paysan 1.3.0 comme comportement, réglez les paramètres du plan (Ressource, Pause entre récoltes, Pods max, Niveau si le serveur ne l'envoie pas), puis cliquez sur Lancer. Le bot va au zaap d'Astrub (par zaap s'il en vaut la peine), puis fauche les cartes du circuit en boucle.
Pour un autre circuit, ouvrez le plan plans/main.js dans le Studio et dessinez le parcours : les étapes de Paysan sont proposées dans la liste.
Ce que fait chaque étape
Bloc
Type
Ce qu'il fait
Faucher (faucher)
étape
Récolte la carte courante : la ressource autorisée la mieux payée d'abord, puis les autres, et reprend un plant repoussé. Reprend la récolte après un combat.
Deposer en banque (banque)
étape
Parle au banquier, ouvre le coffre et dépose les ressources du métier et leurs sacs. À jouer sur la carte de la banque.
Retour de la banque (retour)
étape
Après le dépôt, retourne à la carte de récolte par zaap si cela fait gagner du temps.
Banque la plus proche (plusProcheBanque)
condition
Vraie pour la banque du jeu la plus proche du personnage. Sert à ne jouer que l'arrêt de cette banque. Avant de répondre, elle prend un zaap si cela fait gagner du temps.
Aller au zaap de la zone (versLeZaap)
condition
Prend un zaap pour rejoindre le zaap de la zone des champs si cela fait gagner assez de temps. Vraie quand le personnage y est arrivé par zaap, fausse sinon (l'arrêt est alors sauté).
Le plan plans/main.js enchaîne l'arrêt « Zaap d'Astrub », les cartes du circuit (une étape Faucher chacune), et l'interruption Sacs pleins qui joue le plan plans/bank.js (un arrêt par banque, filtré par Banque la plus proche, puis Deposer en banque et Retour de la banque), puis reprend là où le circuit s'était arrêté.
Réglages
Paramètres du plan (Studio)
Paramètre
Rôle
Défaut
Ressource
« Toutes, selon le niveau » ou une seule céréale
Toutes
Pause entre récoltes
pause en ms, de 0 à 5 000
400
Pods max
au-delà de ce pourcentage, le plan va à la banque la plus proche, puis reprend
90 %
Niveau si le serveur ne l'envoie pas
niveau Paysan supposé
29
Réglages avancés (lib/config.js)
Toutes les valeurs fixes du script sont dans ce fichier. Les principales :
Réglage
Effet
Défaut
zaap.minGainMs
gain de temps minimal pour prendre un zaap
20 000
zaap.overheadMs
temps compté pour utiliser le zaap et charger la carte d'arrivée
4 000
zaap.maxFailures
échecs d'affilée avant de couper le zaap
2
bilan.everyMs
intervalle du bilan de récolte
30 min
fight.elementBonus
avantage de l'élément principal dans le classement des sorts
0,5
fight.coverAfterAp
se mettre à l'abri une fois les PA dépensés
oui
fight.boost, fight.boostHorizon, fight.lethalMargin
boosts : activation, tours pris en compte, marge pour les sauter quand le combat est gagné
oui, 3, 1,1
fight.avoidTackle
éviter les cases voisines d'un ennemi
oui
fight.disengagePush, fight.reservePush
sort qui repousse l'ennemi au corps à corps et réserve de PA pour le lancer
oui
fight.delayedWeight
poids des dégâts retardés dans le classement
0,5
blacklist
un plant refusé deux fois est écarté 6 heures
failures.maxConsecutive
échecs d'étape d'affilée avant d'arrêter le plan
5
La récolte
Les ressources et leurs compétences sont dans lib/data.js ; un plant n'est pris que si le niveau du métier le permet.
La priorité vient des prix : le jeu envoie les prix moyens (par exemple quand on ouvre l'hôtel de vente). Une ressource sans prix connu passe après celles dont le prix est connu, puis se classe par niveau requis.
Après un combat, la carte est relue : si son contenu n'est pas revenu, le script le redemande comme le client (ContextReadyRequest, puis MapInformationRequest).
Un plant refusé deux fois est écarté pendant six heures.
La banque
Les banques et leurs portes sont lues dans les données du jeu (icône de banque sur la carte du monde, porte sans condition vers l'intérieur) : 13 banques.
plusProcheBanque choisit la moins éloignée du personnage, en cartes à traverser.
Le dépôt parle au banquier, répond « Consulter son coffre personnel » (réponse trouvée dans les données du jeu), puis envoie chaque pile de ressources et chaque sac de ressource (« Sac de blé », etc.).
L'inventaire est lu à la connexion et tenu à jour par les événements du jeu ; il est gardé dans la mémoire du bot, donc un rechargement du script ne le perd pas.
Le combat
Aucun abandon : le script joue son tour jusqu'à la fin, y compris pour un combat repris en cours.
Les sorts sont ceux de la classe du personnage, lus dans les données du jeu : coût, portée, ligne, ligne de vue, limites par tour et par cible, délai de relance.
Le classement se fait par dégâts attendus par PA : caractéristiques du personnage (force, intelligence, chance, agilité, dommages, dommages %), résistances de l'ennemi (en % puis fixes), effets qui exigent un état de la cible, dégâts retardés comptés à moitié, bonus pour l'élément principal.
Le placement cherche une case d'où le meilleur sort est lançable, de préférence loin des ennemis ; en combat, la marche n'a pas de diagonale d'écran.
Les boosts sont lancés seulement si le calcul prévoit un gain de dégâts (sur 3 tours au plus) et si le combat n'est pas déjà gagné ce tour.
Au corps à corps : sorts d'abord, puis sort qui repousse, puis repli. Après les sorts, le bot se met à l'abri avec ses PM restants. Les chemins évitent les cases voisines d'un ennemi (tacle).
Le zaap
Pour atteindre une carte (la banque, le zaap des champs, la carte de récolte), le script compare :
la marche directe (durée estimée par Asterobot) ;
la marche jusqu'au zaap connu le plus proche, la téléportation vers le zaap connu le plus proche de la destination, puis la marche.
Il prend le zaap seulement si le gain dépasse zaap.minGainMs (20 s). Le prix du zaap n'entre pas dans la décision. Les zaaps connus sont ceux que le personnage a enregistrés (annoncés à son entrée dans le monde ; au besoin, le script ouvre un zaap une fois pour les apprendre).
Le jeu n'envoie pas le contenu de la carte d'arrivée après une téléportation : le script le demande (ContextReadyRequest, MapInformationRequest) puis ferme le dialogue (DialogLeaveRequest).
Garde-fou. Le zaap est coupé pour la session si le jeu annonce un abonnement terminé, s'il répond « compte non abonné » (message 62) à une téléportation, ou après deux échecs d'affilée. Le script marche alors, sans détour vers un zaap.
Le bilan de récolte
Toutes les 30 minutes, la console du bot affiche une ligne de synthèse et une ligne par ressource :
paysan bilan depuis=21:49 duree=0h30 recolte=1841 kamas_estimes=26139 derniere_periode=0h30 recolte_periode=1841 kamas_periode=26139 paysan bilan-ressource ressource=Avoine quantite=382 prix_unite=26 kamas_estimes=9932 paysan bilan-ressource ressource=Orge quantite=630 prix_unite=15 kamas_estimes=9450La valeur est la quantité multipliée par le prix moyen lu en jeu. Une ressource sans prix connu est comptée en quantité mais pas en kamas, et nommée dans prix_inconnus. Le décompte reprend après un rechargement du script, tant que la connexion au jeu reste la même ; une nouvelle connexion repart de zéro. Il compte ce qu'annonce ObjectHarvestedEvent : si le bonus de récolte n'y figure pas, il n'est pas compté.
Messages envoyés et lus
Messages envoyés
Message
Quand
MapMovementRequest
marche en combat
GameActionFightCastRequest
lancer un sort
FightReadyRequest, FightTurnReadyRequest, FightTurnFinishRequest, GameActionAcknowledgementRequest
déroulement du combat
NpcGenericActionRequest, NpcDialogReplyRequest, ExchangeObjectMoveRequest, DialogLeaveRequest
dépôt en banque ; fermeture du dialogue d'un zaap
TeleportRequest
téléportation par zaap
ContextReadyRequest, MapInformationRequest
demande du contenu d'une carte (après un combat, après un zaap)
Messages lus
Message
Sert à
ObjectHarvestedEvent, InteractiveUseEndedEvent, InteractiveUseErrorEvent
suivre les récoltes et les refus
ObjectAveragePricesEvent
prix moyens des ressources
InventoryContentEvent, ObjectAddedEvent, ObjectQuantityEvent, ObjectDeletedEvent
inventaire du personnage
MapComplementaryInformationEvent, NpcDialogQuestionEvent, ExchangeStartedWithStorageEvent, ExchangeLeaveEvent
banque
ZaapKnownListEvent, TeleportDestinationsEvent, AccountInformationUpdateEvent, TextInformationEvent
zaap et abonnement
FightJoinEvent, FightStartingEvent, FightPlacementPossiblePositionsEvent, FightSynchronizeEvent, FightRefreshCharacterStatsEvent, FightTurnStartPlayingEvent, FightTurnEvent, FightTurnEndEvent, FightNewRoundEvent, FightMapInformationEvent, FightEndEvent, FightLeaveEvent
déroulement du combat
GameActionFightEvent, SequenceEndEvent, EntitiesDispositionEvent, MapMovementEvent, MapMovementRefusedEvent, ShortcutBarContentEvent
sorts, positions, déplacements
Lignes de la console
Ligne
Ce qu'elle dit
harvest, harvest-leave
résultat d'une carte et raison pour laquelle elle est quittée (nothing-left, pods, map-changed…)
harvest-scan
état des plants d'une carte quand rien n'est pris
bank-nearest, bank-open, bank
banque choisie, coffre ouvert, piles déposées
fight-choice
sort choisi et classement des sorts, avec ce qui les empêche
fight-boost-eval, fight-boost
pourquoi un boost est lancé ou non
fight-cover, fight-push, fight-move
repli, sort qui repousse, déplacements
zaap-plan, zaap-skip, zaap-arrived, zaap-refuse
décision de zaap avec les durées comparées, raison d'un non, arrivée, refus du jeu
bilan, bilan-ressource
bilan de récolte toutes les 30 minutes
step-failed, stop
échec d'une étape ; arrêt du plan avec sa cause
Mémoire du bot
Le paquet garde ces clés dans la mémoire du bot : Paysan/prices (prix), Paysan/stacks (inventaire), Paysan/blacklist (plants écartés), Paysan/jobLevel, Paysan/fight et Paysan/fightStats (combat en cours et caractéristiques), Paysan/zaaps, Paysan/returnMap, Paysan/bilan, Paysan/stats (compteurs).
Prérequis
Asterobot 0.5.1 (version essayée), jeu 3.7.11.11, Full socket.
@asterobot:base 0.0.3.
Pour le zaap : un compte abonné et des zaaps enregistrés par le personnage.
Un client en français : le texte de la réponse « coffre personnel » de la banque est cherché en français.
Limites connues
Seul le circuit des champs d'Astrub est fourni.
Combat essayé avec un Sacrieur et un Cra ; soins, invocations, sorts de zone et sorts qui retirent des PA ou des PM ne sont pas gérés ; seuls les gains de dégâts comptent pour les boosts.
Aucune gestion de la vie : pas de seuil de PV, pas de fuite, pas de soin.
Aucune réaction aux invitations de groupe, aux demandes de défi, aux échanges, aux messages privés.
Banques : seule celle d'Astrub et une autre ont été essayées ; les autres sont déduites des données du jeu.
Havre-Sac et commande /zaap : non utilisés (voir ci-dessous).
Les noms de champs des données du jeu sont ceux de la version 3.7.11.11 ; une mise à jour du jeu peut les changer.
L'usage d'un script de ce type peut être sanctionné par l'éditeur du jeu : il se fait aux risques de l'utilisateur.
Pistes et contribution
Ce qui serait utile, et que j'aimerais construire avec vous :
Havre-Sac : le message a été mappé, il sera implémenté dans une future mise à jour d'asterobot.
Commande /zaap : envoyée dans le canal général avec un compte abonné, elle n'a produit aucune réaction. Une capture de ce que le client envoie vraiment aiderait.
Invitations de groupe, défis, échanges, messages privés : les refuser ou les ignorer proprement.
Gestion de la vie : seuil de PV, repli à la banque ou au zaap, soin.
Autres classes : soins, invocations, sorts de zone.
Autres circuits et autres langues du client.
Fichiers
index.js : point d'entrée, exporte les étapes, les conditions et les paramètres.
plans/main.js : plan du Studio, champs d'Astrub ; plans/bank.js : plan de la banque.
lib/steps.js : étapes et conditions ; lib/harvest.js, lib/watch.js : récolte et suivi ; lib/bank.js, lib/nearest-bank.js : dépôt et choix de la banque ; lib/fight.js : combat ; lib/zaap.js : zaap ; lib/prices.js, lib/data.js, lib/bilan.js, lib/stats.js, lib/log.js, lib/util.js, lib/config.js.
README.md, CHANGELOG.md.
Nouveautés de la version 1.3.0
Ajouté
Plans du Studio (plans/main.js, plans/bank.js), au lieu d'un parcours écrit dans le code.
Choix de la banque la plus proche parmi les 13 du jeu.
Dépôt des sacs de ressource.
Combat complet : classement des sorts, résistances, boosts, sort qui repousse, repli, évitement du tacle, délai de relance, reprise des caractéristiques.
Zaap vers la banque, retour, et départ vers les champs, avec seuil de gain et garde-fou pour les comptes non abonnés.
Bilan de récolte toutes les 30 minutes dans la console.
Corrigé
Le dépôt en banque ne déposait rien : le coffre est maintenant ouvert par le dialogue du banquier.
Un combat arrêtait le script : le combat est joué jusqu'au bout et la récolte reprend ensuite.
Après un combat, la carte n'était pas relue et le bot la quittait avec des plants restants.
Les journaux ne recopient plus de trafic brut du jeu ; les messages du jeu ne sont journalisés que par identifiant et type.
Version précédente
La version 1.2.0 (fauche des champs d'Astrub avec un parcours écrit dans le code) a été présentée sur le forum : Paysan, les champs d'Astrub. C'est la première fois que le paquet est publié dans la Librairie.
- Gratuit
- 25 Téléchargements
-
@asterobot:base
@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, et l’équipement, sous forme d’étapes qu’un plan joue ou qu’un script appelle (voir Récolter, Combattre, Se reposer et manger et L’équipement) ;
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, comme la banque et la vente, arrivera plus tard dans base, 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).
Sommaire
Dé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
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 rapide
L’installer
Depuis 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 seul
Choisissez @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 paquet
Dé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 cas
Un 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 seul
Le 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ée
Base 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 base
Importez 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 étape
Les 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
API
import { 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 } 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.
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 étapes
L’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églages
Base 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.
Plans
Un 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 plan
import { 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 tour
L’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 plan
Un 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.
Échecs
Une 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 plan
Elles 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 runner
Pendant 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 plans
Le 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 fichier
Un 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êts
Champ
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 lieux
kind
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 étapes
Chaque é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 conditions
kind
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 interruptions
Champ
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 pas
Un 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 jeu
L’é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écolter
Ré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 }); gather
Ré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écolte
Condition
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
Combattre
Combattre, 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 }); fightMonsters
Attaque 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.
fightSettings
Rè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 combat
Condition
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 manger
Ré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().
rest
S’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
eat
Mange 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’équipement
Mettre 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.
equip
Met 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é.
unequip
Retire 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’équipement
Condition
Arguments
Vraie quand
wears
item (int, un objet)
Le personnage porte cet objet
Les profils de combat
Base 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 vie
Le 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 SequenceNumberEvent
Le 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.
BasicLatencyStatsEvent
Le 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.
Pings
Un 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 monde
Ce 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 cartes
Chaque 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 route
Les 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épond
Un 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 MITM
Sur 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 envoie
Message
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
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 lit
Message
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
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 erreurs
Les 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 bruit
Deux 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 Asterobot
La 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.
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 client
Les 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é.
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 capture
Le 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.
Fichiers
base/ ├── 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 └── 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
- 101 Téléchargements