qb-lapraces
qb-lapraces lets authorized players build lap race tracks with a checkpoint editor, save them to
the database, and run races with laps, leaderboards, and per-track records.
It is a persistent track system, which is what distinguishes it from qb-streetraces. Races are created, joined, and managed through the phone, so qb-phone is a practical requirement.
The manifest at the commit linked above declares version 1.5.0.
Dependencies and start order
From the manifest: config.lua as a shared script, client.lua on the client,
@oxmysql/lib/MySQL.lua and server.lua on the server, with ui_page 'html/index.html'.
From the implementation: qb-core for the core object, commands and permissions, and qb-phone
for qb-phone:client:UpdateLapraces and qb-phone:client:RaceNotify. Without a phone resource
listening, races still work but players get no notifications and no list.
Start order: oxmysql, qb-core, qb-phone, then qb-lapraces.
Database
The repository ships qb-lapraces.sql:
CREATE TABLE IF NOT EXISTS `lapraces` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`checkpoints` text DEFAULT NULL,
`records` text DEFAULT NULL,
`creator` varchar(50) DEFAULT NULL,
`distance` int(11) DEFAULT NULL,
`raceid` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8mb4;checkpoints and records hold JSON. creator holds a citizenid, and the server joins against
the players table to display the creator’s name.
Tracks are loaded from this table into an in-memory Races table at resource start. Live race state
is memory only, so a restart mid race loses the race but not the track.
Configuration
config.lua is two keys:
| Key | Default | Meaning |
|---|---|---|
Config.WhitelistedCreators | { 'PUTCID' } | Citizen IDs allowed to create tracks |
Config.RaceSetupAllowed | true | Global switch for starting a race setup |
The shipped 'PUTCID' is a placeholder. Replace it with real citizen IDs or leave the list empty.
Authorization is not whitelist-only. IsWhitelisted also returns true when
QBCore.Functions.GetPermission(source) is admin or god, so administrators can always create
tracks regardless of the list.
At this commit IsWhitelisted resolves the player with GetPlayerByCitizenId and then indexes
Player.PlayerData.source without a nil check, so calling it for an offline citizen ID raises a
server-side Lua error.
Exports
Two client exports, both read-only state queries:
| Export | Returns |
|---|---|
exports['qb-lapraces']:IsInEditor() | Whether the local player is in the track editor |
exports['qb-lapraces']:IsInRace() | Whether the local player is in a race |
Integration example
Suppressing your own HUD or interaction prompts while a player is racing:
-- client side, inside your own resource
CreateThread(function()
local wasBusy = false
while true do
local isBusy = exports['qb-lapraces']:IsInRace() or exports['qb-lapraces']:IsInEditor()
if isBusy ~= wasBusy then
wasBusy = isBusy
TriggerEvent(isBusy and 'myresource:client:hideInteractions'
or 'myresource:client:showInteractions')
end
Wait(1000)
end
end)The exports are plain state reads, so the only thing worth acting on is the transition. Firing the
event on every tick would send the same event sixty times a minute for the length of a race, and a
version without the showInteractions branch would leave your prompts hidden after the race ends.
Server surface
| Handler | Kind | Arguments |
|---|---|---|
qb-lapraces:server:CreateLapRace | net event | RaceName |
qb-lapraces:server:SaveRace | net event | RaceData |
qb-lapraces:server:SetupRace | net event | RaceId, Laps |
qb-lapraces:server:JoinRace | net event | RaceData |
qb-lapraces:server:LeaveRace | net event | RaceData |
qb-lapraces:server:StartRace | net event | RaceId |
qb-lapraces:server:CancelRace | net event | raceId |
qb-lapraces:server:UpdateRaceState | net event | RaceId, Started, Waiting |
qb-lapraces:server:UpdateRacerData | net event | RaceId, Checkpoint, Lap, Finished |
qb-lapraces:server:FinishPlayer | net event | RaceData, TotalTime, TotalLaps, BestLap |
qb-lapraces:server:GetRaces | callback | none |
qb-lapraces:server:GetListedRaces | callback | none |
qb-lapraces:server:GetRacingData | callback | RaceId |
qb-lapraces:server:GetTrackData | callback | RaceId |
qb-lapraces:server:GetRacingLeaderboards | callback | none |
qb-lapraces:server:HasCreatedRace | callback | none |
qb-lapraces:server:IsAuthorizedToCreateRaces | callback | TrackName |
qb-lapraces:server:CanRaceSetup | callback | none |
CreateLapRace checks IsWhitelisted before opening the editor, which is the one authorization gate
in the set.
Race progress is reported by the client. UpdateRacerData and FinishPlayer accept checkpoint,
lap, and lap time values from the client and write them straight into the in-memory race state and,
for records, into the lapraces table. Nothing validates that a reported lap time is physically
possible or that the checkpoints were passed in order.
That is acceptable for a social race system where the leaderboard is flavour. It is not acceptable if you attach money or items to finishing positions. If you do, validate checkpoint order and minimum lap times on the server first, and only then pay out. See Server functions for the helpers available there.
Commands
/cancelrace is registered through QBCore.Commands.Add with the default user permission and
cancels the caller’s ongoing race.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| ”Not authorized to create races” | The citizen ID is not in Config.WhitelistedCreators and the account is not admin or god. The shipped 'PUTCID' is a placeholder |
| Server Lua error on an authorization check | IsWhitelisted indexes an offline player’s PlayerData |
| Tracks disappear after a restart | The lapraces table was not created, so saves failed |
| Races vanish on restart but tracks survive | Expected. Live race state is memory only |
| No race notifications | qb-phone is not started, or is a fork without those events |
| Implausible leaderboard times | Lap times are reported by the client and stored unvalidated |
Proposed manual smoke test
Not yet executed. On staging, with a database backup:
- Confirm the
lapracestable exists before starting the resource. - Add a real citizen ID to
Config.WhitelistedCreatorsand confirm the editor opens for it. - Build and save a track, restart the server, and confirm the track reloads.
- Run a two-player race and confirm finishing order and the records entry.
- Cancel a race mid run with
/cancelraceand confirm both clients recover.