Bonjour à tous.
Après de nombreux mois ( 9 pour etre précis) je suis heureux de pouvoir faire un petit post de présentation d'un bot que j'ai codé entièrement seul sur mon temps libre. Avant toute chose quelques disclaimers :
1 : Je ne suis pas informaticien. Je fais ça par passion avant toute chose et parce que ce domaine me passionne. Mon approche est clairement non optimisée et très certainement beaucoup d'entres vous auraient fait mieux.
2 : Si vous avez des suggestions sur les méthodes que j'emploie ou des remarques a faire, je serai très heureux de les lire
3 : L'objectif de ce post est simplement de partager ma joie et mon engouement avec des gens suceptibles d'etre intéréssés !
4 : Pour des raisons de référencement ( et au cas ou ) CaT désigne ce que l'on fait dans D0ufs pour trouver un coffre qui a des dents
1 - Description du projet
Il s'agit d'un bot CaT codé en Python, n'utilisant pas la moindre interprétation de paquet. Toutes les informations sont lues, et réalisées via des méthodes les plus "human like" possibles. Cela diminue certes l'efficacité, mais a l'interet majeur d'etre ( selon moi ) parfaitement indétectable. Le principe est l'utilisation de la détection d'image basique couplé a des entrées clavier/souris élémentaires pour perform du début a la fin une CaT. Les CaT étant déclinées en 3 grandes étapes, la suite de ce post servira a expliciter mon cheminement en espérant que cette brève introduction vous ait intéréssé.
2 - Outils utilisés pour les actions basiques
Les entrées clavier/souris :
Initialement je m'étais servi de la librairie d'AutoIt 3 très facilement implémentable en Python via pyAutoIt. Cette librairie permet l'interaction avec des "Controls". L'objectif étant de pouvoir avoir un programme qui fonctionne avec des inputs clavier/souris basique sans pour autant monopoliser l'ordinateur. Les fonctions de click et de send sur Control d'AutoIt fonctionnait de façon relativement irrégulière ( la fenetre du jeu étant en soit dépourvue de control concret ). Le désavantage majeur de ces fonctions est qu'elles ont ( et on me corrigera si je dis de la merde ) une espèce de "focus" propre ( je sais pas comment dire ça ). Mais typiquement, pour copier/coller un message dans le chat, on s'attendrait a simplement faire un clic dans le chat, puis Ctrl A + Ctrl X. Cependant, après le ControlClick, le focus de la fenetre du jeu est "reset" et les entrées clavier qui suivent ne vont pas dans le chat, mais directement dans la fenetre. ( Donc on ouvre l'interface alliance par exemple ).
Pour remédier a ce problème j'ai donc opté pour des fonctions beaucoup plus "bas niveau" si je puis dire de SendMessage et PostMessage avec des codes Hexa. Cela a le mérite d'etre non seulement moins irrégulier et beaucoup plus fiable , mais de permettre un total fonctionnement en background.
La capture d'image :
Le fonctionnement du bot se faisant en background, le répérage d'image doit lui aussi se faire en background. Pour se faire j'ai opté pour la fonction PrintWindow qui permet a partir du handle de la fenetre d'en obtenir une capture d'écran. Ceci dit, il est a noté que la fenetre ne doit pas etre minimisée pour que cette fonction puisse opérer. Une simple modification de l'état de la fenetre a priori permet d'ignorer cette limite.
Et ... c'est tout .. Vu que l'approche était human like, j'avais besoin que le soft soit capable de voir , cliquer et écrire.. Il ne lui reste plus qu'a réfléchir...
3 - Arriver sur la map de départ
Si on ignore les étapes assez simples pour arriver dans la malle, l'obtention de la map de départ se fait elle aussi en capture d'image. Après avoir standardisé la position de la map ( via un dézoom et un recadrage pour etre sur de voir a la fois B0nTA et Brak ) j'ai fais un template matching avec openCV pour rechercher le jalon de départ. Un shift clic et la position se retrouve dans mon chat que je vais copier coller ( via postMessage ) . J'analyse ensuite mon presse papier pour connaitre X et Y. Ensuite via une List de tuple contenant le nom, et les coordonnées de tous les zaaps du jeu, je crée un MinHeap dans lequel j'enregistre la différence de maps entre la map de départ et la position pour savoir quel est le zaap le plus proche. Une fois ceci fait , HavreSac > Zaap > Clic > Entrée du nom de ce zaap le plus proche. Par la suite un "/clear" et un "/travel X Y" me permettent alors d'arriver sur la map de départ. ( Le /clear sert a détecter le message m'indiquant etre arrivé )
4 - Réaliser la CaT
Une fois sur la map de départ commence un processus assez complexe de "lecture". Comme on peut s'y attendre j'ai utilisé Tesseract via un outil nommé Capture2Text. Je fais donc un screenshot de la fenetre, que je crop pour le faire correspondre au premier hint. Je lis ensuite cet indice avec C2T qui me donne un string ressemblant plus ou moins au vrai hint. Ma réso de base étant de 800 x 600 pour réduire le stress du GPU, les mots lus sont parfois très très loin du hint recherché. Je compare donc le string obtenu avec un dictionnaire de tous les hints du jeu via la distance de levenshtein et j'en conclus que celui qui a la plus courte correspond au hint a chercher. J'ai a priori téléchargé toute la base de données d'indice d'un site d'indice, ce qui me permet d'avoir un fichier texte de plus de 35 000 positions d'indices que je charge lors de l'exécution. De ce fait , le programme sait d'ores et déjà des qu'il lit un indice, ou il se trouve. Il se rend alors sur cette map et valide l'indice. Rien de compliqué. La difficulté arrive pour les taupes (on va les appeler comme ça .. )
Trouver les taupes :
Pour trouver les taupes , j'ai commencé par les screenshots dans toutes les orientations. J'ai découpé le fond le et appliquer un masque pour que le template matching d'openCV ignore le contexte des taupes. Elles sont alors détectées quelque soit la map. Seul problème quand on en voit qu'une partie du corps ou quoi. Mais j'ai ma solution pour ça. En couplant l'application d'un masque a openCV et en configurant correctement les cutoffs de détection, a l'heure actuelle, mon bot n'ignore jamais une taupe si elle est visible a l'écran ( chose dont je m'assure en masquant l'HUD quand on en cherche une
5 - Gagner le combat
On arrive a la partie , de très loin , la plus complexe et celle qui m'a pris le plus de temps.
La première étape est l'analyse des cases disponibles et non disponibles de la map. Pour se faire j'analyse en boucle toutes les cases de la map et j'en analyse la couleur ce qui me permet ensuite de créer un Set() d'objet Cellules qui me permet de savoir si elles sont accessibles ou pas.
Ensuite dans un combat est de savoir ou l'on se trouve et ou se trouve l'ennemi. Sur ce point précis, openCV et le template matching ont été pris en très grand défaut pour une raison simple. Tout template matching, quelqu'en soit la méthode doit a priori etre fait sur une échelle de gris. Le combat étant en mode créature pour standardiser la détection d'image, on se rend assez vite compte que le mode créa d'un mob et le mode créa d'un perso ( en échelle de gris ) se ressemble énormément. Il n'était donc pas rare que le bot inversent les deux et prennent mon perso pour l'ennemi et vice versa.
Pour contrer ça, dans l'analyse des cases pour savoir si elles sont pratiquables, je réalise en meme temps une analyse de 40x40 pixels autour du centre de la case. Cette analyse sert a comparer les pixels de ce rectangle avec la couleur des pixels connus de l'ennemi et de mon perso. En effet j'ai a priori extrait via un script externe un array de tous les pixels discriminants de mon perso et de l'ennemi via des screenshots dans toutes les directions.
De ce fait en meme temps que l'analyse des cases, le code vérifie si mon personnage ou l'ennemi se trouve sur cette case en comptant les pixels communs avec les pixels connus. A la fin de l'exécution il enregistre les cases qui avaient le plus de pixels communs. Cela a l'avantage colossal comparé a une détection d'image openCV d'ingorer si le personnage est coupé par un élément du décor .
Cette méthode est garantie et jamais le bot ne s'est trompé dans la position de l'ennemi et de mon personnage. On pourrait croire que cela prend du temps mais avec un travail d'optimisation approfondi, notamment via l'utilisation de Heaps, de Set() plutot que des Lists et autre, il faut moins de 5 secondes au programme pour analyser toutes les cases et savoir ou sont les personnages.
![img]()
La meme méthode est utilisée par la suite mais sans analyser la praticabilité de toutes les cases. Ce qui fait que détecter personnage et ennemi prend moins d'une demi seconde.
Maintenant qu'on sait ou sont les protagonistes du combat, il s'agit de faire se déplacer mon personnage pour aller taper l'ennemi.
Pour se faire j'utilise le célibrissime algorithme A star ( A*). Je n'irai pas en détail mais cet algorithme permet de trouver le chemin le plus court possible vers une cible. A tous ceux qui s'y aventurent , prenez garde a bien chronométrer vos différentes étapes pour ne pas avoir un pathfinding qui prennent 3000 ans. Le gif suivant illustre la méthode de l'algorithme dans mon programme. (cf, dans sa première version, ce pathfinding prenait environ 277 secondes ... Comme quoi , avoir un code optimisé c'est assez utile xD )
![[Image Introuvable]](https://i.ibb.co/PgBcgmm/pathf.gif)
Ainsi a chaque tour, on détecte le nombre de PM de notre perso et on clique sur la PM-ième case de notre path pour se déplacer.. puis on tape .. et on fait ça jusqu'a la fin .. Et voila , le tour est joué, plus qu'a faire ça en boucle.
6 - Limites et conclusion
.
Vous vous doutez évidemment qu'il y a pas mal de limites a ce fonctionnement. Certaines choses seraient bien plus optimisées si j'avais les connaissances requises pour les faire. Je pourrais par exemple avoir un world path finding ou détecter via l'interprétation de paquet la position de l'ennemi. Bien plus de choses seraient accessibles via l'interprétation de paquets et je compte les ajouter quand j'en aurai le temps.
J'ai appris le python pour créer ce projet ( il y a maintenant 2 mois que je code en python et les 7 d'avant j'apprenais le javascript parce que de base ce projet était sous Actionaz .. ) a partir de connaissances éparses en C, Java .. C'est mon premier projet de "grande ampleur" ( a mon échelle ) et tout commentaire, idée ( ou meme de l'aide pour enfin comprendre ces satanés paquets ... ) est bienvenu.
Cordialement