qb-hotdogjob
qb-hotdogjob is a stand-based selling job. The player pays a deposit for a hot dog stand, restocks
it, sells to NPCs, and builds a hotdog reputation stored in player metadata. Returning the stand
refunds the deposit.
It is one of the few QBCore job resources that writes to the reputation metadata rather than only granting money or items.
Before deploying it, read The sale price comes from the client. The payout event trusts a price argument sent by the client.
The manifest at the commit linked above declares version 1.5.0.
Dependencies and start order
From the manifest: @qb-core/shared/locale.lua and locales as shared scripts, PolyZone
client.lua, BoxZone.lua, EntityZone.lua, plus client.lua and server.lua, with
ui_page 'html/ui.html'.
From the implementation: qb-core for GetCoreObject({ 'Functions', 'Commands' }), player money,
item, and reputation methods, and exports['qb-core']:ExploitBan.
Start order: qb-core, PolyZone, then qb-hotdogjob. The hotdog item must exist in your shared
items list.
Configuration
| Key | Default | Meaning |
|---|---|---|
Config.UseTarget | from the UseTarget convar | Set setr UseTarget true in server.cfg |
Config.StandDeposit | 250 | Bank charge for the stand, refunded on return |
Config.MyLevel | 1 | Starting level reference |
Config.MaxReputation | 200 | Ceiling for the hotdog reputation |
Config.Locations | table | Where a stand can be collected and placed |
Config.Stock | table | Restock definitions |
Commands and key mapping
| Binding | Registered with | Notes |
|---|---|---|
letgostand | RegisterCommand plus RegisterKeyMapping(..., 'keyboard', 'G') | Client side, lets the player drop the stand. Players can rebind it in the FiveM settings menu |
/removestand | QBCore.Commands.Add(..., 'admin') | Server side, admin only, removes a stuck stand |
See Commands and key mappings for the difference between those two registration paths.
Server surface
| Handler | Kind | Arguments |
|---|---|---|
qb-hotdogjob:server:HasMoney | callback | none |
qb-hotdogjob:server:BringBack | callback | none |
qb-hotdogjob:server:Sell | net event | coords, amount, price |
qb-hotdogjob:server:UpdateReputation | net event | quality |
Deposit
HasMoney charges Config.StandDeposit from the bank and records the citizen ID in a server-side
Bail table. BringBack refunds the same amount if that entry exists. The table is in memory only,
so a server restart between taking and returning a stand loses the refund.
The sale price comes from the client
qb-hotdogjob:server:Sell is worth reading carefully, because it mixes good and bad practice in one
handler:
RegisterNetEvent('qb-hotdogjob:server:Sell', function(coords, amount, price)
local src = source
local pCoords = GetEntityCoords(GetPlayerPed(src))
...
if #(pCoords - coords) > 4 then exports['qb-core']:ExploitBan(src, 'hotdog job') end
local sellAmount = math.floor(tonumber(amount) or 0)
local sellPrice = tonumber(price) or 0
if sellAmount <= 0 or sellPrice <= 0 then return end
...
Player.AddMoney('cash', sellAmount * sellPrice, 'sold hotdog')
end)What it does right: it reads the ped position on the server, it verifies the player actually holds
enough hotdog items, and it only pays after RemoveItem succeeds.
What it does not do: it never compares price against anything. sellPrice is only checked for
being a positive number, so any positive value a client sends becomes the per-unit payout. The
amount is bounded by the player’s real inventory, but the price is not bounded at all.
Two further details:
coordsis the client’s claimed position, compared against the server-readpCoords. Because both endpoints come from opposite sides, this check does catch a naive teleport, but a client that forgescoordsto match its real position passes it.- The
ExploitBanbranch has noreturn, so execution continues into the sale on the same tick.
If you run this resource, clamp the price server side against your own table:
-- patched into server.lua
local HOTDOG_UNIT_PRICE = 12
Player.AddMoney('cash', sellAmount * HOTDOG_UNIT_PRICE, 'sold hotdog')Reputation
UpdateReputation accepts a quality string of exotic, rare, or common, granting 3, 2, or 1
reputation and clamping at Config.MaxReputation. It then sends the player’s full
PlayerData.metadata['rep'] back to the client. quality is unvalidated beyond the three string
comparisons, so a client can pick its own tier, but the per-call gain is capped at 3 and the total is
capped by Config.MaxReputation.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Deposit not refunded | The Bail table is in memory. A restart between taking and returning clears it |
| Players earning implausible amounts | The price is accepted from the client. Clamp it |
| Bans during normal selling | The claimed coords and the server-read position diverged by more than 4 units, which can happen on a laggy client |
| Stand stuck in the world | Use the admin /removestand command |
| Sale does nothing | The player does not hold enough hotdog items, or the item is missing from your shared items |
Proposed manual smoke test
Not yet executed. On staging:
- Take a stand, confirm the bank charge, return it, confirm the refund.
- Restart the server between those two steps and confirm the refund is lost.
- Sell with an empty inventory and confirm nothing is paid.
- Confirm reputation increases and stops at
Config.MaxReputation. - After clamping the price, confirm the payout matches your own table.