Arquitetura
A regra única
Seção intitulada “A regra única”O bs_core é dono do estado. Dinheiro, XP, necessidades, posição, dados do personagem: só o bs_core os escreve, e só o bs_core guarda a linha. Todos os outros recursos pedem ao bs_core através de exports. É isso que mantém os saves honestos: há um único sítio que marca um personagem como alterado e um único sítio que o escreve.
Camadas
Seção intitulada “Camadas”NUI (html in each resource, shared shell in bs_ui) | SendNUIMessage / fetch (NUI callbacks)Client Lua mirrors state, never authoritative | net events + BSB callbacksServer Lua Player objects, validation, hooks | oxmysql (behind bs_core/server/sv_database.lua)MariaDB migrations in bs_core/sqlO cliente pede, o servidor decide, o servidor devolve o resultado. Tudo o que um cliente envia é verificado outra vez no servidor. As necessidades, por exemplo, só podem descer a partir de um reporte do cliente; repor a fome é uma ação do servidor, vinda de um consumível.
Mapa de recursos
Seção intitulada “Mapa de recursos”O que cada um faz está na lista de resources.
| Grupo | Recursos |
|---|---|
| Core | bs_core, bs_loading, bs_multichar, bs_esx_bridge |
| UI | bs_ui (HUD, notify, partes NUI partilhadas), bs_radial, bs_menu (hub), bs_chat, bs_scoreboard |
| Jogador | bs_inventory, bs_skills, bs_medical, bs_bodily, bs_emotes, bs_boosters, bs_gadgets, bs_drugs |
| Mundo | bs_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 |
| Zombies | bs_zombies, bs_zombiepeds, bs_zombiesfx, bs_atlas, nodes |
| Social | bs_factions, bs_faction_bases, bs_jobs, bs_vip |
| Staff | bs_admin, bs_tester, bs_config, bs_guard, bs_cinecam |
| Assets | bs_assets |
Estados de sessão
Seção intitulada “Estados de sessão”connecting -> playerConnecting: identity resolved, ban checkedloading -> client handshake (bs:session:clientReady)select -> limbo, frozen and invisible, character list/create/delete/selectplaying -> Player object exists server side, ped spawned/logout e uma desconexão passam ambos por BS.UnloadPlayer, que força o save primeiro. Do lado do cliente, exports.bs_core:GetSessionState() diz-te onde estás, e exports.bs_core:IsLoaded() é a verificação simples de “existe um personagem”.
Dados do jogador a partir de outro recurso
Seção intitulada “Dados do jogador a partir de outro recurso”O objeto Player vive no estado Lua do bs_core. As funções não sobrevivem à fronteira dos exports, por isso não recebes o objeto: chamas exports que fazem o trabalho:
-- serverlocal 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.RewardsDo lado do cliente: exports.bs_core:GetData(), GetNeeds(), IsLoaded().
Os metadados do personagem (GetPlayerMeta/SetPlayerMeta) são o sítio fácil para pequenos dados por personagem. O bs_drugs guarda lá o vício, sem tabela própria.
O bs_core tem um pub/sub em processo (BS.On / BS.Emit) e volta a difundir cada hook como um evento normal chamado bs:hook:<name>. No teu recurso:
AddEventHandler('bs:hook:player:loaded', function(data) ... end)AddEventHandler('bs:hook:player:unloaded', function(...) ... end)| Hook | Lado |
|---|---|
core:ready | ambos |
player:loaded, player:spawned, player:unloaded | ambos |
player:money, player:levelUp | ambos |
player:xp, player:action | servidor |
character:created, character:selected | servidor |
session:state, needs:changed | cliente |
A ponte de callbacks (BSB)
Seção intitulada “A ponte de callbacks (BSB)”Pedido/resposta sobre net events. Inclui o ficheiro no teu manifest e passas a ter BSB no teu próprio estado Lua:
shared_scripts { '@bs_core/shared/sh_bridge.lua' }-- serverBSB.RegisterCallback('mything:get', function(src, a, b) return { ok = true, value = a + b }end)
-- clientlocal res = BSB.Await('mything:get', 5000, 1, 2) -- blocks this thread, nil on timeoutNo sentido inverso, o servidor pergunta a um cliente:
-- clientBSB.RegisterClientCallback('mything:where', function() return GetEntityCoords(PlayerPedId()) end)-- serverlocal pos = BSB.AwaitClient(src, 'mything:where', 3000)Como funciona: um pedido é difundido para todos os recursos e aquele que registou o nome responde, os outros ficam calados. Portanto:
- um nome desconhecido ou de um recurso parado dá timeout, não dá erro
- os nomes são globais, põe-lhes o prefixo do teu sistema (
squad:invite,factions:roster,vendor:buy) - o servidor limita cada cliente a 40 pedidos por segundo por recurso
- um handler que lança erro imprime
callback "x" errorede o cliente recebenil
A convenção para os retornos é uma tabela com ok, e error quando falha: { ok = false, error = 'Not in a squad.' }.
Existe também BSB.RegisterCommand(name, ace, help, args, handler) (verificação ACE, pcall e sugestão no chat de uma só vez) e BSB.Radial(spec) no cliente para a roda radial. Usa-os em vez de fazeres o teu.
Serviços
Seção intitulada “Serviços”@bs_core/shared/sh_services.lua dá BS.Provide / BS.Use. É uma forma verificada de chamar exports: o fornecedor diz o que oferece, o consumidor recebe um handle ou uma linha de log bem visível, nunca um nil silencioso.
local inv = BS.Use('inventory')if inv then inv.GiveItem(src, 'bandage', 1) endÉ novo, a maior parte do código ainda chama os exports diretamente. Ambos funcionam.
Statebags usados
Seção intitulada “Statebags usados”| Bag | Em | Definido por | Significado |
|---|---|---|---|
bsDowned | jogador | bs_medical | o jogador está caído |
bsDead, bsRevivable | jogador | bs_medical | |
bsDrugMods | jogador (local) | bs_drugs | drain, regen, cap, infection, sway |
bsSteady | jogador (local) | bs_boosters | multiplicador de recuo/oscilação |
bsDrone | jogador | bs_gadgets | a pilotar um drone |
bsSquad | jogador | bs_factions | informação do esquadrão |
bsFactionStyle, bsFactionTag, bsFactionCanInvite, bsFactionSafeZone | jogador | bs_factions | estilo da tag, permissões, safe zone |
bsVipTag | jogador | bs_vip | { tier, colour, style, hex, blip, hud }, nil sem VIP |
bsInRaid, bsRogue | jogador | bs_extraction | estado da zona PvP |
bsSitting | jogador | bs_emotes | |
bsZombie | entidade | bs_zombies | id do registo do walker. Também zType, zGait, zState… em modo em rede |
bsZombieDead | entidade | bs_zombies | cadáver em rede |
bsCorpse | entidade | bs_medical | corpo de jogador |
bsProtected, bsKeep | entidade | vários | a limpeza do bs_world ignora-a |
bsVehicle, bsLocked, bsOwned, bsWindowsDown | veículo | bs_vehicles | |
bsBuilt | entidade | bs_building |
Se criares uma entidade que não deve ser limpa pelo bs_world, define bsProtected nela.
Nomes de eventos
Seção intitulada “Nomes de eventos”bs:<system>:<thing>, com lower camel case no fim:
bs:session:clientReady bs:characters:select bs:player:moneybs:inventory:state bs:squad:sync bs:z:addbs:ui:notify bs:ui:opened bs:medical:diedO bs_core guarda os seus próprios nomes em BS.Events (bs_core/shared/sh_events.lua) para que nada os tenha escritos à mão. Os hooks são bs:hook:*. Os nomes de callbacks usam system:action sem o prefixo bs:.
Todos os eventos e callbacks por recurso estão na referência da API.
bs19.org · Privacy · Terms · Cookie settings
Cookies and outside content
The site only needs two essential cookies to keep you logged in and protect forms. Videos and streams from YouTube and Twitch load from their servers, which can set their own cookies, so they stay off until you say yes. Nothing is pre-ticked and you can change your mind any time with "Cookie settings" at the bottom of every page. Details.
Questions about the server, the framework or the site? A real person reads this, usually within a day.