Architecture de tlr_core
tlr_core est une seule ressource RedM (fx_version 'cerulean', game 'rdr3', Lua 5.4) qui contient le cœur du framework, tous ses domaines et l'interface NUI. Retour au sommaire.
Dossiers
shared/: règles pures en Lua, sans native, chargées des deux côtés et testées hors jeu (schémas, permissions, métiers, inventaire, console, licence...). C'est là que vit la logique qui doit être vérifiable.server/: un dossier par domaine (players,economy,jobs,inventory,weapons,horses,shops,bank,law,world,console...). On y trouve aussi les services du cœur :core(état global, démarrage),database(migrations),config(paramètres et réglages des systèmes),permissions,logs,modules,rpc(passerelle et actions du panel dansserver/rpc/actions/).client/: un dossier par domaine côté jeu, plusclient/nui(pont avec la page, focus, Échap) etclient/admin(commandes/tlret/admin).web/: la NUI en HTML, CSS et JS vanilla (modules ES, sans bundler).web/js/pages/contient une page par section du panel,web/js/screens/les écrans de jeu,web/js/lib/les composants,web/css/les feuilles (donttokens.cssetthemes.css),web/console/la console web,web/loading/l'écran de chargement.lib/: la libtlib(équivalent maison d'ox_lib), utilisable par d'autres ressources avecshared_script '@tlr_core/lib/init.lua'.target/: le système de cible (Alt maintenu).config/: fichiers de départ modifiables par le propriétaire du serveur (config.lua,ranch.lua,saloon.lua...). TLR ne les réécrit jamais.locales/: textes traduits (fr.lua, tableTLR.Locale.fr), dont les messages des codes d'erreur ; lus avecTLR.T(code).tests/: tests hors jeu, lancés parlua tests/run.lua.install/: schéma SQL et exemple de server.cfg.docs/: documentation technique ; ce guide est dansdocs/guide/.
Cycle de démarrage
Le manifeste charge server/core/boot.lua en dernier, quand tous les services sont définis. Ce fichier exécute dans l'ordre :
database: attend oxmysql puis applique les migrations manquantes (TLR.Migrations.run).settings: charge les paramètres (TLR.Settings.load).permissions: groupes et membres.jobs: métiers.items: objets.weapons,world,doors,zones,blips,targets: facultatifs.
Les cinq premières étapes sont indispensables : si l'une échoue, le démarrage s'interrompt, le cœur reste non prêt et les joueurs sont refusés. Chaque étape publie son état avec TLR.SetServiceStatus(nom, 'pending' | 'ok' | 'error', détail), visible dans le tableau de bord et dans tlr_diag.
Ensuite les boucles démarrent (sauvegarde des joueurs, paie, besoins, horloge du monde), TLR.Ready passe à vrai et l'événement serveur tlr:core:ready est déclenché. Les modules s'enregistrent sur cet événement ; il est aussi déclenché après un redémarrage de tlr_core. Les joueurs déjà connectés reçoivent tlr:player:retryReady pour retrouver l'écran des personnages.
RPC : la passerelle de l'interface
Fichier : server/rpc/rpc.lua, objet TLR.RPC. Toute requête de la page NUI de tlr_core passe par l'événement réseau tlr:rpc (envoyé par client/nui/nui.lua depuis le rappel NUI rpc), puis par ces contrôles, dans l'ordre :
- qui : la source donnée par le serveur, jamais une donnée du client ;
- débit : seau de jetons par joueur (
TLRConfig.RateLimit, 20 requêtes par 5 secondes par défaut) ; - action connue, sinon
UNKNOWN_ACTIONjournalisé en sécurité ; - données valides selon le schéma, sinon
VALIDATION_FAILED; - permission, refus par défaut, sinon
PERMISSION_DENIEDjournalisé ; - personnage chargé si l'action l'exige (
NO_CHARACTER) ; - exécution du gestionnaire, puis réponse
tlr:rpc:result.
Déclarer une action (dans le code de tlr_core) :
local RPC, S = TLR.RPC, TLR.RPC.S
RPC.register('exemple.lire', {
permission = 'tlr.exemple.read',
schema = { type = 'object', fields = { id = S.id } },
character = false,
handler = function(ctx, data)
if not ctx.can('tlr.exemple.read') then RPC.fail('PERMISSION_DENIED') end
return { id = data.id, par = ctx.actor }
end,
})
Le contexte ctx contient source, player, actor (libellé pour le journal), can(node) et, pour la console web, web = true. RPC.fail(code, détail) interrompt le gestionnaire avec une erreur propre. Schémas courants : TLR.RPC.S.empty, S.id, S.name, S.label, S.license.
Autres appels :
RPC.invoke(src, nom, données): appel interne par le serveur (commandes de l'équipe), mêmes contrôles sans réseau.RPC.invokeWeb(src, nom, données): appel depuis la console web. Seules les actions protégées par un droit et sans personnage sont permises.RPC.exists(nom).
Côté page : call(action, données) de web/js/rpc.js. Dans la console web, ce même module bascule en HTTP, ce qui permet de réutiliser les pages du panel telles quelles.
Codes d'erreur stables : PERMISSION_DENIED, VALIDATION_FAILED, RATE_LIMITED, NOT_FOUND, CONFLICT, UNKNOWN_ACTION, NOT_READY, NO_CHARACTER, INSUFFICIENT_FUNDS, INTERNAL.
Permissions
Fichier : server/permissions/permissions.lua, objet TLR.Permissions.
- Une permission est un nœud en points, par exemple
tlr.shops.update. Tout ce qui n'est pas accordé est refusé. TLR.Permissions.declareNode(nœud, libellé, catégorie)rend le nœud visible dans la section Permissions.TLR.Permissions.has(src, nœud): vrai si le joueur a le droit (un nœud nil est toujours permis).TLR.Permissions.profile(src):{ founder, level, permissions, group, groupLabel }.- Les groupes ont un niveau de 1 à 99. On ne gère que les groupes de niveau inférieur au sien et on n'accorde que ce qu'on possède.
- Le fondateur est reconnu par l'ACE
tlr.founderet passe au dessus de tout. - Les sources virtuelles de la console web (inférieures ou égales à moins 1000) ont les droits du groupe de la licence du compte.
Paramètres : TLR.Settings
Fichier : server/config/settings.lua. Toute fonctionnalité administrable déclare ses paramètres ; le panel génère leurs écrans tout seul (section Paramètres).
TLR.Settings.declare('tlr_core', 'exemple.actif', {
type = 'boolean', default = true,
label = 'Exemple actif', category = 'Exemple',
description = "Active la fonction d'exemple.",
})
local actif = TLR.Settings.get('exemple.actif')
TLR.Settings.onChange('exemple.actif', function(valeur) end)
- La clé suit le motif
domaine.nomen minuscules. - Types :
boolean,integer,number,string,enum(avecchoices),money,object,array. Options :min,max,default,label,description,category,pattern,readPermission,writePermission(par défauttlr.settings.readettlr.settings.update),restart = truesi le changement ne vaut qu'au redémarrage,checkpour une règle entre plusieurs paramètres. - Fusion : valeur par défaut, puis config.lua, puis valeur en jeu.
TLR.Settings.reset(clé, acteur)efface la valeur en jeu. TLR.Settings.problems()liste les valeurs invalides du fichier.
Réglages des systèmes : TLR.Tuning
Fichier : server/config/tuning.lua. Pour des données riches (tables de recettes, lieux, prix), un système expose sa table vivante au panel :
TLR.Tuning.declare('monsysteme', {
label = 'Mon système', category = 'Nature', help = "Ce que l'on règle ici",
root = function() return MonSysteme.cfg end,
keys = { 'PRIX', 'LIEUX' },
})
Chaque déclaration crée une plaque du panel (tuning_<id>) rangée par catégorie et les permissions tlr.<id>.read et tlr.<id>.update. Les valeurs modifiées sont gardées dans la table tlr_tuning et posées sur la table vivante sans redémarrage. Options : client, onChange, tools (boutons qui lancent une commande en jeu), emptyHelp. Les déclarations du cœur sont dans server/config/tuning_sections.lua.
Migrations : TLR.Migrations.add
Fichier : server/database/migrations.lua. Chaque domaine ajoute ses migrations depuis son propre fichier :
TLR.Migrations.add({ id = 900, name = 'table exemple', sql = {
[[CREATE TABLE IF NOT EXISTS tlr_exemple (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
label VARCHAR(80) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4]],
} })
- Les identifiants sont uniques : une double déclaration arrête le chargement. Les numéros sont réservés par domaine pour éviter les collisions.
TLR.Migrations.run()créetlr_migrations, puis applique dans l'ordre les migrations absentes ; une requête en échec arrête le démarrage.- Une migration appliquée n'est jamais rejouée ni modifiée : pour changer une table, on ajoute une nouvelle migration.
Journal : TLR.Log
Fichier : server/logs/logs.lua.
TLR.Log.write('shops', ctx.actor, 'shop.update', { id = 12 }, 'medium')
- Arguments : catégorie, auteur, action, détail (table encodée en JSON), gravité facultative (
low,medium,high, déduite de la catégorie si absente). - Catégories :
admin,economy,permissions,settings,jobs,world,players,security,error,system,weapons,appearance,storage,horses,shops,bank,bills,law,vip,christmas,crafting. - L'écriture n'attend pas la base. Les catégories
securityeterrorsont aussi affichées dans la console. Aucune ligne ne s'efface depuis le jeu. TLR.Log.recent(limite, catégorie, gravité, recherche)sert la section Journaux.
Pour aller plus loin
Source : tlr_core/docs/guide/dev_architecture.md