qb-jewelery
qb-jewelery is the jewelry store robbery. Players smash display cases, each case grants one item
drawn from a weighted table, and the whole store locks out for a configured timeout afterwards.
Three spellings, and which one to use where
This resource is spelled inconsistently upstream. All three forms are correct in their own place:
| Context | Spelling |
|---|---|
| Repository and resource folder name | qb-jewelery, one l |
| Every event, callback, and namespace inside the code | qb-jewellery:, two l |
The official documentation page and the qb-scoreboard activity key | qb-jewelry and jewellery respectively |
Use qb-jewelery when cloning and in server.cfg. Use qb-jewellery: when writing an event
handler. The activity key that qb-scoreboard expects is
jewellery, which is what this resource sends.
The manifest at the commit linked above declares version 1.5.0.
There is no qbcore-fivem/qb-jewelry repository. A link using that spelling will 404.
Dependencies and start order
From the manifest:
- Shared:
@qb-core/shared/locale.lua,locale/*.lua,config.lua - Client:
@PolyZone/client.lua,@PolyZone/BoxZone.lua,client.lua - Server:
@oxmysql/lib/MySQL.lua,server.lua
From the implementation, additionally: qb-core exports,
qb-inventory for AddItem and the item box, and qb-log for the
ban log entry.
Start order: oxmysql, qb-core, PolyZone, qb-inventory, then qb-jewelery.
Configuration
| Key | Default | Meaning |
|---|---|---|
Config.UseTarget | GetConvar('UseTarget', 'false') == 'true' | Set setr UseTarget true in server.cfg rather than editing the file |
Config.Timeout | 30 * (60 * 2000) | Milliseconds before display cases reset. The shipped expression evaluates to 3,600,000 ms, which is 60 minutes, not the 30 the shape of the expression suggests |
Config.RequiredCops | 2 | On-duty law enforcement required |
Config.JewelleryLocation | table | Store position |
Config.WhitelistedWeapons | table | Weapons that can break a case |
Config.VitrineRewards | table | Item pool with per-entry probability and amount.min / amount.max |
Config.Locations | table | Individual display cases with coords, isOpened, isBusy |
Duty counting here accepts job.name == 'police' or job.type == 'leo', so custom law enforcement
jobs count as long as their type is leo.
Persistence
This resource has no table of its own and ships no SQL file, but it writes to the bans table:
INSERT INTO bans (name, license, discord, ip, reason, expire, bannedby) VALUES (?, ?, ?, ?, ?, ?, ?)That table comes from your framework or admin stack, not from this resource. Confirm it exists with
those seven columns before enabling the resource, otherwise the ban path errors instead of banning.
Bans are written with expire = 2147483647 and bannedby = 'qb-jewelery'.
Server surface
| Handler | Kind | Arguments |
|---|---|---|
qb-jewellery:server:getCops | callback | none, returns the on-duty count and caches it per source |
qb-jewellery:server:getVitrineState | callback | none, returns Config.Locations |
qb-jewellery:server:vitrineReward | net event | vitrineIndex |
qb-jewellery:server:setVitrineState | net event | stateType, state, k |
qb-jewellery:server:setTimeout | net event | none |
Reward validation
vitrineReward is unusually aggressive compared to the other robbery resources. At this commit it:
- Permanently bans the source if the index is unknown or the case is already opened.
- Drops the source if
getCopswas never called first, because the cached police count is nil. - Requires the cached police count to be at least
Config.RequiredCops. - Requires the player to be within 25.0 units of the case coordinates.
Failures of steps 3 and 4 increment a per-license flag counter and drop the player. At three flags it escalates to a permanent ban.
Two consequences to plan for:
- The police count is whatever the client last caused to be cached through the callback. A player who calls the callback while enough officers are on duty, then robs after they log off, passes the check.
- Step 1 is a ban, not a kick. A legitimate desync where two players open the same case can ban the second one. Test this before going live on a busy server.
At this commit the escalation branch calls exploitBan with a single argument, so the value lands in
the function’s id parameter and reason is nil. That call path does not behave as the surrounding
code reads. Treat the three-flag escalation as untested upstream and rely on the earlier checks.
Timeout and scoreboard
setTimeout sets the store busy on qb-scoreboard under the key jewellery, waits
Config.Timeout, resets every case to not-opened, clears the alert, and marks the activity free
again. It guards against overlapping timers with a module-level timeOut flag, but the event itself
is a RegisterNetEvent with no source check, so any client can start the timeout.
Integration example
Reading the current case state from your own client resource:
-- client side
QBCore.Functions.TriggerCallback('qb-jewellery:server:getVitrineState', function(locations)
for index, case in pairs(locations) do
if not case.isOpened then
print(('case %s is still intact'):format(index))
end
end
end)Troubleshooting
| Symptom | Likely cause |
|---|---|
| Players banned during a normal robbery | Two players triggered the same case, or a case index arrived that is not in Config.Locations. Check the bans table for bannedby = 'qb-jewelery' |
| ”Exploiting” drops with no ban | The client never ran the getCops callback, so the cached count was nil |
| Server error on ban | The bans table is missing or has different columns |
| Cases never reset | setTimeout was never fired, or timeOut is still true from a prior run. Restarting the resource resets in-memory state |
| Scoreboard never shows jewellery as busy | qb-scoreboard is not started, or its Config.IllegalActions no longer has the jewellery key |
| Nothing happens when hitting a case | The equipped weapon is not in Config.WhitelistedWeapons |
Proposed manual smoke test
Not yet executed. On staging, and with a database backup:
- Confirm the
banstable exists with the seven columns listed above. - Rob one case with enough officers on duty and confirm the reward arrives.
- Confirm the case cannot be robbed twice, and check whether that path bans you.
- Confirm
qb-scoreboardshows jewellery busy, then free afterConfig.Timeout. - Review
bansafterwards and clear any test bans.