|
| 1 | +if GetConvar('qbx:enablequeue', 'true') == 'false' then return end |
| 2 | + |
| 3 | +-- Disable hardcap because it kicks the player when the server is full |
| 4 | + |
| 5 | +---@param resource string |
| 6 | +AddEventHandler('onResourceStarting', function(resource) |
| 7 | + if resource == 'hardcap' then |
| 8 | + lib.print.info('Preventing hardcap from starting...') |
| 9 | + CancelEvent() |
| 10 | + end |
| 11 | +end) |
| 12 | + |
| 13 | +if GetResourceState('hardcap'):find('start') then |
| 14 | + lib.print.info('Stopping hardcap...') |
| 15 | + StopResource('hardcap') |
| 16 | +end |
| 17 | + |
| 18 | +-- Queue code |
| 19 | + |
| 20 | +local maxPlayers = GlobalState.MaxPlayers |
| 21 | + |
| 22 | +---Player license to queue position map. |
| 23 | +---@type table<string, integer> |
| 24 | +local playerPositions = {} |
| 25 | +local queueSize = 0 |
| 26 | + |
| 27 | +---Map of player licenses that passed the queue and are downloading server content. |
| 28 | +---Needs to be saved because these players won't be part of regular player counts such as `GetNumPlayerIndices`. |
| 29 | +---@type table<string, true> |
| 30 | +local joiningPlayers = {} |
| 31 | +local joiningPlayerCount = 0 |
| 32 | + |
| 33 | +---@param license string |
| 34 | +local function addPlayerJoining(license) |
| 35 | + if not joiningPlayers[license] then |
| 36 | + joiningPlayerCount += 1 |
| 37 | + end |
| 38 | + joiningPlayers[license] = true |
| 39 | +end |
| 40 | + |
| 41 | +---@param license string |
| 42 | +local function removePlayerJoining(license) |
| 43 | + if joiningPlayers[license] then |
| 44 | + joiningPlayerCount -= 1 |
| 45 | + end |
| 46 | + joiningPlayers[license] = nil |
| 47 | +end |
| 48 | + |
| 49 | +---@param license string |
| 50 | +local function enqueue(license) |
| 51 | + queueSize += 1 |
| 52 | + playerPositions[license] = queueSize |
| 53 | +end |
| 54 | + |
| 55 | +---@param license string |
| 56 | +local function dequeue(license) |
| 57 | + local pos = playerPositions[license] |
| 58 | + |
| 59 | + queueSize -= 1 |
| 60 | + playerPositions[license] = nil |
| 61 | + |
| 62 | + -- decrease the positions of players who are after the current player in queue |
| 63 | + for k, v in pairs(playerPositions) do |
| 64 | + if v > pos then |
| 65 | + playerPositions[k] -= 1 |
| 66 | + end |
| 67 | + end |
| 68 | +end |
| 69 | + |
| 70 | +---@param source Source |
| 71 | +---@param license string |
| 72 | +---@param deferrals Deferrals |
| 73 | +local function awaitPlayerQueue(source, license, deferrals) |
| 74 | + if playerPositions[license] then |
| 75 | + deferrals.done(Lang:t('error.already_in_queue')) |
| 76 | + return |
| 77 | + end |
| 78 | + |
| 79 | + enqueue(license) |
| 80 | + |
| 81 | + -- wait until the player disconnected or until there are available slots and the player is first in queue |
| 82 | + while DoesPlayerExist(source --[[@as string]]) and ((GetNumPlayerIndices() + joiningPlayerCount) >= maxPlayers or playerPositions[license] > 1) do |
| 83 | + deferrals.update(Lang:t('info.in_queue', { |
| 84 | + queuePos = playerPositions[license], |
| 85 | + queueSize = queueSize, |
| 86 | + })) |
| 87 | + |
| 88 | + Wait(1000) |
| 89 | + end |
| 90 | + |
| 91 | + -- if the player disconnected while waiting in queue |
| 92 | + if not DoesPlayerExist(source --[[@as string]]) then |
| 93 | + dequeue(license) |
| 94 | + return |
| 95 | + end |
| 96 | + |
| 97 | + addPlayerJoining(license) |
| 98 | + dequeue(license) |
| 99 | + deferrals.done() |
| 100 | + |
| 101 | + -- wait until the player finally joins or disconnects while installing server content |
| 102 | + -- this may result in waiting ~2 additional minutes if the player disconnects as FXServer will think that the player exists |
| 103 | + while DoesPlayerExist(source --[[@as string]]) do |
| 104 | + Wait(1000) |
| 105 | + end |
| 106 | + removePlayerJoining(license) |
| 107 | +end |
| 108 | + |
| 109 | +return { |
| 110 | + awaitPlayerQueue = awaitPlayerQueue, |
| 111 | +} |
0 commit comments