Des scripts RedM complets, commentés et prêts à l'emploi. Ils fonctionnent sans framework, sauf ceux marqués VORP. Chacun tient dans une seule ressource.
- Créez un dossier dans
resources/[scripts]/, par exempleeso-cheval. - Créez dedans les fichiers indiqués avec le gestionnaire de fichiers du panel ou en SFTP.
- Ajoutez
ensure eso-chevalau server.cfg. - Redémarrez le serveur depuis txAdmin.
Les commandes marquées « admin » utilisent RegisterCommand(..., true). Donnez les droits avec add_ace group.admin command allow et add_principal identifier.fivem:VOTRE_ID group.admin dans le server.cfg.
Le manifeste commun
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'
author 'EsoterHost'
version '1.0.0'
shared_script 'config.lua' -- si le script a un config.lua
client_script 'client.lua'
server_script 'server.lua'La phrase doit rester exactement identique, en anglais, sinon la ressource ne démarre pas.
Fonctions utilitaires
Plusieurs scripts utilisent ces deux fonctions d'affichage. Ajoutez-les en haut de client.lua quand un script les appelle.
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
end1. Téléportation : /tp et /tpm (admin)
local function teleporter(x, y, z)
local ped = PlayerPedId()
DoScreenFadeOut(300)
while not IsScreenFadedOut() do Wait(0) end
SetEntityCoords(ped, x, y, z, false, false, false, false)
-- Si aucune hauteur n'est donnée, on cherche le sol
if z == 0.0 then
for hauteur = 0, 800, 25 do
SetEntityCoords(ped, x, y, hauteur + 0.0, false, false, false, false)
Wait(30)
local ok, sol = GetGroundZFor_3dCoord(x, y, hauteur + 0.0, false)
if ok then SetEntityCoords(ped, x, y, sol + 1.0, false, false, false, false) break end
end
end
DoScreenFadeIn(300)
end
RegisterCommand('tp', function(_, args)
local x, y, z = tonumber(args[1]), tonumber(args[2]), tonumber(args[3])
if not x or not y then
return TriggerEvent('chat:addMessage', { args = { 'TP', 'Utilisation : /tp x y [z]' } })
end
teleporter(x, y, z or 0.0)
end, true)
RegisterCommand('tpm', function()
-- GET_WAYPOINT_COORDS (le point de passage posé sur la carte)
local c = GetWaypointCoords()
if c.x == 0.0 and c.y == 0.0 then
return TriggerEvent('chat:addMessage', { args = { 'TP', 'Posez un point de passage sur la carte' } })
end
teleporter(c.x, c.y, 0.0)
end, true)2. Faire apparaître un cheval : /cheval (admin)
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
Citizen.InvokeNative(0x028F76B6E78246EB, ped, cheval, -1) -- _SET_PED_ONTO_MOUNT
SetModelAsNoLongerNeeded(hash)
end, true)| Modèle | Cheval |
|---|---|
A_C_Horse_Arabian_White | Arabe blanc |
A_C_Horse_Turkoman_Gold | Turkmène doré |
A_C_Horse_Thoroughbred_DappleGrey | Pur-sang gris pommelé |
A_C_Horse_MissouriFoxTrotter_SilverDapplePinto | Missouri Fox Trotter |
A_C_Horse_Mustang_GoldenDun | Mustang isabelle |
A_C_Horse_Shire_DarkBay | Shire bai foncé (trait) |
3. Soigner et réanimer : /heal et /revive (admin)
RegisterCommand('heal', function()
local ped = PlayerPedId()
SetEntityHealth(ped, GetEntityMaxHealth(ped))
end, true)
RegisterCommand('revive', function()
local ped = PlayerPedId()
local c = GetEntityCoords(ped)
NetworkResurrectLocalPlayer(c.x, c.y, c.z, GetEntityHeading(ped), true, false)
SetEntityHealth(PlayerPedId(), GetEntityMaxHealth(PlayerPedId()))
ClearPedTasksImmediately(PlayerPedId())
end, true)VORP gère la mort et la réanimation lui-même. Sur un serveur VORP, utilisez ses commandes d'administration plutôt que /revive.
4. Repères sur la carte
Config = {}
Config.Reperes = {
{ nom = 'Saloon de Valentine', coords = vector3(-313.0, 805.0, 118.98), sprite = 'blip_saloon' },
{ nom = 'Bureau du shérif', coords = vector3(-278.0, 805.4, 119.38), sprite = 'blip_ambient_sheriff' },
{ nom = 'Magasin général', coords = vector3(-324.3, 803.9, 117.88), sprite = 'blip_shop_store' },
{ nom = 'Écurie', coords = vector3(-365.2, 791.9, 116.18), sprite = 'blip_shop_horse' },
}local reperes = {}
CreateThread(function()
for _, r in ipairs(Config.Reperes) do
-- BLIP_ADD_FOR_COORDS, 1664425300 = style de repère standard
local blip = Citizen.InvokeNative(0x554D9D53F696D002, 1664425300, r.coords.x, r.coords.y, r.coords.z)
SetBlipSprite(blip, joaat(r.sprite), true)
SetBlipScale(blip, 0.2)
Citizen.InvokeNative(0x9CB1A1623062F402, blip, r.nom) -- _SET_BLIP_NAME
reperes[#reperes + 1] = blip
end
end)
-- Retire les repères quand la ressource s'arrête
AddEventHandler('onResourceStop', function(nom)
if nom ~= GetCurrentResourceName() then return end
for _, blip in ipairs(reperes) do RemoveBlip(blip) end
end)5. Heure synchronisée : /heure (admin)
Tous les joueurs voient la même heure, qui avance avec le temps.
GlobalState.heure = GlobalState.heure or 9
GlobalState.minute = GlobalState.minute or 0
-- Une minute de jeu toutes les 2 secondes réelles
CreateThread(function()
while true do
Wait(2000)
local m, h = GlobalState.minute + 1, GlobalState.heure
if m >= 60 then m, h = 0, (h + 1) % 24 end
GlobalState.minute, GlobalState.heure = m, h
end
end)
RegisterCommand('heure', function(_, args)
local h, m = tonumber(args[1]), tonumber(args[2] or 0)
if h and h >= 0 and h < 24 then GlobalState.heure, GlobalState.minute = h, m or 0 end
end, true)CreateThread(function()
while true do
NetworkOverrideClockTime(GlobalState.heure or 9, GlobalState.minute or 0, 0)
Wait(1000)
end
end)6. Météo synchronisée : /meteo (admin)
GlobalState.meteo = GlobalState.meteo or 'SUNNY'
local METEOS = {
SUNNY = true, CLOUDS = true, OVERCAST = true, DRIZZLE = true, RAIN = true,
THUNDERSTORM = true, FOG = true, MISTY = true, SNOW = true, SNOWLIGHT = true, BLIZZARD = true,
}
RegisterCommand('meteo', function(src, args)
local m = (args[1] or ''):upper()
if METEOS[m] then
GlobalState.meteo = m
elseif src ~= 0 then
TriggerClientEvent('chat:addMessage', src, { args = { 'Météo', 'SUNNY, CLOUDS, OVERCAST, DRIZZLE, RAIN, THUNDERSTORM, FOG, MISTY, SNOW, SNOWLIGHT, BLIZZARD' } })
end
end, true)local actuelle = nil
CreateThread(function()
while true do
local cible = GlobalState.meteo or 'SUNNY'
if cible ~= actuelle then
-- _SET_WEATHER_TYPE (type, p1, p2, transition, durée, p5)
Citizen.InvokeNative(0x59174F1AFE095B5A, joaat(cible), true, false, true, 15.0, false)
actuelle = cible
end
Wait(5000)
end
end)7. Annonce à tout le serveur : /annonce (admin)
RegisterCommand('annonce', function(src, args)
local message = table.concat(args, ' ')
if message == '' then return end
TriggerClientEvent('chat:addMessage', -1, { color = { 200, 60, 40 }, args = { 'Le télégraphe', message } })
TriggerClientEvent('eso-annonce:afficher', -1, message)
end, true)-- Affiche aussi l'annonce au centre de l'écran pendant 8 secondes
-- (ajoutez la fonction texteEcran des utilitaires en haut du fichier)
RegisterNetEvent('eso-annonce:afficher', function(message)
local fin = GetGameTimer() + 8000
CreateThread(function()
while GetGameTimer() < fin do
texteEcran('ANNONCE', 0.5, 0.08, 0.5)
texteEcran(message, 0.5, 0.12, 0.42)
Wait(0)
end
end)
end)8. Liste blanche par identifiant
Ce script est purement serveur : il fonctionne à l'identique sur FiveM et RedM.
Config = {}
Config.Message = "Ce serveur est en liste blanche. Faites votre demande sur notre Discord : discord.gg/votre-code"
Config.Autorises = {
['license:0123456789abcdef0123456789abcdef01234567'] = true,
['discord:123456789012345678'] = true,
}AddEventHandler('playerConnecting', function(nom, setKickReason, deferrals)
local src = source
deferrals.defer()
Wait(0)
deferrals.update(('Vérification de la liste blanche pour %s…'):format(nom))
for _, id in ipairs(GetPlayerIdentifiers(src)) do
if Config.Autorises[id] then return deferrals.done() end
end
deferrals.done(Config.Message)
end)9. Journal des connexions sur Discord
Créez un webhook dans les paramètres du salon Discord, puis ajoutez au server.cfg :
set eso_logs_webhook "https://discord.com/api/webhooks/xxxx/yyyy"local WEBHOOK = GetConvar('eso_logs_webhook', '')
local function envoyer(titre, description, couleur)
if WEBHOOK == '' then return end
PerformHttpRequest(WEBHOOK, function() end, 'POST', json.encode({
username = 'Journal de la frontière',
embeds = { { title = titre, description = description, color = couleur, timestamp = os.date('!%Y-%m-%dT%H:%M:%SZ') } },
}), { ['Content-Type'] = 'application/json' })
end
AddEventHandler('playerJoining', function()
local src = source
local ids = {}
for _, id in ipairs(GetPlayerIdentifiers(src)) do
if not id:find('^ip:') then ids[#ids + 1] = '`' .. id .. '`' end
end
envoyer('Arrivée', ('**%s**\n%s'):format(GetPlayerName(src), table.concat(ids, '\n')), 3066993)
end)
AddEventHandler('playerDropped', function(raison)
envoyer('Départ', ('**%s**\nRaison : %s'):format(GetPlayerName(source), raison), 15158332)
end)10. Expulsion des joueurs inactifs (anti-AFK)
local LIMITE = 20 * 60
local AVERTISSEMENTS = { 120, 60 }
local derniereAction, dernierePos, averti = GetGameTimer(), vector3(0, 0, 0), {}
CreateThread(function()
while true do
Wait(5000)
local pos = GetEntityCoords(PlayerPedId())
if #(pos - dernierePos) > 1.0 then
derniereAction, dernierePos, averti = GetGameTimer(), pos, {}
end
local restant = LIMITE - (GetGameTimer() - derniereAction) // 1000
if restant <= 0 then
TriggerServerEvent('eso-afk:expulser')
else
for _, seuil in ipairs(AVERTISSEMENTS) do
if restant <= seuil and not averti[seuil] then
averti[seuil] = true
TriggerEvent('chat:addMessage', { args = { 'AFK', ('Bougez, sinon vous serez expulsé dans %d secondes'):format(restant) } })
end
end
end
end
end)RegisterNetEvent('eso-afk:expulser', function()
if IsPlayerAceAllowed(source, 'afk.ignorer') then return end
DropPlayer(source, 'Expulsé pour inactivité. Vous pouvez vous reconnecter.')
end)11. Texte au-dessus du joueur : /me
RegisterCommand('me', function(src, args)
local texte = table.concat(args, ' ')
if src == 0 or texte == '' or #texte > 120 then return end
TriggerClientEvent('eso-me:afficher', -1, src, texte)
end, false)-- (ajoutez texteEcran et texte3D des utilitaires en haut du fichier)
RegisterNetEvent('eso-me:afficher', function(serverId, texte)
local joueur = GetPlayerFromServerId(serverId)
if joueur == -1 then return end
local ped = GetPlayerPed(joueur)
if #(GetEntityCoords(ped) - GetEntityCoords(PlayerPedId())) > 25.0 then return end
local fin = GetGameTimer() + 6000
CreateThread(function()
while GetGameTimer() < fin do
texte3D(GetEntityCoords(ped) + vector3(0.0, 0.0, 1.0), '* ' .. texte .. ' *')
Wait(0)
end
end)
end)12. Signalement aux administrateurs : /report
local derniers = {}
RegisterCommand('report', function(src, args)
if src == 0 then return end
local message = table.concat(args, ' ')
if message == '' then return end
if derniers[src] and os.time() - derniers[src] < 60 then
return TriggerClientEvent('chat:addMessage', src, { args = { 'Report', 'Patientez une minute entre deux signalements' } })
end
derniers[src] = os.time()
for _, id in ipairs(GetPlayers()) do
if IsPlayerAceAllowed(id, 'report.recevoir') then
TriggerClientEvent('chat:addMessage', id, { color = { 255, 80, 80 }, args = { ('Report de %s [%s]'):format(GetPlayerName(src), src), message } })
end
end
TriggerClientEvent('chat:addMessage', src, { args = { 'Report', "Message transmis à l'équipe" } })
end, false)
AddEventHandler('playerDropped', function() derniers[source] = nil end)Donnez le droit de lecture avec add_ace group.admin report.recevoir allow.
13. VORP : salaire automatique
Verse un salaire aux joueurs selon leur métier, toutes les 20 minutes.
Config = {}
Config.Intervalle = 20 * 60 * 1000
Config.Salaires = {
sheriff = 12.0,
medecin = 10.0,
forgeron = 6.0,
chomeur = 1.0,
}local VORPcore = exports.vorp_core:GetCore()
CreateThread(function()
while true do
Wait(Config.Intervalle)
for _, src in ipairs(GetPlayers()) do
local user = VORPcore.getUser(tonumber(src))
local perso = user and user.getUsedCharacter
if perso then
local paie = Config.Salaires[perso.job] or Config.Salaires.chomeur
perso.addCurrency(0, paie)
VORPcore.NotifyRightTip(tonumber(src), ('Paie reçue : %.2f $'):format(paie), 4000)
end
end
end
end)14. VORP : objet utilisable (gourde de whisky)
INSERT INTO items (item, label, `limit`, can_remove, type, usable, `desc`)
VALUES ('gourde_whisky', 'Gourde de whisky', 3, 1, 'item_standard', 1, 'Redonne un peu de santé');local VORPcore = exports.vorp_core:GetCore()
exports.vorp_inventory:registerUsableItem('gourde_whisky', function(data)
local src = data.source
exports.vorp_inventory:closeInventory(src)
exports.vorp_inventory:subItem(src, 'gourde_whisky', 1)
TriggerClientEvent('eso-whisky:boire', src)
VORPcore.NotifyRightTip(src, 'Une gorgée de whisky vous remet sur pied', 3000)
end)RegisterNetEvent('eso-whisky:boire', function()
local ped = PlayerPedId()
SetEntityHealth(ped, math.min(GetEntityHealth(ped) + 30, GetEntityMaxHealth(ped)))
end)15. VORP : avis de recherche et récompense
Un administrateur met une prime sur un joueur. Celui qui le livre au shérif touche la récompense.
local VORPcore = exports.vorp_core:GetCore()
local primes = {} -- [serverId] = montant
local BUREAU = vector3(-278.0, 805.4, 119.38)
RegisterCommand('avis', 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
primes[cible] = (primes[cible] or 0) + montant
TriggerClientEvent('chat:addMessage', -1, { color = { 200, 60, 40 }, args = { 'Avis de recherche', ('%s est recherché mort ou vif : %d $'):format(GetPlayerName(cible), primes[cible]) } })
end, true)
-- /livrer [id] : le chasseur de primes livre la cible au bureau du shérif
RegisterCommand('livrer', function(src, args)
local cible = tonumber(args[1])
if not cible or not primes[cible] then return end
local posChasseur = GetEntityCoords(GetPlayerPed(src))
local posCible = GetEntityCoords(GetPlayerPed(cible))
if #(posChasseur - BUREAU) > 10.0 or #(posCible - BUREAU) > 10.0 then
return VORPcore.NotifyRightTip(src, 'Amenez la cible au bureau du shérif', 4000)
end
local user = VORPcore.getUser(src)
if not user then return end
user.getUsedCharacter.addCurrency(0, primes[cible] + 0.0)
VORPcore.NotifyRightTip(src, ('Récompense touchée : %d $'):format(primes[cible]), 5000)
primes[cible] = nil
end, false)
AddEventHandler('playerDropped', function() primes[source] = nil end)Aller plus loin
- Les notions utilisées ici, expliquées pas à pas : Créer un script RedM en Lua.
- L'API de VORP en détail : Installer VORP Core sur RedM.
- Plus de scripts purement serveur (annonces, journaux, liste blanche) : la bibliothèque de scripts FiveM, dont les scripts serveur fonctionnent aussi sur RedM avec le manifeste ci-dessus.
Ces scripts sont libres : modifiez-les et utilisez-les sur votre serveur.
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.
