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 ; cbne 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
| Fonction | Rô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
| Fonction | Rô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 :
| Hook | Côté | Arguments |
|---|---|---|
variable:changed | les deux | catégorie, clé, valeur : un réglage a changé depuis le panel |
permissions:changed | client | aucun : 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)| Option | Rôle |
|---|---|
coords | position (vector3). Avec un PNJ, donne la position au sol (z - 1.0 d'une position de joueur) |
dist | distance à laquelle la touche est active |
farDist | distance d'affichage de la balise lointaine (sinon dist + 2) |
key | A… Z, ENTER, SPACE, TAB, F1…, chiffres ; E par défaut |
message | texte du point |
onPress | fonction appelée dans un nouveau thread, ou nom d'un event client local déclenché par TriggerEvent |
ped, heading, scenario | PNJ figé et invincible, supprimé avec le point |
prop, propHeading | objet posé sur le point, supprimé avec le point |
icon | accepté, 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 endLe 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)| Type | Rendu |
|---|---|
success | notification de réussite |
error, warning | notification d'erreur |
info (ou tout autre) | notification d'information |
business | annonce d'entreprise : options = { company, businessTitle, number, img, color = { r, g, b } } |
custom | options = { 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
criticalajoute 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.storeCoordsest actif ; la conservation est réglée parlogs.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")
endsrc = 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
endUne 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'offreLe 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") -- nilCore.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 :
| Event | Côté | Arguments | Quand |
|---|---|---|---|
socle:accentChanged | client | r, g, b | au chargement des variables et à chaque changement de la couleur du serveur |
socle:inventorySkinChanged | client | style | habillage d'inventaire changé (hud.inventorySkin) |
socle:hud:refreshHeadshot | client | aucun | à déclencher toi-même après un changement de tenue : le portrait du HUD est refait |
socle:me:said | serveur | src, message | un joueur a utilisé /me |
socle:characterCreated | serveur | src, identifier | un personnage vient d'être créé |
socle:tutorial:action | serveur | src, 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
| Export | Côté | Rôle |
|---|---|---|
getCoreObject(), getSharedObject() | les deux | l'objet Core |
getVariable(catégorie, clé) | les deux | valeur d'un réglage (côté client : attend le chargement) |
getAccent() | les deux | couleur du serveur { r, g, b } |
getAccentHex() | les deux | couleur du serveur "#rrggbb" |
panelTheme() | serveur | thè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) | client | le joueur local a-t-il cette permission (affichage seulement) |
Interface et interactions (client)
| Export | Rô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…, OxCancelSkillCheck | pont utilisé par ox_lib ; n'appelle pas ces exports toi-même, utilise lib.* |
Jeu (serveur)
| Export | Rô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, businessClaimSeed | outils 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 |