返回博客
Tutorial3 分钟阅读

FiveM 武器商店系统:持牌枪械店脚本

为您的 FiveM 服务器构建持证枪械店。许可证、背景调查、弹药、附件以及顶级武器商店脚本,适合角色扮演。

Agency Scripts

Agency Scripts 创始人兼首席开发者

武器商店架构概述

FiveM角色扮演服务器的武器店系统需平衡守法市民的可访问性与创造玩法深度的现实限制。架构分为两条分支:合法商店执行武器许可、背景调查和购买冷却,非法黑市商贩以更高价格出售无记录武器,无需手续。两系统共享相同库存和交易逻辑,但访问要求和武器元数据不同。合法武器带有与买家公民ID绑定的序列号,可被警方追踪,黑市武器序列号被刮除,无法追溯到具体玩家。此双轨设计自然产生RP张力,玩家需权衡合法购买的便利和低价与黑市匿名性的利弊。

武器与许可证的数据库架构

您的数据库需要跟踪每个商店的武器库存、单个武器序列号、玩家执照和购买历史。序列号系统至关重要,因为它连接武器商店和警方调查系统。每把合法购买的武器都有唯一序列号,警方可在交通检查或犯罪现场调查时查询。设计架构以同时处理商店库存管理和单个武器跟踪:

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)
);

weapon_serials 该表跟踪每把武器的生命周期。当玩家合法购买武器时,会生成序列号并关联其市民ID。如果他们通过黑市服务刮除序列号, is_scratched 标志翻转为 true 且 owner_citizenid 被取消。该 stock 字段在 weapon_inventory 使用 -1 表示无限库存,而正值启用有限供应机制,制造稀缺性并推动黑市需求。

合法武器商店实现

合法武器商店是大多数玩家在角色扮演服务器上获取枪械的主要途径。它们执行许可要求,施加购买冷却以防止囤积,并为每把售出武器生成可追踪的序列号。商店UI应按类别分组显示武器,清晰标示每种武器所需的许可等级。无适当许可的玩家可见武器但无法购买,鼓励他们与政府官员进行许可角色扮演。实现冷却系统,限制玩家每天购买武器数量,防止囤积转售黑市:

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

弹药和附件系统

弹药管理增加了资源消耗,保持玩家对经济的参与并防止无限弹药漏洞。弹药不作为通用物品出售,而是绑定到与武器类别匹配的特定口径类型。手枪使用9mm子弹,冲锋枪可能共享9mm或使用.45 ACP,步枪根据型号使用5.56或7.62。该口径系统创造了更真实的体验,并为商店老板在定价不同弹药类型时提供灵活性。将弹药数量存储在武器元数据中,而非作为独立的库存物品,这样每把武器都跟踪自身装载的子弹。附件遵循相同的元数据模式,每个武器物品存储安装组件列表,如瞄准镜、消音器、扩展弹匣和手电筒:

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',
    },
}

当玩家购买附件时,验证其当前武器是否在 compatible 列出,检查其许可证等级,然后将组件哈希添加到武器的元数据表中。装备武器时,遍历存储的组件并应用它们, GiveWeaponComponentToPed这种方法确保附件在会话间持久存在,且无法通过操纵客户端复制。

黑市机械师

黑市是真正角色扮演深度所在。与静态合法商店不同,黑市商人应感觉危险且独特。实现每隔数小时更换的轮换商人位置,玩家需依靠内部消息或联系人寻找。要求声望系统,新玩家必须通过小额购买建立与商人的信任,才能获得高端武器访问权限。黑市出售无序列号武器、合法商店无法存货的受限军用装备,以及诸如擦除序列号等服务,使合法购买的武器无法追踪。所有价格均为合法商店的2-3倍,以反映风险溢价并在合法与非法路径间制造真实经济张力:

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)

武器许可系统

牌照系统创建了重要的官僚RP层,将武器店与政府及执法派系连接。玩家应在市政厅或警察局申请武器许可证,接受背景调查审查犯罪记录,并可能参加由警官管理的射击场测试。实现三级牌照:基础级适用于手枪和霰弹枪,高级适用于冲锋枪和步枪,军用级适用于罕见发放给平民的重型武器。牌照应有有效期,通常为30个现实天,需续期。警察需有命令撤销牌照,当玩家被判定暴力犯罪时立即阻止其在合法商店购买,促进黑市需求。牌照状态存储于数据库,服务器端每次购买尝试时检查,绝不信任客户端牌照检查,因为易被绕过。

客户端商店界面

武器商店NUI应像专业店面,武器按分类标签展示。显示每把武器的名称、价格、所需执照、当前库存和小预览图标。弹药区分开,玩家先选武器,再只显示兼容弹药类型。附件区类似,只显示与玩家当前装备武器兼容的组件。实现购物车系统,允许玩家排队多个物品,一次结账,而非逐个购买。黑市UI采用更暗色调,带有声望进度条显示玩家距离下一级的距离。锁定物品以轮廓显示,并标注声望需求,让玩家知道目标。两种商店打开时均触发服务器回调,获取实时价格和库存,避免依赖缓存配置值,因为管理员可能根据服务器经济动态调整价格。

反利用与警察集成

武器商店是试图复制昂贵武器或无偿生成物品的利用者的主要目标。通过服务器端验证每笔购买,检查玩家资金、许可状态、商店距离和购买冷却时间,确保在创建任何物品前符合条件。记录每笔武器交易,包括买家标识、武器序列号、商店ID和时间戳,以便管理员追踪服务器上任何武器的来源。与警察MDT系统集成,使警员在调查时能查询武器序列号,查看注册所有者、购买日期及序列号是否被刮除。实现武器登记导出功能,生成特定公民所有注册武器的报告,便于缓刑检查。对购买事件进行速率限制,防止通过竞态条件绕过冷却检查的快速请求。当高阶军用武器在黑市出售时,发送Discord webhook警报,便于您的管理团队监控滥用行为。适当的服务器端验证、全面的日志记录和警察集成相结合,打造一个既真实又防止被利用的武器经济。

准备好开始了吗?

在我们的商店获取脚本,或加入 Discord 获取支持、更新以及新功能预告。