DRS Vehicle Shop
Stand de veículos feito de raiz. Podes ter um stand que vende tudo ou um por tipo (carros, motas, VIP, aéreo e barcos), todos criados e editados em jogo pelo menu de staff. O catálogo enche-se sozinho com os veículos do framework. O script inclui ainda:
- showroom com câmara orbital, estatísticas, cores, procura e categorias;
- compra a dinheiro ou banco, com entrega no stand ou direto na garagem;
- financiamento com prestações automáticas e apreensão do carro;
- test drive numa dimensão privada;
- stands de empresa com vendedores, propostas e comissão;
- vouchers de desconto (boas-vindas, staff e outros scripts);
- carros em exposição parados no stand.
Funciona com Qbox, QBCore e ESX.
#Requisitos
| Recurso | Obrigatório |
|---|---|
oxmysql | Sim |
| Qbox, QBCore ou ESX | Sim (deteção automática) |
ox_target ou qb-target | Não — só com Config.UseTarget = true (senão é a tecla E) |
qb-banking, qb-management ou Renewed-Banking | Não — só em stands de empresa (conta da empresa) |
Nota
Não precisa do ox_lib. Desliga o stand antigo (qb-vehicleshop, esx_vehicleshop, …) antes de ligar este.
#Instalação
- Coloca
drs_vehicleshopemresources. - Adiciona ao
server.cfg, depois da framework:
ensure oxmysql
ensure qb-core
ensure drs_vehicleshop- As tabelas são criadas sozinhas no primeiro arranque (também estão em
sql/drs_vehicleshop.sql). Os stands deConfig.DefaultStandsentram nesse arranque. - Dá acesso ao menu de staff (os grupos de admin da framework já têm):
add_ace group.admin drs.vehicleshop allow- Em jogo, abre o
/standadmine ajusta os pontos e o catálogo.
#Stands
#Tipos
| Tipo | O que vende |
|---|---|
all | Todos os veículos |
cars | Carros (categorias de Config.Types.cars) |
bikes | Motas e bicicletas |
vip | Veículos de luxo (shop = 'luxury' no QBCore, ou a partir de minPrice) |
air | Helicópteros e aviões |
boats | Barcos |
Ao criar um stand, o catálogo é preenchido com os veículos do framework desse tipo. Depois mudas preços e stock, e tiras ou pões modelos à mão. As categorias de Config.ExcludedCategories (emergência, militares…) nunca entram sozinhas.
#Menu de staff (/standadmin)
| Separador | O que tem |
|---|---|
| Geral | Nome, tipo, job, blip, compras sem vendedor, test drive, financiamento e esconder do mapa |
| Pontos no mapa | Menu, entrega, carro de pré-visualização, câmara, test drive e vendedores, todos postos onde estás, com TP para cada um |
| Exposição | Carros parados no stand (posição e modelo) |
| Catálogo | Repor do framework, preços, stock, tirar e pôr modelos |
| Vendas | Histórico das vendas do stand |
Dica
Com Config.StandsFromConfig = true, os stands de Config.DefaultStands seguem sempre a config: em cada arranque o nome, o tipo, o job, o blip, os pontos e as opções voltam a vir daí (o catálogo não é tocado). O menu avisa quando um stand vem do config.lua. Os stands criados no /standadmin nunca são tocados.
#Comprar
#Showroom
Carro de pré-visualização com câmara orbital (arrastar para rodar, roda do rato para aproximar). Mostra velocidade, aceleração, travagem, aderência e lugares, tem 16 cores, procura e categorias.
#Compra
A dinheiro ou banco (Config.Payment). O carro recebe uma matrícula única (Config.Plate), chaves e depósito cheio, e aparece no ponto de entrega ou vai direto para a garagem (Config.BoughtVehicle).
#Financiamento
- Escolhes a entrada (
Config.Finance.downPayments) e o número de prestações (Config.Finance.payments), com o juro deConfig.Finance.interest. - As prestações são cobradas do banco a cada
intervalMinutesminutos a jogar, com aviso. - Ao fim de
maxMissedprestações falhadas seguidas, o carro é apreendido. /financiamentosmostra os financiamentos e deixa pagar adiantado.
#Test drive
Numa dimensão privada (privateWorld), com tempo e X para terminar. No fim voltas ao stand.
#Vouchers
Vales de desconto que ficam na conta do jogador (não no inventário):
- Boas-vindas: cada jogador novo recebe um, uma vez, ao entrar (
Config.Vouchers.starter: valor, dias, tipos de stand e modelos). - Staff:
/darvoucher [id] [valor] [dias]. - Outros scripts: export
GiveVoucher(passe de batalha, prémios…).
Um voucher vale até ao seu valor (se o carro for mais barato, a diferença perde-se), usa-se uma vez e não se junta com financiamento.
#Stands de empresa
Um stand com job tem vendedores. Os vendedores de serviço enviam propostas aos clientes perto, o cliente aceita (e pode financiar), o vendedor recebe Config.Commission % e o resto vai para a conta da empresa.
Compras sem vendedor (selfService):
| Valor | Quando |
|---|---|
'always' | Sempre |
'nostaff' | Só quando não há vendedores de serviço |
'never' | Nunca: só com vendedor |
#Comandos
| Comando | Quem | O que faz |
|---|---|---|
/standadmin | Staff | Menu de staff dos stands |
/financiamentos | Todos | Ver e pagar os financiamentos |
/darvoucher [id] [valor] [dias] | Staff | Dar um voucher a um jogador |
#Opções principais
| Opção | Por defeito | Descrição |
|---|---|---|
Config.Locale | 'pt' | Idioma: pt ou en |
Config.UseTarget | false | true = qb-target / ox_target nos pontos |
Config.BoughtVehicle | 'spawn' | 'spawn' = ponto de entrega · 'garage' = vai para a garagem |
Config.DefaultGarage | 'pillboxgarage' | Garagem guardada quando vai para a garagem |
Config.TestDrive.seconds | 60 | Duração do test drive |
Config.Finance.enabled | true | false = sem financiamento e sem /financiamentos |
Config.Commission | 10 | % do preço para o vendedor |
Config.Vouchers.enabled | true | Liga os vouchers |
Config.StandsFromConfig | true | Os stands da config seguem sempre a config |
Config.AdminCommand | 'standadmin' | Comando do menu de staff |
#Chaves e combustível
Deteção automática:
- Chaves: qbx_vehiclekeys, qb-vehiclekeys, wasabi_carlock, MrNewbVehicleKeys, qs-vehiclekeys, vehicles_keys e Renewed-Vehiclekeys.
- Combustível: ox_fuel, LegacyFuel, cdn-fuel, ps-fuel, lj-fuel e qb-fuel.
Para outro sistema, usa Config.OnVehicleSpawned(vehicle, plate) no config.lua.
#Configuração completa
Config = {}
-- ------------------------------------------------------------------ geral
Config.Framework = 'auto' -- 'auto' | 'qbx' | 'qb' | 'esx'
Config.Locale = 'pt' -- 'pt' | 'en'
Config.Currency = '$'
Config.CurrencyLocale = 'pt-PT'
Config.UseOxNotify = false -- notificações do ox_lib em vez das do framework
Config.UseTarget = false -- true = qb-target / ox_target nos pontos (senão tecla E)
Config.InteractKey = 38 -- E
Config.Debug = false
-- ------------------------------------------------------------------ staff
Config.AdminCommand = 'standadmin' -- menu in-game para criar e editar stands
Config.AdminAce = 'drs.vehicleshop'
Config.StaffGroups = { 'god', 'admin', 'superadmin' }
Config.FinanceCommand = 'financiamentos' -- ver / pagar os financiamentos em qualquer lado ('' = desligado)
-- ------------------------------------------------------------------ tipos de stand
-- Um stand pode vender tudo ('all') ou só um tipo. Ao criar um stand, o catálogo é preenchido com os veículos do
-- framework que encaixam no tipo (depois edita-se no /standadmin: preços, stock, tirar e pôr modelos).
-- categories = categorias do framework (QBCore/Qbox: category; ESX: vehicles.category)
-- shops = (QBCore) só os veículos com estes 'shop' no qb-core/shared/vehicles.lua
-- minPrice = a partir deste preço conta como VIP (Qbox/ESX não têm 'shop')
Config.Types = {
all = { icon = 'layer-group', blip = 523, color = 0 },
cars = {
icon = 'car', blip = 326, color = 0,
categories = { 'compacts', 'coupes', 'muscle', 'offroad', 'sedans', 'sports', 'sportsclassics', 'super', 'suvs', 'vans', 'openwheel', 'utility', 'commercial', 'industrial' },
},
bikes = { icon = 'motorcycle', blip = 226, color = 0, categories = { 'motorcycles', 'cycles' } },
vip = { icon = 'crown', blip = 523, color = 46, shops = { 'luxury' }, minPrice = 250000 },
air = { icon = 'helicopter', blip = 64, color = 3, categories = { 'helicopters', 'planes' } },
boats = { icon = 'ship', blip = 410, color = 2, categories = { 'boats' } },
}
-- nunca entram no catálogo automático (podem ser postos à mão)
Config.ExcludedCategories = { 'emergency', 'military', 'service', 'trains' }
Config.ExcludedShops = { 'none' } -- QBCore: shop = 'none'
-- ------------------------------------------------------------------ compra
Config.Payment = { cash = true, bank = true }
Config.Plate = 'AAA 1111' -- A = letra, 1 = número, o resto fica igual (máx. 8)
Config.BoughtVehicle = 'spawn' -- 'spawn' = aparece no ponto de entrega | 'garage' = vai para a garagem
Config.DefaultGarage = 'pillboxgarage' -- garagem guardada quando vai para a garagem (QBCore/Qbox)
Config.MaxDistance = 25.0 -- distância máxima ao stand para comprar/abrir (anti-batota)
-- ------------------------------------------------------------------ test drive
Config.TestDrive = {
enabled = true,
seconds = 60,
privateWorld = true, -- dimensão (routing bucket) própria durante o test drive
cooldown = 30, -- segundos entre test drives
endKey = 73, -- X para terminar
}
-- ------------------------------------------------------------------ financiamento
Config.Finance = {
enabled = true, -- false = sem financiamento em lado nenhum (e sem /financiamentos)
downPayments = { 0.2, 0.3, 0.5 }, -- entrada mínima (fração do preço)
payments = { 6, 12, 24 }, -- número de prestações
interest = 0.10, -- juro total sobre o valor financiado
intervalMinutes = 60, -- de quanto em quanto tempo (a jogar) se cobra uma prestação
maxMissed = 2, -- prestações falhadas seguidas até o carro ser apreendido
minPrice = 20000, -- só dá para financiar a partir deste preço
}
-- ------------------------------------------------------------------ stands de empresa
Config.Commission = 10 -- % do preço para o vendedor (o resto vai para a conta da empresa)
Config.SocietyGetsSelfService = true -- compras sem vendedor num stand com job também vão para a empresa
Config.OfferTimeout = 60 -- segundos para o cliente aceitar a proposta
-- ------------------------------------------------------------------ exposição
Config.DisplayDistance = 90.0 -- carros em exposição aparecem a esta distância
-- ------------------------------------------------------------------ logs
Config.Webhook = ''
-- ------------------------------------------------------------------ vouchers
-- Vales de desconto para comprar um veículo (ficam na conta do jogador, não no inventário). Um voucher vale até `value`
-- (se o carro for mais barato, a diferença perde-se), só se usa uma vez e não se junta com financiamento.
Config.Vouchers = {
enabled = true,
-- voucher de boas-vindas: cada jogador novo recebe um (uma vez) quando entra no servidor
starter = {
enabled = true,
label = 'Voucher de boas-vindas',
value = 25000,
days = 30, -- dias até expirar (0 = não expira)
types = { 'cars', 'bikes' }, -- tipos de stand onde vale ({} = todos; num stand 'all' conta o tipo do veículo)
models = {}, -- só estes modelos ({} = qualquer um até ao valor)
},
command = 'darvoucher', -- staff: /darvoucher [id] [valor] [dias] ('' = desligado)
}
-- ------------------------------------------------------------------ stands da config
-- true = os stands desta lista seguem SEMPRE a config: a cada arranque o nome, o tipo, o job, o blip, os pontos e
-- as opções vêm daqui (as mudanças a esses stands no /standadmin perdem-se no restart; o catálogo não é tocado).
-- false = a config só cria os stands no primeiro arranque; depois manda o /standadmin (base de dados).
-- Os stands criados no /standadmin nunca são tocados.
Config.StandsFromConfig = true
-- pontos: menu = abrir o stand · spawn = entrega · preview = carro de exposição · cam = câmara (opcional)
-- test = início do test drive (opcional) · sell = vendedores (opcional)
-- opcional por stand: job, selfService ('always' | 'nostaff' | 'never'), testDrive, finance, hidden, displays = { vector4(...) + model }
Config.DefaultStands = {
{
id = 'pdm', label = 'Premium Deluxe Motorsport', type = 'cars',
blip = { sprite = 326, color = 0 },
points = {
menu = vector3(-43.61, -1094.69, 26.42),
spawn = vector4(-13.99, -1093.8, 26.05, 160.17),
preview = vector4(-48.75, -1093.08, 26.0, 240.65),
cam = vector3(-54.0, -1090.18, 26.63),
test = vector4(-1331.59, -2202.41, 13.32, 149.94),
sell = vector3(-32.04, -1114.23, 26.42),
},
},
{
id = 'marina', label = 'Puerto del Sol Marina', type = 'boats',
blip = { sprite = 410, color = 2 },
points = {
menu = vector3(-718.53, -1326.79, 1.6),
spawn = vector4(-727.55, -1367.86, 0.52, 136.62),
preview = vector4(-706.67, -1338.55, -0.1, 353.3),
cam = vector3(-710.27, -1336.03, 1.46),
test = vector4(-727.55, -1367.86, 0.52, 136.62),
sell = vector3(-696.65, -1320.73, 5.1),
},
},
{
id = 'aero', label = 'Higgins Aviation', type = 'air',
blip = { sprite = 64, color = 3 },
points = {
menu = vector3(-753.48, -1512.23, 5.02),
spawn = vector4(-763.14, -1452.73, 5.0, 53.69),
preview = vector4(-734.18, -1457.19, 5.66, 47.75),
cam = vector3(-739.79, -1435.5, 5.0),
test = vector4(-734.18, -1457.19, 5.66, 47.75),
sell = vector3(-759.47, -1515.31, 4.98),
},
},
}
-- ------------------------------------------------------------------ chaves e combustível (deteção automática)
-- 'auto' procura: qbx_vehiclekeys, qb-vehiclekeys, wasabi_carlock, MrNewbVehicleKeys, qs-vehiclekeys, vehicles_keys, Renewed-Vehiclekeys
Config.Keys = 'auto'
-- 'auto' procura: ox_fuel, LegacyFuel, cdn-fuel, ps-fuel, lj-fuel, qb-fuel
Config.Fuel = 'auto'
-- chamado no cliente depois de o carro aparecer (para sistemas de chaves que não estão na lista)
function Config.OnVehicleSpawned(vehicle, plate) end#Base de dados
-- DRS Vehicle Shop — as tabelas também são criadas sozinhas no primeiro arranque / tables are also created on the first start
CREATE TABLE IF NOT EXISTS `drs_vehicleshop_stands` (`id` VARCHAR(40) NOT NULL, `data` LONGTEXT NOT NULL, PRIMARY KEY (`id`)) DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `drs_vehicleshop_catalog` (`stand` VARCHAR(40) NOT NULL, `model` VARCHAR(60) NOT NULL, `label` VARCHAR(80) NOT NULL DEFAULT '',
`brand` VARCHAR(60) NOT NULL DEFAULT '', `category` VARCHAR(40) NOT NULL DEFAULT 'other', `price` INT NOT NULL DEFAULT 0, `stock` INT NULL DEFAULT NULL,
PRIMARY KEY (`stand`, `model`)) DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `drs_vehicleshop_sales` (`id` INT NOT NULL AUTO_INCREMENT, `stand` VARCHAR(40) NOT NULL, `model` VARCHAR(60) NOT NULL,
`label` VARCHAR(80) NOT NULL DEFAULT '', `price` INT NOT NULL DEFAULT 0, `plate` VARCHAR(12) NOT NULL, `buyer` VARCHAR(64) NOT NULL, `buyer_name` VARCHAR(80) NOT NULL DEFAULT '',
`seller` VARCHAR(64) NULL, `seller_name` VARCHAR(80) NULL, `financed` TINYINT NOT NULL DEFAULT 0, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), KEY `stand` (`stand`)) DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `drs_vehicleshop_finance` (`plate` VARCHAR(12) NOT NULL, `owner` VARCHAR(64) NOT NULL, `stand` VARCHAR(40) NOT NULL,
`model` VARCHAR(60) NOT NULL, `label` VARCHAR(80) NOT NULL DEFAULT '', `balance` INT NOT NULL DEFAULT 0, `payment` INT NOT NULL DEFAULT 0,
`payments_left` INT NOT NULL DEFAULT 0, `minutes_left` INT NOT NULL DEFAULT 60, `missed` INT NOT NULL DEFAULT 0,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`plate`), KEY `owner` (`owner`)) DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `drs_vehicleshop_vouchers` (`id` INT NOT NULL AUTO_INCREMENT, `owner` VARCHAR(64) NOT NULL, `label` VARCHAR(80) NOT NULL DEFAULT '',
`value` INT NOT NULL DEFAULT 0, `types` VARCHAR(120) NOT NULL DEFAULT '', `models` TEXT NULL, `expires_at` INT NOT NULL DEFAULT 0, `used` TINYINT NOT NULL DEFAULT 0,
`used_at` INT NULL, `used_plate` VARCHAR(12) NULL, `reason` VARCHAR(20) NOT NULL DEFAULT 'staff', `created_at` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), KEY `owner` (`owner`)) DEFAULT CHARSET=utf8mb4;#Para programadores
local preco = exports.drs_vehicleshop:GetVehiclePrice('adder') -- preço mais baixo nos stands, ou nil
local stands = exports.drs_vehicleshop:GetStands() -- { { id, label, type, job, vehicles } }
exports.drs_vehicleshop:GiveVoucher(source, { value = 50000, days = 30, label = 'Prémio', types = { 'cars' } })#Segurança
O servidor valida tudo: distância ao stand, preço (sempre do catálogo), stock, dinheiro, financiamento, voucher e chaves. Ninguém compra ao preço que escolhe, e os vouchers são reservados antes de cobrar.
#Resolução de problemas
O stand está vazio — abre o /standadmin → Catálogo e repõe do framework. Confirma que os veículos têm a categoria certa no framework.
As minhas mudanças no /standadmin voltam atrás no restart — o stand vem do config.lua e Config.StandsFromConfig está a true. Muda no config.lua ou põe a opção a false.
Não consigo comprar sem vendedor — o stand é de empresa e o selfService está em 'never' (ou 'nostaff' com vendedores de serviço).
Não aparece a opção de financiar — o carro custa menos que Config.Finance.minPrice, o stand tem o financiamento desligado, ou estás a usar um voucher.
O carro não tem chaves — o teu sistema de chaves não está na lista: usa Config.OnVehicleSpawned.