Bloga Geri Dön
Guide8 dk. okuma

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)

Başlamaya Hazır mısınız?

Scriptleri mağazamızdan edinin veya destek, güncellemeler ve gelecek yeniliklere göz atmak için Discord'a katılın.