Система оружейного магазина 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 аннулируется. The stock поле в weapon_inventory использует -1 указывать неограниченный запас, в то время как положительные значения включают механику ограниченного предложения, создавая дефицит и стимулируя спрос на чёрном рынке.
Реализация легального оружейного магазина
Легальные оружейные магазины , основной способ приобретения огнестрельного оружия на ролевом сервере. Они обеспечивают соблюдение требований лицензирования, применяют задержки между покупками, чтобы предотвратить массовые закупки, и генерируют отслеживаемые серийные номера для каждого проданного оружия. Интерфейс магазина должен отображать оружие, сгруппированное по категориям, с четкими индикаторами, показывающими, какая лицензия требуется для каждого оружия. Игроки без соответствующей лицензии видят оружие, но не могут его купить, что побуждает их проходить процесс лицензирования с государственными чиновниками. Реализуйте систему задержек, которая не позволяет игрокам покупать более определенного количества оружия за реальный день, чтобы предотвратить накопление для перепродажи на черном рынке:
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
Система боеприпасов и аксессуаров
Управление боеприпасами добавляет расход ресурсов, что удерживает игроков вовлечёнными в экономику и предотвращает бесконечные эксплойты с патронами. Вместо продажи патронов как универсального предмета, привязывайте их к конкретным калибрам, соответствующим категориям оружия. Пистолет использует 9мм патроны, ПП может использовать 9мм или .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)
Система лицензирования оружия
Система лицензирования создает важный бюрократический слой ролевой игры, который связывает оружейные магазины с государственными и правоохранительными фракциями. Игроки должны подавать заявку на лицензию на оружие в мэрии или полицейском участке, проходить проверку биографии, которая изучает их уголовное прошлое, и, возможно, посещать стрельбище для сдачи экзамена, проводимого офицером. Реализуйте три уровня лицензий: базовый для пистолетов и дробовиков, продвинутый для ПП и винтовок и военный для тяжелого оружия, которое редко выдается гражданским. Лицензии должны иметь срок действия, обычно 30 реальных дней, требующий продления. Полицейским нужна команда для отзыва лицензий, когда игроки осуждены за насильственные преступления, что немедленно блокирует их покупку в легальных магазинах и создает спрос на черный рынок. Храните статус лицензии в базе данных и проверяйте его на стороне сервера при каждой попытке покупки, никогда не доверяйте проверкам лицензий на стороне клиента, так как их можно легко обойти.
Клиентский интерфейс магазина
NUI оружейного магазина должен выглядеть как профессиональный магазин с оружием, отображаемым в категоризированных вкладках. Показывайте каждое оружие с названием, ценой, требуемой лицензией, текущим уровнем запасов и небольшим превью или иконкой. Включите отдельный раздел для боеприпасов, где игроки сначала выбирают оружие, а затем видят только совместимые типы патронов. Раздел с аксессуарами работает аналогично, показывая только компоненты, совместимые с текущим оружием игрока. Реализуйте систему корзины покупок, позволяющую игрокам добавлять несколько предметов и оплачивать их одной транзакцией, а не покупать по одному. Для интерфейса черного рынка используйте более темную эстетику с индикатором прогресса репутации, показывающим, насколько игрок близок к следующему уровню. Заблокированные предметы отображайте в виде силуэтов с меткой требования репутации, чтобы игроки знали, к чему стремиться. Оба типа магазинов должны вызывать серверный callback при открытии для получения актуальных цен и запасов, а не полагаться на кэшированные значения конфигурации, так как администраторы могут динамически корректировать цены в зависимости от состояния экономики сервера.
Анти-эксплойт и интеграция с полицией
Оружейные магазины являются основной целью злоумышленников, пытающихся дублировать дорогостоящее оружие или создавать предметы без оплаты. Проверяйте каждую покупку на стороне сервера, проверяя деньги игрока, статус лицензии, близость к магазину и время ожидания между покупками перед созданием предметов. Логируйте каждую транзакцию оружия с идентификаторами покупателя, серийным номером оружия, ID магазина и временной меткой, чтобы администраторы могли отследить происхождение любого оружия на сервере. Интегрируйте с полицейской системой MDT, чтобы офицеры могли искать серийный номер оружия во время расследований и видеть зарегистрированного владельца, дату покупки и информацию о том, был ли номер повреждён. Реализуйте экспорт реестра оружия, который генерирует отчёт обо всех зарегистрированных оружиях конкретного гражданина, полезный для проверок условно осуждённых. Ограничьте частоту событий покупки, чтобы предотвратить быстрые запросы, которые могут обойти проверки времени ожидания через состояния гонки. Отправляйте оповещения в Discord через webhook при продаже высококлассного военного оружия на чёрном рынке, чтобы ваша команда модерации могла контролировать злоупотребления. Сочетание правильной серверной валидации, комплексного логирования и интеграции с полицией создаёт оружейную экономику, которая ощущается реалистичной и устойчива к эксплуатации.