Skip to Content
Resourcesqb-storerobbery Reference

qb-storerobbery

qb-storerobbery handles small shop robberies: cash registers and the back-room safe. Registers give markedbills and sometimes a sticky note carrying the safe code. Safes give more markedbills plus a chance at rolex and goldbar.

It covers convenience stores only. Banks are in qb-bankrobbery, jewelry in qb-jewelery, residential in qb-houserobbery.

The manifest at the commit linked above declares version 1.5.0.

Dependencies and start order

The manifest declares no dependency entry and loads @qb-core/shared/locale.lua as a shared script. The implementation goes further than the manifest admits. At this commit the server script calls:

  • exports['qb-core']:GetCoreObject, GetPlayer, and GetShared('Items')
  • exports['qb-inventory']:AddItem and RemoveItem
  • TriggerClientEvent('qb-inventory:client:ItemBox', ...)
  • TriggerClientEvent('qb-phone:client:addPoliceAlert', ...)
  • Lang:t(...) from the core locale helper

So the real dependency chain is qb-core, qb-inventory, and qb-phone for the alert. Start those first. If you run a different phone resource, the alert call is a no-op and the police are never notified.

Configuration

config.lua is a shared script.

KeyDefaultMeaning
Config.minEarn100Lower bound used to seed Config.RegisterEarnings
Config.maxEarn450Upper bound for the same
Config.RegisterEarningsmath.random(minEarn, maxEarn)Evaluated once at load, not per robbery
Config.MinimumStoreRobberyPolice2On-duty police required
Config.resetTime(60 * 1000) * 30Register cooldown in milliseconds, 30 minutes
Config.tickInterval1000Server cooldown tick in milliseconds
Config.stickyNoteChance10Percent chance a register also drops the safe code
Config.RegisterstableRegister locations with their safeKey and camId
Config.SafestableSafe locations, their type, and camId

The actual payout is not driven by Config.RegisterEarnings. server.lua carries its own local cashA = 250 and cashB = 450 and uses those for the markedbills worth. Editing the config values changes nothing about what players receive. Edit the locals in server.lua as well, or your economy tuning will silently have no effect.

Safe codes are regenerated server side every 40 minutes. Keypad safes get a four digit code, dial safes get an array of angles that the client converts to numbers.

Server events and callbacks

HandlerKindArgumentsValidation at this commit
qb-storerobbery:server:takeMoneynet eventregister, isDoneDistance to the register above 3.0, or an inconsistent robbed state, ends in DropPlayer
qb-storerobbery:server:SafeRewardnet eventsafeDistance above 3.0, or an already robbed safe, ends in DropPlayer
qb-storerobbery:server:setRegisterStatusnet eventregisterNone
qb-storerobbery:server:setSafeStatusnet eventsafeNone
qb-storerobbery:server:callCopsnet eventtype, safe, streetLabel, coordsNone
qb-storerobbery:server:removeAdvancedLockpicknet eventnonePlayer must exist
qb-storerobbery:server:removeLockpicknet eventnonePlayer must exist
qb-storerobbery:server:isCombinationRightcallbacksafeReturns the code
qb-storerobbery:server:getPadlockCombinationcallbacksafeReturns the code
qb-storerobbery:server:getRegisterStatuscallbacknoneReturns Config.Registers
qb-storerobbery:server:getSafeStatuscallbacknoneReturns Config.Safes

Server-side validation pattern

The two reward events are worth reading as a template, because they show the minimum a money-granting event should do. Simplified from server.lua at this commit:

RegisterNetEvent('qb-storerobbery:server:SafeReward', function(safe) local src = source local Player = exports['qb-core']:GetPlayer(src) if not Player then return end local playerCoords = GetEntityCoords(GetPlayerPed(src)) if #(playerCoords - Config.Safes[safe][1].xyz) > 3.0 or Config.Safes[safe].robbed then return DropPlayer(src, 'Attempted exploit abuse') end -- only now grant the reward end)

Three properties make this safe enough to copy:

  1. source is captured on the server, never taken from an argument.
  2. Position is read from the server’s own view of the ped, not sent by the client.
  3. The reward amount comes from server-side constants, not from a client argument.

Contrast this with qb-pawnshop and qb-hotdogjob, where the price is accepted from the client.

The general form of these three properties, and the review questions that go with them, are in Safe server events and the server-authority checklist.

Events that are not validated

setRegisterStatus, setSafeStatus, and callCops are registered with RegisterNetEvent and perform no source or state checks at this commit. A client can mark any register or safe robbed for everyone, or trigger a police alert at arbitrary coordinates. Neither grants items, so the impact is griefing rather than economy damage, but plan for it if you run a public server.

The two safe-code callbacks return the current code to whichever client asks. The code reaches the client in order for the minigame to check it locally, so the code is not a secret from a modified client.

Troubleshooting

SymptomLikely cause
Players are dropped with “Attempted exploit abuse” during normal playThe distance check uses 3.0 units against the configured register coordinate. A misplaced coordinate, or a shell-based interior, puts legitimate players outside it
Payout does not match your configThe payout uses cashA and cashB in server.lua, not Config.minEarn and Config.maxEarn
No police alertqb-phone:client:addPoliceAlert has no listener because you run a different phone resource
Rewards do not arrivemarkedbills, stickynote, rolex, or goldbar is missing from your shared items
Registers never resetThe cooldown thread runs on Config.tickInterval. If the resource was restarted, in-memory robbed state resets to the file defaults instead

Proposed manual smoke test

Not yet executed. On staging:

  1. Rob one register and confirm markedbills arrives with a sensible worth.
  2. Repeat until the sticky note drops, then confirm the code opens the matching safe.
  3. Stand outside 3.0 units and fire the reward event manually, confirm the drop happens.
  4. Confirm the register resets after Config.resetTime.
  5. Confirm the police alert reaches an on-duty officer.

Sources