Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
0

How do I fix the ID Doubling issue in Matchmaking?

Asked by 5 years ago

As you may know, roblox's matchmaking script is extremely broken and has bugs all over it. I have fixed most of the problems, but I still don't know how to fix an important one.

When searching for a game, it looks for another player's ID to create a game. If you cancel the matchmaking, your player's ID does not fully remove itself from the queue, resulting in a teleportation with two of your same ID's.

This only happens when you cancel the matchmaking, it works perfectly if you don't. But for a game, I need to to allow players to cancel matchmaking if they feel like it.

Here is the first script code:

local lookForGameEvent = game.ReplicatedStorage.LookForGameEvent
local rankedList = require(game.ServerStorage.MatchmakingRankedListModule)
local ratingData = game:GetService("DataStoreService"):GetDataStore("PlayerRating")
local coinData = game:GetService("DataStoreService"):GetDataStore("PlayerCoin")
local gemData = game:GetService("DataStoreService"):GetDataStore("PlayerGems")
local teleportService = game:GetService("TeleportService")
local arenaPlaceTemplateId = 1983540262

-- Table to hold the queue of players looking for a game
local matchMakingQueue = {}

-- Add player's id to the queue as well as the time that they entered the queue
local addToMMQueue = function(playerId, enteredTime)
    local data = {}
    data.userId = playerId
    data.EnteredQueue = enteredTime
    table.insert(matchMakingQueue, data)
end

-- Remove player from the queue
local removeFromMMQueue = function(playerId)
    for i, playerData in pairs(matchMakingQueue) do
        if playerData.userId == playerId then
            table.remove(matchMakingQueue, i)
            return
        end
    end
end

-- Handle player joining the game. If they are a new player we need to give them
-- an initial rank. For existing players we need to fetch their rank from our 
-- DataStore. Lastly, we want to display the player's rank in the Leaderboard
game.Players.PlayerAdded:connect(function(player)
    local playerData = {}

    -- Create a default rating of 100 if the player's data is not in the DataStore
    ratingData:UpdateAsync(player.userId, function(oldValue)
        local newValue = oldValue
        if not newValue then
            newValue = {Rating = 100}
        end
        return newValue
    end)
    coinData:UpdateAsync(player.userId, function(oldvalue2)
        local newValue2 = oldvalue2
        if not newValue2 then
            newValue2 = {Coins = 10}
        end
        return newValue2
    end)
    gemData:UpdateAsync(player.userId, function(oldvalue3)
        local newValue3 = oldvalue3
        if not newValue3 then
            newValue3 = {Gems = 0}
        end
        return newValue3
    end)
    playerData.Score = ratingData:GetAsync(tostring(player.userId)).Rating
    playerData.Score2 = coinData:GetAsync(tostring(player.userId)).Coins
    playerData.Score3 = gemData:GetAsync(tostring(player.userId)).Gems

    -- Display rating in Leaderboard
    local stats = Instance.new("Folder", player)
    stats.Name = "leaderstats"

    local displayRating = Instance.new("IntValue", stats)
    displayRating.Name = "Rating"
    displayRating.Value = playerData.Score

    local displayCoins = Instance.new("IntValue", stats)
    displayCoins.Name = "Coins"
    displayCoins.Value = playerData.Score2

    local displayGems = Instance.new("IntValue", stats)
    displayGems.Name = "Gems"
    displayGems.Value = playerData.Score3
end)

-- Adds player both to queue and list to search for match
local function playerSearchingForMatch(userId, rank)
    local now = os.time()
    addToMMQueue(userId, now)
    rankedList:AddPlayer(userId, rank, now)
end

-- Remove player from list and queue when they leave the game
game.Players.PlayerRemoving:connect(function(player)
    removeFromMMQueue(player.userId)
    rankedList:RemovePlayer(player.userId)
end)

-- Handle remote event to either add or remove player from the queue
lookForGameEvent.OnServerEvent:connect(function(player, lookingForGame)
    if lookingForGame then
        print(player.Name .. " now looking for game")
        local enteredTime = os.time()
        playerSearchingForMatch(player.userId, player.leaderstats.Rating.Value)
    else
        print(player.Name .. " has left the queue")
        removeFromMMQueue(player.userId)
        rankedList:RemovePlayer(player.userId)
    end
end)

-- Returns a rank range to search through based on time waiting in the queue.
-- If player has been waiting too long just return math.huge so the player can
-- be matched with anyone.
local getRange = function(timeWaiting)
    if timeWaiting < 10 then
        return 100
    elseif timeWaiting >=10 and timeWaiting < 20 then
        return 200
    elseif timeWaiting >=20 and timeWaiting <= 35 then
        return 300
    end
    return math.huge
end

-- Creates place for game and teleports both players to it
local startGame = function(playerAId, playerBId)
    local message = ""
    print("starting game with " .. playerAId .. " and " .. playerBId)

    --[[if playerAId == playerBId then
        print("same ID!")
        return
    end--]]

    -- Get both player objects
    local playerA = nil
    local playerB = nil
    for _, player in pairs(game.Players:GetPlayers()) do
        if player.userId == playerAId then
            playerA = player
        end
        if player.userId == playerBId then
            playerB = player
        end
    end

    -- Create arena place and get its id
    local arenaPlaceId = game:GetService("AssetService"):CreatePlaceAsync(
        "Match: " .. playerAId .. " VS. " .. playerBId, arenaPlaceTemplateId)

    -- Bind OnTeleport event to playerA (who is teleported first). If that teleport is successful
    -- then we want playerB to be teleported to the same instance
    local connection = playerA.OnTeleport:connect(function(teleportState, placeId)
        if teleportState == Enum.TeleportState.Started then
            local teleportStarted = os.time()
            -- Keep checking if playerA has arrived in other instance.
            while true do
                local success, error, placeId, arenaInstanceId = teleportService:GetPlayerPlaceInstanceAsync(playerAId)
                -- If playerA is in the correct place then we can teleport playerB there as well
                if success then -- placeId == arenaPlaceId
                    teleportService:Teleport(arenaPlaceId, playerB)
                    return
                end
                wait()
            end 
        end
    end)
    wait(1)

    -- Teleport playerA to the arena
    teleportService:Teleport(arenaPlaceId, playerA)
end

-- Matchmaking loop. Cycles about every 5 seconds to match players.
while true do
    local now = os.time()
    -- Cycle through queue, try to find players in range
    for _, mmData in pairs(matchMakingQueue) do
        print("attempting to find match for " .. mmData.userId)
        -- Get rank range to search
        local range = getRange(now - mmData.EnteredQueue)
        -- Use list to find a player in player's rank range
        local otherPlayerId = rankedList:FindPlayerInRange(mmData.userId, range)
        if otherPlayerId then
            -- Another player was found. Remove both players from the queue and list so they
            -- can't be matched with anyone else
            print("found player: " .. otherPlayerId)
            rankedList:RemovePlayer(mmData.userId)
            rankedList:RemovePlayer(otherPlayerId)
            removeFromMMQueue(mmData.userId)
            removeFromMMQueue(otherPlayerId)
            -- Start game with the two players. This function can take some times, so start a
            -- coroutine so the loop can continue.
            local thread = coroutine.create(function() startGame(mmData.userId, otherPlayerId) end)
            coroutine.resume(thread)
        end
    end
    wait(5)
end

If you need the module script, please ask! Thanks!

Answer this question