Sal's Kewl Korner Documentation / Sal's Kewl Roadside Assist Discord

Sal's Kewl Roadside Assist — owner's manual

AAA-style roadside assistance for FiveM. Players call for help, see a real price before they confirm, and an NPC tow truck or an on-duty player mechanic comes to the scene. This manual covers installation, upgrading, configuration and troubleshooting for server owners.

documents resource version 2.0.1

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

  1. Request. The player runs /roadsideassist (or a phone app, or another resource, asks on their behalf) in or near their vehicle.
  2. 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.
  3. Pay at confirm. The player pays the moment they confirm, not when the work is finished.
  4. 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.
  5. 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.
  6. 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.
  7. 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:

AreaSupported
Frameworkqb-core, qbx_core, es_extended, or none (standalone: services are free)
Menuqb-menu, ox_lib. Without either, players name the services on the command, or Config.Services.NoMenuDefault is used
Notifications / progress barsqb-core, ox_lib, ESX, or GTA's own
Targetingox_target, qb-target (optional; commands work without)
FuelLegacyFuel, ps-fuel, cdn-fuel, ox_fuel
Databaseoxmysql, mysql-async or ghmattimysql, for vehicle-ownership checks and garage storage. Without it, ownership isn't checked and lockout is refused
Society payoutsRenewed-Banking, qb-banking, qb-management, esx_addonaccount
Alertssals_kewljobnotify

Installation

  1. Extract the resource into your server's resources/ folder. The folder must be named sals_kewlroadsideassist.
  2. Add it to server.cfg after your framework (and after your database resource, ox_lib and your target resource, if you use them):
    ensure sals_kewlroadsideassist
  3. 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.
  4. 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.GarageIntegration is replaced by Config.Towing.Garage. An old Event setting is still honored, but it is now triggered on the server, as TriggerEvent(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.TowTruckSpawnPoints is now Config.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 (use Config.Pricing.ExpressMultiplier), Config.TowPedModels, Config.TowTruckModels.
  • /roadsidecleanup no longer clears a player's own cooldown. Admins passing a player id still can.
  • Config.IdleTimeout now defaults to 600 and only applies while waiting for the NPC truck. Keep it longer than Config.TowTruckSettings.ArrivalTimeout.
  • New services (tire change, jump start, lockout) only appear if your config defines them. Copy those blocks from the new config.lua into yours.

Commands

CommandWhoWhat
/roadsideassist [services] [express]Players Request roadside assistance. With no menu resource, name the services: /roadsideassist repair fuel
/roadsideassistcancelPlayers Cancel your request (refunded)
/roadsidemembership [check | tiers | buy <tier>] PlayersCheck, list or buy a membership
/rcallsMechanicsList open roadside calls
/raccept [number]MechanicsAccept a call (the oldest if no number is given)
/rcompleteMechanicsComplete your call (you must be at the vehicle)
/towthisOfficersPick 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
/roadsidestatusAdmins (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:

SettingCommands
Config.CommandsRequest, Cancel
Config.Dispatch.CommandsCalls, Accept, Complete
Config.Membership.CommandThe membership command
Config.AdminCleanupCommand, StatusCommand
Config.ServiceRequests.SelectCommandThe /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:

SettingDefaultWhat it does
Config.Locale"en"Language. Shipped: en, es, fr, de, pt-br. Must match a file in locales/.
Config.AllowNearVehicletrue 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.NearVehicleRadius5.0 Meters, used when AllowNearVehicle is on.
Config.DespawnDistance150.0 If the player goes this far from the vehicle while help is on the way, the request is canceled (and refunded).
Config.IdleTimeout600 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.

KeyMenu labelCostTime
RepairRepair$1,00010 s
FuelRefuel (Recharge for EVs)$5005 s
WashWash$2503 s
UprightVehicleUpright Vehicle$7508 s
TiresTire Change$4006 s
JumpStartJump Start$3004 s
LockoutLockout$3505 s
TowTow / Recover$1,5006 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.

SettingDefaultWhat 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.
DistanceSurchargeonExtra 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).
NightSurchargeonMultiplies 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.
ExpressMultiplier1.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.

SettingDefaultWhat it does
EnabledtrueTurns 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.
AllowRenewaltrueBuying while a membership is active extends it from its current expiry. Set false to refuse instead.
MaxDays90How 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

TypeWhat happens on delivery
depotThe vehicle is removed from the world. On qb/qbx it can be collected from your garage resource's depot.
mechanicThe vehicle is left parked at the shop, unlocked.
garageThe 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.

ModeWhat 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-duty Config.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 settingDefaultWhat it does
EnabledtrueTurns the offer on.
OfferDuration10000Milliseconds the offer stays on screen before the truck departs.
MaxOfferDistance20.0Only 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.

SettingDefaultWhat it does
Jobs{ "mechanic", "bennys" } Jobs that receive calls.
RequireOnDutytrueOnly on-duty players get calls.
AcceptTimeout30Seconds to wait for an accept before the NPC truck is sent.
ArrivalTimeout900Seconds an accepting mechanic has to finish before the call goes to the NPC.
CompleteDistance30.0The mechanic must be within this many meters of the vehicle to complete.
PayMechanictrueThe customer's fee is paid to the mechanic.
PayCurrency"cash"What the mechanic is paid in.
PayToSocietyfalsePay 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.

SettingChoicesNotes
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.
PhoneCommanda command nameAn extra command that does the same as the request command ("roadside"). Leave blank for none.
TrustedResourcesresource 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:

SettingDefaultWhat it does
EnabledtrueTurns the arrival interaction on.
ApproachPlayertrueThe mechanic exits the truck and walks up to the player.
FacePlayer / TurnPlayerToFace trueThe speakers turn to face each other; also rotate the player toward the mechanic.
Speech / Gesturestrue Vanilla ambient voice lines; greeting and talking gestures.
LineDuration3500Milliseconds each line of dialogue stays on screen.
ApproachDistance1.6How close the mechanic stops.
MaxTalkDistance30.0Skip the chat if the player is farther than this on arrival.
WalkUpAttempts / WalkUpTimeout 2 / 8000How many times the mechanic tries to walk up, and how long each try may take (ms).
MaxDuration30000Hard ceiling on the whole interaction, in ms. If the greeting hasn't finished, the service starts anyway.
Textfont 4, scale 0.42Style 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:

SettingDefaultWhat it does
MaxSpawnAttempts3How many times a truck is spawned before giving up.
FallbackRepairtrueRepair in place if the truck can't reach the vehicle.
ArrivalTimeout400Seconds for the truck to arrive before giving up on this spawn. Keep Config.IdleTimeout longer than this.
DrivingSpeed18.0Cruise 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 mHow far away the truck spawns; express spawns closer. Never closer than MinSpawnDistance (30 m).
StuckThreshold20Seconds stuck before giving up on this spawn. Within StuckArriveDistance (28 m) it just parks and the driver walks the rest.
EmergencyLightson scene onlyLight 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 have SelectTimeout (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 within AutoPickRadius (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 = 0 keeps 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, and ShowPicker = false turns 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 (below TowBelowEngineHealth, 100) means Tow and nothing else. Otherwise a locked vehicle means Lockout, a flat tire means Tire Change, an empty tank (below FuelBelow, 15%) means Fuel, a stalled engine (below JumpStartBelowEngineHealth, 400) means Jump Start, and body damage (below RepairBelowBodyHealth, 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, or roadside when the problem isn't known yet (resolved on arrival as described above).
  • vehicleResolved is false when plate was omitted or didn't match a vehicle within PlateSearchRadius; the requester then picks the vehicle on scene.
  • Payment. free = true is honored only when the calling resource is listed in Config.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 as already_active. With additional = true it gets its own truck, running alongside the first, up to Config.ServiceRequests.MaxPerRequester (default 3). If the plate matches a vehicle a truck is already coming for, it's the same tow and comes back already_active. A player's own /roadsideassist request stays one at a time.
  • already_active says what's open: the matching (or newest) request's summary, including requestId, status, etaSeconds, plate and description, is flattened into the result, and open lists every open request.
  • etaSeconds is 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 reasonMeaning
not_enabledConfig.ServiceRequests.Enabled is off
unknown_serviceservice isn't one of the names above
already_activeThe requester already has a call open (or one for this vehicle); details included
limit_reachedadditional asked for, but MaxPerRequester trucks are already coming
cooldown_activeThe requester is on cooldown
no_playersrc isn't a connected player
restricted_zoneThe requester is inside a restricted zone
restricted_job_activeA restricted job is on duty (dispatch off)
cannot_affordNot 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)
statusMeaningextra
mechanic_assigneda player mechanic acceptedmechanic
npc_dispatchedthe NPC truck is being sent
enroutethe truck is driving to the sceneeta (seconds)
awaiting_vehicleon scene, waiting for the requester to pick the vehicle
vehicle_selectedthe vehicle was pickedplate
arrivedon scene, services decidedservices
loading / transporttow in progress
completeddone
cancelled / failedended without the work being donereason

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.

SymptomFix
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.