What it does
Sal's Kewl Roadside Assist (sals_kewlroadsideassist)
gives your players a roadside assistance service. They call for help, see a
real price before they confirm, and an NPC tow truck drives to the scene, or
an on-duty player mechanic takes the call. The truck does the work they
picked, or loads the vehicle and delivers it where they chose.
Every price and every charge is decided on the server, so a
player can't talk the resource into a cheaper bill.
It runs on qb-core, qbx_core, ESX and standalone, detected automatically.
How a call works
- Request. The player runs
/roadsideassist(or a phone app, or another resource, asks on their behalf) in or near their vehicle. - See the price. They pick services from a menu: repair, refuel (recharge for electric vehicles), wash, upright, tire change, jump start, lockout and tow. The menu shows the server's total, itemized: base cost, distance and night surcharges, express premium, and membership discount.
- Pay at confirm. The player pays the moment they confirm, not when the work is finished.
- Mechanic or NPC truck. On-duty player mechanics get the call first. If nobody accepts in time, or the mechanic who took it doesn't finish within the time limit, an NPC tow truck is sent instead.
- Arrival. The NPC mechanic walks up, greets the player by time of day, and has a short conversation with voice lines and gestures. A passive HUD card tracks the call the whole way: requested, en route, on scene, service, complete.
- Work or tow. The mechanic does the work, or the flatbed loads the vehicle and delivers it to a depot, a mechanic shop or the owner's own garage. The player can ride along in the truck for free.
- Refund if it fails. If the call is canceled or fails before the work is done, the money goes back automatically. That includes a player who disconnects mid-call: they are refunded when they next load in.
Players can't mess with each other's cars
- It never picks a vehicle with another player inside.
- Players can't tow a car registered to someone else, and can only use lockout on their own.
- Emergency vehicles can only be towed by on-duty police and EMS.
- Players can't steal the tow truck.
- Per-character cooldowns (a shorter one after canceling), restricted zones such as the prison and the military base, checked against the player's position on the server, and a rate limit on menu requests.
Also in the box
- Membership tiers (
/roadsidemembership). Basic gives a discount and Premier makes service free. Tiers are fully configurable, and renewals stack up to a cap. - Police dispatch tows. Another resource, such as Sal's Kewl Dispatch (3.12.0 or later), can send a truck on a player's behalf. See Dispatch-requested tows.
- Discord logging of completed, canceled and failed calls (optional).
- Localization. English, Spanish, French, German and
Brazilian Portuguese ship in
locales/. Every player-facing string, the HUD included, lives there.
Requirements
None, beyond a FiveM server. Everything else is detected at runtime, and none of it is a hard dependency:
| Area | Supported |
|---|---|
| Framework | qb-core, qbx_core,
es_extended, or none (standalone: services are free) |
| Menu | qb-menu, ox_lib. Without
either, players name the services on the command, or
Config.Services.NoMenuDefault is used |
| Notifications / progress bars | qb-core, ox_lib, ESX, or GTA's own |
| Targeting | ox_target, qb-target
(optional; commands work without) |
| Fuel | LegacyFuel, ps-fuel,
cdn-fuel, ox_fuel |
| Database | oxmysql, mysql-async or ghmattimysql, for vehicle-ownership checks
and garage storage. Without it, ownership isn't checked and lockout
is refused |
| Society payouts | Renewed-Banking,
qb-banking, qb-management,
esx_addonaccount |
| Alerts | sals_kewljobnotify |
Installation
- Extract the resource into your server's
resources/folder. The folder must be namedsals_kewlroadsideassist. - Add it to
server.cfgafter your framework (and after your database resource, ox_lib and your target resource, if you use them):ensure sals_kewlroadsideassist - Review
config.lua. Everything a server owner can tune lives there, commented in plain language: prices, services, destinations, pricing rules, membership tiers, dispatch jobs, the ride-along and integrations. The rest of this manual walks through it. - Start the server. The console prints one line confirming what was
detected:
[sals_kewlroadsideassist] framework: qb | database: oxmysql | garages: auto
Read that line. It is the quickest check that the
install took. If database says none,
ownership checks are off and lockout is refused. If garages
says off, the garage destination won't be offered (see
Troubleshooting).
Upgrading from 1.x
Updating 2.0.0 to 2.0.1. Copy the new folder over the
old one and restart it. Your config.lua keeps working; to use
the new automatic vehicle pick, add AutoPickAfter = 25, and
AutoPickRadius = 20.0, to Config.ServiceRequests
(left out, it stays off and the picker works as before). Nothing else
changed.
2.0.0 is a major release. Your existing config.lua
keeps working: keys that were renamed are mapped automatically. But
read every point below before you upgrade.
Files moved. client.lua,
server.lua, shared.lua and
vehicle_services.lua are now in client/,
server/ and shared/. Replace the whole
resource folder, then copy your config.lua back in.
If you added custom services to vehicle_services.lua, move
them into client/vehicle_services.lua.
- Players now pay when they confirm, not when the work finishes, and are refunded automatically if the call doesn't happen.
Config.Towing.GarageIntegrationis replaced byConfig.Towing.Garage. An oldEventsetting is still honored, but it is now triggered on the server, asTriggerEvent(event, src, plate, garageId, destinationName), not from the client. If your garage resource listened for it on the client, it needs a server-side handler now.Config.TowTruckSpawnPointsis nowConfig.Depots. The old name still works. They were only ever used for the distance surcharge; trucks spawn on a road near the player.- Removed keys that did nothing:
Config.Cooldown.MaxActive,Config.Providers[].PriceMultiplier(useConfig.Pricing.ExpressMultiplier),Config.TowPedModels,Config.TowTruckModels. /roadsidecleanupno longer clears a player's own cooldown. Admins passing a player id still can.Config.IdleTimeoutnow defaults to 600 and only applies while waiting for the NPC truck. Keep it longer thanConfig.TowTruckSettings.ArrivalTimeout.- New services (tire change, jump start, lockout) only
appear if your config defines them. Copy those blocks from the new
config.luainto yours.
Commands
| Command | Who | What |
|---|---|---|
/roadsideassist [services] [express] | Players | Request roadside assistance. With no menu resource, name the services:
/roadsideassist repair fuel |
/roadsideassistcancel | Players | Cancel your request (refunded) |
/roadsidemembership [check | tiers | buy <tier>] |
Players | Check, list or buy a membership |
/rcalls | Mechanics | List open roadside calls |
/raccept [number] | Mechanics | Accept a call (the oldest if no number is given) |
/rcomplete | Mechanics | Complete your call (you must be at the vehicle) |
/towthis | Officers | Pick the vehicle a dispatch tow should take, when none was named |
/roadsidecleanup [id] | Anyone (own state) / Admins (ACE, any player) | Clear a stuck request, with a refund. Admins also clear the player's cooldown |
/roadsidestatus | Admins (ACE) | List active calls |
The services a player can name on the command line are
repair fuel wash upright tires jumpstart lockout tow, plus
express.
/roadside is an extra alias for the request command, handy for
phone apps that run a command (Config.Integrations.PhoneCommand;
leave it blank for none).
Renaming commands
Every command name is configurable:
| Setting | Commands |
|---|---|
Config.Commands | Request,
Cancel |
Config.Dispatch.Commands | Calls,
Accept, Complete |
Config.Membership.Command | The membership command |
Config.Admin | CleanupCommand,
StatusCommand |
Config.ServiceRequests.SelectCommand | The
/towthis command |
Admin access
The admin ACE is command.roadsidecleanup by default
(Config.Admin.AcePermission). In server.cfg:
add_ace group.admin command.roadsidecleanup allow
Configuration: where things live
Everything is in config.lua, and every setting has a
plain-language comment beside it. The sections below follow the file. The
first few settings decide how a player starts a call:
| Setting | Default | What it does |
|---|---|---|
Config.Locale | "en" | Language.
Shipped: en, es, fr, de,
pt-br. Must match a file in locales/. |
Config.AllowNearVehicle | true |
Lets a player request while standing near a vehicle. Off means they must be seated inside it. When standing, the vehicle they last drove is preferred; otherwise the closest, but never one with another player inside. |
Config.NearVehicleRadius | 5.0 |
Meters, used when AllowNearVehicle is on. |
Config.DespawnDistance | 150.0 |
If the player goes this far from the vehicle while help is on the way, the request is canceled (and refunded). |
Config.IdleTimeout | 600 |
Backstop, in seconds, on waiting for an NPC truck. Counted from each
spawn attempt. Keep it longer than
Config.TowTruckSettings.ArrivalTimeout (see
Troubleshooting). Player mechanics use
Config.Dispatch.ArrivalTimeout instead. |
Config.Debug.Enabled ships false. Leave it off on
a live server.
Services & prices
Config.Services turns each service on or off and sets its base
cost. These base costs are what the server charges from; nothing the client
sends can change them. ChargeForServices = false is the master
switch that makes everything free.
| Key | Menu label | Cost | Time |
|---|---|---|---|
Repair | Repair | $1,000 | 10 s |
Fuel | Refuel (Recharge for EVs) | $500 | 5 s |
Wash | Wash | $250 | 3 s |
UprightVehicle | Upright Vehicle | $750 | 8 s |
Tires | Tire Change | $400 | 6 s |
JumpStart | Jump Start | $300 | 4 s |
Lockout | Lockout | $350 | 5 s |
Tow | Tow / Recover | $1,500 | 6 s |
Each service block has Enabled, Label,
Description, Cost and Time (how long
the work takes, in milliseconds). The menu lists them in the order above.
Repair = {
Enabled = true,
Label = "Repair",
Description = "Fix your vehicle's engine and body damage",
Cost = 1000,
Time = 10000,
},
Lockout
Lockout has two extra settings. RequireOwnership = true only
unlocks vehicles the requester owns. That needs a database resource and a framework
vehicle table; if ownership can't be checked, lockout is refused rather than
risk unlocking someone else's car. KeysEvent is an optional
client event fired with the plate after unlocking, to hand the
player keys (for qb-vehiclekeys: "vehiclekeys:client:SetOwner").
Leave it blank to only unlock.
Electric vehicles
Models listed in Config.ElectricVehicles are offered
Recharge instead of fuel. Add your own addon EVs by model
name.
No menu installed
Without qb-menu or ox_lib, the services in
Config.Services.NoMenuDefault ({ "Repair" }) are
requested unless the player names their own on the command line, for example
/roadsideassist repair fuel express.
Custom services
Add your own under Config.Services.CustomServices. Each entry
is charged on the server by its key, and FunctionName names the
client-side function that does the work, which you write in
client/vehicle_services.lua without touching core code. The
shipped file has a commented example:
CustomServices = {
["WindowRepair"] = {
Enabled = true,
Label = "Window Repair",
Description = "Replace broken windows",
Cost = 300,
Time = 5000,
FunctionName = "RepairWindows",
Params = {}
}
}
Pricing rules
Config.Pricing is computed on the server, and surcharges only
ever add to the base cost. The menu shows each part as its own line,
so the player sees exactly what they're paying for.
| Setting | Default | What it does |
|---|---|---|
Currency | "cash" |
'cash', 'bank' or 'choice' (the
player picks at the menu). With cash or bank the server ignores whatever
the client asks for. |
DistanceSurcharge | on | Extra charge the
further the player is from the nearest depot in Config.Depots,
measured from the player's position on the server. No surcharge within
FreeRadius (300 m), then PerMeter ($0.50) beyond
it, capped at Max ($1,500). |
NightSurcharge | on | Multiplies the price
by Multiplier (1.25) between StartHour (22:00,
inclusive) and EndHour (06:00, exclusive) game time. The hour
comes from the player's client, because the server has no game clock of its
own. |
ExpressMultiplier | 1.5 |
Express requests spawn the truck closer and cost this much more. |
How the total is built
In order: base cost, plus the distance surcharge; then the night multiplier; then the express multiplier; then the membership discount comes off. For example, a $1,000 repair requested 700 m from the nearest depot, at night, express, by a Basic member:
Base $1,000
Distance (700 - 300) x 0.5 + $200 = $1,200
Night x 1.25 + $300 = $1,500
Express x 1.5 + $750 = $2,250
Basic member, 50% off - $1,125 = $1,125
Service is free, with no charge at all, when
ChargeForServices is off, when no framework is running
(standalone), for on-duty exempt jobs, for members on a
free-service tier, and for trusted dispatch
tows.
Depots
Config.Depots is a list of named points across the map. It is
only used for the distance surcharge: trucks spawn on a road near the player,
not at these points. The default list covers Los Santos, Sandy Shores,
Paleto Bay, Grapeseed and the Great Ocean Highway.
Config.Depots = {
{ name = "LS Downtown", coords = { x = 216.19, y = -786.03, z = 30.82 } },
-- ...
}
Membership tiers
A AAA-style subscription. Players buy it with
/roadsidemembership buy <tier>, list tiers with
/roadsidemembership tiers, and check their own with
/roadsidemembership check. It's stored in player metadata on
qb/qbx, and in the resource's own key-value store on ESX and standalone.
| Setting | Default | What it does |
|---|---|---|
Enabled | true | Turns memberships on. |
Command | "roadsidemembership" |
The command name. |
MetadataKey | "roadside_membership" |
Where it's stored in player metadata. |
PayCurrency | "bank" | What the membership is paid from. |
AllowRenewal | true | Buying
while a membership is active extends it from its current expiry. Set
false to refuse instead. |
MaxDays | 90 | How far ahead renewals can stack. |
DefaultTier | "basic" | The
tier bought by a plain /roadsidemembership buy. |
Tiers
Each tier in Config.Membership.Tiers has a
Label, Price, DurationDays,
Discount (0 to 1, the fraction taken off the price) and
FreeService (true means members pay nothing). Add,
rename or remove tiers freely. The shipped two:
Tiers = {
basic = {
Label = "Basic",
Price = 5000,
DurationDays = 30,
Discount = 0.5, -- 0-1, fraction taken off the price
FreeService = false, -- true = members pay nothing
},
premier = {
Label = "Premier",
Price = 12000,
DurationDays = 30,
Discount = 0.0,
FreeService = true,
},
},
Other resources can grant, revoke and check memberships through the server exports.
Cooldowns & jobs
Cooldowns
Config.Cooldown is enforced on the server, per character.
Seconds (120) applies after a completed request;
CancelSeconds (30) is a shorter one after the player cancels,
which stops request-and-cancel spam. Enabled = false turns both
off.
Exempt jobs
On-duty players with a job in Config.ExemptJobs are not
charged, and may tow emergency vehicles (see
Towing). Shipped: police,
sasp, ambulance.
Restricted jobs
Config.RestrictedJobs (shipped: bennys) is
only used when Config.Dispatch.Enabled = false.
If any player with one of these jobs is on duty, NPC service is refused
outright so players call the shop instead. With dispatch on, on-duty
mechanics get the call first and the NPC is only the fallback (see
Player-mechanic dispatch).
Towing
The flatbed loads the vehicle and delivers it to the destination the
player chose. Config.Towing.Enabled turns towing on, and with
AllowPlayerChoice = true the player picks the destination in the
service menu. The nearest one is always the default.
Destinations and their three types
| Type | What happens on delivery |
|---|---|
depot | The vehicle is removed from the world. On qb/qbx it can be collected from your garage resource's depot. |
mechanic | The vehicle is left parked at the shop, unlocked. |
garage | The vehicle is stored in the owner's
garage. Garage is the garage id your garage resource uses.
Only offered for vehicles the player owns. |
Destinations = {
{ name = "LS Depot", type = "depot", coords = { x = 407.4, y = -1637.4, z = 29.3 } },
{ name = "Sandy Depot", type = "depot", coords = { x = 1620.9, y = 3308.9, z = 40.6 } },
{ name = "Bennys Mechanic", type = "mechanic", coords = { x = -205.5, y = -1310.5, z = 31.3 } },
-- { name = "Pillbox Garage", type = "garage", Garage = "pillboxgarage", coords = { x = 215.9, y = -809.7, z = 30.7 } },
},
The garage destination ships commented out. To offer
garage delivery, uncomment the Pillbox line (or add your own) and set
Garage to the id your garage resource uses.
Garage storage
Config.Towing.Garage decides how a vehicle delivered to a
garage destination is stored. It runs on the server. Storing it
is supported out of the box on qb-garages, qbx_garages and esx_garage.
Mode | What it does |
|---|---|
'auto' (default) | Use the SQL preset for your framework when a database resource is running. |
'sql' | Always use the SQL in
Garage.Sql. |
'event' | TriggerEvent(Event, src, plate,
garageId, destinationName) on the server. |
'export' | exports[resource][fn](src,
plate, garageId), with Export = "resource:fn". |
'none' | Garages are not offered. |
The SQL presets fill in @plate and @garage. The
qb one matches qb-garages and qbx_garages. The esx
one targets legacy esx_garage; drop , parking = @garage if your
owned_vehicles table has no parking column.
Sql = {
qb = "UPDATE player_vehicles SET state = 1, garage = @garage WHERE TRIM(plate) = @plate",
esx = "UPDATE owned_vehicles SET stored = 1, parking = @garage WHERE TRIM(plate) = @plate",
},
Who may tow what
BlockedClasses({ 18 }): vehicle classes nobody may tow except on-dutyConfig.ExemptJobs. Class 18 is emergency vehicles. Empty the list to allow everything.BlockOtherPlayersVehicles(true): refuses to tow a vehicle registered to another player. Unowned vehicles (locals, job cars) can still be towed. This needs a database resource; if ownership can't be checked, the tow is allowed.
Ride-along
Once the vehicle is loaded, the driver offers the player a lift to the
destination (press E). It's optional and free, and
the player can hop out at any point mid-trip. If the offer isn't accepted
within OfferDuration, the truck leaves without them.
RideAlong setting | Default | What it does |
|---|---|---|
Enabled | true | Turns the offer on. |
OfferDuration | 10000 | Milliseconds the offer stays on screen before the truck departs. |
MaxOfferDistance | 20.0 | Only offer if the player is within this many meters of the truck. |
Delivery
DeliverSpeed (18 m/s) is the truck's speed while towing.
DeliverTimeout (240 seconds) is how long before the towed
vehicle is force-delivered. FadeOnArrival fades the vehicle out
on delivery to a depot or garage. AttachOffset positions the
towed vehicle on the bed.
Player-mechanic dispatch
With Config.Dispatch.Enabled = true, every call goes to
on-duty player mechanics first. They list open calls with
/rcalls, take one by number (/raccept 3), and
complete it at the vehicle with /rcomplete or the
Complete Roadside Service target option on the car. If
nobody accepts in time, or the mechanic doesn't finish within the time
limit, the NPC truck takes over.
| Setting | Default | What it does |
|---|---|---|
Jobs | { "mechanic", "bennys" } |
Jobs that receive calls. |
RequireOnDuty | true | Only on-duty players get calls. |
AcceptTimeout | 30 | Seconds to wait for an accept before the NPC truck is sent. |
ArrivalTimeout | 900 | Seconds an accepting mechanic has to finish before the call goes to the NPC. |
CompleteDistance | 30.0 | The mechanic must be within this many meters of the vehicle to complete. |
PayMechanic | true | The customer's fee is paid to the mechanic. |
PayCurrency | "cash" | What the mechanic is paid in. |
PayToSociety | false | Pay the shop's society account instead (Renewed-Banking, qb-banking, qb-management or esx_addonaccount). If the payment can't be made to the society, it goes to the mechanic instead. |
SocietyAccount | "mechanic" |
The society account to pay. |
Alerts through sals_kewljobnotify
When sals_kewljobnotify is running, calls also alert
mechanics through it. Config.Dispatch.JobNotify sets
Enabled (true), the jobnotify Service
whose jobs are alerted ("tow"), and the notification
Type within that service ("towsummon").
Integrations
Config.Integrations. 'auto' means detect the
installed resource, which is right for nearly every server.
| Setting | Choices | Notes |
|---|---|---|
Menu | 'qb-menu',
'ox_lib', 'auto', 'none' |
'auto' prefers qb-menu, then ox_lib. 'none'
skips the menu and uses NoMenuDefault or the command's
arguments. |
Target | 'ox_target',
'qb-target', 'auto', 'none' |
Optional; every target option also has a command. |
Fuel | 'LegacyFuel',
'ps-fuel', 'cdn-fuel', 'ox_fuel',
'auto', 'none' | |
Notify | 'qb', 'esx',
'ox_lib', 'native', 'auto' |
'auto' follows the framework: qb uses qb notify, qbx uses
ox_lib, esx uses esx notify; ox_lib is used when present, GTA's own
otherwise. |
Progress | 'qb',
'ox_lib', 'native', 'auto' |
Same rules as Notify. |
PhoneCommand | a command name | An extra
command that does the same as the request command
("roadside"). Leave blank for none. |
TrustedResources | resource names | Resources allowed to make a request free through the
RequestService export. Shipped:
{ "sals_kewldispatch" }. Any other resource may still call it,
but is charged normally (see
Dispatch-requested tows). |
Phone resources can also call
exports['sals_kewlroadsideassist']:RequestAssist() to open the
request flow (see For developers).
Restricted zones
Places where roadside assistance isn't available. Each zone in
Config.RestrictedZones has a label,
coords and a radius in meters, and is checked on the
client and against the player's position on the server, so it can't
be bypassed. The player is told the zone's name.
Config.RestrictedZones = {
{ label = "Bolingbroke Penitentiary", coords = { x = 1845.0, y = 2585.0, z = 45.0 }, radius = 250.0 },
{ label = "Fort Zancudo", coords = { x = -2360.0, y = 3244.0, z = 32.0 }, radius = 500.0 },
}
Arrival & the HUD
Arrival interaction
When the NPC truck arrives, the mechanic gets out, walks up, greets the
player by time of day, faces them, and plays a short translated
conversation with voice lines and gestures. If the player can't be reached
(on a roof, in the water, or down), the mechanic skips the chat and gets on
with the work. Everything is in Config.Interaction:
| Setting | Default | What it does |
|---|---|---|
Enabled | true | Turns the arrival interaction on. |
ApproachPlayer | true | The mechanic exits the truck and walks up to the player. |
FacePlayer / TurnPlayerToFace |
true | The speakers turn to face each other; also rotate the player toward the mechanic. |
Speech / Gestures | true |
Vanilla ambient voice lines; greeting and talking gestures. |
LineDuration | 3500 | Milliseconds each line of dialogue stays on screen. |
ApproachDistance | 1.6 | How close the mechanic stops. |
MaxTalkDistance | 30.0 | Skip the chat if the player is farther than this on arrival. |
WalkUpAttempts / WalkUpTimeout |
2 / 8000 | How many times the mechanic tries to walk up, and how long each try may take (ms). |
MaxDuration | 30000 | Hard ceiling on the whole interaction, in ms. If the greeting hasn't finished, the service starts anyway. |
Text | font 4, scale 0.42 | Style of the floating dialogue text. |
The dialogue lines live in locales/<lang>.lua under
dialogue, in three sets (tow, repair, default), so you can
translate or rewrite them.
Dispatch HUD
A passive status card in the top-left corner (requested, en route, on
scene, service, complete) that never steals input.
Config.HUD.Enabled turns it on; Title = nil uses
the locale's hud_title (“Roadside Assistance”);
HideDelay (6000 ms) is how long the “Complete” card
lingers before hiding.
Repair animation
Config.RepairAnimation sets the animation the mechanic plays
while working (mini@repair / fixing_a_ped) and which
doors open during a repair: hood, trunk and all four side doors by
default.
Discord logging
Optional, and off by default. Logs completed calls to a Discord webhook, and canceled and failed calls with the reason. It runs on the server.
Config.Discord = {
Enabled = false,
Webhook = "",
BotName = "Roadside Assist",
Avatar = "",
Color = 3066993, -- green
LogCancels = true, -- also log canceled and failed calls, with the reason
}
Set Enabled = true and paste your webhook URL into
Webhook. Set LogCancels = false to log completed
calls only.
The trucks & server-side spawning
Server-side spawning
By default the truck and driver are created by the player's own game.
If your server runs sv_entityLockdown, which
blocks client-created entities, set
Config.TowTruckSettings.ServerSpawn = true so they're spawned on
the server instead. This requires OneSync.
Truck behavior
Config.TowTruckSettings controls how the truck gets to the
scene. The settings most owners touch:
| Setting | Default | What it does |
|---|---|---|
MaxSpawnAttempts | 3 | How many times a truck is spawned before giving up. |
FallbackRepair | true | Repair in place if the truck can't reach the vehicle. |
ArrivalTimeout | 400 | Seconds
for the truck to arrive before giving up on this spawn. Keep
Config.IdleTimeout longer than this. |
DrivingSpeed | 18.0 | Cruise
speed (m/s) on the way to the scene. The truck slows within
ApproachDistance and crawls within
FinalApproachDistance, so it never rams the stranded
vehicle. |
SpawnOffset / ExpressSpawnOffset |
200 m / 80 m | How far away the truck spawns; express spawns
closer. Never closer than MinSpawnDistance (30 m). |
StuckThreshold | 20 | Seconds
stuck before giving up on this spawn. Within
StuckArriveDistance (28 m) it just parks and the driver walks
the rest. |
EmergencyLights | on scene only | Light bar
DuringResponse and OnScene. |
Providers
Config.Providers lists the tow companies. The first is the
standard provider; the one marked Express = true handles express
calls. Each has TruckModels, FlatbedModels (used
for the Tow service), PedModels and a map Blip.
Pricing lives in Config.Pricing, not here.
{
Name = "AAA Roadside",
TruckModels = { "towtruck" },
FlatbedModels = { "flatbed" }, -- used for the Tow service
PedModels = { "s_m_m_autoshop_02" },
Blip = { Sprite = 68, Color = 61, Scale = 0.75 },
},
Dispatch-requested tows
Police dispatch can send a tow truck to a scene. Another resource, such as Sal's Kewl Dispatch 3.12.0 or later, asks for a truck on an officer's behalf, and the truck drives to that officer. On-duty player mechanics get first refusal exactly as they do for a normal call.
Who pays
A dispatch tow is free only when the resource asking for it is
listed in Config.Integrations.TrustedResources
(shipped: sals_kewldispatch). Any other resource can still ask,
but the officer is charged normally. A trusted (free) call doesn't put the
officer on cooldown when it completes.
Which vehicle gets towed
- The officer names the plate. If the plate matches a
vehicle within
Config.ServiceRequests.PlateSearchRadius(75 m) of the officer, that vehicle is used. - Or the officer picks it on scene. When no plate was
named, or it didn't match, the tow driver asks which vehicle to take once
the truck arrives. The officer uses the Have the tow take this
one target option on it, or stands next to it and types
/towthis. They haveSelectTimeout(180 seconds) to choose. - Or the driver picks the obvious car (since 2.0.1).
An officer busy with a stop often never points at one. Once the truck is
on scene and nobody has picked within
Config.ServiceRequests.AutoPickAfter(25 seconds), the driver takes the nearest vehicle withinAutoPickRadius(20 m) of the officer that is not the officer’s own, not another player’s and not already assigned to a truck, and the officer is told which.AutoPickAfter = 0keeps the picker and its timeout as before. - The officer's own vehicle is never assumed at a scene. When the officer's own car is the one broken down (they aren't on a call), dispatch says so and the truck comes for the car they're in, or last drove.
When the problem isn't known yet
If the officer said what's needed ("needs a jump", "flat tire"), dispatch sends that service. If not, dispatch asks for a general roadside call, and nobody has to ask over the radio:
- The officer gets a service picker on screen, with the
likely problem already checked and no prices on a free call. They tick
what's needed and send it, or choose Let the driver decide on
scene. The picker closes after
Config.ServiceRequests.PickerTimeout(20 seconds) or when the truck arrives, andShowPicker = falseturns it off. It needs qb-menu or ox_lib. - Otherwise the driver works it out on arrival from the
vehicle's own state, using
Config.ServiceRequests.Resolve. A wrecked or undriveable engine (belowTowBelowEngineHealth, 100) means Tow and nothing else. Otherwise a locked vehicle means Lockout, a flat tire means Tire Change, an empty tank (belowFuelBelow, 15%) means Fuel, a stalled engine (belowJumpStartBelowEngineHealth, 400) means Jump Start, and body damage (belowRepairBelowBodyHealth, 950) means Repair, which is also the answer when nothing else is found.
When a dispatch tow falls through
If a dispatch tow ends without the vehicle moved — the driver could not find it, no truck could be sent — the resource that asked hears about it and can send another. Sal’s Kewl Dispatch 3.13.0 or later asks your callout script for a tow in its place and tells the officer on the air; on a Public Safety Simulator call (0.0.16-beta or later) the simulator sends its own truck. A tow the officer canceled is left alone.
Config.ServiceRequests.Enabled = false turns
dispatch-requested calls off entirely.
For developers
This section is for whoever writes the resource that integrates with roadside assist. Server owners can skip it.
Client exports
-- Open the request flow for the local player (e.g. from a phone app).
exports['sals_kewlroadsideassist']:RequestAssist({ services = { 'Repair' }, express = false })
TriggerEvent('sals_kewlroadsideassist:client:request', { services = { 'Tow' } })
exports['sals_kewlroadsideassist']:HasActiveRequest() --> boolean
Server exports
exports['sals_kewlroadsideassist']:IsPlayerMember(src) --> boolean
exports['sals_kewlroadsideassist']:GetMembership(src) --> { tier, label, expiry } or nil
exports['sals_kewlroadsideassist']:GrantMembership(src, tier, days) --> boolean
exports['sals_kewlroadsideassist']:RevokeMembership(src) --> boolean
exports['sals_kewlroadsideassist']:HasActiveRequest(src) --> boolean
Sending a truck on a player's behalf: RequestService
local res = exports['sals_kewlroadsideassist']:RequestService(src, service, {
free = true, -- honored only for Config.Integrations.TrustedResources
plate = 'ABC123', -- optional, up to 12 characters
description = 'blue sedan in the median', -- optional, up to 80 characters
unitName = '1-Adam-12', -- optional, up to 32 characters
additional = false, -- true: a DIFFERENT vehicle; send another truck alongside
forSelf = false, -- true: the requester's OWN vehicle (an officer not on a call)
requestedBy = 'sals_kewldispatch',
})
-- { accepted = true, requestId = '...', mode = 'pending' | 'npc', vehicleResolved = true | false,
-- etaSeconds = 75, openCount = 1 }
-- { accepted = false, reason = '...' }
- It returns immediately. Nothing in the call waits. The truck, the drive and finding the vehicle all happen afterward and are reported through the lifecycle event.
service:tow,repair,fuel,wash,upright,tires,jumpstart,lockout, orroadsidewhen the problem isn't known yet (resolved on arrival as described above).vehicleResolvedis false whenplatewas omitted or didn't match a vehicle withinPlateSearchRadius; the requester then picks the vehicle on scene.- Payment.
free = trueis honored only when the calling resource is listed inConfig.Integrations.TrustedResources. Any other resource may still call the export, but the requester is charged normally. Location always comes from the server. - More than one vehicle. Without
additional, a second request while one is open is refused asalready_active. Withadditional = trueit gets its own truck, running alongside the first, up toConfig.ServiceRequests.MaxPerRequester(default 3). If the plate matches a vehicle a truck is already coming for, it's the same tow and comes backalready_active. A player's own/roadsideassistrequest stays one at a time. already_activesays what's open: the matching (or newest) request's summary, includingrequestId,status,etaSeconds,plateanddescription, is flattened into the result, andopenlists every open request.etaSecondsis the best current estimate of seconds until the truck arrives. It starts from the spawn distance, is replaced by the truck's measured distance once it's driving (about every 10 seconds), counts down in between, and is 0 on arrival.
Refusal reason | Meaning |
|---|---|
not_enabled | Config.ServiceRequests.Enabled is off |
unknown_service | service isn't one of the names above |
already_active | The requester already has a call open (or one for this vehicle); details included |
limit_reached | additional asked for, but MaxPerRequester trucks are already coming |
cooldown_active | The requester is on cooldown |
no_player | src isn't a connected player |
restricted_zone | The requester is inside a restricted zone |
restricted_job_active | A restricted job is on duty (dispatch off) |
cannot_afford | Not free, and the requester can't pay |
exports['sals_kewlroadsideassist']:CancelService(src, requestId) --> boolean; one request, or all if requestId is nil (refunds anything paid)
exports['sals_kewlroadsideassist']:GetActiveRequests(src) --> list of summaries, oldest first ({} when none)
exports['sals_kewlroadsideassist']:GetActiveRequest(src) --> the newest summary, or nil
-- summary = { requestId, callNo, status, etaSeconds, plate, description, unitName, services,
-- generic, provider, mechanic, vehicleResolved, free, paid, origin, createdAt }
Lifecycle event
Fired on the server for every call, player or resource, so any resource can follow along:
AddEventHandler('sals_kewlroadsideassist:requestUpdate', function(src, requestId, status, extra) end)
status | Meaning | extra |
|---|---|---|
mechanic_assigned | a player mechanic accepted | mechanic |
npc_dispatched | the NPC truck is being sent | |
enroute | the truck is driving to the scene | eta (seconds) |
awaiting_vehicle | on scene, waiting for the requester to pick the vehicle | |
vehicle_selected | the vehicle was picked | plate |
arrived | on scene, services decided | services |
loading / transport | tow in progress | |
completed | done | |
cancelled / failed | ended without the work being done | reason |
Every status except completed, cancelled and
failed also carries etaSeconds.
(cancelled is spelled that way because it is part of the
published contract.) Statuses reported by a client are checked against the
active request and a fixed list before they are passed on.
Troubleshooting
Start with the console line the resource prints at startup
(framework: … | database: … | garages: …) and any
warning or note lines after it. They say plainly
when something is missing or the config contradicts itself.
| Symptom | Fix |
|---|---|
| Nothing happens, or players see “Roadside assistance didn't respond” | The server never answered the request. Check that the folder is named
exactly sals_kewlroadsideassist, that the startup line
printed, and that it starts after your framework in
server.cfg. Then look in the server console for a
[sals_kewlroadsideassist] rpc '…' failed line, which names
the error. |
| Lockout is always refused (“ownership can't be checked”) | No database resource (oxmysql, mysql-async or ghmattimysql) is running, or it starts after this resource. Lockout only
unlocks vehicles the player owns, and without the database it refuses
rather than risk unlocking someone else's car. The startup line shows
database: none and a note says so. Start your database resource first, or
set Config.Services.Lockout.RequireOwnership = false if you
accept the risk. |
| The garage destination isn't offered | All of these must be true: a type = "garage" destination
exists with a Garage id (the shipped one is commented out);
Config.Towing.Garage.Mode isn't 'none', and in
'auto' or 'sql' mode a database resource is running (the
startup line shows garages: off otherwise); and the player
owns the vehicle. Garages are never offered for a car the player doesn't
own. |
| A towed car didn't show up in the garage | The console prints could not store plate … in garage '…' --
check Config.Towing.Garage. Check the Garage id
matches your garage resource, and that the SQL preset matches your
table (for esx, drop , parking = @garage if there's no
parking column). |
| NPC trucks are canceled before they arrive | Config.IdleTimeout isn't longer than
Config.TowTruckSettings.ArrivalTimeout, so a slow truck is
canceled before it can retry or fall back to an in-place repair. The
startup log warns: Config.IdleTimeout (…) is not longer than
TowTruckSettings.ArrivalTimeout (…). The defaults (600 and 400) are
fine; an upgraded 1.x config may carry an old, shorter value. |
| Nothing spawns: no truck, no driver | Your server probably runs sv_entityLockdown, which blocks
entities created by players' games. Set
Config.TowTruckSettings.ServerSpawn = true (requires
OneSync). |
| Everything is free | No framework was detected (standalone), and the startup log says
“services are free”. Otherwise check
Config.Services.ChargeForServices, whether the player has an
on-duty ExemptJobs job, or a free-service membership. |
| Players are told a local mechanic is handling it, and no truck comes | Dispatch is off and someone with a Config.RestrictedJobs
job is on duty, so NPC service is refused on purpose. Empty the list, or
turn on Config.Dispatch so mechanics get the call first with
the NPC as fallback. |
| The request was canceled while the player waited | They went more than Config.DespawnDistance (150 m) from
the vehicle. The call is refunded. |
| A request is stuck | The player can run /roadsidecleanup to clear their own,
with a refund. Admins can run /roadsidecleanup <id>
for any player, which also clears that player's cooldown, and
/roadsidestatus lists active calls. |
| A dispatch tow charged the officer | The calling resource isn't in
Config.Integrations.TrustedResources. Add its exact resource
name. |
Support
Support and release news are on Discord. All owner's manuals are at salskewlkorner.com/documentation. The resource ships with its own README and CHANGELOG; the CHANGELOG calls out breaking config changes explicitly.