Архитектура
Главное правило
Заголовок раздела «Главное правило»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 callbacksServer 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 |
| UI | bs_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 checkedloading -> client handshake (bs:session:clientReady)select -> limbo, frozen and invisible, character list/create/delete/selectplaying -> Player object exists server side, ped spawned/logout и отключение оба проходят через BS.UnloadPlayer, который сначала принудительно сохраняет. На клиенте exports.bs_core:GetSessionState() говорит, где вы находитесь, а exports.bs_core:IsLoaded() - простая проверка «есть ли персонаж».
Данные игрока из другого ресурса
Заголовок раздела «Данные игрока из другого ресурса»Объект Player живёт в Lua-состоянии bs_core. Функции не переживают границу экспорта, поэтому вы получаете не объект, а вызываете экспорты, которые делают дело:
-- 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.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:action | server |
character:created, character:selected | server |
session:state, needs:changed | client |
Мост колбэков (BSB)
Заголовок раздела «Мост колбэков (BSB)»Запрос/ответ через net-события. Подключите файл в манифесте, и BSB появится в вашем 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 timeoutВ обратную сторону, сервер спрашивает одного клиента:
-- clientBSB.RegisterClientCallback('mything:where', function() return GetEntityCoords(PlayerPedId()) end)-- serverlocal 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Это ново, большая часть кода по-прежнему вызывает экспорты напрямую. Работают оба способа.
Используемые statebag’и
Заголовок раздела «Используемые statebag’и»| Bag | На | Задаёт | Значение |
|---|---|---|---|
bsDowned | игрок | bs_medical | игрок упал |
bsDead, bsRevivable | игрок | bs_medical | |
bsDrugMods | игрок (локально) | bs_drugs | drain, 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_zombies | id записи ходячего. Также 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:moneybs:inventory:state bs:squad:sync bs:z:addbs:ui:notify bs:ui:opened bs:medical:diedbs_core хранит свои имена в BS.Events (bs_core/shared/sh_events.lua), чтобы ничего не было прописано жёстко. Хуки - bs:hook:*. Имена колбэков используют system:action без префикса bs:.
Каждое событие и колбэк по ресурсам описаны в справочнике 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.