Files
2026-03-29 21:41:17 +03:00

1205 lines
37 KiB
Lua
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
if not IsDuplicityVersion() then
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
if NetworkIsSessionStarted() then
TriggerServerEvent("Queue:playerActivated")
return
end
end
end)
return
end
local Queue = {}
-- EDIT THESE IN SERVER.CFG + OTHER OPTIONS IN CONFIG.LUA
Queue.MaxPlayers = GetConvarInt("sv_maxclients", 30)
Queue.Debug = GetConvar("sv_debugqueue", "true") == "true" and true or false
Queue.DisplayQueue = GetConvar("sv_displayqueue", "true") == "true" and true or false
Queue.InitHostName = GetConvar("sv_hostname")
-- This is needed because msgpack will break when tables are too large
local _Queue = {}
_Queue.QueueList = {}
_Queue.PlayerList = {}
_Queue.PlayerCount = 0
_Queue.Priority = {}
_Queue.Connecting = {}
_Queue.JoinCbs = {}
_Queue.TempPriority = {}
_Queue.JoinDelay = GetGameTimer() + Config.JoinDelay and Config.JoinDelay or 0
local tostring = tostring
local tonumber = tonumber
local ipairs = ipairs
local pairs = pairs
local print = print
local string_len = string.len
local string_sub = string.sub
local string_format = string.format
local string_lower = string.lower
local math_abs = math.abs
local math_floor = math.floor
local math_random = math.random
local os_time = os.time
local table_insert = table.insert
local table_remove = table.remove
Queue.InitHostName = Queue.InitHostName ~= "default FXServer" and Queue.InitHostName or false
for id, power in pairs(Config.Priority) do
_Queue.Priority[string_lower(id)] = power
end
-- ╔════════════════════════════════════════════════════════════════════════════╗
-- ║ ADVANCED LOGGING SYSTEM ║
-- ╚════════════════════════════════════════════════════════════════════════════╝
local LogLevel = {
DEBUG = { level = 1, color = "^5", icon = "🔍", name = "DEBUG" },
INFO = { level = 2, color = "^2", icon = "", name = "INFO" },
WARN = { level = 3, color = "^3", icon = "⚠️", name = "WARN" },
ERROR = { level = 4, color = "^1", icon = "", name = "ERROR" },
SUCCESS = { level = 2, color = "^2", icon = "", name = "SUCCESS" },
PLAYER = { level = 2, color = "^6", icon = "👤", name = "PLAYER" },
QUEUE = { level = 2, color = "^4", icon = "📋", name = "QUEUE" },
PRIORITY = { level = 2, color = "^8", icon = "", name = "PRIORITY" },
TIMEOUT = { level = 3, color = "^1", icon = "⏱️", name = "TIMEOUT" },
SYSTEM = { level = 2, color = "^9", icon = "⚙️", name = "SYSTEM" }
}
local function GetTimestamp()
local time = os.date("*t")
return string_format("^7[^3%02d:%02d:%02d^7]", time.hour, time.min, time.sec)
end
local function GetFormattedDate()
return os.date("%Y-%m-%d %H:%M:%S")
end
local function FormatDuration(seconds)
if seconds < 60 then
return string_format("%ds", seconds)
elseif seconds < 3600 then
return string_format("%dm %ds", math_floor(seconds / 60), seconds % 60)
else
return string_format("%dh %dm %ds", math_floor(seconds / 3600), math_floor((seconds % 3600) / 60), seconds % 60)
end
end
local function CreateSeparator(length, char)
char = char or ""
return string.rep(char, length or 50)
end
local function Log(logType, msg, details)
if not Queue.Debug then return end
local config = LogLevel[logType] or LogLevel.INFO
local timestamp = GetTimestamp()
local prefix = string_format("%s %s[%s]^7", timestamp, config.color, config.name)
local icon = config.icon or ""
local formattedMsg = string_format("%s %s %s^7", prefix, icon, tostring(msg))
print(formattedMsg)
if details and type(details) == "table" then
for key, value in pairs(details) do
print(string_format(" ^7├─ ^3%s: ^7%s", tostring(key), tostring(value)))
end
end
end
function Queue:DebugPrint(msg)
Log("DEBUG", msg)
end
function Queue:LogInfo(msg, details)
Log("INFO", msg, details)
end
function Queue:LogWarn(msg, details)
Log("WARN", msg, details)
end
function Queue:LogError(msg, details)
Log("ERROR", msg, details)
end
function Queue:LogSuccess(msg, details)
Log("SUCCESS", msg, details)
end
function Queue:LogPlayer(msg, details)
Log("PLAYER", msg, details)
end
function Queue:LogQueue(msg, details)
Log("QUEUE", msg, details)
end
function Queue:LogPriority(msg, details)
Log("PRIORITY", msg, details)
end
function Queue:LogTimeout(msg, details)
Log("TIMEOUT", msg, details)
end
function Queue:LogSystem(msg, details)
Log("SYSTEM", msg, details)
end
function Queue:PrintHeader(title)
if not Queue.Debug then return end
local sep = CreateSeparator(60, "")
print("^4╔" .. sep .. "╗^7")
local padding = math_floor((60 - string_len(title)) / 2)
print(string_format("^4║^7%s%s%s^4║^7", string.rep(" ", padding), title, string.rep(" ", 60 - padding - string_len(title))))
print("^4╚" .. sep .. "╝^7")
end
function Queue:PrintStats()
if not Queue.Debug then return end
self:PrintHeader("📊 QUEUE STATISTICS")
print(string_format(" ^2Players Online:^7 %d / %d", Queue:GetPlayerCount(), Queue.MaxPlayers))
print(string_format(" ^3Queue Size:^7 %d", Queue:GetSize()))
print(string_format(" ^5Connecting:^7 %d", Queue:ConnectingSize()))
print(string_format(" ^6Available Slots:^7 %d", math.max(0, Queue.MaxPlayers - Queue:GetPlayerCount() - Queue:ConnectingSize())))
print("^7" .. CreateSeparator(62, ""))
end
function Queue:HexIdToSteamId(hexId)
local cid = math_floor(tonumber(string_sub(hexId, 7), 16))
local steam64 = math_floor(tonumber(string_sub( cid, 2)))
local a = steam64 % 2 == 0 and 0 or 1
local b = math_floor(math_abs(6561197960265728 - steam64 - a) / 2)
local sid = "steam_0:"..a..":"..(a == 1 and b -1 or b)
return sid
end
function Queue:IsSteamRunning(src)
for _, id in ipairs(GetPlayerIdentifiers(src)) do
if string_sub(id, 1, 5) == "steam" then
return true
end
end
return false
end
function Queue:GetPlayerCount()
return _Queue.PlayerCount
end
function Queue:GetSize()
return #_Queue.QueueList
end
function Queue:ConnectingSize()
return #_Queue.Connecting
end
function Queue:GetQueueList()
return _Queue.QueueList
end
function Queue:GetPriorityList()
return _Queue.Priority
end
function Queue:GetPlayerList()
return _Queue.PlayerList
end
function Queue:GetTempPriorityList()
return _Queue.TempPriority
end
function Queue:GetConnectingList()
return _Queue.Connecting
end
function Queue:IsInQueue(ids, rtnTbl, bySource, connecting)
local connList = Queue:GetConnectingList()
local queueList = Queue:GetQueueList()
for genericKey1, genericValue1 in ipairs(connecting and connList or queueList) do
local inQueue = false
if not bySource then
for genericKey2, genericValue2 in ipairs(genericValue1.ids) do
if inQueue then break end
for genericKey3, genericValue3 in ipairs(ids) do
if genericValue3 == genericValue2 then inQueue = true break end
end
end
else
inQueue = ids == genericValue1.source
end
if inQueue then
if rtnTbl then
return genericKey1, connecting and connList[genericKey1] or queueList[genericKey1]
end
return true
end
end
return false
end
function Queue:IsPriority(ids)
local prio = false
local tempPower, tempEnd = Queue:HasTempPriority(ids)
local prioList = Queue:GetPriorityList()
for _, id in ipairs(ids) do
id = string_lower(id)
if prioList[id] then prio = prioList[id] break end
if string_sub(id, 1, 5) == "steam" then
local steamid = Queue:HexIdToSteamId(id)
if prioList[steamid] then prio = prioList[steamid] break end
end
end
if tempPower or prio then
if tempPower and prio then
return tempPower > prio and tempPower or prio
else
return tempPower or prio
end
end
return false
end
function Queue:HasTempPriority(ids)
local tmpPrio = Queue:GetTempPriorityList()
for _, id in pairs(ids) do
id = string_lower(id)
if tmpPrio[id] then return tmpPrio[id].power, tmpPrio[id].endTime, id end
if string_sub(id, 1, 5) == "steam" then
local steamid = Queue:HexIdToSteamId(id)
if tmpPrio[steamid] then return tmpPrio[steamid].power, tmpPrio[steamid].endTime, id end
end
end
return false
end
function Queue:AddToQueue(ids, connectTime, name, src, deferrals)
if Queue:IsInQueue(ids) then return end
local tmp = {
source = src,
ids = ids,
name = name,
priority = Queue:IsPriority(ids) or (src == "debug" and math_random(0, 15)),
timeout = 0,
deferrals = deferrals,
firstconnect = connectTime,
queuetime = function() return (os_time() - connectTime) end
}
local _pos = false
local queueCount = Queue:GetSize() + 1
local queueList = Queue:GetQueueList()
for pos, data in ipairs(queueList) do
if tmp.priority then
if not data.priority then
_pos = pos
else
if tmp.priority > data.priority then
_pos = pos
end
end
if _pos then
Queue:LogPriority(string_format("Player prioritized in queue"), {
Player = tmp.name,
Identifier = ids[1],
Position = string_format("%d/%d", _pos, queueCount),
Priority_Power = tostring(tmp.priority)
})
break
end
end
end
if not _pos then
_pos = Queue:GetSize() + 1
Queue:LogQueue(string_format("Player added to queue"), {
Player = tmp.name,
Identifier = ids[1],
Position = string_format("%d/%d", _pos, queueCount),
Priority = tmp.priority and tostring(tmp.priority) or "None"
})
end
table_insert(queueList, _pos, tmp)
end
function Queue:RemoveFromQueue(ids, bySource, byIndex)
local queueList = Queue:GetQueueList()
if byIndex then
if queueList[byIndex] then
table_remove(queueList, byIndex)
end
return
end
if Queue:IsInQueue(ids, false, bySource) then
local pos, data = Queue:IsInQueue(ids, true, bySource)
table_remove(queueList, pos)
end
end
function Queue:TempSize()
local count = 0
for _pos, data in pairs(Queue:GetQueueList()) do
if Queue:HasTempPriority(data.ids) then count = count +1 end
end
return count > 0 and count or false
end
function Queue:IsInConnecting(ids, bySource, refresh)
local inConnecting, tbl = Queue:IsInQueue(ids, refresh and true or false, bySource and true or false, true)
if not inConnecting then return false end
if refresh and inConnecting and tbl then
Queue:GetConnectingList()[inConnecting].timeout = 0
end
return true
end
function Queue:RemoveFromConnecting(ids, bySource, byIndex)
local connList = Queue:GetConnectingList()
if byIndex then
if connList[byIndex] then
table_remove(connList, byIndex)
end
return
end
for genericKey1, genericValue1 in ipairs(connList) do
local inConnecting = false
if not bySource then
for genericKey2, genericValue2 in ipairs(genericValue1.ids) do
if inConnecting then break end
for genericKey3, genericValue3 in ipairs(ids) do
if genericValue3 == genericValue2 then inConnecting = true break end
end
end
else
inConnecting = ids == genericValue1.source
end
if inConnecting then
table_remove(connList, genericKey1)
return true
end
end
return false
end
function Queue:AddToConnecting(ids, ignorePos, autoRemove, done)
local function remove()
if not autoRemove then return end
done(Config.Language.connectingerr)
Queue:RemoveFromConnecting(ids)
Queue:RemoveFromQueue(ids)
Queue:LogError("Failed to add player to connecting list", {
Identifier = ids[1] or "Unknown",
Reason = "Server may be full or position invalid"
})
end
local connList = Queue:GetConnectingList()
if Queue:ConnectingSize() + Queue:GetPlayerCount() + 1 > Queue.MaxPlayers then remove() return false end
if ids[1] == "debug" then
table_insert(connList, {source = ids[1], ids = ids, name = ids[1], firstconnect = ids[1], priority = ids[1], timeout = 0})
return true
end
if Queue:IsInConnecting(ids) then Queue:RemoveFromConnecting(ids) end
local pos, data = Queue:IsInQueue(ids, true)
if not ignorePos and (not pos or pos > 1) then remove() return false end
table_insert(connList, data)
Queue:RemoveFromQueue(ids)
return true
end
function Queue:GetIds(src)
local ids = GetPlayerIdentifiers(src)
local ip = GetPlayerEndpoint(src)
ids = (ids and ids[1]) and ids or (ip and {"ip:" .. ip} or false)
ids = ids ~= nil and ids or false
if ids and #ids > 1 then
for k, id in ipairs(ids) do
if string_sub(id, 1, 3) == "ip:" and not Queue:IsPriority({id}) then table_remove(ids, k) end
end
end
return ids
end
function Queue:AddPriority(id, power, temp)
if not id then return false end
if type(id) == "table" then
for _id, power in pairs(id) do
if _id and type(_id) == "string" and power and type(power) == "number" then
Queue:GetPriorityList()[_id] = power
else
Queue:DebugPrint("Error adding a priority id, invalid data passed")
return false
end
end
return true
end
power = (power and type(power) == "number") and power or 10
if temp then
local tempPower, tempEnd, tempId = Queue:HasTempPriority({id})
id = tempId or id
Queue:GetTempPriorityList()[string_lower(id)] = {power = power, endTime = os_time() + temp}
else
Queue:GetPriorityList()[string_lower(id)] = power
end
return true
end
function Queue:RemovePriority(id)
if not id then return false end
id = string_lower(id)
Queue:GetPriorityList()[id] = nil
return true
end
function Queue:UpdatePosData(src, ids, deferrals)
local pos, data = Queue:IsInQueue(ids, true)
data.source = src
data.ids = ids
data.timeout = 0
data.firstconnect = os_time()
data.name = GetPlayerName(src)
data.deferrals = deferrals
end
function Queue:NotFull(firstJoin)
local canJoin = Queue:GetPlayerCount() + Queue:ConnectingSize() < Queue.MaxPlayers
if firstJoin and canJoin then canJoin = Queue:GetSize() <= 1 end
return canJoin
end
function Queue:SetPos(ids, newPos)
if newPos <= 0 or newPos > Queue:GetSize() then return false end
local pos, data = Queue:IsInQueue(ids, true)
local queueList = Queue:GetQueueList()
table_remove(queueList, pos)
table_insert(queueList, newPos, data)
end
function Queue:CanJoin(src, cb)
local allow = true
for _, data in ipairs(_Queue.JoinCbs) do
local await = true
data.func(src, function(reason)
if reason and type(reason) == "string" then allow = false cb(reason) end
await = false
end)
while await do Citizen.Wait(0) end
if not allow then return end
end
if allow then cb(false) end
end
function Queue:OnJoin(cb, resource)
if not cb then return end
local tmp = {resource = resource, func = cb}
table_insert(_Queue.JoinCbs, tmp)
end
exports("GetQueueExports", function()
return Queue
end)
local function playerConnect(name, setKickReason, deferrals)
local src = source
local ids = Queue:GetIds(src)
local name = GetPlayerName(src)
local connectTime = os_time()
local connecting = true
deferrals.defer()
if Config.AntiSpam then
for i=Config.AntiSpamTimer,0,-1 do
deferrals.update(string.format(Config.PleaseWait, i))
Citizen.Wait(1000)
end
end
Citizen.CreateThread(function()
while connecting do
Citizen.Wait(100)
if not connecting then return end
deferrals.update(Config.Language.connecting)
end
end)
Citizen.Wait(500)
local function done(msg, _deferrals)
connecting = false
local deferrals = _deferrals or deferrals
if msg then deferrals.update(tostring(msg) or "") end
Citizen.Wait(500)
if not msg then
deferrals.done()
if Config.EnableGrace then Queue:AddPriority(ids[1], Config.GracePower, Config.GraceTime) end
else
deferrals.done(tostring(msg) or "") CancelEvent()
end
return
end
local function update(msg, _deferrals)
local deferrals = _deferrals or deferrals
connecting = false
deferrals.update(tostring(msg) or "")
end
if not ids then
-- prevent joining
done(Config.Language.idrr)
CancelEvent()
Queue:LogError("Player connection rejected - No identifiers", {
Player = name,
Source = src,
Reason = "Could not retrieve any valid identifiers"
})
return
end
if Config.RequireSteam and not Queue:IsSteamRunning(src) then
-- prevent joining
done(Config.Language.steam)
CancelEvent()
return
end
local allow
Queue:CanJoin(src, function(reason)
if reason == nil or allow ~= nil then return end
if reason == false or #_Queue.JoinCbs <= 0 then allow = true return end
if reason then
-- prevent joining
allow = false
done(reason and tostring(reason) or "You were blocked from joining")
Queue:RemoveFromQueue(ids)
Queue:RemoveFromConnecting(ids)
Queue:LogWarn("Player blocked from joining", {
Player = name,
Identifier = ids[1],
Reason = tostring(reason),
Action = "Connection Denied"
})
CancelEvent()
return
end
allow = true
end)
while allow == nil do Citizen.Wait(0) end
if not allow then return end
if Config.PriorityOnly and not Queue:IsPriority(ids) then done(Config.Language.wlonly) return end
local rejoined = false
if Queue:IsInConnecting(ids, false, true) then
Queue:RemoveFromConnecting(ids)
if Queue:NotFull() then
-- let them in the server
if not Queue:IsInQueue(ids) then
Queue:AddToQueue(ids, connectTime, name, src, deferrals)
end
local added = Queue:AddToConnecting(ids, true, true, done)
if not added then CancelEvent() return end
done()
return
else
rejoined = true
end
end
if Queue:IsInQueue(ids) then
rejoined = true
Queue:UpdatePosData(src, ids, deferrals)
Queue:LogPlayer("Player rejoined queue", {
Player = name,
Identifier = ids[1],
Status = "Reconnected after cancel"
})
else
Queue:AddToQueue(ids, connectTime, name, src, deferrals)
if rejoined then
Queue:SetPos(ids, 1)
rejoined = false
end
end
local pos, data = Queue:IsInQueue(ids, true)
if not pos or not data then
done(Config.Language.err .. " [1]")
Queue:RemoveFromQueue(ids)
Queue:RemoveFromConnecting(ids)
CancelEvent()
return
end
if Queue:NotFull(true) and _Queue.JoinDelay <= GetGameTimer() then
-- let them in the server
local added = Queue:AddToConnecting(ids, true, true, done)
if not added then CancelEvent() return end
done()
Queue:LogSuccess("Player connecting to server (Direct Join)", {
Player = name,
Identifier = ids[1],
Slots_Available = Queue.MaxPlayers - Queue:GetPlayerCount() - Queue:ConnectingSize(),
Server_Population = string_format("%d/%d", Queue:GetPlayerCount(), Queue.MaxPlayers)
})
return
end
update(string_format(Config.Language.pos .. ((Queue:TempSize() and Config.ShowTemp) and " (" .. Queue:TempSize() .. " temp)" or "00:00:00"), pos, Queue:GetSize(), ""))
if rejoined then return end
while true do
Citizen.Wait(500)
local pos, data = Queue:IsInQueue(ids, true)
local function remove(msg)
if data then
if msg then
update(msg, data.deferrals)
end
Queue:RemoveFromQueue(data.source, true)
Queue:RemoveFromConnecting(data.source, true)
else
Queue:RemoveFromQueue(ids)
Queue:RemoveFromConnecting(ids)
end
end
if not data or not data.deferrals or not data.source or not pos then
remove("[Queue] Removed from queue, queue data invalid :(")
Queue:LogError("Player removed - Invalid queue data", {
Player = name,
Identifier = ids[1],
Reason = "Missing or corrupted queue data",
Data_Deferrals = data and data.deferrals and "Valid" or "Invalid",
Data_Source = data and data.source and "Valid" or "Invalid",
Data_Position = pos and "Valid" or "Invalid"
})
return
end
local endPoint = GetPlayerEndpoint(data.source)
if not endPoint then data.timeout = data.timeout + 0.5 else data.timeout = 0 end
if data.timeout >= Config.QueueTimeOut and os_time() - connectTime > 5 then
remove("[Queue] Removed due to timeout")
Queue:LogTimeout("Player removed - Queue timeout", {
Player = name,
Identifier = ids[1],
Timeout_Duration = FormatDuration(data.timeout),
Config_Timeout = FormatDuration(Config.QueueTimeOut),
Queue_Time = FormatDuration(os_time() - connectTime)
})
return
end
if pos <= 1 and Queue:NotFull() and _Queue.JoinDelay <= GetGameTimer() then
-- let them in the server
local added = Queue:AddToConnecting(ids)
update(Config.Language.joining, data.deferrals)
Citizen.Wait(500)
if not added then
done(Config.Language.connectingerr)
CancelEvent()
return
end
done(nil, data.deferrals)
if Config.EnableGrace then Queue:AddPriority(ids[1], Config.GracePower, Config.GraceTime) end
Queue:RemoveFromQueue(ids)
Queue:LogSuccess("Player joining server from queue", {
Player = name,
Identifier = ids[1],
Wait_Time = FormatDuration(data.queuetime()),
Priority = data.priority and tostring(data.priority) or "None",
Grace_Enabled = Config.EnableGrace and "Yes" or "No"
})
return
end
local seconds = data.queuetime()
local qTime = string_format("%02d", math_floor((seconds % 86400) / 3600)) .. ":" .. string_format("%02d", math_floor((seconds % 3600) / 60)) .. ":" .. string_format("%02d", math_floor(seconds % 60))
local msg = string_format(Config.Language.pos .. ((Queue:TempSize() and Config.ShowTemp) and " (" .. Queue:TempSize() .. " temp)" or ""), pos, Queue:GetSize(), qTime)
update(msg, data.deferrals)
end
end
AddEventHandler("playerConnecting", playerConnect)
Citizen.CreateThread(function()
local function remove(data, pos, msg)
if data and data.source then
Queue:RemoveFromQueue(data.source, true)
Queue:RemoveFromConnecting(data.source, true)
elseif pos then
table_remove(Queue:GetQueueList(), pos)
end
end
while true do
Citizen.Wait(1000)
local i = 1
while i <= Queue:ConnectingSize() do
local data = Queue:GetConnectingList()[i]
local endPoint = GetPlayerEndpoint(data.source)
data.timeout = data.timeout + 1
if ((data.timeout >= 300 and not endPoint) or data.timeout >= Config.ConnectTimeOut) and data.source ~= "debug" and os_time() - data.firstconnect > 5 then
remove(data)
Queue:LogTimeout("Player removed - Connection timeout", {
Player = data.name,
Identifier = data.ids[1],
Timeout_Duration = FormatDuration(data.timeout),
Has_Endpoint = endPoint and "Yes" or "No",
Connect_Time = FormatDuration(os_time() - data.firstconnect)
})
else
i = i + 1
end
end
for id, data in pairs(Queue:GetTempPriorityList()) do
if os_time() >= data.endTime then
Queue:GetTempPriorityList()[id] = nil
end
end
Queue.MaxPlayers = GetConvarInt("sv_maxclients", 30)
Queue.Debug = GetConvar("sv_debugqueue", "true") == "true" and true or false
Queue.DisplayQueue = GetConvar("sv_displayqueue", "true") == "true" and true or false
local qCount = Queue:GetSize()
if Queue.DisplayQueue then
if Queue.InitHostName then
SetConvar("sv_hostname", (qCount > 0 and "[" .. tostring(qCount) .. "] " or "") .. Queue.InitHostName)
else
Queue.InitHostName = GetConvar("sv_hostname")
Queue.InitHostName = Queue.InitHostName ~= "default FXServer" and Queue.InitHostName or false
end
end
end
end)
RegisterServerEvent("Queue:playerActivated")
AddEventHandler("Queue:playerActivated", function()
local src = source
local ids = Queue:GetIds(src)
local name = GetPlayerName(src)
local endpoint = GetPlayerEndpoint(src)
local ping = GetPlayerPing(src)
if not Queue:GetPlayerList()[src] then
_Queue.PlayerCount = Queue:GetPlayerCount() + 1
Queue:GetPlayerList()[src] = true
Queue:RemoveFromQueue(ids)
Queue:RemoveFromConnecting(ids)
-- Get all identifiers for logging
local allIds = GetPlayerIdentifiers(src)
local steamId, discordId, license, xbl, live, fivem = "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"
for _, id in ipairs(allIds) do
local lowerId = string_lower(id)
if string.find(lowerId, "^steam:") then
steamId = id
elseif string.find(lowerId, "^discord:") then
discordId = id
elseif string.find(lowerId, "^license:") then
license = id
elseif string.find(lowerId, "^xbl:") then
xbl = id
elseif string.find(lowerId, "^live:") then
live = id
elseif string.find(lowerId, "^fivem:") then
fivem = id
end
end
Queue:LogSuccess("Player fully connected to server", {
Player = name,
Source = src,
Steam = steamId,
Discord = discordId,
License = license,
FiveM = fivem,
IP = endpoint or "Unknown",
Ping = ping .. "ms",
Server_Population = string_format("%d/%d", Queue:GetPlayerCount(), Queue.MaxPlayers),
Available_Slots = Queue.MaxPlayers - Queue:GetPlayerCount()
})
end
end)
AddEventHandler("playerDropped", function(reason)
local src = source
local ids = Queue:GetIds(src)
local name = GetPlayerName(src)
if Queue:GetPlayerList()[src] then
-- Get all identifiers before cleanup
local allIds = GetPlayerIdentifiers(src)
local steamId, discordId, license = "N/A", "N/A", "N/A"
for _, id in ipairs(allIds) do
local lowerId = string_lower(id)
if string.find(lowerId, "^steam:") then
steamId = id
elseif string.find(lowerId, "^discord:") then
discordId = id
elseif string.find(lowerId, "^license:") then
license = id
end
end
_Queue.PlayerCount = Queue:GetPlayerCount() - 1
Queue:GetPlayerList()[src] = nil
Queue:RemoveFromQueue(ids)
Queue:RemoveFromConnecting(ids)
local graceStatus = "Disabled"
if Config.EnableGrace then
Queue:AddPriority(ids[1], Config.GracePower, Config.GraceTime)
graceStatus = string_format("Granted (%d power for %s)", Config.GracePower, FormatDuration(Config.GraceTime))
end
Queue:LogPlayer("Player disconnected from server", {
Player = name or "Unknown",
Source = src,
Steam = steamId,
Discord = discordId,
License = license,
Reason = reason or "Unknown",
Grace_Period = graceStatus,
Server_Population = string_format("%d/%d", Queue:GetPlayerCount(), Queue.MaxPlayers),
Available_Slots = Queue.MaxPlayers - Queue:GetPlayerCount()
})
end
end)
AddEventHandler("onResourceStop", function(resource)
if Queue.DisplayQueue and Queue.InitHostName and resource == GetCurrentResourceName() then SetConvar("sv_hostname", Queue.InitHostName) end
for k, data in ipairs(_Queue.JoinCbs) do
if data.resource == resource then
table_remove(_Queue.JoinCbs, k)
end
end
end)
if Config.DisableHardCap then
Queue:LogSystem("Hardcap resource disabled", {
Resource = "hardcap",
Action = "Blocked from starting",
Reason = "Config.DisableHardCap = true"
})
AddEventHandler("onResourceStarting", function(resource)
if resource == "hardcap" then CancelEvent() return end
end)
StopResource("hardcap")
end
local testAdds = 0
local commands = {}
commands.addq = function()
Queue:LogInfo("Debug queue entry added", {
Test_ID = testAdds,
Steam_ID = "steam:110000103fd1bb1" .. testAdds
})
Queue:AddToQueue({"steam:110000103fd1bb1"..testAdds}, os_time(), "TestAdd: " .. testAdds, "debug")
testAdds = testAdds + 1
end
commands.removeq = function(args)
args[1] = tonumber(args[1])
local name = Queue:GetQueueList()[args[1]] and Queue:GetQueueList()[args[1]].name or nil
Queue:RemoveFromQueue(nil, nil, args[1])
Queue:LogInfo("Player removed from queue", {
Player = tostring(name),
Position = args[1]
})
end
commands.printq = function()
Queue:PrintHeader("📋 CURRENT QUEUE LIST")
local queueList = Queue:GetQueueList()
if #queueList == 0 then
print(" ^7No players in queue^7")
else
for pos, data in ipairs(queueList) do
print(string_format(" ^3#%d^7 ┃ ^2%s^7", pos, data.name))
print(string_format(" ├─ ^5Source:^7 %s", data.source))
print(string_format(" ├─ ^5ID:^7 %s", data.ids[1]))
print(string_format(" ├─ ^5Priority:^7 %s", tostring(data.priority or "None")))
print(string_format(" ├─ ^5Last Msg:^7 %s", data.source ~= "debug" and GetPlayerLastMsg(data.source) or "debug"))
print(string_format(" ├─ ^5Timeout:^7 %s", FormatDuration(data.timeout)))
print(string_format(" └─ ^5Queue Time:^7 %s", FormatDuration(data.queuetime())))
if pos < #queueList then print(" ^7" .. CreateSeparator(40, "")) end
end
end
print("^7" .. CreateSeparator(62, ""))
end
commands.addc = function()
Queue:AddToConnecting({"debug"})
Queue:LogInfo("Debug connecting entry added", {
Status = "Added to connecting list"
})
end
commands.removec = function(args)
args[1] = tonumber(args[1])
local name = Queue:GetConnectingList()[args[1]] and Queue:GetConnectingList()[args[1]].name or nil
Queue:RemoveFromConnecting(nil, nil, args[1])
Queue:LogInfo("Player removed from connecting list", {
Player = tostring(name),
Position = args[1]
})
end
commands.printc = function()
Queue:PrintHeader("🔗 CURRENT CONNECTING LIST")
local connList = Queue:GetConnectingList()
if #connList == 0 then
print(" ^7No players connecting^7")
else
for pos, data in ipairs(connList) do
print(string_format(" ^3#%d^7 ┃ ^2%s^7", pos, data.name))
print(string_format(" ├─ ^5Source:^7 %s", data.source))
print(string_format(" ├─ ^5ID:^7 %s", data.ids[1]))
print(string_format(" ├─ ^5Priority:^7 %s", tostring(data.priority or "None")))
print(string_format(" ├─ ^5Last Msg:^7 %s", data.source ~= "debug" and GetPlayerLastMsg(data.source) or "debug"))
print(string_format(" └─ ^5Timeout:^7 %s", FormatDuration(data.timeout)))
if pos < #connList then print(" ^7" .. CreateSeparator(40, "")) end
end
end
print("^7" .. CreateSeparator(62, ""))
end
commands.printl = function()
Queue:PrintHeader("👥 ACTIVE PLAYERS")
local count = 0
for k, joined in pairs(Queue:GetPlayerList()) do
count = count + 1
print(string_format(" ^2●^7 Source ID: ^3%s^7 | Active: ^2%s^7", k, tostring(joined)))
end
print(string_format(" ^7Total: ^2%d^7 players", count))
print("^7" .. CreateSeparator(62, ""))
end
commands.printp = function()
Queue:PrintHeader("⭐ PRIORITY LIST")
local count = 0
for id, power in pairs(Queue:GetPriorityList()) do
count = count + 1
print(string_format(" ^8★^7 %s ^7=> Power: ^3%s^7", id, tostring(power)))
end
print(string_format(" ^7Total: ^3%d^7 priority entries", count))
print("^7" .. CreateSeparator(62, ""))
end
commands.printcount = function()
Queue:PrintStats()
end
commands.printtp = function()
Queue:PrintHeader("⏳ TEMP PRIORITY LIST")
local count = 0
local currentTime = os_time()
for k, data in pairs(Queue:GetTempPriorityList()) do
count = count + 1
local remaining = data.endTime - currentTime
print(string_format(" ^6◆^7 %s", k))
print(string_format(" ├─ ^5Power:^7 %s", tostring(data.power)))
print(string_format(" ├─ ^5Expires In:^7 %s", FormatDuration(math.max(0, remaining))))
print(string_format(" └─ ^5End Time:^7 %s", os.date("%H:%M:%S", data.endTime)))
end
print(string_format(" ^7Total: ^6%d^7 temp priority entries", count))
print("^7" .. CreateSeparator(62, ""))
end
commands.removetp = function(args)
if not args[1] then return end
Queue:GetTempPriorityList()[args[1]] = nil
Queue:LogInfo("Temp priority removed", {
Identifier = args[1],
Action = "Removed from temp priority list"
})
end
commands.setpos = function(args)
if not args[1] or not args[2] then return end
args[1], args[2] = tonumber(args[1]), tonumber(args[2])
local data = Queue:GetQueueList()[args[1]]
Queue:SetPos(data.ids, args[2])
Queue:LogInfo("Queue position updated", {
Player = data.name,
Old_Position = args[1],
New_Position = args[2]
})
end
commands.setdata = function(args)
if not args[1] or not args[2] or not args[3] then return end
args[1] = tonumber(args[1])
local num = tonumber(args[3])
local data = Queue:GetQueueList()[args[1]]
if args[2] == "queuetime" then
local time = data.queuetime()
local dif = time - num
data.firstconnect = data.firstconnect + dif
data.queuetime = function() return (os_time() - data.firstconnect) end
else
data[args[2]] = num and num or args[3]
end
Queue:LogInfo("Player data modified", {
Player = data.name,
Field = args[2],
New_Value = args[3]
})
end
commands.commands = function()
Queue:PrintHeader("📚 AVAILABLE COMMANDS")
print(" ^3addq^7 - Add debug queue entry")
print(" ^3removeq^7 - Remove from queue by index")
print(" ^3printq^7 - Print current queue list")
print(" ^3addc^7 - Add debug connecting entry")
print(" ^3removec^7 - Remove from connecting by index")
print(" ^3printc^7 - Print connecting list")
print(" ^3printl^7 - Print active players")
print(" ^3printp^7 - Print priority list")
print(" ^3printcount^7- Print server statistics")
print(" ^3printtp^7 - Print temp priority list")
print(" ^3removetp^7 - Remove temp priority")
print(" ^3setpos^7 - Set queue position")
print(" ^3setdata^7 - Modify player data")
print(" ^3commands^7 - Show this help")
print("^7" .. CreateSeparator(62, ""))
end
commands.stats = function()
Queue:PrintStats()
end
AddEventHandler("rconCommand", function(command, args)
if command == "queue" and commands[args[1]] then
command = args[1]
table_remove(args, 1)
commands[command](args)
CancelEvent()
end
end)