FiveM Crafting Sistemi: Tarifler, İstasyonlar ve İlerleme
Kapsamlı bir FiveM crafting sistemi oluşturun. Tarif mantığı, çalışma tezgahları, yetenek ilerlemesi ve QBCore, ESX ile ox_inventory için en iyi crafting scriptleri.
Agency Scripts
Agency Scripts Kurucusu ve Baş Geliştiricisi
Crafting Sistemleri Oyuncu Bağlılığını Neden Artırır?
Bir crafting sistemi, oyunculara kaynak toplama, haritayı keşfetme, ticaret için diğer oyuncularla etkileşime girme ve somut bir şey üretmek için zaman ayırma nedeni sunar. Crafting olmadan eşyalar ya sonsuz stoğa sahip dükkanlardan ya da admin spawn işlemlerinden gelir; bu durum ekonomiyi sıradanlaştırır ve oyuncu etkisini ortadan kaldırır. İyi tasarlanmış bir crafting sistemi, bir oyuncunun ham maddeleri çıkardığı, diğerinin bunları işlediği ve üçüncüsünün nihai ürünü monte ettiği tedarik zincirleri oluşturur. Bu karşılıklı bağımlılık, doğal roleplay etkileşimleri ve ekonomik hareketlilik üretir. Silah üretimi suç dünyasına bir risk ve ödül katmanı eklerken yemek pişirme, terzilik veya elektronik onarımı gibi yasal üretim yolları meşru gelir akışları sağlar. Bu rehberde tarifler, tezgahlar, yetenek ilerlemesi ve blueprint keşfi içeren eksiksiz bir crafting altyapısı kuracağız.
Tarif Sistemi Tasarımı
Tarifler, her crafting sisteminin temel veri yapısıdır. Her tarif; girdi olarak hangi eşyaların gerektiğini, çıktı olarak hangi eşyanın üretildiğini, crafting sürecinin ne kadar sürdüğünü, hangi yetenek seviyesinin gerektiğini ve isteğe bağlı olarak hangi çalışma tezgahı türünün zorunlu olduğunu tanımlar. İstemci crafting UI ekranını görüntülemek için tarif verilerine ihtiyaç duyarken, sunucu crafting denemelerini doğrulamak için buna ihtiyaç duyduğundan, tarifleri hem istemcinin hem de sunucunun erişebileceği paylaşımlı bir yapılandırma dosyasında saklayın. Hızlı aramalar için benzersiz bir tarif kimliği ile indekslenmiş düz bir tablo kullanın. Şunları dahil edin: category UI'da tarifleri düzenlemek için bir alan ve bir successChance oyuncunun beceri seviyesiyle değiştirilebilen ve üst düzey zanaatkarlığa bir risk unsuru ekleyen alan.
-- 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},
},
},
}
Sunucu Taraflı Crafting Mantığı
Kötüye kullanımı önlemek için tüm üretim doğrulamaları sunucuda gerçekleşmelidir. Sunucu, oyuncunun envanterinde gerekli malzemelerin bulunduğunu, yetenek gereksinimini karşıladığını, doğru tezgah türünün yakınında olduğunu ve halihazırda bir üretim sürecinde olmadığını kontrol eder. Doğrulamanın ardından malzemeleri kaldırır, süre zamanlayıcısını çalıştırır, yetenek seviyesine göre uyarlanan temel şansa dayalı olarak başarıyı hesaplar ve üretilen eşyayı verir ya da başarısızlık durumunda malzemelerin bir kısmını iade eder. Üretim sonuçlarını bildirmesi konusunda istemciye asla güvenmeyin. İstemci yalnızca belirli bir tarif ID'sini üretmek için bir istek göndermeli ve sunucu her koşulu bağımsız olarak doğrulamalıdır. Bu, modifiye edilmiş bir istemcinin malzeme harcamadan eşya ürettiğini iddia ettiği eşya kopyalama açıklarını önler.
-- 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)
Yetenek Gelişimi ve Deneyim
Bir beceri ilerleme sistemi, craft işine zaman ayıran oyuncular için uzun vadeli hedefler ve bir gelişim hissi ekler. Her başarılı craft, beceri seviyelerine doğru biriken deneyim puanları kazandırır. Daha yüksek beceri seviyeleri gelişmiş tariflerin kilidini açar ve zorlu craft işlemlerinin başarı oranını artırır. Beceri verilerini veritabanında karakter başına saklayın; böylece veriler kalıcı olur ve oyuncu hesabı yerine karaktere bağlanır. Net kilometre taşları belirleyin: seviye 0-10 temel yemek pişirme ve tamire izin verir, seviye 11-30 elektronik eşyaları ve temel aletleri açar, seviye 31-60 silah bileşenlerini ve gelişmiş üretimi etkinleştirir, seviye 61-100 ise nadir ve efsanevi tariflerin kilidini açar. Mevcut beceri seviyesini ve ilerleme çubuğunu craft UI üzerinde gösterin, böylece oyuncular bir sonraki kilit açmaya ne kadar yakın olduklarını her zaman görebilirler.
-- 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
Workbench Etkileşimi ve Client UI
Workbench'ler, oyuncuların eşya üretmek için gittiği oyun dünyasındaki fiziksel konumlardır. Her workbench türü belirli bir tarif kategorisini destekler: ateşli silahlar için silah tezgahı, yemekler için mutfak, aletler için elektronik istasyonu. Client tarafında bir marker oluşturun veya her workbench konumunda bir etkileşim noktası oluşturmak için ox_target gibi bir target sistemi kullanın. Oyuncu etkileşime girdiğinde yakınlığını kontrol edin, mevcut tarifleri workbench türüne ve oyuncunun yetenek seviyesine göre filtreleyin ve crafting UI ekranını açın. Arayüz; her tarifin adını, mevcut envanter adetleriyle birlikte gerekli malzemeleri, başarı olasılığını, üretim süresini ve yetenek XP ödülünü göstermelidir. Oyuncunun tüm gerekli malzemelere sahip olduğu tarifleri vurgulayın ve eksik malzemesi olanları soluklaştırın. Üretim sırasında uygun bir animasyon oynatın, bir ilerleme çubuğu gösterin ve oyuncuların üretim ortasında uzaklaşmasını engellemek için oyuncu hareketini devre dışı bırakın.
-- 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)
Üretim Zincirleri ve Materyal Toplama
Üretim zincirleri, nihai ürünü üretmeden önce oyuncuların ham maddeleri birden fazla aşamadan geçirmesini gerektirerek derinlik katar. Demir cevheri bir fırında çelik külçelere eritilmeli, ardından bir metal işleme atölyesinde çelik plakalara preslenmeli ve son olarak ateşli silahlar üretmek için bir silah tezgâhında kullanılmalıdır. Her adım farklı bir tezgâh konumu ve potansiyel olarak farklı bir yetenek gerektirir. Bu zincir tasarımı, bazı oyuncuların madencilik ve eritmeye odaklanırken diğerlerinin montajda uzmanlaştığı uzmanlaşmayı teşvik eder. Materyal toplama noktaları, oyuncuların kaya düğümlerini kazma, bitki toplama veya hurdalıklardan elektronik bileşenler arama gibi toplama eylemleri gerçekleştirebileceği bölgeler kullanılarak haritaya yayılmalıdır. Kaynakların sınırlı olması ve oyuncuların erişim için rekabet ederek bölgeye dayalı rol yapma çatışmalarını tetiklemesi amacıyla toplama noktalarında bir yeniden doğma (respawn) zamanlayıcısı kullanın.
Blueprint Keşfi ve Nadir Tarifler
Tüm tarifler en baştan itibaren erişilebilir olmamalıdır. Blueprint keşfi, gelişmiş tarifleri ganimet düşürmelerinin, görev tamamlamalarının, NPC satın alımlarının veya rastgele dünya etkinliklerinin arkasına gizleyerek bir keşif ve ilerleme teşviki sunar. Bir oyuncu bir blueprint keşfettiğinde, bu veritabanında saklanan kişisel tarif listesine eklenir. Blueprint'ler takas edilebilir eşyalar olabilir, böylece nadir tariflerin değerli mallara dönüştüğü ikincil bir pazar oluşur. Örneğin, askeri düzeyde bir silah blueprint'i yalnızca belirli soygun ödüllerinden düşebilirken, efsanevi bir yemek tarifi yalnızca belirli zamanlarda ortaya çıkan gizli bir NPC'den satın alınabilir. Crafting UI'ı, oyuncunun kilidini açtığı blueprint'leri tam tarif listesiyle karşılaştırır ve yalnızca erişebildikleri tarifleri gösterir; bu da her yeni blueprint'in üretim yeteneklerini genişleten anlamlı bir kilit açma gibi hissettirdiği bir ilerleme duygusu yaratır.
-- 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)