Description
A showroom dealership for QBCore servers: a player previews a model (a frozen, locked display car), confirms purchase, the server validates funds and charges the bank, the vehicle is written to player_vehicles, and a real owned car with a generated plate is delivered. This is the economy backbone of any roleplay server's vehicle gameplay.
Prompt Template
You are writing a FiveM resource for QBCore. Produce ONE Lua file with clearly
separated client and server sections (banner comments). Use
`exports['qb-core']:GetCoreObject()` on BOTH sides.
Client:
- A preview spawn function: RequestModel + `while not HasModelLoaded do Wait(0) end`,
CreateVehicle at a fixed showroom vector4 [PREVIEW_COORDS], then
FreezeEntityPosition(veh,true) and SetVehicleDoorsLocked(veh,2). Delete any prior
preview before spawning a new one.
- RegisterNetEvent "dealership:preview" (model) and "dealership:buy"
(model, price) — buy only TriggerServerEvent("dealership:purchase", model, price).
- RegisterNetEvent "dealership:deliver" (model, plate) that spawns the real car
under the player, SetVehicleNumberPlateText, seats them, deletes the preview.
Server:
- RegisterNetEvent "dealership:purchase": resolve Player via
QBCore.Functions.GetPlayer(source); reject if nil. Check money.bank >= price
SERVER-SIDE (never trust the client). RemoveMoney("bank", price, "vehicle-purchase").
- Generate a plate, INSERT into player_vehicles (citizenid, vehicle, plate, state).
- TriggerClientEvent("dealership:deliver", src, model, plate).
Requirements: all money logic server-side; price [PRICES per model]; showroom coords
[PREVIEW_COORDS]. Modern natives, no placeholders.
Expected Output
The reference Lua at content/expected-outputs/vehicles/02-car-dealership.lua implements the full preview → server-validated purchase → DB insert → delivery loop. The fxmanifest splits client_script 'client.lua' / server_script 'server.lua', depends on qb-core and oxmysql, and the money check + DB write live entirely server-side.
-- Resource: qb-dealership
-- Car dealership: spawn a preview vehicle, purchase it server-side, deliver to player.
-- Framework: QBCore. Client + server in one file (separated by banners).
-- ===== client.lua =====
local QBCore = exports['qb-core']:GetCoreObject()
local previewVeh = nil
local previewSpawn = vector4(-56.7, -1097.2, 26.4, 160.0)
local function spawnPreview(model)
local hash = GetHashKey(model)
RequestModel(hash)
while not HasModelLoaded(hash) do Wait(0) end
if previewVeh and DoesEntityExist(previewVeh) then
DeleteEntity(previewVeh)
end
previewVeh = CreateVehicle(hash, previewSpawn.x, previewSpawn.y, previewSpawn.z, previewSpawn.w, false, false)
SetEntityAsMissionEntity(previewVeh, true, true)
FreezeEntityPosition(previewVeh, true)
SetVehicleDoorsLocked(previewVeh, 2)
SetModelAsNoLongerNeeded(hash)
end
RegisterNetEvent("dealership:preview", function(model)
spawnPreview(model)
end)
RegisterNetEvent("dealership:buy", function(model, price)
TriggerServerEvent("dealership:purchase", model, price)
end)
RegisterNetEvent("dealership:deliver", function(model, plate)
local ped = PlayerPedId()
local hash = GetHashKey(model)
RequestModel(hash)
while not HasModelLoaded(hash) do Wait(0) end
local coords = GetEntityCoords(ped)
local veh = CreateVehicle(hash, coords.x, coords.y, coords.z, GetEntityHeading(ped), true, false)
SetVehicleNumberPlateText(veh, plate)
SetPedIntoVehicle(ped, veh, -1)
SetEntityAsMissionEntity(veh, true, true)
SetModelAsNoLongerNeeded(hash)
if previewVeh and DoesEntityExist(previewVeh) then DeleteEntity(previewVeh) end
QBCore.Functions.Notify("Vehicle delivered", "success")
end)
-- ===== server.lua =====
local QBCoreSv = exports['qb-core']:GetCoreObject()
RegisterNetEvent("dealership:purchase", function(model, price)
local src = source
local Player = QBCoreSv.Functions.GetPlayer(src)
if not Player then return end
if Player.PlayerData.money.bank < price then
TriggerClientEvent("QBCore:Notify", src, "Not enough money in bank", "error")
return
end
Player.Functions.RemoveMoney("bank", price, "vehicle-purchase")
local plate = ("QB%05d"):format(math.random(0, 99999))
MySQL.insert.await(
"INSERT INTO player_vehicles (citizenid, vehicle, plate, state) VALUES (?, ?, ?, ?)",
{ Player.PlayerData.citizenid, model, plate, 0 }
)
TriggerClientEvent("dealership:deliver", src, model, plate)
end)
Known Failure Modes
- Client-side money — Claude deducts cash in the client event, trivially exploitable. Require the balance check and
RemoveMoneyin the server event only. - No DB row — it delivers a car but never inserts into
player_vehicles, so the garage is empty. Mandate the INSERT. - Drivable preview — the showroom car isn't frozen/locked and gets stolen. Demand
FreezeEntityPosition+SetVehicleDoorsLocked(veh,2). - Plate collisions — naive plates duplicate; generate a unique formatted plate and rely on the DB to surface clashes.
Integration Notes
Split into client.lua and server.lua; fxmanifest.lua declares client_scripts and server_scripts (include @oxmysql/lib/MySQL.lua) and dependencies { 'qb-core', 'oxmysql' }. Assumes the standard QBCore player_vehicles table. Test with ensure then trigger dealership:preview and dealership:buy from a target/menu; confirm a row appears in player_vehicles.
Profit Potential
$250–$3800/mo on Tebex (expected ~$950). [INFERRED] A server-authoritative dealership is a core economy purchase with steady demand; it sits mid-to-high in the $50-389 band against the corpus median seller.
Trend Signal
↗ rising — vehicle modding/tuning niche-selection 3.75; corpus ox_fuel active.
Sales Angle
Position as the economy backbone every roleplay server needs: frozen preview, server-validated purchase, and DB-delivered ownership in one drop-in. Recommend $149 on Tebex, undercutting bloated all-in-one vehicle-shop bundles.
Difficulty & Ship Time
intermediate · ships in 1 day.