Les bases du lua sur FiveM

tutoriel
FiveM
lua-bases
lua

#1

Bonjour à tous,

Je fais ce tutoriel pour tous ceux qui débutent sur FiveM et qui veulent créer des ressources. Le lua est un bon début quand on ne s’y connait pas trop en développement, mais il faut quand même un minimum de base que l’on va voir ici.

Prenez votre temps et commencez par des scripts simples avant de vous lancer dans un gros projet.

Sommaire

Les bases du Lua:

Le Lua sur FiveM:

Les bases du Lua

Les commentaires

Déjà les premières choses à savoir sont les commentaires, qui vous permettent d’écrire ce que vous voulez dans le code sans que ce ne soit interprété pendant l’exécution du script.

Il y a deux types de commentaire :

  • Pour une ligne :
--Voici un commentaire
  • pour plusieurs lignes :
--[[
 Commentaires
 sur
 plusieurs lignes
]]--

Les variables

Voici maintenant une partie très importante dans la programmation, les variables, elles permettent de stocker des valeurs pour les utiliser plus tard et peuvent changer de valeur quand vous lui indiquez.

Il y a plusieurs types de variable :

  • Nombre
  • Chaine de caractère (string)
  • Booléen (une valeur avec uniquement deux possibilités: true/false, vrai/faux, oui/non, 1/0)
  • Liste
  • Fonction (oui c’est un type, mais on reviendra dessus après)
  • Objet (Pour la programmation orientée objet)

Voici quelques exemples :

age = 89
nom = 'TheAncêtre'
nombreVirgule = 8.53
homme = true

On peut aussi faire des affectations de variable en une ligne :

a, b, c = 1, 5, "hello"

Pour les chaines de caractère, autrement dit String, on peut faire ça en plusieurs lignes :

chaine = "bonjour,\
je suis une chaine de caractère"

Voyons un peu de la portée des variables. La portée ? Wesh ta variable elle a accouché ou quoi ? :kappa:. Mais non, quand je parle de portée (scope en anglais), c’est par rapport à quels endroits elle sera accessible. Par défaut, une variable est globale, ça veut dire qu’elle est accessible n’importe où dans l’environnement du script, mais si vous utilisez le mot-clé local, alors, elle sera accessible uniquement à l’endroit où elle est déclarée:

a = 10 --global
local b = 25 --locale

Maintenant vous pouvez vous amuser un peu avec ces variables, comme des calculs :

a, b = 5, 3
c = a + b
-- donc c est égale à 8

Mais c’est légèrement différent pour les chaines de caractère, en effet dans ce cas, on ne parle pas d’addition mais de concaténation et on n’utilise pas le même opérateur :

a, b = "bonjour, ", "Je suis une chaine de caractère"
c = a .. b
-- donc c est égale à "bonjour, Je suis une chaine de caractère"

Petit aparté sur les booléens, ils n’ont que deux valeurs possibles : true et false, que l’on peut traduire par vrai et faux, oui et non, 1 et 0.

Il faut voir maintenant les listes, que l’on va appeler table, voici un exemple :

maListe = {
	5,
	7,
	2,
	6,
	4
}

Pour accéder aux valeurs contenues dans la liste, il faut utiliser des index, contrairement à la plupart des langages, il faut commencer par 1, qui correspond au premier élément :

a, b, c = maListe[1], maListe[2], maListe[3]
--[[
a -> 5
b -> 7
c -> 2
]]--

On peut mettre n’importe quel type de variable dans une table, mais on peut utiliser des chaines de caractère comme index, que l’on va appeler clé ou key dans ce cas :

maListe =  {}
maListe["nom"] = "Bernard"

maListe2 = {
	["nom"] = "Bernard"
}

Pour les autres types de variable, on verra ça après :wink:

Les conditions

AAAaahhh les conditions, c’est ce qu’il y a de plus important en programmation, ça permet de définir un chemin dans votre script selon les situations

Voici un peu à quoi ça ressemble :

if condition then 

end

Maintenant on peut faire des chemins alternatifs, si la condition n’est pas remplie, alors on fait autre chose :

if condition then 

else

end

Allons encore plus loin, on peut ajouter des conditions alternatives, si la première condition n’est pas remplie alors on regarde la deuxième condition et ainsi de suite :

if condition then

elseif condition2 then

else

end

Mais qu’est-ce qu’on met dans ces conditions ? Eh bien tout simplement des booléens (true / false), Mais comment je fais si ma variable n’est pas un booléen ? Justement, c’est ce qu’on va voir maintenant.

Je vous présente, les comparateurs, ils permettent de comparer des valeurs (Merci captain obvious ! :kappa: ). Plus sérieusement, voici quelques exemples avec des nombres :

a = 10
a == 10 -- Égale à 10?
a > 5 -- Strictement supérieur à 5?
a < 5 -- Strictement inférieur à 5?
a >= 7 -- Supérieur ou égale à 7?
a <= 7 -- Inférieur ou égale à 7?
a ~= 5 -- Différent de 5?

Vous pouvez faire la même chose avec des chaines de caractère.

Et comment faire plusieurs comparaisons dans une condition ? En utilisant des opérateurs booléens :

a, b = 5, 10
not a == b -- Contraire de la comparaison, not true -> false / not true -> false
a > 1 and b < 20 -- a Strictement supérieur à 1 ET b Strictement inférieur à 20
a > 1 or b < 20 -- a Strictement supérieur à 1 OU b Strictement inférieur à 20

et avec des parenthèses vous pouvez prioriser certaines comparaisons :

a, b, c = 5, 10, 20
a == 5 and (b > 10 or c > 10) --Le OU sera exécuté avant le reste

Exemple avec tout ce qu’on a vu:

a, b, c, d = 10, "fivem-france", true, false
if a > 10 and not d then
-- Ne sera pas exécuté car a n'est pas STRICTEMENT supérieur à 10
elseif b == "fivem-france" and (a == 10 or d) then
-- Sera exécuté
else
-- Ne sera pas exécuté car la deuxième condition a été rempli
end

Maintenant, voyons un peu comment faire un ternaire. C’est quoi ce truc? C’est une condition qui comprend 3 opérandes, d’où son nom, qui peut être utilisée directement dans un calcul.

Voici comment il est structuré:

condition and A or B

Si la condition est vraie, alors on utilisera la valeur A sinon on utilisera la valeur B.

Attention, comme vous le voyez, on utilise and et or, pas comme des opérateurs booléens mais comme then et else, donc pensez bien à utiliser des parenthèses si vous avez des opérateurs dans votre condition.

Petit exemple d’un ternaire:

local negatif = true
local maVariable = (negatif and -1 or 1) * maVariable

Donc ici, on multiplie maVariable par -1 si negatif est vrai, sinon par 1. Je sais que multiplier par 1 ne sert à rien, mais un ternaire comprend 3 opérandes, donc on est obligé de mettre quelque chose.

Les boucles

Bien, c’est légèrement différent des conditions, mais c’est tout aussi simple. Une boucle permet d’exécuter plusieurs fois des opérations, il y plusieurs types de boucles, celles qui vont s’exécuter tant qu’une condition est vraie ou pour un nombre d’itérations défini.

Commençons par les while, qui peut se traduire par “Tant que la condition est vraie alors faire”, exemple :

while condition do

end

Attention! Il n’est pas recommandé de faire des boucles infinies (while true do), sauf pour les thread de FiveM que l’on va voir plus tard

Il y a aussi une boucle qui permet de faire la même chose que le while mais qui exécute les opérations au moins 1 fois. il s’agit du repeat until, qui se traduit par “Répéter tant que la condition est vrai”
exemple :

repeat

until condition

Maintenant voyons les boucles for qui exécutent les opérations un certain nombre de fois défini. On peut le traduire par “Pour tous les nombres de 1 à 10 faire”,
exemple :

for i = 1, 10, 1 do

end

Ici, à chaque itération, i va commencer à 1, puis va s’incrémenter de 1 jusqu’à arriver à 10. Mais on peut aussi le faire dans l’autre sens :

for i = 10, 1, -1 do

end

Cette boucle permet aussi de parcourir une liste en utilisant le mot-clé in :

maListe = {
	5,
	7,
	2,
	6,
	4
}

for key, value in ipairs(maListe) do

end

Ici, key représente l’index de la liste et value la valeur, la fonction pairs permet de récupérer les pairs, clé et valeur, de chaque élément d’une liste.

Les fonctions

Cette partie n’est pas très compliquée, une fonction est une encapsulation d’instruction, elles peuvent être appelées à n’importe quel moment et peuvent retourner un résultat.

exemple:

function carre(a)
    return a * a
end

carre(2) -- 2 * 2 = 4

Comme vous le voyez, une fonction est structurée de telle manière:

function + nom de la fonction + ( paramètres )

Vous pouvez avoir plusieurs paramètres en les séparant avec des virgules, comme:

function somme(a, b, c, d)
    return a + b + c + d
end

somme(1, 2, 3, 4) -- 1 + 2 + 3 + 4 = 10

Petit bonux, voici quelques fonctions utiles:

print("quelque chose") -- affiche quelque chose dans la console
type(arg) -- permet de récupérer le type d'une variable
tonumber(arg) -- transforme une valeur en nombre
tostring(arg) -- transforme une valeur en chaine de caractère 
ipairs(table) -- permet une itération pour des listes avec comme index des nombres
pairs(table) -- permet une itération pour des listes avec comme index des chaines de caractère

Les tables

On en a un peu parlé avant mais voyons ça un peu plus en détail ici. Dans n’importe quel langage de programmation, les tableaux et les listes sont très importantes, en lua on appelle ça des tables. Comme nous sommes dans les bases, je vais rester simple et lorsque vous les aurez apprises, vous pourrez aller plus loin.

Comme on l’a vu, une table se déclare de cette manière:

maTable = {}

Ici elle est vide mais on peut lui mettre des valeurs par défaut à l’intérieur:

maTable = {
    10,
    "Un texte"
}

Comme vous le voyez, on peut y mettre des types de variable différents.

Maintenant, pour accéder à ces valeurs, il faut utiliser des index, qui sont des clés pour identifier une position dans la table. En lua, premier index est 1 contrairement aux autres langages qui commence par 0, donc:

maTable[1] -- 10
maTable[2] -- "Un texte"

Mais il est aussi possible d’utiliser des chaines de caractères comme index, que l’on va appeler clé ou key en anglais:

maTable = {
    ["nombre"] = 10,
    ["texte"] = "Un texte"
}

maTable["nombre"] -- 10
maTable["texte"] -- "Un texte"

Ensuite, il faut pouvoir être capable de modifier ces valeurs et c’est très facile:

maTable["nombre"] = 20

Pour ajouter une nouvelle valeur dans notre table, on peut le faire comme juste au-dessus ou en faisant un insert:

maTable = {
    10,
    12, 
    5
}

table.insert(maTable, 69)
--[[
maTable est maintenant:
{
    10,
    12, 
    5,
    69
}
]]--

C’est bien beau d’ajouter des valeurs, mais il faut maintenant être capable d’en enlever et une méthode remove existe pour ça, il faut juste lui spécifier l’index de l’élément à supprimer:

maTable = {
    10,
    12, 
    5
}

table.remove(maTable, 2)
--[[
maTable est maintenant:
{
    10,
    5
}
]]--

Je ne vais pas aller plus loin pour les bases, je vous laisse aller voir ici pour plus de fonctions utiles.

Les Callbacks

Oh la la la la la! Bon commençons par traduire “callback” par “Appel de retour” mot pour mot mais que l’on va traduire par “Fonction de rappel”. C’est une fonction qui est passé en argument à une autre fonction et qui pourra être appelée à n’importe quel moment pendant l’exécution des opérations, le plus souvent à la fin du traitement.

Bon je pense que certains n’ont rien compris et c’est normal, ce n’est pas facile à expliquer. Voici un exemple pour mieux comprendre:

function puissance(a, b, callbackA, callbackB)
    local c = a
    for i = 1, b - 1 do
        c = c * a
        callbackA(c)
    end
    callbackB(c)
end

-- pour 2 puissance 4
puissance(2, 4, function(inter)
    print(inter) -- Affiche la valeur intermédiaire, ici 4, 8 et 16
end, function(resultat)
    print(resultat) -- Affiche le résultat, ici 16
end)

Dans cet exemple ce n’est pas très utile car on peut faire un simple return pour récupérer la valeur, mais ça permet de vous montrer à quoi ça ressemble.

Bon à quoi ça sert maintenant? Un callback peut être très utile pour exécuter quelque chose de manière asynchrone et récupérer un résultat. Comme pour du SQL, si la requête est lourde, on va l’exécuter de manière asynchrone pour ne rien bloquer et ensuite récupérer le résultat lorsque la requête est finie.

On le verra plus tard, mais les événements (events) de FiveM utilisent des callbacks et c’est très pratique pour ça.

Les objets

Je vais passer dessus rapidement que vous puissiez savoir à quoi ça ressemble, mais je ne vais pas rentrer dans le détail car ça sort du domaine des bases.

Un objet en programmation contient des informations et des mécanismes, que l’on peut appeler respectivement des attributs et des méthodes.

Pour vous expliquer ça, on va imaginer un objet Player dans un jeu quelconque. Il va avoir comme attributs: un nom, de la vie et de l’armure par exemple.

Player = {
    nom = "Julia",
    vie = 100,
    armure = 100
}

Je ne vais pas vous montrer la création de méthode car il y a plusieurs manières de faire et je ne veux pas vous embrouiller.

Pour accéder aux valeurs, c’est très simple, il faut juste utiliser un point:

print("Bonjour, je suis " .. Player.nom)
Player.vie = 50

Et de la même manière vous pouvez appeler des méthodes:

Player.attaquer()

Voilà, vous savez à quoi ressemblent des objets, si vous voulez en savoir plus vous pouvez regarder ça

Le Lua sur FiveM

Structure d'une ressource

Bien, avant de commencer, je vais éclaircir quelque chose, certaines personnes disent “script” en parlant de ressource, alors que ce n’est pas du tout la même chose. Un script est une suite d’exécution d’opérations non compilées directement interprétée par l’ordinateur. Une ressource, quant à elle, est un ensemble de fichiers, souvent des scripts, des librairies et des métadonnées, qui seront exécutés par un programme.

Maintenant que c’est plus clair, les ressources sont toutes dans le dossier “resources” de votre serveur, et comme vous pouvez le voir certains dossiers ont des ‘[ ]’, cela permet de dire à FiveM que c’est un dossier qui contient des ressources. On peut appeler ça des catégories, mais c’est uniquement pour organiser vos ressources, ça ne va rien changer pour le serveur.

Ensuite, pour indiquer à FiveM comment utiliser la ressource, il faut un fichier très important: __resource.lua, attention, il y a 2 underscores ( ‘_’ ). Voici ce que l’on peut mettre dedans:

resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937' --Défini la version des fonctions à utiliser (https://docs.fivem.net/scripting-reference/resource-manifest/resource-manifest/#manifest-version-44febabe-d386-4d18-afbe-5e627f4af937-2017-06-07)

client_script "monScript.lua" --Défini un script à être exécuté par le client

client_scripts {
    "monScript1.lua",
    "monScript2.lua"
} -- Défini plusieurs scripts à être exécuté par le client

server_script "monScript_serveur.lua" -- Défini un script à être exécuté par le serveur

server_scripts {
    "monScript1_serveur.lua",
    "monScript2_serveur.lua"
} -- Défini plusieurs scripts à être exécuté par le serveur

export "maFonction" -- exporte une fonction pour être exécuté à l'extérieur

exports {
    "maFonction1",
    "maFonction2"
} -- exporte plusieurs fonctions

file "monFichier.truc" -- Défini un fichier utilisable par le client

files {
    "monFichier1.truc",
    "monFichier2.truc"
} -- Défini plusieurs fichiers utilisables par le client

ui_page "monUI.html" -- Défini le fichier html pour le NUI (Attention, doit être défini dans file ou files)

loadscreen "monLoadingScreen.html" -- Défini l'écran de chargement (Attention, doit être défini dans file ou files)

replace_level_meta "maMap" -- Replace le 'level meta' (Attention, doit être défini dans file ou files)

-- Ajoute des fichiers "data" avec un type spécifique 
data_file 'AUDIO_WAVEPACK' 'audio/mywaves'
data_file 'VEHICLE_METADATA_FILE' 'myvehicles.meta'

this_is_a_map 'yes' -- Fait de la ressource une 'map' 

server_only 'yes' -- Force la ressource à être uniquement exécuté par le serveur et ne pas être chargé par le client

dependency "autreRessource" -- Défini une dépendance

dependencies {
    "autreRessource1",
    "autreRessource2"
} -- Défini plusieurs dépendances

Vous pouvez aussi ajouter des données qui ne seront pas interprétées, comme l’auteur, une description, une version, etc…

resource_name "ESX"
author "fauconjona"
description "Une nouvelle version de ESX bien meilleure"
version "3.0"

Pour finir, à l’intérieur de votre ressource, vous pouvez utiliser des sous-dossiers pour organiser vos fichiers. Je vous conseille d’utiliser un dossier “server” pour les scripts qui sont exécutés par le serveur et un dossier “client” pour ceux exécutés par le client. Mais le fichier __resource.lua doit obligatoirement être à la racine de la ressource.

Les events

Soyez attentif ici, les events, événements en français, sont très importants sur FiveM, ce sont eux qui permettent la communication entre le client et le serveur dans les scripts et entre les différentes ressources. Déjà, contrairement à une fonction, ce n’est pas quelque chose que l’on appelle mais que l’on déclenche (trigger), oui ce n’est pas la même chose, une fonction est unique et va être appelée à un seul endroit, alors qu’un événement peut être déclenché à plusieurs endroits. Une autre différence très importante, alors qu’une fonction est généralement synchrone, un événement sera toujours asynchrone, ce qui veut dire que vous ne pouvez pas attendre de résultat, sauf en utilisant un callback.

C’est bien beau tous ça, mais comment qu’on fait?

Pour commencer, avant de déclencher un événement, il faut l’attendre quelque part, pour ça il suffit juste d’utiliser AddEventHandler, voici un exemple:

AddEventHandler("monEvent", function()
    print("monEvent a été déclenché")
end)

Comme vous pouvez le voir, on utilise un callback dans la fonction AddEventHandler pour exécuter ce que l’on veut faire dans cet événement, on a aussi la possibilité de lui passer des arguments:

AddEventHandler("monEvent", function(valeur)
    print("monEvent a été déclenché avec cette valeur: " .. valeur)
end)

Ensuite, pour déclencher l’événement, il faut tout simplement utiliser la fonction TriggerEvent:

TriggerEvent("monEvent", valeur)

Pour les arguments de l’event, il faut juste les mettre après le nom de l’événement.

Ce que je viens de vous montrer, ne va fonctionner que pour les événements qui restent en local (sur la même machine). Si vous voulez déclencher un événement pour le serveur depuis le client, il faut l’indiquer pour que le serveur l’autorise:

RegisterNetEvent("monEventServer")
AddEventHandler("monEventServer", function()
    print("monEventServer a été déclenché")
end)

Et si vous voulez faire l’inverse, il faut faire la même chose mais coté client:

RegisterNetEvent("monEventClient")
AddEventHandler("monEventClient", function()
    print("monEventClienta été déclenché")
end)

La différence que vous allez avoir est pour déclencher ces événements:

TriggerServerEvent("monEventServer") -- Déclenche un événement coté serveur depuis le client

TriggerClientEvent("monEventClient", player_id) --Déclenche un événement coté client, correspondant au player_id, depuis le serveur

Bon, je sais que vous allez me demander c’est quoi ce player_id, déjà, ça n’a rien à voir avec les steam id ou les autres trucs de ce genre. il s’agit d’un nombre permettant d’identifier le joueur depuis le serveur, il est déterminé au moment de la connexion de celui-ci. Grâce à cet id, vous pouvez déclencher un événement pour un joueur en particulier.

Lorsqu’un événement côté serveur est déclenché par un client, vous avez accès à une variable global source (Qui est accessible partout, donc pas en paramètre de l’événement). Vous pouvez récupérer l’id du joueur d’où provient l’événement qui est dans cette variable:

RegisterNetEvent("monEventServer")
AddEventHandler("monEventServer", function()
    local player_id = source
    print("monEventServer a été déclenché par " .. player_id)
    TriggerClientEvent("monEventClient", player_id)
end)

Si vous voulez envoyer un event à tous les joueurs en même temps depuis le serveur, vous pouvez utiliser -1:

TriggerClientEvent("monEventClient", -1) -- Envoyé à tout le monde

Attention, vous ne pouvez pas déclencher des événements directement entre les clients, vous êtes obligé de passer par le serveur, pour ensuite le déclencher pour le joueur voulut.

Si vous avez bien lu comment utiliser les callbacks, ce que je vais expliquer ne va pas vous perdre. Comme je l’ai dit, les événements sont asynchrones, donc vous ne pouvez pas attendre de résultat sauf en utilisant un callback. Voici un petit exemple:

AddEventHandler("monEventAvecCB", function(cb)
    print("Déclenchement de mon event")
    cb("résultat quelconque")
end)

TriggerEvent("monEventAvecCB", function(resultat)
    print("Résultat: " .. resultat)
end)

--Dans l'ordre:

--Déclenchement de mon event
--Résultat: résultat quelconque

Les Threads

Alors, ce n’est pas très long, mais important à comprendre. Déjà un thread est l’exécution d’une partie d’un programme en parallèle, ça permet de faire plusieurs choses en même temps, c’est de l’asynchrone. Sur FiveM, ces threads sont très importants car c’est uniquement dans ceux-là que vous pouvez appeler des fonctions “Natives” de GTA. C’est très important aussi de noter que si vous faites un wait (“Citizen.Wait()”) et que vous n’êtes pas dans un thread, le jeu va freeze complètement.

Donc pour créer un thread, il faut juste utiliser la fonction CreateThread:

CreateThread(function()
    --Vous êtes maintenant dans un thread
end)

Comme je l’ai dis juste avant, lorsqu’un événement est déclenché, c’est asynchrone, donc c’est dans un thread.

Je vous l’avais expliqué plus haut, il n’est pas recommandé de faire des boucles infinies (while true do), mais sur FiveM, vous pouvez le faire dans un thread:

CreateThread(function()
    while true do
        Citizen.Wait(0) 
        -- exécuté à chaque frame
    end
end)

C’est très souvent utilisé pour des actions qui doivent être exécutées à chaque frame et comme vous le voyez il y a Citizen.Wait(0), qui permet de limiter le processus à la vitesse d’exécution du programme, si vous ne le mettez pas, vous risquez de faire tout planter.

Attention! Il est très important de savoir qu’exécuter quelque chose à chaque frame est gourmand, donc faites le si c’est vraiment nécessaire.

Exemple, si vous avez besoin de savoir si un joueur est dans un véhicule ou non, vous pouvez faire cette vérification toutes les 500 millisecondes:

local inVehicle = false

CreateThread(function()
    while true do
        Citizen.Wait(500) -- 500ms 
        inVehicle = IsPedInAnyVehicle(PlayerPedId(), false) -- Si le joueur est dans n'importe quel véhicule
    end
end)

Les Commandes

C’est une partie très facile à comprendre et surtout à mettre en pratique. Je ne vais pas vous expliquer comment configurer les permissions, mais je peux vous rediriger ici.

Une commande est un texte que l’on exécute dans la console, que ce soit côté client avec F8 ou le chat, ou côté serveur via un RCON. Elle est composé du nom de la commande, en un mot, et des différents paramètres, et peut-être défini côté client ou côté serveur.

Pour créer une commande, une fonction toute simple permet de le faire:

RegisterCommand("maCommande", function(source, args, raw)
    -- source: player_id de celui qui fait la commande
    -- args: arguments de la commande dans une table
    -- raw: texte complet de la commande entré par l'utilisateur
end, 
false) -- permet d'utiliser ou non les permissions ACE (true -> oui / false -> non)

Petit exemple pour un simple TP:

RegisterCommand("tp", function(source, args, raw)
    local x = args[1]
    local y = args[2]
    local z = args[3]
    SetEntityCoords(PlayerPedId(), tonumber(x), tonumber(y), tonumber(z), 1, 0, 0, 1) 
end, false)

Bien entendu cette commande n’est pas à utiliser tel quel, il manque certaines choses, mais c’est pour expliquer.

Donc ici, n’importe quel joueur peut taper ceci dans le chat: /tp 217.80 65.04 -1009.53 et se retrouver à un endroit super cool :kappa:. Comme vous pouvez le voir, j’utilise tonumber sur les coordonnées, c’est parce que les valeurs dans args sont des strings, il faut donc les convertir dans le bon type.

Les Controls

Attention, il ne faut pas confondre les controls avec un key handler, sur FiveM on ne peut pas détecter la pression d’une touche mais uniquement la pression d’un control. Oui ce n’est pas la même chose, un control est une action prédéfinie du jeu, comme: avancer, reculer, sauter, etc…

Il faut donc prendre en compte que les joueurs peuvent changer leurs touches, se dire que l’action qui est par défaut sur W ne veut pas dire que le joueur appui forcement sur la touche W et se dire que le joueur peut aussi déclencher un control en utilisant sa manette, mais ne vous inquiétez pas, il y a un moyen de détecter ça.

Liste des controls

Maintenant, comment ça fonctionne? On peut détecter plusieurs choses sur un control, s’il est actuellement pressé/relâché ou s’il vient juste d’être pressé/relâché. Pour ce faire, il faut créer une boucle de jeu qui va vérifier l’état du control à chaque frame:

CreateThread(function()
	while true do
		if IsControlPressed(1, 38) then 
			print("'Ramasser' est actuellement pressé")
		end
		
		if IsControlReleased(1, 38) then 
			print("'Ramasser' est actuellement relâché")
		end
		
		if IsControlJustPressed(1, 38) then 
			print("'Ramasser' vient d'être pressé")
		end
		
		if IsControlJustReleased(1, 38) then 
			print("'Ramasser' vient d'être relâché")
		end
	end
end)

Vous pouvez aussi, si vous le souhaitez, vous pouvez désactiver des controls:

DisableControlAction(1, 38, true) -- Désactiver
EnableControlAction(1, 38, true) -- Activer

Attention! Si vous en désactivez, il faut utiliser d’autres fonctions pour détecter la pression/relâchement:

CreateThread(function()
	while true do	
		if IsDisabledControlJustPressed(1, 38) then 
			print("'Ramasser' vient d'être pressé")
		end
		
		if IsDisabledControlJustReleased(1, 38) then 
			print("'Ramasser' vient d'être relâché")
		end
	end
end)

Maintenant, avec une fonction vous pouvez détecter si le dernier control était via le clavier ou la manette:

if IsInputDisabled(0) then
    print("Clavier")
else 
    print("Manette")
end

Les Exports

Comme vous le savez, FiveM utilise des ressources et les scripts qui sont exécutés à l’intérieur sont locaux, en gros tout ce qui se passe dans une ressource reste dans cette ressource. Comme on l’a vu plus haut, on peut utiliser des événements pour communiquer entre elles, mais c’est asynchrone et on ne veut pas forcément déclencher quelque chose dans plusieurs ressources en même temps. La solution à ce problème sont les exports, qui sont des fonctions accessibles et utilisables depuis l’extérieur.

Pour exporter une fonction, il y a plusieurs manière, mais voici la plus efficace:

function FaireCoucou()
    print('coucou')
end

exports("FaireCoucou", FaireCoucou)

-- Ou directement:
exports("FaireCoucou", function()
    print('coucou')
end)

la fonction sera alors accessible depuis n’importe quelle ressource de cette manière:

exports["ma-ressource"]:FaireCoucou()

Vous pouvez aussi déclarer des exports dans le fichier __resource.lua directement, mais elles ne seront accessibles qu’à la fin de l’initialisation de la ressource:

client_script "client.lua"
export "FaireCoucou"

server_script "server.lua"
server_export "FaireCoucouServeur"

Fin !

J’espère que je ne vous ai pas trop perdu, prenez votre temps et faites des tests en même temps. Je serai ravis de vous éclairer sur le discord mais uniquement si vous faites l’effort de chercher et d’essayer.