Przejdź do głównej zawartości

Architektura

bs_core jest właścicielem stanu. Pieniądze, XP, potrzeby, pozycja, dane postaci: tylko bs_core je zapisuje i tylko bs_core zapisuje wiersz. Wszystkie inne zasoby pytają bs_core przez eksporty. Dzięki temu zapisy są uczciwe: jest jedno miejsce, które oznacza postać jako zmienioną, i jedno, które zapisuje.

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

Klient pyta, serwer decyduje, serwer odsyła wynik. Wszystko, co wysyła klient, jest sprawdzane ponownie na serwerze. Na przykład potrzeby z raportu klienta mogą tylko spadać, przywracanie głodu to akcja serwera z przedmiotu zużywalnego.

Co robi każdy z nich, opisuje lista resource’ów.

GrupaZasoby
Rdzeńbs_core, bs_loading, bs_multichar, bs_esx_bridge
UIbs_ui (HUD, powiadomienia, wspólne elementy NUI), bs_radial, bs_menu (hub), bs_chat, bs_scoreboard
Graczbs_inventory, bs_skills, bs_medical, bs_bodily, bs_emotes, bs_boosters, bs_gadgets, bs_drugs
Światbs_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
Zombiebs_zombies, bs_zombiepeds, bs_zombiesfx, bs_atlas, nodes
Społecznośćbs_factions, bs_faction_bases, bs_jobs, bs_vip
Personelbs_admin, bs_tester, bs_config, bs_guard, bs_cinecam
Zasoby graficznebs_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 i rozłączenie przechodzą przez BS.UnloadPlayer, który najpierw wymusza zapis. Po stronie klienta exports.bs_core:GetSessionState() mówi, gdzie jesteś, a exports.bs_core:IsLoaded() to proste sprawdzenie „czy jest postać”.

Obiekt Player żyje w stanie Lua bs_core. Funkcje nie przechodzą przez granicę eksportu, więc nie dostajesz obiektu, tylko wywołujesz eksporty, które robią robotę:

-- 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

Strona klienta: exports.bs_core:GetData(), GetNeeds(), IsLoaded().

Metadane postaci (GetPlayerMeta/SetPlayerMeta) to łatwe miejsce na małe dane na postać. bs_drugs trzyma tam uzależnienie bez własnej tabeli.

bs_core ma wewnątrzprocesowy pub/sub (BS.On / BS.Emit) i rozsyła każdy hook ponownie jako zwykłe zdarzenie bs:hook:<name>. Z twojego zasobu:

AddEventHandler('bs:hook:player:loaded', function(data) ... end)
AddEventHandler('bs:hook:player:unloaded', function(...) ... end)
HookStrona
core:readyobie
player:loaded, player:spawned, player:unloadedobie
player:money, player:levelUpobie
player:xp, player:actionserver
character:created, character:selectedserver
session:state, needs:changedclient

Żądanie/odpowiedź przez zdarzenia net. Dołącz plik w manifeście, a dostaniesz BSB we własnym stanie 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

W drugą stronę, serwer pyta jednego klienta:

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

Jak to działa: żądanie jest rozsyłane do każdego zasobu, odpowiada ten, który zarejestrował nazwę, reszta milczy. Czyli:

  • nieznana lub zatrzymana nazwa kończy się timeoutem, nie błędem
  • nazwy są globalne, dodawaj prefiks swojego systemu (squad:invite, factions:roster, vendor:buy)
  • serwer ogranicza każdego klienta do 40 żądań na sekundę na zasób
  • handler, który rzuca błąd, wypisuje callback "x" errored, a klient dostaje nil

Konwencja zwrotów to tabela z ok i error przy niepowodzeniu: { ok = false, error = 'Not in a squad.' }.

Jest też BSB.RegisterCommand(name, ace, help, args, handler) (sprawdzenie ACE, pcall i podpowiedź na czacie w jednym) oraz BSB.Radial(spec) po stronie klienta dla kółka radialnego. Używaj ich zamiast pisać własne.

@bs_core/shared/sh_services.lua daje BS.Provide / BS.Use. To sprawdzany sposób wywoływania eksportów: dostawca mówi, co oferuje, a konsument dostaje uchwyt albo głośną linię w logu, nigdy cichego nil.

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

To nowość, większość kodu nadal wywołuje eksporty bezpośrednio. Działają oba sposoby.

BagNaUstawiaZnaczenie
bsDownedgraczbs_medicalgracz jest powalony
bsDead, bsRevivablegraczbs_medical
bsDrugModsgracz (lokalnie)bs_drugsdrain, regen, cap, infection, sway
bsSteadygracz (lokalnie)bs_boostersmnożnik odrzutu/chwiania
bsDronegraczbs_gadgetspilotowanie drona
bsSquadgraczbs_factionsinformacje o oddziale
bsFactionStyle, bsFactionTag, bsFactionCanInvite, bsFactionSafeZonegraczbs_factionsstyl tagu, uprawnienia, strefa bezpieczna
bsVipTaggraczbs_vip{ tier, colour, style, hex, blip, hud }, nil bez VIP
bsInRaid, bsRoguegraczbs_extractionstan strefy PvP
bsSittinggraczbs_emotes
bsZombieencjabs_zombiesid rekordu szwendacza. Także zType, zGait, zState… w trybie sieciowym
bsZombieDeadencjabs_zombiessieciowe zwłoki
bsCorpseencjabs_medicalciało gracza
bsProtected, bsKeepencjawielesprzątanie bs_world go pomija
bsVehicle, bsLocked, bsOwned, bsWindowsDownpojazdbs_vehicles
bsBuiltencjabs_building

Jeśli tworzysz encję, której bs_world nie może sprzątnąć, ustaw na niej bsProtected.

bs:<system>:<thing>, na końcu lower camel case:

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 trzyma własne nazwy w BS.Events (bs_core/shared/sh_events.lua), żeby nic nie było wpisane na sztywno. Hooki to bs:hook:*. Nazwy callbacków używają system:action bez prefiksu bs:.

Każde zdarzenie i callback dla każdego zasobu jest w dokumentacji API.