Aller au contenu

Architecture

bs_core possède l’état. Argent, XP, besoins, position, données du personnage : seul bs_core les écrit, et seul bs_core sauvegarde la ligne. Toutes les autres ressources demandent à bs_core via des exports. C’est ce qui garde les sauvegardes fiables : un seul endroit marque un personnage comme modifié et un seul endroit l’écrit.

NUI (html in each resource, shared shell in bs_ui)
| SendNUIMessage / fetch (NUI callbacks)
Client Lua mirrors state, never authoritative
| net events + BSB callbacks
Server Lua Player objects, validation, hooks
| oxmysql (behind bs_core/server/sv_database.lua)
MariaDB migrations in bs_core/sql

Le client demande, le serveur décide, le serveur renvoie le résultat. Tout ce qu’un client envoie est revérifié côté serveur. Les besoins, par exemple, ne peuvent que baisser à partir d’un rapport du client ; restaurer la faim est une action du serveur depuis un consommable.

Ce que fait chacune est détaillé dans la liste des resources.

GroupeRessources
Corebs_core, bs_loading, bs_multichar, bs_esx_bridge
UIbs_ui (HUD, notify, éléments NUI partagés), bs_radial, bs_menu (hub), bs_chat, bs_scoreboard
Joueurbs_inventory, bs_skills, bs_medical, bs_bodily, bs_emotes, bs_boosters, bs_gadgets, bs_drugs
Mondebs_world, bs_interactions (ALT target dot, containers), bs_economy (loot), bs_traders, bs_vehicles, bs_building, bs_crafting, bs_gates, bs_stash, bs_recycler, bs_mining, bs_scenes, bs_events, bs_extraction
Zombiesbs_zombies, bs_zombiepeds, bs_zombiesfx, bs_atlas, nodes
Socialbs_factions, bs_faction_bases, bs_jobs, bs_vip
Staffbs_admin, bs_tester, bs_config, bs_guard, bs_cinecam
Assetsbs_assets
connecting -> playerConnecting: identity resolved, ban checked
loading -> client handshake (bs:session:clientReady)
select -> limbo, frozen and invisible, character list/create/delete/select
playing -> Player object exists server side, ped spawned

/logout et une déconnexion passent tous deux par BS.UnloadPlayer, qui force d’abord la sauvegarde. Côté client, exports.bs_core:GetSessionState() vous dit où vous en êtes, et exports.bs_core:IsLoaded() est la vérification simple « y a-t-il un personnage ».

L’objet Player vit dans l’état Lua de bs_core. Les fonctions ne traversent pas la frontière des exports, vous n’obtenez donc pas l’objet : vous appelez des exports qui font le travail :

-- server
local p = exports.bs_core:GetPlayerSummary(src) -- plain table: charId, name, level, xp, accounts, needs, role...
exports.bs_core:AddMoney(src, 'cash', 50, 'my_reason')
exports.bs_core:RemoveMoney(src, 'bank', 200, 'my_reason')
exports.bs_core:AddXP(src, 25, 'my_reason')
local v = exports.bs_core:GetPlayerMeta(src, 'my_key', nil)
exports.bs_core:SetPlayerMeta(src, 'my_key', { any = 'table' })
exports.bs_core:AwardAction(src, 'zombie_kill') -- uses Config.Rewards

Côté client : exports.bs_core:GetData(), GetNeeds(), IsLoaded().

Les métadonnées du personnage (GetPlayerMeta/SetPlayerMeta) sont l’endroit facile pour de petites données par personnage. bs_drugs y garde la dépendance, sans table à lui.

bs_core a un pub/sub en processus (BS.On / BS.Emit) et rediffuse chaque hook comme un événement normal appelé bs:hook:<name>. Depuis votre ressource :

AddEventHandler('bs:hook:player:loaded', function(data) ... end)
AddEventHandler('bs:hook:player:unloaded', function(...) ... end)
HookCôté
core:readyles deux
player:loaded, player:spawned, player:unloadedles deux
player:money, player:levelUples deux
player:xp, player:actionserveur
character:created, character:selectedserveur
session:state, needs:changedclient

Requête/réponse via des net events. Incluez le fichier dans votre manifest et vous obtenez BSB dans votre propre état Lua :

shared_scripts { '@bs_core/shared/sh_bridge.lua' }
-- server
BSB.RegisterCallback('mything:get', function(src, a, b)
return { ok = true, value = a + b }
end)
-- client
local res = BSB.Await('mything:get', 5000, 1, 2) -- blocks this thread, nil on timeout

Dans l’autre sens, le serveur interroge un client :

-- client
BSB.RegisterClientCallback('mything:where', function() return GetEntityCoords(PlayerPedId()) end)
-- server
local pos = BSB.AwaitClient(src, 'mything:where', 3000)

Comment ça marche : une requête est diffusée à toutes les ressources et celle qui a enregistré le nom répond, les autres se taisent. Donc :

  • un nom inconnu ou arrêté expire en timeout, il ne provoque pas d’erreur
  • les noms sont globaux, préfixez-les avec votre système (squad:invite, factions:roster, vendor:buy)
  • le serveur limite chaque client à 40 requêtes par seconde par ressource
  • un handler qui lève une erreur affiche callback "x" errored et le client reçoit nil

La convention pour les retours est une table avec ok, et error en cas d’échec : { ok = false, error = 'Not in a squad.' }.

Il y a aussi BSB.RegisterCommand(name, ace, help, args, handler) (vérification ACE, pcall et suggestion de chat en un) et BSB.Radial(spec) côté client pour la roue radiale. Utilisez-les plutôt que de bricoler les vôtres.

@bs_core/shared/sh_services.lua fournit BS.Provide / BS.Use. C’est une façon vérifiée d’appeler des exports : le fournisseur dit ce qu’il offre, le consommateur reçoit un handle ou une ligne de log bien visible, jamais un nil silencieux.

local inv = BS.Use('inventory')
if inv then inv.GiveItem(src, 'bandage', 1) end

C’est récent, la plupart du code appelle encore les exports directement. Les deux fonctionnent.

BagSurDéfini parSignification
bsDownedjoueurbs_medicalle joueur est à terre
bsDead, bsRevivablejoueurbs_medical
bsDrugModsjoueur (local)bs_drugsdrain, regen, cap, infection, sway
bsSteadyjoueur (local)bs_boostersmultiplicateur de recul/balancement
bsDronejoueurbs_gadgetspilote un drone
bsSquadjoueurbs_factionsinfos d’escouade
bsFactionStyle, bsFactionTag, bsFactionCanInvite, bsFactionSafeZonejoueurbs_factionsstyle du tag, permissions, safe zone
bsVipTagjoueurbs_vip{ tier, colour, style, hex, blip, hud }, nil sans VIP
bsInRaid, bsRoguejoueurbs_extractionétat de la zone PvP
bsSittingjoueurbs_emotes
bsZombieentitébs_zombiesid du registre du walker. Aussi zType, zGait, zState… en mode en réseau
bsZombieDeadentitébs_zombiescadavre en réseau
bsCorpseentitébs_medicalcorps de joueur
bsProtected, bsKeepentitéplusieursle nettoyage de bs_world l’ignore
bsVehicle, bsLocked, bsOwned, bsWindowsDownvéhiculebs_vehicles
bsBuiltentitébs_building

Si vous créez une entité que bs_world ne doit pas nettoyer, définissez bsProtected dessus.

bs:<system>:<thing>, en lowerCamelCase à la fin :

bs:session:clientReady bs:characters:select bs:player:money
bs:inventory:state bs:squad:sync bs:z:add
bs:ui:notify bs:ui:opened bs:medical:died

bs_core garde ses propres noms dans BS.Events (bs_core/shared/sh_events.lua) pour que rien ne les écrive en dur. Les hooks sont bs:hook:*. Les noms de callbacks utilisent system:action sans le préfixe bs:.

Chaque événement et callback par ressource se trouve dans la référence de l’API.