Visão geral da arquitetura da loja de armas
Um sistema de loja de armas em um servidor de RPG FiveM precisa equilibrar a acessibilidade para cidadãos cumpridores da lei com restrições realistas que criem profundidade de jogo. A arquitetura se divide em dois ramos distintos: lojas legais que impõem licenciamento de armas, verificações de antecedentes e cooldowns de compra, e negociantes ilegais do mercado negro que vendem armas não rastreáveis a preços mais altos e sem documentação. Ambos os sistemas compartilham o mesmo inventário subjacente e lógica de transação, mas diferem em seus requisitos de acesso e nos metadados anexados a cada arma. As armas legais têm números de série vinculados à identificação de cidadão do comprador, tornando-as rastreáveis pela polícia, enquanto as armas do mercado negro têm números de série riscados que não podem ser rastreados até um jogador específico. Este design de pista dupla cria uma tensão natural no roleplay porque os jogadores devem pesar a conveniência e o custo mais baixo das compras legais em relação ao anonimato do mercado negro.
Esquema de banco de dados para armas e licenças
Seu banco de dados precisa rastrear o inventário de armas por loja, números de série de armas individuais, licenças de jogadores e histórico de compras. O sistema de números de série é fundamental porque une a loja de armas e os sistemas de investigação policial. Cada arma comprada legalmente recebe uma série exclusiva que a polícia pode consultar durante paradas de trânsito ou investigações da cena do crime. Projete o esquema para lidar com o gerenciamento de estoque da loja e o rastreamento de armas individuais:
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)
);A tabela weapon_serials rastreia cada arma individualmente durante seu ciclo de vida. Quando um jogador compra uma arma legalmente, uma série é gerada e vinculada ao seu ID de cidadão. Se eles riscarem a série usando um serviço do mercado negro, o sinalizador is_scratched muda para verdadeiro e owner_citizenid é anulado. O campo stock em weapon_inventory usa -1 para indicar estoque ilimitado, enquanto valores positivos permitem mecânica de oferta limitada que cria escassez e impulsiona a demanda no mercado negro.
Implementação de loja de armas legais
Lojas de armas legais são a principal forma pela qual a maioria dos jogadores adquirem armas de fogo em um servidor de RPG. Eles impõem requisitos de licenciamento, aplicam intervalos de compra para evitar compras em massa e geram números de série rastreáveis para cada arma vendida. A interface da loja deve exibir armas agrupadas por categoria com indicadores claros mostrando qual nível de licença cada arma exige. Jogadores sem a licença adequada veem a arma, mas não podem comprá-la, incentivando-os a interpretar o processo de licenciamento com autoridades governamentais. Implemente um sistema de resfriamento que evite que os jogadores comprem mais do que um determinado número de armas por dia em tempo real para evitar o armazenamento para revenda no mercado negro:
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
endSistema de munição e acessórios
O gerenciamento de munição adiciona um dreno de recursos que mantém os jogadores envolvidos com a economia e evita explorações infinitas de munição. Em vez de vender munição como um item genérico, vincule-a a tipos de calibres específicos que correspondam às categorias de armas. Uma pistola usa cartuchos de 9 mm, uma SMG pode compartilhar 9 mm ou usar .45 ACP e rifles usam 5,56 ou 7,62 dependendo do modelo. Este sistema de calibre cria uma experiência mais realista e dá aos lojistas flexibilidade na definição de preços de diferentes tipos de munição. A munição armazenada conta com os metadados da arma, e não como itens de inventário separados, para que cada arma rastreie suas próprias munições carregadas. Os anexos seguem o mesmo padrão de metadados, onde cada item de arma armazena uma lista de componentes instalados, como miras telescópicas, supressores, carregadores estendidos e lanternas:
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',
},
}Quando um jogador compra um anexo, valide se sua arma atual está na lista compatible, verifique seu nível de licença e adicione o hash do componente à tabela de metadados da arma. Ao equipar a arma, percorra os componentes armazenados e aplique-os com GiveWeaponComponentToPed. Essa abordagem garante que os anexos persistam entre as sessões e não possam ser duplicados pela manipulação do cliente.
Mecânica do Mercado Negro
O mercado negro é onde reside a verdadeira profundidade do RPG. Ao contrário das lojas jurídicas estáticas, os negociantes do mercado negro devem sentir-se perigosos e exclusivos. Implemente locais rotativos de revendedores que mudam a cada poucas horas para que os jogadores precisem de conhecimento interno ou contatos para encontrá-los. Exigir um sistema de reputação onde os novos jogadores devam construir a confiança do revendedor por meio de compras menores antes de obter acesso a armas de alto nível. O mercado negro vende armas sem números de série, equipamentos restritos de nível militar que as lojas legais não podem estocar e serviços como a raspagem de números de série que tornam as armas compradas legalmente indetectáveis. Fixe o preço de tudo 2 a 3 vezes mais alto do que as lojas legais para reflectir o prémio de risco e criar uma tensão económica genuína entre os caminhos legais e ilegais:
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)Sistema de licenciamento de armas
O sistema de licenciamento cria uma importante camada burocrática de roleplay que conecta lojas de armas ao governo e às facções policiais. Os jogadores devem solicitar uma licença de porte de arma na prefeitura ou em uma delegacia de polícia, passar por uma verificação de antecedentes que examina sua ficha criminal e, potencialmente, participar de um teste de campo de tiro administrado por um policial. Implementar três níveis de licença: básica para revólveres e espingardas, avançada para submetralhadoras e rifles, e militar para armas pesadas que raramente são emitidas para civis. As licenças devem ter uma data de validade, normalmente 30 dias em tempo real, exigindo renovação. Os policiais precisam de um comando para revogar licenças quando os jogadores são condenados por crimes violentos, o que os impede imediatamente de comprar em lojas legais e cria demanda para o mercado negro. Armazene o status da licença no banco de dados e verifique-o no servidor em cada tentativa de compra; nunca confie nas verificações de licença do cliente, pois elas podem ser contornadas trivialmente.
Interface da loja do lado do cliente
A loja de armas NUI deve parecer uma vitrine profissional com armas exibidas em guias categorizadas. Mostre cada arma com seu nome, preço, licença necessária, nível de estoque atual e uma pequena imagem ou ícone de visualização. Inclua uma seção separada para munição onde os jogadores selecionam sua arma primeiro e depois veem apenas os tipos de munição compatíveis. A seção de anexos funciona de forma semelhante, mostrando apenas componentes compatíveis com a arma atualmente equipada do jogador. Implemente um sistema de carrinho de compras que permita aos jogadores colocar vários itens na fila e finalizar a compra em uma única transação, em vez de comprar cada item individualmente. Para a interface do mercado negro, use uma estética mais sombria com uma barra de progresso de reputação mostrando o quão perto o jogador está do próximo nível. Exiba itens bloqueados como silhuetas com um rótulo de exigência de reputação para que os jogadores saibam no que estão trabalhando. Ambos os tipos de loja devem acionar um retorno de chamada do servidor ao abrir para buscar preços e estoque em tempo real, em vez de depender de valores de configuração em cache, porque os administradores podem ajustar os preços dinamicamente com base nas condições de economia do servidor.
Antiexploração e Integração Policial
As lojas de armas são o principal alvo dos exploradores que tentam duplicar armas caras ou gerar itens sem pagar. Valide cada compra no servidor verificando o dinheiro do jogador, o status da licença, a proximidade da loja e o tempo de espera da compra antes de criar qualquer item. Registre todas as transações de armas com os identificadores do comprador, o número de série da arma, o ID da loja e o carimbo de data e hora para que os administradores possam rastrear a origem de qualquer arma no servidor. Integre-se ao sistema MDT da polícia para que os policiais possam procurar a série da arma durante as investigações e ver o proprietário registrado, a data da compra e se a série foi arranhada. Implementar uma exportação de registro de armas que gere um relatório de todas as armas registradas para um cidadão específico, útil para verificações de liberdade condicional. Eventos de compra com limite de taxa para evitar solicitações rápidas que poderiam ignorar as verificações de resfriamento por meio de condições de corrida. Envie alertas de webhook do Discord quando armas militares de alto nível forem vendidas no mercado negro para que sua equipe de moderação possa monitorar abusos. A combinação de validação adequada do lado do servidor, registro abrangente e integração policial cria uma economia de armas que parece realista, ao mesmo tempo que permanece resistente à exploração.

