542 lines
21 KiB
Lua
Raw Normal View History

2025-04-07 01:41:12 +00:00
local Races = {}
local AvailableRaces = {}
local LastRaces = {}
local NotFinished = {}
-- Functions
local function SecondsToClock(seconds)
seconds = tonumber(seconds)
if seconds > 0 then
local hours = string.format("%02.f", math.floor(seconds / 3600))
local mins = string.format("%02.f", math.floor(seconds / 60 - (hours * 60)))
local secs = string.format("%02.f", math.floor(seconds - hours * 3600 - mins * 60))
return hours..":"..mins..":"..secs
end
return "00:00:00"
end
local function IsWhitelisted(citizenId)
for _, cid in pairs(Config.WhitelistedCreators) do
if cid == citizenId then
return true
end
end
local player = exports.qbx_core:GetPlayerByCitizenId(citizenId)
local perms = exports.qbx_core:GetPermission(player.PlayerData.source)
return perms == "admin" or perms == "god"
end
local function IsNameAvailable(raceName)
for RaceId in pairs(Races) do
if Races[RaceId].RaceName == raceName then
return false
end
end
return true
end
local function HasOpenedRace(citizenId)
for _, v in pairs(AvailableRaces) do
if v.SetupCitizenId == citizenId then
return true
end
end
return false
end
local function GetOpenedRaceKey(raceId)
for k, v in pairs(AvailableRaces) do
if v.RaceId == raceId then
return k
end
end
end
local function GetCurrentRace(citizenId)
for raceId in pairs(Races) do
for cid in pairs(Races[raceId].Racers) do
if cid == citizenId then
return raceId
end
end
end
end
local function GetRaceId(name)
for k, v in pairs(Races) do
if v.RaceName == name then
return k
end
end
end
local function GenerateRaceId()
local raceId = "LR-" .. math.random(0, 9999999)
while Races[raceId] do
raceId = "LR-" .. math.random(0, 9999999)
end
return raceId
end
-- Events
RegisterNetEvent('qb-lapraces:server:FinishPlayer', function(RaceData, TotalTime, TotalLaps, BestLap)
local src = source
local Player = exports.qbx_core:GetPlayer(src)
local AvailableKey = GetOpenedRaceKey(RaceData.RaceId)
local PlayersFinished = 0
local AmountOfRacers = 0
for _, v in pairs(Races[RaceData.RaceId].Racers) do
if v.Finished then
PlayersFinished += 1
end
AmountOfRacers += 1
end
BestLap = TotalLaps < 2 and TotalTime or BestLap
if LastRaces[RaceData.RaceId] then
LastRaces[RaceData.RaceId][#LastRaces[RaceData.RaceId] + 1] = {
TotalTime = TotalTime,
BestLap = BestLap,
Holder = {
Player.PlayerData.charinfo.firstname,
Player.PlayerData.charinfo.lastname
}
}
else
LastRaces[RaceData.RaceId] = {}
LastRaces[RaceData.RaceId][#LastRaces[RaceData.RaceId] + 1] = {
TotalTime = TotalTime,
BestLap = BestLap,
Holder = {
Player.PlayerData.charinfo.firstname,
Player.PlayerData.charinfo.lastname
}
}
end
if Races[RaceData.RaceId].Records and table.type(Races[RaceData.RaceId].Records) ~= 'empty' then
if BestLap < Races[RaceData.RaceId].Records.Time then
Races[RaceData.RaceId].Records = {
Time = BestLap,
Holder = {
Player.PlayerData.charinfo.firstname,
Player.PlayerData.charinfo.lastname
}
}
MySQL.update('UPDATE lapraces SET records = ? WHERE raceid = ?', {json.encode(Races[RaceData.RaceId].Records), RaceData.RaceId})
TriggerClientEvent('qb-phone:client:RaceNotify', src, locale('phonenotif.wonWR', RaceData.RaceName, SecondsToClock(BestLap)))
end
else
Races[RaceData.RaceId].Records = {
Time = BestLap,
Holder = {
Player.PlayerData.charinfo.firstname,
Player.PlayerData.charinfo.lastname
}
}
MySQL.update('UPDATE lapraces SET records = ? WHERE raceid = ?', {json.encode(Races[RaceData.RaceId].Records), RaceData.RaceId})
TriggerClientEvent('qb-phone:client:RaceNotify', src, locale('phonenotif.wonWR2', RaceData.RaceName, SecondsToClock(BestLap)))
end
AvailableRaces[AvailableKey].RaceData = Races[RaceData.RaceId]
TriggerClientEvent('qb-lapraces:client:PlayerFinishs', -1, RaceData.RaceId, PlayersFinished, Player)
if PlayersFinished == AmountOfRacers then
if NotFinished and table.type(NotFinished) ~= 'empty' and NotFinished[RaceData.RaceId] and table.type(NotFinished[RaceData.RaceId]) ~= 'empty' then
for _, v in pairs(NotFinished[RaceData.RaceId]) do
LastRaces[RaceData.RaceId][#LastRaces[RaceData.RaceId] + 1] = {
TotalTime = v.TotalTime,
BestLap = v.BestLap,
Holder = v.Holder
}
end
end
Races[RaceData.RaceId].LastLeaderboard = LastRaces[RaceData.RaceId]
Races[RaceData.RaceId].Racers = {}
Races[RaceData.RaceId].Started = false
Races[RaceData.RaceId].Waiting = false
table.remove(AvailableRaces, AvailableKey)
LastRaces[RaceData.RaceId] = nil
NotFinished[RaceData.RaceId] = nil
end
TriggerClientEvent('qb-phone:client:UpdateLapraces', -1)
end)
RegisterNetEvent('qb-lapraces:server:CreateLapRace', function(RaceName)
local src = source
local Player = exports.qbx_core:GetPlayer(src)
if Player and IsWhitelisted(Player.PlayerData.citizenid) then
if IsNameAvailable(RaceName) then
TriggerClientEvent('qb-lapraces:client:StartRaceEditor', source, RaceName)
else
exports.qbx_core:Notify(source, locale('error.namealreadyused'), 'error')
end
else
exports.qbx_core:Notify(source, locale('error.notauthorized', locale('general.createraces')), 'error')
end
end)
RegisterNetEvent('qb-lapraces:server:JoinRace', function(RaceData)
local src = source
local Player = exports.qbx_core:GetPlayer(src)
local RaceId = RaceData.RaceId
local AvailableKey = GetOpenedRaceKey(RaceId)
local CurrentRace = GetCurrentRace(Player.PlayerData.citizenid)
if CurrentRace then
local AmountOfRacers = 0
local PreviousRaceKey = GetOpenedRaceKey(CurrentRace)
for _ in pairs(Races[CurrentRace].Racers) do
AmountOfRacers += 1
end
Races[CurrentRace].Racers[Player.PlayerData.citizenid] = nil
if AmountOfRacers - 1 == 0 then
Races[CurrentRace].Racers = {}
Races[CurrentRace].Started = false
Races[CurrentRace].Waiting = false
table.remove(AvailableRaces, PreviousRaceKey)
exports.qbx_core:Notify(src, locale('error.raceended'), 'error')
TriggerClientEvent('qb-lapraces:client:LeaveRace', src, Races[CurrentRace])
else
AvailableRaces[PreviousRaceKey].RaceData = Races[CurrentRace]
TriggerClientEvent('qb-lapraces:client:LeaveRace', src, Races[CurrentRace])
end
TriggerClientEvent('qb-phone:client:UpdateLapraces', -1)
end
Races[RaceId].Waiting = true
Races[RaceId].Racers[Player.PlayerData.citizenid] = {
Checkpoint = 0,
Lap = 1,
Finished = false
}
AvailableRaces[AvailableKey].RaceData = Races[RaceId]
TriggerClientEvent('qb-lapraces:client:JoinRace', src, Races[RaceId], AvailableRaces[AvailableKey].Laps)
TriggerClientEvent('qb-phone:client:UpdateLapraces', -1)
local creatorsource = exports.qbx_core:GetPlayerByCitizenId(AvailableRaces[AvailableKey].SetupCitizenId).PlayerData.source
if creatorsource ~= Player.PlayerData.source then
TriggerClientEvent('qb-phone:client:RaceNotify', creatorsource, locale('phonenotif.joinedrace',string.sub(Player.PlayerData.charinfo.firstname, 1, 1), Player.PlayerData.charinfo.lastname))
end
end)
RegisterNetEvent('qb-lapraces:server:LeaveRace', function(RaceData)
local src = source
local Player = exports.qbx_core:GetPlayer(src)
local RaceName = RaceData.RaceData and RaceData.RaceData.RaceName or RaceData.RaceName
local RaceId = GetRaceId(RaceName)
local AvailableKey = GetOpenedRaceKey(RaceData.RaceId)
local creatorsource = exports.qbx_core:GetPlayerByCitizenId(AvailableRaces[AvailableKey].SetupCitizenId)?.PlayerData.source
if creatorsource ~= Player.PlayerData.source then
TriggerClientEvent('qb-phone:client:RaceNotify', creatorsource, locale('phonenotif.LeaveRace',string.sub(Player.PlayerData.charinfo.firstname, 1, 1), Player.PlayerData.charinfo.lastname))
end
local AmountOfRacers = 0
for _ in pairs(Races[RaceData.RaceId].Racers) do
AmountOfRacers += 1
end
if NotFinished[RaceData.RaceId] then
NotFinished[RaceData.RaceId][#NotFinished[RaceData.RaceId] + 1] = {
TotalTime = locale('general.DNF'),
BestLap = locale('general.DNF'),
Holder = {
Player.PlayerData.charinfo.firstname,
Player.PlayerData.charinfo.lastname
}
}
else
NotFinished[RaceData.RaceId] = {}
NotFinished[RaceData.RaceId][#NotFinished[RaceData.RaceId] + 1] = {
TotalTime = locale('general.DNF'),
BestLap = locale('general.DNF'),
Holder = {
Player.PlayerData.charinfo.firstname,
Player.PlayerData.charinfo.lastname
}
}
end
Races[RaceId].Racers[Player.PlayerData.citizenid] = nil
if AmountOfRacers - 1 == 0 then
if NotFinished and table.type(NotFinished) ~= 'empty' and NotFinished[RaceId] and table.type(NotFinished[RaceId]) ~= 'empty' then
for _, v in pairs(NotFinished[RaceId]) do
if LastRaces[RaceId] then
LastRaces[RaceId][#LastRaces[RaceId]+1] = {
TotalTime = v.TotalTime,
BestLap = v.BestLap,
Holder = v.Holder
}
else
LastRaces[RaceId] = {}
LastRaces[RaceId][#LastRaces[RaceId] + 1] = {
TotalTime = v.TotalTime,
BestLap = v.BestLap,
Holder = v.Holder
}
end
end
end
Races[RaceId].LastLeaderboard = LastRaces[RaceId]
Races[RaceId].Racers = {}
Races[RaceId].Started = false
Races[RaceId].Waiting = false
table.remove(AvailableRaces, AvailableKey)
exports.qbx_core:Notify(src, locale('error.raceended'), 'error')
TriggerClientEvent('qb-lapraces:client:LeaveRace', src, Races[RaceId])
LastRaces[RaceId] = nil
NotFinished[RaceId] = nil
else
AvailableRaces[AvailableKey].RaceData = Races[RaceId]
TriggerClientEvent('qb-lapraces:client:LeaveRace', src, Races[RaceId])
end
TriggerClientEvent('qb-phone:client:UpdateLapraces', -1)
end)
RegisterNetEvent('qb-lapraces:server:SetupRace', function(RaceId, Laps)
local Player = exports.qbx_core:GetPlayer(source)
if Races[RaceId] then
if not Races[RaceId].Waiting then
if not Races[RaceId].Started then
Races[RaceId].Waiting = true
AvailableRaces[#AvailableRaces + 1] = {
RaceData = Races[RaceId],
Laps = Laps,
RaceId = RaceId,
SetupCitizenId = Player.PlayerData.citizenid
}
TriggerClientEvent('qb-phone:client:UpdateLapraces', -1)
SetTimeout(5 * 60 * 1000, function()
if Races[RaceId].Waiting then
local AvailableKey = GetOpenedRaceKey(RaceId)
for cid in pairs(Races[RaceId].Racers) do
local RacerData = exports.qbx_core:GetPlayerByCitizenId(cid)
if RacerData then
TriggerClientEvent('qb-lapraces:client:LeaveRace', RacerData.PlayerData.source, Races[RaceId])
end
end
table.remove(AvailableRaces, AvailableKey)
Races[RaceId].LastLeaderboard = {}
Races[RaceId].Racers = {}
Races[RaceId].Started = false
Races[RaceId].Waiting = false
LastRaces[RaceId] = nil
TriggerClientEvent('qb-phone:client:UpdateLapraces', -1)
end
end)
else
exports.qbx_core:Notify(source, locale('error.alreadyrunning'), 'error')
end
else
exports.qbx_core:Notify(source, locale('error.alreadyrunning'), 'error')
end
else
exports.qbx_core:Notify(source, locale('error.notexist'), 'error')
end
end)
RegisterNetEvent('qb-lapraces:server:CancelRace', function(raceId)
local src = source
local Player = exports.qbx_core:GetPlayer(source)
local AvailableKey = GetOpenedRaceKey(raceId)
exports.qbx_core:Notify(src, locale('error.stoppingrace',raceId), 'error')
if AvailableKey then
if AvailableRaces[AvailableKey].SetupCitizenId == Player.PlayerData.citizenid then
for cid in pairs(Races[raceId].Racers) do
local RacerData = exports.qbx_core:GetPlayerByCitizenId(cid)
if RacerData then
TriggerClientEvent('qb-lapraces:client:LeaveRace', RacerData.PlayerData.source, Races[raceId])
end
end
table.remove(AvailableRaces, AvailableKey)
Races[raceId].LastLeaderboard = {}
Races[raceId].Racers = {}
Races[raceId].Started = false
Races[raceId].Waiting = false
LastRaces[raceId] = nil
TriggerClientEvent('qb-phone:client:UpdateLapraces', -1)
end
else
exports.qbx_core:Notify(src, locale('error.racenotopen', raceId), 'error')
end
end)
RegisterNetEvent('qb-lapraces:server:UpdateRaceState', function(RaceId, Started, Waiting)
Races[RaceId].Waiting = Waiting
Races[RaceId].Started = Started
end)
RegisterNetEvent('qb-lapraces:server:UpdateRacerData', function(RaceId, Checkpoint, Lap, Finished)
local src = source
local Player = exports.qbx_core:GetPlayer(src)
local CitizenId = Player.PlayerData.citizenid
Races[RaceId].Racers[CitizenId].Checkpoint = Checkpoint
Races[RaceId].Racers[CitizenId].Lap = Lap
Races[RaceId].Racers[CitizenId].Finished = Finished
TriggerClientEvent('qb-lapraces:client:UpdateRaceRacerData', -1, RaceId, Races[RaceId])
end)
RegisterNetEvent('qb-lapraces:server:StartRace', function(RaceId)
local src = source
local MyPlayer = exports.qbx_core:GetPlayer(src)
local AvailableKey = GetOpenedRaceKey(RaceId)
if RaceId then
if AvailableRaces[AvailableKey].SetupCitizenId == MyPlayer.PlayerData.citizenid then
AvailableRaces[AvailableKey].RaceData.Started = true
AvailableRaces[AvailableKey].RaceData.Waiting = false
for CitizenId in pairs(Races[RaceId].Racers) do
local Player = exports.qbx_core:GetPlayerByCitizenId(CitizenId)
if Player then
TriggerClientEvent('qb-lapraces:client:RaceCountdown', Player.PlayerData.source)
end
end
TriggerClientEvent('qb-phone:client:UpdateLapraces', -1)
else
exports.qbx_core:Notify(src, locale('error.notcreator'), 'error')
end
else
exports.qbx_core:Notify(src, locale('error.notinarace'), 'error')
end
end)
RegisterNetEvent('qb-lapraces:server:SaveRace', function(RaceData)
local src = source
local Player = exports.qbx_core:GetPlayer(src)
local RaceId = GenerateRaceId()
local Checkpoints = {}
for k, v in pairs(RaceData.Checkpoints) do
Checkpoints[k] = {
offset = v.offset,
coords = v.coords
}
end
Races[RaceId] = {
RaceName = RaceData.RaceName,
Checkpoints = Checkpoints,
Records = {},
Creator = Player.PlayerData.citizenid,
RaceId = RaceId,
Started = false,
Waiting = false,
Distance = math.ceil(RaceData.RaceDistance),
Racers = {},
LastLeaderboard = {}
}
MySQL.insert('INSERT INTO lapraces (name, checkpoints, creator, distance, raceid) VALUES (?, ?, ?, ?, ?)', {RaceData.RaceName, json.encode(Checkpoints), Player.PlayerData.citizenid, RaceData.RaceDistance, GenerateRaceId()})
end)
-- Callbacks
lib.callback.register('qb-lapraces:server:GetRacingLeaderboards', function()
return Races
end)
lib.callback.register('qb-lapraces:server:GetRaces', function()
return AvailableRaces
end)
lib.callback.register('qb-lapraces:server:GetListedRaces', function()
return Races
end)
lib.callback.register('qb-lapraces:server:GetRacingData', function(_, RaceId)
return Races[RaceId]
end)
lib.callback.register('qb-lapraces:server:HasCreatedRace', function(source)
return HasOpenedRace(exports.qbx_core:GetPlayer(source).PlayerData.citizenid)
end)
lib.callback.register('qb-lapraces:server:IsAuthorizedToCreateRaces', function(source, TrackName)
return IsWhitelisted(exports.qbx_core:GetPlayer(source).PlayerData.citizenid), IsNameAvailable(TrackName)
end)
lib.callback.register('qb-lapraces:server:CanRaceSetup', function(_, cb)
return Config.RaceSetupAllowed
end)
lib.callback.register('qb-lapraces:server:GetTrackData', function(_, RaceId)
local result = MySQL.query.await('SELECT * FROM players WHERE citizenid = ?', {Races[RaceId].Creator})
if result and result[1] then
result[1].charinfo = json.decode(result[1].charinfo)
return Races[RaceId], result[1]
end
return Races[RaceId], {
charinfo = {
firstname = locale('general.unknown'),
lastname = locale('general.unknown')
}
}
end)
-- Commands
lib.addCommand('cancelrace', {help = locale('commands.cancelrace')}, function(source, args)
local Player = exports.qbx_core:GetPlayer(source)
if IsWhitelisted(Player.PlayerData.citizenid) then
local RaceName = table.concat(args, " ")
if RaceName then
local RaceId = GetRaceId(RaceName)
if Races[RaceId].Started then
local AvailableKey = GetOpenedRaceKey(RaceId)
for cid in pairs(Races[RaceId].Racers) do
local RacerData = exports.qbx_core:GetPlayerByCitizenId(cid)
if RacerData then
TriggerClientEvent('qb-lapraces:client:LeaveRace', RacerData.PlayerData.source, Races[RaceId])
end
end
table.remove(AvailableRaces, AvailableKey)
Races[RaceId].LastLeaderboard = {}
Races[RaceId].Racers = {}
Races[RaceId].Started = false
Races[RaceId].Waiting = false
LastRaces[RaceId] = nil
TriggerClientEvent('qb-phone:client:UpdateLapraces', -1)
else
exports.qbx_core:Notify(source, locale('error.notstarted'), 'error')
end
end
else
exports.qbx_core:Notify(source, locale('error.notauthorized', locale('general.dothis')), 'error')
end
end)
lib.addCommand('togglesetup', {help = locale('commands.togglesetup')}, function(source)
local Player = exports.qbx_core:GetPlayer(source)
if IsWhitelisted(Player.PlayerData.citizenid) then
Config.RaceSetupAllowed = not Config.RaceSetupAllowed
if not Config.RaceSetupAllowed then
exports.qbx_core:Notify(source, locale('error.nomoreraces'), 'error')
else
exports.qbx_core:Notify(source, locale('success.cancreate'), 'success')
end
else
exports.qbx_core:Notify(source, locale('error.notauthorized', locale('general.dothis')), 'error')
end
end)
-- Threads
CreateThread(function()
local races = MySQL.query.await('SELECT * FROM lapraces', {})
if races and races[1] then
for _, v in pairs(races) do
local Records = v.records and json.decode(v.records) or {}
Races[v.raceid] = {
RaceName = v.name,
Checkpoints = json.decode(v.checkpoints),
Records = Records,
Creator = v.creator,
RaceId = v.raceid,
Started = false,
Waiting = false,
Distance = v.distance,
LastLeaderboard = {},
Racers = {}
}
end
end
end)