Ce tutoriel vous apprend à écrire vos propres ressources RedM en Lua. Les bases sont les mêmes que sur FiveM (client, serveur, événements, commandes), avec une particularité : beaucoup de natives de Red Dead Redemption 2 n'ont pas encore de nom et s'appellent par leur code. Chaque notion est illustrée par un exemple complet.
Client et serveur
| Côté | Où le code s'exécute | À quoi il sert |
|---|---|---|
| Client | Sur le PC de chaque joueur | Afficher, lire les touches, agir sur le personnage, le cheval, le monde |
| Serveur | Sur votre serveur | Garder les données, décider, vérifier, parler à la base de données |
Règle d'or : le client peut être modifié par un tricheur, le serveur non. L'argent, les objets et les droits se décident côté serveur.
Étape 1 : la ressource minimale
Créez un dossier ma-ressource dans resources/ :
fx_version 'cerulean'
game 'rdr3'
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
lua54 'yes'
name 'ma-ressource'
version '1.0.0'
shared_script 'config.lua'
client_script 'client.lua'
server_script 'server.lua'Config = {}
Config.Saloon = vector3(-313.0, 805.0, 118.98) -- saloon de Valentineprint('Client chargé')print('Serveur chargé')Ajoutez ensure ma-ressource au server.cfg et redémarrez. Pendant le développement, refresh puis ensure ma-ressource dans la Live Console de txAdmin rechargent la ressource sans couper le serveur.
La phrase doit être copiée exactement, sans traduction ni modification, sinon la ressource ne démarre pas.
Étape 2 : une première commande
RegisterCommand('position', function()
local ped = PlayerPedId()
local c = GetEntityCoords(ped)
print(('vector3(%.2f, %.2f, %.2f), cap %.2f'):format(c.x, c.y, c.z, GetEntityHeading(ped)))
TriggerEvent('chat:addMessage', { args = { 'Position', ('%.2f, %.2f, %.2f'):format(c.x, c.y, c.z) } })
end, false)Le dernier argument false ouvre la commande à tous. Avec true, elle est réservée aux joueurs qui ont le droit ACE command.position.
Étape 3 : les natives de Red Dead Redemption 2
Les natives sont les fonctions du jeu. Sur RedM, il en existe deux sortes :
- Les natives nommées, appelées directement :
PlayerPedId(),GetEntityCoords(),SetEntityCoords(),SetEntityHealth(),CreatePed(),NetworkResurrectLocalPlayer()… - Les natives sans nom, appelées par leur code hexadécimal avec
Citizen.InvokeNative(0x..., paramètres).
La référence complète, avec les noms trouvés par la communauté, est sur rdr3natives.com et dans la documentation de VORP.
-- Native nommée : soigner le joueur
SetEntityHealth(PlayerPedId(), GetEntityMaxHealth(PlayerPedId()))
-- Native appelée par son code : donner une tenue aléatoire à un PNJ ou un cheval
-- (0x283978A15512B2FE = _SET_RANDOM_OUTFIT_VARIATION)
Citizen.InvokeNative(0x283978A15512B2FE, ped, true)Notez toujours le nom de la native en commentaire à côté de son code : dans six mois, 0x283978A15512B2FE ne vous dira plus rien.
Natives très utilisées sur RedM :
| Code ou nom | Rôle |
|---|---|
PlayerPedId() | Le personnage du joueur |
GetEntityCoords(entite) | Position d'une entité |
SetEntityCoords(entite, x, y, z) | Téléporter |
NetworkResurrectLocalPlayer(x, y, z, cap, true, false) | Réanimer le joueur |
NetworkOverrideClockTime(h, m, s) | Imposer l'heure |
0x283978A15512B2FE | Tenue aléatoire d'un PNJ ou d'un cheval |
0x028F76B6E78246EB | Mettre un personnage en selle sur une monture |
0x554D9D53F696D002 | Créer un repère (blip) à des coordonnées |
0x9CB1A1623062F402 | Donner un nom à un repère |
Étape 4 : écrire à l'écran
Les fonctions de texte de GTA V n'existent pas sur RedM. On crée une chaîne avec CreateVarString puis on l'affiche avec DisplayText.
local function texteEcran(texte, x, y, taille)
SetTextScale(taille or 0.4, taille or 0.4)
SetTextColor(255, 255, 255, 230)
SetTextCentre(true)
SetTextDropshadow(1, 0, 0, 0, 255)
DisplayText(CreateVarString(10, 'LITERAL_STRING', texte), x, y)
end
local function texte3D(coords, texte)
local visible, x, y = GetScreenCoordFromWorldCoord(coords.x, coords.y, coords.z)
if visible then texteEcran(texte, x, y, 0.35) end
endÉtape 5 : un point d'interaction avec une touche
Sur RedM, les touches se désignent par un code (hash). Quelques touches courantes :
| Touche | Code |
|---|---|
| E | 0xCEFD9220 |
| G | 0x760A9C6F |
| R | 0xE30CD707 |
| Entrée | 0xC7B5340A |
| Retour arrière | 0x156F7119 |
local TOUCHE_E = 0xCEFD9220
CreateThread(function()
while true do
local attente = 1000
local distance = #(GetEntityCoords(PlayerPedId()) - Config.Saloon)
if distance < 15.0 then
attente = 0
if distance < 2.0 then
texte3D(Config.Saloon + vector3(0.0, 0.0, 1.0), 'Appuyez sur E pour commander un verre')
if IsControlJustReleased(0, TOUCHE_E) then
TriggerServerEvent('ma-ressource:commander')
end
end
end
Wait(attente)
end
end)La boucle ne tourne à chaque image (Wait(0)) que lorsque le joueur est proche. Loin, elle ne vérifie qu'une fois par seconde. Vérifiez le coût de vos ressources avec resmon 1 dans la console F8.
Étape 6 : les événements réseau
RegisterNetEvent('ma-ressource:commander', function()
local src = source
-- Vérifier côté serveur que le joueur est bien au comptoir (OneSync)
local ped = GetPlayerPed(src)
if #(GetEntityCoords(ped) - Config.Saloon) > 4.0 then return end
TriggerClientEvent('chat:addMessage', src, { args = { 'Saloon', 'Le barman vous sert un whisky.' } })
TriggerClientEvent('ma-ressource:boire', src)
end)RegisterNetEvent('ma-ressource:boire', function()
-- Petite animation : on récupère un peu de santé
local ped = PlayerPedId()
SetEntityHealth(ped, math.min(GetEntityHealth(ped) + 25, GetEntityMaxHealth(ped)))
end)| Fonction | Sens |
|---|---|
TriggerServerEvent('nom', ...) | Client vers serveur |
TriggerClientEvent('nom', joueur, ...) | Serveur vers un joueur (-1 pour tous) |
RegisterNetEvent('nom', fonction) | Écouter un événement réseau |
Étape 7 : faire apparaître un cheval
Un exemple complet qui combine chargement de modèle, natives nommées et natives par code.
RegisterCommand('cheval', function(_, args)
local modele = args[1] or 'A_C_Horse_Arabian_White'
local hash = joaat(modele)
if not IsModelValid(hash) then
return TriggerEvent('chat:addMessage', { args = { 'Écurie', 'Modèle inconnu : ' .. modele } })
end
RequestModel(hash)
local limite = GetGameTimer() + 5000
while not HasModelLoaded(hash) and GetGameTimer() < limite do Wait(10) end
if not HasModelLoaded(hash) then return end
local ped = PlayerPedId()
local pos = GetOffsetFromEntityInWorldCoords(ped, 0.0, 3.0, 0.0)
local cheval = CreatePed(hash, pos.x, pos.y, pos.z, GetEntityHeading(ped), true, true, false, false)
Citizen.InvokeNative(0x283978A15512B2FE, cheval, true) -- _SET_RANDOM_OUTFIT_VARIATION : selle et apparence
Citizen.InvokeNative(0x028F76B6E78246EB, ped, cheval, -1) -- _SET_PED_ONTO_MOUNT : le joueur monte en selle
SetModelAsNoLongerNeeded(hash)
end, true)Quelques modèles de chevaux : A_C_Horse_Arabian_White, A_C_Horse_Turkoman_Gold, A_C_Horse_Thoroughbred_DappleGrey, A_C_Horse_MissouriFoxTrotter_SilverDapplePinto, A_C_Horse_Mustang_GoldenDun.
Étape 8 : la base de données
Déclarez oxmysql dans le manifeste :
fx_version 'cerulean'
game 'rdr3'
rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.'
lua54 'yes'
dependency 'oxmysql'
client_script 'client.lua'
server_scripts { '@oxmysql/lib/MySQL.lua', 'server.lua' }CREATE TABLE IF NOT EXISTS primes (
identifier VARCHAR(64) PRIMARY KEY,
nom VARCHAR(64) NOT NULL,
montant INT NOT NULL DEFAULT 0
);-- /prime [id] [montant] : met une prime sur la tête d'un joueur (admin)
RegisterCommand('prime', function(src, args)
local cible, montant = tonumber(args[1]), tonumber(args[2])
if not cible or not montant or montant <= 0 or not GetPlayerName(cible) then return end
local license = GetPlayerIdentifierByType(cible, 'license')
MySQL.insert.await('INSERT INTO primes (identifier, nom, montant) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE montant = montant + VALUES(montant), nom = VALUES(nom)',
{ license, GetPlayerName(cible), montant })
TriggerClientEvent('chat:addMessage', -1, {
color = { 200, 60, 40 },
args = { 'Avis de recherche', ('%s est recherché : %d $ de récompense'):format(GetPlayerName(cible), montant) },
})
end, true)
-- /primes : les 5 plus grosses primes
RegisterCommand('primes', function(src)
local rows = MySQL.query.await('SELECT nom, montant FROM primes ORDER BY montant DESC LIMIT 5')
for i, r in ipairs(rows) do
local ligne = ('%d. %s : %d $'):format(i, r.nom, r.montant)
if src == 0 then print(ligne) else TriggerClientEvent('chat:addMessage', src, { args = { 'Primes', ligne } }) end
end
end, false)Étape 9 : utiliser VORP
Sur un serveur VORP, l'argent, l'inventaire et les métiers passent par l'API de VORP plutôt que par votre propre base.
local VORPcore = exports.vorp_core:GetCore()
RegisterNetEvent('ma-ressource:commander', function()
local src = source
local user = VORPcore.getUser(src)
if not user then return end
local perso = user.getUsedCharacter
local PRIX = 2.0
if perso.money < PRIX then
return VORPcore.NotifyRightTip(src, "Vous n'avez pas assez d'argent", 4000)
end
perso.removeCurrency(0, PRIX) -- 0 = dollars, 1 = or
VORPcore.NotifyRightTip(src, ('Whisky servi pour %.2f $'):format(PRIX), 4000)
TriggerClientEvent('ma-ressource:boire', src)
end)Tout le détail (installation, objets, métiers, inventaire) est dans le guide Installer VORP Core sur RedM.
Bonnes pratiques
- Copiez la ligne
rdr3_warningà l'identique dans chaque manifeste. - Commentez le nom de chaque native appelée par son code.
- Préfixez événements, commandes et exports avec le nom de la ressource.
- Gardez textes, positions et prix dans
config.lua. - Vérifiez côté serveur tout ce qui a de la valeur : position, fréquence, montants.
- Mesurez avec
resmon 1avant de publier.
Et ensuite ?
Des scripts complets prêts à copier vous attendent dans la bibliothèque de scripts RedM.
EsoterHost n'est ni approuvé, ni sponsorisé, ni affilié à Rockstar Games ou à Cfx.re. RedM et Red Dead Redemption 2 sont des marques de leurs propriétaires.
