DRS Vehicle Shop

v1.9.3 drs_vehicleshop
Ver na loja

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

RecursoObrigatório
oxmysqlSim
Qbox, QBCore ou ESXSim (deteção automática)
ox_target ou qb-targetNão — só com Config.UseTarget = true (senão é a tecla E)
qb-banking, qb-management ou Renewed-BankingNã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

  1. Coloca drs_vehicleshop em resources.
  2. Adiciona ao server.cfg, depois da framework:
server.cfg
ensure oxmysql
ensure qb-core
ensure drs_vehicleshop
  1. As tabelas são criadas sozinhas no primeiro arranque (também estão em sql/drs_vehicleshop.sql). Os stands de Config.DefaultStands entram nesse arranque.
  2. Dá acesso ao menu de staff (os grupos de admin da framework já têm):
server.cfg
add_ace group.admin drs.vehicleshop allow
  1. Em jogo, abre o /standadmin e ajusta os pontos e o catálogo.

#Stands

#Tipos

TipoO que vende
allTodos os veículos
carsCarros (categorias de Config.Types.cars)
bikesMotas e bicicletas
vipVeículos de luxo (shop = 'luxury' no QBCore, ou a partir de minPrice)
airHelicópteros e aviões
boatsBarcos

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.

SeparadorO que tem
GeralNome, tipo, job, blip, compras sem vendedor, test drive, financiamento e esconder do mapa
Pontos no mapaMenu, entrega, carro de pré-visualização, câmara, test drive e vendedores, todos postos onde estás, com TP para cada um
ExposiçãoCarros parados no stand (posição e modelo)
CatálogoRepor do framework, preços, stock, tirar e pôr modelos
VendasHistó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 de Config.Finance.interest.
  • As prestações são cobradas do banco a cada intervalMinutes minutos a jogar, com aviso.
  • Ao fim de maxMissed prestações falhadas seguidas, o carro é apreendido.
  • /financiamentos mostra 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):

ValorQuando
'always'Sempre
'nostaff'Só quando não há vendedores de serviço
'never'Nunca: só com vendedor

#Comandos

ComandoQuemO que faz
/standadminStaffMenu de staff dos stands
/financiamentosTodosVer e pagar os financiamentos
/darvoucher [id] [valor] [dias]StaffDar um voucher a um jogador

#Opções principais

OpçãoPor defeitoDescrição
Config.Locale'pt'Idioma: pt ou en
Config.UseTargetfalsetrue = 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.seconds60Duração do test drive
Config.Finance.enabledtruefalse = sem financiamento e sem /financiamentos
Config.Commission10% do preço para o vendedor
Config.Vouchers.enabledtrueLiga os vouchers
Config.StandsFromConfigtrueOs 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.lua
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_vehicleshop.sql
-- 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

servidor
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.