DRS Advanced Mechanics

v2.5.0 drs_mechanic
Ver na loja

Sistema de mecânico completo, feito de raiz. Há oficinas self-service (o jogador paga e modifica) e oficinas de empresa (com empregados, cargos, saldo e o job do framework), todas criadas e editadas em jogo pelo menu de staff. O Customs tem pré-visualização ao vivo e câmara orbital. O script inclui ainda:

  • reparações com minijogo e animações de trabalho;
  • quilometragem e desgaste de 10 peças, que mexe no handling do carro;
  • tuning de motor, turbo, tração, caixa, pneus e travões;
  • stance, nitro com chamas e purga, controlador de luzes, dinamómetro e elevador;
  • encomendas de peças, fornecedores com vendedor, armazém, faturas com comissão e anúncios;
  • um tablet com 9 aplicações.

Funciona com Qbox, QBCore e ESX.

#Requisitos

RecursoObrigatório
oxmysqlSim
Qbox, QBCore ou ESXSim (deteção automática)
ox_inventory ou qb-inventorySim (peças, kits e armazém)
qb-banking, qb-management ou Renewed-BankingNão — só com Config.UseFrameworkJobs = true (conta da empresa no banco)

Nota

Não precisa do ox_lib. Se tinhas outro mecânico (ex.: jg-mechanic ou qb-mechanicjob), desliga-o antes de ligar este.

#Instalação

  1. Coloca drs_mechanic em resources.
  2. Adiciona ao server.cfg, depois da framework e do inventário:
server.cfg
ensure oxmysql
ensure qb-core
ensure qb-inventory
ensure drs_mechanic
  1. As tabelas são criadas sozinhas no primeiro arranque (também estão em sql/drs_mechanic.sql).
  2. Cria os itens (ver abaixo) e copia as imagens de install/images/ para o teu inventário.
  3. Dá acesso ao menu de staff (os grupos de admin da framework já têm):
server.cfg
add_ace group.admin drs.mechanic allow
  1. Em jogo, abre o /mecanicoadmin e cria ou ajusta as oficinas.

#Itens

Importante

Tens de criar o item tablet_mecanico no teu inventário. Sem ele, o tablet só abre com o comando /tablet. Copia a linha do item (está nas listas de baixo) e a imagem install/images/tablet_mecanico.png (podes usar a imagem do tablet que já tens no inventário). O nome do item muda-se em Config.Tablet.item, que também aceita uma lista, por exemplo { 'tablet_mecanico', 'mechanic_tablet' } se já tinhas um item de tablet.

qb-core/shared/items.lua
tablet_mecanico = { name = 'tablet_mecanico', label = 'Tablet de mecânico', weight = 800, type = 'item', image = 'tablet_mecanico.png', unique = true, useable = true, shouldClose = true, description = 'Tablet da oficina' },

QBCore — dentro de qb-core/shared/items.lua:

items_qb-core.lua
-- drs_mechanic: cola dentro de qb-core/shared/items.lua (drs-core: drs-core/shared/items.lua)
-- Imagens: copia install/images/*.png para a pasta de imagens do inventário (qb-inventory/html/images/)

    advancedrepairkit = { name = 'advancedrepairkit', label = 'Kit de reparação avançado', weight = 4000, type = 'item', image = 'advancedrepairkit.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Reparação completa (mecânicos)' },
    air_filter = { name = 'air_filter', label = 'Filtro de ar', weight = 400, type = 'item', image = 'air_filter.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    awd_drivetrain = { name = 'awd_drivetrain', label = 'Tração integral', weight = 15000, type = 'item', image = 'awd_drivetrain.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    brakepad_set = { name = 'brakepad_set', label = 'Jogo de pastilhas', weight = 1500, type = 'item', image = 'brakepad_set.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    ceramic_brakes = { name = 'ceramic_brakes', label = 'Travões cerâmicos', weight = 5000, type = 'item', image = 'ceramic_brakes.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    cleaningkit = { name = 'cleaningkit', label = 'Kit de limpeza', weight = 800, type = 'item', image = 'cleaningkit.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Limpa o veículo' },
    clutch = { name = 'clutch', label = 'Embraiagem', weight = 3000, type = 'item', image = 'clutch.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    empty_nitrous_bottle = { name = 'empty_nitrous_bottle', label = 'Garrafa de nitro vazia', weight = 1500, type = 'item', image = 'empty_nitrous_bottle.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    engine_oil = { name = 'engine_oil', label = 'Óleo do motor', weight = 1000, type = 'item', image = 'engine_oil.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    ev_battery = { name = 'ev_battery', label = 'Bateria elétrica', weight = 9000, type = 'item', image = 'ev_battery.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    ev_coolant = { name = 'ev_coolant', label = 'Refrigerante elétrico', weight = 1000, type = 'item', image = 'ev_coolant.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    ev_engine = { name = 'ev_engine', label = 'Motor elétrico de performance', weight = 25000, type = 'item', image = 'ev_engine.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    ev_motor = { name = 'ev_motor', label = 'Motor elétrico', weight = 12000, type = 'item', image = 'ev_motor.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    fwd_drivetrain = { name = 'fwd_drivetrain', label = 'Tração dianteira', weight = 10000, type = 'item', image = 'fwd_drivetrain.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    gearbox_6 = { name = 'gearbox_6', label = 'Caixa de 6 velocidades', weight = 12000, type = 'item', image = 'gearbox_6.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    gearbox_8 = { name = 'gearbox_8', label = 'Caixa de 8 velocidades', weight = 14000, type = 'item', image = 'gearbox_8.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    i4_engine = { name = 'i4_engine', label = 'Motor I4 turbo', weight = 20000, type = 'item', image = 'i4_engine.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    lighting_controller = { name = 'lighting_controller', label = 'Controlador de luzes', weight = 500, type = 'item', image = 'lighting_controller.png', unique = true, useable = true, shouldClose = true, combinable = nil, description = 'Usa dentro do carro' },
    tablet_mecanico = { name = 'tablet_mecanico', label = 'Tablet de mecânico', weight = 800, type = 'item', image = 'tablet_mecanico.png', unique = true, useable = true, shouldClose = true, combinable = nil, description = 'Tablet da oficina' },
    nitrous_bottle = { name = 'nitrous_bottle', label = 'Garrafa de nitro', weight = 3000, type = 'item', image = 'nitrous_bottle.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Enche o nitro do carro' },
    nitrous_install_kit = { name = 'nitrous_install_kit', label = 'Kit de instalação de nitro', weight = 5000, type = 'item', image = 'nitrous_install_kit.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    offroad_tyres = { name = 'offroad_tyres', label = 'Pneus todo-o-terreno', weight = 9000, type = 'item', image = 'offroad_tyres.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    repairkit = { name = 'repairkit', label = 'Kit de reparação', weight = 2500, type = 'item', image = 'repairkit.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Repara o motor e a carroçaria' },
    rwd_drivetrain = { name = 'rwd_drivetrain', label = 'Tração traseira', weight = 12000, type = 'item', image = 'rwd_drivetrain.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    slick_tyres = { name = 'slick_tyres', label = 'Pneus slick', weight = 8000, type = 'item', image = 'slick_tyres.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    spark_plug = { name = 'spark_plug', label = 'Vela', weight = 80, type = 'item', image = 'spark_plug.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    stancer_kit = { name = 'stancer_kit', label = 'Kit de stance', weight = 4000, type = 'item', image = 'stancer_kit.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    suspension_parts = { name = 'suspension_parts', label = 'Peças de suspensão', weight = 6000, type = 'item', image = 'suspension_parts.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    turbo_kit = { name = 'turbo_kit', label = 'Kit turbo', weight = 6000, type = 'item', image = 'turbo_kit.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    tyre_set = { name = 'tyre_set', label = 'Jogo de pneus', weight = 8000, type = 'item', image = 'tyre_set.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    tyrekit = { name = 'tyrekit', label = 'Kit de pneus', weight = 1500, type = 'item', image = 'tyrekit.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Repara os pneus' },
    v12_engine = { name = 'v12_engine', label = 'Motor V12', weight = 38000, type = 'item', image = 'v12_engine.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },
    v8_engine = { name = 'v8_engine', label = 'Motor V8', weight = 30000, type = 'item', image = 'v8_engine.png', unique = false, useable = true, shouldClose = true, combinable = nil, description = 'Peça de oficina' },

ox_inventory / Qbox — dentro de ox_inventory/data/items.lua:

items_ox_inventory.lua
-- drs_mechanic: cola dentro de ox_inventory/data/items.lua
-- Imagens: copia install/images/*.png para ox_inventory/web/images/

    ['advancedrepairkit'] = { label = 'Kit de reparação avançado', weight = 4000, stack = true, close = true, description = 'Reparação completa (mecânicos)', client = { image = 'advancedrepairkit.png' } },
    ['air_filter'] = { label = 'Filtro de ar', weight = 400, stack = true, close = true, description = 'Peça de oficina', client = { image = 'air_filter.png' } },
    ['awd_drivetrain'] = { label = 'Tração integral', weight = 15000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'awd_drivetrain.png' } },
    ['brakepad_set'] = { label = 'Jogo de pastilhas', weight = 1500, stack = true, close = true, description = 'Peça de oficina', client = { image = 'brakepad_set.png' } },
    ['ceramic_brakes'] = { label = 'Travões cerâmicos', weight = 5000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'ceramic_brakes.png' } },
    ['cleaningkit'] = { label = 'Kit de limpeza', weight = 800, stack = true, close = true, description = 'Limpa o veículo', client = { image = 'cleaningkit.png' } },
    ['clutch'] = { label = 'Embraiagem', weight = 3000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'clutch.png' } },
    ['empty_nitrous_bottle'] = { label = 'Garrafa de nitro vazia', weight = 1500, stack = true, close = true, description = 'Peça de oficina', client = { image = 'empty_nitrous_bottle.png' } },
    ['engine_oil'] = { label = 'Óleo do motor', weight = 1000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'engine_oil.png' } },
    ['ev_battery'] = { label = 'Bateria elétrica', weight = 9000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'ev_battery.png' } },
    ['ev_coolant'] = { label = 'Refrigerante elétrico', weight = 1000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'ev_coolant.png' } },
    ['ev_engine'] = { label = 'Motor elétrico de performance', weight = 25000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'ev_engine.png' } },
    ['ev_motor'] = { label = 'Motor elétrico', weight = 12000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'ev_motor.png' } },
    ['fwd_drivetrain'] = { label = 'Tração dianteira', weight = 10000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'fwd_drivetrain.png' } },
    ['gearbox_6'] = { label = 'Caixa de 6 velocidades', weight = 12000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'gearbox_6.png' } },
    ['gearbox_8'] = { label = 'Caixa de 8 velocidades', weight = 14000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'gearbox_8.png' } },
    ['i4_engine'] = { label = 'Motor I4 turbo', weight = 20000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'i4_engine.png' } },
    ['lighting_controller'] = { label = 'Controlador de luzes', weight = 500, stack = false, close = true, description = 'Usa dentro do carro', client = { image = 'lighting_controller.png' } },
    ['tablet_mecanico'] = { label = 'Tablet de mecânico', weight = 800, stack = false, close = true, description = 'Tablet da oficina', client = { image = 'tablet_mecanico.png' } },
    ['nitrous_bottle'] = { label = 'Garrafa de nitro', weight = 3000, stack = true, close = true, description = 'Enche o nitro do carro', client = { image = 'nitrous_bottle.png' } },
    ['nitrous_install_kit'] = { label = 'Kit de instalação de nitro', weight = 5000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'nitrous_install_kit.png' } },
    ['offroad_tyres'] = { label = 'Pneus todo-o-terreno', weight = 9000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'offroad_tyres.png' } },
    ['repairkit'] = { label = 'Kit de reparação', weight = 2500, stack = true, close = true, description = 'Repara o motor e a carroçaria', client = { image = 'repairkit.png' } },
    ['rwd_drivetrain'] = { label = 'Tração traseira', weight = 12000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'rwd_drivetrain.png' } },
    ['slick_tyres'] = { label = 'Pneus slick', weight = 8000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'slick_tyres.png' } },
    ['spark_plug'] = { label = 'Vela', weight = 80, stack = true, close = true, description = 'Peça de oficina', client = { image = 'spark_plug.png' } },
    ['stancer_kit'] = { label = 'Kit de stance', weight = 4000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'stancer_kit.png' } },
    ['suspension_parts'] = { label = 'Peças de suspensão', weight = 6000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'suspension_parts.png' } },
    ['turbo_kit'] = { label = 'Kit turbo', weight = 6000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'turbo_kit.png' } },
    ['tyre_set'] = { label = 'Jogo de pneus', weight = 8000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'tyre_set.png' } },
    ['tyrekit'] = { label = 'Kit de pneus', weight = 1500, stack = true, close = true, description = 'Repara os pneus', client = { image = 'tyrekit.png' } },
    ['v12_engine'] = { label = 'Motor V12', weight = 38000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'v12_engine.png' } },
    ['v8_engine'] = { label = 'Motor V8', weight = 30000, stack = true, close = true, description = 'Peça de oficina', client = { image = 'v8_engine.png' } },

ESX — importa na base de dados:

items_esx.sql
-- drs_mechanic: itens para ESX (es_extended)
INSERT IGNORE INTO `items` (`name`, `label`, `weight`) VALUES
    ('advancedrepairkit', 'Kit de reparação avançado', 4),
    ('air_filter', 'Filtro de ar', 1),
    ('awd_drivetrain', 'Tração integral', 15),
    ('brakepad_set', 'Jogo de pastilhas', 1),
    ('ceramic_brakes', 'Travões cerâmicos', 5),
    ('cleaningkit', 'Kit de limpeza', 1),
    ('clutch', 'Embraiagem', 3),
    ('empty_nitrous_bottle', 'Garrafa de nitro vazia', 1),
    ('engine_oil', 'Óleo do motor', 1),
    ('ev_battery', 'Bateria elétrica', 9),
    ('ev_coolant', 'Refrigerante elétrico', 1),
    ('ev_engine', 'Motor elétrico de performance', 25),
    ('ev_motor', 'Motor elétrico', 12),
    ('fwd_drivetrain', 'Tração dianteira', 10),
    ('gearbox_6', 'Caixa de 6 velocidades', 12),
    ('gearbox_8', 'Caixa de 8 velocidades', 14),
    ('i4_engine', 'Motor I4 turbo', 20),
    ('lighting_controller', 'Controlador de luzes', 1),
    ('tablet_mecanico', 'Tablet de mecânico', 1),
    ('nitrous_bottle', 'Garrafa de nitro', 3),
    ('nitrous_install_kit', 'Kit de instalação de nitro', 5),
    ('offroad_tyres', 'Pneus todo-o-terreno', 9),
    ('repairkit', 'Kit de reparação', 2),
    ('rwd_drivetrain', 'Tração traseira', 12),
    ('slick_tyres', 'Pneus slick', 8),
    ('spark_plug', 'Vela', 1),
    ('stancer_kit', 'Kit de stance', 4),
    ('suspension_parts', 'Peças de suspensão', 6),
    ('turbo_kit', 'Kit turbo', 6),
    ('tyre_set', 'Jogo de pneus', 8),
    ('tyrekit', 'Kit de pneus', 1),
    ('v12_engine', 'Motor V12', 38),
    ('v8_engine', 'Motor V8', 30);

#Oficinas

#Self-service e empresa

  • Self-service: qualquer jogador leva o carro ao lugar de trabalho, abre o Customs ou repara, e paga do bolso (com o desconto Config.SelfServiceDiscount).
  • Empresa: só os empregados de serviço trabalham. As modificações faturam-se a um cliente, que aceita ou recusa a fatura, e o empregado recebe a comissão.

Toda a empresa tem um job do framework. Se o job não existir, o script cria-o no QBCore, no Qbox ou no ESX com os graus de Config.JobGrades. Quem é contratado, promovido ou fica dono recebe esse job no grau certo, e quem é despedido fica com Config.UnemployedJob.

SeparadorO que tem
GeralNome, tipo (self-service ou empresa), logótipo, job, blip, funcionalidades e regras da empresa
Pontos no mapaLugares de trabalho, elevadores, dinamómetro, ponto de serviço, armazém e blip, todos postos onde estás, com TP para cada um
EmpresaDono, saldo e armazém (slots e peso)

No topo da lista de oficinas está o item Fornecedores: as lojas de peças, comuns a todas as oficinas (nome, posição, vendedor e artigos com preço).

O blip de cada oficina mostra se está aberta ou fechada.

Dica

Na empresa, a regra "Customs sem fatura pago pela empresa" decide o que acontece quando o mecânico modifica um carro sem faturar. Ligada, o valor sai do saldo da empresa. Desligada, o mecânico tem sempre de faturar um cliente.

#Empregados e serviço

Com o sistema próprio (Config.UseFrameworkJobs = false), os cargos são os de Config.Ranks e o dono gere tudo no tablet: contratar, promover, despedir, saldo e comissão. Pode-se trabalhar em várias oficinas. Para entrar ao serviço, usa o ponto de serviço da oficina, o tablet, ou o serviço do job do framework.

Com Config.UseFrameworkJobs = true, quem manda são o job, o menu do patrão e a conta da empresa no banco do framework.

#Trabalho no carro

#Customs

Câmara orbital (arrastar para rodar, roda do rato para aproximar), pré-visualização ao vivo e carrinho. Inclui performance, carroçaria, rodas, pintura (clássica, metalizada, pérola, mate, metal, cromada e RGB), luzes, interior, extras e buzinas, que tocam ao escolher. O preço é uma percentagem do valor do carro mais um valor fixo, multiplicado pelo nível, e é sempre recalculado no servidor.

#Reparar, serviços e kits

  • Reparar no lugar de trabalho: os empregados fazem um minijogo (Espaço, clique ou E na zona certa) com animação.
  • Serviços: 10 peças gastam-se com os quilómetros (óleo, filtro de ar, velas, pastilhas, pneus, embraiagem, suspensão e as peças dos elétricos) e o desgaste mexe na potência, velocidade, travagem e aderência. O condutor recebe avisos quando uma peça está gasta.
  • Kits: repairkit, advancedrepairkit (só mecânicos), cleaningkit e tyrekit, usados em qualquer lado.

#Tuning, stance e nitro

  • Tuning: motores (I4, V8, V12, elétrico, com som próprio), turbo, tração (AWD, RWD, FWD), caixas de 6 e 8 velocidades, pneus slick e todo-o-terreno, e travões cerâmicos. Usam o item da peça (Config.TuningRequiresItem), e a peça antiga volta ao inventário.
  • Stance: afastamento, cambagem e altura, à frente e atrás, visíveis para todos os jogadores perto.
  • Nitro: instalar com o kit e encher com a garrafa. Usa-se com LSHIFT e a purga com LCTRL (teclas mudam-se nas definições do FiveM). Tem chamas no escape e consumo controlado pelo servidor.

#Ferramentas

  • Controlador de luzes (item): néons, modos pisca, respira e arco-íris, e xénon, dentro do carro.
  • Dinamómetro: potência, binário, rotações e velocidade máxima, com o gráfico ao vivo.
  • Elevador: PAGEUP sobe, PAGEDOWN desce e E termina.

#Tablet

/tablet ou o item tablet_mecanico (só empregados; o item tem de ser criado). Tem 9 aplicações:

AppO que faz
InícioResumo da oficina, serviço, aberta ou fechada, anúncio para a cidade
VeículoEstado, quilómetros, peças e histórico do carro ligado
ServiçoTrocar peças gastas
TuningInstalar e tirar peças de performance
StanceInstalar o kit e ajustar ao vivo
FaturasPassar e cancelar faturas
EncomendasEncomendar peças, que chegam ao armazém
EquipaContratar, cargos e despedir
GestãoSaldo, depósitos, levantamentos e comissão

#Lojas, encomendas e armazém

  • Fornecedores: uma lista única para todas as oficinas (no primeiro arranque vem de Config.Suppliers; depois edita-se em /mecanicoadmin → Fornecedores). Só os mecânicos veem o vendedor e o marcador. A compra é paga pela oficina onde o mecânico está de serviço (Config.UseSocietyFund) ou por ele.
  • Encomendas: pedidas no tablet, chegam ao armazém ao fim de Config.Orders.deliveryMinutes e recolhem-se lá. O que não couber no inventário fica à espera.
  • Armazém: partilhado pelos empregados (ox_inventory, qb-inventory ou inventários antigos).

#Comandos

ComandoQuemO que faz
/mecanicoadminStaffMenu de staff das oficinas
/tabletEmpregadosAbre o tablet
/faturasTodosFaturas por pagar (em inglês: /invoices)

#Opções principais

OpçãoPor defeitoDescrição
Config.Locale'pt'Idioma: pt ou en
Config.UseFrameworkJobsfalsefalse = empregados próprios; true = job e conta do framework
Config.SyncFrameworkJobtrueDá o job do framework a quem é contratado
Config.Commission10% das faturas pagas para o mecânico
Config.UseSocietyFundtruePeças e encomendas pagas pela empresa
Config.Repair.price1500Reparação no self-service
Config.TuningRequiresItemtrueO tuning gasta o item da peça
Config.ShopPed's_m_m_lathandy_01'Vendedor das lojas
Config.Servicing.mileageHudtrueBarra dos km no ecrã (false esconde; o nitro instalado continua a aparecer)
Config.Lift.height1.9Altura máxima do elevador (baixa se a MLO deixar de desenhar o carro lá em cima)
Config.AdminCommand'mecanicoadmin'Comando do menu de staff

#Configuração completa

config.lua
Config = {}

-- ------------------------------------------------------------------ geral / general
Config.Locale = 'pt'                -- 'pt' | 'en'
Config.Framework = 'auto'           -- 'auto' | 'qb' | 'qbx' | 'esx'
Config.UseOxNotify = false
Config.Currency = '$'
Config.CurrencyLocale = 'pt-PT'     -- formato dos números na interface
Config.InteractKey = 38             -- E
Config.Debug = false

-- Empregados: false = sistema próprio (contratar/despedir/cargos/saldo no tablet, podes trabalhar em várias oficinas)
--             true  = empregos do framework (job + boss menu + conta da empresa no banco do framework)
Config.UseFrameworkJobs = false

-- Cargos do sistema próprio (0 = mais baixo). manage = gere empregados, saldo e definições.
-- grade = grau do job no framework (com Config.SyncFrameworkJob)
Config.Ranks = {
    [0] = { label = 'Aprendiz', manage = false, grade = 0 },
    [1] = { label = 'Mecânico', manage = false, grade = 2 },
    [2] = { label = 'Chefe de oficina', manage = true, grade = 3 },
    [3] = { label = 'Dono', manage = true, grade = 4 },
}

-- Sistema próprio + job do framework: quem é contratado/promovido/fica dono numa oficina com `job`
-- passa a ter esse job no framework (grau acima), e o "de serviço" do tablet liga o serviço do job.
-- Despedido -> Config.UnemployedJob.
Config.SyncFrameworkJob = true

-- Cada oficina de empresa TEM de ter um job. Se o job não existir no framework (QBCore/Qbox/ESX) é criado com estes graus.
-- (os graus usados pelos cargos acima são 0, 2, 3 e 4; o 4 é o patrão)
Config.JobGrades = {
    [0] = { name = 'Recruta', payment = 50 },
    [1] = { name = 'Aprendiz', payment = 75 },
    [2] = { name = 'Mecânico', payment = 100 },
    [3] = { name = 'Chefe de oficina', payment = 125 },
    [4] = { name = 'Patrão', payment = 150, isboss = true },
}
Config.UnemployedJob = 'unemployed'

-- ------------------------------------------------------------------ tablet
Config.Tablet = {
    item = 'tablet_mecanico',       -- usar o item abre o tablet (nil = só comando; vários: { 'tablet_mecanico', 'mechanic_tablet' })
    command = 'tablet',
    connectDistance = 6.0,          -- distância para ligar o tablet a um veículo
}

-- ------------------------------------------------------------------ preços / pricing
-- Preço de cada mod = valor do carro × percentVehVal (+ price fixo), × (1 + priceMult × nível)
Config.PricesAsPercentOfValue = true
Config.PricesIncludeFixed = true
Config.DefaultVehicleValue = 50000  -- quando não se sabe o valor do carro
Config.UseSocietyFund = true        -- compras de peças e encomendas pagas pela empresa (false = pelo empregado)
Config.Commission = 10              -- % das faturas pagas que vai para o empregado (o resto para a empresa)
Config.SelfServiceDiscount = 0      -- % de desconto nas oficinas self-service

-- ------------------------------------------------------------------ reparar
Config.Repair = {
    price = 1500,                   -- self-service: preço da reparação completa
    time = 12,                      -- segundos (empregados: com minijogo)
    minigame = true,
}

-- Kits de reparação (itens). engine/body = até que valor recupera (0-1000)
Config.RepairKits = {
    repairkit = { engine = 650, body = 650, time = 10, mechanicOnly = false },
    advancedrepairkit = { engine = 1000, body = 1000, time = 15, mechanicOnly = true },
    cleaningkit = { clean = true, time = 5 },
    tyrekit = { tyres = true, time = 6 },
}

-- ------------------------------------------------------------------ nitro
Config.Nitrous = {
    kitItem = 'nitrous_install_kit',   -- instala o sistema no carro
    bottleItem = 'nitrous_bottle',     -- garrafa cheia (enche o sistema)
    emptyItem = 'empty_nitrous_bottle',-- garrafa vazia que fica quando enches
    key = 'LSHIFT',                    -- tecla para usar (no carro)
    power = 35.0,                      -- aumento de potência enquanto está ligado
    usePerSecond = 12,                 -- % da garrafa gasta por segundo
    purgeKey = 'LCONTROL',             -- purga (só efeito)
}

-- ------------------------------------------------------------------ outros itens
Config.Items = {
    stancer = 'stancer_kit',           -- instala o stance no carro
    lighting = 'lighting_controller',  -- controlador de luzes (usar dentro do carro)
}

-- ------------------------------------------------------------------ elevador / dyno
Config.Lift = {
    height = 1.9, speed = 0.012,       -- altura máxima (m). Se a MLO da oficina deixar de desenhar o carro lá em cima, baixa (ex.: 1.4)
    -- props do elevador: se estes modelos existirem no servidor (ex.: o recurso grátis jg-mechanic-props, licença GPL-3.0),
    -- aparece a base e a plataforma sobe com o carro (igual para todos). Sem eles, o carro sobe sozinho.
    props = { base = 'jg_lift_stand', platform = 'jg_lift' },
    propsDistance = 80.0,
}
Config.Dyno = { duration = 9 }         -- segundos do teste no dinamómetro

-- ------------------------------------------------------------------ encomendas de peças
Config.Orders = {
    deliveryMinutes = 10,               -- tempo até chegarem ao armazém
    items = {                           -- o que se pode encomendar e o preço por unidade
        { name = 'repairkit', price = 250 }, { name = 'advancedrepairkit', price = 600 },
        { name = 'cleaningkit', price = 60 }, { name = 'tyrekit', price = 180 },
        { name = 'engine_oil', price = 120 }, { name = 'air_filter', price = 90 }, { name = 'spark_plug', price = 70 },
        { name = 'clutch', price = 650 }, { name = 'brakepad_set', price = 300 }, { name = 'tyre_set', price = 700 },
        { name = 'suspension_parts', price = 900 }, { name = 'ev_battery', price = 2500 }, { name = 'ev_coolant', price = 150 },
        { name = 'ev_motor', price = 3000 }, { name = 'nitrous_install_kit', price = 4500 }, { name = 'nitrous_bottle', price = 900 },
        { name = 'stancer_kit', price = 3500 }, { name = 'lighting_controller', price = 1200 },
        { name = 'turbo_kit', price = 9000 }, { name = 'awd_drivetrain', price = 12000 }, { name = 'rwd_drivetrain', price = 9000 },
        { name = 'fwd_drivetrain', price = 7000 }, { name = 'gearbox_6', price = 6000 }, { name = 'gearbox_8', price = 9000 },
        { name = 'slick_tyres', price = 5000 }, { name = 'offroad_tyres', price = 3500 }, { name = 'ceramic_brakes', price = 7500 },
        { name = 'v8_engine', price = 25000 }, { name = 'v12_engine', price = 45000 }, { name = 'i4_engine', price = 12000 },
        { name = 'ev_engine', price = 30000 },
    },
}

-- ------------------------------------------------------------------ oficinas / locations
-- type: 'self' (self-service: o próprio jogador paga e modifica) | 'owned' (empresa com empregados)
-- bays: lugares onde se trabalha no carro (customs, reparar). lifts: elevadores. dyno: dinamómetro.
-- stash/shop: armazém e loja de peças (owned). job: emprego no framework (obrigatório nas empresas; criado se não existir).
-- features: o que esta oficina faz. Os pontos editam-se no jogo com /mecanicoadmin.
Config.Locations = {
    lscburton = {
        label = 'Los Santos Customs Burton', type = 'self', logo = 'lsc', job = 'mechanic',
        blip = { sprite = 72, color = 47, coords = vector3(-337.38, -136.92, 39.01) },
        bays = { vector4(-337.38, -136.92, 38.60, 69.0) },
        features = { customs = true, repair = true },
    },
    lscmesa = {
        label = 'Los Santos Customs La Mesa', type = 'self', logo = 'lsc', job = 'lscmesa',
        blip = { sprite = 72, color = 47, coords = vector3(731.80, -1088.80, 22.17) },
        bays = { vector4(731.80, -1088.80, 21.80, 90.0) },
        features = { customs = true, repair = true },
    },
    lscairport = {
        label = 'Los Santos Customs Aeroporto', type = 'self', logo = 'lsc', job = 'lscairport',
        blip = { sprite = 72, color = 47, coords = vector3(-1155.30, -2007.20, 13.18) },
        bays = { vector4(-1155.30, -2007.20, 12.80, 315.0) },
        features = { customs = true, repair = true },
    },
    lscharmony = {
        label = 'Los Santos Customs Harmony', type = 'self', logo = 'lsc', job = 'lscharmony',
        blip = { sprite = 72, color = 47, coords = vector3(1175.00, 2640.50, 37.75) },
        bays = { vector4(1175.00, 2640.50, 37.40, 0.0) },
        features = { customs = true, repair = true },
    },
    lscpaleto = {
        label = "Beeker's Garage", type = 'self', logo = 'lsc', job = 'beeker',
        blip = { sprite = 72, color = 47, coords = vector3(110.80, 6626.40, 31.79) },
        bays = { vector4(110.80, 6626.40, 31.40, 225.0) },
        features = { customs = true, repair = true },
    },
    bennys = {
        label = "Benny's Original Motor Works", type = 'owned', logo = 'bennys', job = 'bennys',
        blip = { sprite = 446, color = 5, coords = vector3(-205.70, -1310.70, 31.30) },
        bays = {
            vector4(-222.50, -1329.60, 30.50, 270.0), vector4(-222.50, -1322.60, 30.50, 270.0),
            vector4(-200.20, -1311.20, 30.90, 180.0),
        },
        lifts = { vector4(-214.30, -1334.10, 30.40, 0.0) },
        dyno = vector4(-197.20, -1323.40, 30.60, 90.0),
        stash = vector3(-224.40, -1333.80, 30.89),
        features = { customs = true, repair = true, servicing = true, tuning = true, stance = true, nitrous = true, lighting = true, dyno = true, lift = true },
        stashSlots = 80, stashWeight = 2000000,
    },
}

-- ------------------------------------------------------------------ fornecedores de peças
-- Uma lista única para TODAS as oficinas: qualquer mecânico compra aqui e paga a oficina onde está de serviço.
-- Só os mecânicos os veem. Isto é só a semente do primeiro arranque: depois edita-se em /mecanicoadmin → Fornecedores.
Config.Suppliers = {
    {
        name = 'Fornecedor de Cosméticos', coords = vector4(109.15, 6631.66, 31.78, 0.0),
        items = {
            { name = 'stancer_kit', price = 3500 }, { name = 'lighting_controller', price = 1200 }, { name = 'cleaningkit', price = 60 },
        },
    },
    {
        name = 'Fornecedor de Performance', coords = vector4(1172.80, 2635.54, 37.79, 0.0),
        items = {
            { name = 'repairkit', price = 300 }, { name = 'advancedrepairkit', price = 700 }, { name = 'tyrekit', price = 220 },
            { name = 'engine_oil', price = 150 }, { name = 'air_filter', price = 110 }, { name = 'spark_plug', price = 90 },
            { name = 'brakepad_set', price = 350 }, { name = 'clutch', price = 750 }, { name = 'tyre_set', price = 800 },
            { name = 'suspension_parts', price = 1000 }, { name = 'turbo_kit', price = 9000 }, { name = 'v8_engine', price = 25000 },
            { name = 'nitrous_install_kit', price = 4500 }, { name = 'nitrous_bottle', price = 900 }, { name = 'tablet_mecanico', price = 1500 },
        },
    },
}

-- ------------------------------------------------------------------ admin
Config.AdminCommand = 'mecanicoadmin'

-- vendedor (NPC) das lojas de peças; cada loja pode ter outro modelo ou nenhum (no /mecanicoadmin)
Config.ShopPed = 's_m_m_lathandy_01'
Config.ShopPedDistance = 60.0
Config.AdminAce = 'drs.mechanic'

-- Discord (vazio = desligado)
Config.Webhook = ''

#Base de dados

drs_mechanic.sql
-- drs_mechanic: tabelas (também são criadas sozinhas no primeiro arranque)

CREATE TABLE IF NOT EXISTS drs_mechanic_locations (
    id VARCHAR(50) NOT NULL,
    data LONGTEXT NOT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS drs_mechanic_business (
    location VARCHAR(50) NOT NULL,
    owner VARCHAR(60) DEFAULT NULL,
    owner_name VARCHAR(100) DEFAULT NULL,
    balance INT NOT NULL DEFAULT 0,
    `open` TINYINT(1) NOT NULL DEFAULT 0,
    commission INT NOT NULL DEFAULT 10,
    PRIMARY KEY (location)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS drs_mechanic_employees (
    location VARCHAR(50) NOT NULL,
    citizenid VARCHAR(60) NOT NULL,
    name VARCHAR(100) DEFAULT NULL,
    grade INT NOT NULL DEFAULT 0,
    hired_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (location,
    citizenid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS drs_mechanic_vehicles (
    plate VARCHAR(12) NOT NULL,
    mileage DOUBLE NOT NULL DEFAULT 0,
    parts LONGTEXT NULL,
    tuning LONGTEXT NULL,
    stance LONGTEXT NULL,
    nitrous LONGTEXT NULL,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (plate)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS drs_mechanic_invoices (
    id INT NOT NULL AUTO_INCREMENT,
    location VARCHAR(50) NOT NULL,
    from_cid VARCHAR(60) NULL,
    from_name VARCHAR(100) NULL,
    to_cid VARCHAR(60) NULL,
    to_name VARCHAR(100) NULL,
    items LONGTEXT NOT NULL,
    total INT NOT NULL DEFAULT 0,
    status VARCHAR(12) NOT NULL DEFAULT 'pending',
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    paid_at TIMESTAMP NULL,
    PRIMARY KEY (id),
    KEY location (location),
    KEY to_cid (to_cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS drs_mechanic_orders (
    id INT NOT NULL AUTO_INCREMENT,
    location VARCHAR(50) NOT NULL,
    by_name VARCHAR(100) NULL,
    items LONGTEXT NOT NULL,
    total INT NOT NULL DEFAULT 0,
    status VARCHAR(12) NOT NULL DEFAULT 'pending',
    deliver_at INT NOT NULL DEFAULT 0,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    KEY location (location)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS drs_mechanic_history (
    id INT NOT NULL AUTO_INCREMENT,
    plate VARCHAR(12) NOT NULL,
    location VARCHAR(50) NULL,
    kind VARCHAR(20) NOT NULL,
    detail VARCHAR(120) NULL,
    by_name VARCHAR(100) NULL,
    mileage DOUBLE NOT NULL DEFAULT 0,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id),
    KEY plate (plate)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

#Para programadores

servidor
local km, unidade = exports.drs_mechanic:GetMileage(matricula)
local dados = exports.drs_mechanic:GetVehicleData(matricula) -- { mileage, parts, tuning, stance, nitrous }

#Segurança

O servidor valida tudo: acesso à oficina, estar de serviço, distância ao carro e ao ponto, itens, saldo, tempo de cada trabalho e preços (o Customs recalcula o preço de cada peça). Só se guardam no carro as modificações pagas, e as faturas não se cobram duas vezes.

#Resolução de problemas

"Tens de estar de serviço" — entra ao serviço no ponto de serviço da oficina, no tablet ou no job do framework.

"A empresa não tem job" — escreve o job em /mecanicoadmin → Geral e guarda. Se não existir, é criado.

O elevador diz que o carro tem pessoas — sai toda a gente do carro, incluindo NPCs.

Não vejo o fornecedor — só os empregados de uma oficina o veem. Para comprar tens de estar de serviço (ou trabalhar numa só oficina).

As modificações desaparecem quando guardo o carro — o carro tem de ter dono (estar em player_vehicles ou owned_vehicles). Os carros sem dono só mudam enquanto existem.