Les scripts Lua sont au cœur de l’automatisation dans dotouch-emu. Avec plus de 310 méthodes réparties dans une dizaine de catégories, vous pouvez automatiser pratiquement tous les aspects du jeu : déplacement, combat, récolte, quêtes, échanges, craft, achat/vente à l’hôtel des ventes, et bien plus encore.Ce guide vous accompagne de la découverte du langage Lua jusqu’à l’écriture de vos premiers scripts fonctionnels. Que vous soyez débutant en programmation ou développeur expérimenté, vous trouverez ici les bases pour commencer efficacement.
Lua est un langage de script léger, rapide et facile à apprendre. Voici les concepts fondamentaux dont vous aurez besoin.
Variables et types
-- Variables localeslocal nom = "MonPersonnage"local niveau = 150local estVivant = truelocal vie = 85.5-- Tables (listes et dictionnaires)local sorts = {201, 202, 203}local config = {mapWait = 3000, potionId = 548}
Conditions
-- Condition simpleif character:lifePointsP() < 50 then inventory:useItem(potionId)end-- Condition avec alternativesif character:level() >= 100 then print("Haut niveau")elseif character:level() >= 50 then print("Niveau intermédiaire")else print("Bas niveau")end-- Opérateurs : ==, ~= (différent), <, >, <=, >=, and, or, not
Boucles
-- Boucle for numériquefor i = 1, 10 do print(i)end-- Boucle for sur une tablelocal items = {548, 549, 550}for _, itemId in ipairs(items) do inventory:useItem(itemId)end-- Boucle whilewhile map:currentMapId() ~= 123456 do map:changeMap("right") global:delay(1000)end
Fonctions
-- Exemple réel — extrait de hiden.lua-- ================================================-- 1. Fonction simple : changer de script selon le niveaufunction checkLevel() if character:level() >= 41 then local path = global:getCurrentDirectory() .. "farm_tofus.lua" if global:fileExists(path) then global:loadAndStart(path) -- passer au script suivant else global:printError("Fichier introuvable : farm_tofus.lua") global:restartScript(true) -- redémarrer en cas d'erreur end endend-- 2. Fonction avancée : équiper des objets selon la classelocal itemsByClass = { [8] = { { gid = 18969, position = 29 }, { gid = 18939, position = 28 } }, -- Iop [9] = { { gid = 18987, position = 29 }, { gid = 18957, position = 28 } }, -- Cra}function equipItems() local breed = character:breed() local items = itemsByClass[breed] or {} for _, item in ipairs(items) do local pos = inventory:itemPosition(item.gid) if pos == 63 then -- 63 = dans l'inventaire inventory:equipItem(item.gid, item.position) global:delay(1500) global:printSuccess("Équipé GID " .. item.gid .. " → pos " .. item.position) elseif pos == item.position then global:printMessage("Déjà équipé à la position " .. item.position) end global:delay(500) endend-- 3. Fonction PNJ : interaction avec un PNJ (extrait de hiden.lua)function albueraReturn() if inventory:itemCount(14721) == 0 then npc:npc(1503, 3) global:delay(200) npc:reply(41327) global:delay(200) npc:reply(41326) global:delay(200) global:leaveDialog() end npc:npc(3916, 3) global:delay(300) npc:reply(-1) global:delay(200)end-- AppelscheckLevel()equipItems()albueraReturn()
Un script dotouch-emu suit une structure logique simple. Voici un exemple annoté illustrant les principaux éléments.
-- ==================================-- Script de récolte basique-- ==================================-- Configurationlocal POTION_ID = 548 -- Identifiant de la potionlocal SEUIL_VIE = 50 -- Pourcentage de vie minimumlocal DELAI_CARTE = 1500 -- Délai entre les changements de carte-- Vérifier et restaurer la santéif character:lifePointsP() < SEUIL_VIE then inventory:useItem(POTION_ID) global:delay(500)end-- Se déplacer vers la droitemap:changeMap("right")global:delay(DELAI_CARTE)-- Récolter toutes les ressources de la cartemap:gather()
La fonction delay(ms) est indispensable entre les actions. Elle attend le nombre de millisecondes spécifié et simule le temps de réaction d’un joueur humain. Ne chaînez jamais des actions sans délai.
Le pattern le plus basique : vérifier une condition avant d’effectuer une action.
-- Se soigner si nécessaireif character:lifePointsP() < 50 then inventory:useItem(548)end-- Changer de carte uniquement s'il n'y a pas de ressourcesif not map:hasGatherableResource() then map:changeMap("right")end
Parcourir une liste d’objets pour effectuer une action sur chacun.
-- Déposer une liste d'objets à la banquelocal objets = {289, 401, 548, 1782}for _, objetId in ipairs(objets) do if inventory:itemCount(objetId) > 0 then inventory:putItem(objetId, inventory:itemCount(objetId)) global:delay(300) endend
Sauvegarder et lire des données entre les exécutions de script grâce aux fonctions de fichier.
-- Sauvegarder un compteur dans un fichierlocal dir = global:getCurrentScriptDirectory()local file = dir .. "\\compteur.txt"local compteur = 0if global:fileExists(file) then -- lire la valeur précédente (le fichier contient uniquement le nombre) local f = io.open(file, "r") if f then compteur = tonumber(f:read("*n")) or 0 f:close() endendcompteur = compteur + 1global:writeFile(file, tostring(compteur))if compteur >= 100 then global:printSuccess("100 récoltes atteintes !") global:writeFile(file, "0") -- réinitialiserend
Ajouter de l’aléatoire aux délais pour simuler un comportement humain.
-- Délai aléatoire entre 800 et 2000 msglobal:delay(math.random(800, 2000))-- Après un changement de cartemap:changeMap("bottom")global:delay(math.random(1000, 3000))
Voici un exemple de script de récolte complet avec gestion du poids, de la santé et retour automatique à la banque.
Voir le script complet
-- =========================================================-- Script de récolte complet — style hiden.lua / farm_tofus-- Déplacement en circuit + retour banque automatique-- =========================================================-- Configurationlocal POTION_ID = 548 -- Identifiant de la potionlocal SEUIL_VIE = 40 -- % de santé minimum avant de se soignerlocal MAX_FIGHTS = 19 -- nombre max de combats par carte avant forcer le changement-- Circuit de cartes à suivrelocal circuit = { { map = "145752576", fight = true, path = "top" }, { map = "145752833", fight = true, path = "bottom" }, { map = "145752064", fight = false, path = "right" },}-- Marquer toutes les cartes comme non visitéesfor _, c in ipairs(circuit) do c.done = false endlocal fightCounter = 0-- Trouver la prochaine carte à visiterlocal function nextMap() for _, c in ipairs(circuit) do if map:onMap(c.map) and not c.done then c.done = true return c end end -- Circuit complet : réinitialiser for _, c in ipairs(circuit) do c.done = false end return nextMap()end-- Aller à la banque via PNJlocal function goBank() npc:npcBank() global:delay(500) exchange:putAllItems() global:delay(500) global:leaveDialog() global:printSuccess("Banque : inventaire vidé")end-- Se soigner si nécessairelocal function healIfNeeded() if character:lifePointsP() < SEUIL_VIE then if inventory:itemCount(POTION_ID) > 0 then inventory:useItem(POTION_ID) global:delay(global:random(400, 800)) end endend-- === Point d'entrée appelé par le bot à chaque tour ===function move() healIfNeeded() -- Après un combat : vérifier si on reste sur cette carte if global:afterFight() then fightCounter = fightCounter + 1 if fightCounter < MAX_FIGHTS then return -- rester sur la carte end fightCounter = 0 -- assez de combats ici, continuer le circuit end -- Vérifier le poids (inventaire plein → banque) if inventory:podsPercent() >= 85 then goBank() return end -- Changer de carte selon le circuit local next = nextMap() if next then global:printMessage("→ Carte " .. next.map .. " | chemin : " .. (next.path or "?")) return { { map = next.map, path = next.path, fight = next.fight, done = false } } endend
Voici un exemple de script de combat avec gestion des sorts et des déplacements.
Voir le script de combat
-- ================================================-- Script de combat — utilise l'API fightAction-- Approche : frapper l'ennemi le plus proche-- ================================================-- Identifiants des sorts (à ajuster selon votre classe)local SORT_PRINCIPAL = 8139 -- sort d'attaque principallocal SORT_BOOST = 201 -- sort de buff (optionnel)-- Cette fonction est appelée automatiquement par le bot à chaque tourfunction playTurn() -- 1. Récupérer tous les combattants local entities = fightAction:getAllEntities() -- 2. Trouver l'ennemi avec le moins de PV local target = nil local minHP = math.huge for _, e in ipairs(entities) do if e["Team"] == false and e["LifePoints"] < minHP then minHP = e["LifePoints"] target = e end end if target == nil then fightDebug:print("Aucun ennemi trouvé") return end local myCell = fightCharacter:getCellId() local enemyCell = target["CellId"] -- 3. Se rapprocher si nécessaire if fightAction:getDistance(myCell, enemyCell) > 3 then fightAction:moveToWardCell(enemyCell) myCell = fightCharacter:getCellId() -- position mise à jour end -- 4. Vérifier la portée et lancer le sort local err = fightAction:canCastSpellOnCell(myCell, SORT_PRINCIPAL, enemyCell) if err == 0 then fightAction:castSpellOnCell(SORT_PRINCIPAL, enemyCell) else fightDebug:print("Impossible de lancer le sort, code d'erreur : " .. err) endend-- Appel du tour (fightBasic:playTurn() appelle playTurn())fightBasic:playTurn()
Commencez simplement. Écrivez d’abord un script minimal qui fonctionne, puis ajoutez des fonctionnalités progressivement. Déboguer un script de 20 lignes est bien plus facile qu’un script de 200 lignes.
1
Utiliser des variables de configuration
Regroupez toutes les valeurs modifiables (identifiants d’objets, seuils, délais) en haut du script. Cela facilite les ajustements sans avoir à chercher dans le code.
2
Toujours ajouter des délais
Ne déclenchez jamais deux actions consécutives sans un delay() entre elles. Utilisez math.random() pour varier les délais et simuler un comportement humain.
3
Gérer les cas d'erreur
Vérifiez toujours qu’une action est possible avant de l’exécuter. Par exemple, vérifiez qu’un objet existe dans l’inventaire avant de l’utiliser.
4
Tester étape par étape
Testez chaque partie de votre script individuellement avant de tout assembler. Utilisez print() pour afficher des informations de débogage.
5
Consulter la documentation de l'API
Chaque méthode est documentée avec ses paramètres, ses valeurs de retour et des exemples. Consultez les pages API pour découvrir toutes les possibilités.
N’oubliez pas les bonnes pratiques d’anti-détection. Un script techniquement parfait qui se comporte de manière suspecte entraînera quand même un bannissement.