Race_client.lua

local curCheckpoint, nextCheckpoint
local goGoGo

local playerCar
local weFinished
local resultsShown

local checkpoints = {}
local playerScores = {}

local function initializeMap()
    echo("[RACE] initializeMap\n")

    TriggerServerEvent('race:updateCheckpoints', checkpoints)
end

local function resetGameMode()
    curCheckpoint = nil
    nextCheckpoint = nil

    SetMultiplayerHudTime('')

    checkpointCount = 0
    playerScores = {}

    if IsThisMachineTheServer() then
        -- load the initial map
        initializeMap()
    end

    goGoGo = false
    weFinished = false
    resultsShown = false
end

local function updatePositions()
    local players = {}

    for id, data in pairs(playerScores) do
        data.playerId = id

        table.insert(players, data)
    end

    table.sort(players, function(a, b)
        if a.finishPosition or b.finishPosition then
            if not b.finishPosition then
                return true
            end

            if not a.finishPosition then
                return false
            end

            return a.finishPosition < b.finishPosition
        end

        if a.cp == b.cp then
            local aPed = a.ped
            local bPed = b.ped

            local aPos
            local bPos

            if not DoesCharExist(aPed) or not DoesCharExist(bPed) then
                aPos = { 0, 0 }
                bPos = { 0, 0 }
            else
                aPos = a.ped.position
                bPos = b.ped.position
            end

            local nextCp = checkpoints[a.cp + 1]

            if not nextCp then
                return a.cp > b.cp
            end

            local aDist = GetDistanceBetweenCoords2d(aPos[1], aPos[2], nextCp.pos[1], nextCp.pos[2])
            local bDist = GetDistanceBetweenCoords2d(bPos[1], bPos[2], nextCp.pos[1], nextCp.pos[2])

            return aDist < bDist
        end

        return a.cp > b.cp
    end)

    if not playerScores[GetPlayerId().serverId] then
        return
    end

    local lastPosition = selfLastPosition

    local i = 1

    for _, v in ipairs(players) do
        playerScores[v.playerId].position = i

        i = i + 1
    end

    local selfPosition = playerScores[GetPlayerId().serverId].position
    selfLastPosition = selfPosition

    if selfPosition ~= lastPosition then
        TriggerEvent('chatMessage', '', { 0, 0, 0 }, 'position changed to ' .. tostring(selfPosition) .. ' from ' .. tostring(lastPosition))
    end


    -- positions updated, we hope
end

AddEventHandler('race:onPlayerFinished', function(player, data)
    local selfId = GetPlayerId().serverId

    if not playerScores[player] then
        local ped = sPlayer.ped

        playerScores[player] = {
            cp = #checkpoints,
            ped = ped,
            vehicle = ped.vehicle
        }
    end

    playerScores[player].finishPosition = data.position

    if selfId == player then
        exports.obituary:printObituary('New world record!')

        TriggerEvent('chatMessage', '', { 0, 0, 0 }, 'you finished!')

        weFinished = true

        tearDownCheckpoint(curCheckpoint)
        tearDownCheckpoint(nextCheckpoint)

        -- todo: spectate?

        CreateThread(function()
            Wait(500)

            if playerCar then
                FreezeCarPosition(playerCar, true)
            end
        end)
    end

    local sPlayer = GetPlayerByServerId(player)

    if sPlayer then
        exports.obituary:printObituary('<b>%s</b> finished in %s seconds', sPlayer.name, tostring(data.finishSeconds))
    end
end)

AddEventHandler('onClientGameTypeStart', function()
    CreateThread(function()
        --[[while true do
            Wait(500)

            local player = GetPlayerId()

            TriggerServerEvent('race:updatePos', player.ped.position)
        end]]
    end)

    CreateThread(function()
        while true do
            Wait(250)

            updatePositions()
        end
    end)
end)

function GetPlayerInteger(i)
    local serverId = i.serverId
    local players = GetPlayers()

    for k, v in ipairs(players) do
        if v.serverId == serverId then
            return k
        end
    end

    return 1
end

local function spawnVehicle(spawnPoint)
    local carModel

    if not spawnPoint.carModel then
        carModel = 'admiral'
    else
        carModel = spawnPoint.carModel
    end

    if not tonumber(carModel) then
        carModel = GetHashKey(carModel, _r)
    end

    -- is the model actually a model?
    if not IsModelInCdimage(carModel) then
        error("invalid spawn model")
    end

    -- is is even a vehicle?
    if not IsThisModelAVehicle(carModel) then
        error("this model ain't a vehicle!")
    end

    -- spawn a vehicle for our lovely player
    RequestModel(carModel)
    LoadAllObjectsNow()

    playerCar = CreateCar(carModel, spawnPoint.x, spawnPoint.y, spawnPoint.z, 0, 1)
    SetCarHeading(playerCar, spawnPoint.heading)
    SetCarOnGroundProperly(playerCar)

    WarpCharIntoCar(GetPlayerId().ped, playerCar)

    if not goGoGo then
        FreezeCarPosition(playerCar, true)
    end

    LockCarDoors(playerCar, 4)

    -- and done, hopefully.
end

AddEventHandler('race:itsGoTime', function()
    if playerCar then
        -- let go of the brakes
        FreezeCarPosition(playerCar, false)
    end

    -- gogogo
    goGoGo = true
end)

string.lpad = function(str, len, char)
    if char == nil then char = ' ' end
    return string.rep(char, len - #str) .. str
end

AddEventHandler('race:results', function(time)
    if playerCar then
        FreezeCarPosition(playerCar, true)
    end

    tearDownCheckpoint(curCheckpoint)
    tearDownCheckpoint(nextCheckpoint)

    SetMultiplayerHudTime('')

    updatePositions()

    local players = {}

    for id, data in pairs(playerScores) do
        table.insert(players, data)
    end

    table.sort(players, function(a, b) return a.position < b.position end)

    TriggerEvent('chatMessage', '', { 0, 0, 0 }, 'RESULTS')

    for i, p in ipairs(players) do
        local name = '**INVALID**'
        local sp = GetPlayerByServerId(p.playerId)

        if sp then
            name = sp.name
        end

        TriggerEvent('chatMessage', '', { 0, 0, 0 }, tostring(i) .. '. ' .. name)
    end
end)

AddEventHandler('race:hurryUp', function(time)
    CreateThread(function()
        echo("resultsShown: " .. tostring(resultsShown) .. " , weF: " .. tostring(weFinished) .. "\n")

        while not resultsShown and not weFinished do
            Wait(1000)

            time = time - 1000

            SetMultiplayerHudTime('00:' .. tostring(math.floor(time / 1000)):lpad(2, '0'))
            echo(tostring(math.floor(time / 1000)):lpad(2, '0') .. ':' .. tostring(math.floor((time % 1000) / 100)):lpad(2, '0') .. "\n")
        end
    end)
end)

AddEventHandler('race:showGoMessage', function(message)
    TriggerEvent('chatMessage', '', { 0, 0, 0 }, message)
end)

AddEventHandler('onClientMapStart', function(res)
    resetGameMode()

    requestedGo = true

    TriggerServerEvent('race:requestGo')
end)

AddEventHandler('onClientMapStop', function(res)
    DoScreenFadeOut(50)
end)

AddEventHandler('race:weGotPorn', function()
    echo("[RACE] race:weGotPorn\n")

    if not requestedGo then
        return
    end

    requestedGo = false

    exports.spawnmanager:setAutoSpawn(false)

    exports.spawnmanager:spawnPlayer(GetPlayerInteger(GetPlayerId()), function(spawnPoint)
        spawnVehicle(spawnPoint)
    end)

    TriggerServerEvent('race:requestCheckpoint', '1234')
end)

local function setUpCheckpoint(cp, next)
    local nextPos, typeNum

    if next then
        nextPos = next.pos
        typeNum = 2
    else
        nextPos = { 0.0, 0.0, 0.0 }
        typeNum = 3
    end

    -- 2 = regular 'ground', 3 = finish 'ground', others are different 3dmarker types
    cp.handle = CreateCheckpoint(typeNum, cp.pos[1], cp.pos[2], cp.pos[3] + 2.5, nextPos[1], nextPos[2], nextPos[3], 1.0001, _r)
    cp.blip = AddBlipForCoord(cp.pos[1], cp.pos[2], cp.pos[3], _i)

    if cp == nextCheckpoint then
        ChangeBlipScale(cp.blip, 0.8)
    end

    ChangeBlipSprite(cp.blip, 3)
end

function tearDownCheckpoint(cp)
    if not cp then
        return
    end

    if cp.blip then
        RemoveBlip(cp.blip)
        cp.blip = nil
    end

    if cp.handle then
        DeleteCheckpoint(cp.handle)
        cp.handle = nil
    end
end

AddEventHandler('race:setCheckpoint', function(cur, next, later)
    if curCheckpoint then
        tearDownCheckpoint(curCheckpoint)
    end

    if nextCheckpoint then
        tearDownCheckpoint(nextCheckpoint)
    end

    curCheckpoint = cur
    nextCheckpoint = next

    if cur then
        setUpCheckpoint(curCheckpoint, nextCheckpoint)

        -- make a background thread waiting for the checkpoint to be reached
        CreateThread(function()
            local localCur = curCheckpoint

            -- so we exit if the checkpoint target is changed
            while curCheckpoint == localCur do
                Wait(25)

                if playerCar then
                    local px, py, pz = GetCarCoordinates(playerCar)
                    local distance = GetDistanceBetweenCoords2d(px, py, localCur.pos[1], localCur.pos[2])

                    if distance < 10 then
                        -- pass the fact we reached the checkpoint to the server
                        TriggerServerEvent('race:gotCP', '1234')

                        break
                    end
                end
            end
        end)
    end

    if next then
        setUpCheckpoint(nextCheckpoint, later)
    end
end)

AddEventHandler('race:confirmCP', function()
    PlayAudioEvent('FRONTEND_GAME_PICKUP_CHECKPOINT')
end)

AddEventHandler('race:updateStatus', function(player, curCP)
    if curCP == -1 then
        playerScores[player] = nil
    end

    local sPlayer = GetPlayerByServerId(player)

    if not sPlayer then
        return
    end

    local ped = sPlayer.ped

    playerScores[player] = {
        cp = curCP,
        ped = ped,
        vehicle = ped.vehicle
    }

    TriggerEvent('chatMessage', '', { 0, 0, 0 }, sPlayer.name .. ' now has cp ' .. curCP)

    updatePositions()
end)

AddEventHandler('onClientMapStop', function()
    if playerCar then
        MarkCarAsNoLongerNeeded(playerCar)
        playerCar = nil
    end

    if curCheckpoint and curCheckpoint.handle then
        DeleteCheckpoint(curCheckpoint.handle)
    end

    if nextCheckpoint and nextCheckpoint.handle then
        DeleteCheckpoint(nextCheckpoint.handle)
    end
end)

AddEventHandler('getMapDirectives', function(add)
    -- call the remote callback
    add('checkpoint', function(state, data)
        table.insert(checkpoints, data)

        state.add('pos', data.pos)

        -- delete callback follows on the next line
    end, function(state, arg)
        for i, sp in ipairs(checkpoints) do
            if sp.pos[1] == state.pos[1] and sp.pos[2] == state.pos[2] and sp.pos[3] == state.pos[3] then
                table.remove(checkpoints, i)
                return
            end
        end
    end)
end)

race_server.lua

local checkpoints = {}
local raceId = 0

RegisterServerEvent('race:updateCheckpoints')

AddEventHandler('race:updateCheckpoints', function(cps)
    if #checkpoints > 0 then
        return
    end

    checkpoints = cps

    TriggerClientEvent('race:weGotPorn', -1)
end)

local playerData = {}

local function ensurePlayerData(id)
    if playerData[id] then
        return
    end

    playerData[id] = {
        curCheckpoint = 0
    }
end

local raceStarted = false
local playerCount = 0

local function startRace()
    raceStarted = true

    print("really starting race")

    local function raceCountdown(num)
        local time = (4000 - (num * 1000))

        print("setting countdown for " .. tostring(time))

        SetTimeout(time, function()
            print("trig'd countdown for " .. tostring(time))

            if num == 0 then
                TriggerClientEvent('race:itsGoTime', -1, 0)
                TriggerClientEvent('race:showGoMessage', -1, 'GO')
            else
                TriggerClientEvent('race:showGoMessage', -1, tostring(num))
            end
        end)
    end

    raceCountdown(3) -- 3...
    raceCountdown(2) -- 2...
    raceCountdown(1) -- 1...
    raceCountdown(0) -- GOGOGO
end

local function incrementPlayerCount()
    playerCount = playerCount + 1

    if playerCount > 4 then
        startRace()
    end

    if playerCount == 1 then
        SetTimeout(3000, function()
            if raceStarted then
                return
            end

            print("starting race")

            startRace()
        end)
    end
end

local playersFinished
local raceEnded

AddEventHandler('onMapStart', function()
    playerCount = 0
    playersFinished = 0
    raceId = raceId + 1
    raceStarted = false
    raceEnded = false

    playerData = {}
    checkpoints = {}

    print("mmmmmm race")
end)

local function endRace()
    raceEnded = true

    TriggerClientEvent('race:results', -1, '1234')

    SetTimeout(7500, function()
        TriggerEvent('mapmanager:roundEnded')
    end)
end

AddEventHandler('race:onPlayerFinished', function(player)
    print(GetPlayerName(player) .. ' finished')

    local data = playerData[player]
    local finishSeconds = os.clock() - data.startTime

    local position = playersFinished + 1
    data.position = position
    playersFinished = position

    TriggerClientEvent('race:onPlayerFinished', -1, player, {
        finishSeconds = finishSeconds,
        position = position
    })

    if playersFinished == playerCount then
        endRace()
    elseif playersFinished == 1 then
        local thisRaceId = raceId

        TriggerClientEvent('race:hurryUp', -1, 30000)

        SetTimeout(30000, function()
            if raceId ~= thisRaceId or raceEnded then
                return
            end

            endRace()
        end)
    end
end)

AddEventHandler('playerActivated', function()
    if #checkpoints > 0 then
        TriggerClientEvent('race:weGotPorn', source)
    end
end)

RegisterServerEvent('race:requestGo')

AddEventHandler('race:requestGo', function()
    if #checkpoints > 0 then
        TriggerClientEvent('race:weGotPorn', source)
    end
end)

AddEventHandler('playerDropped', function(player)
    if playerData[player] and playerData[player].curCheckpoint > 0 then
        TriggerClientEvent('race:updateStatus', -1, player, -1)

        playerCount = playerCount - 1
    end
end)

RegisterServerEvent('race:gotCP')

AddEventHandler('race:gotCP', function()
    ensurePlayerData(source)

    local data = playerData[source]

    local next = data.curCheckpoint + 1

    if next > #checkpoints then
        print("omg finished")

        TriggerEvent('race:onPlayerFinished', source)

        return
    end

    data.curCheckpoint = next

    TriggerClientEvent('race:confirmCP', source) -- for sound effects
    TriggerClientEvent('race:setCheckpoint', source, checkpoints[next], checkpoints[next + 1], checkpoints[next + 2])
    TriggerClientEvent('race:updateStatus', -1, source, next - 1)
end)

RegisterServerEvent('race:requestCheckpoint')

AddEventHandler('race:requestCheckpoint', function()
    print('is it even in here')

    ensurePlayerData(source)

    print(source, 'requesting cp')

    if playerData[source].curCheckpoint == 0 then
        incrementPlayerCount()

        print(source, 'requesting cp 0')

        local curCP = 1
        playerData[source].curCheckpoint = curCP
        playerData[source].startTime = os.clock()

        TriggerClientEvent('race:setCheckpoint', source, checkpoints[curCP], checkpoints[curCP + 1], checkpoints[curCP + 2])
        TriggerClientEvent('race:updateStatus', -1, source, 0)

        -- should have raceReallyStarted since 4-second countdown
        if raceStarted then
            TriggerClientEvent('race:itsGoTime', -1, 0)
        end
    end
end)

I can not start this gamemode help me pls.