qb-recyclejob
qb-recyclejob is a warehouse loop. The player carries boxes to a drop point, receives crafting
materials, and can sell those materials back to an NPC at a fixed price list. It is the usual source
of metalscrap, plastic, copper, iron, aluminum, steel, and glass on a stock server.
It pairs naturally with qb-scrapyard, which produces the same material set from scrapping vehicles.
The manifest at the commit linked above declares version 2.2.0.
Dependencies and start order
From the manifest: @qb-core/shared/locale.lua and locales as shared scripts, then PolyZone
client.lua, BoxZone.lua, CircleZone.lua, plus client.lua and server.lua.
From the implementation: qb-core, qb-inventory for the item box,
qb-log for the ban log line, and a MySQL layer for the ban insert.
Start order: oxmysql, qb-core, qb-inventory, PolyZone, then qb-recyclejob.
Configuration
config.lua is a single table literal.
| Key | Default | Meaning |
|---|---|---|
UseTarget | from the UseTarget convar | Set setr UseTarget true in server.cfg |
OutsideLocation | vector4(55.55, 6472.18, 31.43, 44.0) | Entrance |
InsideLocation | vector4(1073.0, -3102.49, -39.0, 266.61) | Warehouse interior |
DutyLocation | vector4(1048.7, -3100.62, -38.2, 88.02) | Clock on and off |
DropLocation | vector4(1048.224, -3097.071, -38.999, 274.81) | Where boxes are turned in |
SellMaterials | true | Allow selling to the NPC |
LimitedMaterials | true | Enforce a finite material stock |
SellPed | vector4(1049.84, -3094.08, -40.0, 178.84) | Buyer NPC position |
DrawPackageLocationBlip | true | Blip on the current box |
PickupActionDuration | math.random(4000, 6000) | Evaluated once at load, so it is the same for every pickup until restart |
DeliveryActionDuration | 5000 | Milliseconds |
PickupBoxModel | 'prop_cs_cardbox_01' | Carried prop |
PickupLocations | 21 entries | Box spawn points. Each model field is also evaluated once at load |
WarehouseObjects | 7 entries | Box pile props |
PickupActionDuration and each model field use math.random at file load. They are fixed for the
lifetime of the resource, not rolled per action. Restart the resource to reshuffle them.
Stock and pricing
Stock and the price list live in server.lua, not in config.lua. At this commit each material
starts at 3000 units. Selling a material returns it to stock, and taking one removes it, so a busy
server drains and refills the same pool.
With LimitedMaterials = false, stock is ignored entirely and materials are unlimited.
To retune the economy, edit the Sales and stock tables in server.lua. Editing config.lua will
not change prices.
Server surface
| Handler | Kind | Arguments |
|---|---|---|
qb-recyclejob:server:getPriceList | callback | none, returns the sale price table |
qb-recyclejob:server:getItem | net event | none |
qb-recyclejob:server:sellItem | net event | item, amount |
Distance checks and escalation
Both money and item paths go through one helper:
local function isClose(source, loc)
-- reads the ped position on the server
-- 'turnIn' compares against the drop location
-- 'sell' compares against the sell location
-- returns true under 5.0 units
endPosition is read on the server, which is the right approach. On failure the helper increments a
per-citizen counter and permanently bans at three strikes, writing to the bans table with
bannedby = 'qb-recyclejob' and expire = 2147483647.
Two caveats:
- The
banstable is not created by this resource. Confirm it exists with the columnsname,license,discord,ip,reason,expire,bannedbybefore enabling. - The counter in
isCloseusesuhohs[cid] = uhohs[cid] + 1 or 0, which errors on the first failure for a citizen with no existing entry, because the addition is evaluated before theor. ThegetItemhandler works around this with its own nil check, thesellpath does not.
sellItem takes amount from the client but clamps it: the helper reduces amount to what the
player actually holds before pricing, and the price itself comes from the server-side table. A forged
amount therefore cannot inflate the payout.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| ”Out of stock” for a common material | LimitedMaterials is true and the pool is drained. It refills as players sell back |
| Server Lua error when selling from far away | The uhohs counter arithmetic, see above. Approach the sell point |
| Ban insert fails | The bans table is missing or has different columns |
| Every pickup takes the same time | PickupActionDuration is rolled once at load. Restart to reshuffle |
| Price changes have no effect | Prices are in server.lua, not config.lua |
| Materials never arrive | An item name in the receive table is missing from your shared items |
Proposed manual smoke test
Not yet executed. On staging, with a database backup:
- Confirm the
banstable exists before starting. - Run one full loop: pick up a box, turn it in, confirm materials arrive.
- Sell materials at the NPC and confirm cash matches the server-side price list.
- Attempt to sell from outside 5 units and record whether a ban or an error results.
- Set
LimitedMaterials = falseand confirm stock is ignored. - Clear any test bans afterwards.