This guide walks through designing and shipping a robust FiveM drug dealer script. Focus is on server authority, safe item/cash handling, target integration, and anti-exploit validation. Use the patterns below to build a maintainable, audit-ready resource.
Overview
Drug dealer mechanics are common in RP servers: players procure or produce illicit items and sell them to NPC dealers for cash. Because in-game illicit economies affect balance and invite exploits, the canonical rule is: trust nothing from the client. All checks that affect player inventory, cash, or police notifications must be authoritative on the server.
Design principles
- Server authority: validate items, quantities, and payments server-side.
- Modular integration: keep qb-target / ox_lib / radial menus as optional adapters.
- Config-driven behavior: store prices, locations, cooldowns, and police odds in a config file.
- Anti-exploit: rate-limits, ownership checks, and persistent logs for suspicious activity.
Assumptions and framework choices
This guide uses QBCore as the primary example because it's popular in FiveM RP communities. Where appropriate I'll note ESX alternatives. If your server uses ox_lib utilities or qb-target, the code shows how to add optional exports without hard dependencies.
High-level implementation plan
- Create a small resource with fxmanifest.lua, config.lua, client.lua and server.lua.
- Define dealer locations and a server-side sell event that validates items and pays the player.
- Support target interaction (qb-target) and a fallback keypress interaction.
- Add anti-exploit controls: cooldowns, item ownership checks, and server logging.
- Test with an admin account and run simulated abuse cases (rapid sell, forged item reports).
Key server-side rules
- Never accept client-provided item amounts or prices without server-side verification.
- Use framework inventory functions (QBCore.Functions.GetPlayer, QBCore.Functions.RemoveItem, AddMoney) rather than direct DB writes.
- Keep payment and police notifications atomic: only notify police after successful item removal and payment.
Example files (complete minimal resource)
Below are destination-labelled files for a minimal QBCore resource. Adjust names, folder location, and exports to match your server's conventions.
-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
author 'YourName'
description 'Simple drug dealer script (QBCore)'
version '1.0.0'
server_script 'server.lua'
client_script 'client.lua'
shared_script 'config.lua'
-- config.lua
Config = {}
Config.Dealers = {
{name = 'Downtown Dealer', coords = vector3( -1486.1, -378.2, 40.16 ), sell_item = 'weed', price = 200, policeChance = 0.05},
}
Config.SellCooldown = 5 -- seconds per player per dealer
-- server.lua (QBCore example)
local QBCore = exports['qb-core']:GetCoreObject()
local lastSell = {}
RegisterNetEvent('drugdealer:server:sell', function(dealerIndex)
local src = source
local xPlayer = QBCore.Functions.GetPlayer(src)
if not xPlayer then return end
local dealer = Config.Dealers[dealerIndex]
if not dealer then return end
-- rate limit
lastSell[src] = lastSell[src] or {}
if lastSell[src][dealerIndex] and (os.time() - lastSell[src][dealerIndex]) < Config.SellCooldown then
TriggerClientEvent('QBCore:Notify', src, 'You must wait before selling again', 'error')
return
end
-- check inventory server-side
local item = xPlayer.Functions.GetItemByName(dealer.sell_item)
if not item or item.amount < 1 then
TriggerClientEvent('QBCore:Notify', src, 'You have nothing to sell', 'error')
return
end
-- remove item and give money atomically
local removed = xPlayer.Functions.RemoveItem(dealer.sell_item, 1)
if not removed then
TriggerClientEvent('QBCore:Notify', src, 'Failed to remove item', 'error')
return
end
xPlayer.Functions.AddMoney('cash', dealer.price, 'sold-drugs')
lastSell[src][dealerIndex] = os.time()
TriggerClientEvent('QBCore:Notify', src, 'Sold 1 '..dealer.sell_item..' for $'..dealer.price, 'success')
-- optional police alert with validated chance
if math.random() < dealer.policeChance then
TriggerEvent('police:server:sendAlert', {x = dealer.coords.x, y = dealer.coords.y, z = dealer.coords.z, message = 'Suspicious deal'})
end
end)
-- client.lua (target integration example)
local QBCore = exports['qb-core']:GetCoreObject()
Citizen.CreateThread(function()
for i, d in ipairs(Config.Dealers) do
if exports['qb-target'] then
exports['qb-target']:AddBoxZone('dealer_'..i, d.coords, 1.2, 1.2, {name = 'dealer_'..i, heading = 0, debugPoly = false}, {
options = {{type = 'client', event = 'drugdealer:client:openMenu', icon = 'fas fa-user-secret', label = 'Talk to dealer', dealerIndex = i}},
distance = 2.5
})
end
end
end)
RegisterNetEvent('drugdealer:client:openMenu', function(data)
-- fallback: directly call server event
TriggerServerEvent('drugdealer:server:sell', data.dealerIndex)
end)
Security and anti-exploit measures
- Rate-limit sells per-player per-dealer to prevent rapid-grinding.
- Perform all inventory checks and removes on the server using framework APIs.
- Log unusual patterns to a secure server log: high-frequency sells, negative balances, or failed item removals.
- Don’t trust client-sent dealerIndex blindly — validate index exists in Config.Dealers.
ESX differences
For ESX, replace QBCore.Functions.GetPlayer and inventory calls with ESX.GetPlayerFromId and xPlayer.getInventoryItem / xPlayer.removeInventoryItem. Also use ESX.ShowNotification on the client where appropriate. Keep the server authority rule unchanged.
Testing and validation
Local testing should include:
- Sell flow with correct item and without item.
- Rapid sell attempts to validate cooldown behavior.
- Simulated packet tampering: call the server event with invalid dealerIndex or spoofed player IDs (as server owner) to ensure checks block it.
Deployment checklist
| Task | Why | Done? |
|---|---|---|
| Use framework APIs for inventory/cash | Server authority | |
| Add cooldowns & rate-limits | Prevent rapid abuse | |
| Validate dealerIndex & item existence | Prevent null deref / exploits | |
| Log suspicious events | Audit & ban support | |
| Test with ESX/QBCore and qb-target/ox_lib | Compatibility |
Next steps and resources
To iterate quickly, consider using a planning workspace that produces destination-labelled files and installs quickly. If you want a guided generator to produce full resource files, try the Stellar AI app to prototype variations and generate complete file sets: https://trystellarai.com/app. When you're iterating on police notification logic or balancing payout rates, keep the configuration external so you can tweak values without changing logic.
For community guides and deeper patterns (hooking into evidence systems, laundering mechanics, or vendor cooldown persistence), read articles and collections from the Stellar blog for recommended patterns: https://trystellarai.com/blog. You can also open the Stellar AI app to prototype alternate dealer behaviors and test multiple configurations quickly: https://trystellarai.com/app.
Closing notes
Keep social features (informing players, leaving hints) client-side only as UI, and keep all economic changes server-side. If you later add production chains (grow -> process -> sell), maintain the same atomic, validated operations at each step. That contract—server trust, client UI—keeps your server balanced and more resistant to exploits.