Passer au contenu principal

Introduction

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.

Langage Lua

Lua est un langage de script léger, rapide et facile à apprendre. Voici les concepts fondamentaux dont vous aurez besoin.
-- Variables locales
local nom = "MonPersonnage"
local niveau = 150
local estVivant = true
local vie = 85.5

-- Tables (listes et dictionnaires)
local sorts = {201, 202, 203}
local config = {mapWait = 3000, potionId = 548}
-- Condition simple
if character:lifePointsP() < 50 then
  inventory:useItem(potionId)
end

-- Condition avec alternatives
if 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
-- Boucle for numérique
for i = 1, 10 do
  print(i)
end

-- Boucle for sur une table
local items = {548, 549, 550}
for _, itemId in ipairs(items) do
  inventory:useItem(itemId)
end

-- Boucle while
while map:currentMapId() ~= 123456 do
  map:changeMap("right")
  global:delay(1000)
end
-- Exemple réel — extrait de hiden.lua
-- ================================================

-- 1. Fonction simple : changer de script selon le niveau
function 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
    end
end

-- 2. Fonction avancée : équiper des objets selon la classe
local 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)
    end
end

-- 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

-- Appels
checkLevel()
equipItems()
albueraReturn()

Structure d’un script

Un script dotouch-emu suit une structure logique simple. Voici un exemple annoté illustrant les principaux éléments.
-- ==================================
-- Script de récolte basique
-- ==================================

-- Configuration
local POTION_ID = 548          -- Identifiant de la potion
local SEUIL_VIE = 50           -- Pourcentage de vie minimum
local 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 droite
map:changeMap("right")
global:delay(DELAI_CARTE)

-- Récolter toutes les ressources de la carte
map: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.

Catégories de méthodes

dotouch-emu expose plus de 310 méthodes organisées en catégories. Chaque catégorie correspond à un aspect du jeu.

Navigation

35 méthodes pour le déplacement, les changements de carte, la récolte, la détection de monstres et la navigation sur la carte du monde.

Combat

50+ méthodes pour gérer les combats : lancer des sorts, se déplacer, cibler des ennemis, gérer les placements et les invocations.

Personnage

20+ méthodes pour accéder aux informations du personnage : santé, niveau, statistiques, position, métiers et état actuel.

Inventaire

29 méthodes pour gérer l’inventaire : utiliser, équiper, déplacer et supprimer des objets, vérifier le poids et les quantités.

Global

50+ méthodes utilitaires : délais, mémoire persistante, journalisation, gestion du temps, conditions avancées et variables globales.

PNJ

Interagir avec les PNJ : ouvrir des dialogues, répondre, ouvrir la banque, acheter et vendre.

Hôtel des ventes

Acheter et vendre des objets aux hôtels des ventes, gérer les prix et les quantités.

Quêtes

Suivre et accomplir des quêtes, vérifier les objectifs et les conditions de progression.

Chat

Envoyer et recevoir des messages sur les différents canaux de chat du jeu.

Craft

Automatiser les recettes de craft : ouvrir un atelier, sélectionner une recette et fabriquer en boucle.

Monture

Gérer les montures et les familiers : monter, démonter, nourrir et accéder aux informations.

Mode développeur

30+ méthodes avancées pour le débogage, l’inspection des paquets réseau et le développement de scripts complexes.

Patterns courants

Voici les patterns de programmation que vous utiliserez le plus souvent dans vos scripts.

Vérifier avant d’agir

Le pattern le plus basique : vérifier une condition avant d’effectuer une action.
-- Se soigner si nécessaire
if character:lifePointsP() < 50 then
  inventory:useItem(548)
end

-- Changer de carte uniquement s'il n'y a pas de ressources
if not map:hasGatherableResource() then
  map:changeMap("right")
end

Itérer sur des objets

Parcourir une liste d’objets pour effectuer une action sur chacun.
-- Déposer une liste d'objets à la banque
local 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)
  end
end

Persistance entre les exécutions

Sauvegarder et lire des données entre les exécutions de script grâce aux fonctions de fichier.
-- Sauvegarder un compteur dans un fichier
local dir = global:getCurrentScriptDirectory()
local file = dir .. "\\compteur.txt"

local compteur = 0
if 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()
    end
end

compteur = compteur + 1
global:writeFile(file, tostring(compteur))

if compteur >= 100 then
    global:printSuccess("100 récoltes atteintes !")
    global:writeFile(file, "0")   -- réinitialiser
end

Délais variables

Ajouter de l’aléatoire aux délais pour simuler un comportement humain.
-- Délai aléatoire entre 800 et 2000 ms
global:delay(math.random(800, 2000))

-- Après un changement de carte
map:changeMap("bottom")
global:delay(math.random(1000, 3000))

Script de récolte complet

Voici un exemple de script de récolte complet avec gestion du poids, de la santé et retour automatique à la banque.
-- =========================================================
-- Script de récolte complet — style hiden.lua / farm_tofus
-- Déplacement en circuit + retour banque automatique
-- =========================================================

-- Configuration
local POTION_ID   = 548    -- Identifiant de la potion
local SEUIL_VIE   = 40     -- % de santé minimum avant de se soigner
local MAX_FIGHTS  = 19     -- nombre max de combats par carte avant forcer le changement

-- Circuit de cartes à suivre
local 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ées
for _, c in ipairs(circuit) do c.done = false end

local fightCounter = 0

-- Trouver la prochaine carte à visiter
local 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 PNJ
local function goBank()
    npc:npcBank()
    global:delay(500)
    exchange:putAllItems()
    global:delay(500)
    global:leaveDialog()
    global:printSuccess("Banque : inventaire vidé")
end

-- Se soigner si nécessaire
local 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
    end
end

-- === 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 } }
    end
end

Script de combat complet

Voici un exemple de script de combat avec gestion des sorts et des déplacements.
-- ================================================
-- 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 principal
local SORT_BOOST     = 201    -- sort de buff (optionnel)

-- Cette fonction est appelée automatiquement par le bot à chaque tour
function 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)
    end
end

-- Appel du tour (fightBasic:playTurn() appelle playTurn())
fightBasic:playTurn()

Conseils pour écrire de bons scripts

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.