Skip to content

Commit

Permalink
feat(queue): sub-queues
Browse files Browse the repository at this point in the history
  • Loading branch information
D4isDAVID committed Dec 25, 2023
1 parent 5499200 commit 45ca988
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 20 deletions.
13 changes: 13 additions & 0 deletions config/queue.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
},
}
3 changes: 2 additions & 1 deletion locale/en.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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 = {
Expand Down
111 changes: 92 additions & 19 deletions server/queue.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,82 @@ end
local config = require 'config.queue'
local maxPlayers = GlobalState.MaxPlayers

---Player license to queue position map.
---@type table<string, integer>
local playerPositions = {}
local queueSize = 0
---@class SubQueue : SubQueueConfig
---@field positions table<string, number> 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<string, PlayerQueueData>
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
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 45ca988

Please sign in to comment.