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.
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.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 globaleWait. C'est une raison d'être du namespace :Thread.Waitne peut plus entrer en collision avec lui. Le natif reste joignable parNative.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].
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éfaut0xFFFFFFFF(blanc opaque).size(int, optionnel) : hauteur des glyphes en pixels. Omis ou0: la police système, exactement comme avant.font(string, optionnel) : un nom de famille GDI ("Segoe UI","Impact", ou une famille chargée parDraw.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éfaut0xFF000000(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.0pour les deux = taille native du fichier ;0pour un seul = l'autre est déduit en conservant les proportions.chemin(string) : par ex."ui/button.png".teinte(int, optionnel) :0xAARRGGBBmultiplié dans l'image. Défaut0xFFFFFFFF(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
ScriptAnypar les sources publiques, donc enregistrés enint: la fonction globale typée casterait silencieusement une chaîne ou un flottant en entier.Native.Callpousse 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.
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.
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.