Documentation · Développeurs

Référence de l'API

Callbacks, events, hooks, interactions, notifications, logs, permissions, exports.

SommaireRéférence de l'API

Lire cette page

Toutes les API de cette page sont disponibles dans la ressource `socle` (donc dans tes modules modules/<nom>/). Depuis une autre ressource, utilise les exports listés en fin de page.

Une différence de syntaxe à retenir, côté client : la classe d'events s'appelle `Event` (variable globale), côté serveur `Core.Event`.

Callbacks

Un callback est une question posée à l'autre côté, avec une réponse. C'est la forme à utiliser pour toute demande d'un joueur qui attend un résultat (acheter, réclamer, ouvrir un coffre…).

Client → serveur

Côté serveur, le gestionnaire reçoit l'identifiant du joueur, une fonction de réponse, puis les arguments du client :

-- serveur
Core.Callback:register("prime:server:reclamer", function(src, cb, quelquechose)
    -- toujours valider : type, bornes, distance, droits…
    if type(quelquechose) ~= "string" then return cb(false, "invalide") end
    cb(true, nil, 500)
end)

Côté client, deux façons d'attendre la réponse :

-- 1. avec une fonction : appelée avec les valeurs passées à cb côté serveur
Core.Callback:trigger("prime:server:reclamer", function(ok, raison, montant)
    if ok then print("reçu", montant) end
end, "valeur")

-- 2. avec true : renvoie une promesse ; Citizen.Await donne un tableau { ... }
CreateThread(function()
    local res = Citizen.Await(Core.Callback:trigger("prime:server:reclamer", true, "valeur"))
    local ok, raison, montant = res[1], res[2], res[3]
end)

Bon à savoir :

  • si le gestionnaire serveur plante, l'erreur est écrite en console et le client reçoit une réponse vide (nil) : prévois ce cas ;
  • cb ne répond qu'une fois : les appels suivants sont ignorés ;
  • un callback inconnu est signalé en console (Unknown callback) et répond vide ;
  • l'appel part par Event:send : il est signé (voir Architecture) et attend que Core soit prêt.

Serveur → client

Plus rare, pour demander une information au client (son état local, une saisie…). Le client enregistre :

-- client : respond renvoie la réponse au serveur
Core.Callback:register("prime:client:estAssis", function(respond)
    respond(IsPedUsingAnyScenario(PlayerPedId()))
end)

Et le serveur interroge :

-- serveur : attend la première valeur renvoyée (dans un thread)
local assis = Core.Callback:await(src, "prime:client:estAssis")

-- ou avec une fonction
Core.Callback:trigger(src, "prime:client:estAssis", function(assis) end)

Events

Serveur

FonctionRôle
Core.Event:register(nom, fn)reçoit un event signé d'un client. fn(src, ...)
Core.Event:registerRaw(nom, fn)reçoit un event non signé (venant d'une ressource externe). fn(src, ...)
Core.Event:registerLocal(nom, fn)event serveur local (AddEventHandler)
Core.Event:send(cible, nom, ...)envoie à un joueur
Core.Event:sendSegment(cible, nom, octetsParSeconde, ...)envoi « latent » pour un gros volume de données
Core.Event:broadcast(nom, ...)envoie à tous (à réserver à ce qui concerne vraiment tout le monde)
Core.Event:call(nom, ...)déclenche un event local
-- serveur : le joueur demande à relâcher ; l'état est relu côté serveur
Core.Event:register("prime:server:annuler", function(src)
    if not enCours[src] then return end
    enCours[src] = nil
    Core.Event:send(src, "prime:client:annule")
end)

Client

FonctionRôle
Event:send(nom, ...)envoie au serveur, signé (attend que Core soit prêt)
Event:sendSegment(nom, octetsParSeconde, ...)envoi latent signé
Event:register(nom, fn)reçoit un event du serveur (RegisterNetEvent). fn(...)
Event:registerLocal(nom, fn)event client local
Event:call(nom, ...)déclenche un event client local
-- client
Event:register("prime:client:annule", function()
    exports.socle:Notify(Core.t("prime.notify.annule"), "info")
end)

Event:send("prime:server:annuler")

N'utilise pas TriggerServerEvent vers un event enregistré avec Core.Event:register : il n'a pas de signature et sera refusé dès que network_token est réglé.

Hooks

Les hooks sont un bus local (pas réseau) : un fichier annonce, d'autres écoutent, dans la même ressource et du même côté.

-- écouter (toujours)
Core.Hook.listen("prime:versee", function(src, montant)
    print(("prime de %d versée à %d"):format(montant, src))
end)

-- écouter une seule fois
Core.Hook.once("prime:versee", function(src, montant) end)

-- annoncer
Core.Hook.call("prime:versee", src, 500)

La syntaxe avec deux-points (Core.Hook:listen) fonctionne aussi. Une erreur dans un écouteur est attrapée et écrite en console sans arrêter les autres.

Hooks émis par le Socle :

HookCôtéArguments
variable:changedles deuxcatégorie, clé, valeur : un réglage a changé depuis le panel
permissions:changedclientaucun : les permissions du joueur local ont changé

Raccourcis clavier

Côté client, Core.Keybind:add crée un raccourci que chaque joueur peut changer dans ses paramètres GTA (Raccourcis FiveM). La touche par défaut vient d'un réglage du panel, jamais d'une constante :

-- client ; la touche par défaut n'est lue qu'à la création (variable en runtime = false)
CreateThread(function()
    Core.Keybind:add({
        name = "prime_ouvrir",                         -- identifiant unique
        description = Core.t("prime.keybind.ouvrir"),  -- texte affiché dans les paramètres GTA
        defaultKey = Core.Variable:get("prime", "touche") or "F7",
        onPressed = function() ouvrirMenu() end,        -- ignoré pendant le menu pause
        -- onReleased = function() end, secondaryKey = "…", hidden = true (absent de la liste du menu pause)
    })
end)

-- touche réellement choisie par le joueur, pour l'afficher (nil si inconnue)
local touche = Core.Keybind:getControlLabel("prime_ouvrir")

La touche par défaut ne s'applique qu'aux joueurs qui n'ont jamais réglé ce raccourci : déclare la variable correspondante avec runtime = false.

Points d'interaction

Le Socle gère tous les points « appuie sur E » de la base avec un seul thread de proximité. N'écris jamais ta propre boucle de distance.

-- client ; renvoie l'identifiant du point
local id = exports.socle:addInteractionPoint({
    coords = vector3(215.8, -810.1, 29.7),  -- obligatoire, un vector3
    dist = 2.0,                              -- distance de la touche (2.0 par défaut)
    key = "E",                               -- touche (E par défaut)
    message = Core.t("prime.point"),         -- texte affiché
    onPress = function() ouvrir() end,       -- fonction, ou nom d'un event client local
    ped = joaat("a_m_y_business_02"),        -- facultatif : PNJ créé sur le point
    heading = 160.0,                         -- orientation du PNJ ou de l'objet
    scenario = "WORLD_HUMAN_CLIPBOARD",      -- facultatif : scénario joué par le PNJ
})

exports.socle:removeInteractionPoint(id)
OptionRôle
coordsposition (vector3). Avec un PNJ, donne la position au sol (z - 1.0 d'une position de joueur)
distdistance à laquelle la touche est active
farDistdistance d'affichage de la balise lointaine (sinon dist + 2)
keyA… Z, ENTER, SPACE, TAB, F1…, chiffres ; E par défaut
messagetexte du point
onPressfonction appelée dans un nouveau thread, ou nom d'un event client local déclenché par TriggerEvent
ped, heading, scenarioPNJ figé et invincible, supprimé avec le point
prop, propHeadingobjet posé sur le point, supprimé avec le point
iconaccepté, facultatif

Tous les points créés par une ressource sont supprimés quand elle s'arrête.

Deux variantes :

-- point qui suit quelque chose de mobile : à rappeler au moins toutes les 500 ms,
-- il disparaît seul quand les appels cessent
exports.socle:addInteractionPointThisFrame("prime:coffre", {
    coords = GetEntityCoords(vehicule), dist = 1.5, message = Core.t("prime.coffre"),
    onPress = function() ouvrirCoffre() end,
})

-- vrai quand un point est à portée de touche (pour ne pas réagir à la même touche)
if exports.socle:isInteractionActive() then return end

Le style du point (balise, anneau, faisceau), la distance d'apparition et le cercle lointain se règlent dans la catégorie Interface du panel (hud.markerStyle, hud.markerDistance, hud.interactCircle).

Notifications et décisions

Notifications

-- client : Notify(message, type, duréeMs, options)
exports.socle:Notify(Core.t("prime.notify.recue", { 500 }), "success", 5000)
TypeRendu
successnotification de réussite
error, warningnotification d'erreur
info (ou tout autre)notification d'information
businessannonce d'entreprise : options = { company, businessTitle, number, img, color = { r, g, b } }
customoptions = { icon, iconColor }

ESX.ShowNotification(message, type, durée) est redirigé vers la même fonction : le code ESX existant affiche donc des notifications Socle. Côté serveur, utilise :

-- serveur
local xPlayer = Core.getPlayer(src)
xPlayer.showNotification(Core.t("prime.notify.recue", { montant }), "success")
-- ou
TriggerClientEvent("esx:showNotification", src, Core.t("prime.notify.recue", { montant }), "success")

Autres affichages côté client : exports.socle:StartProgress({ label, duration }) / CancelProgress(), exports.socle:HelpText(texte, duréeMs), exports.socle:Subtitle(texte, duréeMs), exports.socle:KeyHints({ { key = "E", label = "…" } }) / HideKeyHints().

Décisions (Oui / Non)

Une notification avec deux touches et un compte à rebours, sans souris : le joueur continue de jouer. Une seule décision s'affiche à la fois, les suivantes attendent.

-- client, dans un thread : bloque jusqu'à la réponse
CreateThread(function()
    local reponse = exports.socle:Decide({
        title = Core.t("prime.invite.titre"),      -- 80 caractères max
        message = Core.t("prime.invite.message"),  -- 300 caractères max
        yes = Core.t("prime.invite.accepter"),     -- 30 caractères max
        no = Core.t("prime.invite.refuser"),
        timeout = 15,                              -- secondes, entre 3 et 300
        -- color = { r, g, b }, img = "nui://…" (facultatifs)
    })
    -- reponse = "yes", "no" ou "timeout"
    Event:send("prime:server:reponse", reponse == "yes")
end)

Sans timeout, le délai vient du panel (hud.decisionTimeout, 20 s par défaut). Les touches par défaut sont hud.decisionYesKey et hud.decisionNoKey ; chaque joueur peut les changer dans ses raccourcis GTA. Le serveur doit revérifier la situation à la réception de la réponse : elle a pu changer pendant l'attente.

Logs

Chaque évènement important s'écrit dans la page Logs du panel et, si un webhook est réglé pour sa catégorie, sur Discord.

-- serveur
Core.Logs.push({
    category = "money",        -- 32 caractères max ; "general" par défaut
    level = "info",            -- "info" (défaut), "warning" ou "critical"
    title = "Prime de minuit", -- 120 caractères max
    message = ("Prime de %d $ versée"):format(montant), -- 2000 caractères max
    src = src,                 -- joueur auteur : nom, identifiant et position ajoutés
    target = cible,            -- facultatif : joueur visé
    data = { montant = montant }, -- facultatif : table enregistrée en JSON
})

-- forme courte : Core.Logs.send(catégorie, titre, message, src, données)
Core.Logs.send("money", "Prime de minuit", "Prime versée", src, { montant = 500 })

Depuis une autre ressource : exports.socle:logPush(opts) ou exports.socle:log(catégorie, titre, message, src, données).

  • Les logs sont écrits en base par lots toutes les 5 s : n'en fais pas une file de messages.
  • Un log critical ajoute la mention d'alerte réglée au panel (logs.alertMention) sur Discord.
  • Catégories reconnues par les réglages Discord : connection, admin, money, heist, drugs, death, property, vehicle, inventory, staff, job, chat, sanction, server, explosion. Une catégorie à toi fonctionne aussi : règle son webhook avec son nom, ou *.
  • La position du joueur est ajoutée si logs.storeCoords est actif ; la conservation est réglée par logs.retentionDays.

Permissions

Côté serveur, la seule vérification qui compte :

-- vrai si le joueur a la permission (en propre, ou par son groupe) ; "admin.*" donne tout
if not Core.Permission:can(src, "admin.economy") then
    return cb(false, "interdit")
end

src = 0 (la console) a toutes les permissions. Autres fonctions serveur : Core.Permission:has(src, nœud) (permission personnelle seulement), Core.Permission:add(src, nœud), :remove(src, nœud), :set(src, nœud, bool) (persistés dans socle_user_permissions), Core.Group:isStaff(src), Core.Group:getPlayerGroup(src).

Côté client, pour afficher ou masquer une option seulement :

if exports.socle:can("admin.economy") then
    -- montrer le bouton ; le serveur revérifiera
end

Une commande peut porter sa permission :

-- serveur (deux-points) : refusée avec un message si la permission manque
Core.Command:register("primereset", function(src, args)
    -- ...
end, { permission = "admin.economy", suggestion = { help = "Remet à zéro la prime d'un joueur", params = { { name = "id", help = "ID serveur" } } } })

-- client (point) : la commande est ignorée sans la permission
Core.Command.register("primeinfo", function(_, args) end, { permission = "admin.economy" })

Ajouter une permission

Core.Permission:can accepte n'importe quel nom de nœud : Core.Permission:can(src, "prime.manage") fonctionne pour un joueur ou un groupe qui porte prime.manage, et pour tout admin.*. En revanche, l'éditeur de groupes du menu staff ne propose que les nœuds de la liste interne du Socle (modules/permissions/server/Permission.lua), qui est remplacée par les mises à jour. Deux choix :

  • réutiliser un nœud existant proche de ton besoin (admin.economy, admin.society, admin.business…) : c'est le plus simple ;
  • donner ton nœud en propre à un joueur, depuis ton code, avec Core.Permission:add(src, "prime.manage").

Licence

-- serveur
local statut = exports.socle:licenseStatus()  -- "pending", "active", "grace", "expired" ou "unknown"
local omni = exports.socle:licenseShield()    -- true si OmniShield est inclus dans l'offre

Le détail des statuts et du mode limité est dans Panel en ligne, agent et licence.

Codes de ban

Les bans s'affichent sous la forme lisible BAN-<id> (au joueur banni, dans le menu staff, dans le panel) :

Core.BanCode.format(12)        -- "BAN-12" ; "-" si l'id n'est pas un nombre
Core.BanCode.parse("ban-12")   -- 12 ; accepte aussi "BAN-12", "ban12", "#12", "12"
Core.BanCode.parse("hello")    -- nil

Core.BanCode est partagé (client et serveur). /unban et la recherche du panel acceptent toutes ces écritures.

Events publics

Events locaux émis par le Socle, à écouter depuis n'importe quelle ressource avec AddEventHandler :

EventCôtéArgumentsQuand
socle:accentChangedclientr, g, bau chargement des variables et à chaque changement de la couleur du serveur
socle:inventorySkinChangedclientstylehabillage d'inventaire changé (hud.inventorySkin)
socle:hud:refreshHeadshotclientaucunà déclencher toi-même après un changement de tenue : le portrait du HUD est refait
socle:me:saidserveursrc, messageun joueur a utilisé /me
socle:characterCreatedserveursrc, identifierun personnage vient d'être créé
socle:tutorial:actionserveursrc, actionà déclencher toi-même : signale au tutoriel d'arrivée qu'un joueur a fait une action ("bank", "clothes", "item:<nom>"…)
-- autre ressource, client : suivre la couleur du serveur
AddEventHandler("socle:accentChanged", function(r, g, b)
    SendNUIMessage({ action = "accent", color = ("%d %d %d"):format(r, g, b) })
end)

-- serveur : faire avancer le tutoriel quand le joueur a utilisé ton guichet
TriggerEvent("socle:tutorial:action", src, "prime")

socle:tutorial:action et socle:me:said sont des events serveur locaux : un client ne peut pas les déclencher.

Un event réseau est aussi disponible pour afficher une information serveur : TriggerClientEvent("hud:advancedServerInfo", src, objet, message, duréeMs, titre).

Tous les exports du Socle

Base et réglages

ExportCôtéRôle
getCoreObject(), getSharedObject()les deuxl'objet Core
getVariable(catégorie, clé)les deuxvaleur d'un réglage (côté client : attend le chargement)
getAccent()les deuxcouleur du serveur { r, g, b }
getAccentHex()les deuxcouleur du serveur "#rrggbb"
panelTheme()serveurthème d'interface reçu du panel ({ vars, css })
licenseStatus(), licenseShield()serveurétat de la licence
log(cat, titre, message, src, données), logPush(opts)serveurécrire un log
can(permission)clientle joueur local a-t-il cette permission (affichage seulement)

Interface et interactions (client)

ExportRôle
addInteractionPoint(opts), removeInteractionPoint(id)point « appuie sur E »
addInteractionPointThisFrame(id, opts)point mobile, à rappeler régulièrement
isInteractionActive()un point est à portée de touche
Notify(message, type, durée, options), showNotification(...)notification
Decide(opts)décision Oui / Non, renvoie "yes", "no" ou "timeout"
StartProgress({ label, duration }), CancelProgress()barre de progression
ShowAdvancedServerInfo(objet, message, durée, titre)information serveur
HelpText(texte, durée), Subtitle(texte, durée)texte d'aide, sous-titre (remplacent les natifs GTA)
KeyHints(liste), HideKeyHints()barre de touches
NewsBanner(data)bandeau d'antenne plein écran (false pour le retirer)
drawInfo(titre, éléments), hideInfo()panneau d'informations
createPrompt(titre, description, description2, fn), isPromptOpen()demande de confirmation avec curseur, fn(accepté)
requestTextInput({ title, default })saisie de texte (bloquant, dans un thread), renvoie le texte ou nil
setWorldTag(id, data), removeWorldTag(id)étiquette flottante dans le monde (coords, title, eyebrow, price, specs, distance)
SetHudVisible(bool), ToggleVisible(bool)afficher / masquer le HUD
IsInSafeZone()le joueur est dans une zone protégée
licenseCard(item), showOwnedCard(nomObjet)afficher une carte de permis ou d'identité
kevlarUsePlates(data, slot)utilisation des plaques de kevlar (objet ox_inventory)
OxInputDialog, OxAlertDialog, OxSkillCheck, OxShowTextUI, OxHideTextUI, OxNuiRaw, OxFocus, OxRelease, OxClose…, OxCancelSkillCheckpont utilisé par ox_lib ; n'appelle pas ces exports toi-même, utilise lib.*

Jeu (serveur)

ExportRôle
getSocietyAccount(société), addSocietyAccount(société, montant, raison, identifier), removeSocietyAccount(...), setSocietyAccount(société, montant)comptes de société (add/remove renvoient false si refusé)
getSocietyPoints(), getSocietyState(nom), setSocietyState(nom, état)points et état des sociétés
businessOnDuty, businessItemExists, businessCanCarry, businessHasItems, businessGive, businessTake, businessTakeMoney, businessCredit, businessClaimSeedoutils des commerces de joueurs
hasLicense(src, type)le joueur possède-t-il ce permis
getCharacterLimit(license), addCharacterSlot(license, nombre)emplacements de personnages
getPlayerSkin(src), setPlayerSkin(src, skin), getPlayerTattoos(src), getTattooCatalogue()apparence
getVehicleKind(modèle, catégorie), getCategoryKind(catégorie), vehicleNativeType(modèle), getDefaultGarage(type)garages et types de véhicules
cardealerGetStock(concession, modèle), cardealerConsumeStock(...), cardealerRestoreStock(...)stock des concessions
deathscreenRespawnTime(src)délai de réapparition du joueur
drugMarketFactor(objet), drugMarketSold(objet, quantité)marché de la drogue
recordTreatment(type, patient, médecin, montant, facturé)dossier médical EMS
getPoliceArmoury(métier), getServiceJobPoints(type)armurerie et points des métiers de service