Aller au contenu

Structure d'un mod et manifest.lua

Un mod est un dossier placé dans mods/ qui contient un manifest.lua. C'est ce fichier qui fait du dossier un mod : il lui donne un nom dans le gestionnaire de mods, il lui permet de démarrer tout seul, et il liste les fichiers à charger. Un dossier sans manifeste est ignoré, même s'il déborde de Lua. Les deux anciens formats restent lus, voir Formats hérités.

📁 mods/
└── 📁 MonPremierMod/
    ├── 📄 manifest.lua
    ├── 📄 main.lua
    └── 📁 lib/
        └── 📄 menu.lua

Le dossier mods/ se trouve à côté de GTAIV.exe, avec le loader lui-même. Voir Installation.


Le manifeste

-- manifest.lua
name 'Mon Premier Mod'
author 'Vous'
description 'Soigne le joueur sur F4.'
version '1.0.0'
episode 'IV'
autostart = true

client_scripts {
    'lib/menu.lua',
    'main.lua',
}
Clé Défaut Rôle
name le nom du dossier Nom affiché dans le gestionnaire de mods.
author vide Texte libre.
description vide Texte libre, affiché dans le gestionnaire.
version vide (1.0.0 à la réécriture) Texte libre.
episode vide L'épisode visé, par exemple IV, TLAD ou TBoGT. Purement déclaratif : le loader ne cache jamais un mod là-dessus.
autostart false true démarre le mod tout seul une fois le jeu en partie.
client_script / client_scripts main.lua Les fichiers à charger, dans l'ordre, dans le même état Lua. Les deux orthographes remplissent la même liste.
main le premier script client Fichier d'entrée. Rarement utile.

Chaque clé est optionnelle, un manifeste peut donc tenir en une ligne, mais le fichier doit exister.

Comment la syntaxe marche

Le manifeste est du vrai Lua, exécuté dans un état jetable qui n'a ni io ni os. Chaque ligne est un appel de fonction, et Lua permet d'enlever les parenthèses quand l'argument unique est une chaîne ou une table. C'est ce qui donne à name 'Mon Premier Mod' et à client_scripts { ... } leur allure.

autostart true est une erreur de syntaxe

On ne peut enlever les parenthèses que pour une chaîne ou une table, jamais pour un booléen. Écrivez autostart = true ou autostart(true). Pareil pour toute autre clé à laquelle vous voulez donner true, false ou un nombre.

Une clé que le loader ne connaît pas n'est pas une erreur. Elle est rangée dans les métadonnées du mod, vous pouvez donc déclarer ce dont vous avez besoin :

my_config_file 'settings.json'
tags { 'trainer', 'ui' }

Laisser l'éditeur l'écrire

Rien de tout cela n'est à taper à la main. Dans le gestionnaire de mods, faites un clic droit puis New mod : le loader crée le dossier, un manifest.lua avec les clés ci-dessus et un commentaire sur chacune, et un main.lua qui contient déjà la boucle de thread et un onUnload. Il ouvre ensuite main.lua pour vous. autostart part à false, le nouveau mod attend donc un Start tant que vous ne changez pas cette ligne.

Vos commentaires survivent. Quand un mod appelle Script.DeclareModFile, le loader glisse le nouveau chemin dans le bloc client_scripts au lieu de réécrire le fichier, donc rien de ce que vous y avez écrit n'est perdu.


Plusieurs fichiers

client_scripts est ce qui permet d'étaler un mod sur plusieurs fichiers Lua. Ils sont tous chargés dans le même lua_State, dans l'ordre où ils apparaissent, donc une globale définie dans le premier est visible dans le suivant.

client_scripts {
    'lib/config.lua',
    'lib/menu.lua',
    'main.lua',
}

Deux conséquences :

  • L'ordre compte. Mettez les bibliothèques avant le code qui s'en sert.
  • Les chemins s'écrivent avec / et sont relatifs au dossier du mod.

Un mod peut aussi ajouter un fichier à son propre manifeste en cours d'exécution avec Script.DeclareModFile, ce qui permet à un mod qui génère des fichiers de données de les faire charger au démarrage suivant.


Streaming : modèles et données du jeu

Un mod ne se limite pas au Lua. Deux dossiers de plus lui permettent de livrer un véhicule, un modèle d'arme, une texture ou quelques lignes de handling.dat, et le joueur l'installe toujours en copiant un seul dossier dans mods/. Aucun fichier du jeu n'est édité, et rien n'est écrit dans le dossier du jeu.

📁 mods/
└── 📁 MaVoiture/
    ├── 📄 manifest.lua
    ├── 📁 stream/
    │   ├── 📄 f620.wft
    │   └── 📄 f620.wtd
    └── 📁 gamedata/
        ├── 📄 vehicles.ide
        ├── 📄 handling.dat
        └── 📄 carcols.dat

Les deux dossiers sont servis par dinput8.dll, qui se tient entre le jeu et ses fichiers. Ils sont lus une fois, au lancement : un fichier ajouté dans stream/ ou gamedata/ pendant que le jeu tourne est pris au lancement suivant, pas à un rechargement du mod.

stream/

Chaque fichier sous stream/, sous-dossiers compris, est empaqueté au lancement dans une archive IMG que le moteur monte comme les siennes. Modèles, textures, collisions et animations (.wdr, .wft, .wtd, .wbn, .wad…) vont tous là, sous leur simple nom de fichier : le moteur trouve une ressource par son nom, pas par son chemin.

L'archive est écrite dans le cache du loader, le dossier du jeu s'il est inscriptible et %LOCALAPPDATA%\LuaModLoader\cache\ sinon, et elle n'est reconstruite que si le nom, la taille ou la date d'un fichier a changé. asi_loader.log dit combien d'entrées y sont entrées et si le cache a été réutilisé.

Si le joueur a posé son propre update/update.img dans le dossier du jeu, ses entrées sont absorbées dans la même archive plutôt que contournées, et un fichier de mod passe devant une entrée absorbée du même nom.

stream/ ajoute des fichiers ; il ne remplace pas ceux du jeu. Pour remplacer un fichier d'origine, utilisez la disposition de FusionFix : update/common/… et update/pc/… dans le dossier du jeu sont servis par le loader à la place de common/… et pc/….

gamedata/

Un fichier sous gamedata/ est un fragment du fichier du jeu qui porte le même nom : vehicles.ide, handling.dat, carcols.dat, default.ide, images.txt… Il ne contient que vos lignes, sous l'en-tête de section de ce format :

# gamedata/vehicles.ide
cars
f620, f620, car, SUPERGT, F620, VEH@LOW, 100, 999, 0.2740, 0.2740, 0, 1.0, 1.0, 0
end

Les lignes hors de toute section vont à la fin du fichier fusionné, ce qu'il faut à handling.dat, qui n'a pas de sections. Vos lignes s'ajoutent à ce que le jeu, FusionFix ou un autre mod sert déjà pour ce fichier : deux mods qui ajoutent chacun une voiture ajoutent deux lignes au même vehicles.ide au lieu que l'un écrase l'autre. Le fichier fusionné vit dans le cache et est servi au moteur par la redirection de fichiers ; l'original n'est jamais touché.

Le moteur a une table de 160 entrées de handling ; le loader la porte à 512, donc ajouter des véhicules ne la fait pas déborder.

Fichiers en lignes uniquement

Le fusionneur comprend les fichiers faits d'enregistrements sous des en-têtes de section, ou d'enregistrements nus sans section. WeaponInfo.xml est du XML et n'est pas fusionné depuis gamedata/ ; un fragment posé là serait ajouté comme du texte. Livrez plutôt un update/common/data/WeaponInfo.xml complet.


Globales propres au mod

Chaque mod tourne dans son propre état Lua sandboxé, et le loader y injecte deux globales :

Globale Valeur
MOD_DIR Chemin absolu du dossier du mod, sans séparateur final. nil pour un buffer lancé depuis l'éditeur.
SCRIPT_NAME Identifiant de l'instance en cours.

MOD_DIR est ce à partir de quoi on construit les chemins. Le répertoire de travail du process appartient au jeu, pas à votre mod :

local path = MOD_DIR .. "\\settings.txt"
local f = io.open(path, "r")

Le bac à sable

Un mod peut lire et écrire ses propres fichiers (io.open), mais le shell est hors de portée : os.execute, os.exit et io.popen sont retirés de la bibliothèque standard. Rien de ce que fait un mod ne doit pouvoir ouvrir une fenêtre de commande.


Formats hérités

Deux anciennes dispositions restent lues, dans cet ordre : manifest.lua d'abord, puis manifest.xml, puis meta.xml. Un mod qui marche continue de marcher, et rien n'a besoin d'être converti.

manifest.xml était le format du loader avant manifest.lua. Les mêmes champs, en XML :

<mod>
    <name>Mon Premier Mod</name>
    <description>Soigne le joueur sur F4.</description>
    <version>1.0.0</version>
    <main>main.lua</main>
    <autostart>true</autostart>
    <files>
        <file>main.lua</file>
    </files>
</mod>

Son <autostart> est comparé littéralement à true, donc TRUE, 1 et yes sont lus comme false. Script.DeclareModFile continue d'écrire du XML pour un mod qui en a encore un, rien n'est converti dans votre dos.

meta.xml vient des arborescences de ressources multijoueur. Chaque balise <script> de type="client" ou type="shared" apporte son src à la liste des fichiers, et la première devient le fichier d'entrée. Dans ce cas, autostart vaut implicitement true.

Les nouveaux mods doivent utiliser manifest.lua.


Démarrer, arrêter, recharger

  • Autostart : les mods marqués <autostart>true</autostart> sont mis en file au lancement et démarrent au premier tick réellement en jeu, jamais pendant l'écran de chargement, où le moteur n'a pas encore de contexte de script.
  • À la main : depuis l'éditeur in-game F6, dont la liste de mods permet de Start, Stop et Reload n'importe quel mod sans quitter la partie.
  • Reload repart de zéro : l'état Lua est jeté et les fichiers sont rechargés. Aucun besoin de relancer le jeu pour itérer.

Il n'y a pas de onLoad()

L'initialisation, c'est simplement le code écrit au niveau du fichier : il s'exécute une fois, au chargement. À la sortie, onUnload() est bien appelée : le loader l'invoque juste avant de fermer l'état Lua, lors d'un arrêt comme sur la première moitié d'un rechargement. Une erreur levée dedans est journalisée et l'état est fermé malgré tout.