diff --git a/config/queue.lua b/config/queue.lua index fad5e0823..6203bd301 100644 --- a/config/queue.lua +++ b/config/queue.lua @@ -5,4 +5,17 @@ return { ---Amount of seconds to wait before removing a player from the queue after disconnecting while installing server data. ---Notice that an additional ~2 minutes will be waited due to limitations with how FiveM handles joining players. joiningTimeoutSeconds = 0, + + ---@class SubQueueConfig + ---@field name string + ---@field predicate? fun(source: Source): boolean + + ---Sub-queues from most to least prioritized. + ---The first sub-queue without a predicate function will be considered the default. + ---If a player doesn't pass any predicated and a sub-queuee with no predicate does not exist they will not be let into the server unless player slots are available. + ---@type SubQueueConfig[] + subQueues = { + { name = 'Admin Queue', predicate = function(source) return HasPermission(source, 'admin') end }, + { name = 'Regular Queue' }, + }, } diff --git a/locale/en.lua b/locale/en.lua index 880dace3e..c9d74962c 100644 --- a/locale/en.lua +++ b/locale/en.lua @@ -21,6 +21,7 @@ local Translations = { connecting_error = 'An error occurred while connecting to the server. (Check your server console)', no_match_character_registration = 'Anything other than letters aren\'t allowed, trailing whitespaces aren\'t allowed either and words must start with a capital letter in input fields. You can however add words with spaces inbetween.', already_in_queue = 'You are already in queue.', + no_subqueue = 'You were not let in any sub-queue.', }, success = { server_opened = 'The server has been opened', @@ -60,7 +61,7 @@ local Translations = { birth_date = 'Birth Date', select_gender = 'Select your gender...', confirm_delete = 'Are you sure you wish to delete this character?', - in_queue = '🐌 You are %{queuePos}/%{queueSize} in queue.', + in_queue = '🐌 You are %{queuePos}/%{queueSize} in queue. (%{subQueue})', }, command = { tp = { diff --git a/server/queue.lua b/server/queue.lua index 14469cef9..eab85019c 100644 --- a/server/queue.lua +++ b/server/queue.lua @@ -20,28 +20,82 @@ end local config = require 'config.queue' local maxPlayers = GlobalState.MaxPlayers ----Player license to queue position map. ----@type table -local playerPositions = {} -local queueSize = 0 +---@class SubQueue : SubQueueConfig +---@field positions table Player license to sub-queue position map. +---@field size number + +---@type SubQueue[] +local subQueues = {} +for i = 1, #config.subQueues do + subQueues[i] = { + name = config.subQueues[i].name, + predicate = config.subQueues[i].predicate, + positions = {}, + size = 0, + } +end + +---@class PlayerQueueData +---@field subQueueIndex number +---@field globalPos number + +---Player license to queue data map. +---@type table +local playerDatas = {} +local totalQueueSize = 0 ---@param license string -local function enqueue(license) - queueSize += 1 - playerPositions[license] = queueSize +---@param subQueueIndex number +local function enqueue(license, subQueueIndex) + local subQueue = subQueues[subQueueIndex] + + subQueue.size += 1 + subQueue.positions[license] = subQueue.size + + local globalPos = subQueue.size + -- increase set the global position of the current player by the sizes of sub-queues the player comes after + for i = 1, subQueueIndex - 1 do + globalPos += subQueues[i].size + end + + totalQueueSize += 1 + playerDatas[license] = { + subQueueIndex = subQueueIndex, + globalPos = globalPos, + } + + -- inrease the global positions of players who are in sub-queues that come after the current player + for i = subQueueIndex + 1, #subQueues do + for k in pairs(subQueues[i].positions) do + playerDatas[k].globalPos += 1 + end + end end ---@param license string local function dequeue(license) - local pos = playerPositions[license] + local subQueueIndex = playerDatas[license].subQueueIndex + local subQueue = subQueues[subQueueIndex] + local subQueuePos = subQueue.positions[license] - queueSize -= 1 - playerPositions[license] = nil + subQueue.size -= 1 + subQueue.positions[license] = nil - -- decrease the positions of players who are after the current player in queue - for k, v in pairs(playerPositions) do - if v > pos then - playerPositions[k] -= 1 + totalQueueSize -= 1 + playerDatas[license] = nil + + -- decrease the positions of players who are after the current player in the same sub-queue + for k, v in pairs(subQueue.positions) do + if v > subQueuePos then + subQueue.positions[k] -= 1 + playerDatas[k].globalPos -= 1 + end + end + + -- decrease the global positions of players who are in sub-queues that come after the current player + for i = subQueueIndex + 1, #subQueues do + for k in pairs(subQueues[i].positions) do + playerDatas[k].globalPos -= 1 end end end @@ -133,21 +187,40 @@ local function awaitPlayerQueue(source, license, deferrals) end local playerTimingOut = isPlayerTimingOut(license) + local data = playerDatas[license] - if playerPositions[license] and not playerTimingOut then + if data and not playerTimingOut then deferrals.done(Lang:t('error.already_in_queue')) return end if not playerTimingOut then - enqueue(license) + local subQueueIndex + for i = 1, #subQueues do + local predicate = subQueues[i].predicate + if not predicate or predicate(source) then + subQueueIndex = i + break + end + end + + if not subQueueIndex then + deferrals.done(Lang:t('error.no_subqueue')) + return + end + + enqueue(license, subQueueIndex) + data = playerDatas[license] end + local subQueue = subQueues[data.subQueueIndex] + -- wait until the player disconnected or until there are available slots and the player is first in queue - while DoesPlayerExist(source --[[@as string]]) and ((GetNumPlayerIndices() + joiningPlayerCount) >= maxPlayers or playerPositions[license] > 1) do + while DoesPlayerExist(source --[[@as string]]) and ((GetNumPlayerIndices() + joiningPlayerCount) >= 1--[[maxPlayers]] or data.globalPos > 1) do deferrals.update(Lang:t('info.in_queue', { - queuePos = playerPositions[license], - queueSize = queueSize, + queuePos = data.globalPos, + queueSize = totalQueueSize, + subQueue = subQueue.name, })) Wait(1000)