Architektur
Die eine Regel
Abschnitt betitelt „Die eine Regel“bs_core besitzt den Zustand. Geld, XP, Bedürfnisse, Position, Charakterdaten: nur bs_core schreibt sie, und nur bs_core speichert die Zeile. Jede andere Ressource fragt bs_core über Exports. So bleiben Speicherstände sauber: Es gibt genau eine Stelle, die einen Charakter als geändert markiert, und genau eine, die ihn schreibt.
Schichten
Abschnitt betitelt „Schichten“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/sqlDer Client fragt, der Server entscheidet, der Server schickt das Ergebnis zurück. Alles, was ein Client sendet, wird auf dem Server erneut geprüft. Bedürfnisse zum Beispiel können aus einem Client-Report nur sinken; Hunger zu stillen ist eine Server-Aktion durch ein Verbrauchsgut.
Ressourcenübersicht
Abschnitt betitelt „Ressourcenübersicht“Was jede davon tut, steht in der Resource-Liste.
| Gruppe | Ressourcen |
|---|---|
| Core | bs_core, bs_loading, bs_multichar, bs_esx_bridge |
| UI | bs_ui (HUD, Notify, gemeinsame NUI-Teile), bs_radial, bs_menu (Hub), bs_chat, bs_scoreboard |
| Spieler | bs_inventory, bs_skills, bs_medical, bs_bodily, bs_emotes, bs_boosters, bs_gadgets, bs_drugs |
| Welt | 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 |
| Sozial | bs_factions, bs_faction_bases, bs_jobs, bs_vip |
| Staff | bs_admin, bs_tester, bs_config, bs_guard, bs_cinecam |
| Assets | bs_assets |
Sitzungszustände
Abschnitt betitelt „Sitzungszustände“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 und ein Disconnect laufen beide über BS.UnloadPlayer, das zuerst erzwungen speichert. Clientseitig sagt dir exports.bs_core:GetSessionState(), wo du stehst, und exports.bs_core:IsLoaded() ist die einfache Prüfung “gibt es einen Charakter”.
Spielerdaten aus einer anderen Ressource
Abschnitt betitelt „Spielerdaten aus einer anderen Ressource“Das Player-Objekt lebt im Lua-State von bs_core. Funktionen überstehen die Export-Grenze nicht, du bekommst also nicht das Objekt, sondern rufst Exports auf, die die Arbeit erledigen:
-- 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.RewardsClientseitig: exports.bs_core:GetData(), GetNeeds(), IsLoaded().
Charakter-Metadaten (GetPlayerMeta/SetPlayerMeta) sind der einfache Ort für kleine Daten pro Charakter. bs_drugs speichert dort die Sucht, ohne eigene Tabelle.
bs_core hat ein In-Process-Pub/Sub (BS.On / BS.Emit) und sendet jeden Hook erneut als normales Event namens bs:hook:<name>. Aus deiner Ressource:
AddEventHandler('bs:hook:player:loaded', function(data) ... end)AddEventHandler('bs:hook:player:unloaded', function(...) ... end)| Hook | Seite |
|---|---|
core:ready | beide |
player:loaded, player:spawned, player:unloaded | beide |
player:money, player:levelUp | beide |
player:xp, player:action | Server |
character:created, character:selected | Server |
session:state, needs:changed | Client |
Die Callback-Brücke (BSB)
Abschnitt betitelt „Die Callback-Brücke (BSB)“Request/Response über Net-Events. Binde die Datei in dein Manifest ein, und du hast BSB in deinem eigenen Lua-State:
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 timeoutIn die andere Richtung, der Server fragt einen Client:
-- clientBSB.RegisterClientCallback('mything:where', function() return GetEntityCoords(PlayerPedId()) end)-- serverlocal pos = BSB.AwaitClient(src, 'mything:where', 3000)So funktioniert es: Eine Anfrage wird an jede Ressource gesendet, und die, die den Namen registriert hat, antwortet, die übrigen schweigen. Daher:
- ein unbekannter oder gestoppter Name läuft in ein Timeout, es gibt keinen Fehler
- Namen sind global, setze das Präfix deines Systems davor (
squad:invite,factions:roster,vendor:buy) - der Server begrenzt jeden Client auf 40 Anfragen pro Sekunde pro Ressource
- ein Handler, der einen Fehler wirft, gibt
callback "x" erroredaus, und der Client erhältnil
Die Konvention für Rückgaben ist eine Tabelle mit ok und bei Fehler zusätzlich error: { ok = false, error = 'Not in a squad.' }.
Es gibt außerdem BSB.RegisterCommand(name, ace, help, args, handler) (ACE-Prüfung, pcall und Chat-Vorschlag in einem) und BSB.Radial(spec) auf dem Client für das Radialrad. Nutze diese, statt eigene zu bauen.
Services
Abschnitt betitelt „Services“@bs_core/shared/sh_services.lua liefert BS.Provide / BS.Use. Es ist ein geprüfter Weg, Exports aufzurufen: Der Anbieter sagt, was er bietet, der Nutzer bekommt ein Handle oder eine laute Logzeile, nie ein stilles nil.
local inv = BS.Use('inventory')if inv then inv.GiveItem(src, 'bandage', 1) endEs ist neu, der meiste Code ruft Exports noch direkt auf. Beides funktioniert.
Verwendete Statebags
Abschnitt betitelt „Verwendete Statebags“| Bag | Auf | Gesetzt von | Bedeutung |
|---|---|---|---|
bsDowned | Spieler | bs_medical | Spieler ist am Boden |
bsDead, bsRevivable | Spieler | bs_medical | |
bsDrugMods | Spieler (lokal) | bs_drugs | drain, regen, cap, infection, sway |
bsSteady | Spieler (lokal) | bs_boosters | Rückstoß-/Schwank-Multiplikator |
bsDrone | Spieler | bs_gadgets | steuert eine Drohne |
bsSquad | Spieler | bs_factions | Squad-Infos |
bsFactionStyle, bsFactionTag, bsFactionCanInvite, bsFactionSafeZone | Spieler | bs_factions | Tag-Stil, Berechtigungen, Safe Zone |
bsVipTag | Spieler | bs_vip | { tier, colour, style, hex, blip, hud }, nil ohne VIP |
bsInRaid, bsRogue | Spieler | bs_extraction | PvP-Zonen-Status |
bsSitting | Spieler | bs_emotes | |
bsZombie | Entity | bs_zombies | Walker-Registry-ID. Im vernetzten Modus auch zType, zGait, zState… |
bsZombieDead | Entity | bs_zombies | vernetzte Leiche |
bsCorpse | Entity | bs_medical | Spielerkörper |
bsProtected, bsKeep | Entity | viele | der Aufräumlauf von bs_world überspringt sie |
bsVehicle, bsLocked, bsOwned, bsWindowsDown | Fahrzeug | bs_vehicles | |
bsBuilt | Entity | bs_building |
Wenn du eine Entity erzeugst, die bs_world nicht aufräumen soll, setze bsProtected darauf.
Event-Namen
Abschnitt betitelt „Event-Namen“bs:<system>:<thing>, am Ende in lowerCamelCase:
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 hält seine eigenen Namen in BS.Events (bs_core/shared/sh_events.lua), damit nichts sie hartkodiert. Hooks heißen bs:hook:*. Callback-Namen nutzen system:action ohne das Präfix bs:.
Jedes Event und jeder Callback pro Ressource steht in der API-Referenz.
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.