FiveM store and bank robbery scripts: setup and server-side design

How to set up and design store and bank robberies on FiveM: police count, server-side cooldowns, rewards given only by the server, police alerts and distance checks.

The symptom: players rob a store with no police online, the same store is robbed ten times in a row, or a cheater fires one event and gets the whole payout.

A robbery script is a small server-side state machine: check the rules, start, wait, pay. Most of the exploits come from putting those steps on the client. This article covers a safe design and the config mistakes that cause the problems above.

The flow you want

Think of every robbery as four steps, and run steps 1, 2 and 4 on the server:

  1. Start request: the client asks to rob a store. The server checks police count, cooldown, distance and that nobody is already robbing it.
  2. Lock: the server marks the store as being robbed and sets the cooldown.
  3. Action: the client plays the progress bar, the hack or the lockpick.
  4. Finish: the client reports done, the server checks the time that has passed and the distance again, then pays.

The client only handles the visuals. It never decides the outcome.

Police count

Require a minimum number of on-duty officers, counted on the server. A script that reads the count from the client, or from a value the client sends, is trivial to bypass. The counting code is in setting up a police job. Check it when the robbery starts, and decide whether it should be checked again before the payout.

Cooldowns stored on the server

Keep the state in a server table, keyed by the location id:

lua
-- server
-- [id] = { busy = bool, last = os.time(), robber = source, started = os.time() }
local robberies = {}

local function canRob(id, cooldown)
    local state = robberies[id]
    if not state then return true end
    if state.busy then return false end
    return (os.time() - state.last) >= cooldown
end

Two details matter:

  • A table in memory resets when the resource restarts. If you want the cooldown to survive a restart, store the last robbery time in the database.
  • Set busy when the robbery starts and clear it when it ends or when the robber disconnects, or the store stays locked forever.

Rewards only from the server

The most common exploit is a reward event that the client can trigger on its own:

lua
-- WRONG: any client can fire this
RegisterNetEvent('robbery:reward', function(amount)
    exports.ox_inventory:AddItem(source, 'money', amount)
end)

The fix is to not have that event at all. The server decides the amount and pays inside the code that verified the robbery:

lua
-- server (ox_lib callback)
lib.callback.register('robbery:finish', function(source, id)
    local state = robberies[id]
    if not state or state.robber ~= source then return false end
    if os.time() - state.started < Config.MinDuration then return false end

    local ped = GetPlayerPed(source)
    if #(GetEntityCoords(ped) - Config.Stores[id].coords) > 5.0 then return false end

    state.busy = false
    state.last = os.time()
    local amount = math.random(Config.Reward.min, Config.Reward.max)
    exports.ox_inventory:AddItem(source, 'money', amount)
    return true
end)

Notice the three checks: the same player started it, enough time has passed, and the player is still at the store. Server-side distance works because the server knows the player's position with OneSync. See securing server events and server callbacks for the pattern.

Warning: the item name for cash depends on your setup. Use the item or the framework function your server uses for money, and keep it on the server.

Alerting the police

Tell the officers when the robbery starts, from the server, to every on-duty police player:

lua
-- server: send to each on-duty officer you found with your police count
TriggerClientEvent('robbery:policeAlert', officerSrc, Config.Stores[id].coords, Config.Stores[id].label)

On the client, add a blip and a notification. You can also hand the alert to your dispatch resource instead of building your own, which gives officers a single place for all calls. A small delay or a chance of a silent alarm makes the robbery feel less scripted. Blips are covered in creating blips.

Common config mistakes

  • Police count set to 0 while testing, then left that way on the live server.
  • Reward range too high. A robbery that pays more than an hour of any job gets farmed. Compare with your paycheck, as in balancing the server economy.
  • Cooldown too short. With a few stores and fast cooldowns, players rotate between them.
  • No distance check on the finish step, so a player can start the robbery and collect it from across the map.
  • Items needed (lockpick, drill) never removed, or removed on the client.
  • Busy flag never cleared after a disconnect or a script error.
  • Coordinates copied from another map. If you use a different MLO or map, the store location is wrong.

Test with a second account that is not an admin, because admin permissions can hide a missing check.

Checklist

Symptom Fix
Robbery with no police online Count on-duty officers on the server, set a minimum above 0
Store robbed again immediately Add a server cooldown keyed by store id
Cheater gets the payout Remove client reward events, pay inside the verified server code
Payout from far away Check distance on the server at start and finish
Store locked forever Clear the busy flag on finish, failure and disconnect
Cooldown lost on restart Store the last robbery time in the database

Quick answers

Where should the robbery cooldown be stored?

On the server, in a table keyed by the store or bank id. A cooldown kept on the client resets on every reconnect and is easy to skip.

Why do players get money from robberies without doing them?

The reward event trusts the client. Give the reward only inside the server code that started and checked the robbery, never from an event the client can fire alone.

How many police should a robbery need?

It depends on how many officers your server usually has. A common start is one or two for a small store and more for a bank, then adjust from what you see.

Scripts that skip this problem

CCTV Security CamerasPlaceable cameras, a live multi-view tablet and printed evidence photos.View script →Advanced BoostingTablet-driven vehicle boosting: contracts from class D to S+, crews and a live queue.View script →Shop CreatorBuild a shop in under a minute — owners, employees, vaults and robberies included.View script →

Keep reading