Independent third-party developer. Not affiliated with, endorsed, sponsored or approved by Rockstar Games, Take-Two Interactive, Cfx.re or FiveM. GTA, Grand Theft Auto and FiveM are trademarks of their respective owners.

Robberies & police

[FREE] Dynamic NPC Robberies & Police Chases | MINIGAME | Legacy & Enhanced comaptible

v1.0.3

Free

Original Lua/JavaScript resource developed independently by WebMerc. Requires FiveM (GTA V). No Rockstar game files are included.

  • Free download from your account. You only need to log in.
  • Full source code. No escrow, no license checks.
  • Free updates while the script is maintained

Independent third-party developer. Not affiliated with, endorsed, sponsored or approved by Rockstar Games, Take-Two Interactive, Cfx.re or FiveM. GTA, Grand Theft Auto and FiveM are trademarks of their respective owners.

Read it before you buy

These are the same files that come in the download, updated with every release.

lastresort_wheelman

A man in a mask, a bag in his hand, and no ride. That is your cue.

lastresort_wheelman is a getaway-driver street contract for FiveM. It is not a job menu and it is not a menu-driven minigame: it is an ambient world event that places itself, runs on its own clock, and is validated end to end on the server.

Author: LastResort Development — intelnpc.com Version: 1.0.3

Open source. No escrow, no license check, no subscription, no obfuscation. You get every line and you can change every line.


What it is, and what it is not

A robber is standing at a street corner with a bag. He needs a car. You pull up, you hold your horn, he gets in, and the police are two seconds behind you. Lose them, and he tells you where to drop him. Get shot at along the way and he might not survive the trip — in which case the bag is still on the pavement, and it spends just as well.

It is an ambient world event: it places itself, it expires on its own, any player can take it, and only one player can take each one.

It is not a heist script, a robbery script or a police script. It does not rob stores, it does not touch your dispatch's own alerts beyond firing one, it does not manage jail, and it never assumes your server has police online. It creates one ped and, on a bad day, one bag.


Gameplay flow

director places a contract   ->  robber spawns, blip + 25 m area blip + alarm
  player drives within 100 m ->  help text: honk, or the reason you cannot
  player holds the horn      ->  server reserves the contract for exactly one driver
  robber gets the enter task ->  he runs to the car and takes a passenger seat
  2 s later                  ->  two stars, doors lock, dispatch alert goes out
  pursuit                    ->  heat 0-100, fed by player police, NPC police and proximity
  heat reaches zero          ->  wanted cleared, drop-off revealed and routed
  arrive and stop            ->  he gets out and walks the last 1.5 m
  server validates           ->  payout, XP, cleanup

The other ending:

robber is killed             ->  bag drops where he fell, blipped
  walk up and press E        ->  grab animation, one wanted star
  get the heat back to zero  ->  server validates, smaller payout, cleanup

The robber

He carries the bag from the moment the contract is placed — it is a real prop on his hand, with a carry walk to match, and it is the same model that drops if he is killed. The bag rides in the car with him and leaves with him at the drop.

He also talks. 169 lines across 32 situations, in English and Hungarian, spoken as a 3D speech bubble over his head. With lastresort_chat running they are real chat bubbles; without it the resource draws its own. He reacts to what is happening rather than to the state machine alone — being told to hurry when the police are close reads differently from a quiet stretch, a driver who has stopped dead gets told about it, and so does one who just wrote the car off. Every line lives in locales/, so any of them can be replaced without touching Lua. Config.Bark.Enabled = false silences him.

Whether he fights back depends on who he is. Each of the seven robber profiles maps to a combat style:

Style Who Behaviour
quiet the corner kid, the downtown runner, the man in the suit Never draws in the car
nervous the Balla, the Lost MC rider Fires in bursts, poor aim, short range
shooter the hired gun Aims properly, holds fire past 65 m
veteran the last one Full auto, 85 m, and he does not miss much

Only a passenger fires — the driver's seat is checked, so a robber who somehow ends up driving never shoots. He stands down the moment the wanted level clears, and again before he gets out at the drop.

Difficulty tiers

Contracts are graded, and the grade is what the robber, the heat and the payout are built from. Higher reputation unlocks the harder tiers rather than simply paying more for the same job.

Tier Unlocks at Robber Heat Payout XP
Street job Rookie 160 hp, pistol, poor aim 55 ×0.85 ×0.8
Armed robbery Rookie 200 hp, pistol 70 ×1.00 ×1.0
Professional crew Wheelman 260 hp, 50 armour, SMG 85 ×1.35 ×1.3
No-questions contract Ghost 320 hp, 100 armour, rifle 100 ×1.75 ×1.6

Seven robber profiles back these — a corner kid, a downtown runner, a Balla, a Lost MC rider, a man in a suit, a hired gun and something worse — and each tier draws from its own subset, so the person you pick up looks like the job you took. The director grades against the highest-ranked player online, so a server of rookies never sees the top tier.

The gate is enforced where it matters, not only where it is displayed: the server checks the claiming driver's own rank against the tier's requiresRank inside the claim, so a rookie who walks up to a ghost contract placed for someone else is refused with a message rather than paid for it.

Turn the whole thing off with Config.Difficulty.Enabled = false and every contract runs at Config.Difficulty.Default.

Getaway crew

Off by default. With Config.Crew.Enabled = true, a player standing near a driver who is already on a job can run /wheelmancrew to ride along. The server picks the nearest joinable contract itself — the client never names one — and refuses if the crew is full, the player is already working, the player is already riding with another crew, the contract is in a different routing bucket, or the contract has moved past Config.Crew.JoinBefore. Riding along counts as working: a crew member cannot claim a contract of his own while he holds a share of someone else's.

Crew members get Config.Crew.ShareFraction of the driver's payout each. The driver's own cut is not reduced, so the share is minted on top — which is why Config.Crew.TotalShareCap bounds what the whole crew can draw between them, defaulting to one driver payout however many join. They optionally take a scaled share of the XP and are dropped from the crew on disconnect.

Outcomes

Outcome Meaning Pays
DELIVERED The robber reached the drop-off alive Full
PACKAGE_STOLEN He died, you took the bag and got clear Reduced
ROBBER_KILLED He died and the bag was never collected No
BUSTED You were arrested No
PLAYER_DEAD You died No
ABANDONED You left him more than 250 m behind No
VEHICLE_LOST Your car was destroyed or you stopped driving it No
TIMEOUT The pursuit or the contract ran out of time No
ENTITY_LOST The robber or the bag went missing No
EXPIRED Nobody took the contract before it aged out No
CANCELLED An admin or another resource cancelled it No
RELEASED Internal: you were released before the pickup, the contract is offered again No

Install

Full instructions are in INSTALLATION.md. The short version:

set onesync on

ensure oxmysql        # optional
ensure es_extended    # or qb-core / qbx_core, or neither
ensure lastresort_notify   # optional
ensure lastresort_chat   # optional
ensure lastresort_wheelman

OneSync is required. Contract entities are created on the server, which is what makes a contract impossible to duplicate from a client.


Dependencies

Nothing is mandatory.

Resource If present If absent
es_extended / qb-core / qbx_core Identity, job, wallet Standalone mode, license identifier, ConfigServer.Rewards.CustomPayout
oxmysql SQL persistence JSON file persistence
lastresort_notify Rich notifications, the objective tracker, the wanted flash, money and rank cards, HUD gauges and the dispatch surface ox_lib → framework → native feed for toasts; hand-drawn bars for the gauges; nothing lost
lastresort_chat Admin replies, the 911 dispatch card with a map pin, optional contract chatter chat:addMessage, which the default FiveM chat renders
ox_inventory ConfigServer.Rewards.Item payouts Framework inventory, then skipped
ps-dispatch / cd_dispatch / qs-dispatch / core_dispatch / linden_outlawalert Police alert Plain notification to on-duty police

Sibling resources

Both of your own resources are used natively when they are running, and every one of these degrades to something that still works when they are not.

lastresort_notify

What Export used Without it
The contract objective list, stepped as the state machine advances Objectives / ObjectiveStep / ObjectivesComplete The existing notifications carry the same beats
Robber health and heat as real HUD gauges RegisterHudWidget / SetHudStats / UnregisterHudWidget The hand-drawn bars in client/hud.lua
The wanted flash when the chase starts Wanted Nothing; the stars speak for themselves
The payout card Money A success notification with the figure
The rank-up card Rank A success notification
The drop-off name when it is revealed Location An inform notification
Police dispatch DispatchAlert The next dispatch provider in the chain
Toasts Notify ox_lib, then the framework, then the native feed

Gauges and rich surfaces are separately switchable, so you can keep the HUD widgets and drop the objective tracker or vice versa:

Config.Integrations = {
    Notify = 'auto',        -- 'auto' | 'off'
    RichSurfaces = true,    -- objectives, wanted, money, rank, location
    HudWidgets = true,      -- the two HUD gauges
    HealthWidget = { id = 'wheelmanRobber', icon = 'user',  colour = '#FF7B54', warn = 25 },
    HeatWidget   = { id = 'wheelmanHeat',   icon = 'siren', colour = '#FF453A', warn = 60 },
}

lastresort_chat

What Export used Without it
Admin command replies Send to ConfigServer.Chat.Channel chat:addMessage to that player
The police alert as a dispatch card with a map pin SendCard to the 911 channel The dispatch provider chain, then a plain notification
Optional "a job is going down in X" chatter Send chat:addMessage
ConfigServer.Chat = {
    Enabled = true,             -- false silences every chat path, including the fallback
    Handler = 'auto',           -- 'auto' | 'off' — 'off' forces the chat:addMessage fallback
    Channel = 'system',
    PoliceChannel = '911',
    Prefix = 'Wheelman',
    Colour = { 255, 123, 84 },
    CardIcon = 'siren',
    CardAccent = '#FF453A',
    CardExpiry = 300,
    AnnounceContracts = false,  -- chatter when a contract is placed; off by default
}

The card is the one thing with no equivalent: without lastresort_chat the police still get the alert, just as a dispatch notification rather than a card with a pin. Everything else has a real fallback, and Enabled = false turns the whole chat surface off without touching anything else.


Framework

bridge/client.lua and bridge/server.lua are the only files that know a framework exists. Gameplay code calls LRW.Bridge.* and nothing else.

Config.Framework = 'auto'   -- 'auto' | 'standalone' | 'esx' | 'qb' | 'qbox'

Server bridge surface: getIdentifier, getName, getJob, isPolice, isStaff, pay, giveItem, sqlAvailable.

Standalone servers have no wallet, so supply one:

ConfigServer.Rewards.CustomPayout = function(src, amount, account, reason)
    exports.my_banking:AddMoney(src, amount)
    return true
end

Return true and the payout is considered done. Return anything else and it is logged and skipped — never silently doubled.


Database

ConfigServer.Persistence.Mode is auto by default: oxmysql when it is started, a JSON file otherwise. The table is created at runtime with CREATE TABLE IF NOT EXISTS; sql/lastresort_wheelman.sql is provided for anyone who prefers to import it by hand.

wheelman_drivers (identifier, name, xp, completed, failed, stolen_packages, earnings, updated_at)

One row per character. Nothing else is persisted — contracts are in-memory and deliberately do not survive a restart.


Reputation

Five ranks, thresholds in shared/reputation.lua:

Rank XP Payout multiplier
Rookie 0 1.00
Driver 400 1.08
Wheelman 1200 1.16
Professional 2800 1.24
Ghost 6000 1.32

The multiplier is 1 + (rankIndex - 1) * Config.Reputation.PayoutStep and is applied on the server, after the reward is calculated, never from anything the client sent.


Dispatch

Detected automatically from the started resources. Every event name and export signature below was taken from the resource it belongs to — none are invented.

Provider Call
ps-dispatch exports['ps-dispatch']:CustomAlert(payload)
cd_dispatch TriggerEvent('cd_dispatch:AddNotification', payload)
qs-dispatch exports['qs-dispatch']:VehicleTheft(payload)
core_dispatch TriggerEvent('core_dispatch:addCall', ...)
linden_outlawalert TriggerEvent('wf-alerts:svNotify', payload)
none TriggerClientEvent notification to each on-duty police source

Force one with ConfigServer.Dispatch.Provider. Alerts are rate-limited per contract by ConfigServer.Dispatch.RepeatInterval.


Configuration reference

config/config.lua — shared, client-visible

Block Key Default Meaning
Locale 'auto' Locale pack; locales/*.lua is globbed. 'auto' picks it — lr:locale convar, then locale / ox:locale / esx:locale / txAdmin-locale, then the player's game language on the client, then en (see Localisation)
Debug false Enables LRW.log, /wheelmandebug, /wheelmanwaypoint
Framework 'auto' Framework override
Director Enabled true Master switch for automatic placement
MaxConcurrent 2 Live contracts at once. One per player is fixed, not tunable
SpawnAttemptInterval 45000 How often a placement is rolled
SpawnChance 0.35 Probability per roll
MinPlayers 1 Players online before anything is placed
LocationCooldown 1800000 Per-location cooldown after use
PlayerCooldown 600000 Per-character cooldown after a contract ends
OfferLifetime 600000 How long an unclaimed contract survives
MinDistanceFromPlayer 120.0 Never spawn on top of someone
MaxDistanceFromPlayer 700.0 Never spawn where nobody will see it
ActiveHours / HourStart / HourEnd false / 18 / 5 Optional night-only window
Robber Models five street peds One is picked per contract
Weapon / Armed WEAPON_PISTOL / true What he draws when the police arrive
Health 200 Max health; the on-screen bar offsets it by HealthBarOffset
Accuracy 35 SetPedAccuracy
EventRadius 25.0 Area blip radius, and the radius inside which the help text shows and the horn is armed
BoardDistance 3.5 "He is at the door" threshold: inside it the contract reports boarding and a standing robber is not a stall
ApproachTimeout 30000 Give up on the run
DressTimeout 10000 How long to keep asking for control to configure a freshly streamed robber
BoardTimeout 20000 Timeout handed to each TASK_ENTER_VEHICLE; once he is at the door the retry budget is this times ApproachRetries
ApproachRetries 3 Attempts before the warp fallback
WarpFallback true Last resort only, and only while the car is stopped
ShootsAtPursuit true He fires from the passenger seat when you are wanted
StreamRadius 350.0 Blip and alarm activation radius
FleeDistance 500.0 Flee distance when he gives up on you
Carry.* bag prop, bone 36029, move_m@clipboard The holdall he carries, attached to his hand with a matching walk. Enabled = false removes it
PacifyInterval 1500 How often the client that owns an unclaimed robber re-settles him if he has wandered
StallRetask 4000 How long an enter task may be dead, or a running robber motionless away from the door, before it is re-issued; also the floor between two re-issues
Combat.Profiles / Styles / Weapons four styles Which robber fights back and how hard — see below
AlarmSound / AlarmSoundset / AlarmSounds / AlarmBanks true / ALARMS_SOUNDSET / … Location-keyed alarm bell
Package Model ch_prop_ch_duffelbag_01x The bag he carries and drops — one model for both. Any object model; swap it freely
PickupDistance 1.5 Grab radius
DespawnDistance 1000.0 Bag is abandoned past this
Lifetime 300000 Bag lifetime before the contract closes
BlockInWater / WaterDepthLimit true / 0.5 No fishing it out of the sea
RequireOnFoot true Get out of the car to take it
WantedOnPickup 1 Stars granted on collection
GrabAnimDict / GrabAnimClip / GrabAnimPhase / GrabDuration Pickup animation
Vehicle AllowBikes false Bikes have no passenger seat worth the name
RequireDriveable true IsVehicleDriveable
RequireEmptyPassengerSeats true He will not ride with other passengers aboard
PreferredSeats {0,1,2} Seat preference order
BlockedModels / AllowedModels {} Your own overrides; allow wins over block
AbandonGracePeriod 20000 How long you may be out of the car pre-pickup
LockDoorsWithRobber / ExitLockout true / 10000 The ten-second lock-in
Signal HornHoldTime / HornReleaseGrace 250 / 120 Hold, do not tap; the grace keeps a held horn latched across dropped frames
BlipDistanceFoot / BlipDistanceVehicle 50.0 / 100.0 Blip and area blip range
ResendCooldown / RetryDelay / ClaimTimeout 3000 / 2000 / 8000 Client-side signal throttling; a rejected claim reopens the horn after RetryDelay, which must stay above the server's SignalRateLimit (1500)
NoticeCooldown 2500 Floor between two toasts about the same signal problem
Pursuit UseGameWanted 'auto' 'auto' uses the vanilla wanted system until another resource is proven to be suppressing it, then drives the chase off the heat model alone. true forces the vanilla stars, false never touches them
WantedLevel 2 Stars applied when the run starts
StartDelay 2000 Delay from the run starting to the stars
HeatStart / HeatMax 70 / 100 Custom heat scale
HeatDecayPerSecond / HeatGainPerSecond 2.2 / 4.0 Cooling and heating rates
PoliceSightRange / PoliceCloseRange 90.0 / 45.0 Player-police pressure bands
DecayGraceTime / DecayGracePerStar 8000 / 4000 Quiet time before heat falls, extended by the stars last reported
CorroborationRange / UncorroboratedDecayFactor 250.0 / 0.5 How near a real officer must be to corroborate a calm report, and how much slower heat falls without one
NpcPoliceFallback / NpcPoliceRange / NpcPoliceRequireSiren true / 120.0 / true NPC police contribution; only responding units with a siren count
ReportInterval 1000 Client observation cadence
MinPursuitTime / MaxPursuitTime 30000 / 480000 Floor and ceiling on the chase
EvadedGraceTime / CleanDecayMultiplier 4000 / 3.0 Once the stars are genuinely gone and no NPC police are near, heat drains at triple rate after a short beat, so the post-chase wait does not drag
RaiseMaxWanted true Raise the game's wanted ceiling for the chase and restore it afterwards. Needed on any server that sets SetMaxWantedLevel(0)
ForceDispatch / DispatchRestore true / 'disable' QBCore and QBox disable the game's police dispatch services at startup, so stars alone bring nobody; this re-enables them for the chase and puts them back after. Skipped automatically when lastresort_intel_npc owns the police
EvasionGrace / HoldTime 15000 / 300000 The stars release only after the engine calls you evaded for this long with no responding police nearby, and unconditionally after HoldTime
StompWindow / StompStrikes 1000 / 3 How the resource decides another script is wiping the wanted level: this many wipes inside this window of a fresh write
FightHeat 45 With the vanilla wanted system off, the robber fights and hurries you above this heat
Bark Enabled / Backend true / 'auto' The robber's speech. 'native' forces the built-in renderer instead of lastresort_chat
Duration / MinGap / DefaultCooldown 4500 / 6000 / 30000 How long a line stays up, the floor between any two lines, and the per-situation repeat guard
NativeRange 25.0 Draw distance for the built-in renderer
ChatEcho true Also print his lines into the chat panel — Robber: …, with shouting and muttering labelled
SolicitRadius 30.0 He calls out to any player inside this radius while unclaimed — on foot or in a car
SlowSpeed / LongPursuit / CleanRun 8.0 / 120000 / 45000 Thresholds behind the "you are crawling", "this is dragging on" and "you are good at this" lines
Cooldowns per situation Overrides DefaultCooldown for the chatty ones
Delivery ArriveDistance 15.0 Where you may stop
RobberArriveRadius 2.5 Where he must reach
StopSpeedThreshold 3.0 You must actually stop
RevealDelay 3000 Beat before the drop-off message
RegionMatched true Pick a drop-off in the robbery's own region
OriginalDestinationsOnly false true restricts to the three primary drop-offs
MinSafehouseDistance / MaxSafehouseDistance 500.0 / 6000.0 Distance band
AbandonDistance / AbandonGrace 250.0 / 20000 Leave him this far behind for this long and the job is off
Exit 'flee' What he does once he is out at the drop: 'flee' sprints him away from the car, 'walk' walks him to the door instead
PayoutBeat 4000 The pause between him getting out at the drop and the money landing, so the farewell plays before the payout shard
DismountTimeout / DismountAttempts 8000 / 3 Wait for the robber to be out of the car before he leaves; retries if he is not
Blips Robber / Package / Safehouse / Area sprite 143 / 143 / 40, colours 57 / 57 / 12 Sprite and colour per blip
Hud ShowHealthBar / ShowHeatBar + geometry Two bars, drawn only while a contract is live
Integrations ObjectivesPosition 'tr' Which corner the objective tracker docks to; moved off the chat's corner by default
Notify Handler / Fallback / Duration / Position / Custom 'auto' / 'builtin' Notification routing
Reputation XpDelivered / XpStolen / XpFailed / PayoutStep 120 / 70 / 0 / 0.08 Progression

config/config_server.lua — server-only, never sent to a client

Block Key Default Meaning
Rewards Account 'cash' 'cash' or 'bank'
Delivered 9000–15000 Base payout band
PackageStolen 4000–7000 Reduced band
DistanceBonusPerKm / DistanceBonusCap 400 / 4000 Server-measured distance only
PursuitBonusPerMinute / PursuitBonusCap 500 / 2500 Server-clocked pursuit length; heat never enters the payout
CleanRunBonus 2000 The robber was never hit
IntactVehicleBonus / IntactVehicleThreshold 1500 / 850 The getaway car arrived above that health
RobberSurvivalBonus / RobberSurvivalThreshold 1200 / 0.75 He arrived above that fraction of his health
FastCompletionBonus / FastCompletionTime 2000 / 240000 Whole job inside four minutes
Item / ItemCount nil / 1 Optional item payout
CustomPayout nil Standalone wallet hook
Police Jobs / Aces / RequireOnDuty Who counts as police
MinimumOnline 0 Suppress dispatch below this many police
Dispatch Enabled / Provider / Title / Message / Code / blip fields / RepeatInterval Alert content
Persistence Mode / Table / File / SaveInterval 'auto' / wheelman_drivers / data/drivers.json / 60000 Storage
Limits ContractLifetime 1800000 Hard ceiling on a claimed contract
SignalRateLimit / ObserveRateLimit 1500 / 750 Per-source event throttles
MaxSignalDistance 40.0 Server-checked claim distance
MaxDeliveryDistance 25.0 Server-checked delivery distance
MaxPackageDistance 6.0 Server-checked pickup distance
MaxPositionDrift / MaxSpeed 250.0 / 90.0 Teleport rejection for the distance bonus, in metres and metres per second
MinEscortDuration / MinEscortDistance 12000 / 250.0 The robber must have been in your car this long, over this far, before a delivery counts
MaxEscortStep 3000 Longest gap between two escort samples that still counts towards it
MaxSeatScan 8 Seats scanned when looking for the robber
OutroCeiling / FailureOutroCeiling 75000 / 45000 How long the server waits for the client's ending before it cleans up itself
SilentDriverTimeout 45000 A claimed contract with no observations for this long is handed back
FailConfirmWindow 5000 A client-reported failure must still be true after this long
MaxReportedWanted / MaxReportedNpcPolice 5 / 12 Ceilings on what a client may claim in an observation
MinEvadedClaim 5000 A client must have been in pursuit this long before it may claim it shook them
MinContractSweep 250 Floor on the sweep cadence
BoardingDeadline 120000 How long a contract may sit unboarded before the server hands it back
MaxReleases 2 Times one contract may be handed back before it is cancelled
ReputationRateLimit 5000 Throttle on the stats query
StaleContractSweep 30000 Sweep cadence
Preflight Enabled true Scan the started resources once at boot for anything that turns the police off
Delay 15000 How long after this resource starts the scan runs, so late resources are included
Files five common config paths Tried in every resource on top of its own non-globbed script list
MaxFiles 600 Hard ceiling on files read by the scan
Skip {} Resources the scan ignores
StopOnConflict false Stop this resource instead of printing the notice. See below before turning it on
Admin Aces wheelman.admin, command, group.admin Who may run the admin command
Logging Enabled / Console / Webhook / WebhookName / WebhookColour / LogPayouts / LogRejections Console and Discord logging

Commands

Command Who What
/wheelman anyone Your rank, XP, completions, bags taken, failures and lifetime earnings
/wheelmanadmin list admin Every live contract with its state and driver
/wheelmanadmin spawn [locationId] admin Place one; no id means the director picks
/wheelmanadmin tp [id] admin Teleport yourself to a contract, a robbery id or a drop-off id. No argument goes to your own drop-off if you are on a job, otherwise to the nearest offer. Your car comes with you
/wheelmanadmin clear [contractId] admin Cancel one, or all
/wheelmanadmin state [id] admin Full dump of one contract: state, bucket, driver, crew, net ids, escort samples, travel, heat, safehouse
/wheelmanadmin cleanup <id> admin Run CleanupContract on it and report what was removed — the centralised teardown, testable in isolation
/wheelmanadmin difficulty [tier] admin List the tiers, or force a contract at one
/wheelmanadmin reset admin Clear location and player cooldowns
/wheelmancrew anyone Join the nearest getaway as crew, when crews are enabled
/wheelmandebug Config.Debug only On-screen state panel: contract id, state, heat, robber ped and health, package, nearest offer, whether your car passes the gate and why not, wanted level
/wheelmanwaypoint Config.Debug only Waypoint to the nearest offer, at any distance — the blip itself only draws when you are close

Admin access is ACE-gated through ConfigServer.Admin.Aces — wheelman.admin, command or group.admin by default:

add_ace group.admin wheelman.admin allow

Every action except tp also runs from the server console, which skips the ace check.

The two /wheelman… debug commands need Config.Debug = true in config/config.lua and a restart lastresort_wheelman; it ships false. With it off they say so rather than doing nothing.

Testing a contract end to end

The director will not place a contract closer than MinDistanceFromPlayer (120 m), so for a test you want the forced path:

/wheelmanadmin reset                 -- drop the location and player cooldowns
/wheelmanadmin spawn getaway_03      -- force one at Strawberry, ignoring cooldown and concurrency
/wheelmanadmin tp getaway_03         -- go there, in whatever you are driving

Then pull up next to the robber and hold the horn. /wheelmanadmin tp with no argument will take you to the drop-off once the contract reveals it.


Exports

Server

exports.lastresort_wheelman:GetActiveContracts()          -- array of contract summaries
exports.lastresort_wheelman:GetContract(contractId)       -- one summary, or nil
exports.lastresort_wheelman:GetPlayerContract(src)        -- that player's contract, or nil
exports.lastresort_wheelman:IsPlayerOnContract(src)       -- boolean
exports.lastresort_wheelman:CreateContract(locationId, force)  -- summary, or nil + reason
exports.lastresort_wheelman:CancelContract(contractId)    -- boolean
exports.lastresort_wheelman:GetDriverStats(src)           -- xp, rank, completed, failed, stolenPackages, earnings
                                               -- reads the warm cache; nil until the player has been seen
exports.lastresort_wheelman:GetLeaderboard()              -- top 25 by xp, snapshot refreshed every two minutes
exports.lastresort_wheelman:GetLocations()                -- copy of the robbery location table
exports.lastresort_wheelman:GetSafehouses()               -- copy of the drop-off table
exports.lastresort_wheelman:SetDirectorEnabled(bool)      -- pause or resume automatic placement

Client

exports.lastresort_wheelman:GetContractState()            -- current state string, or nil
exports.lastresort_wheelman:IsOnContract()                -- boolean
exports.lastresort_wheelman:IsGetawayVehicleValid()       -- boolean, reason
exports.lastresort_wheelman:RequestDriverStats()          -- asks the server to push your stats

Events

Fired server-side for other resources to listen to. All are TriggerEvent, not net events.

'lastresort_wheelman:contractOffered'    -- (contractId, locationId)
'lastresort_wheelman:contractStarted'    -- (contractId, src)
'lastresort_wheelman:contractResolved'   -- (contractId, outcome, src, payout)
'lastresort_wheelman:packageCollected'   -- (contractId, src)
'lastresort_wheelman:reputationChanged'  -- (src, identifier, rankId, xp)

Player state bag

While a player holds a contract the server sets a state bag on them, so other resources can see it without an export call:

Player(src).state['lastresort_wheelman:contract']   -- the contract id, or nil

It is set on claim and cleared on release, resolution, disconnect and cleanup.


Security model

The server owns ActiveContracts, PlayerContracts, LocationCooldowns and PlayerCooldowns. The client owns nothing but its own presentation.

  • The client never names a reward. LRW.Net.deliver carries a contract id and nothing else. The amount is computed on the server from a config band, a server-measured travelled distance, the server's own peak-heat record and the server's copy of your reputation.
  • There is no "mission complete" event. Delivery is validated: correct state, you are the contract's driver, you are within MaxDeliveryDistance of the drop-off the server chose, the robber entity still exists, he is alive, and he is within 25 m of you.
  • Claiming is a test-and-set. LRW.Contracts.claim() checks state, checks that nobody else holds the contract, checks your cooldown and checks your distance, then writes the driver — all inside one server tick, so two simultaneous honks cannot both win.
  • State transitions are checked against a table. LRW.canTransition(from, to) in shared/states.lua is the only way a contract changes state. A client asking for an illegal jump is dropped silently.
  • Entity ids are resolved, not trusted. Every net id a client sends is turned into a real server entity with NetworkGetEntityFromNetworkId and DoesEntityExist before it is used, and the escape transition additionally verifies that you are in seat -1 of the vehicle you named.
  • Package pickup cannot be repeated. State must be DOWNED, the bag must exist, you must be within MaxPackageDistance, and packageClaimed is set before the object is deleted.
  • Distance travelled is measured server-side, each sample is clamped to what ConfigServer.Limits.MaxSpeed allows in the elapsed interval, and the running total is capped by wall-clock time since the claim. A teleport cannot inflate the distance bonus.
  • The pursuit bonus is on the server's clock, not on client-reported heat, so a client that claims five stars gains nothing from saying so.
  • Delivery requires the robber to have actually been carried. The server samples GetVehiclePedIsIn(robber) == yourVehicle on every observation and adds up the time he was aboard and the ground the car covered while he was. A delivery under MinEscortDuration or MinEscortDistance is refused, and a jump longer than MaxEscortStep adds nothing to either, so teleporting the ped to the drop-off does not pay.
  • Boarding is not proximity. The server resolves the vehicle from the net id, confirms the caller is in its driver seat, checks the model and the vehicle type, and requires the robber to be in one of that vehicle's passenger seats. Standing next to a car is not getting into it.
  • Delivery re-checks all three parties: the driver in the bound car's driver seat, the robber a passenger in that same car, and the car itself within MaxDeliveryDistance of the drop.
  • Leaving him behind is measured on the server. Every observation and every sweep compares the driver's ped to the robber entity; past AbandonDistance for AbandonGrace the contract ends as ABANDONED whatever the client reports, and a client-reported abandonment is still verified against the same distance before it is accepted.
  • A missing robber is ENTITY_LOST, never a death. A vanished entity or net id cannot be reported as a kill and cannot produce a package.
  • Package pickup needs the real object. The entity must exist, be the configured model, be in the contract's routing bucket, and its position is read from the entity — the cached coordinate is never a fallback. On-foot is enforced when configured.
  • Heat only falls when nothing contradicts it. Cooling needs a grace window of clean reports, and no player officer inside PoliceSightRange whatever the client claims. The grace is extended by DecayGracePerStar for every star the client itself last reported, so a client that suddenly announces its four-star chase evaporated waits the longest. Corroboration is positional, not a head-count: only an officer inside CorroborationRange of the contract counts, and without one the rate is cut by UncorroboratedDecayFactor. A client reporting wanted = 0 cannot cool itself down on its own word.
  • The package branch cannot skip the pursuit. Taking the bag starts the heat clock if it was not already running, and cashing out goes through the same LRW.Heat.isCool gate as a delivery.
  • Handing a contract back is bounded by MaxReleases, so claim-and-release cannot hold a location's slot open indefinitely, and the server hands one back on its own after BoardingDeadline if the client stops making progress before the pickup.
  • Failure costs a cooldown only when it was yours. An admin cancellation, an expiry or a lost entity leaves you free to take the next contract immediately.
  • Every net event is rate-limited per source and type-checks its arguments before doing anything.
  • Contract entities are created on the server, so a client cannot spawn a second robber or a second bag.
  • Routing buckets are respected. A contract carries the bucket it was created in; offers are sent only to players in that bucket, and a claim — or a crew join — from another bucket is refused, so an event in one instance cannot be taken, seen or ridden along with from another.
  • A driver can only be paid once per contract. Holding a crew share counts as being busy, so a passenger cannot take a contract of his own alongside it, and he cannot ride with two crews.
  • The difficulty gate is checked at the claim. The tier's requiresRank is compared against the claiming player's own reputation, not against whoever the contract was graded for.
  • Condition bonuses use the worst reading, not the last one. The server keeps a running minimum of the car's and the robber's health across the whole run, so repairing the car or healing the ped in the last hundred metres does not buy the intact-vehicle, survival or clean-run bonus.
  • Vehicle types are an allowlist. GetVehicleType is checked server-side against the road vehicles the job accepts; boats, aircraft, blimps, bicycles, trains, trailers, submarines and anything a future game build introduces are refused rather than tolerated.
  • No export yields. GetDriverStats reads the warm cache and GetLeaderboard reads a snapshot refreshed every two minutes, so a cross-resource call never blocks on a database round trip.

Cleanup

LRW.Contracts.cleanup(contractId, reason) is the single teardown path. It deletes the robber, the bag, the driver mapping and the broadcast, in that order, and runs on:

success, failure, timeout, expiry, entity loss, admin cancellation, playerDropped and onResourceStop.

Client-side, LRW.Blips.clear(), LRW.Alarm.stopAll(), LRW.Pursuit.release() and LRW.Vehicle.endLockdown() all fire on onClientResourceStop, so a restart cannot leave a locked car, a stuck wanted level, a route line or an orphan blip behind. The client never creates a networked entity, so it has none to delete — it only ever releases its hold with SetEntityAsNoLongerNeeded.

Disconnecting mid-contract before the pickup releases the contract back to OFFERED for someone else; disconnecting after the pickup resolves it as ABANDONED.


Performance

There is no unbounded Wait(0) anywhere. Every loop is state-gated and picks its own sleep:

Loop Sleep
client/director.lua discovery 1500 ms, tightening to 250 ms only within three times EventRadius
client/director.lua horn prompt 500 ms, dropping to 0 only while the help text is actually on screen
client/director.lua blips 1000 ms
client/contract.lua 1000 ms idle; 250–750 ms per state while a contract is live
client/hud.lua 500 ms, dropping to 0 only while a bar is drawn
client/vehicle.lua lockdown 500 ms, dropping to 0 only while you are sitting in the locked car
client/debug.lua 500 ms, and 0 only when the panel is open and Config.Debug is set

Server-side there are two timers: the contract sweep (StaleContractSweep, 30 s) and the persistence flush (SaveInterval, 60 s). Heat is recalculated on the client's observation event, not on a server tick.

Expected resmon: 0.00 ms idle for a player with no contract and none nearby; roughly 0.02–0.06 ms while a contract is running, which is the two HUD bars.

These figures are the design target derived from the structure above, not a live measurement: this build was produced without a FiveM server and game client to record resmon against. Confirm them on your own server before publishing them as your own numbers.


OneSync

Required, and used deliberately.

  • The robber and the bag are created with the server-side CreatePed / CreateObject, so they exist once, globally, and no client race can duplicate them.
  • The driver's client takes control before every task with LRW.Entities.requestControl(), which loops NetworkRequestControlOfEntity until NetworkHasControlOfEntity returns true or the attempt budget runs out. Every task call is guarded by it.
  • Net ids are re-resolved on every use rather than cached, so a migrated entity is picked up again instead of going stale.
  • If the robber or the bag stops resolving, the contract ends as ENTITY_LOST rather than hanging.
  • Entities the client did not create are released with SetEntityAsNoLongerNeeded, never deleted — deletion is the server's job.

Tests

The resource ships its offline test suites. They boot the whole server half against stubbed FiveM natives and drive it — the happy path, the package path, the claim race, every illegal state transition, delivery and pickup validation, payout idempotency, disconnects, rate limiting, hostile payloads, routing-bucket isolation, the server-side abandonment rule and a final entity-leak sweep — plus a static pass over the manifest, both locales, config parity, the location data and the startup police check.

tests/boarding.test.lua boots the whole client half against a stubbed engine with coroutine-driven threads and drives the boarding state machine: the enter task on the first tick after the horn, the blocked-door seat choice, re-tasking and the stopped-car warp, the mid-run re-board in ESCAPE and DROPOFF, the abandon bail from a robber who has streamed out, the driver on foot, the wreck, and the drop-off outro as the one legitimate exit.

473 assertions across five files, no server required:

cd lastresort_wheelman
lua5.4 tests/boarding.test.lua
lua5.4 tests/contracts.test.lua
lua5.4 tests/integrity.test.lua
lua5.4 tests/preflight.test.lua
lua5.4 tests/vehicle.test.lua

They do not exercise GTA's ped AI, the wanted system or entity ownership; nothing here runs a game client. What the harness therefore cannot prove, and what a first in-game run should watch [NEEDS IN-GAME TEST]: that SCRIPT_TASK_ENTER_VEHICLE is the hash GET_SCRIPT_TASK_STATUS reports the enter task under (the name follows the SCRIPT_TASK_<NATIVE> convention of every other entry and is backed by GET_VEHICLE_PED_IS_TRYING_TO_ENTER either way); that the stay-in flags hold him through a hard frontal crash and a wreck; that IS_PED_GETTING_INTO_A_VEHICLE turns true at the door and not before; and that the ten-second lock-in (SET_VEHICLE_DOORS_LOCKED state 3, player-only lockout) never stops his enter task, which is why the lock is dropped the moment he is found outside anyway.


Localisation

Twelve complete language packs ship in locales/, with 275 strings each, including robber dialogue:

Language Code
English en
Hungarian hu
German de
French fr
Spanish es
Brazilian Portuguese pt-br
Polish pl
Turkish tr
Russian ru
Italian it
Czech cs (also accepts cz)
Slovak sk

Every player-facing string is a locale key; nothing is hardcoded in Lua. For example, use setr lr:locale cs for Czech or setr lr:locale sk for Slovak in server.cfg, then restart the resource. Explicit settings such as Config.Locale = 'cz' and regional codes such as cs_CZ / sk_SK are also normalised.

To add a language, copy locales/en.lua to locales/de.lua, change Locales['en'] to Locales['de'], translate, and set Config.Locale = 'de' — or leave it on 'auto' and set setr lr:locale de in server.cfg. The manifest already globs locales/*.lua.

Config.Locale = 'auto' resolves once at start, on the server and on each client, and takes the first source that names a language with a pack in locales/:

  1. the lr:locale convar — one line in server.cfg sets every LastResort resource at once: setr lr:locale hu
  2. the locale, ox:locale, esx:locale and txAdmin-locale convars, in that order
  3. on the client only, the player's own GTA language setting
  4. en

Codes are normalised before matching (hu_HU → hu, en-US → en, pt_BR → pt-br), and a code with no pack is skipped rather than failing. The server console names the result once at boot — [lastresort_wheelman] locale: hu (lr:locale convar) / [lastresort_wheelman] locale: en (default); the client prints nothing. Use setr, not set, for lr:locale: a client resolves its own copy, a set convar never reaches it, and the client would fall through to the player's game language while the server stayed on its own result. An explicit code such as 'hu' skips detection entirely; an explicit code with no pack falls back to English as before.


Turning the vanilla wanted level on or off

Config.Pursuit.UseGameWanted has three settings:

Value Behaviour
'auto' (default) Uses the game's stars, and watches them. If another resource keeps clearing or clamping them (three wipes within a second of a fresh write), the driver is told once, the server console prints once which problem it is, and that client finishes the chase on the heat model alone.
true Always writes the stars and keeps re-asserting them for the length of the pursuit. Never switches itself off; the console diagnostic still prints so you know what is fighting it.
false Never touches the wanted system. The whole chase runs on the 0-100 heat model, NPC police pressure and real player police. Pick this on any server whose framework owns the wanted level.

On QBCore and QBox servers the suppressor is the smallresources pack, and it does two separate things at startup: Config.AIResponse.wantedLevels = false runs SetMaxWantedLevel(0) (this resource raises the clamp back for the chase on its own), and Config.AIResponse.dispatchServices turns off the game's police dispatch — which is why stars alone bring nobody. ForceDispatch re-enables dispatch for the length of the chase and DispatchRestore puts it back after, so the no-police world outside contracts is untouched. To make stars global instead, set wantedLevels = true and the dispatch entries in the qb-smallresources / qbx_smallresources config.lua, and leave ForceDispatch on anyway — it costs nothing when dispatch is already up.

Either way the contract flow is unaffected: heat is what reveals the drop-off, and heat never depended on the stars.


The startup police check

Fifteen seconds after the resource starts, the server reads every started resource once and looks for the handful of calls and settings that turn the police off. It reads each resource's own non-globbed shared_scripts and client_scripts entries plus five common config paths, and it stops after ConfigServer.Preflight.MaxFiles files. Nothing is written, nothing is patched, and the scan never runs again until the next server start.

What it looks for, and what each one means:

Found What it does What to do
SetCreateRandomCops(false) Ambient police stop spawning anywhere on the map Nothing here puts them back. Player police and the heat model still run the chase
SetCreateRandomCopsNotOnScenarios(false) Police stop spawning outside scenario points As above
SetMaxWantedLevel(0) The wanted ceiling is zero, so no stars can appear This resource raises the clamp back for the length of a contract
wantedLevels = false The QBCore/Qbox smallresources form of the same clamp As above
SetPoliceIgnorePlayer(player, true) Police ignore that player entirely Config.Pursuit.ForceDispatch undoes it at the start of every chase
SetDispatchCopsForPlayer(player, false) The game dispatches nobody to that player As above

The output names the resource, the file and the setting, then the fix on their side and the knob on this side:

[lastresort_wheelman] preflight: another started resource turns off part of the police response
[lastresort_wheelman]   qb-smallresources -> config.lua: wantedLevels = false
[lastresort_wheelman]     effect: the wanted ceiling is zero, so no stars can appear for anyone
[lastresort_wheelman]     there:  set it to true
[lastresort_wheelman]     here:   the clamp is raised back for the length of a contract, or set Config.Pursuit.UseGameWanted = false

It is a notice, not a fault. Every line above is survivable: a contract still places, the horn still claims it, the robber still boards, fights and talks, heat still rises from player police, the drop-off still reveals and the payout still lands. That is why ConfigServer.Preflight.StopOnConflict is false. Turning it on makes the resource stop itself whenever the scan finds anything — it is there if you want a hard gate on your own box, but on a live server it turns a config note into an outage.

A resource whose files are escrowed cannot be read, so it is skipped in silence; so is anything listed in ConfigServer.Preflight.Skip, anything not started, and this resource itself. Comments are stripped before the scan, so a native named in a note is not a finding. A match never crosses a line break, and a conditional such as SetMaxWantedLevel(Config.AIResponse.wantedLevels and 5 or 0) is not reported as a clamp to zero — that case is caught by the config setting behind it instead.

lastresort_intel_npc is the one resource the scan answers for. Its police module suppresses ambient police and dispatch on purpose while its own police system is on, and a chase here already stands down for it — ForceDispatch asks its IsPoliceSystemEnabled export first and leaves the world alone if the answer is yes. The scan says that in one line instead of listing its calls as faults.


Troubleshooting

Nothing ever spawns. Check Config.Director.Enabled, then MinPlayers, then ActiveHours. The director only places a contract between MinDistanceFromPlayer and MaxDistanceFromPlayer of someone, so on an empty map nothing will appear. Force one with /wheelmanadmin spawn.

The robber spawns but there is no help text. You are outside PromptDistanceVehicle, or the contract is already claimed. Turn on Config.Debug and run /wheelmandebug — the panel shows the nearest offer and the exact eligibility reason.

The horn does nothing. The help text tells you why: wrong seat, law vehicle, passengers on board, unsuitable vehicle, airborne, or you already have a wanted level. Hold the horn for Config.Signal.HornHoldTime; a tap will not register.

He runs to the car but will not get in. Almost always a vehicle with no reachable entry point, or one parked against geometry. He is sent to the first seat whose door is clear, re-tasked every StallRetask while the engine refuses, and warped in after ApproachRetries attempts if Config.Robber.WarpFallback is on — but only once the car has stopped, so stop. Set Config.Debug and watch the boarding row in the panel: it shows whether the enter task is live and the attempt count.

He got out of the car by himself. He should not: the windscreen, wreck, panic and jack exits are all switched off on him. If it still happens, stop — he is re-tasked into the car within a tick and the notification says so. Driving on without him ends the job as ABANDONED after AbandonGrace.

"payout … went unbanked" in the console. No framework wallet was found. Either set Config.Framework explicitly or supply ConfigServer.Rewards.CustomPayout.

Persistence says json when you wanted SQL. oxmysql was not started when lastresort_wheelman started. Put ensure oxmysql above ensure lastresort_wheelman.

The drop-off never appears. Heat has not reached Config.Pursuit.ClearThreshold, or Config.Pursuit.MinPursuitTime has not elapsed. If the console prints that another resource is clearing or clamping the wanted level, that is the built-in detector: with UseGameWanted = 'auto' the resource has already switched that client to the heat-model-only chase, and the print tells you which framework script to look at. Set UseGameWanted = false to make that the permanent behaviour, or stop the other script from touching drivers who hold a contract. With UseGameWanted = false and no police near you at all, heat decays on its own once DecayGraceTime has passed — at half rate, because nothing corroborates the calm — so the drop-off shows up about a minute after the pickup.

The console says another resource turns off part of the police response. That is the startup check above. It names the resource, the file and the setting; fix it there, or take the knob it offers on this side. Nothing is broken by the notice itself.

Police never get an alert. Check ConfigServer.Police.Jobs matches your job names, that RequireOnDuty matches how your framework reports duty, and that MinimumOnline is not above the number of police online.


Support

intelnpc.com — LastResort Development.

What you get

  • The full source code. Every Lua, config and UI file, readable and editable. Rename it, restyle it or hook your own systems into it.
  • No escrow, no license checks. No key and nothing that phones home. It keeps running as long as your server does.
  • Free updates. New versions land in your account for as long as we maintain the script. Download the latest one whenever you like.
  • One community per purchase. Use it on that community's live, development and test servers. Reselling or re-uploading the code is not allowed.

Get [FREE] Dynamic NPC Robberies & Police Chases | MINIGAME | Legacy & Enhanced comaptible

It is free. Log in and download it from your account.

[FREE] Dynamic NPC Robberies & Police Chases | MINIGAME | Legacy & Enhanced comaptible

Free
Log in to download