From b451307f6f57d6a68ca8543145cac7c7a8601588 Mon Sep 17 00:00:00 2001 From: David Malchin Date: Wed, 13 Dec 2023 22:09:42 +0200 Subject: [PATCH 1/2] feat: in-house queue system --- locale/en.lua | 6 ++- server/events.lua | 7 ++- server/queue.lua | 111 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 server/queue.lua diff --git a/locale/en.lua b/locale/en.lua index 92a975f9f..880dace3e 100644 --- a/locale/en.lua +++ b/locale/en.lua @@ -19,7 +19,8 @@ local Translations = { tp_error = 'Error While Teleporting.', connecting_database_timeout = 'Connection to database timed out. (Is the SQL server on?)', 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.' + 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.', }, success = { server_opened = 'The server has been opened', @@ -58,7 +59,8 @@ local Translations = { gender = 'Sex', birth_date = 'Birth Date', select_gender = 'Select your gender...', - confirm_delete = 'Are you sure you wish to delete this character?' + confirm_delete = 'Are you sure you wish to delete this character?', + in_queue = '🐌 You are %{queuePos}/%{queueSize} in queue.', }, command = { tp = { diff --git a/server/events.lua b/server/events.lua index 6ebe171f2..67fbfb56b 100644 --- a/server/events.lua +++ b/server/events.lua @@ -1,6 +1,7 @@ local serverConfig = require 'config.server'.server local loggingConfig = require 'config.server'.logging local logger = require 'modules.logger' +local queue = require 'server.queue' -- Event Handler @@ -108,7 +109,11 @@ local function onPlayerConnecting(name, _, deferrals) -- wait for database to finish databasePromise:next(function() deferrals.update(string.format(Lang:t('info.join_server'), name)) - deferrals.done() + if queue then + queue.awaitPlayerQueue(src --[[@as Source]], license, deferrals) + else + deferrals.done() + end end, function(err) deferrals.done(Lang:t('error.connecting_error')) lib.print.error(err) diff --git a/server/queue.lua b/server/queue.lua new file mode 100644 index 000000000..6a8f7eb3b --- /dev/null +++ b/server/queue.lua @@ -0,0 +1,111 @@ +if GetConvar('qbx:enablequeue', 'true') == 'false' then return end + +-- Disable hardcap because it kicks the player when the server is full + +---@param resource string +AddEventHandler('onResourceStarting', function(resource) + if resource == 'hardcap' then + lib.print.info('Preventing hardcap from starting...') + CancelEvent() + end +end) + +if GetResourceState('hardcap'):find('start') then + lib.print.info('Stopping hardcap...') + StopResource('hardcap') +end + +-- Queue code + +local maxPlayers = GlobalState.MaxPlayers + +---Player license to queue position map. +---@type table +local playerPositions = {} +local queueSize = 0 + +---Map of player licenses that passed the queue and are downloading server content. +---Needs to be saved because these players won't be part of regular player counts such as `GetNumPlayerIndices`. +---@type table +local joiningPlayers = {} +local joiningPlayerCount = 0 + +---@param license string +local function addPlayerJoining(license) + if not joiningPlayers[license] then + joiningPlayerCount += 1 + end + joiningPlayers[license] = true +end + +---@param license string +local function removePlayerJoining(license) + if joiningPlayers[license] then + joiningPlayerCount -= 1 + end + joiningPlayers[license] = nil +end + +---@param license string +local function enqueue(license) + queueSize += 1 + playerPositions[license] = queueSize +end + +---@param license string +local function dequeue(license) + local pos = playerPositions[license] + + queueSize -= 1 + playerPositions[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 + end + end +end + +---@param source Source +---@param license string +---@param deferrals Deferrals +local function awaitPlayerQueue(source, license, deferrals) + if playerPositions[license] then + deferrals.done(Lang:t('error.already_in_queue')) + return + end + + enqueue(license) + + -- 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 + deferrals.update(Lang:t('info.in_queue', { + queuePos = playerPositions[license], + queueSize = queueSize, + })) + + Wait(1000) + end + + -- if the player disconnected while waiting in queue + if not DoesPlayerExist(source --[[@as string]]) then + dequeue(license) + return + end + + addPlayerJoining(license) + dequeue(license) + deferrals.done() + + -- wait until the player finally joins or disconnects while installing server content + -- this may result in waiting ~2 additional minutes if the player disconnects as FXServer will think that the player exists + while DoesPlayerExist(source --[[@as string]]) do + Wait(1000) + end + removePlayerJoining(license) +end + +return { + awaitPlayerQueue = awaitPlayerQueue, +} From 290c784c99a0d1ae45e69f44a81f4a2d3b901ef1 Mon Sep 17 00:00:00 2001 From: David Malchin Date: Fri, 15 Dec 2023 03:19:56 +0200 Subject: [PATCH 2/2] refactor(server/queue): organize code --- server/queue.lua | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/server/queue.lua b/server/queue.lua index 6a8f7eb3b..87cd84997 100644 --- a/server/queue.lua +++ b/server/queue.lua @@ -24,6 +24,27 @@ local maxPlayers = GlobalState.MaxPlayers local playerPositions = {} local queueSize = 0 +---@param license string +local function enqueue(license) + queueSize += 1 + playerPositions[license] = queueSize +end + +---@param license string +local function dequeue(license) + local pos = playerPositions[license] + + queueSize -= 1 + playerPositions[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 + end + end +end + ---Map of player licenses that passed the queue and are downloading server content. ---Needs to be saved because these players won't be part of regular player counts such as `GetNumPlayerIndices`. ---@type table @@ -46,27 +67,6 @@ local function removePlayerJoining(license) joiningPlayers[license] = nil end ----@param license string -local function enqueue(license) - queueSize += 1 - playerPositions[license] = queueSize -end - ----@param license string -local function dequeue(license) - local pos = playerPositions[license] - - queueSize -= 1 - playerPositions[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 - end - end -end - ---@param source Source ---@param license string ---@param deferrals Deferrals