Skip to content

Commit 9ff7c5f

Browse files
authored
feat(queue): sub-queues
* feat(queue): sub-queues * refactor(config/queue): fix typo and improve wording * refactor(config/queue): fix typo
1 parent a717d1e commit 9ff7c5f

File tree

3 files changed

+107
-20
lines changed

3 files changed

+107
-20
lines changed

config/queue.lua

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,17 @@ return {
55
---Amount of seconds to wait before removing a player from the queue after disconnecting while installing server data.
66
---Notice that an additional ~2 minutes will be waited due to limitations with how FiveM handles joining players.
77
joiningTimeoutSeconds = 0,
8+
9+
---@class SubQueueConfig
10+
---@field name string
11+
---@field predicate? fun(source: Source): boolean
12+
13+
---Sub-queues from most to least prioritized.
14+
---The first sub-queue without a predicate function will be considered the default.
15+
---If a player doesn't pass any predicate and a sub-queue with no predicate does not exist they will not be let into the server unless a player slot is available.
16+
---@type SubQueueConfig[]
17+
subQueues = {
18+
{ name = 'Admin Queue', predicate = function(source) return HasPermission(source, 'admin') end },
19+
{ name = 'Regular Queue' },
20+
},
821
}

locale/en.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ local Translations = {
2121
connecting_error = 'An error occurred while connecting to the server. (Check your server console)',
2222
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.',
2323
already_in_queue = 'You are already in queue.',
24+
no_subqueue = 'You were not let in any sub-queue.',
2425
},
2526
success = {
2627
server_opened = 'The server has been opened',
@@ -60,7 +61,7 @@ local Translations = {
6061
birth_date = 'Birth Date',
6162
select_gender = 'Select your gender...',
6263
confirm_delete = 'Are you sure you wish to delete this character?',
63-
in_queue = '🐌 You are %{queuePos}/%{queueSize} in queue.',
64+
in_queue = '🐌 You are %{queuePos}/%{queueSize} in queue. (%{subQueue})',
6465
},
6566
command = {
6667
tp = {

server/queue.lua

Lines changed: 92 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,82 @@ end
2020
local config = require 'config.queue'
2121
local maxPlayers = GlobalState.MaxPlayers
2222

23-
---Player license to queue position map.
24-
---@type table<string, integer>
25-
local playerPositions = {}
26-
local queueSize = 0
23+
---@class SubQueue : SubQueueConfig
24+
---@field positions table<string, number> Player license to sub-queue position map.
25+
---@field size number
26+
27+
---@type SubQueue[]
28+
local subQueues = {}
29+
for i = 1, #config.subQueues do
30+
subQueues[i] = {
31+
name = config.subQueues[i].name,
32+
predicate = config.subQueues[i].predicate,
33+
positions = {},
34+
size = 0,
35+
}
36+
end
37+
38+
---@class PlayerQueueData
39+
---@field subQueueIndex number
40+
---@field globalPos number
41+
42+
---Player license to queue data map.
43+
---@type table<string, PlayerQueueData>
44+
local playerDatas = {}
45+
local totalQueueSize = 0
2746

2847
---@param license string
29-
local function enqueue(license)
30-
queueSize += 1
31-
playerPositions[license] = queueSize
48+
---@param subQueueIndex number
49+
local function enqueue(license, subQueueIndex)
50+
local subQueue = subQueues[subQueueIndex]
51+
52+
subQueue.size += 1
53+
subQueue.positions[license] = subQueue.size
54+
55+
local globalPos = subQueue.size
56+
-- increase set the global position of the current player by the sizes of sub-queues the player comes after
57+
for i = 1, subQueueIndex - 1 do
58+
globalPos += subQueues[i].size
59+
end
60+
61+
totalQueueSize += 1
62+
playerDatas[license] = {
63+
subQueueIndex = subQueueIndex,
64+
globalPos = globalPos,
65+
}
66+
67+
-- inrease the global positions of players who are in sub-queues that come after the current player
68+
for i = subQueueIndex + 1, #subQueues do
69+
for k in pairs(subQueues[i].positions) do
70+
playerDatas[k].globalPos += 1
71+
end
72+
end
3273
end
3374

3475
---@param license string
3576
local function dequeue(license)
36-
local pos = playerPositions[license]
77+
local subQueueIndex = playerDatas[license].subQueueIndex
78+
local subQueue = subQueues[subQueueIndex]
79+
local subQueuePos = subQueue.positions[license]
3780

38-
queueSize -= 1
39-
playerPositions[license] = nil
81+
subQueue.size -= 1
82+
subQueue.positions[license] = nil
4083

41-
-- decrease the positions of players who are after the current player in queue
42-
for k, v in pairs(playerPositions) do
43-
if v > pos then
44-
playerPositions[k] -= 1
84+
totalQueueSize -= 1
85+
playerDatas[license] = nil
86+
87+
-- decrease the positions of players who are after the current player in the same sub-queue
88+
for k, v in pairs(subQueue.positions) do
89+
if v > subQueuePos then
90+
subQueue.positions[k] -= 1
91+
playerDatas[k].globalPos -= 1
92+
end
93+
end
94+
95+
-- decrease the global positions of players who are in sub-queues that come after the current player
96+
for i = subQueueIndex + 1, #subQueues do
97+
for k in pairs(subQueues[i].positions) do
98+
playerDatas[k].globalPos -= 1
4599
end
46100
end
47101
end
@@ -133,21 +187,40 @@ local function awaitPlayerQueue(source, license, deferrals)
133187
end
134188

135189
local playerTimingOut = isPlayerTimingOut(license)
190+
local data = playerDatas[license]
136191

137-
if playerPositions[license] and not playerTimingOut then
192+
if data and not playerTimingOut then
138193
deferrals.done(Lang:t('error.already_in_queue'))
139194
return
140195
end
141196

142197
if not playerTimingOut then
143-
enqueue(license)
198+
local subQueueIndex
199+
for i = 1, #subQueues do
200+
local predicate = subQueues[i].predicate
201+
if not predicate or predicate(source) then
202+
subQueueIndex = i
203+
break
204+
end
205+
end
206+
207+
if not subQueueIndex then
208+
deferrals.done(Lang:t('error.no_subqueue'))
209+
return
210+
end
211+
212+
enqueue(license, subQueueIndex)
213+
data = playerDatas[license]
144214
end
145215

216+
local subQueue = subQueues[data.subQueueIndex]
217+
146218
-- wait until the player disconnected or until there are available slots and the player is first in queue
147-
while DoesPlayerExist(source --[[@as string]]) and ((GetNumPlayerIndices() + joiningPlayerCount) >= maxPlayers or playerPositions[license] > 1) do
219+
while DoesPlayerExist(source --[[@as string]]) and ((GetNumPlayerIndices() + joiningPlayerCount) >= maxPlayers or data.globalPos > 1) do
148220
deferrals.update(Lang:t('info.in_queue', {
149-
queuePos = playerPositions[license],
150-
queueSize = queueSize,
221+
queuePos = data.globalPos,
222+
queueSize = totalQueueSize,
223+
subQueue = subQueue.name,
151224
}))
152225

153226
Wait(1000)

0 commit comments

Comments
 (0)