Bloga Geri Dön
Tutorial9 dk. okuma

FiveM Silah Dükkanı Sistemi: Ruhsatlı Silah Mağazası Scriptleri

FiveM sunucunuz için ruhsatlı bir silah dükkanı kurun. Ruhsatlar, adli sicil kontrolleri, mühimmat, eklentiler ve rol için en iyi silah dükkanı scriptleri.

Agency Scripts

Agency Scripts Kurucusu ve Baş Geliştiricisi

Silah Mağazası Mimarisine Genel Bakış

Bir FiveM roleplay sunucusundaki silah dükkanı sistemi, yasalara saygılı vatandaşlar için erişilebilirlik ile oynanış derinliği yaratan gerçekçi kısıtlamaları dengelemelidir. Mimari iki farklı kola ayrılır: silah ruhsatı, geçmiş kontrolü ve satın alma bekleme sürelerini zorunlu kılan yasal dükkanlar ile hiçbir evrak olmadan daha yüksek fiyatlara izlenemez silahlar satan yasa dışı karaborsa satıcıları. Her iki sistem de aynı temel envanter ve işlem mantığını paylaşır, ancak erişim gereksinimleri ve her silaha eklenen meta veriler açısından farklılık gösterir. Yasal silahlar, alıcının vatandaşlık kimliğine bağlı seri numaraları alarak polis tarafından izlenebilir hale gelirken, karaborsa silahları belirli bir oyuncuya kadar takip edilemeyen kazınmış seri numaralarına sahiptir. Bu ikili tasarım doğal bir roleplay gerilimi yaratır, çünkü oyuncular yasal satın alımların kolaylığı ve düşük maliyeti ile karaborsanın anonimliği arasında bir denge kurmak zorundadır.

Silahlar ve Lisanslar İçin Veri Tabanı Şeması

Veritabanınızın mağaza başına silah envanterini, bireysel silah seri numaralarını, oyuncu lisanslarını ve satın alma geçmişini takip etmesi gerekir. Seri numarası sistemi silah dükkanı ile polis soruşturma sistemleri arasında köprü kurduğu için kritik öneme sahiptir. Yasal olarak satın alınan her silah, polisin trafik kontrollerinde veya olay yeri incelemelerinde sorgulayabileceği benzersiz bir seri numarası alır. Şemayı hem mağaza stok yönetimini hem de bireysel silah takibini idare edecek şekilde tasarlayın:

CREATE TABLE IF NOT EXISTS weapon_shops (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    type ENUM('legal', 'blackmarket') DEFAULT 'legal',
    coords_x FLOAT NOT NULL,
    coords_y FLOAT NOT NULL,
    coords_z FLOAT NOT NULL,
    is_active BOOLEAN DEFAULT TRUE
);

CREATE TABLE IF NOT EXISTS weapon_inventory (
    id INT AUTO_INCREMENT PRIMARY KEY,
    shop_id INT NOT NULL,
    weapon_name VARCHAR(50) NOT NULL,
    label VARCHAR(100) NOT NULL,
    price INT NOT NULL,
    ammo_price INT DEFAULT 0,
    category ENUM('handguns', 'smgs', 'rifles', 'shotguns', 'melee', 'throwables') NOT NULL,
    license_required ENUM('none', 'basic', 'advanced', 'military') DEFAULT 'none',
    stock INT DEFAULT -1,
    INDEX idx_shop (shop_id),
    FOREIGN KEY (shop_id) REFERENCES weapon_shops(id)
);

CREATE TABLE IF NOT EXISTS weapon_serials (
    serial VARCHAR(20) PRIMARY KEY,
    weapon_name VARCHAR(50) NOT NULL,
    owner_citizenid VARCHAR(50) DEFAULT NULL,
    is_scratched BOOLEAN DEFAULT FALSE,
    purchased_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    shop_id INT DEFAULT NULL,
    INDEX idx_owner (owner_citizenid)
);

CREATE TABLE IF NOT EXISTS weapon_licenses (
    id INT AUTO_INCREMENT PRIMARY KEY,
    citizenid VARCHAR(50) NOT NULL,
    license_type ENUM('basic', 'advanced', 'military') NOT NULL,
    issued_by VARCHAR(50) DEFAULT NULL,
    issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP NULL,
    revoked BOOLEAN DEFAULT FALSE,
    UNIQUE KEY unique_license (citizenid, license_type)
);

The weapon_serials tablosu her silahı yaşam döngüsü boyunca ayrı ayrı takip eder. Bir oyuncu yasal olarak bir silah satın aldığında, bir seri numarası oluşturulur ve citizen ID'sine bağlanır. Seri numarasını bir karaborsa hizmeti kullanarak kazırlarsa, is_scratched bayrağı true değerine geçer ve owner_citizenid geçersiz kılınır. Şunlar: stock içindeki alan weapon_inventory kullanır -1 sınırsız stoğu belirtmek için, pozitif değerler ise kıtlık yaratan ve karaborsa talebini artıran sınırlı tedarik mekanizmalarını etkinleştirir.

Yasal Silah Dükkanı Uygulaması

Yasal silah dükkanları, rol yapma sunucusunda çoğu oyuncunun ateşli silah edinmesinin birincil yoludur. Lisans gereksinimlerini uygular, toplu alımları önlemek için satın alma bekleme süreleri (cooldown) koyar ve satılan her silah için takip edilebilir seri numaraları üretir. Dükkan arayüzü, silahları kategoriye göre gruplandırılmış olarak ve her silahın hangi lisans seviyesini gerektirdiğini gösteren net göstergelerle sergilemelidir. Uygun lisansı olmayan oyuncular silahı görür ancak satın alamaz, bu da onları hükümet yetkilileriyle lisans alma sürecini rol yaparak yürütmeye teşvik eder. Karaborsada yeniden satmak üzere stok yapılmasını önlemek için oyuncuların gerçek zamanlı gün başına belirlenen sayıdan fazla silah satın almasını engelleyen bir bekleme süresi sistemi kurun:

RegisterNetEvent('weaponshop:server:purchase', function(shopId, weaponName)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local citizenid = Player.PlayerData.citizenid
    local item = GetShopItem(shopId, weaponName)

    if not item then
        TriggerClientEvent('QBCore:Notify', src, 'Item not available', 'error')
        return
    end

    -- Check license requirement
    if item.license_required ~= 'none' then
        local hasLicense = HasValidLicense(citizenid, item.license_required)
        if not hasLicense then
            TriggerClientEvent('QBCore:Notify', src, 'You need a ' .. item.license_required .. ' weapons license', 'error')
            return
        end
    end

    -- Check purchase cooldown
    local recentPurchases = GetRecentPurchaseCount(citizenid, 86400) -- last 24h
    if recentPurchases >= Config.DailyPurchaseLimit then
        TriggerClientEvent('QBCore:Notify', src, 'Daily purchase limit reached', 'error')
        return
    end

    -- Check stock
    if item.stock ~= -1 then
        if item.stock <= 0 then
            TriggerClientEvent('QBCore:Notify', src, 'Out of stock', 'error')
            return
        end
    end

    -- Check funds
    if Player.PlayerData.money.bank < item.price then
        TriggerClientEvent('QBCore:Notify', src, 'Insufficient funds', 'error')
        return
    end

    -- Generate serial number
    local serial = GenerateWeaponSerial()

    -- Process purchase
    Player.Functions.RemoveMoney('bank', item.price, 'weapon-purchase')
    Player.Functions.AddItem(weaponName, 1, false, { serial = serial })

    -- Record serial
    MySQL.insert('INSERT INTO weapon_serials (serial, weapon_name, owner_citizenid, shop_id) VALUES (?, ?, ?, ?)',
        { serial, weaponName, citizenid, shopId })

    -- Update stock
    if item.stock ~= -1 then
        MySQL.update('UPDATE weapon_inventory SET stock = stock - 1 WHERE shop_id = ? AND weapon_name = ?',
            { shopId, weaponName })
    end

    TriggerClientEvent('QBCore:Notify', src, 'Purchased ' .. item.label .. ' (S/N: ' .. serial .. ')', 'success')
end)

function GenerateWeaponSerial()
    local chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    local serial = ''
    for i = 1, 3 do
        serial = serial .. chars:sub(math.random(#chars), math.random(#chars))
    end
    serial = serial .. '-'
    for i = 1, 5 do
        serial = serial .. tostring(math.random(0, 9))
    end
    return serial
end

Mühimmat ve Eklenti Sistemi

Mühimmat yönetimi, oyuncuların ekonomiyle etkileşimde kalmasını sağlayan ve sınırsız mermi açıklarını önleyen bir kaynak tüketimi ekler. Mermiyi genel bir eşya olarak satmak yerine, silah kategorileriyle eşleşen belirli kalibre türlerine bağlayın. Bir tabanca 9mm mermi kullanır, bir hafif makineli tüfek 9mm paylaşabilir veya .45 ACP kullanabilir ve tüfekler modele bağlı olarak 5.56 veya 7.62 kullanır. Bu kalibre sistemi daha gerçekçi bir deneyim yaratır ve mağaza sahiplerine farklı mühimmat türlerini fiyatlandırmada esneklik sağlar. Her silahın kendi doldurulan mermilerini takip edebilmesi için mermi sayılarını ayrı envanter eşyaları yerine silah meta verilerinde saklayın. Eklentiler de aynı meta veri modelini takip eder; her silah eşyası dürbünler, susturucular, uzatılmış şarjörler ve el fenerleri gibi takılı bileşenlerin bir listesini saklar:

Config.AmmoTypes = {
    ['ammo_9mm']     = { label = '9mm Rounds',    price = 5,  amount = 24, weapons = {'WEAPON_PISTOL', 'WEAPON_COMBATPISTOL', 'WEAPON_SMG'} },
    ['ammo_45acp']   = { label = '.45 ACP Rounds', price = 7,  amount = 24, weapons = {'WEAPON_APPISTOL', 'WEAPON_MACHINEPISTOL'} },
    ['ammo_556']     = { label = '5.56 Rounds',    price = 12, amount = 30, weapons = {'WEAPON_CARBINERIFLE', 'WEAPON_ASSAULTRIFLE'} },
    ['ammo_762']     = { label = '7.62 Rounds',    price = 15, amount = 30, weapons = {'WEAPON_SNIPERRIFLE', 'WEAPON_MARKSMANRIFLE'} },
    ['ammo_12gauge'] = { label = '12ga Shells',    price = 10, amount = 8,  weapons = {'WEAPON_PUMPSHOTGUN', 'WEAPON_ASSAULTSHOTGUN'} },
}

Config.Attachments = {
    ['attachment_suppressor'] = {
        label = 'Suppressor',
        price = 8500,
        component = 'COMPONENT_AT_PI_SUPP_02',
        compatible = {'WEAPON_PISTOL', 'WEAPON_COMBATPISTOL', 'WEAPON_SMG', 'WEAPON_CARBINERIFLE'},
        license_required = 'advanced',
    },
    ['attachment_scope'] = {
        label = 'Holographic Scope',
        price = 3200,
        component = 'COMPONENT_AT_SCOPE_MACRO_02',
        compatible = {'WEAPON_SMG', 'WEAPON_CARBINERIFLE', 'WEAPON_ASSAULTRIFLE'},
        license_required = 'basic',
    },
    ['attachment_extmag'] = {
        label = 'Extended Magazine',
        price = 4500,
        component = 'COMPONENT_CARBINERIFLE_CLIP_02',
        compatible = {'WEAPON_CARBINERIFLE', 'WEAPON_ASSAULTRIFLE', 'WEAPON_SMG'},
        license_required = 'basic',
    },
    ['attachment_flashlight'] = {
        label = 'Flashlight',
        price = 1200,
        component = 'COMPONENT_AT_AR_FLSH',
        compatible = {'WEAPON_PISTOL', 'WEAPON_COMBATPISTOL', 'WEAPON_CARBINERIFLE', 'WEAPON_PUMPSHOTGUN'},
        license_required = 'none',
    },
}

Bir oyuncu bir eklenti satın aldığında, mevcut silahının şurada olduğunu doğrulayın: compatible listesini kontrol edin, lisans seviyelerini doğrulayın ve ardından bileşen hash değerini silahın metadata tablosuna ekleyin. Silah kuşanıldığında, kayıtlı bileşenleri döngüye alıp şununla uygulayın: GiveWeaponComponentToPed. Bu yaklaşım, eklentilerin oturumlar arasında kalıcı olmasını ve istemci manipüle edilerek kopyalanamamasını sağlar.

Kara Borsa Mekanikleri

Kara borsa, gerçek rol yapma derinliğinin yaşadığı yerdir. Statik yasal dükkanların aksine kara borsa satıcıları tehlikeli ve özel hissettirmelidir. Oyuncuların onları bulmak için içeriden bilgiye veya bağlantılara ihtiyaç duyması adına birkaç saatte bir değişen dinamik satıcı konumları uygulayın. Yeni oyuncuların üst düzey silahlara erişmeden önce daha küçük satın alımlarla satıcının güvenini kazanması gereken bir itibar sistemi şart koşun. Kara borsa seri numarasız silahlar, yasal dükkanların stoklayamadığı kısıtlı askeri düzeyde ekipmanlar ve yasal olarak satın alınan silahları takip edilemez hale getiren seri numarası kazıma gibi hizmetler satar. Risk primini yansıtmak ve yasal ile yasa dışı yollar arasında gerçek bir ekonomik gerilim yaratmak için her şeyi yasal dükkanlardan 2-3 kat daha pahalı fiyatlandırın:

Config.BlackMarket = {
    rotationInterval = 10800, -- 3 hours between location changes
    locations = {
        { coords = vector3(89.98, -1810.87, 24.98), heading = 230.0, label = 'Underground Parking' },
        { coords = vector3(1394.27, 1141.48, 114.33), heading = 90.0, label = 'Desert Warehouse' },
        { coords = vector3(-58.21, 6443.31, 31.43), heading = 45.0, label = 'Paleto Docks' },
        { coords = vector3(981.45, -1812.66, 31.14), heading = 180.0, label = 'Industrial Zone' },
    },
    reputationTiers = {
        [0] = { label = 'Unknown', items = {'WEAPON_KNIFE', 'WEAPON_BAT'} },
        [1] = { label = 'Associate', items = {'WEAPON_PISTOL', 'WEAPON_MICROSMG', 'ammo_9mm'} },
        [2] = { label = 'Trusted', items = {'WEAPON_SMG', 'WEAPON_PUMPSHOTGUN', 'ammo_45acp', 'ammo_12gauge', 'attachment_suppressor'} },
        [3] = { label = 'Inner Circle', items = {'WEAPON_ASSAULTRIFLE', 'WEAPON_CARBINERIFLE', 'ammo_556', 'service_scratch_serial'} },
        [4] = { label = 'Arms Dealer', items = {'WEAPON_SNIPERRIFLE', 'WEAPON_RPG', 'WEAPON_GRENADELAUNCHER', 'ammo_762', 'armor_heavy'} },
    },
    priceMultiplier = 2.5,
    reputationGainPerPurchase = 0.15,
}

-- Serial scratching service
RegisterNetEvent('blackmarket:server:scratchSerial', function(weaponSlot)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local citizenid = Player.PlayerData.citizenid
    local reputation = GetBlackMarketReputation(citizenid)

    if reputation < 3 then
        TriggerClientEvent('QBCore:Notify', src, 'You don\'t have enough reputation', 'error')
        return
    end

    local item = Player.Functions.GetItemBySlot(weaponSlot)
    if not item or not item.info or not item.info.serial then
        TriggerClientEvent('QBCore:Notify', src, 'No weapon with serial found', 'error')
        return
    end

    local cost = Config.ScratchSerialPrice -- e.g., 15000
    if Player.PlayerData.money.cash < cost then
        TriggerClientEvent('QBCore:Notify', src, 'Not enough cash', 'error')
        return
    end

    Player.Functions.RemoveMoney('cash', cost, 'serial-scratch')

    -- Update database
    MySQL.update('UPDATE weapon_serials SET is_scratched = TRUE, owner_citizenid = NULL WHERE serial = ?',
        { item.info.serial })

    -- Update item metadata
    item.info.serial = 'SCRATCHED'
    Player.Functions.SetInventoryItem(item.name, item.amount, item.info, weaponSlot)

    TriggerClientEvent('QBCore:Notify', src, 'Serial number removed', 'success')
end)

Silah Ruhsat Sistemi

Ruhsat sistemi, silah dükkanlarını hükümet ve kolluk kuvvetleri fraksiyonlarına bağlayan önemli bir bürokratik roleplay katmanı oluşturur. Oyuncular belediye binasında veya polis merkezinde silah ruhsatı için başvurmalı, adli sicil kayıtlarını inceleyen bir geçmiş kontrolünden geçmeli ve gerekirse bir memur tarafından uygulanan atış poligonu testine katılmalıdır. Üç lisans kademesi uygulayın: tabancalar ve pompalı tüfekler için temel, hafif makineli tüfekler ve tüfekler için gelişmiş, sivillere nadiren verilen ağır silahlar için askeri. Lisansların genellikle 30 gerçek gün gibi bir son kullanma tarihi olmalı ve yenileme gerektirmelidir. Polis memurlarının, oyuncular şiddet suçlarından hüküm giydiğinde lisansları iptal etmek için bir komuta ihtiyacı vardır; bu da yasal dükkanlardan satın almalarını derhal engeller ve karaborsa için talep yaratır. Lisans durumunu veritabanında saklayın ve her satın alma girişiminde server-side kontrol edin, client-side lisans kontrollerine asla güvenmeyin çünkü bunlar kolayca atlatılabilir.

Client Tarafı Mağaza Arayüzü

Silah dükkanı NUI'si, kategorilere ayrılmış sekmelerde sergilenen silahlarla profesyonel bir vitrin gibi hissettirmelidir. Her silahı adı, fiyatı, gerekli lisansı, mevcut stok seviyesi ve küçük bir önizleme resmi veya simgesiyle gösterin. Oyuncuların önce silahlarını seçtiği, ardından yalnızca uyumlu mühimmat türlerini gördüğü mühimmat için ayrı bir bölüm ekleyin. Eklenti bölümü de benzer şekilde çalışarak yalnızca oyuncunun o sırada kuşanmış olduğu silahla uyumlu bileşenleri gösterir. Oyuncuların her bir eşyayı ayrı ayrı satın almak yerine birden fazla eşyayı sıraya koyup tek bir işlemle ödeme yapmasını sağlayan bir alışveriş sepeti sistemi uygulayın. Kara borsa arayüzü için, oyuncunun bir sonraki kademeye ne kadar yakın olduğunu gösteren bir itibar ilerleme çubuğuyla daha karanlık bir estetik kullanın. Oyuncuların neye doğru çalıştıklarını bilmeleri için kilitli öğeleri bir itibar gereksinimi etiketiyle silüet olarak görüntüleyin. Her iki dükkan türü de açılışta önbelleğe alınmış yapılandırma değerlerine güvenmek yerine gerçek zamanlı fiyatları ve stoğu almak için bir sunucu geri çağırmasını (server callback) tetiklemelidir; çünkü yöneticiler fiyatları sunucu ekonomisi koşullarına göre dinamik olarak ayarlayabilir.

Anti-Exploit ve Polis Entegrasyonu

Silah mağazaları, pahalı silahları kopyalamaya veya ödeme yapmadan eşya üretmeye çalışan hileciler için birincil hedeftir. Herhangi bir eşya oluşturmadan önce oyuncunun parasını, ruhsat durumunu, mağazaya yakınlığını ve satın alma bekleme süresini kontrol ederek her satın alımı sunucu tarafında doğrulayın. Yöneticilerin sunucudaki herhangi bir silahın kaynağını takip edebilmesi için her silah işlemini alıcının tanımlayıcıları, silah seri numarası, mağaza ID'si ve zaman damgasıyla kaydedin. Memurların soruşturmalar sırasında bir silah seri numarasını aratıp kayıtlı sahibini, satın alma tarihini ve seri numarasının kazınıp kazınmadığını görebilmesi için polis MDT sistemiyle entegre edin. Belirli bir vatandaş için kayıtlı tüm silahların raporunu oluşturan bir silah kayıt dışa aktarma özelliği uygulayın; bu, denetimli serbestlik kontrolleri için yararlıdır. Yarış durumları (race conditions) üzerinden bekleme süresi kontrollerini atlatabilecek seri istekleri önlemek için satın alma etkinliklerine hız sınırı (rate-limit) uygulayın. Moderasyon ekibinizin suistimalleri izleyebilmesi için karaborsada üst düzey askeri silahlar satıldığında Discord webhook uyarıları gönderin. Doğru sunucu tarafı doğrulama, kapsamlı kayıt tutma ve polis entegrasyonunun birleşimi, suistimallere karşı dirençli kalırken gerçekçi hissettiren bir silah ekonomisi yaratır.

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.