├── README.md ├── connectqueue.lua ├── fxmanifest.lua ├── server └── sv_queue_config.lua └── shared └── sh_queue.lua /README.md: -------------------------------------------------------------------------------- 1 | # ConnectQueue 2 | --- 3 | Easy to use queue system for FiveM with: 4 | - Simple API 5 | - Priority System 6 | - Config 7 | - Ability for whitelist only 8 | - Require steam 9 | - Language options 10 | 11 | **Please report any bugs on the release thread [Here](https://forum.fivem.net/t/alpha-connectqueue-a-server-queue-system-fxs/22228) or through [GitHub](https://github.com/Nick78111/ConnectQueue/issues).** 12 | 13 | ## How to install 14 | --- 15 | - Drop the folder inside your resources folder. 16 | - Add `start connectqueue` inside your server.cfg. - *Preferrably at the top* 17 | - Set convars to your liking. 18 | - Open `connectqueue/server/sv_queue_config.lua` and edit to your liking. 19 | - Renaming the resource may cause problems. 20 | 21 | ## ConVars 22 | --- 23 | set sv_debugqueue true # prints debug messages to console 24 | set sv_displayqueue true # shows queue count in the server name '[count] server name' 25 | 26 | ## How to use / Examples 27 | --- 28 | To use the API add `server_script "@connectqueue/connectqueue.lua"` at the top of the `__resource.lua` file in question. 29 | I would also suggest adding `dependency "connectqueue"` to it aswell. 30 | You may now use any of the functions below, anywhere in that resource. 31 | 32 | ### OnReady 33 | This is called when the queue functions are ready to be used. 34 | ```Lua 35 | Queue.OnReady(function() 36 | print("HI") 37 | end) 38 | ``` 39 | All of the functions below must be called **AFTER** the queue is ready. 40 | 41 | ### OnJoin 42 | This is called when a player tries to join the server. 43 | Calling `allow` with no arguments will let them through. 44 | Calling `allow` with a string will prevent them from joining with the given message. 45 | `allow` must be called or the player will hang on connecting... 46 | ```Lua 47 | Queue.OnJoin(function(source, allow) 48 | allow("No, you can't join") 49 | end) 50 | ``` 51 | 52 | ## AddPriority 53 | Call this to add an identifier to the priority list. 54 | The integer is how much power they have over other users with priority. 55 | This function can take a table of ids or individually. 56 | ```Lua 57 | -- individual 58 | Queue.AddPriority("STEAM_0:1:33459672", 100) 59 | Queue.AddPriority("steam:110000103fd1bb1", 10) 60 | Queue.AddPriority("ip:127.0.0.1", 25) 61 | 62 | -- table 63 | local prioritize = { 64 | ["STEAM_0:1:33459672"] = 100, 65 | ["steam:110000103fd1bb1"] = 10, 66 | ["ip:127.0.0.1"] = 25, 67 | } 68 | Queue.AddPriority(prioritize) 69 | ``` 70 | 71 | ## RemovePriority 72 | Removes priority from a user. 73 | ```Lua 74 | Queue.RemovePriority("STEAM_0:1:33459672") 75 | ``` 76 | 77 | ## IsReady 78 | Will return whether or not the queue's exports are ready to be called. 79 | ```Lua 80 | print(Queue.IsReady()) 81 | ``` 82 | 83 | ## Other Queue Functions 84 | You can call every queue function within sh_queue.lua. 85 | ```Lua 86 | local ids = Queue.Exports:GetIds(src) 87 | 88 | -- sets the player to position 1 in queue 89 | Queue.Exports:SetPos(ids, 1) 90 | -- returns whether or not the player has any priority 91 | Queue.Exports:IsPriority(ids) 92 | --- returns size of queue 93 | Queue.Exports:GetSize() 94 | -- plus many more... 95 | ``` -------------------------------------------------------------------------------- /connectqueue.lua: -------------------------------------------------------------------------------- 1 | Queue = {} 2 | Queue.Ready = false 3 | Queue.Exports = nil 4 | Queue.ReadyCbs = {} 5 | Queue.CurResource = GetCurrentResourceName() 6 | 7 | if Queue.CurResource == "connectqueue" then return end 8 | 9 | function Queue.OnReady(cb) 10 | if not cb then return end 11 | if Queue.IsReady() then cb() return end 12 | table.insert(Queue.ReadyCbs, cb) 13 | end 14 | 15 | function Queue.OnJoin(cb) 16 | if not cb then return end 17 | Queue.Exports:OnJoin(cb, Queue.CurResource) 18 | end 19 | 20 | function Queue.AddPriority(id, power, temp) 21 | if not Queue.IsReady() then return end 22 | Queue.Exports:AddPriority(id, power, temp) 23 | end 24 | 25 | function Queue.RemovePriority(id) 26 | if not Queue.IsReady() then return end 27 | Queue.Exports:RemovePriority(id) 28 | end 29 | 30 | function Queue.IsReady() 31 | return Queue.Ready 32 | end 33 | 34 | function Queue.LoadExports() 35 | Queue.Exports = exports.connectqueue:GetQueueExports() 36 | Queue.Ready = true 37 | Queue.ReadyCallbacks() 38 | end 39 | 40 | function Queue.ReadyCallbacks() 41 | if not Queue.IsReady() then return end 42 | for _, cb in ipairs(Queue.ReadyCbs) do 43 | cb() 44 | end 45 | end 46 | 47 | AddEventHandler("onResourceStart", function(resource) 48 | if resource == "connectqueue" then 49 | while GetResourceState(resource) ~= "started" do Citizen.Wait(0) end 50 | Citizen.Wait(1) 51 | Queue.LoadExports() 52 | end 53 | end) 54 | 55 | AddEventHandler("onResourceStop", function(resource) 56 | if resource == "connectqueue" then 57 | Queue.Ready = false 58 | Queue.Exports = nil 59 | end 60 | end) 61 | 62 | SetTimeout(1, function() Queue.LoadExports() end) -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'bodacious' 2 | game 'common' 3 | 4 | server_script "server/sv_queue_config.lua" 5 | server_script "connectqueue.lua" 6 | 7 | server_script "shared/sh_queue.lua" 8 | client_script "shared/sh_queue.lua" 9 | -------------------------------------------------------------------------------- /server/sv_queue_config.lua: -------------------------------------------------------------------------------- 1 | Config = {} 2 | 3 | -- priority list can be any identifier. (hex steamid, steamid32, ip) Integer = power over other people with priority 4 | -- a lot of the steamid converting websites are broken rn and give you the wrong steamid. I use https://steamid.xyz/ with no problems. 5 | -- you can also give priority through the API, read the examples/readme. 6 | Config.Priority = { 7 | ["STEAM_0:1:0000####"] = 1, 8 | ["steam:110000######"] = 25, 9 | ["ip:127.0.0.0"] = 85 10 | } 11 | 12 | -- require people to run steam 13 | Config.RequireSteam = false 14 | 15 | -- "whitelist" only server 16 | Config.PriorityOnly = false 17 | 18 | -- disables hardcap, should keep this true 19 | Config.DisableHardCap = true 20 | 21 | -- will remove players from connecting if they don't load within: __ seconds; May need to increase this if you have a lot of downloads. 22 | -- i have yet to find an easy way to determine whether they are still connecting and downloading content or are hanging in the loadscreen. 23 | -- This may cause session provider errors if it is too low because the removed player may still be connecting, and will let the next person through... 24 | -- even if the server is full. 10 minutes should be enough 25 | Config.ConnectTimeOut = 600 26 | 27 | -- will remove players from queue if the server doesn't recieve a message from them within: __ seconds 28 | Config.QueueTimeOut = 90 29 | 30 | -- will give players temporary priority when they disconnect and when they start loading in 31 | Config.EnableGrace = false 32 | 33 | -- how much priority power grace time will give 34 | Config.GracePower = 5 35 | 36 | -- how long grace time lasts in seconds 37 | Config.GraceTime = 480 38 | 39 | Config.AntiSpam = false 40 | Config.AntiSpamTimer = 30 41 | Config.PleaseWait = "Please wait %f seconds. The connection will start automatically!" 42 | 43 | -- on resource start, players can join the queue but will not let them join for __ milliseconds 44 | -- this will let the queue settle and lets other resources finish initializing 45 | Config.JoinDelay = 30000 46 | 47 | -- will show how many people have temporary priority in the connection message 48 | Config.ShowTemp = false 49 | 50 | -- simple localization 51 | Config.Language = { 52 | joining = "\xF0\x9F\x8E\x89Joining...", 53 | connecting = "\xE2\x8F\xB3Connecting...", 54 | idrr = "\xE2\x9D\x97[Queue] Error: Couldn't retrieve any of your id's, try restarting.", 55 | err = "\xE2\x9D\x97[Queue] There was an error", 56 | pos = "\xF0\x9F\x90\x8CYou are %d/%d in queue \xF0\x9F\x95\x9C%s", 57 | connectingerr = "\xE2\x9D\x97[Queue] Error: Error adding you to connecting list", 58 | timedout = "\xE2\x9D\x97[Queue] Error: Timed out?", 59 | wlonly = "\xE2\x9D\x97[Queue] You must be whitelisted to join this server", 60 | steam = "\xE2\x9D\x97 [Queue] Error: Steam must be running" 61 | } 62 | -------------------------------------------------------------------------------- /shared/sh_queue.lua: -------------------------------------------------------------------------------- 1 | if not IsDuplicityVersion() then 2 | Citizen.CreateThread(function() 3 | while true do 4 | Citizen.Wait(0) 5 | if NetworkIsSessionStarted() then 6 | TriggerServerEvent("Queue:playerActivated") 7 | return 8 | end 9 | end 10 | end) 11 | return 12 | end 13 | 14 | local Queue = {} 15 | -- EDIT THESE IN SERVER.CFG + OTHER OPTIONS IN CONFIG.LUA 16 | Queue.MaxPlayers = GetConvarInt("sv_maxclients", 30) 17 | Queue.Debug = GetConvar("sv_debugqueue", "true") == "true" and true or false 18 | Queue.DisplayQueue = GetConvar("sv_displayqueue", "true") == "true" and true or false 19 | Queue.InitHostName = GetConvar("sv_hostname") 20 | 21 | 22 | -- This is needed because msgpack will break when tables are too large 23 | local _Queue = {} 24 | _Queue.QueueList = {} 25 | _Queue.PlayerList = {} 26 | _Queue.PlayerCount = 0 27 | _Queue.Priority = {} 28 | _Queue.Connecting = {} 29 | _Queue.JoinCbs = {} 30 | _Queue.TempPriority = {} 31 | _Queue.JoinDelay = GetGameTimer() + Config.JoinDelay and Config.JoinDelay or 0 32 | 33 | local tostring = tostring 34 | local tonumber = tonumber 35 | local ipairs = ipairs 36 | local pairs = pairs 37 | local print = print 38 | local string_len = string.len 39 | local string_sub = string.sub 40 | local string_format = string.format 41 | local string_lower = string.lower 42 | local math_abs = math.abs 43 | local math_floor = math.floor 44 | local math_random = math.random 45 | local os_time = os.time 46 | local table_insert = table.insert 47 | local table_remove = table.remove 48 | 49 | Queue.InitHostName = Queue.InitHostName ~= "default FXServer" and Queue.InitHostName or false 50 | 51 | for id, power in pairs(Config.Priority) do 52 | _Queue.Priority[string_lower(id)] = power 53 | end 54 | 55 | function Queue:DebugPrint(msg) 56 | if Queue.Debug then 57 | msg = "^3QUEUE: ^0" .. tostring(msg) .. "^7" 58 | print(msg) 59 | end 60 | end 61 | 62 | function Queue:HexIdToSteamId(hexId) 63 | local cid = math_floor(tonumber(string_sub(hexId, 7), 16)) 64 | local steam64 = math_floor(tonumber(string_sub( cid, 2))) 65 | local a = steam64 % 2 == 0 and 0 or 1 66 | local b = math_floor(math_abs(6561197960265728 - steam64 - a) / 2) 67 | local sid = "steam_0:"..a..":"..(a == 1 and b -1 or b) 68 | return sid 69 | end 70 | 71 | function Queue:IsSteamRunning(src) 72 | for _, id in ipairs(GetPlayerIdentifiers(src)) do 73 | if string_sub(id, 1, 5) == "steam" then 74 | return true 75 | end 76 | end 77 | 78 | return false 79 | end 80 | 81 | function Queue:GetPlayerCount() 82 | return _Queue.PlayerCount 83 | end 84 | 85 | function Queue:GetSize() 86 | return #_Queue.QueueList 87 | end 88 | 89 | function Queue:ConnectingSize() 90 | return #_Queue.Connecting 91 | end 92 | 93 | function Queue:GetQueueList() 94 | return _Queue.QueueList 95 | end 96 | 97 | function Queue:GetPriorityList() 98 | return _Queue.Priority 99 | end 100 | 101 | function Queue:GetPlayerList() 102 | return _Queue.PlayerList 103 | end 104 | 105 | function Queue:GetTempPriorityList() 106 | return _Queue.TempPriority 107 | end 108 | 109 | function Queue:GetConnectingList() 110 | return _Queue.Connecting 111 | end 112 | 113 | function Queue:IsInQueue(ids, rtnTbl, bySource, connecting) 114 | local connList = Queue:GetConnectingList() 115 | local queueList = Queue:GetQueueList() 116 | 117 | for genericKey1, genericValue1 in ipairs(connecting and connList or queueList) do 118 | local inQueue = false 119 | 120 | if not bySource then 121 | for genericKey2, genericValue2 in ipairs(genericValue1.ids) do 122 | if inQueue then break end 123 | 124 | for genericKey3, genericValue3 in ipairs(ids) do 125 | if genericValue3 == genericValue2 then inQueue = true break end 126 | end 127 | end 128 | else 129 | inQueue = ids == genericValue1.source 130 | end 131 | 132 | if inQueue then 133 | if rtnTbl then 134 | return genericKey1, connecting and connList[genericKey1] or queueList[genericKey1] 135 | end 136 | 137 | return true 138 | end 139 | end 140 | 141 | return false 142 | end 143 | 144 | function Queue:IsPriority(ids) 145 | local prio = false 146 | local tempPower, tempEnd = Queue:HasTempPriority(ids) 147 | local prioList = Queue:GetPriorityList() 148 | 149 | for _, id in ipairs(ids) do 150 | id = string_lower(id) 151 | 152 | if prioList[id] then prio = prioList[id] break end 153 | 154 | if string_sub(id, 1, 5) == "steam" then 155 | local steamid = Queue:HexIdToSteamId(id) 156 | if prioList[steamid] then prio = prioList[steamid] break end 157 | end 158 | end 159 | 160 | if tempPower or prio then 161 | if tempPower and prio then 162 | return tempPower > prio and tempPower or prio 163 | else 164 | return tempPower or prio 165 | end 166 | end 167 | 168 | return false 169 | end 170 | 171 | function Queue:HasTempPriority(ids) 172 | local tmpPrio = Queue:GetTempPriorityList() 173 | 174 | for _, id in pairs(ids) do 175 | id = string_lower(id) 176 | 177 | if tmpPrio[id] then return tmpPrio[id].power, tmpPrio[id].endTime, id end 178 | 179 | if string_sub(id, 1, 5) == "steam" then 180 | local steamid = Queue:HexIdToSteamId(id) 181 | if tmpPrio[steamid] then return tmpPrio[steamid].power, tmpPrio[steamid].endTime, id end 182 | end 183 | end 184 | 185 | return false 186 | end 187 | 188 | function Queue:AddToQueue(ids, connectTime, name, src, deferrals) 189 | if Queue:IsInQueue(ids) then return end 190 | 191 | local tmp = { 192 | source = src, 193 | ids = ids, 194 | name = name, 195 | priority = Queue:IsPriority(ids) or (src == "debug" and math_random(0, 15)), 196 | timeout = 0, 197 | deferrals = deferrals, 198 | firstconnect = connectTime, 199 | queuetime = function() return (os_time() - connectTime) end 200 | } 201 | 202 | local _pos = false 203 | local queueCount = Queue:GetSize() + 1 204 | local queueList = Queue:GetQueueList() 205 | 206 | for pos, data in ipairs(queueList) do 207 | if tmp.priority then 208 | if not data.priority then 209 | _pos = pos 210 | else 211 | if tmp.priority > data.priority then 212 | _pos = pos 213 | end 214 | end 215 | 216 | if _pos then 217 | Queue:DebugPrint(string_format("%s[%s] was prioritized and placed %d/%d in queue", tmp.name, ids[1], _pos, queueCount)) 218 | break 219 | end 220 | end 221 | end 222 | 223 | if not _pos then 224 | _pos = Queue:GetSize() + 1 225 | Queue:DebugPrint(string_format("%s[%s] was placed %d/%d in queue", tmp.name, ids[1], _pos, queueCount)) 226 | end 227 | 228 | table_insert(queueList, _pos, tmp) 229 | end 230 | 231 | function Queue:RemoveFromQueue(ids, bySource, byIndex) 232 | local queueList = Queue:GetQueueList() 233 | 234 | if byIndex then 235 | if queueList[byIndex] then 236 | table_remove(queueList, byIndex) 237 | end 238 | 239 | return 240 | end 241 | 242 | if Queue:IsInQueue(ids, false, bySource) then 243 | local pos, data = Queue:IsInQueue(ids, true, bySource) 244 | table_remove(queueList, pos) 245 | end 246 | end 247 | 248 | function Queue:TempSize() 249 | local count = 0 250 | 251 | for _pos, data in pairs(Queue:GetQueueList()) do 252 | if Queue:HasTempPriority(data.ids) then count = count +1 end 253 | end 254 | 255 | return count > 0 and count or false 256 | end 257 | 258 | function Queue:IsInConnecting(ids, bySource, refresh) 259 | local inConnecting, tbl = Queue:IsInQueue(ids, refresh and true or false, bySource and true or false, true) 260 | 261 | if not inConnecting then return false end 262 | 263 | if refresh and inConnecting and tbl then 264 | Queue:GetConnectingList()[inConnecting].timeout = 0 265 | end 266 | 267 | return true 268 | end 269 | 270 | function Queue:RemoveFromConnecting(ids, bySource, byIndex) 271 | local connList = Queue:GetConnectingList() 272 | 273 | if byIndex then 274 | if connList[byIndex] then 275 | table_remove(connList, byIndex) 276 | end 277 | 278 | return 279 | end 280 | 281 | for genericKey1, genericValue1 in ipairs(connList) do 282 | local inConnecting = false 283 | 284 | if not bySource then 285 | for genericKey2, genericValue2 in ipairs(genericValue1.ids) do 286 | if inConnecting then break end 287 | 288 | for genericKey3, genericValue3 in ipairs(ids) do 289 | if genericValue3 == genericValue2 then inConnecting = true break end 290 | end 291 | end 292 | else 293 | inConnecting = ids == genericValue1.source 294 | end 295 | 296 | if inConnecting then 297 | table_remove(connList, genericKey1) 298 | return true 299 | end 300 | end 301 | 302 | return false 303 | end 304 | 305 | function Queue:AddToConnecting(ids, ignorePos, autoRemove, done) 306 | local function remove() 307 | if not autoRemove then return end 308 | 309 | done(Config.Language.connectingerr) 310 | Queue:RemoveFromConnecting(ids) 311 | Queue:RemoveFromQueue(ids) 312 | Queue:DebugPrint("Player could not be added to the connecting list") 313 | end 314 | 315 | local connList = Queue:GetConnectingList() 316 | 317 | if Queue:ConnectingSize() + Queue:GetPlayerCount() + 1 > Queue.MaxPlayers then remove() return false end 318 | 319 | if ids[1] == "debug" then 320 | table_insert(connList, {source = ids[1], ids = ids, name = ids[1], firstconnect = ids[1], priority = ids[1], timeout = 0}) 321 | return true 322 | end 323 | 324 | if Queue:IsInConnecting(ids) then Queue:RemoveFromConnecting(ids) end 325 | 326 | local pos, data = Queue:IsInQueue(ids, true) 327 | if not ignorePos and (not pos or pos > 1) then remove() return false end 328 | 329 | table_insert(connList, data) 330 | Queue:RemoveFromQueue(ids) 331 | 332 | return true 333 | end 334 | 335 | function Queue:GetIds(src) 336 | local ids = GetPlayerIdentifiers(src) 337 | local ip = GetPlayerEndpoint(src) 338 | 339 | ids = (ids and ids[1]) and ids or (ip and {"ip:" .. ip} or false) 340 | ids = ids ~= nil and ids or false 341 | 342 | if ids and #ids > 1 then 343 | for k, id in ipairs(ids) do 344 | if string_sub(id, 1, 3) == "ip:" and not Queue:IsPriority({id}) then table_remove(ids, k) end 345 | end 346 | end 347 | 348 | return ids 349 | end 350 | 351 | function Queue:AddPriority(id, power, temp) 352 | if not id then return false end 353 | 354 | if type(id) == "table" then 355 | for _id, power in pairs(id) do 356 | if _id and type(_id) == "string" and power and type(power) == "number" then 357 | Queue:GetPriorityList()[_id] = power 358 | else 359 | Queue:DebugPrint("Error adding a priority id, invalid data passed") 360 | return false 361 | end 362 | end 363 | 364 | return true 365 | end 366 | 367 | power = (power and type(power) == "number") and power or 10 368 | 369 | if temp then 370 | local tempPower, tempEnd, tempId = Queue:HasTempPriority({id}) 371 | id = tempId or id 372 | 373 | Queue:GetTempPriorityList()[string_lower(id)] = {power = power, endTime = os_time() + temp} 374 | else 375 | Queue:GetPriorityList()[string_lower(id)] = power 376 | end 377 | 378 | return true 379 | end 380 | 381 | function Queue:RemovePriority(id) 382 | if not id then return false end 383 | id = string_lower(id) 384 | Queue:GetPriorityList()[id] = nil 385 | return true 386 | end 387 | 388 | function Queue:UpdatePosData(src, ids, deferrals) 389 | local pos, data = Queue:IsInQueue(ids, true) 390 | data.source = src 391 | data.ids = ids 392 | data.timeout = 0 393 | data.firstconnect = os_time() 394 | data.name = GetPlayerName(src) 395 | data.deferrals = deferrals 396 | end 397 | 398 | function Queue:NotFull(firstJoin) 399 | local canJoin = Queue:GetPlayerCount() + Queue:ConnectingSize() < Queue.MaxPlayers 400 | if firstJoin and canJoin then canJoin = Queue:GetSize() <= 1 end 401 | return canJoin 402 | end 403 | 404 | function Queue:SetPos(ids, newPos) 405 | if newPos <= 0 or newPos > Queue:GetSize() then return false end 406 | 407 | local pos, data = Queue:IsInQueue(ids, true) 408 | local queueList = Queue:GetQueueList() 409 | 410 | table_remove(queueList, pos) 411 | table_insert(queueList, newPos, data) 412 | end 413 | 414 | function Queue:CanJoin(src, cb) 415 | local allow = true 416 | 417 | for _, data in ipairs(_Queue.JoinCbs) do 418 | local await = true 419 | 420 | data.func(src, function(reason) 421 | if reason and type(reason) == "string" then allow = false cb(reason) end 422 | await = false 423 | end) 424 | 425 | while await do Citizen.Wait(0) end 426 | 427 | if not allow then return end 428 | end 429 | 430 | if allow then cb(false) end 431 | end 432 | 433 | function Queue:OnJoin(cb, resource) 434 | if not cb then return end 435 | 436 | local tmp = {resource = resource, func = cb} 437 | table_insert(_Queue.JoinCbs, tmp) 438 | end 439 | 440 | exports("GetQueueExports", function() 441 | return Queue 442 | end) 443 | 444 | local function playerConnect(name, setKickReason, deferrals) 445 | local src = source 446 | local ids = Queue:GetIds(src) 447 | local name = GetPlayerName(src) 448 | local connectTime = os_time() 449 | local connecting = true 450 | 451 | deferrals.defer() 452 | 453 | if Config.AntiSpam then 454 | for i=Config.AntiSpamTimer,0,-1 do 455 | deferrals.update(string.format(Config.PleaseWait, i)) 456 | Citizen.Wait(1000) 457 | end 458 | end 459 | 460 | Citizen.CreateThread(function() 461 | while connecting do 462 | Citizen.Wait(100) 463 | if not connecting then return end 464 | deferrals.update(Config.Language.connecting) 465 | end 466 | end) 467 | 468 | Citizen.Wait(500) 469 | 470 | local function done(msg, _deferrals) 471 | connecting = false 472 | 473 | local deferrals = _deferrals or deferrals 474 | 475 | if msg then deferrals.update(tostring(msg) or "") end 476 | 477 | Citizen.Wait(500) 478 | 479 | if not msg then 480 | deferrals.done() 481 | if Config.EnableGrace then Queue:AddPriority(ids[1], Config.GracePower, Config.GraceTime) end 482 | else 483 | deferrals.done(tostring(msg) or "") CancelEvent() 484 | end 485 | 486 | return 487 | end 488 | 489 | local function update(msg, _deferrals) 490 | local deferrals = _deferrals or deferrals 491 | connecting = false 492 | deferrals.update(tostring(msg) or "") 493 | end 494 | 495 | if not ids then 496 | -- prevent joining 497 | done(Config.Language.idrr) 498 | CancelEvent() 499 | Queue:DebugPrint("Dropped " .. name .. ", couldn't retrieve any of their id's") 500 | return 501 | end 502 | 503 | if Config.RequireSteam and not Queue:IsSteamRunning(src) then 504 | -- prevent joining 505 | done(Config.Language.steam) 506 | CancelEvent() 507 | return 508 | end 509 | 510 | local allow 511 | 512 | Queue:CanJoin(src, function(reason) 513 | if reason == nil or allow ~= nil then return end 514 | if reason == false or #_Queue.JoinCbs <= 0 then allow = true return end 515 | 516 | if reason then 517 | -- prevent joining 518 | allow = false 519 | done(reason and tostring(reason) or "You were blocked from joining") 520 | Queue:RemoveFromQueue(ids) 521 | Queue:RemoveFromConnecting(ids) 522 | Queue:DebugPrint(string_format("%s[%s] was blocked from joining; Reason: %s", name, ids[1], reason)) 523 | CancelEvent() 524 | return 525 | end 526 | 527 | allow = true 528 | end) 529 | 530 | while allow == nil do Citizen.Wait(0) end 531 | if not allow then return end 532 | 533 | if Config.PriorityOnly and not Queue:IsPriority(ids) then done(Config.Language.wlonly) return end 534 | 535 | local rejoined = false 536 | 537 | if Queue:IsInConnecting(ids, false, true) then 538 | Queue:RemoveFromConnecting(ids) 539 | 540 | if Queue:NotFull() then 541 | -- let them in the server 542 | 543 | if not Queue:IsInQueue(ids) then 544 | Queue:AddToQueue(ids, connectTime, name, src, deferrals) 545 | end 546 | 547 | local added = Queue:AddToConnecting(ids, true, true, done) 548 | if not added then CancelEvent() return end 549 | done() 550 | 551 | return 552 | else 553 | rejoined = true 554 | end 555 | end 556 | 557 | if Queue:IsInQueue(ids) then 558 | rejoined = true 559 | Queue:UpdatePosData(src, ids, deferrals) 560 | Queue:DebugPrint(string_format("%s[%s] has rejoined queue after cancelling", name, ids[1])) 561 | else 562 | Queue:AddToQueue(ids, connectTime, name, src, deferrals) 563 | 564 | if rejoined then 565 | Queue:SetPos(ids, 1) 566 | rejoined = false 567 | end 568 | end 569 | 570 | local pos, data = Queue:IsInQueue(ids, true) 571 | 572 | if not pos or not data then 573 | done(Config.Language.err .. " [1]") 574 | 575 | Queue:RemoveFromQueue(ids) 576 | Queue:RemoveFromConnecting(ids) 577 | 578 | CancelEvent() 579 | return 580 | end 581 | 582 | if Queue:NotFull(true) and _Queue.JoinDelay <= GetGameTimer() then 583 | -- let them in the server 584 | local added = Queue:AddToConnecting(ids, true, true, done) 585 | if not added then CancelEvent() return end 586 | 587 | done() 588 | Queue:DebugPrint(name .. "[" .. ids[1] .. "] is loading into the server") 589 | 590 | return 591 | end 592 | 593 | update(string_format(Config.Language.pos .. ((Queue:TempSize() and Config.ShowTemp) and " (" .. Queue:TempSize() .. " temp)" or "00:00:00"), pos, Queue:GetSize(), "")) 594 | 595 | if rejoined then return end 596 | 597 | while true do 598 | Citizen.Wait(500) 599 | 600 | local pos, data = Queue:IsInQueue(ids, true) 601 | 602 | local function remove(msg) 603 | if data then 604 | if msg then 605 | update(msg, data.deferrals) 606 | end 607 | 608 | Queue:RemoveFromQueue(data.source, true) 609 | Queue:RemoveFromConnecting(data.source, true) 610 | else 611 | Queue:RemoveFromQueue(ids) 612 | Queue:RemoveFromConnecting(ids) 613 | end 614 | end 615 | 616 | if not data or not data.deferrals or not data.source or not pos then 617 | remove("[Queue] Removed from queue, queue data invalid :(") 618 | Queue:DebugPrint(tostring(name .. "[" .. ids[1] .. "] was removed from the queue because they had invalid data")) 619 | return 620 | end 621 | 622 | local endPoint = GetPlayerEndpoint(data.source) 623 | if not endPoint then data.timeout = data.timeout + 0.5 else data.timeout = 0 end 624 | 625 | if data.timeout >= Config.QueueTimeOut and os_time() - connectTime > 5 then 626 | remove("[Queue] Removed due to timeout") 627 | Queue:DebugPrint(name .. "[" .. ids[1] .. "] was removed from the queue because they timed out") 628 | return 629 | end 630 | 631 | if pos <= 1 and Queue:NotFull() and _Queue.JoinDelay <= GetGameTimer() then 632 | -- let them in the server 633 | local added = Queue:AddToConnecting(ids) 634 | 635 | update(Config.Language.joining, data.deferrals) 636 | Citizen.Wait(500) 637 | 638 | if not added then 639 | done(Config.Language.connectingerr) 640 | CancelEvent() 641 | return 642 | end 643 | 644 | done(nil, data.deferrals) 645 | 646 | if Config.EnableGrace then Queue:AddPriority(ids[1], Config.GracePower, Config.GraceTime) end 647 | 648 | Queue:RemoveFromQueue(ids) 649 | Queue:DebugPrint(name .. "[" .. ids[1] .. "] is loading into the server") 650 | return 651 | end 652 | 653 | local seconds = data.queuetime() 654 | 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)) 655 | 656 | local msg = string_format(Config.Language.pos .. ((Queue:TempSize() and Config.ShowTemp) and " (" .. Queue:TempSize() .. " temp)" or ""), pos, Queue:GetSize(), qTime) 657 | update(msg, data.deferrals) 658 | end 659 | end 660 | AddEventHandler("playerConnecting", playerConnect) 661 | 662 | Citizen.CreateThread(function() 663 | local function remove(data, pos, msg) 664 | if data and data.source then 665 | Queue:RemoveFromQueue(data.source, true) 666 | Queue:RemoveFromConnecting(data.source, true) 667 | elseif pos then 668 | table_remove(Queue:GetQueueList(), pos) 669 | end 670 | end 671 | 672 | while true do 673 | Citizen.Wait(1000) 674 | 675 | local i = 1 676 | 677 | while i <= Queue:ConnectingSize() do 678 | local data = Queue:GetConnectingList()[i] 679 | 680 | local endPoint = GetPlayerEndpoint(data.source) 681 | 682 | data.timeout = data.timeout + 1 683 | 684 | if ((data.timeout >= 300 and not endPoint) or data.timeout >= Config.ConnectTimeOut) and data.source ~= "debug" and os_time() - data.firstconnect > 5 then 685 | remove(data) 686 | Queue:DebugPrint(data.name .. "[" .. data.ids[1] .. "] was removed from the connecting queue because they timed out") 687 | else 688 | i = i + 1 689 | end 690 | end 691 | 692 | for id, data in pairs(Queue:GetTempPriorityList()) do 693 | if os_time() >= data.endTime then 694 | Queue:GetTempPriorityList()[id] = nil 695 | end 696 | end 697 | 698 | Queue.MaxPlayers = GetConvarInt("sv_maxclients", 30) 699 | Queue.Debug = GetConvar("sv_debugqueue", "true") == "true" and true or false 700 | Queue.DisplayQueue = GetConvar("sv_displayqueue", "true") == "true" and true or false 701 | 702 | local qCount = Queue:GetSize() 703 | 704 | if Queue.DisplayQueue then 705 | if Queue.InitHostName then 706 | SetConvar("sv_hostname", (qCount > 0 and "[" .. tostring(qCount) .. "] " or "") .. Queue.InitHostName) 707 | else 708 | Queue.InitHostName = GetConvar("sv_hostname") 709 | Queue.InitHostName = Queue.InitHostName ~= "default FXServer" and Queue.InitHostName or false 710 | end 711 | end 712 | end 713 | end) 714 | 715 | RegisterServerEvent("Queue:playerActivated") 716 | AddEventHandler("Queue:playerActivated", function() 717 | local src = source 718 | local ids = Queue:GetIds(src) 719 | 720 | if not Queue:GetPlayerList()[src] then 721 | _Queue.PlayerCount = Queue:GetPlayerCount() + 1 722 | Queue:GetPlayerList()[src] = true 723 | Queue:RemoveFromQueue(ids) 724 | Queue:RemoveFromConnecting(ids) 725 | end 726 | end) 727 | 728 | AddEventHandler("playerDropped", function() 729 | local src = source 730 | local ids = Queue:GetIds(src) 731 | 732 | if Queue:GetPlayerList()[src] then 733 | _Queue.PlayerCount = Queue:GetPlayerCount() - 1 734 | Queue:GetPlayerList()[src] = nil 735 | Queue:RemoveFromQueue(ids) 736 | Queue:RemoveFromConnecting(ids) 737 | if Config.EnableGrace then Queue:AddPriority(ids[1], Config.GracePower, Config.GraceTime) end 738 | end 739 | end) 740 | 741 | AddEventHandler("onResourceStop", function(resource) 742 | if Queue.DisplayQueue and Queue.InitHostName and resource == GetCurrentResourceName() then SetConvar("sv_hostname", Queue.InitHostName) end 743 | 744 | for k, data in ipairs(_Queue.JoinCbs) do 745 | if data.resource == resource then 746 | table_remove(_Queue.JoinCbs, k) 747 | end 748 | end 749 | end) 750 | 751 | if Config.DisableHardCap then 752 | Queue:DebugPrint("^1 [connectqueue] Disabling hardcap ^7") 753 | 754 | AddEventHandler("onResourceStarting", function(resource) 755 | if resource == "hardcap" then CancelEvent() return end 756 | end) 757 | 758 | StopResource("hardcap") 759 | end 760 | 761 | local testAdds = 0 762 | local commands = {} 763 | 764 | commands.addq = function() 765 | Queue:DebugPrint("ADDED DEBUG QUEUE") 766 | Queue:AddToQueue({"steam:110000103fd1bb1"..testAdds}, os_time(), "TestAdd: " .. testAdds, "debug") 767 | testAdds = testAdds + 1 768 | end 769 | 770 | commands.removeq = function(args) 771 | args[1] = tonumber(args[1]) 772 | local name = Queue:GetQueueList()[args[1]] and Queue:GetQueueList()[args[1]].name or nil 773 | Queue:RemoveFromQueue(nil, nil, args[1]) 774 | Queue:DebugPrint("REMOVED " .. tostring(name) .. " FROM THE QUEUE") 775 | end 776 | 777 | commands.printq = function() 778 | Queue:DebugPrint("CURRENT QUEUE LIST") 779 | 780 | for pos, data in ipairs(Queue:GetQueueList()) do 781 | Queue:DebugPrint(pos .. ": [src: " .. data.source .. "] " .. data.name .. "[" .. data.ids[1] .. "] | Priority: " .. (tostring(data.priority and data.priority or false)) .. " | Last Msg: " .. (data.source ~= "debug" and GetPlayerLastMsg(data.source) or "debug") .. " | Timeout: " .. data.timeout .. " | Queue Time: " .. data.queuetime() .. " Seconds") 782 | end 783 | end 784 | 785 | commands.addc = function() 786 | Queue:AddToConnecting({"debug"}) 787 | Queue:DebugPrint("ADDED DEBUG CONNECTING QUEUE") 788 | end 789 | 790 | commands.removec = function(args) 791 | args[1] = tonumber(args[1]) 792 | local name = Queue:GetConnectingList()[args[1]] and Queue:GetConnectingList()[args[1]].name or nil 793 | Queue:RemoveFromConnecting(nil, nil, args[1]) 794 | Queue:DebugPrint("REMOVED " .. tostring(name) .. " FROM THE CONNECTING LIST") 795 | end 796 | 797 | commands.printc = function() 798 | Queue:DebugPrint("CURRENT CONNECTING LIST") 799 | 800 | for pos, data in ipairs(Queue:GetConnectingList()) do 801 | Queue:DebugPrint(pos .. ": [src: " .. data.source .. "] " .. data.name .. "[" .. data.ids[1] .. "] | Priority: " .. (tostring(data.priority and data.priority or false)) .. " | Last Msg: " .. (data.source ~= "debug" and GetPlayerLastMsg(data.source) or "debug") .. " | Timeout: " .. data.timeout) 802 | end 803 | end 804 | 805 | commands.printl = function() 806 | for k, joined in pairs(Queue:GetPlayerList()) do 807 | Queue:DebugPrint(k .. ": " .. tostring(joined)) 808 | end 809 | end 810 | 811 | commands.printp = function() 812 | Queue:DebugPrint("CURRENT PRIORITY LIST") 813 | 814 | for id, power in pairs(Queue:GetPriorityList()) do 815 | Queue:DebugPrint(id .. ": " .. tostring(power)) 816 | end 817 | end 818 | 819 | commands.printcount = function() 820 | Queue:DebugPrint("Player Count: " .. Queue:GetPlayerCount()) 821 | end 822 | 823 | commands.printtp = function() 824 | Queue:DebugPrint("CURRENT TEMP PRIORITY LIST") 825 | 826 | for k, data in pairs(Queue:GetTempPriorityList()) do 827 | Queue:DebugPrint(k .. ": Power: " .. tostring(data.power) .. " | EndTime: " .. tostring(data.endTime) .. " | CurTime: " .. tostring(os_time())) 828 | end 829 | end 830 | 831 | commands.removetp = function(args) 832 | if not args[1] then return end 833 | 834 | Queue:GetTempPriorityList()[args[1]] = nil 835 | Queue:DebugPrint("REMOVED " .. args[1] .. " FROM THE TEMP PRIORITY LIST") 836 | end 837 | 838 | commands.setpos = function(args) 839 | if not args[1] or not args[2] then return end 840 | 841 | args[1], args[2] = tonumber(args[1]), tonumber(args[2]) 842 | 843 | local data = Queue:GetQueueList()[args[1]] 844 | 845 | Queue:SetPos(data.ids, args[2]) 846 | 847 | Queue:DebugPrint("SET " .. data.name .. "'s QUEUE POSITION TO: " .. args[2]) 848 | end 849 | 850 | commands.setdata = function(args) 851 | if not args[1] or not args[2] or not args[3] then return end 852 | args[1] = tonumber(args[1]) 853 | 854 | local num = tonumber(args[3]) 855 | local data = Queue:GetQueueList()[args[1]] 856 | 857 | if args[2] == "queuetime" then 858 | local time = data.queuetime() 859 | local dif = time - num 860 | 861 | data.firstconnect = data.firstconnect + dif 862 | data.queuetime = function() return (os_time() - data.firstconnect) end 863 | else 864 | data[args[2]] = num and num or args[3] 865 | end 866 | 867 | Queue:DebugPrint("SET " .. data.name .. "'s " .. args[2] .. " DATA TO " .. args[3]) 868 | end 869 | 870 | commands.commands = function() 871 | for cmd, func in pairs(commands) do 872 | Queue:DebugPrint(tostring(cmd)) 873 | end 874 | end 875 | 876 | AddEventHandler("rconCommand", function(command, args) 877 | if command == "queue" and commands[args[1]] then 878 | command = args[1] 879 | table_remove(args, 1) 880 | commands[command](args) 881 | CancelEvent() 882 | end 883 | end) 884 | --------------------------------------------------------------------------------