Zum Inhalt springen

Eine Ressource auf bs_core schreiben

Bauen wir eine kleine: einen “Funkturm”, den man alle 10 Minuten einmal für $50 Bargeld und etwas XP benutzen kann. Er ist ausgedacht, die Teile, die er nutzt, sind echt.

resources/[beyond]/bs_radiotower/
├── fxmanifest.lua
├── config/config.lua
├── client/cl_main.lua
└── server/sv_main.lua

Gib ihm das Präfix bs_. FiveM-Ressourcennamen sind ein einziger globaler Namensraum, eine Ressource namens radio kollidiert früher oder später mit dem Skript von jemandem.

fx_version 'cerulean'
game 'gta5'
lua54 'yes'
name 'bs_radiotower'
description 'BeyondSurvival - radio tower example'
shared_scripts {
'@bs_core/shared/sh_bridge.lua', -- BSB callbacks + BSB.RegisterCommand
'config/config.lua',
}
client_scripts { 'client/cl_main.lua' }
server_scripts { 'server/sv_main.lua' }
dependencies { 'bs_core' }
-- live overrides from the F1 console, AFTER your config files
shared_script '@bs_config/shared/apply.lua'
-- crash breadcrumbs
client_script '@bs_core/shared/sh_crumbs.lua'

Füge dann ensure bs_radiotower in die server.cfg ein, nach bs_core und nach allem, was du aufrufst.

RadioTower = {}
RadioTower.Settings = {
reward = 50,
xp = 20,
cooldown = 600, -- seconds
pos = vector3(0.0, 0.0, 0.0),
range = 3.0,
}

Eine globale Tabelle mit dem Namen deiner Ressource. Das ist es, was der F1-Apply-Helper patcht (siehe Config-Apply-Helper).

local last = {} -- [charId] = os.time()
BSB.RegisterCallback('radiotower:use', function(src)
local p = exports.bs_core:GetPlayerSummary(src)
if not p then return { ok = false, error = 'Not loaded.' } end
-- never trust the client: check distance here too
local pos = GetEntityCoords(GetPlayerPed(src))
if #(pos - RadioTower.Settings.pos) > RadioTower.Settings.range + 2.0 then
return { ok = false, error = 'Too far.' }
end
local now = os.time()
if last[p.charId] and now - last[p.charId] < RadioTower.Settings.cooldown then
return { ok = false, error = 'Nothing on the air yet.' }
end
last[p.charId] = now
exports.bs_core:AddMoney(src, 'cash', RadioTower.Settings.reward, 'radiotower')
exports.bs_core:AddXP(src, RadioTower.Settings.xp, 'radiotower')
return { ok = true }
end)

Geld und XP nur über bs_core-Exports. Schreibe nie selbst in die DB-Spalten.

local function use()
if not exports.bs_core:IsLoaded() then return end
Crumb('radiotower', 'use')
local res = BSB.Await('radiotower:use', 5000)
if not res then return end -- timed out, server side not running
TriggerEvent('bs:ui:notify', {
type = res.ok and 'success' or 'error',
message = res.ok and 'You picked up a signal.' or res.error,
})
end
RegisterCommand('radiotower', use, false)

Für eine echte Interaktion würdest du statt eines Befehls eine Option bei bs_interactions registrieren (der ALT-Punkt); schau, wie bs_recycler oder bs_mining es machen.

Rufe exports.bs_guard:Allow(src, 'teleport', 3000) auf, bevor du sie serverseitig bewegst, sonst markiert bs_guard jede Nutzung.

  • Globals einer anderen Ressource sind unsichtbar. Items.Definitions in bs_admin ist eine leere Tabelle, nicht die von bs_inventory. Nutze Exports.
  • Funktionen überqueren Exports nicht. Eine Callback-Funktion an den Export einer anderen Ressource zu übergeben funktioniert nicht. Deshalb ist BSB eine Datei zum Einbinden und kein Export.
  • Kein Wait in einem Export, den jemand anderes aufruft. Er läuft im Thread des Aufrufers in deiner Runtime und wirft einen Fehler. bs_radial hatte diesen Bug, deshalb gibt es BSB.Radial.
  • local a, b = x and f() or y - Lua behält dort nur den ersten Rückgabewert von f(). Schreibe es mit einem if.
  • Ein Local, das oberhalb seiner Deklaration benutzt wird, ist ein globales nil. luac5.4 -l file.lua | grep _ENV findet sie.
  • Sichere GetEntityModel mit DoesEntityExist ab. Bei einem toten Handle stürzt der Spielclient hart ab.
  • Keine NUI-Nachrichten pro Frame im Leerlauf. Bei Änderung senden.
  • Jedes bs:ui:opened braucht ein bs:ui:closed auf jedem Ausgangspfad, siehe UI-/NUI-Konventionen.