-- Local parameters local START_PROMPT_DISTANCE = 10.0 -- distance to prompt to start race local DRAW_TEXT_DISTANCE = 100.0 -- distance to start rendering the race name text local DRAW_SCORES_DISTANCE = 25.0 -- Distance to start rendering the race scores local DRAW_SCORES_COUNT_MAX = 15 -- Maximum number of scores to draw above race title local CHECKPOINT_Z_OFFSET = 5.00 -- checkpoint offset in z-axis local RACING_HUD_COLOR = {203, 49, 48, 255} -- color for racing HUD above map -- State variables local raceState = { cP = 1, index = 0 , scores = nil, startTime = 0, blip = nil, checkpoint = nil } -- Array of colors to display scores, top to bottom and scores out of range will be white local raceScoreColors = { {214, 175, 54, 255}, {167, 167, 173, 255}, {167, 112, 68, 255} } -- Create preRace thread Citizen.CreateThread(function() preRace() end) -- Function that runs when a race is NOT active function preRace() -- Initialize race state raceState.cP = 1 raceState.index = 0 raceState.startTime = 0 raceState.blip = nil raceState.checkpoint = nil -- While player is not racing while raceState.index == 0 do -- Update every frame Citizen.Wait(0) -- Get player local player = GetPlayerPed(-1) -- Teleport player to waypoint if active and button pressed --if IsWaypointActive() and IsControlJustReleased(0, 182) then -- Teleport player to waypoint --local waypoint = GetFirstBlipInfoId(8) --if DoesBlipExist(waypoint) then -- Teleport to location, wait 100ms to load then get ground coordinate --local coords = GetBlipInfoIdCoord(waypoint) --teleportToCoord(coords.x, coords.y, coords.z, 0) --Citizen.Wait(100) --local temp, zCoord = GetGroundZFor_3dCoord(coords.x, coords.y, 9999.9, 1) --teleportToCoord(coords.x, coords.y, zCoord + 4.0, 0) --end --end -- Loop through all races for index, race in pairs(races) do if race.isEnabled then -- Draw map marker DrawMarker(1, race.start.x, race.start.y, race.start.z - 1, 0, 0, 0, 0, 0, 0, 3.0001, 3.0001, 1.5001, 203, 49, 48, 255, 0, 0, 0,0) -- Check distance from map marker and draw text if close enough if GetDistanceBetweenCoords( race.start.x, race.start.y, race.start.z, GetEntityCoords(player)) < DRAW_TEXT_DISTANCE then -- Draw race name Draw3DText(race.start.x, race.start.y, race.start.z-0.600, race.title, RACING_HUD_COLOR, 4, 0.3, 0.3) end -- When close enough, draw scores if GetDistanceBetweenCoords( race.start.x, race.start.y, race.start.z, GetEntityCoords(player)) < DRAW_SCORES_DISTANCE then -- If we've received updated scores, display them if raceState.scores ~= nil then -- Get scores for this race and sort them raceScores = raceState.scores[race.title] if raceScores ~= nil then local sortedScores = {} for k, v in pairs(raceScores) do table.insert(sortedScores, { key = k, value = v }) end table.sort(sortedScores, function(a,b) return a.value.time < b.value.time end) -- Create new list with scores to draw local count = 0 drawScores = {} for k, v in pairs(sortedScores) do if count < DRAW_SCORES_COUNT_MAX then count = count + 1 table.insert(drawScores, v.value) end end -- Initialize offset local zOffset = 0 if (#drawScores > #raceScoreColors) then zOffset = 0.450*(#raceScoreColors) + 0.300*(#drawScores - #raceScoreColors - 1) else zOffset = 0.450*(#drawScores - 1) end -- Print scores above title for k, score in pairs(drawScores) do -- Draw score text with color coding if (k > #raceScoreColors) then -- Draw score in white, decrement offset Draw3DText(race.start.x, race.start.y, race.start.z+zOffset, string.format("%s %.2fs (%s)", score.car, (score.time/1000.0), score.player), {255,255,255,255}, 4, 0.13, 0.13) zOffset = zOffset - 0.300 else -- Draw score with color and larger text, decrement offset Draw3DText(race.start.x, race.start.y, race.start.z+zOffset, string.format("%s %.2fs (%s)", score.car, (score.time/1000.0), score.player), raceScoreColors[k], 4, 0.22, 0.22) zOffset = zOffset - 0.450 end end end end end -- When close enough, prompt player if GetDistanceBetweenCoords( race.start.x, race.start.y, race.start.z, GetEntityCoords(player)) < START_PROMPT_DISTANCE then helpMessage("Press ~INPUT_CONTEXT~ to Race!") if (IsControlJustReleased(1, 51)) then -- Set race index, clear scores and trigger event to start the race raceState.index = index raceState.scores = nil TriggerEvent("raceCountdown") break end end end end end end -- Receive race scores from server and print RegisterNetEvent("raceReceiveScores") AddEventHandler("raceReceiveScores", function(scores) -- Save scores to state raceState.scores = scores end) -- Countdown race start with controls disabled RegisterNetEvent("raceCountdown") AddEventHandler("raceCountdown", function() -- Get race from index local race = races[raceState.index] -- Teleport player to start and set heading teleportToCoord(race.start.x, race.start.y, race.start.z + 4.0, race.start.heading) Citizen.CreateThread(function() -- Countdown timer local time = 0 function setcountdown(x) time = GetGameTimer() + x*1000 end function getcountdown() return math.floor((time-GetGameTimer())/1000) end -- Count down to race start setcountdown(6) while getcountdown() > 0 do -- Update HUD Citizen.Wait(1) DrawHudText(getcountdown(), {203,49,48,255},0.5,0.4,4.0,4.0) -- Disable acceleration/reverse until race starts DisableControlAction(2, 71, true) DisableControlAction(2, 72, true) end -- Enable acceleration/reverse once race starts EnableControlAction(2, 71, true) EnableControlAction(2, 72, true) -- Start race TriggerEvent("raceRaceActive") end) end) -- Main race function RegisterNetEvent("raceRaceActive") AddEventHandler("raceRaceActive", function() -- Get race from index local race = races[raceState.index] -- Start a new timer raceState.startTime = GetGameTimer() Citizen.CreateThread(function() -- Create first checkpoint checkpoint = CreateCheckpoint(race.checkpoints[raceState.cP].type, race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y, race.checkpoints[raceState.cP].z + CHECKPOINT_Z_OFFSET, race.checkpoints[raceState.cP+1].x,race.checkpoints[raceState.cP+1].y, race.checkpoints[raceState.cP+1].z, race.checkpointRadius, 164, 76, 242, math.ceil(255*race.checkpointTransparency), 0) raceState.blip = AddBlipForCoord(race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y, race.checkpoints[raceState.cP].z) SetBlipColour(raceState.blip, 27) -- Set waypoints if enabled if race.showWaypoints == true then SetNewWaypoint(race.checkpoints[raceState.cP+1].x, race.checkpoints[raceState.cP+1].y) end -- While player is racing, do stuff while raceState.index ~= 0 do Citizen.Wait(1) -- Stop race when L is pressed, clear and reset everything if IsControlJustReleased(0, 182) and GetLastInputMethod(0) then -- Delete checkpoint and raceState.blip DeleteCheckpoint(checkpoint) RemoveBlip(raceState.blip) -- Set new waypoint and teleport to the same spot SetNewWaypoint(race.start.x, race.start.y) teleportToCoord(race.start.x, race.start.y, race.start.z + 4.0, race.start.heading) -- Clear racing index and break raceState.index = 0 break end -- Draw checkpoint and time HUD above minimap local checkpointDist = math.floor(GetDistanceBetweenCoords(race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y, race.checkpoints[raceState.cP].z, GetEntityCoords(GetPlayerPed(-1)))) DrawHudText(("%.3fs"):format((GetGameTimer() - raceState.startTime)/1000), RACING_HUD_COLOR, 0.015, 0.725, 0.7, 0.7) DrawHudText(string.format("Checkpoint %i / %i (%d m)", raceState.cP, #race.checkpoints, checkpointDist), RACING_HUD_COLOR, 0.015, 0.765, 0.5, 0.5) -- Check distance from checkpoint if GetDistanceBetweenCoords(race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y, race.checkpoints[raceState.cP].z, GetEntityCoords(GetPlayerPed(-1))) < race.checkpointRadius then -- Delete checkpoint and map raceState.blip, DeleteCheckpoint(checkpoint) RemoveBlip(raceState.blip) -- Play checkpoint sound PlaySoundFrontend(-1, "RACE_PLACED", "HUD_AWARDS") -- Check if at finish line if raceState.cP == #(race.checkpoints) then -- Save time and play sound for finish line local finishTime = (GetGameTimer() - raceState.startTime) PlaySoundFrontend(-1, "ScreenFlash", "WastedSounds") -- Get vehicle name and create score local aheadVehHash = GetEntityModel(GetVehiclePedIsUsing(GetPlayerPed(-1))) local aheadVehNameText = GetLabelText(GetDisplayNameFromVehicleModel(aheadVehHash)) local score = {} score.player = GetPlayerName(PlayerId()) score.time = finishTime score.car = aheadVehNameText -- Send server event with score and message, move this to server eventually message = string.format("Player " .. GetPlayerName(PlayerId()) .. " has completed " .. race.title .. " whilst driving " .. aheadVehNameText .. " in the timespan of: " .. (finishTime / 1000) .. " seconds.") TriggerServerEvent('racePlayerFinished', GetPlayerName(PlayerId()), message, race.title, score) -- Clear racing index and break raceState.index = 0 break end -- Increment checkpoint counter and create next checkpoint raceState.cP = math.ceil(raceState.cP+1) if race.checkpoints[raceState.cP].type == 7 then -- Create normal checkpoint checkpoint = CreateCheckpoint(race.checkpoints[raceState.cP].type, race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y, race.checkpoints[raceState.cP].z + CHECKPOINT_Z_OFFSET, race.checkpoints[raceState.cP+1].x, race.checkpoints[raceState.cP+1].y, race.checkpoints[raceState.cP+1].z, race.checkpointRadius, 164, 76, 242, math.ceil(155*race.checkpointTransparency), 0) raceState.blip = AddBlipForCoord(race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y, race.checkpoints[raceState.cP].z) SetBlipColour(raceState.blip, 27) SetNewWaypoint(race.checkpoints[raceState.cP+1].x, race.checkpoints[raceState.cP+1].y) elseif race.checkpoints[raceState.cP].type == 10 then -- Create finish line checkpoint = CreateCheckpoint(race.checkpoints[raceState.cP].type, race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y, race.checkpoints[raceState.cP].z + 4.0, race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y, race.checkpoints[raceState.cP].z, race.checkpointRadius, 164, 76, 242, math.ceil(155*race.checkpointTransparency), 0) raceState.blip = AddBlipForCoord(race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y, race.checkpoints[raceState.cP].z) SetBlipColour(raceState.blip, 27) SetNewWaypoint(race.checkpoints[raceState.cP].x, race.checkpoints[raceState.cP].y) end end end -- Reset race preRace() end) end) -- Create map blips for all enabled tracks Citizen.CreateThread(function() for _, race in pairs(races) do if race.isEnabled then race.blip = AddBlipForCoord(race.start.x, race.start.y, race.start.z) SetBlipSprite(race.blip, race.mapBlipId) SetBlipDisplay(race.blip, 4) SetBlipScale(race.blip, 1.0) SetBlipColour(race.blip, race.mapBlipColor) SetBlipAsShortRange(race.blip, true) BeginTextCommandSetBlipName("STRING") AddTextComponentString(race.title) EndTextCommandSetBlipName(race.blip) end end end) -- Utility function to teleport to coordinates function teleportToCoord(x, y, z, heading) Citizen.Wait(1) local player = GetPlayerPed(-1) if IsPedInAnyVehicle(player, true) then SetEntityCoords(GetVehiclePedIsUsing(player), x, y, z) Citizen.Wait(100) SetEntityHeading(GetVehiclePedIsUsing(player), heading) else SetEntityCoords(player, x, y, z) Citizen.Wait(100) SetEntityHeading(player, heading) end end -- Utility function to display help message function helpMessage(text, duration) BeginTextCommandDisplayHelp("STRING") AddTextComponentSubstringPlayerName(text) EndTextCommandDisplayHelp(0, false, true, duration or 5000) end -- Utility function to display 3D text function Draw3DText(x,y,z,textInput,colour,fontId,scaleX,scaleY) local px,py,pz=table.unpack(GetGameplayCamCoords()) local dist = GetDistanceBetweenCoords(px,py,pz, x,y,z, 1) local scale = (1/dist)*20 local fov = (1/GetGameplayCamFov())*100 local scale = scale*fov SetTextScale(scaleX*scale, scaleY*scale) SetTextFont(fontId) SetTextProportional(1) local colourr,colourg,colourb,coloura = table.unpack(colour) SetTextColour(colourr,colourg,colourb, coloura) SetTextDropshadow(2, 1, 1, 1, 255) SetTextEdge(3, 0, 0, 0, 150) SetTextDropShadow() SetTextOutline() SetTextEntry("STRING") SetTextCentre(1) AddTextComponentString(textInput) SetDrawOrigin(x,y,z+2, 0) DrawText(0.0, 0.0) ClearDrawOrigin() end -- Utility function to display HUD text function DrawHudText(text,colour,coordsx,coordsy,scalex,scaley) SetTextFont(4) SetTextProportional(7) SetTextScale(scalex, scaley) local colourr,colourg,colourb,coloura = table.unpack(colour) SetTextColour(colourr,colourg,colourb, coloura) SetTextDropshadow(0, 0, 0, 0, coloura) SetTextEdge(1, 0, 0, 0, coloura) SetTextDropShadow() SetTextOutline() SetTextEntry("STRING") AddTextComponentString(text) DrawText(coordsx,coordsy) end