Перейти к содержимому

Архитектура

bs_core владеет состоянием. Деньги, опыт, потребности, позиция, данные персонажа: только bs_core их пишет и только bs_core сохраняет строку. Все остальные ресурсы обращаются к bs_core через экспорты. Так сохранения остаются честными: есть одно место, которое помечает персонажа изменённым, и одно, которое пишет.

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

Клиент спрашивает, сервер решает, сервер возвращает результат. Всё, что отправляет клиент, проверяется на сервере заново. Например, потребности от клиента могут только уменьшаться, восстановление голода - серверное действие от расходника.

Что делает каждый из них, описано в списке resource.

ГруппаРесурсы
Ядроbs_core, bs_loading, bs_multichar, bs_esx_bridge
UIbs_ui (HUD, уведомления, общие части NUI), bs_radial, bs_menu (хаб), bs_chat, bs_scoreboard
Игрокbs_inventory, bs_skills, bs_medical, bs_bodily, bs_emotes, bs_boosters, bs_gadgets, bs_drugs
Мир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
Зомбиbs_zombies, bs_zombiepeds, bs_zombiesfx, bs_atlas, nodes
Социальноеbs_factions, bs_faction_bases, bs_jobs, bs_vip
Персоналbs_admin, bs_tester, bs_config, bs_guard, bs_cinecam
Ассетыbs_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 и отключение оба проходят через BS.UnloadPlayer, который сначала принудительно сохраняет. На клиенте exports.bs_core:GetSessionState() говорит, где вы находитесь, а exports.bs_core:IsLoaded() - простая проверка «есть ли персонаж».

Объект Player живёт в Lua-состоянии bs_core. Функции не переживают границу экспорта, поэтому вы получаете не объект, а вызываете экспорты, которые делают дело:

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

Клиентская сторона: exports.bs_core:GetData(), GetNeeds(), IsLoaded().

Метаданные персонажа (GetPlayerMeta/SetPlayerMeta) - удобное место для небольших данных на персонажа. bs_drugs хранит там зависимость без собственной таблицы.

У bs_core есть внутрипроцессная публикация/подписка (BS.On / BS.Emit), и каждый хук повторно рассылается как обычное событие bs:hook:<name>. Из вашего ресурса:

AddEventHandler('bs:hook:player:loaded', function(data) ... end)
AddEventHandler('bs:hook:player:unloaded', function(...) ... end)
ХукСторона
core:readyобе
player:loaded, player:spawned, player:unloadedобе
player:money, player:levelUpобе
player:xp, player:actionserver
character:created, character:selectedserver
session:state, needs:changedclient

Запрос/ответ через net-события. Подключите файл в манифесте, и BSB появится в вашем 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

В обратную сторону, сервер спрашивает одного клиента:

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

Как это работает: запрос рассылается каждому ресурсу, отвечает тот, кто зарегистрировал имя, остальные молчат. Поэтому:

  • неизвестное или остановленное имя истекает по таймауту, ошибки не будет
  • имена глобальные, добавляйте префикс своей системы (squad:invite, factions:roster, vendor:buy)
  • сервер ограничивает каждого клиента 40 запросами в секунду на ресурс
  • обработчик, который выбрасывает ошибку, печатает callback "x" errored, и клиент получает nil

Соглашение для возвратов: таблица с ok и error при неудаче: { ok = false, error = 'Not in a squad.' }.

Есть также BSB.RegisterCommand(name, ace, help, args, handler) (проверка ACE, pcall и подсказка в чате за один вызов) и BSB.Radial(spec) на клиенте для радиального колеса. Используйте их, а не пишите своё.

@bs_core/shared/sh_services.lua даёт BS.Provide / BS.Use. Это проверяемый способ вызывать экспорты: провайдер говорит, что предлагает, потребитель получает дескриптор или громкую строку в логе, а не молчаливый nil.

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

Это ново, большая часть кода по-прежнему вызывает экспорты напрямую. Работают оба способа.

BagНаЗадаётЗначение
bsDownedигрокbs_medicalигрок упал
bsDead, bsRevivableигрокbs_medical
bsDrugModsигрок (локально)bs_drugsdrain, regen, cap, infection, sway
bsSteadyигрок (локально)bs_boostersмножитель отдачи/раскачки
bsDroneигрокbs_gadgetsуправление дроном
bsSquadигрокbs_factionsинформация об отряде
bsFactionStyle, bsFactionTag, bsFactionCanInvite, bsFactionSafeZoneигрокbs_factionsстиль тега, права, безопасная зона
bsVipTagигрокbs_vip{ tier, colour, style, hex, blip, hud }, nil без VIP
bsInRaid, bsRogueигрокbs_extractionсостояние PvP-зоны
bsSittingигрокbs_emotes
bsZombieсущностьbs_zombiesid записи ходячего. Также zType, zGait, zState… в сетевом режиме
bsZombieDeadсущностьbs_zombiesсетевой труп
bsCorpseсущностьbs_medicalтело игрока
bsProtected, bsKeepсущностьмногозачистка bs_world его пропускает
bsVehicle, bsLocked, bsOwned, bsWindowsDownмашинаbs_vehicles
bsBuiltсущностьbs_building

Если вы создаёте сущность, которую bs_world не должен убирать, задайте ей bsProtected.

bs:<system>:<thing>, в конце 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 хранит свои имена в BS.Events (bs_core/shared/sh_events.lua), чтобы ничего не было прописано жёстко. Хуки - bs:hook:*. Имена колбэков используют system:action без префикса bs:.

Каждое событие и колбэк по ресурсам описаны в справочнике API.