Issue with Cross Server Matchmaking system

So recently I have been trying to make a Quick Play Matchmaking system for my Tower Defense game where a Player can Queue up for either a Solo, Duo, Trio, or a Squad match. Although I have run into an issue where when queueing for a Match it adds a Duplicate of the Player’s User ID to the Queue Table. For context I had set up a test game where the main place was only allowed one player per server so Me and my alt account would be placed in separate servers. And when I had queued for a Duo Match on my main account before I had even queued up for a Duo match on my alt account a Match was instantly found and teleported my main account into the match. I then checked the developer console and in the script there is a print statement that prints out when a match is starting and for which players the match is starting for. And I saw my User ID pasted in twice. I then tried queuing up for a Trio match on both my accounts and it worked and it still yet again showed my User ID Twice as seen here:

image

I had implemented multiple checks to remove any duplicate ID’s from the table but it still creates a duplicate. Here is the entire server script:

local QuickPlayQueueEvent = game.ReplicatedStorage.MatchRS.QuickPlayQueue
local Players = game:GetService("Players")
local TeleportService = game:GetService("TeleportService")
local MessagingService = game:GetService("MessagingService")
local RunService = game:GetService("RunService")

local PLACE_ID = 0 -- Match ID
local QUEUE_UPDATE_TOPIC = "QueueUpdate"
local MATCH_FOUND_TOPIC = "MatchFound"

local matchmakingQueue = {
	solo = {},
	duo = {},
	trio = {},
	squad = {}
}

local reservedServerCodes = {}
local CHECK_INTERVAL = 5 -- Seconds between each match check

-- Utility function to get table size
local function tableSize(tbl)
	local size = 0
	for _ in pairs(tbl) do
		size = size + 1
	end
	return size
end
-- Function to remove duplicate entries of a player in the queue
local function removeDuplicateFromQueue(playerId, queueType)
	if matchmakingQueue[queueType] then
		-- Loop through the queue and remove any duplicate entries
		for id, _ in pairs(matchmakingQueue[queueType]) do
			if id == playerId then
				matchmakingQueue[queueType][id] = nil -- Remove the duplicate
				print("Removed duplicate entry for player with UserID: " .. playerId)
			end
		end
	end
end

-- Function to check if a player is already in the queue
local function isPlayerInQueue(queueType, playerId)
	return matchmakingQueue[queueType] and matchmakingQueue[queueType][playerId] ~= nil
end

-- Function to queue a player, with strict check to prevent duplicates
local function queuePlayer(player, queueType)
	if matchmakingQueue[queueType] then
		-- Ensure player is not already in the queue
		if not isPlayerInQueue(queueType, player.UserId) then
			matchmakingQueue[queueType][player.UserId] = player.UserId
			print(player.Name .. " queued for " .. queueType)
			MessagingService:PublishAsync(QUEUE_UPDATE_TOPIC, matchmakingQueue)
		else
			-- If player is already in the queue, remove duplicate
			print(player.Name .. " is already in the queue. Removing duplicate...")
			removeDuplicateFromQueue(player.UserId, queueType)
			matchmakingQueue[queueType][player.UserId] = player.UserId -- Re-add correctly
		end
	end
end
-- Function to broadcast queue updates
local function broadcastQueueUpdate()
	local success, result = pcall(function()
		MessagingService:PublishAsync(QUEUE_UPDATE_TOPIC, matchmakingQueue)
	end)
	if not success then
		warn("Failed to publish queue update: " .. result)
	end
end

-- Function to handle updates from other servers
local function onQueueUpdate(message)
	local incomingQueueData = message.Data
	-- Merge incoming queue data, but avoid duplicating any player
	for queueType, incomingPlayers in pairs(incomingQueueData) do
		for userId, _ in pairs(incomingPlayers) do
			if not isPlayerInQueue(queueType, userId) then
				matchmakingQueue[queueType][userId] = userId
			end
		end
	end
end

-- Function to check if enough players are in the queue for a match
local function checkForMatch()
	for queueType, players in pairs(matchmakingQueue) do
		local teamSize
		if queueType == "solo" then
			teamSize = 1
		elseif queueType == "duo" then
			teamSize = 2
		elseif queueType == "trio" then
			teamSize = 3
		elseif queueType == "squad" then
			teamSize = 4
		end

		-- Deduplicate any players in case they somehow appear twice in the queue
		local uniquePlayers = {}
		for userId, _ in pairs(players) do
			if not uniquePlayers[userId] then
				uniquePlayers[userId] = true
			else
				-- Remove duplicate player
				--players[userId] = nil
				table.remove(uniquePlayers, userId)
				print("Removed duplicate player with UserID: " .. userId)
			end
		end

		-- Now check if the required number of players is met
		if teamSize and tableSize(players) >= teamSize then
			print("Starting match for " .. queueType)

			local matchPlayers = {}
			for userId, _ in pairs(players) do
				table.insert(matchPlayers, userId)
				matchmakingQueue[queueType][userId] = nil -- Remove from queue
				if #matchPlayers == teamSize then
					break
				end
			end

			-- Only proceed if we have exactly enough players
			if #matchPlayers == teamSize then
				print("Match found for players: " .. table.concat(matchPlayers, ", "))

				-- Reserve a server for the match
				local success, reservedServerCode = pcall(function()
					return TeleportService:ReserveServer(PLACE_ID)
				end)

				if success and reservedServerCode then
					reservedServerCodes[table.concat(matchPlayers, "-")] = reservedServerCode

					-- Publish the match found message
					local successPublish, result = pcall(function()
						return MessagingService:PublishAsync(MATCH_FOUND_TOPIC, {
							teamSize = teamSize,
							playerIds = matchPlayers,
							reservedServerCode = reservedServerCode
						})
					end)

					if not successPublish then
						warn("Failed to publish match found message: " .. result)
					end
				else
					warn("Failed to reserve server: " .. (reservedServerCode or "Unknown error"))
				end
			end
		else
			print("Not enough players for " .. queueType .. " match. Current: " .. tableSize(players))
		end
	end
end

-- Function to handle match found notifications
local function onMatchFound(message)
	local matchData = message.Data
	local reservedServerCode = matchData.reservedServerCode
	local playerIds = matchData.playerIds

	for _, playerId in ipairs(playerIds) do
		local player = Players:GetPlayerByUserId(playerId)
		if player then
			game.ReplicatedStorage.MatchRS.MatchFoundEvent:FireClient(player)
			TeleportService:TeleportToPlaceInstance(PLACE_ID, reservedServerCode, player)
		end
	end
end

-- Subscribe to MessagingService topics
local function subscribeToTopics()
	local success, result
	-- Subscribe to the match found topic
	success, result = pcall(function()
		MessagingService:SubscribeAsync(MATCH_FOUND_TOPIC, onMatchFound)
	end)
	if not success then
		warn("Failed to subscribe to match found topic: " .. result)
	end

	-- Subscribe to queue updates
	success, result = pcall(function()
		MessagingService:SubscribeAsync(QUEUE_UPDATE_TOPIC, onQueueUpdate)
	end)
	if not success then
		warn("Failed to subscribe to queue update topic: " .. result)
	end
end

-- Event for queuing players
QuickPlayQueueEvent.OnServerEvent:Connect(function(player, queueType)
	-- Ensure player is not already in any queue
	if not isPlayerInQueue(queueType, player.UserId) then
		queuePlayer(player, queueType)
	else
		print(player.Name .. " is already in a queue!")
	end
end)

-- Continuous match checking
local function startContinuousMatchCheck()
	while true do
		checkForMatch()
		wait(CHECK_INTERVAL)
	end
end

-- Start the system
subscribeToTopics()
startContinuousMatchCheck()

does htis work

local QuickPlayQueueEvent = game.ReplicatedStorage.MatchRS.QuickPlayQueue
local Players = game:GetService("Players")
local TeleportService = game:GetService("TeleportService")
local MessagingService = game:GetService("MessagingService")
local RunService = game:GetService("RunService")

local PLACE_ID = 0 -- Match ID
local QUEUE_UPDATE_TOPIC = "QueueUpdate"
local MATCH_FOUND_TOPIC = "MatchFound"

local matchmakingQueue = {
	solo = {},
	duo = {},
	trio = {},
	squad = {}
}

local reservedServerCodes = {}
local CHECK_INTERVAL = 5 -- Seconds between each match check

-- Utility function to get table size
local function tableSize(tbl)
	local size = 0
	for _ in pairs(tbl) do
		size = size + 1
	end
	return size
end
-- Function to remove duplicate entries of a player in the queue
local function removeDuplicateFromQueue(playerId, queueType)
	if matchmakingQueue[queueType] then
		-- Loop through the queue and remove any duplicate entries
		for id, _ in pairs(matchmakingQueue[queueType]) do
			if id == playerId then
				matchmakingQueue[queueType][id] = nil -- Remove the duplicate
				print("Removed duplicate entry for player with UserID: " .. playerId)
			end
		end
	end
end

-- Function to check if a player is already in the queue
local function isPlayerInQueue(queueType, playerId)
	return matchmakingQueue[queueType] and matchmakingQueue[queueType][playerId] ~= nil
end

-- Function to queue a player, with strict check to prevent duplicates
local function queuePlayer(player, queueType)
	if matchmakingQueue[queueType] then
		-- Ensure player is not already in the queue
		if not isPlayerInQueue(queueType, player.UserId) then
			matchmakingQueue[queueType][player.UserId] = player.UserId
			print(player.Name .. " queued for " .. queueType)
			MessagingService:PublishAsync(QUEUE_UPDATE_TOPIC, matchmakingQueue)
		else
			-- If player is already in the queue, remove duplicate
			print(player.Name .. " is already in the queue. Removing duplicate...")
			removeDuplicateFromQueue(player.UserId, queueType)
			--matchmakingQueue[queueType][player.UserId] = player.UserId -- Re-add correctly
		end
	end
end
-- Function to broadcast queue updates
local function broadcastQueueUpdate()
	local success, result = pcall(function()
		MessagingService:PublishAsync(QUEUE_UPDATE_TOPIC, matchmakingQueue)
	end)
	if not success then
		warn("Failed to publish queue update: " .. result)
	end
end

-- Function to handle updates from other servers
local function onQueueUpdate(message)
	local incomingQueueData = message.Data
	-- Merge incoming queue data, but avoid duplicating any player
	for queueType, incomingPlayers in pairs(incomingQueueData) do
		for userId, _ in pairs(incomingPlayers) do
			if not isPlayerInQueue(queueType, userId) then
				matchmakingQueue[queueType][userId] = userId
			end
		end
	end
end

-- Function to check if enough players are in the queue for a match
local function checkForMatch()
	for queueType, players in pairs(matchmakingQueue) do
		local teamSize
		if queueType == "solo" then
			teamSize = 1
		elseif queueType == "duo" then
			teamSize = 2
		elseif queueType == "trio" then
			teamSize = 3
		elseif queueType == "squad" then
			teamSize = 4
		end

		-- Deduplicate any players in case they somehow appear twice in the queue
		local uniquePlayers = {}
		for userId, _ in pairs(players) do
			if not uniquePlayers[userId] then
				uniquePlayers[userId] = true
			else
				-- Remove duplicate player
				--players[userId] = nil
				table.remove(uniquePlayers, userId)
				print("Removed duplicate player with UserID: " .. userId)
			end
		end

		-- Now check if the required number of players is met
		if teamSize and tableSize(players) >= teamSize then
			print("Starting match for " .. queueType)

			local matchPlayers = {}
			for userId, _ in pairs(players) do
				table.insert(matchPlayers, userId)
				matchmakingQueue[queueType][userId] = nil -- Remove from queue
				if #matchPlayers == teamSize then
					break
				end
			end

			-- Only proceed if we have exactly enough players
			if #matchPlayers == teamSize then
				print("Match found for players: " .. table.concat(matchPlayers, ", "))

				-- Reserve a server for the match
				local success, reservedServerCode = pcall(function()
					return TeleportService:ReserveServer(PLACE_ID)
				end)

				if success and reservedServerCode then
					reservedServerCodes[table.concat(matchPlayers, "-")] = reservedServerCode

					-- Publish the match found message
					local successPublish, result = pcall(function()
						return MessagingService:PublishAsync(MATCH_FOUND_TOPIC, {
							teamSize = teamSize,
							playerIds = matchPlayers,
							reservedServerCode = reservedServerCode
						})
					end)

					if not successPublish then
						warn("Failed to publish match found message: " .. result)
					end
				else
					warn("Failed to reserve server: " .. (reservedServerCode or "Unknown error"))
				end
			end
		else
			print("Not enough players for " .. queueType .. " match. Current: " .. tableSize(players))
		end
	end
end

-- Function to handle match found notifications
local function onMatchFound(message)
	local matchData = message.Data
	local reservedServerCode = matchData.reservedServerCode
	local playerIds = matchData.playerIds

	for _, playerId in ipairs(playerIds) do
		local player = Players:GetPlayerByUserId(playerId)
		if player then
			game.ReplicatedStorage.MatchRS.MatchFoundEvent:FireClient(player)
			TeleportService:TeleportToPlaceInstance(PLACE_ID, reservedServerCode, player)
		end
	end
end

-- Subscribe to MessagingService topics
local function subscribeToTopics()
	local success, result
	-- Subscribe to the match found topic
	success, result = pcall(function()
		MessagingService:SubscribeAsync(MATCH_FOUND_TOPIC, onMatchFound)
	end)
	if not success then
		warn("Failed to subscribe to match found topic: " .. result)
	end

	-- Subscribe to queue updates
	success, result = pcall(function()
		MessagingService:SubscribeAsync(QUEUE_UPDATE_TOPIC, onQueueUpdate)
	end)
	if not success then
		warn("Failed to subscribe to queue update topic: " .. result)
	end
end

-- Event for queuing players
QuickPlayQueueEvent.OnServerEvent:Connect(function(player, queueType)
	-- Ensure player is not already in any queue
	if not isPlayerInQueue(queueType, player.UserId) then
		queuePlayer(player, queueType)
	else
		print(player.Name .. " is already in a queue!")
	end
end)

-- Continuous match checking
local function startContinuousMatchCheck()
	while true do
		checkForMatch()
		wait(CHECK_INTERVAL)
	end
end

-- Start the system
subscribeToTopics()
startContinuousMatchCheck()
2 Likes

Yes it does! Thank you so much! I’ve been stuck on this all day lol

1 Like

you can move topic to Script support to mark a solution

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.