DRS VIP Store
A DRS VIP Store é uma loja VIP com moeda própria (DRS Coins por defeito). Os jogadores compram coins no Tebex, resgatam o código na loja e gastam-nas no que quiseres vender:
- veículos e aéreos com a foto oficial, as estatísticas reais e matrícula à escolha;
- casas do qb-houses, peds VIP, VIPs ativados pela staff e prioridade na fila;
- armas, itens, dinheiro e crypto;
- matrícula e número de telemóvel personalizados;
- pacotes, promoções, edições limitadas, códigos de eventos e ofertas entre jogadores.
Abre com F4 ou /loja. Funciona com QBCore e Qbox.
#Requisitos
| Recurso | Obrigatório |
|---|---|
oxmysql | Sim |
| QBCore ou Qbox | Sim |
qb-houses | Não — só para vender casas |
connectqueue | Não — só para a prioridade na fila |
DRS Ped VIP (drs_pedvip) | Não — só para vender peds |
#Instalação
- Coloca
drs_donationsemresources. - Adiciona ao
server.cfg, depois do core, do inventário e dooxmysql:
ensure oxmysql
ensure qb-core
ensure qb-inventory
ensure drs_donations- As tabelas criam-se sozinhas no primeiro arranque (também tens
sql/drs_donations.sql). - Em
config.luaconfirma o nome do core (Config.Core), do inventário (Config.Inventory) e a garagem (Config.DefaultGarage). - Em
server/config_server.luapõe o webhook das compras (Webhook) e o dos pedidos à staff (RequestWebhook).
#Tebex
Em cada pacote de coins do Tebex, adiciona este comando de servidor e marca "executar mesmo com o jogador offline":
drs_coins {"transid":"{transaction}","coins":"1000"}O jogador recebe o ID da transação (tbx-...) por email e resgata-o na loja, em Início > Resgatar código. Cada código só dá uma vez.
Nota
O comando antigo purchase_donate_credit {"transid":"{transaction}","credit":"1000"} também funciona, para quem vem de outra loja.
Os pacotes que aparecem na loja (coins, bónus, preço, imagem e link) estão em Config.Packs.
#Produtos
Cada produto em Config.Products tem um id (não mudar depois de haver compras), uma category, o price em coins e as rewards. Um produto pode ter várias recompensas (pacotes).
{ id = 'starter', category = 'bundles', label = 'Pacote Iniciante', price = 900, oldPrice = 1350, tag = 'SÓ 1 VEZ', limit = 1, featured = true,
rewards = {
{ type = 'vehicle', model = 'jugular' },
{ type = 'item', name = 'weapon_heavypistol', amount = 1 },
{ type = 'money', account = 'bank', amount = 150000 },
} },| Recompensa | O que dá |
|---|---|
{ type = 'vehicle', model = 'krieger' } | Veículo na garagem (Config.DefaultGarage) |
{ type = 'item', name = 'weapon_pistol', amount = 1 } | Item ou arma no inventário |
{ type = 'money', account = 'bank', amount = 100000 } | Dinheiro: bank, cash ou crypto |
{ type = 'house', name = 'lake vinewood est1' } | Casa do qb-houses com as chaves (nome da tabela houselocations) |
{ type = 'ped', model = 'player_one' } | Ped VIP para sempre (precisa do drs_pedvip) |
{ type = 'priority', tier = 'VIP', power = 35 } | Prioridade na fila do connectqueue |
{ type = 'request', text = 'VIP OURO' } | Pedido à staff no Discord (para o que é ativado à mão) |
{ type = 'plate' } | Matrícula personalizada no carro onde o jogador está |
{ type = 'phone' } | Número de telemóvel à escolha (random = true: número novo ao calhas) |
{ type = 'keys', amount = 5 } / { type = 'skin', id = '...' } | Chaves e skins das caixas da Base DRS PVP |
Opções de cada produto:
oldPrice: preço riscado (promoção);tag: etiqueta (NOVO, HOT…);featured: aparece no Início;limit: máximo de compras por jogador;availableUntil = 'AAAA-MM-DD': sai da loja depois desse dia;plateOption: preço extra para escolher a matrícula ao comprar um veículo;label,desceimage: nos veículos vêm do core e da galeria do FiveM; nos itens, do inventário.
As casas saem da loja quando já têm dono. Os peds também podem vir da base de dados: liga Config.PedCatalog e a loja lê a tabela vip_ped_catalog (name, price, enabled).
#Integrações por tabela
Para ligar a loja a outros scripts há mais três recompensas, que só escrevem numa tabela (o teu script tem de a ler):
{ type = 'charslots', amount = 1 }→vip_character_slots(personagens extra no multicharacter);{ type = 'housestash', slots = 10, weight = 200000 }→vip_house_stash_upgrade(baú da casa onde o jogador está);{ type = 'salary', amount = 2000, days = 30 }→vip_salaryevip_players(salário VIP).
#Segurança
- O servidor decide tudo: preço, saldo, limites e o que se recebe. A interface só pede.
- O saldo desconta numa única operação: duas compras ao mesmo tempo não passam do saldo.
- Se uma recompensa falhar (inventário cheio, casa já vendida, ped que não aplica), as coins voltam e nada fica dado a meio.
- Os códigos têm proteção contra quem tenta adivinhar (
ServerConfig.RedeemTries). - O dinheiro passa pelo anti-cheat do servidor quando existe (
drake_guard).
#Comandos de staff (ACE)
| Comando | O que faz | |
|---|---|---|
/darcoins <id> <n> · /tirarcoins <id> <n> | Dar ou tirar coins | |
/vercoins <id> | Ver o saldo de um jogador | |
| `/criarcodigocoins <CODIGO\ | auto> <coins> [usos] [dias]` | Código para eventos (0 usos = sem limite) |
/apagarcodigocoins <codigo> | Apagar um código |
add_ace group.admin command.darcoins allow
add_ace group.admin command.tirarcoins allow
add_ace group.admin command.vercoins allow
add_ace group.admin command.criarcodigocoins allow
add_ace group.admin command.apagarcodigocoins allow#Para programadores
exports.drs_donations:GetCoins(source)
exports.drs_donations:AddCoins(source, 500, 'Evento de Halloween')
exports.drs_donations:RemoveCoins(source, 100)exports.drs_donations:Open() -- abre a loja (ex.: a partir de outro menu)#Configuração completa
-- =====================================================================
-- DRS DONATIONS: loja VIP com moeda própria (DRS Coins)
-- Os jogadores compram coins no Tebex, resgatam o código da compra na loja (F4) e gastam as coins em veículos,
-- casas, peds, VIPs, prioridade na fila, armas, dinheiro, matrículas e números de telemóvel.
-- O webhook do Discord fica em server/config_server.lua (não é enviado aos jogadores).
-- =====================================================================
Config = {}
Config.Core = 'qb-core' -- nome do recurso do core (QBCore; no Qbox também 'qb-core')
Config.Inventory = 'qb-inventory' -- inventário (AddItem / RemoveItem e imagens dos itens)
Config.Command = 'loja' -- /loja abre a loja (também /donate)
Config.Key = 'F4' -- tecla da loja (cada jogador pode mudar em Definições > Teclas)
Config.ServerName = 'DRS' -- aparece no topo da loja
Config.Currency = { name = 'DRS Coins', short = 'DC' }
Config.PerAccount = true -- true = as coins são da conta (licença, todas as personagens); false = de cada personagem
Config.DefaultGarage = 'pillboxgarage' -- garagem para onde vão os veículos comprados
Config.Phone = 'qb-phone' -- o número fica em charinfo.phone (e no drs_phone, se o usares)
Config.KeysEvent = 'vehiclekeys:client:SetOwner' -- evento que dá as chaves da matrícula nova
Config.TebexUrl = 'https://drs.tebex.io' -- botão "Loja Tebex" (os pacotes abaixo têm o link de cada um)
-- oferecer coins a outro jogador (em jogo, pelo ID)
Config.Gifts = { enabled = true, min = 50, cooldown = 30 } -- cooldown em segundos
-- matrícula e número de telemóvel personalizados
Config.Plate = { min = 2, max = 8 } -- letras / números (A-Z, 0-9 e espaço)
Config.PhoneNumber = { min = 7, max = 9 } -- só números
-- ------------------------------------------------------------------ pacotes de coins (Tebex)
-- No Tebex, em cada pacote, adiciona o comando de servidor (marca "executar mesmo com o jogador offline"):
-- drs_coins {"transid":"{transaction}","coins":"1000"}
-- O jogador recebe o ID da transação (tbx-...) por email e resgata-o na loja (Início > Resgatar código).
-- (O comando antigo purchase_donate_credit {"transid":"{transaction}","credit":"1000"} continua a funcionar.)
Config.Packs = {
{ coins = 500, bonus = 0, price = '5€', image = 'img/pack1.png', url = 'https://drs.tebex.io' },
{ coins = 1000, bonus = 100, price = '10€', image = 'img/pack2.png', url = 'https://drs.tebex.io', tag = 'POPULAR' },
{ coins = 2500, bonus = 400, price = '25€', image = 'img/pack3.png', url = 'https://drs.tebex.io' },
{ coins = 5000, bonus = 1250, price = '50€', image = 'img/pack4.png', url = 'https://drs.tebex.io', tag = 'MELHOR VALOR' },
}
-- ------------------------------------------------------------------ categorias (pela ordem do menu)
Config.Categories = {
{ id = 'vehicles', label = 'Veículos', icon = 'fa-car-side' },
{ id = 'houses', label = 'Casas', icon = 'fa-house' },
{ id = 'peds', label = 'Personagens', icon = 'fa-user-astronaut' },
{ id = 'bundles', label = 'Pacotes VIP', icon = 'fa-crown' },
{ id = 'air', label = 'Aéreos', icon = 'fa-helicopter' },
{ id = 'weapons', label = 'Armas', icon = 'fa-gun' },
{ id = 'money', label = 'Dinheiro', icon = 'fa-sack-dollar' },
{ id = 'cases', label = 'Caixas', icon = 'fa-box-open' },
{ id = 'extras', label = 'Personalizar', icon = 'fa-wand-magic-sparkles' },
}
-- ------------------------------------------------------------------ produtos
-- id único (não mudar depois de haver compras) · category · price em coins
-- label / desc / image: opcionais nos veículos (vêm do drs-core e da galeria do FiveM)
-- oldPrice = preço riscado (promoção) · tag = etiqueta (NOVO, HOT...) · featured = aparece no Início
-- limit = máximo de compras por conta (nil = sem limite)
-- rewards = o que recebe:
-- { type = 'vehicle', model = 'krieger' } veículo na garagem (Config.DefaultGarage)
-- { type = 'item', name = 'weapon_pistol', amount = 1 } item / arma no inventário
-- { type = 'money', account = 'bank', amount = 100000 }
-- { type = 'keys', amount = 5 } chaves das caixas da Base DRS PVP (drs_pvp)
-- { type = 'skin', id = 'pistol_luxe' } skin de arma da Base DRS PVP (ids no drs_pvp/config_cases.lua)
-- { type = 'plate' } matrícula personalizada no carro onde está (tem de ser dele)
-- { type = 'phone' } número de telemóvel personalizado ({ type = 'phone', random = true } = número novo ao calhas)
-- { type = 'house', name = 'nome em houselocations' } casa do qb-houses (sai da loja quando tem dono)
-- { type = 'ped', model = 'nome do ped' } ped VIP para sempre (precisa do DRS Ped VIP: drs_pedvip)
-- { type = 'priority', tier = 'VIP', power = 35 } prioridade na fila do connectqueue (fica guardada)
-- { type = 'request', text = 'VIP OURO' } a staff ativa à mão: chega um pedido ao Discord (ServerConfig.RequestWebhook)
-- Integrações (o teu script tem de ler estas tabelas): { type = 'charslots', amount = 1 } -> vip_character_slots,
-- { type = 'housestash', slots = 10, weight = 200000 } -> vip_house_stash_upgrade (casa onde o jogador está),
-- { type = 'salary', amount = 2000, days = 30 } -> vip_salary / vip_players
-- availableUntil = 'AAAA-MM-DD' -> o produto sai da loja depois desse dia (edições limitadas)
-- plateOption = preço extra para escolher a matrícula ao comprar um veículo (nil = não dá)
Config.Products = {
-- veículos
{ id = 'krieger', category = 'vehicles', price = 2400, featured = true, tag = 'HOT', plateOption = 150, rewards = { { type = 'vehicle', model = 'krieger' } } },
{ id = 'emerus', category = 'vehicles', price = 2200, plateOption = 150, rewards = { { type = 'vehicle', model = 'emerus' } } },
{ id = 'zentorno', category = 'vehicles', price = 1800, oldPrice = 2200, image = 'img/zentorno.png', plateOption = 150, rewards = { { type = 'vehicle', model = 'zentorno' } } },
{ id = 't20', category = 'vehicles', price = 1900, plateOption = 150, rewards = { { type = 'vehicle', model = 't20' } } },
{ id = 'deveste', category = 'vehicles', price = 2600, tag = 'NOVO', plateOption = 150, rewards = { { type = 'vehicle', model = 'deveste' } } },
{ id = 'italigto', category = 'vehicles', price = 1500, plateOption = 150, rewards = { { type = 'vehicle', model = 'italigto' } } },
{ id = 'jugular', category = 'vehicles', price = 1200, plateOption = 150, rewards = { { type = 'vehicle', model = 'jugular' } } },
{ id = 'revolter', category = 'vehicles', price = 1100, plateOption = 150, rewards = { { type = 'vehicle', model = 'revolter' } } },
{ id = 'toros', category = 'vehicles', price = 1300, plateOption = 150, rewards = { { type = 'vehicle', model = 'toros' } } },
{ id = 'shotaro', category = 'vehicles', price = 900, plateOption = 150, rewards = { { type = 'vehicle', model = 'shotaro' } } },
{ id = 'hakuchou2', category = 'vehicles', price = 800, plateOption = 150, rewards = { { type = 'vehicle', model = 'hakuchou2' } } },
-- aéreos
{ id = 'supervolito', category = 'air', price = 3500, featured = true, rewards = { { type = 'vehicle', model = 'supervolito' } } },
{ id = 'frogger', category = 'air', price = 2800, image = 'img/heli.png', rewards = { { type = 'vehicle', model = 'frogger' } } },
{ id = 'swift', category = 'air', price = 3200, rewards = { { type = 'vehicle', model = 'swift' } } },
{ id = 'volatus', category = 'air', price = 4000, tag = 'LUXO', rewards = { { type = 'vehicle', model = 'volatus' } } },
-- armas (itens do inventário)
{ id = 'w_pistol50', category = 'weapons', price = 350, rewards = { { type = 'item', name = 'weapon_pistol50', amount = 1 } } },
{ id = 'w_heavypistol', category = 'weapons', price = 300, rewards = { { type = 'item', name = 'weapon_heavypistol', amount = 1 } } },
{ id = 'w_smg', category = 'weapons', price = 600, rewards = { { type = 'item', name = 'weapon_smg', amount = 1 } } },
{ id = 'w_carbine', category = 'weapons', price = 900, rewards = { { type = 'item', name = 'weapon_carbinerifle', amount = 1 } } },
{ id = 'w_ak', category = 'weapons', price = 950, tag = 'HOT', rewards = { { type = 'item', name = 'weapon_assaultrifle', amount = 1 } } },
{ id = 'w_special', category = 'weapons', price = 1000, rewards = { { type = 'item', name = 'weapon_specialcarbine', amount = 1 } } },
{ id = 'w_pump', category = 'weapons', price = 500, rewards = { { type = 'item', name = 'weapon_pumpshotgun', amount = 1 } } },
{ id = 'w_sniper', category = 'weapons', price = 1200, rewards = { { type = 'item', name = 'weapon_sniperrifle', amount = 1 } } },
-- dinheiro (no banco)
{ id = 'money_100k', category = 'money', label = '$100.000', price = 100, image = 'img/money.png', rewards = { { type = 'money', account = 'bank', amount = 100000 } } },
{ id = 'money_250k', category = 'money', label = '$250.000', price = 240, image = 'img/money.png', rewards = { { type = 'money', account = 'bank', amount = 250000 } } },
{ id = 'money_500k', category = 'money', label = '$500.000', price = 450, image = 'img/money.png', rewards = { { type = 'money', account = 'bank', amount = 500000 } } },
{ id = 'money_1m', category = 'money', label = '$1.000.000', price = 850, oldPrice = 1000, image = 'img/money.png', tag = 'POUPA 15%', rewards = { { type = 'money', account = 'bank', amount = 1000000 } } },
-- chaves e skins das caixas (Base DRS PVP)
{ id = 'keys_5', category = 'cases', label = '5 Chaves', desc = 'Abre 5 caixas à escolha no F5 > Caixas.', price = 200, image = 'img/classica.png', rewards = { { type = 'keys', amount = 5 } } },
{ id = 'keys_15', category = 'cases', label = '15 Chaves', desc = 'Abre 15 caixas à escolha no F5 > Caixas.', price = 550, image = 'img/luxo.png', tag = '+1 GRÁTIS', rewards = { { type = 'keys', amount = 15 } } },
{ id = 'keys_40', category = 'cases', label = '40 Chaves', desc = 'Abre 40 caixas à escolha no F5 > Caixas.', price = 1300, oldPrice = 1600, image = 'img/caveira.png', featured = true, rewards = { { type = 'keys', amount = 40 } } },
{ id = 'skin_ak_caveira', category = 'cases', label = 'AK Mk II | Caveira', desc = 'A skin mais rara da Caixa Caveira, sem sorte nenhuma.', price = 900, tag = 'DOURADA', limit = 1,
weapon = 'weapon_assaultrifle_mk2', rewards = { { type = 'skin', id = 'assaultrifle_mk2_camo_04' } } },
{ id = 'skin_pistol_luxe', category = 'cases', label = 'Pistola | Yusuf Amir', desc = 'Acabamento de luxo dourado para a pistola.', price = 400, limit = 1,
weapon = 'weapon_pistol', rewards = { { type = 'skin', id = 'pistol_luxe' } } },
-- pacotes (várias coisas juntas)
{ id = 'starter', category = 'bundles', label = 'Pacote Iniciante', desc = 'Para começar bem: carro, pistola, $150.000 e 5 chaves.', price = 900, oldPrice = 1350,
image = 'img/hero.png', tag = 'SÓ 1 VEZ', limit = 1, featured = true,
rewards = { { type = 'vehicle', model = 'jugular' }, { type = 'item', name = 'weapon_heavypistol', amount = 1 },
{ type = 'money', account = 'bank', amount = 150000 }, { type = 'keys', amount = 5 } } },
{ id = 'gangster', category = 'bundles', label = 'Pacote Gangster', desc = 'AK, SMG, Revolter e 10 chaves.', price = 2300, oldPrice = 2950, image = 'img/redeem.png',
rewards = { { type = 'vehicle', model = 'revolter' }, { type = 'item', name = 'weapon_assaultrifle', amount = 1 },
{ type = 'item', name = 'weapon_smg', amount = 1 }, { type = 'keys', amount = 10 } } },
-- pacotes VIP (a staff ativa: chega um pedido ao Discord)
{ id = 'vip_bronze', category = 'bundles', label = 'VIP Bronze', desc = 'A staff recebe o pedido no Discord e ativa o teu VIP.', price = 600, image = 'img/gen/vip_bronze.png', rewards = { { type = 'request', text = 'VIP BRONZE' } } },
{ id = 'vip_silver', category = 'bundles', label = 'VIP Prata', desc = 'A staff recebe o pedido no Discord e ativa o teu VIP.', price = 1000, image = 'img/gen/vip_silver.png', rewards = { { type = 'request', text = 'VIP PRATA' } } },
{ id = 'vip_gold', category = 'bundles', label = 'VIP Ouro', desc = 'A staff recebe o pedido no Discord e ativa o teu VIP.', price = 1800, image = 'img/gen/vip_gold.png', tag = 'POPULAR', featured = true, rewards = { { type = 'request', text = 'VIP OURO' } } },
{ id = 'vip_diamond', category = 'bundles', label = 'VIP Diamante', desc = 'A staff recebe o pedido no Discord e ativa o teu VIP.', price = 3000, image = 'img/gen/vip_diamond.png', rewards = { { type = 'request', text = 'VIP DIAMANTE' } } },
-- casas do qb-houses (o nome é o da tabela houselocations; sai da loja quando tem dono)
-- { id = 'house_vinewood', category = 'houses', label = 'Mansão Vinewood', price = 2500, image = 'img/gen/house.png', rewards = { { type = 'house', name = 'lake vinewood est1' } } },
-- peds VIP (precisa do drs_pedvip; ou liga Config.PedCatalog para ler a tabela vip_ped_catalog)
-- { id = 'ped_franklin', category = 'peds', label = 'Franklin', price = 800, image = 'img/gen/ped.png', limit = 1, rewards = { { type = 'ped', model = 'player_one' } } },
-- extras
{ id = 'priority', category = 'extras', label = 'Prioridade na fila', desc = 'Entras primeiro na fila do servidor (fica guardado para sempre).', price = 500, image = 'img/gen/priority.png', limit = 1,
rewards = { { type = 'priority', tier = 'VIP', power = 35 } } },
{ id = 'crypto_pack', category = 'money', label = 'Pack Crypto', desc = '10 de crypto na tua carteira.', price = 300, image = 'img/gen/crypto.png', rewards = { { type = 'money', account = 'crypto', amount = 10 } } },
{ id = 'phone_new', category = 'extras', label = 'Número novo', desc = 'Recebes um número de telemóvel novo ao calhas.', price = 100, image = 'img/gen/phone_new.png', rewards = { { type = 'phone', random = true } } },
{ id = 'plate', category = 'extras', label = 'Matrícula personalizada', desc = 'Muda a matrícula do carro onde estás (tem de ser teu). As chaves passam para a matrícula nova.',
price = 150, image = 'img/plate.png', rewards = { { type = 'plate' } } },
{ id = 'phone', category = 'extras', label = 'Número de telemóvel', desc = 'Escolhe o teu número. Fica ativo depois de voltares a entrar no servidor.',
price = 200, image = 'img/phone.png', rewards = { { type = 'phone' } } },
}
-- peds VIP lidos da base de dados (tabela vip_ped_catalog: name, price, enabled) e mostrados em "Personagens"
Config.PedCatalog = { enabled = false, category = 'peds', image = 'img/gen/ped.png' }#Configuração do servidor (webhooks)
-- Só no servidor (os jogadores não recebem este ficheiro)
ServerConfig = {
Webhook = '', -- webhook do Discord para as compras, códigos e coins dados pela staff ('' = desligado)
WebhookName = 'DRS Donations',
RequestWebhook = '', -- pedidos dos produtos que a staff ativa à mão (VIPs, org...); '' = usa o Webhook
MaxCoinsPerCommand = 100000, -- limite de segurança do /darcoins e do comando do Tebex
RedeemTries = { perMinute = 5, blockAfter = 10, blockSeconds = 300 }, -- proteção contra quem tenta adivinhar códigos
}