ridev

queuinghandlern(new)

Aug 3rd, 2025
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.21 KB | None | 0 0
  1. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  2. local TeleportService = game:GetService("TeleportService")
  3. local Players = game:GetService("Players")
  4. local RunService = game:GetService("RunService")
  5.  
  6. -- Remote events for client-server communication
  7. local QueueRequest = ReplicatedStorage.Remotes.QueueRequest
  8. local QueueUpdate = ReplicatedStorage.Remotes.QueueUpdate
  9. local data = require(ReplicatedStorage.Modules.Data.GameDefinitions)
  10.  
  11. -- Regional server configuration for optimal matchmaking
  12. -- Each region has its own queue to reduce latency
  13. local REGIONS = {
  14.     ["US-East"] = "us-east",
  15.     ["US-West"] = "us-west",
  16.     ["Europe"] = "eu",
  17.     ["Asia"] = "asia"
  18. }
  19.  
  20. -- Place IDs for different game modes
  21. -- SOD appears to be the main game with different PvP modes
  22. local PLACE_IDS = {
  23.     sod = {
  24.         pvp_1v1 = data.SOD.PlaceId,        -- 1v1 PvP matches
  25.         pvp_3player = data.SOD.PlaceId,    -- 3-player free-for-all
  26.     }
  27. }
  28.  
  29. -- Configuration for each queue type
  30. -- Defines how many players are needed and timeout settings
  31. local QUEUE_SETTINGS = {
  32.     sod = {
  33.         pvp_1v1 = {
  34.             playersNeeded = 2,      -- Need exactly 2 players for 1v1
  35.             matchTimeout = 30,      -- Wait 30 seconds before cross-region matching
  36.         },
  37.         pvp_3player = {
  38.             playersNeeded = 3,      -- Need exactly 3 players for FFA
  39.             matchTimeout = 45,      -- Wait 45 seconds before cross-region matching
  40.         }
  41.     }
  42. }
  43.  
  44. -- Multi-dimensional queue structure: queues[game][mode][region] = {players}
  45. local queues = {}
  46. -- Parallel structure to track when each player joined: queueTimestamps[game][mode][region] = {timestamps}
  47. local queueTimestamps = {}
  48.  
  49. -- Initialize the queue data structures
  50. -- Creates nested tables for each game, mode, and region combination
  51. for gameKey, modes in pairs(QUEUE_SETTINGS) do
  52.     queues[gameKey] = {}
  53.     queueTimestamps[gameKey] = {}
  54.     for mode, _ in pairs(modes) do
  55.         queues[gameKey][mode] = {}
  56.         queueTimestamps[gameKey][mode] = {}
  57.         for region, _ in pairs(REGIONS) do
  58.             queues[gameKey][mode][region] = {}
  59.             queueTimestamps[gameKey][mode][region] = {}
  60.         end
  61.     end
  62. end
  63.  
  64. -- Track which players are currently in any queue to prevent double-queuing
  65. local playersInQueue = {}
  66. -- Rate limiting to prevent spam requests
  67. local lastQueueRequest = {}
  68.  
  69. -- Rate limit configuration: max 3 requests per 10 seconds per player
  70. local RATE_LIMIT = {
  71.     requests = 3,
  72.     window = 10,
  73. }
  74.  
  75. --[[
  76.     Rate limiting function to prevent queue spam
  77.     Maintains a sliding window of recent requests per player
  78.     @param player: The player making the request
  79.     @return success: boolean, error message if failed
  80. ]]
  81. local function checkRateLimit(player)
  82.     local now = tick()
  83.     local playerRequests = lastQueueRequest[player] or {}
  84.  
  85.     -- Remove requests outside the time window (sliding window approach)
  86.     for i = #playerRequests, 1, -1 do
  87.         if now - playerRequests[i] > RATE_LIMIT.window then
  88.             table.remove(playerRequests, i)
  89.         end
  90.     end
  91.  
  92.     -- Check if player has exceeded rate limit
  93.     if #playerRequests >= RATE_LIMIT.requests then
  94.         return false, "Too many queue requests. Please wait before trying again."
  95.     end
  96.  
  97.     -- Add current request to tracking
  98.     table.insert(playerRequests, now)
  99.     lastQueueRequest[player] = playerRequests
  100.  
  101.     return true, nil
  102. end
  103.  
  104. --[[
  105.     Get player's optimal region based on their location
  106.     Currently returns US-East as default, but should be implemented
  107.     to use actual geolocation or ping testing
  108.     @param player: The player to get region for
  109.     @return region: String identifier for the player's region
  110. ]]
  111. local function getPlayerRegion(player)
  112.     -- TODO: Implement actual region detection based on:
  113.     -- - Player's locale/country
  114.     -- - Ping testing to different regions
  115.     -- - Manual region selection preference
  116.     return "US-East"
  117. end
  118.  
  119. --[[
  120.     Remove a player from all queues they might be in
  121.     Used when player leaves game or cancels queue
  122.     @param player: The player to remove from queues
  123. ]]
  124. local function removePlayerFromQueues(player)
  125.     -- Search through all possible queue locations
  126.     for gameKey, gameModes in pairs(queues) do
  127.         for mode, regions in pairs(gameModes) do
  128.             for region, queue in pairs(regions) do
  129.                 -- Search backwards to avoid index issues when removing
  130.                 for i = #queue, 1, -1 do
  131.                     if queue[i] == player then
  132.                         table.remove(queue, i)
  133.                         table.remove(queueTimestamps[gameKey][mode][region], i)
  134.                         break -- Player can only be in one position per queue
  135.                     end
  136.                 end
  137.             end
  138.         end
  139.     end
  140.     playersInQueue[player] = nil
  141. end
  142.  
  143. --[[
  144.     Attempt to create a match for a specific game mode and region
  145.     Will try cross-region matching if timeout is exceeded
  146.     @param gameKey: Game identifier (e.g. "sod")
  147.     @param mode: Game mode (e.g. "pvp_1v1")
  148.     @param region: Region identifier (e.g. "US-East")
  149. ]]
  150. local function tryMatch(gameKey, mode, region)
  151.     local queue = queues[gameKey][mode][region]
  152.     local settings = QUEUE_SETTINGS[gameKey][mode]
  153.     local timestamps = queueTimestamps[gameKey][mode][region]
  154.  
  155.     -- Check if we have enough players for a match
  156.     if #queue < settings.playersNeeded then
  157.         -- If queue has players but not enough, check if oldest player has waited too long
  158.         if #queue > 0 then
  159.             local oldestWaitTime = tick() - timestamps[1]
  160.             if oldestWaitTime >= settings.matchTimeout then
  161.                 -- Try cross-region matching due to timeout
  162.                 return tryMatchCrossRegion(gameKey, mode, region)
  163.             end
  164.         end
  165.         return -- Not enough players and no timeout yet
  166.     end
  167.  
  168.     -- We have enough players! Remove them from queue (FIFO - first in, first out)
  169.     local playersToMatch = {}
  170.     for i = 1, settings.playersNeeded do
  171.         table.insert(playersToMatch, table.remove(queue, 1))
  172.         table.remove(timestamps, 1)
  173.     end
  174.  
  175.     -- Remove players from tracking since they're about to be teleported
  176.     for _, player in pairs(playersToMatch) do
  177.         playersInQueue[player] = nil
  178.     end
  179.  
  180.     -- Reserve a private server for the match
  181.     local placeId = PLACE_IDS[gameKey][mode]
  182.     local success, code = pcall(function()
  183.         return TeleportService:ReserveServer(placeId)
  184.     end)
  185.  
  186.     if success then
  187.         -- Prepare teleport data to send to the game server
  188.         local teleportData = {
  189.             gameKey = gameKey,
  190.             mode = mode,
  191.             region = region,
  192.             playerCount = #playersToMatch
  193.         }
  194.  
  195.         -- Log match creation for debugging
  196.         local modeDesc = mode == "pvp_1v1" and "1v1" or "3-player FFA"
  197.         print("Starting", modeDesc, "match for", gameKey, "in", region, "with", #playersToMatch, "players")
  198.  
  199.         -- Attempt to teleport players to the reserved server
  200.         local teleportSuccess, teleportError = pcall(function()
  201.             TeleportService:TeleportToPrivateServer(placeId, code, playersToMatch, nil, teleportData)
  202.         end)
  203.  
  204.         -- If teleport fails, put players back in queue
  205.         if not teleportSuccess then
  206.             warn("Failed to teleport players:", teleportError)
  207.             -- Re-add players to front of queue since the failure wasn't their fault
  208.             for i = #playersToMatch, 1, -1 do -- Reverse order to maintain original queue position
  209.                 local player = playersToMatch[i]
  210.                 if player and player.Parent then -- Verify player is still in game
  211.                     table.insert(queue, 1, player)
  212.                     table.insert(timestamps, 1, tick())
  213.                     playersInQueue[player] = {gameKey = gameKey, mode = mode, region = region}
  214.                 end
  215.             end
  216.         end
  217.     else
  218.         -- Server reservation failed, put players back in queue
  219.         warn("Failed to reserve server:", code)
  220.         for i = #playersToMatch, 1, -1 do
  221.             local player = playersToMatch[i]
  222.             if player and player.Parent then
  223.                 table.insert(queue, 1, player)
  224.                 table.insert(timestamps, 1, tick())
  225.                 playersInQueue[player] = {gameKey = gameKey, mode = mode, region = region}
  226.             end
  227.         end
  228.     end
  229.  
  230.     -- Update all remaining players in queue about their new positions
  231.     updateQueueStatus(gameKey, mode, region)
  232. end
  233.  
  234. --[[
  235.     Attempt cross-region matching when regional queues don't have enough players
  236.     Combines players from all regions for the same game mode
  237.     @param gameKey: Game identifier
  238.     @param mode: Game mode
  239.     @param primaryRegion: Preferred region for the match server
  240.     @return success: Boolean indicating if a match was created
  241. ]]
  242. local function tryMatchCrossRegion(gameKey, mode, primaryRegion)
  243.     local settings = QUEUE_SETTINGS[gameKey][mode]
  244.     local allPlayers = {}
  245.     local playerRegions = {}
  246.  
  247.     -- Collect all players from all regions for this game mode
  248.     for region, queue in pairs(queues[gameKey][mode]) do
  249.         for _, player in pairs(queue) do
  250.             table.insert(allPlayers, player)
  251.             playerRegions[player] = region -- Remember which region each player came from
  252.         end
  253.     end
  254.  
  255.     -- Check if we have enough players across all regions
  256.     if #allPlayers >= settings.playersNeeded then
  257.         local playersToMatch = {}
  258.        
  259.         -- Take the required number of players (FIFO across all regions)
  260.         for i = 1, settings.playersNeeded do
  261.             local player = allPlayers[i]
  262.             local playerRegion = playerRegions[player]
  263.             table.insert(playersToMatch, player)
  264.  
  265.             -- Remove player from their original regional queue
  266.             local queue = queues[gameKey][mode][playerRegion]
  267.             local timestamps = queueTimestamps[gameKey][mode][playerRegion]
  268.             for j = #queue, 1, -1 do
  269.                 if queue[j] == player then
  270.                     table.remove(queue, j)
  271.                     table.remove(timestamps, j)
  272.                     break
  273.                 end
  274.             end
  275.             playersInQueue[player] = nil
  276.         end
  277.  
  278.         -- Reserve server and attempt teleport
  279.         local placeId = PLACE_IDS[gameKey][mode]
  280.         local success, code = pcall(function()
  281.             return TeleportService:ReserveServer(placeId)
  282.         end)
  283.  
  284.         if success then
  285.             local teleportData = {
  286.                 gameKey = gameKey,
  287.                 mode = mode,
  288.                 region = primaryRegion,
  289.                 crossRegion = true, -- Flag to indicate this is a cross-region match
  290.                 playerCount = #playersToMatch
  291.             }
  292.  
  293.             local modeDesc = mode == "pvp_1v1" and "1v1" or "3-player FFA"
  294.             print("Starting cross-region", modeDesc, "match for", gameKey, "with", #playersToMatch, "players")
  295.  
  296.             -- Note: No error handling here - could be improved
  297.             pcall(function()
  298.                 TeleportService:TeleportToPrivateServer(placeId, code, playersToMatch, nil, teleportData)
  299.             end)
  300.         end
  301.  
  302.         return true -- Match was attempted
  303.     end
  304.  
  305.     return false -- Not enough players even across regions
  306. end
  307.  
  308. --[[
  309.     Update all players in a specific queue about their current status
  310.     Sends position in queue, estimated wait time, etc.
  311.     @param gameKey: Game identifier
  312.     @param mode: Game mode
  313.     @param region: Region identifier
  314. ]]
  315. function updateQueueStatus(gameKey, mode, region)
  316.     local queue = queues[gameKey][mode][region]
  317.     local queueSize = #queue
  318.  
  319.     -- Send update to each player in the queue
  320.     for i, player in pairs(queue) do
  321.         if player and player.Parent then -- Verify player is still in game
  322.             pcall(function() -- Wrap in pcall to handle disconnected players
  323.                 QueueUpdate:FireClient(player, {
  324.                     gameKey = gameKey,
  325.                     mode = mode,
  326.                     region = region,
  327.                     position = i,                    -- Player's position in queue (1st, 2nd, etc.)
  328.                     totalInQueue = queueSize,        -- Total players in this specific queue
  329.                     playersNeeded = QUEUE_SETTINGS[gameKey][mode].playersNeeded,
  330.                     estimatedWait = calculateEstimatedWait(gameKey, mode, region)
  331.                 })
  332.             end)
  333.         end
  334.     end
  335. end
  336.  
  337. --[[
  338.     Calculate estimated wait time message for players in queue
  339.     @param gameKey: Game identifier
  340.     @param mode: Game mode
  341.     @param region: Region identifier
  342.     @return waitMessage: String describing expected wait time
  343. ]]
  344. function calculateEstimatedWait(gameKey, mode, region)
  345.     local queueSize = #queues[gameKey][mode][region]
  346.     local settings = QUEUE_SETTINGS[gameKey][mode]
  347.  
  348.     if queueSize >= settings.playersNeeded then
  349.         return "Starting match..." -- Enough players, match should start soon
  350.     else
  351.         local playersNeeded = settings.playersNeeded - queueSize
  352.         return "Waiting for " .. playersNeeded .. " more player(s)"
  353.     end
  354. end
  355.  
  356. -- Main event handler for queue requests from clients
  357. QueueRequest.OnServerEvent:Connect(function(player, requestData)
  358.     -- Validate request data exists
  359.     if not requestData then
  360.         warn("Invalid queue request from", player.Name, ": No request data")
  361.         return
  362.     end
  363.    
  364.     local gameKey = requestData.gameKey
  365.     local mode = requestData.mode
  366.     local action = requestData.action or "join" -- Default to join action
  367.  
  368.     -- Check rate limiting first
  369.     local rateLimitOk, rateLimitMsg = checkRateLimit(player)
  370.     if not rateLimitOk then
  371.         warn("Rate limit exceeded for", player.Name, ":", rateLimitMsg)
  372.         return
  373.     end
  374.  
  375.     -- Validate that the requested game and mode exist
  376.     if not gameKey or not mode or not queues[gameKey] or not queues[gameKey][mode] then
  377.         warn("Invalid queue request from", player.Name, ":", gameKey, mode)
  378.         return
  379.     end
  380.  
  381.     -- Handle PvE mode separately (single player, immediate start)
  382.     if mode == "pve" then
  383.         print(player.Name, "is starting PvE mode for", gameKey)
  384.         -- TODO: Implement PvE server creation and teleport
  385.         return
  386.     end
  387.  
  388.     -- Handle leaving queue
  389.     if action == "leave" then
  390.         removePlayerFromQueues(player)
  391.         print(player.Name, "left the queue for", gameKey, mode)
  392.         return
  393.     end
  394.  
  395.     -- Check if player is already in a queue (prevent double-queuing)
  396.     if playersInQueue[player] then
  397.         local current = playersInQueue[player]
  398.         print(player.Name, "is already in queue for", current.gameKey, current.mode, "in", current.region)
  399.         return -- Could send error message to client here
  400.     end
  401.  
  402.     -- Determine player's optimal region
  403.     local region = getPlayerRegion(player)
  404.  
  405.     -- Add player to appropriate queue
  406.     local queue = queues[gameKey][mode][region]
  407.     local timestamps = queueTimestamps[gameKey][mode][region]
  408.  
  409.     table.insert(queue, player)           -- Add to end of queue (FIFO)
  410.     table.insert(timestamps, tick())      -- Record join time
  411.     playersInQueue[player] = {            -- Track that player is now in queue
  412.         gameKey = gameKey,
  413.         mode = mode,
  414.         region = region
  415.     }
  416.  
  417.     print(player.Name, "joined", mode, "queue for", gameKey, "in", region, "- Queue size:", #queue)
  418.  
  419.     -- Update all players in this queue about the new status
  420.     updateQueueStatus(gameKey, mode, region)
  421.  
  422.     -- Try to create a match now that we have a new player
  423.     tryMatch(gameKey, mode, region)
  424. end)
  425.  
  426. -- Clean up when players leave the game
  427. Players.PlayerRemoving:Connect(function(player)
  428.     removePlayerFromQueues(player)      -- Remove from any queues
  429.     lastQueueRequest[player] = nil      -- Clear rate limit tracking
  430. end)
  431.  
  432. -- Periodic maintenance and matching attempts
  433. local lastQueueCheck = 0
  434. RunService.Heartbeat:Connect(function()
  435.     local now = tick()
  436.     if now - lastQueueCheck >= 3 then -- Run maintenance every 3 seconds
  437.         lastQueueCheck = now
  438.  
  439.         -- Check all queues for disconnected players and matching opportunities
  440.         for gameKey, gameModes in pairs(queues) do
  441.             for mode, regions in pairs(gameModes) do
  442.                 for region, queue in pairs(regions) do
  443.                     if #queue > 0 then
  444.                         local timestamps = queueTimestamps[gameKey][mode][region]
  445.                        
  446.                         -- Remove disconnected players (cleanup)
  447.                         for i = #queue, 1, -1 do
  448.                             if not queue[i] or not queue[i].Parent then
  449.                                 table.remove(queue, i)
  450.                                 table.remove(timestamps, i)
  451.                             end
  452.                         end
  453.  
  454.                         -- Try to create matches with remaining players
  455.                         if #queue > 0 then
  456.                             tryMatch(gameKey, mode, region)
  457.                         end
  458.                     end
  459.                 end
  460.             end
  461.         end
  462.     end
  463. end)
Advertisement
Add Comment
Please, Sign In to add comment