Aller au contenu

Référence de l'API Lua

L'API du loader tient en deux règles.

1. Les natifs GTA IV sont des fonctions globales, en PascalCase.

SetCharHealth(GetPlayerChar(GetPlayerId()), 200)

La forme ALL_CAPS n'existe plus comme fonction globale. Le nom canonique SNAKE_CASE reste joignable par la table natives et par Native.Call.

2. Tout le reste vit dans un namespace.

Thread, Console, Log, Draw, Input, Gamepad, Native, Memory, Vehicle et Script. Il n'y a plus aucune fonction globale nue appartenant au loader.

Migration depuis l'ancienne API

print, log, msgbox, is_key_down, draw_text, call_native, CreateThread, Wait… valent tous nil désormais. C'est délibéré : un ancien script échoue franchement (attempt to call a nil value) au lieu d'échouer en silence. Le tableau de correspondance est en bas de page.


Thread (attente et coroutines)

Le loader embarque un ordonnanceur de coroutines. Il donne le Wait() bloquant auquel les mods ScriptHookDotNet sont écrits, sans figer le jeu : un thread qui attend rend la main au loader, qui le relance quand le délai est écoulé.

C'est coopératif, et cela tourne sur le thread de script du jeu. Ce n'est pas un thread système.

Thread.Create(fonction, ...)

Démarre une coroutine gérée par le loader. Les arguments qui suivent la fonction lui sont transmis. Rend un identifiant entier.

  • Exemple :
    Thread.Create(function()
        while true do
            SetCharHealth(GetPlayerChar(GetPlayerId()), 200)
            Thread.Wait(1000) -- une seconde, sans figer le jeu
        end
    end)
    

Thread.Wait(ms)

Suspend le thread courant pendant ms millisecondes. Thread.Wait(0) (ou sans argument) reprend à la frame suivante. Utilisable à n'importe quelle profondeur d'appel dans le thread, et lève une erreur si on l'appelle hors d'un Thread.Create.

Thread.Stop(id)

Arrête le thread portant cet identifiant. Rend true s'il tournait encore. Utilisable depuis l'intérieur d'un thread, y compris sur lui-même.

Thread.Count()

Nombre de threads encore vivants dans ce mod.

Bon à savoir

  • Un thread créé pendant un tick démarre au tick suivant.
  • Une erreur levée dans un thread n'arrête que ce thread : elle est journalisée, et les autres threads continuent.
  • Le délai est du temps réel : une partie en pause ne réveille rien, mais tous les délais échus se déclenchent d'un coup à la reprise. La résolution est celle d'une frame, donc à 60 fps Thread.Wait(2000) rend la main entre 2000 et 2017 ms.
  • Il existe un natif GTA IV nommé WAIT, donc une fonction globale Wait. C'est une raison d'être du namespace : Thread.Wait ne peut plus entrer en collision avec lui. Le natif reste joignable par Native.Call("WAIT", …).

Console et Log (sortie texte)

Deux namespaces, même verbe : la destination est dans le nom du namespace.

Console.Print(...)

Écrit dans la console de l'éditeur F6 in-game. Pas dans le fichier. Accepte plusieurs arguments, joints par une tabulation. Marche aussi depuis un Thread.Create.

Console.MsgBox(msg)

Boîte de dialogue Windows modale au-dessus du jeu. Bloque le jeu tant qu'elle n'est pas fermée : à réserver au démarrage ou au diagnostic.

Log.Print(msg), avec Log.Write(msg) comme alias

Écrit dans le fichier LuaModLoader.log, à côté du binaire du jeu. Pas dans la console.

Les erreurs arrivent seules dans la console

Vous n'avez pas besoin d'attraper puis de réafficher les erreurs pour les voir. Tout ce qui échoue à l'exécution part dans le journal et dans la console de l'éditeur : une erreur de syntaxe au chargement, une erreur levée dans un Thread.Create, un natif appelé sous un nom que le jeu n'a jamais enregistré. La ligne de console porte le préfixe [erreur].

Console.Print("visible dans la console")
Log.Print("ecrit dans LuaModLoader.log")

Draw (rendu 2D)

Le dessin est empilé dans la frame courante et rendu une fois par frame. Il n'est pas persistant : à rappeler à chaque tick.

Draw.Text(x, y, text, [color], [size], [font])

  • x, y (int) : position en pixels, origine en haut à gauche.
  • text (string) : le texte à dessiner.
  • color (int, optionnel) : 0xAARRGGBB. Défaut 0xFFFFFFFF (blanc opaque).
  • size (int, optionnel) : hauteur des glyphes en pixels. Omis ou 0 : la police système, exactement comme avant.
  • font (string, optionnel) : un nom de famille GDI ("Segoe UI", "Impact", ou une famille chargée par Draw.LoadFont). Défaut "Segoe UI" quand une taille est donnée.

Draw.TextSize(text, [size], [font]) -> w, h

Mesure le texte tel que Draw.Text le dessinerait, pour caler une valeur à droite ou centrer un titre. Rend nil si la mesure échoue.

Draw.LoadFont(chemin) -> ok

Charge un .ttf/.otf pour ce process seulement (rien n'est installé sur la machine). Chemin relatif au dossier du mod, comme les images. La famille se nomme ensuite par son nom interne ("Pricedown"), pas par le fichier.

Draw.ScreenSize() -> w, h

Taille du backbuffer du jeu, 0, 0 tant que rien n'a été rendu. Un menu qui veut les proportions de GTA V dessine une mise en page 1280x720 multipliée par h / 720.

Draw.Rect(x, y, w, h, [color])

  • x, y (int) : coin supérieur gauche, en pixels.
  • w, h (int) : largeur et hauteur.
  • color (int, optionnel) : 0xAARRGGBB. Défaut 0xFF000000 (noir opaque).

Images

PNG, JPEG, BMP et GIF, décodés une seule fois et gardés en cache -- réafficher la même image à chaque frame ne coûte plus rien après la première. Les chemins sont relatifs au dossier du mod (la même racine que require) ; un chemin absolu est pris tel quel.

Draw.Image(x, y, chemin, [teinte]) Draw.Image(x, y, w, h, chemin, [teinte])

  • x, y (int) : coin supérieur gauche, en pixels.
  • w, h (int) : taille voulue. 0 pour les deux = taille native du fichier ; 0 pour un seul = l'autre est déduit en conservant les proportions.
  • chemin (string) : par ex. "ui/button.png".
  • teinte (int, optionnel) : 0xAARRGGBB multiplié dans l'image. Défaut 0xFFFFFFFF (image telle quelle). Comme c'est une multiplication, une teinte ne peut qu'assombrir ou estomper -- jamais éclaircir.

Draw.ImageSize(chemin) -> w, h, ou nil si le fichier est illisible. Charge l'image au passage : c'est comme ça qu'on centre ou qu'on aligne un bouton.

Draw.LoadImage(chemin) -> ok, w, h. Décode tout de suite au lieu d'attendre la frame où l'image apparaît. À appeler depuis onLoad pour un fond plein écran : le décodage prend quelques millisecondes, soit un à-coup visible pile au moment où le joueur ouvre le menu.

Draw.UnloadImage(chemin) la retire du cache. Le prochain affichage relit le fichier -- c'est aussi comme ça qu'on voit une image modifiée sans relancer le jeu.

Où mettre ses images

Dans le dossier du mod, à côté des .lua. mods/monmenu/ui/bg.png se dessine avec Draw.Image(0, 0, "ui/bg.png"). Les deux sortes de barres obliques fonctionnent.


Éléments cliquables

De quoi construire des boutons, des en-têtes et des lignes de menu. Tous testent la position du curseur de l'overlay (voir Souris) : il faut donc Input.SetMouseCapture(true) pour qu'ils servent à quelque chose.

Draw.ImageButton(x, y, w, h, chemin, [teinte_survol], [teinte_normale]) -> clique, survole

Dessine l'image, la teinte tant que le curseur est dessus, et rend true pour clique à la frame où un clic gauche tombe dedans. 0 en w ou h reprend la taille du fichier.

teinte_survol vaut 0xFFC0C0C0 par défaut (légèrement assombri). Pour un bouton qui s'allume au contraire, fournissez une image sombre et passez 0xFFFFFFFF en teinte_survol avec 0xFFA0A0A0 en teinte_normale.

Draw.IsHovered(x, y, w, h) -> true si le curseur est dans ce rectangle. De quoi surligner une ligne dessinée au Draw.Rect.

Draw.Clicked(x, y, w, h, [bouton]) -> true si un clic est tombé dans ce rectangle. Transforme n'importe quel rectangle déjà dessiné en bouton, sans image. bouton vaut 1/"left" (défaut), 2/"right" ou 3/"middle".

Un clic est consommé par le premier élément dont le rectangle le contient, et laissé aux autres sinon. C'est ce qui permet d'écrire les éléments les uns après les autres sans registre ni gestion de profondeur -- dessinez de l'arrière vers l'avant, et celui du dessus l'emporte.

local ouvert = false

Thread.Create(function()
    Draw.LoadImage("ui/panel.png")
    while true do
        if Input.IsKeyDown(0x73) then -- F4
            ouvert = not ouvert
            Input.SetMenuOpen(ouvert)
            Input.SetMouseCapture(ouvert) -- rend la souris au jeu en refermant
            Thread.Wait(250)
        end

        if ouvert then
            Draw.Image(100, 100, 400, 300, "ui/panel.png")
            Draw.Text(120, 115, "Menu", Color.RGB(255, 255, 255))

            if Draw.ImageButton(120, 160, 160, 40, "ui/button.png") then
                Console.Print("bouton clique")
            end

            -- Une ligne de liste sans image : un rectangle + un test de clic.
            local fond = Draw.IsHovered(120, 220, 360, 28)
                         and Color.RGBA(255, 255, 255, 20)
                         or  Color.RGBA(0, 0, 0, 40)
            Draw.Rect(120, 220, 360, 28, fond)
            Draw.Text(128, 224, "Faire apparaitre un vehicule")
            if Draw.Clicked(120, 220, 360, 28) then
                Console.Print("ligne cliquee")
            end

            -- Curseur : le jeu n'en dessine pas dans l'overlay, a vous de le faire.
            local mx, my = Input.MousePos()
            Draw.Image(mx, my, 24, 24, "ui/cursor.png")
        end
        Thread.Wait(0)
    end
end)

Color (construction des couleurs)

Draw.* attend un seul entier 0xAARRGGBB. Ces deux fonctions le construisent à partir de composantes 0-255, pour ne pas avoir à écrire de décalage de bits.

Color.RGB(r, g, b)

Couleur opaque. Chaque composante est un entier 0-255 (les valeurs hors bornes sont ramenées dans l'intervalle).

Color.RGBA(r, g, b, [opacite])

Pareil, mais opacite est un pourcentage de 0 (invisible) à 100 (opaque), pas un 0-255. Défaut : 100.

Thread.Create(function()
    local fond  = Color.RGBA(0, 0, 0, 70)   -- noir a 70 % d'opacite
    local texte = Color.RGB(255, 255, 255)  -- blanc opaque
    while true do
        Draw.Rect(10, 10, 200, 40, fond)
        Draw.Text(20, 20, "LuaModLoader is active", texte)
        Thread.Wait(0)
    end
end)

Draw.Text prend x et y en premier

L'ordre est Draw.Text(x, y, texte, couleur), pas (texte, x, y), et la couleur est un seul argument : Draw.Rect(x, y, w, h, r, g, b, a) lève bad argument.

Fondus au noir

Tout ce que vous dessinez se fond sous l'écran noir du jeu, exactement comme le téléphone : sortie d'appartement, enchaînement de mission, chargement. C'est automatique -- rien à appeler, rien à tester.

Le loader interroge GET_SCREEN_FADE_ALPHA une fois par tick et en applique la valeur à l'alpha de chaque commande de dessin ; au noir complet, plus rien n'est dessiné. C'est bien l'alpha qui baisse, pas la couleur : votre overlay disparaît derrière le voile au lieu de virer au gris, et c'est ce qui lui donne l'air natif.


Input et Gamepad (entrées)

Input.IsKeyDown(vk_code)

true si la touche de code virtuel Windows vk_code est enfoncée (ex. 0x73 pour F4). Rend false quand la fenêtre du jeu n'est pas au premier plan - c'est volontaire, et c'est la cause la plus fréquente d'un script qui « ne fait rien » pendant les tests : GTA IV doit avoir le focus.

Input.SetMenuOpen(ouvert)

Signale au loader que votre menu est ouvert. Pendant ce temps, les touches que votre mod a réservées sont neutralisées pour le jeu (pas pour Input.IsKeyDown), pour éviter que le personnage bouge ou tire pendant la navigation dans le menu.

Chaque mod a son propre état ouvert/fermé et sa propre liste de touches : deux menus peuvent donc réserver des touches différentes sans se marcher dessus. Appelez SetMenuOpen(false) en fermant le menu ; le loader libère de lui-même les touches d'un mod quand celui-ci est arrêté ou rechargé.

Input.SetMenuKeys{ VK_UP, VK_DOWN, ... }

Touches masquées au jeu uniquement tant que votre menu est ouvert. Accepte une table ou une simple liste d'arguments ; les valeurs sont des codes de touche virtuelle Windows, les mêmes que pour Input.IsKeyDown. Par défaut : Haut/Bas/Gauche/Droite/Entrée/Retour.

Input.SetToggleKeys{ VK_F4 }

Touches masquées au jeu en permanence, menu ouvert ou fermé : votre touche d'ouverture. Le jeu ne doit jamais la voir, sinon ouvrir le menu déclenche aussi l'action que le jeu associe à cette touche. Par défaut : F4.

Souris

Le loader suit le curseur dans les mêmes coordonnées en pixels que Draw.*, et met les fronts de clic en file pour qu'aucun ne se perde entre deux ticks.

Input.SetMouseCapture(actif)

Retire la souris au jeu : plus de caméra qui tourne, plus de tir, et le curseur système reste visible au lieu d'être recaché à chaque frame. À rappeler avec false en refermant votre menu, sinon le joueur ne peut plus viser. Le loader la libère de lui-même quand votre mod est arrêté ou rechargé.

Comme les touches réservées, la capture est par mod : la souris est confisquée au jeu tant qu'au moins un mod la détient.

Input.MousePos() -> x, y en pixels du backbuffer, bornés à la fenêtre.

Input.SetMousePos(x, y) déplace le curseur du loader (pas celui du système). De quoi piloter les mêmes éléments à la manette ou aux flèches.

Input.IsMouseDown([bouton]) -> true tant que le bouton est enfoncé.

Input.MouseClicked([bouton]) -> clique, x, y. Consomme un front d'appui et dit où il a eu lieu. Pour les éléments d'interface, préférez Draw.Clicked / Draw.ImageButton ; celle-ci sert à un clic n'importe où à l'écran.

Input.MouseWheel() -> crans accumulés depuis le dernier appel, 120 par cran, négatif vers le bas. La lecture remet à zéro.

Dans toutes ces fonctions, bouton vaut 1/"left" (défaut), 2/"right" ou 3/"middle".

Aucun curseur n'est dessiné pour vous

Le jeu masque le curseur système tant qu'il a le focus, et le loader ne l'en empêche que pendant une capture souris. Si votre menu est en plein écran ou que le joueur est dans une cinématique, dessinez votre propre curseur avec Draw.Image à Input.MousePos() -- c'est une ligne, et c'est toujours juste.

Gamepad.IsButtonDown(bouton)

true si le bouton est enfoncé sur la manette 0. bouton est un masque de bits XInput brut (ex. 0x1000 = XINPUT_GAMEPAD_A). Même garde-fou fenêtre-au-premier-plan que Input.IsKeyDown.

Gamepad.SetMenuButtons(masque) / Gamepad.SetToggleButtons(masque)

Les deux mêmes notions côté manette, en masque de bits XInput brut plutôt qu'en liste -- la même valeur que prend Gamepad.IsButtonDown, combinée par OU binaire. SetMenuButtons vaut par défaut croix directionnelle + A + B ; SetToggleButtons ne vaut rien par défaut.

Déclarer ses touches est facultatif

Un mod qui n'appelle jamais ces quatre fonctions garde le jeu de touches historique : F4 en permanence, flèches/Entrée/Retour tant que son menu est ouvert. Rien à changer dans un mod existant.

-- Menu ouvert avec F5, navigation en ZQSD, validation avec Espace.
Input.SetToggleKeys{ 0x74 }                          -- F5
Input.SetMenuKeys{ 0x5A, 0x53, 0x51, 0x44, 0x20 }    -- Z S Q D Espace
Gamepad.SetMenuButtons(0x0001 | 0x0002 | 0x1000)     -- croix haut/bas + A

Native (appel de natifs par leur nom)

Les noms passés à Native.* sont en SNAKE_CASE

FindNativeHash() est une table à correspondance exacte sur le nom canonique. Native.Call("TaskPlayAnim", …) échouerait silencieusement. Il faut Native.Call("TASK_PLAY_ANIM", …). C'est la seule ALL_CAPS qui survit dans l'API, et ce n'est pas un oubli.

Deux raisons d'utiliser Native.Call plutôt que la fonction globale typée :

  • un nom construit dynamiquement.
  • un natif dont la signature enregistrée est fausse. Une soixantaine de natifs sont décrits ScriptAny par les sources publiques, donc enregistrés en int : la fonction globale typée casterait silencieusement une chaîne ou un flottant en entier. Native.Call pousse les arguments tels quels, sans consulter la signature.

Native.Call(name, ...)

Appelle un natif par son nom SNAKE_CASE. Rend les 4 octets bruts du buffer de retour, à décoder au string.unpack ("<i4", "<f"…). nil si le nom est inconnu ou si l'appel a planté (protégé par SEH côté moteur).

Native.CallOut(name, outputCount, ...)

Comme Native.Call, mais alloue outputCount buffers de sortie (max 4) et les rend après la valeur de retour. Les buffers sont contigus : demander 3 sorties revient à passer un Vector3 au natif.

local _, bx, by, bz = Native.CallOut("GET_PED_BONE_POSITION", 3, ped, bone, 0.0, 0.0, 0.0)
local x = string.unpack("<f", bx)

Native.Probe(name, ...)

Comme Native.Call, mais ne jette jamais le contenu du buffer : même si l'appel plante, ce qui y a déjà été écrit est rendu. Rend crashed (boolean) puis les 4 octets bruts.

Native.Info(name)

Signature enregistrée d'un natif, ou nil. La table rendue contient params (chacun avec type, output, pointerInput), hasReturn, returnType, et confidence, qui dit d'où vient la signature. Tout ce qui n'est pas x32dbg_live_verified_* est à prendre avec prudence.

Native.Available(name)

true si le natif est connu et actuellement résolu en mémoire. Faux au menu principal pour la plupart des natifs de gameplay.

Native.Address(name [, raw])

Adresse absolue du handler du natif, 0 s'il n'est pas enregistré. Diagnostic : l'adresse Ghidra vaut valeur - base_du_module + 0x400000.

Le loader détourne un natif pour obtenir son tick à chaque frame, en remplaçant le handler de ce natif dans la table du jeu. Pour ce natif-là, la table ne contient donc plus une adresse du jeu, et la réponse par défaut est celle que le jeu avait à l'origine, la seule qu'il soit utile de décompiler. Passer raw à true rend ce que la table contient réellement à cet instant, c'est-à-dire une adresse dans LuaModLoader.dll pour un natif détourné.

Native.StringAddr(s)

Adresse brute d'une chaîne Lua. Sert à sonder les natifs dont le premier argument est lui-même un pointeur d'entrée.

Native.Register(nom, fonction)

Ajoute un natif à la table du jeu. Rend son hash, ou nil suivi d'un message. Tout ce qui résout un natif par hash y accède ensuite, quel que soit le langage du mod appelant.

Native.Register("ADD", function(a, b) return a + b end)

La fonction reçoit les arguments en entiers bruts de 32 bits, le jeu ne transportant aucune information de type. La convention entre appelant et appelé est leur affaire.

Trois limites, imposées par le jeu et non par le loader. La table ne grandit jamais et ne sait pas libérer une entrée : il reste environ 72 places au total, pour tous les mods confondus. Réenregistrer le même nom réutilise sa place, donc le rechargement à chaud ne coûte rien. Enfin, un natif personnalisé n'est servi que s'il est appelé depuis le thread du tick Lua ; un appel venu d'un autre thread est ignoré, entrer dans un même état Lua depuis deux threads étant un comportement indéfini.

Native.CustomHash(nom)

Le hash qu'aurait ce nom, sans rien enregistrer. C'est Jenkins one-at-a-time sur "LML_" .. nom:upper(), réimplémentable en cinq lignes dans n'importe quel langage : c'est ainsi qu'un mod écrit ailleurs appelle le nôtre sans partager le moindre en-tête.

Native.CallHash(hash, ...)

Appelle un natif par son hash au lieu de son nom. Nécessaire pour atteindre un natif personnalisé, dont le hash n'existe dans aucune table statique.

local h = Native.CustomHash("ADD")
local raw = Native.CallHash(h, 2, 3)

natives

Table de tous les natifs, indexée par le nom canonique SNAKE_CASE. C'est le seul endroit où cette forme reste appelable, avec natives.SET_CHAR_HEALTH(ped, 200).


Memory (lecture et écriture mémoire)

Outillage de rétro-ingénierie. Rien ici n'est nécessaire à un mod ordinaire.

Fonction Rôle
Memory.ModuleInfo([name]) {base, size} d'un module chargé ; sans argument, GTAIV.exe
Memory.FindString(text, …) cherche une chaîne exacte dans la mémoire lisible
Memory.FindDword(value, …) idem, pour un DWORD little-endian
Memory.ReadHex(addr, [count]) dump hexadécimal, lecture protégée
Memory.WriteHex(addr, hex) écrit des octets donnés en hexa, écriture protégée
Memory.ReadU32(addr) lit un uint32 little-endian, nil si illisible
Memory.CallThiscall1(rva, this, arg) appelle une fonction __thiscall du jeu
Memory.CallVtableMethod0(obj, offset) appelle une méthode virtuelle sans argument

Les scans sont plafonnés en temps, et il y a une raison

Memory.FindString / Memory.FindDword s'arrêtent après budgetMs (100 ms par défaut) et rendent en second retour l'adresse où reprendre. Sans ce plafond, un scan par défaut sur toute la plage utilisateur 32 bits fige le jeu plusieurs dizaines de secondes, car le tick Lua est synchrone. Ne jamais boucler sur resumeAddr dans le même appel. Rappelez plutôt depuis un prochain tick.


Vehicle (pièces de carrosserie)

Toutes ces fonctions prennent un handle de véhicule (le même entier que GetCarCharIsUsing), pas un pointeur.

Fonction Rôle
Vehicle.BonePrepare(veh) (re)construit le cache de bones ; rend ok, rebuilt
Vehicle.HasBone(veh, nom) le squelette contient-il ce bone ?
Vehicle.SetBoneVisible(veh, nom, visible) affiche/masque la pièce portée par un bone
Vehicle.IsBoneVisible(veh, nom) état courant ; nil = bone inconnu
Vehicle.BoneNames(veh, [filtre]) table { [nom] = index }
Vehicle.HideComponent(veh, index) masque un composant fragment (à sens unique)
Vehicle.RestoreComponents(veh) réaffiche tout ce qui a été masqué
Vehicle.ResolvePtr(veh) résout le handle en CVehicle*

Note

Le moteur recalcule la pose du squelette lors d'un FixCar, d'une réparation ou d'un re-stream : tout redevient visible d'un coup. Garder le nom d'un bone masqué et relire Vehicle.IsBoneVisible de temps en temps permet de le détecter et de réappliquer.


Script (le mod et son exécution)

Script.DeclareModFile(relPath)

Ajoute un fichier à la liste des scripts du manifest.lua de ce mod, pour qu'il soit chargé au prochain (re)démarrage. Rend false pour un buffer lancé depuis l'éditeur, qui n'a pas de dossier.

Script.IsTickFallback()

Rend toujours false. Elle signalait autrefois un tick qui tournait hors du thread de script du jeu, sur le thread de rendu, là où les natifs qui ont besoin de ce thread (streaming de modèle, CHANGE_PLAYER_MODEL) pouvaient figer le jeu. Les mods tournent maintenant toujours sur la boucle de script du jeu, donc ce cas n'existe plus. La fonction reste parce que des mods l'appellent déjà.

Script.TimeMs()

Millisecondes depuis le démarrage du processus, en flottant (QueryPerformanceCounter). C'est l'horloge de Thread.Wait.


Événements et globaux

Un thread est le seul point d'entrée par image. Le loader n'appelle aucune fonction globale à chaque image, donc le code qui doit tourner par image vit dans une boucle :

Thread.Create(function()
    while true do
        if Input.IsKeyDown(0x73) then -- F4
            -- tourne à chaque image tant que F4 est enfoncée
        end
        Thread.Wait(0)
    end
end)

Un seul point d'entrée veut dire une seule politique d'erreur : une erreur ne tue que le thread où elle survient, et tous les autres continuent.

onUnload()

Appelée juste avant la fermeture de l'état Lua du mod, lors d'un Stop et sur la première moitié d'un Reload. Sert à défaire ce que le mod a changé dans le monde : supprimer les véhicules et les peds créés, retirer les blips, sauvegarder les réglages.

function onUnload()
    if myCar and DoesVehicleExist(myCar) then DeleteCar(myCar) end
    Log.Print("mod arrêté")
end

Une erreur levée dans onUnload est journalisée et l'état est fermé malgré tout : un nettoyage cassé ne peut pas bloquer le loader.

MOD_DIR

Chemin absolu du dossier du mod courant, sans séparateur final. nil pour un buffer lancé depuis l'éditeur.

SCRIPT_NAME

Identifiant de l'instance courante.

Il n'y a pas de onLoad(). L'initialisation, c'est simplement le code écrit dans la portée globale du fichier : il s'exécute une fois au chargement.


Correspondance avec l'ancienne API

Avant Maintenant
print(...) Console.Print(...)
log(msg) Log.Print(msg)
msgbox(msg) Console.MsgBox(msg)
draw_text(...) Draw.Text(...)
draw_rect(...) Draw.Rect(...)
is_key_down(vk) Input.IsKeyDown(vk)
set_menu_open(b) Input.SetMenuOpen(b)
is_pad_button_down(b) Gamepad.IsButtonDown(b)
call_native(...) Native.Call(...)
call_native_out(...) Native.CallOut(...)
call_native_probe(...) Native.Probe(...)
native_info(n) Native.Info(n)
native_available(n) Native.Available(n)
native_address(n) Native.Address(n)
string_addr(s) Native.StringAddr(s)
module_info([n]) Memory.ModuleInfo([n])
mem_find_string(...) Memory.FindString(...)
mem_find_dword(...) Memory.FindDword(...)
mem_read_hex(...) Memory.ReadHex(...)
mem_write_hex(...) Memory.WriteHex(...)
mem_read_u32(a) Memory.ReadU32(a)
call_thiscall1(...) Memory.CallThiscall1(...)
call_vtable_method0(...) Memory.CallVtableMethod0(...)
resolve_vehicle_ptr(v) Vehicle.ResolvePtr(v)
hide_vehicle_component(...) Vehicle.HideComponent(...)
restore_vehicle_components(v) Vehicle.RestoreComponents(v)
vehicle_bone_prepare(v) Vehicle.BonePrepare(v)
vehicle_has_bone(...) Vehicle.HasBone(...)
set_vehicle_bone_visible(...) Vehicle.SetBoneVisible(...)
is_vehicle_bone_visible(...) Vehicle.IsBoneVisible(...)
vehicle_bone_names(...) Vehicle.BoneNames(...)
declare_mod_file(p) Script.DeclareModFile(p)
is_script_tick_fallback() Script.IsTickFallback()
TimeMs() Script.TimeMs()
CreateThread(fn, ...) Thread.Create(fn, ...)
Wait(ms) Thread.Wait(ms)
StopThread(id) Thread.Stop(id)
ThreadCount() Thread.Count()
SET_CHAR_HEALTH(...) SetCharHealth(...)

La surcouche multijoueur (Events, Chat, Player, Game, Console.Log) a été retirée. Les scripts qui en dépendaient doivent appeler les natifs directement.