FiveM-Handwerkssystem: Rezepte, Werkbänke und Fortschritt
Erstelle ein vielseitiges Handwerkssystem für FiveM mit Rezepten, Werkbänken, Fähigkeiten und den besten Scripts für QBCore, ESX und ox_inventory.
Agency Scripts
Gründer und leitender Entwickler bei Agency Scripts
Warum Crafting-Systeme das Engagement von Spielern fördern
Ein Crafting-System gibt Spielern einen Grund, Ressourcen zu sammeln, die Karte zu erkunden, mit anderen Spielern zu handeln und Zeit in die Herstellung greifbarer Gegenstände zu investieren. Ohne Crafting kommen Gegenstände entweder aus Läden mit unbegrenztem Vorrat oder aus Admin-Spawns. Beides schwächt die Wirtschaft und nimmt Spielern den Einfluss auf das Spielgeschehen. Ein gut konzipiertes Crafting-System schafft Lieferketten, in denen ein Spieler Rohstoffe abbaut, ein anderer sie verarbeitet und ein dritter das Endprodukt herstellt. Diese gegenseitige Abhängigkeit sorgt für spontane Roleplay-Interaktionen und wirtschaftliche Aktivität. Das Herstellen von Waffen ergänzt die kriminelle Unterwelt um Risiko und Belohnung, während legale Crafting-Wege wie Kochen, Schneidern oder die Reparatur von Elektronik legitime Einnahmequellen bieten. In diesem Leitfaden erstellen wir ein vollständiges Crafting-Framework mit Rezepten, Werkbänken, Skill-Fortschritt und dem Entdecken von Bauplänen.
Das Rezeptsystem entwerfen
Rezepte sind die zentrale Datenstruktur jedes Crafting-Systems. Jedes Rezept legt fest, welche Gegenstände als Zutaten benötigt werden, welcher Gegenstand als Ergebnis entsteht, wie lange der Crafting-Vorgang dauert, welches Skill-Level erforderlich ist und optional, welcher Werkbanktyp benötigt wird. Speichere Rezepte in einer gemeinsamen Konfigurationsdatei, auf die Client und Server zugreifen können. Der Client benötigt Rezeptdaten, um die Crafting-UI anzuzeigen, während der Server sie zur Validierung von Crafting-Versuchen benötigt. Verwende für schnelle Suchvorgänge eine flache Tabelle, die nach einer eindeutigen Rezept-ID indiziert ist. Füge ein category -Feld hinzu, um Rezepte in der UI zu organisieren, und ein successChance -Feld, das sich anhand des Skill-Levels des Spielers ändern lässt und bei Crafting auf hohem Niveau ein Risikoelement hinzufügt.
-- shared/config.lua (accessible by both client and server)
Config = {}
Config.Recipes = {
-- Weapons
pistol_craft = {
label = 'Craft Pistol',
category = 'weapons',
result = {item = 'weapon_pistol', count = 1},
ingredients = {
{item = 'steel_plate', count = 3},
{item = 'weapon_spring', count = 2},
{item = 'rubber_grip', count = 1},
{item = 'gun_oil', count = 1},
},
duration = 15000, -- 15 seconds
skillReq = 50, -- crafting skill level
workbench = 'weapons_bench',
successBase = 0.75, -- 75% base success
xpReward = 25,
},
-- Electronics
radio_craft = {
label = 'Assemble Radio',
category = 'electronics',
result = {item = 'radio', count = 1},
ingredients = {
{item = 'electronic_parts', count = 2},
{item = 'copper_wire', count = 3},
{item = 'plastic_casing', count = 1},
},
duration = 8000,
skillReq = 10,
workbench = 'electronics_bench',
successBase = 0.90,
xpReward = 10,
},
-- Cooking
sandwich_craft = {
label = 'Make Sandwich',
category = 'cooking',
result = {item = 'sandwich', count = 2},
ingredients = {
{item = 'bread', count = 2},
{item = 'cheese', count = 1},
{item = 'lettuce', count = 1},
},
duration = 3000,
skillReq = 0,
workbench = 'kitchen',
successBase = 1.0,
xpReward = 3,
},
}
Config.Workbenches = {
weapons_bench = {
label = 'Weapons Workbench',
model = 'prop_tool_bench02',
coords = {
{x = 1083.5, y = -1975.3, z = 31.5, h = 140.0},
},
},
electronics_bench = {
label = 'Electronics Station',
model = 'prop_cs_server_drive',
coords = {
{x = 732.1, y = -1073.8, z = 22.2, h = 90.0},
},
},
kitchen = {
label = 'Kitchen',
model = 'prop_cooker_03',
coords = {
{x = -1192.3, y = -896.1, z = 13.9, h = 35.0},
},
},
}
Crafting-Logik auf dem Server
Die gesamte Crafting-Validierung muss auf dem Server erfolgen, um Exploits zu verhindern. Der Server prüft, ob der Spieler die erforderlichen Zutaten im Inventar hat, das nötige Skill-Level erreicht, sich in der Nähe des passenden Werkbanktyps befindet und nicht bereits einen Crafting-Vorgang ausführt. Nach der Validierung entfernt der Server die Zutaten, startet den Timer für die Dauer, berechnet die Erfolgschance anhand der Basiswahrscheinlichkeit, angepasst an das Skill-Level, und gibt entweder den hergestellten Gegenstand aus oder erstattet bei einem Fehlschlag einen Teil der Materialien zurück. Vertraue dem Client niemals die Meldung des Crafting-Ergebnisses an. Der Client sollte nur eine Anfrage zum Crafting einer bestimmten Rezept-ID senden. Der Server überprüft dann unabhängig alle Bedingungen. So werden Exploits zur Gegenstandsduplizierung verhindert, bei denen ein modifizierter Client behauptet, Gegenstände hergestellt zu haben, ohne Zutaten zu verbrauchen.
-- server.lua
local craftingPlayers = {} -- track who is currently crafting
local playerSkills = {} -- cache of player crafting XP
RegisterNetEvent('crafting:attempt', function(recipeId)
local src = source
if craftingPlayers[src] then
TriggerClientEvent('notifications:show', src, 'Error', 'Already crafting.', 'error')
return
end
local recipe = Config.Recipes[recipeId]
if not recipe then return end
-- Check skill requirement
local skill = GetPlayerCraftingSkill(src)
if skill < recipe.skillReq then
TriggerClientEvent('notifications:show', src,
'Error', 'Requires crafting level ' .. recipe.skillReq .. '.', 'error')
return
end
-- Check ingredients
for _, ing in ipairs(recipe.ingredients) do
local count = exports.ox_inventory:GetItemCount(src, ing.item)
if count < ing.count then
TriggerClientEvent('notifications:show', src,
'Error', 'Missing materials.', 'error')
return
end
end
-- Remove ingredients
for _, ing in ipairs(recipe.ingredients) do
exports.ox_inventory:RemoveItem(src, ing.item, ing.count)
end
craftingPlayers[src] = true
TriggerClientEvent('crafting:startProgress', src, recipe.duration, recipe.label)
-- Wait for crafting duration
SetTimeout(recipe.duration, function()
craftingPlayers[src] = nil
-- Calculate success chance (skill bonus caps at +20%)
local skillBonus = math.min((skill - recipe.skillReq) * 0.005, 0.20)
local finalChance = math.min(recipe.successBase + skillBonus, 1.0)
if math.random() <= finalChance then
exports.ox_inventory:AddItem(src, recipe.result.item, recipe.result.count)
AddCraftingXP(src, recipe.xpReward)
TriggerClientEvent('notifications:show', src,
'Success', 'Crafted: ' .. recipe.label, 'success')
else
-- Return 50% of materials on failure
for _, ing in ipairs(recipe.ingredients) do
local refund = math.floor(ing.count * 0.5)
if refund > 0 then
exports.ox_inventory:AddItem(src, ing.item, refund)
end
end
TriggerClientEvent('notifications:show', src,
'Failed', 'Crafting failed. Some materials recovered.', 'error')
end
end)
end)
Skill-Fortschritt und Erfahrung
Ein Skill-Fortschrittssystem gibt Spielern, die Zeit ins Crafting investieren, langfristige Ziele und ein Gefühl des Vorankommens. Für jedes erfolgreiche Crafting erhalten sie Erfahrungspunkte, die sich bis zum nächsten Skill-Level summieren. Höhere Skill-Level schalten fortgeschrittene Rezepte frei und erhöhen die Erfolgschance bei anspruchsvollen Crafting-Vorgängen. Speichere Skill-Daten für jeden Charakter in der Datenbank, damit sie erhalten bleiben und an den Charakter statt an das Spielerkonto gebunden sind. Lege klare Meilensteine fest: Level 0-10 ermöglicht einfaches Kochen und Reparaturen, Level 11-30 schaltet Elektronik und einfache Werkzeuge frei, Level 31-60 ermöglicht Waffenkomponenten und fortgeschrittene Fertigung, und Level 61-100 schaltet seltene und legendäre Rezepte frei. Zeige das aktuelle Skill-Level und den Fortschrittsbalken in der Crafting-UI an, damit Spieler immer sehen, wie nah sie am nächsten freischaltbaren Inhalt sind.
-- Skill system (server.lua continued)
local skillCache = {}
function GetPlayerCraftingSkill(src)
if skillCache[src] then return skillCache[src].level end
return 0
end
function AddCraftingXP(src, amount)
if not skillCache[src] then
skillCache[src] = {xp = 0, level = 0}
end
skillCache[src].xp = skillCache[src].xp + amount
local newLevel = CalculateLevel(skillCache[src].xp)
if newLevel > skillCache[src].level then
skillCache[src].level = newLevel
TriggerClientEvent('notifications:show', src,
'Level Up!', 'Crafting level: ' .. newLevel, 'success', 6000)
-- Check for newly unlocked recipes
local unlocked = GetNewlyUnlockedRecipes(newLevel, skillCache[src].level)
for _, recipe in ipairs(unlocked) do
TriggerClientEvent('notifications:show', src,
'Recipe Unlocked', recipe.label .. ' is now available.', 'info', 5000)
end
end
skillCache[src].level = newLevel
SaveCraftingSkill(src)
end
function CalculateLevel(xp)
-- Each level requires progressively more XP
-- Level 1 = 50xp, Level 2 = 150xp, Level 10 = 2750xp, etc.
local level = 0
local required = 50
local remaining = xp
while remaining >= required and level < 100 do
remaining = remaining - required
level = level + 1
required = math.floor(required * 1.15)
end
return level
end
function SaveCraftingSkill(src)
local charId = exports['multichar']:GetCharacterId(src)
if not charId or not skillCache[src] then return end
MySQL.update(
'INSERT INTO character_skills (character_id, skill, xp, level) VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE xp = ?, level = ?',
{charId, 'crafting', skillCache[src].xp, skillCache[src].level, skillCache[src].xp, skillCache[src].level}
)
end
Interaktion mit Werkbänken und Client-UI
Werkbänke sind Orte in der Spielwelt, an denen Spieler Gegenstände herstellen können. Jeder Werkbanktyp unterstützt eine bestimmte Rezeptkategorie: eine Waffenwerkbank für Schusswaffen, eine Küche für Lebensmittel, eine Elektronikstation für Gadgets. Erstelle clientseitig an jedem Werkbankstandort einen Marker oder verwende ein Target-System wie ox_target, um einen Interaktionspunkt einzurichten. Prüfe bei der Interaktion, ob sich der Spieler in der Nähe befindet, filtere die verfügbaren Rezepte nach Werkbanktyp und Skill-Level des Spielers und öffne die Crafting-UI. Die UI sollte für jedes Rezept den Namen, die benötigten Zutaten mit den aktuellen Inventarbeständen, die Erfolgschance, die Crafting-Dauer und die Skill-XP-Belohnung anzeigen. Hebe Rezepte hervor, für die der Spieler alle benötigten Materialien besitzt, und blende Rezepte ab, bei denen Materialien fehlen. Spiele während des Craftings eine passende Animation ab, zeige einen Fortschrittsbalken an und deaktiviere die Spielerbewegung, um Exploits zu verhindern, bei denen Spieler sich mitten im Crafting-Vorgang entfernen.
-- client.lua
local isCrafting = false
-- Setup workbench interaction points
CreateThread(function()
for benchType, bench in pairs(Config.Workbenches) do
for _, loc in ipairs(bench.coords) do
exports.ox_target:addSphereZone({
coords = vector3(loc.x, loc.y, loc.z),
radius = 1.5,
options = {
{
label = 'Use ' .. bench.label,
icon = 'fas fa-tools',
onSelect = function()
OpenCraftingMenu(benchType)
end,
canInteract = function()
return not isCrafting
end
}
}
})
end
end
end)
function OpenCraftingMenu(benchType)
local recipes = {}
for id, recipe in pairs(Config.Recipes) do
if recipe.workbench == benchType then
recipes[#recipes + 1] = {
id = id,
label = recipe.label,
category = recipe.category,
ingredients = recipe.ingredients,
duration = recipe.duration,
skillReq = recipe.skillReq,
successBase = recipe.successBase,
xpReward = recipe.xpReward,
result = recipe.result,
}
end
end
-- Send to NUI or use a menu library
SendNUIMessage({action = 'openCrafting', recipes = recipes, benchType = benchType})
SetNuiFocus(true, true)
end
RegisterNetEvent('crafting:startProgress', function(duration, label)
isCrafting = true
local ped = PlayerPedId()
FreezeEntityPosition(ped, true)
-- Play crafting animation
RequestAnimDict('mini@repair')
while not HasAnimDictLoaded('mini@repair') do Wait(10) end
TaskPlayAnim(ped, 'mini@repair', 'fixing_a_player', 8.0, -8.0, -1, 1, 0, false, false, false)
-- Progress bar via ox_lib or custom NUI
exports.ox_lib:progressBar({
duration = duration,
label = label,
useWhileDead = false,
canCancel = false,
disable = {move = true, car = true, combat = true},
})
ClearPedTasks(ped)
FreezeEntityPosition(ped, false)
isCrafting = false
end)
Produktionsketten und Materialbeschaffung
Produktionsketten sorgen für mehr Tiefe, indem Spieler Rohstoffe in mehreren Schritten verarbeiten müssen, bevor sie das Endprodukt herstellen können. Eisenerz muss in einem Schmelzofen zu Stahlbarren geschmolzen werden. Anschließend werden diese in einem Metallwerk zu Stahlplatten gepresst, die schließlich an einer Waffenwerkbank zur Herstellung von Schusswaffen verwendet werden. Jeder Schritt erfordert einen anderen Werkbankstandort und möglicherweise eine andere Fähigkeit. Dieses Kettendesign fördert die Spezialisierung: Einige Spieler konzentrieren sich auf den Abbau und das Schmelzen, während andere sich auf die Montage spezialisieren. Sammelstellen für Materialien sollten über die Karte verteilt sein und Zonen nutzen, in denen Spieler Rohstoffe sammeln können, zum Beispiel durch den Abbau von Gesteinsvorkommen, das Ernten von Pflanzen oder das Durchsuchen von Schrottplätzen nach elektronischen Bauteilen. Nutze einen Respawn-Timer für Sammelstellen, damit Ressourcen begrenzt sind und Spieler um den Zugang konkurrieren, was territoriale Rollenspielkonflikte fördert.
Bauplanentdeckung und seltene Rezepte
Nicht alle Rezepte sollten von Anfang an verfügbar sein. Die Entdeckung von Bauplänen sorgt für einen Anreiz zur Erkundung und zum Spielfortschritt, indem fortgeschrittene Rezepte hinter Beute, abgeschlossenen Quests, Käufen bei NPCs oder zufälligen Weltereignissen verborgen werden. Entdeckt ein Spieler einen Bauplan, wird er seiner persönlichen, in der Datenbank gespeicherten Rezeptliste hinzugefügt. Baupläne können handelbare Gegenstände sein und so einen Sekundärmarkt schaffen, auf dem seltene Rezepte zu wertvollen Handelsgütern werden. So könnte ein Bauplan für eine Waffe in Militärqualität nur als Belohnung für bestimmte Raubüberfälle erhältlich sein, während ein legendäres Kochrezept bei einem versteckten NPC gekauft werden kann, der nur zu bestimmten Zeiten auftaucht. Die Crafting-UI gleicht die freigeschalteten Baupläne des Spielers mit der vollständigen Rezeptliste ab und zeigt nur Rezepte an, auf die der Spieler Zugriff hat. So entsteht ein Gefühl des Fortschritts, bei dem jeder neue Bauplan eine bedeutsame Freischaltung darstellt und die Crafting-Möglichkeiten erweitert.
-- Blueprint system (server.lua)
RegisterNetEvent('crafting:useBlueprint', function(blueprintItem)
local src = source
local charId = exports['multichar']:GetCharacterId(src)
if not charId then return end
-- Validate the player has the blueprint item
local count = exports.ox_inventory:GetItemCount(src, blueprintItem)
if count < 1 then return end
-- Map blueprint items to recipe IDs
local blueprintMap = {
blueprint_pistol = 'pistol_craft',
blueprint_radio = 'radio_craft',
blueprint_armor = 'armor_craft',
blueprint_lockpick = 'adv_lockpick_craft',
}
local recipeId = blueprintMap[blueprintItem]
if not recipeId then return end
-- Check if already unlocked
local exists = MySQL.scalar.await(
'SELECT 1 FROM character_blueprints WHERE character_id = ? AND recipe_id = ?',
{charId, recipeId}
)
if exists then
TriggerClientEvent('notifications:show', src,
'Info', 'You already know this recipe.', 'info')
return
end
-- Consume blueprint and unlock recipe
exports.ox_inventory:RemoveItem(src, blueprintItem, 1)
MySQL.insert(
'INSERT INTO character_blueprints (character_id, recipe_id) VALUES (?, ?)',
{charId, recipeId}
)
local recipe = Config.Recipes[recipeId]
TriggerClientEvent('notifications:show', src,
'Blueprint Learned', 'You can now craft: ' .. (recipe and recipe.label or recipeId), 'success', 6000)
end)