LuckyEcho

LuckyEcho HiddenDev Application

Aug 7th, 2025
390
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 15.74 KB | None | 0 0
  1. -- @LXCKYDEV 2025 - PUT IN SERVER SCRIPT SERVICE
  2. -- The script is run, because there is a script under this that calls Initialize.
  3.  
  4. -- [ SERVICES ]
  5. local MarketplaceService = game:GetService("MarketplaceService")
  6. local Players = game:GetService("Players")
  7. local ReplicatedStorage = game:GetService("ReplicatedStorage")
  8. local Configuration = require(script:FindFirstChild("Configuration"))
  9.  
  10. -- [ FOLDERS ]
  11. local QueueFolder = workspace:FindFirstChild("Queues")
  12.  
  13. -- [ EVENTS ]
  14. local DisableControlEvent = ReplicatedStorage:WaitForChild("DisableControlEvent", 5)
  15. local LeaveQueueEvent = ReplicatedStorage:WaitForChild("LeaveQueueEvent", 5)
  16.  
  17. -- [ VARIABLES ]
  18. local QueueHandler = {
  19.     Queues = {},
  20.     WalkingTasks = {}
  21. }
  22.  
  23. -- [ FUNCTIONS ]
  24.  
  25. -- Order a table by it's children names
  26. local function GetQueueUpPoints(folder: Folder): {Attachment}
  27.     local attachments = {}
  28.    
  29.     -- For each child, set it to be open
  30.     for _, child in ipairs(folder:GetChildren()) do
  31.         if child:IsA("Attachment") then
  32.             table.insert(attachments, child)
  33.             child:SetAttribute("Open", true)
  34.         end
  35.     end
  36.    
  37.     -- Sort the table by 1. 2. 3, etc.
  38.     table.sort(attachments, function(a, b)
  39.         return tonumber(a.Name) < tonumber(b.Name)
  40.     end)
  41.  
  42.     return attachments
  43. end
  44.  
  45. -- Checks if a slot, for example 1, is backfilled, meaning 1 is open but 2 has a player, we shouldn't take the 1 slot.
  46. local function IsSlotBackFilled(queue, index)
  47.     for i = 1, index - 2 do
  48.         local data = queue[i]
  49.  
  50.         if data.Open == false then
  51.            
  52.             return false
  53.         end
  54.     end
  55.     return true
  56. end
  57.  
  58. -- Call to force a player to walk to a given point, given a set of attachments in queueData.Queue
  59. local function WalkToPoint(player: Player, queueData: {}, point, index, startindex): nil
  60.     if QueueHandler.WalkingTasks[player] then
  61.         task.cancel(QueueHandler.WalkingTasks[player])
  62.     end
  63.    
  64.     -- Start a new task to handle walking.
  65.     QueueHandler.WalkingTasks[player] = task.spawn(function()
  66.         local humanoid = player.Character and player.Character:FindFirstChild("Humanoid")
  67.         if not humanoid then return end
  68.        
  69.         -- Setup variables for walking, should stop queue, and current target index.
  70.         local shouldBreak = false
  71.         local currentTargetIndex = index
  72.         local walking = true
  73.        
  74.         -- Start a timeout task
  75.         local finished = task.spawn(function()
  76.             task.wait(120)
  77.            
  78.             -- Remove their slot from the line, or reset it, and stop walking.
  79.             walking = false
  80.             queueData._debounce[player.UserId] = nil
  81.             queueData.Queue[currentTargetIndex].Owner = nil
  82.             queueData.Queue[currentTargetIndex].Finished = nil
  83.             queueData.Queue[currentTargetIndex].Open = true
  84.             shouldBreak = true
  85.            
  86.             -- Check if player exists, enable controls, and move to start.
  87.             if player.Character then
  88.                 player.Character:PivotTo(queueData.Folder.QueueUpPoints.CFrame)
  89.                 DisableControlEvent:FireClient(player, true)
  90.                 humanoid:MoveTo(player.Character:FindFirstChild("HumanoidRootPart").Position)  
  91.             end
  92.         end)
  93.        
  94.         -- While we need to walk, do...
  95.         while walking do
  96.             local restart = false
  97.            
  98.             if not startindex then
  99.                 startindex  = 1
  100.             end
  101.             -- For each node, move to each one, starting from 1, or given index.
  102.             for i = startindex, currentTargetIndex do
  103.                 local stepPoint = queueData.Queue[i]
  104.                 local part = stepPoint.Model
  105.            
  106.                 -- Check if part exists
  107.                 if part then
  108.                     humanoid:MoveTo(part.WorldPosition)
  109.  
  110.                     local timeout = 30
  111.                     local startTime = os.clock()
  112.                    
  113.                     -- Check the distance between me, and the next node point
  114.                     while true do
  115.                         task.wait(0.1)
  116.  
  117.                         if not player.Character or not player.Character:FindFirstChild("HumanoidRootPart") or not part then
  118.                             warn("Aborting walk missing character, root part, or target part.")
  119.                             break
  120.                         end
  121.  
  122.                         local rootPart = player.Character.HumanoidRootPart
  123.                         local distance = (rootPart.Position - part.WorldPosition).Magnitude
  124.  
  125.                         if distance < 5 then
  126.                             break
  127.                         end
  128.                        
  129.                         -- Check for timeout
  130.                         if os.clock() - startTime > timeout then
  131.                            
  132.                             -- Reset the data for that index
  133.                             walking = false
  134.                             queueData._debounce[player.UserId] = nil
  135.                             queueData.Queue[currentTargetIndex].Owner = nil
  136.                             queueData.Queue[currentTargetIndex].Finished = nil
  137.                             queueData.Queue[currentTargetIndex].Open = true
  138.                            
  139.                             -- Restart
  140.                             shouldBreak = true
  141.                             restart =false
  142.                            
  143.                             -- Move player to start, enable controls, and stop moving.
  144.                             if player.Character then
  145.                                 player.Character:PivotTo(queueData.Folder.QueueUpPoints.CFrame)
  146.                                 DisableControlEvent:FireClient(player, true)
  147.                                 humanoid:MoveTo(player.Character:FindFirstChild("HumanoidRootPart").Position)
  148.                             end
  149.                            
  150.                             -- Remove timeout task
  151.                             if finished then
  152.                                 task.cancel(finished)
  153.                                 finished = nil
  154.                             end
  155.                             break
  156.                         end
  157.                     end
  158.                 end
  159.                
  160.                 -- If we should stop early, because of timeout.
  161.                 if shouldBreak then
  162.                     break
  163.                 end
  164.    
  165.                 -- Find the highest possible index to move to. 
  166.                 for j = #queueData.Queue, currentTargetIndex + 1, -1 do
  167.                     local newPoint = queueData.Queue[j]
  168.                     if newPoint.Open == true and IsSlotBackFilled(queueData.Queue, j) then
  169.                         -- Free current point
  170.                         queueData.Queue[index].Open = true
  171.                         queueData.Queue[index].Owner = nil
  172.                         queueData.Queue[index].Finished = false
  173.                        
  174.                         -- Claim new point
  175.                         queueData.Queue[j].Open = false
  176.                         queueData.Queue[j].Owner = player.UserId
  177.                         queueData.Queue[j].Finished = false
  178.                        
  179.                         -- Update variables, and restart loop.
  180.                         point = newPoint
  181.                         currentTargetIndex = j
  182.                         restart = true
  183.                         startindex = i
  184.                    
  185.                         continue
  186.                     end
  187.                 end
  188.                 if restart then break end -- Break path loop and restart walking
  189.             end
  190.            
  191.             -- If we need to restart loop because of index change.
  192.             if not restart then
  193.                 walking = false
  194.             end
  195.         end
  196.  
  197.         -- When we reach our destination, set finished to true, and cancel our timeout task.
  198.         queueData.Queue[currentTargetIndex].Finished = true
  199.         if finished then
  200.             task.cancel(finished)
  201.             finished = nil
  202.         end
  203.         queueData._debounce[player.UserId] = nil
  204.     end)
  205. end
  206.  
  207. -- Called when a player touches the starting part.
  208. local function OnUserInteract(hit: Part, queueData: {}): nil
  209.     if not hit.Parent:FindFirstChildOfClass("Humanoid") then return end
  210.     if hit.Parent:FindFirstChildOfClass("Humanoid").Health <= 0 then return end
  211.    
  212.     local character = hit.Parent
  213.     if not character then return end
  214.    
  215.     local player: Player = Players:GetPlayerFromCharacter(character)
  216.     if not player then return end
  217.    
  218.     -- Debounce, so all parts don't touch the start part.
  219.     queueData._debounce = queueData._debounce or {}
  220.     if queueData._debounce[player.UserId] then
  221.         return
  222.     end
  223.     queueData._debounce[player.UserId] = true
  224.    
  225.     -- Find the highest possible place to go.
  226.     local point
  227.     local index
  228.     for i = #queueData.Queue - 1, 1, -1 do
  229.         if queueData.Queue[i].Open == true then
  230.             queueData.Queue[i].Open = false
  231.             queueData.Queue[i].Owner = player.UserId
  232.             queueData.Queue[i].Finished = false
  233.            
  234.             -- Reupdate variables, stop controls
  235.             point = queueData.Queue[i]
  236.             index = i
  237.             DisableControlEvent:FireClient(player, false)
  238.             break
  239.         end
  240.     end
  241.    
  242.     -- If we find a point, move to the last point in line, and start moving.
  243.     if point then
  244.         task.spawn(function()
  245.             character:PivotTo(queueData.Queue[1].Model.WorldCFrame)
  246.             task.wait(.1)
  247.             WalkToPoint(player, queueData, point, index)
  248.         end)
  249.     end
  250. end
  251. -- Remove their walking thread
  252. local function RemovePlayerFromQueue(queueData, player)
  253.     if QueueHandler.WalkingTasks[player] then
  254.         task.cancel(QueueHandler.WalkingTasks[player])
  255.         QueueHandler.WalkingTasks[player] = nil
  256.     end
  257.    
  258.     -- Clear debounce
  259.     queueData._debounce[player.UserId] = nil
  260. end
  261.  
  262. -- Open up a slot for a player.
  263. local function OpenUpSlot(data)
  264.     data.Open = true
  265.     data.Finished = nil
  266.     data.Owner = nil
  267. end
  268.  
  269. local function MovePlayersUp(queueData)
  270.     -- MOVE ALL PLAYERS UP BY ONE
  271.     local freeToMoveOn = true
  272.     for i = #queueData.Queue, 1, -1 do
  273.        
  274.         local data = queueData.Queue[i]
  275.         if data.Open == false and data.Owner ~= nil  then
  276.            
  277.             -- Grab the player to check if they exist.
  278.             local player = Players:GetPlayerByUserId(tonumber(data.Owner))
  279.             if player then
  280.                
  281.                 if data.Finished == true then-- Check if they reached the point
  282.                    
  283.                     local character = player.Character
  284.                     if character then
  285.                         local humanoid = character:FindFirstChildOfClass("Humanoid")
  286.                         if humanoid then
  287.                             if data.Finished == true then
  288.                                 -- Check if they are 1st in line, if so teleport out to the end of the queue
  289.                                 if i == #queueData.Queue then
  290.                                     -- Enable their controls, and teleport them.
  291.                                     DisableControlEvent:FireClient(player, true)
  292.                                     character:PivotTo(queueData.Folder.RideEnd.CFrame)
  293.  
  294.                                     -- Open up the point
  295.                                     data.Open = true
  296.                                     data.Finished = nil
  297.                                     data.Owner = nil
  298.  
  299.                                     freeToMoveOn = true
  300.                                 else -- If they are not 1st in line, try to move them up.
  301.                                     local highestFreeIndex = nil
  302.  
  303.                                     -- Search for highest free spot ahead
  304.                                     for j = #queueData.Queue, i + 1, -1 do
  305.                                         local candidate = queueData.Queue[j]
  306.                                         if candidate.Open == true and candidate.Owner == nil then
  307.                                             highestFreeIndex = j
  308.                                             break -- Stop at the highest open spot found
  309.                                         end
  310.                                     end
  311.                                    
  312.                                     -- If we find a higher index, then move to it.
  313.                                     if highestFreeIndex then
  314.                                         local targetPoint = queueData.Queue[highestFreeIndex]
  315.                                         WalkToPoint(player, queueData, targetPoint, highestFreeIndex, i)
  316.  
  317.                                         -- Free current spot
  318.                                         data.Open = true
  319.                                         data.Finished = nil
  320.                                         data.Owner = nil
  321.  
  322.                                         -- Claim new spot
  323.                                         targetPoint.Open = false
  324.                                         targetPoint.Owner = player.UserId
  325.                                         targetPoint.Finished = false
  326.  
  327.                                         freeToMoveOn = true
  328.                                     end
  329.                                 end
  330.                             end
  331.                         else
  332.                             -- Humanoid didn't exist, open up the point.
  333.                             OpenUpSlot(data)
  334.                             freeToMoveOn = true
  335.                         end
  336.                     else
  337.                         -- Character didn't exist, open up the point.
  338.                         OpenUpSlot(data)
  339.                         freeToMoveOn = true
  340.                     end
  341.                 end
  342.             else
  343.                 -- Player didn't exist, open up the point.
  344.                 OpenUpSlot(data)
  345.                 freeToMoveOn = true
  346.             end
  347.         end
  348.  
  349.     end
  350. end
  351.  
  352. -- The core queue loop.
  353. local function QueueLoop(queueData): nil
  354.     -- Start a while loop that detects players leaving the game.
  355.     task.spawn(function()
  356.         -- Every 30 seconds do...
  357.         while task.wait(30) do
  358.             -- Remove players that shouldn't be there
  359.             for _, att in queueData.Queue do
  360.                 local owner = att.Owner
  361.                 if owner then
  362.                     -- Check if the player exists
  363.                     local player = Players:GetPlayerByUserId(owner)
  364.                     if not player then
  365.                         -- Remove
  366.                         att.Owner = nil
  367.                         att.Finished = nil
  368.                         att.Open = true
  369.                     end
  370.                 end
  371.             end
  372.         end
  373.     end)
  374.    
  375.     -- We want a loop to run for the whole game.
  376.     while true do
  377.         task.wait(1)
  378.         -- Display a countdown
  379.         for i = Configuration.ALLOW_NEXT_PLAYER, 0, -1 do
  380.             -- Update signs
  381.             for _, sign in queueData.Signs:GetChildren() do
  382.                 sign.SurfaceGui.TextLabel.Text = string.gsub(Configuration.TIME_LABEL, "#", i)
  383.             end
  384.             task.wait(1)
  385.         end
  386.        
  387.         -- Move all players up in the line
  388.         MovePlayersUp(queueData)   
  389.     end
  390. end
  391.  
  392. -- Call with a player and queue data to remove them from the premises and queue.
  393. function HandlePlayerRemovedFromQueue(player, queueData, dontTeleportPlayer)
  394.     local attachments = queueData.Queue
  395.     local foundIndex = nil
  396.  
  397.     -- Find the players position in the queue
  398.     for i, attachment in ipairs(attachments) do
  399.         if attachment.Owner == player.UserId then
  400.             foundIndex = i
  401.             break
  402.         end
  403.     end
  404.    
  405.     -- Player is not in the queue
  406.     if not foundIndex then return end
  407.    
  408.     -- Teleport back to start, or to the end.
  409.     local character = player.Character
  410.     if character and dontTeleportPlayer ~= true then
  411.         character:PivotTo(queueData.Folder.QueueUpPoints.CFrame)
  412.     elseif character and dontTeleportPlayer == true then
  413.         character:PivotTo(queueData.Folder.RideEnd.CFrame)
  414.     end
  415.    
  416.     -- Enable controls
  417.     DisableControlEvent:FireClient(player, true)
  418.  
  419.     -- Clear the attachment where the player was
  420.     queueData.Queue[foundIndex].Owner = nil
  421.     queueData.Queue[foundIndex].Open = true
  422.     queueData.Queue[foundIndex].Finished = nil
  423.  
  424.     -- Actually remove him from the queue, and move players up.
  425.     RemovePlayerFromQueue(queueData, player)
  426.     MovePlayersUp(queueData)
  427. end
  428.  
  429. local function OnPlayerRemoved(player: Player): nil
  430.     -- Get all ones they are in, and remove
  431.     for _, queueData in pairs(QueueHandler.Queues) do
  432.         HandlePlayerRemovedFromQueue(player, queueData)
  433.     end
  434. end
  435.  
  436. local function OnPlayerAdded(player: Player): nil
  437.     -- Handle character removing
  438.     player.CharacterAdded:Connect(function()
  439.         -- Enable controls
  440.         DisableControlEvent:FireClient(player, true)
  441.        
  442.         -- Remove queue data
  443.         for _, queueData in pairs(QueueHandler.Queues) do
  444.             HandlePlayerRemovedFromQueue(player, queueData)
  445.         end
  446.     end)
  447. end
  448.  
  449. -- When a player clicks the Exit queue button.
  450. local function OnExitQueue(player: Player): nil
  451.     -- Find the character
  452.     local character = player.Character
  453.     if character then
  454.         -- Make sure they are not sitting down, as it can teleport the track/ride cart.
  455.         local humanoid = character:FindFirstChildOfClass("Humanoid")
  456.         if humanoid and humanoid.SeatPart == nil then
  457.             -- Remove from all possible queues
  458.             for _, queueData in pairs(QueueHandler.Queues) do
  459.                 HandlePlayerRemovedFromQueue(player, queueData)
  460.             end
  461.         end
  462.     end
  463. end
  464.  
  465. -- Handles the SkipLine product.
  466. local function OnPromptProductFinished(userID: number, productId: number, isPurchased: boolean): nil
  467.     -- Check if it was actually purchased
  468.     if isPurchased then
  469.         -- Find the player
  470.         local player = Players:GetPlayerByUserId(userID)
  471.         if not player then return end
  472.        
  473.         -- Remove from queue
  474.         for _, queueData in pairs(QueueHandler.Queues) do
  475.             HandlePlayerRemovedFromQueue(player, queueData, true)
  476.            
  477.             break
  478.         end
  479.     end
  480. end
  481.  
  482. -- [ INITIALIZATION ]
  483. function QueueHandler.InitializeSystem(): nil
  484.     -- Loop through all the possible queues
  485.     for _, queueFolder in ipairs(QueueFolder:GetChildren()) do
  486.         -- Insert the queue data
  487.         local data = {
  488.             Folder = queueFolder,
  489.             Attachments = GetQueueUpPoints(queueFolder:FindFirstChild("QueueUpPoints")),
  490.             CurrentPlayers = {},
  491.             MaxPlayers = (#queueFolder:FindFirstChild("QueueUpPoints"):GetChildren() - 1),
  492.             Signs = queueFolder:FindFirstChild("Signs"),
  493.             Queue = {},
  494.         }
  495.         data.EndPosition = data.Attachments[#data.Attachments]
  496.        
  497.         -- For each attachment, provide data.
  498.         for _, attachment in ipairs(data.Attachments) do
  499.             data.Queue[_] = {
  500.                 Model = attachment,
  501.                 Open =  true,
  502.                 Owner = nil,
  503.             }
  504.         end
  505.         table.insert(QueueHandler.Queues, data)
  506.        
  507.         -- Handle run into start part.
  508.         queueFolder.Ride.Touched:Connect(function(hit)
  509.             OnUserInteract(hit, data)
  510.         end)
  511.        
  512.         -- DEBUG, show points at each attachment
  513.         if Configuration.DEBUG then
  514.             for _, attachment in ipairs(data.Attachments) do
  515.                 local clone = script.Debug:Clone()
  516.                 clone.Parent = workspace
  517.                 clone.Position = attachment.WorldPosition
  518.                
  519.                 if _ == #data.Attachments then
  520.                     clone.BrickColor = BrickColor.Green()
  521.                 end
  522.             end
  523.         end
  524.        
  525.         -- Start the core loop, under a new thread.
  526.         task.spawn(QueueLoop, data)
  527.     end
  528.    
  529.     print("Succesfully initialized QueueSystem.")
  530. end
  531.  
  532. -- [ CONNECTIONS ]
  533. Players.PlayerRemoving:Connect(OnPlayerRemoved)
  534. Players.PlayerAdded:Connect(OnPlayerAdded)
  535. LeaveQueueEvent.OnServerEvent:Connect(OnExitQueue)
  536. MarketplaceService.PromptProductPurchaseFinished:Connect(OnPromptProductFinished)
  537.  
  538. -- [ RETURNING ]
  539. return QueueHandler
  540.  
Advertisement
Add Comment
Please, Sign In to add comment