Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- @LXCKYDEV 2025 - PUT IN SERVER SCRIPT SERVICE
- -- The script is run, because there is a script under this that calls Initialize.
- -- [ SERVICES ]
- local MarketplaceService = game:GetService("MarketplaceService")
- local Players = game:GetService("Players")
- local ReplicatedStorage = game:GetService("ReplicatedStorage")
- local Configuration = require(script:FindFirstChild("Configuration"))
- -- [ FOLDERS ]
- local QueueFolder = workspace:FindFirstChild("Queues")
- -- [ EVENTS ]
- local DisableControlEvent = ReplicatedStorage:WaitForChild("DisableControlEvent", 5)
- local LeaveQueueEvent = ReplicatedStorage:WaitForChild("LeaveQueueEvent", 5)
- -- [ VARIABLES ]
- local QueueHandler = {
- Queues = {},
- WalkingTasks = {}
- }
- -- [ FUNCTIONS ]
- -- Order a table by it's children names
- local function GetQueueUpPoints(folder: Folder): {Attachment}
- local attachments = {}
- -- For each child, set it to be open
- for _, child in ipairs(folder:GetChildren()) do
- if child:IsA("Attachment") then
- table.insert(attachments, child)
- child:SetAttribute("Open", true)
- end
- end
- -- Sort the table by 1. 2. 3, etc.
- table.sort(attachments, function(a, b)
- return tonumber(a.Name) < tonumber(b.Name)
- end)
- return attachments
- end
- -- 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.
- local function IsSlotBackFilled(queue, index)
- for i = 1, index - 2 do
- local data = queue[i]
- if data.Open == false then
- return false
- end
- end
- return true
- end
- -- Call to force a player to walk to a given point, given a set of attachments in queueData.Queue
- local function WalkToPoint(player: Player, queueData: {}, point, index, startindex): nil
- if QueueHandler.WalkingTasks[player] then
- task.cancel(QueueHandler.WalkingTasks[player])
- end
- -- Start a new task to handle walking.
- QueueHandler.WalkingTasks[player] = task.spawn(function()
- local humanoid = player.Character and player.Character:FindFirstChild("Humanoid")
- if not humanoid then return end
- -- Setup variables for walking, should stop queue, and current target index.
- local shouldBreak = false
- local currentTargetIndex = index
- local walking = true
- -- Start a timeout task
- local finished = task.spawn(function()
- task.wait(120)
- -- Remove their slot from the line, or reset it, and stop walking.
- walking = false
- queueData._debounce[player.UserId] = nil
- queueData.Queue[currentTargetIndex].Owner = nil
- queueData.Queue[currentTargetIndex].Finished = nil
- queueData.Queue[currentTargetIndex].Open = true
- shouldBreak = true
- -- Check if player exists, enable controls, and move to start.
- if player.Character then
- player.Character:PivotTo(queueData.Folder.QueueUpPoints.CFrame)
- DisableControlEvent:FireClient(player, true)
- humanoid:MoveTo(player.Character:FindFirstChild("HumanoidRootPart").Position)
- end
- end)
- -- While we need to walk, do...
- while walking do
- local restart = false
- if not startindex then
- startindex = 1
- end
- -- For each node, move to each one, starting from 1, or given index.
- for i = startindex, currentTargetIndex do
- local stepPoint = queueData.Queue[i]
- local part = stepPoint.Model
- -- Check if part exists
- if part then
- humanoid:MoveTo(part.WorldPosition)
- local timeout = 30
- local startTime = os.clock()
- -- Check the distance between me, and the next node point
- while true do
- task.wait(0.1)
- if not player.Character or not player.Character:FindFirstChild("HumanoidRootPart") or not part then
- warn("Aborting walk missing character, root part, or target part.")
- break
- end
- local rootPart = player.Character.HumanoidRootPart
- local distance = (rootPart.Position - part.WorldPosition).Magnitude
- if distance < 5 then
- break
- end
- -- Check for timeout
- if os.clock() - startTime > timeout then
- -- Reset the data for that index
- walking = false
- queueData._debounce[player.UserId] = nil
- queueData.Queue[currentTargetIndex].Owner = nil
- queueData.Queue[currentTargetIndex].Finished = nil
- queueData.Queue[currentTargetIndex].Open = true
- -- Restart
- shouldBreak = true
- restart =false
- -- Move player to start, enable controls, and stop moving.
- if player.Character then
- player.Character:PivotTo(queueData.Folder.QueueUpPoints.CFrame)
- DisableControlEvent:FireClient(player, true)
- humanoid:MoveTo(player.Character:FindFirstChild("HumanoidRootPart").Position)
- end
- -- Remove timeout task
- if finished then
- task.cancel(finished)
- finished = nil
- end
- break
- end
- end
- end
- -- If we should stop early, because of timeout.
- if shouldBreak then
- break
- end
- -- Find the highest possible index to move to.
- for j = #queueData.Queue, currentTargetIndex + 1, -1 do
- local newPoint = queueData.Queue[j]
- if newPoint.Open == true and IsSlotBackFilled(queueData.Queue, j) then
- -- Free current point
- queueData.Queue[index].Open = true
- queueData.Queue[index].Owner = nil
- queueData.Queue[index].Finished = false
- -- Claim new point
- queueData.Queue[j].Open = false
- queueData.Queue[j].Owner = player.UserId
- queueData.Queue[j].Finished = false
- -- Update variables, and restart loop.
- point = newPoint
- currentTargetIndex = j
- restart = true
- startindex = i
- continue
- end
- end
- if restart then break end -- Break path loop and restart walking
- end
- -- If we need to restart loop because of index change.
- if not restart then
- walking = false
- end
- end
- -- When we reach our destination, set finished to true, and cancel our timeout task.
- queueData.Queue[currentTargetIndex].Finished = true
- if finished then
- task.cancel(finished)
- finished = nil
- end
- queueData._debounce[player.UserId] = nil
- end)
- end
- -- Called when a player touches the starting part.
- local function OnUserInteract(hit: Part, queueData: {}): nil
- if not hit.Parent:FindFirstChildOfClass("Humanoid") then return end
- if hit.Parent:FindFirstChildOfClass("Humanoid").Health <= 0 then return end
- local character = hit.Parent
- if not character then return end
- local player: Player = Players:GetPlayerFromCharacter(character)
- if not player then return end
- -- Debounce, so all parts don't touch the start part.
- queueData._debounce = queueData._debounce or {}
- if queueData._debounce[player.UserId] then
- return
- end
- queueData._debounce[player.UserId] = true
- -- Find the highest possible place to go.
- local point
- local index
- for i = #queueData.Queue - 1, 1, -1 do
- if queueData.Queue[i].Open == true then
- queueData.Queue[i].Open = false
- queueData.Queue[i].Owner = player.UserId
- queueData.Queue[i].Finished = false
- -- Reupdate variables, stop controls
- point = queueData.Queue[i]
- index = i
- DisableControlEvent:FireClient(player, false)
- break
- end
- end
- -- If we find a point, move to the last point in line, and start moving.
- if point then
- task.spawn(function()
- character:PivotTo(queueData.Queue[1].Model.WorldCFrame)
- task.wait(.1)
- WalkToPoint(player, queueData, point, index)
- end)
- end
- end
- -- Remove their walking thread
- local function RemovePlayerFromQueue(queueData, player)
- if QueueHandler.WalkingTasks[player] then
- task.cancel(QueueHandler.WalkingTasks[player])
- QueueHandler.WalkingTasks[player] = nil
- end
- -- Clear debounce
- queueData._debounce[player.UserId] = nil
- end
- -- Open up a slot for a player.
- local function OpenUpSlot(data)
- data.Open = true
- data.Finished = nil
- data.Owner = nil
- end
- local function MovePlayersUp(queueData)
- -- MOVE ALL PLAYERS UP BY ONE
- local freeToMoveOn = true
- for i = #queueData.Queue, 1, -1 do
- local data = queueData.Queue[i]
- if data.Open == false and data.Owner ~= nil then
- -- Grab the player to check if they exist.
- local player = Players:GetPlayerByUserId(tonumber(data.Owner))
- if player then
- if data.Finished == true then-- Check if they reached the point
- local character = player.Character
- if character then
- local humanoid = character:FindFirstChildOfClass("Humanoid")
- if humanoid then
- if data.Finished == true then
- -- Check if they are 1st in line, if so teleport out to the end of the queue
- if i == #queueData.Queue then
- -- Enable their controls, and teleport them.
- DisableControlEvent:FireClient(player, true)
- character:PivotTo(queueData.Folder.RideEnd.CFrame)
- -- Open up the point
- data.Open = true
- data.Finished = nil
- data.Owner = nil
- freeToMoveOn = true
- else -- If they are not 1st in line, try to move them up.
- local highestFreeIndex = nil
- -- Search for highest free spot ahead
- for j = #queueData.Queue, i + 1, -1 do
- local candidate = queueData.Queue[j]
- if candidate.Open == true and candidate.Owner == nil then
- highestFreeIndex = j
- break -- Stop at the highest open spot found
- end
- end
- -- If we find a higher index, then move to it.
- if highestFreeIndex then
- local targetPoint = queueData.Queue[highestFreeIndex]
- WalkToPoint(player, queueData, targetPoint, highestFreeIndex, i)
- -- Free current spot
- data.Open = true
- data.Finished = nil
- data.Owner = nil
- -- Claim new spot
- targetPoint.Open = false
- targetPoint.Owner = player.UserId
- targetPoint.Finished = false
- freeToMoveOn = true
- end
- end
- end
- else
- -- Humanoid didn't exist, open up the point.
- OpenUpSlot(data)
- freeToMoveOn = true
- end
- else
- -- Character didn't exist, open up the point.
- OpenUpSlot(data)
- freeToMoveOn = true
- end
- end
- else
- -- Player didn't exist, open up the point.
- OpenUpSlot(data)
- freeToMoveOn = true
- end
- end
- end
- end
- -- The core queue loop.
- local function QueueLoop(queueData): nil
- -- Start a while loop that detects players leaving the game.
- task.spawn(function()
- -- Every 30 seconds do...
- while task.wait(30) do
- -- Remove players that shouldn't be there
- for _, att in queueData.Queue do
- local owner = att.Owner
- if owner then
- -- Check if the player exists
- local player = Players:GetPlayerByUserId(owner)
- if not player then
- -- Remove
- att.Owner = nil
- att.Finished = nil
- att.Open = true
- end
- end
- end
- end
- end)
- -- We want a loop to run for the whole game.
- while true do
- task.wait(1)
- -- Display a countdown
- for i = Configuration.ALLOW_NEXT_PLAYER, 0, -1 do
- -- Update signs
- for _, sign in queueData.Signs:GetChildren() do
- sign.SurfaceGui.TextLabel.Text = string.gsub(Configuration.TIME_LABEL, "#", i)
- end
- task.wait(1)
- end
- -- Move all players up in the line
- MovePlayersUp(queueData)
- end
- end
- -- Call with a player and queue data to remove them from the premises and queue.
- function HandlePlayerRemovedFromQueue(player, queueData, dontTeleportPlayer)
- local attachments = queueData.Queue
- local foundIndex = nil
- -- Find the players position in the queue
- for i, attachment in ipairs(attachments) do
- if attachment.Owner == player.UserId then
- foundIndex = i
- break
- end
- end
- -- Player is not in the queue
- if not foundIndex then return end
- -- Teleport back to start, or to the end.
- local character = player.Character
- if character and dontTeleportPlayer ~= true then
- character:PivotTo(queueData.Folder.QueueUpPoints.CFrame)
- elseif character and dontTeleportPlayer == true then
- character:PivotTo(queueData.Folder.RideEnd.CFrame)
- end
- -- Enable controls
- DisableControlEvent:FireClient(player, true)
- -- Clear the attachment where the player was
- queueData.Queue[foundIndex].Owner = nil
- queueData.Queue[foundIndex].Open = true
- queueData.Queue[foundIndex].Finished = nil
- -- Actually remove him from the queue, and move players up.
- RemovePlayerFromQueue(queueData, player)
- MovePlayersUp(queueData)
- end
- local function OnPlayerRemoved(player: Player): nil
- -- Get all ones they are in, and remove
- for _, queueData in pairs(QueueHandler.Queues) do
- HandlePlayerRemovedFromQueue(player, queueData)
- end
- end
- local function OnPlayerAdded(player: Player): nil
- -- Handle character removing
- player.CharacterAdded:Connect(function()
- -- Enable controls
- DisableControlEvent:FireClient(player, true)
- -- Remove queue data
- for _, queueData in pairs(QueueHandler.Queues) do
- HandlePlayerRemovedFromQueue(player, queueData)
- end
- end)
- end
- -- When a player clicks the Exit queue button.
- local function OnExitQueue(player: Player): nil
- -- Find the character
- local character = player.Character
- if character then
- -- Make sure they are not sitting down, as it can teleport the track/ride cart.
- local humanoid = character:FindFirstChildOfClass("Humanoid")
- if humanoid and humanoid.SeatPart == nil then
- -- Remove from all possible queues
- for _, queueData in pairs(QueueHandler.Queues) do
- HandlePlayerRemovedFromQueue(player, queueData)
- end
- end
- end
- end
- -- Handles the SkipLine product.
- local function OnPromptProductFinished(userID: number, productId: number, isPurchased: boolean): nil
- -- Check if it was actually purchased
- if isPurchased then
- -- Find the player
- local player = Players:GetPlayerByUserId(userID)
- if not player then return end
- -- Remove from queue
- for _, queueData in pairs(QueueHandler.Queues) do
- HandlePlayerRemovedFromQueue(player, queueData, true)
- break
- end
- end
- end
- -- [ INITIALIZATION ]
- function QueueHandler.InitializeSystem(): nil
- -- Loop through all the possible queues
- for _, queueFolder in ipairs(QueueFolder:GetChildren()) do
- -- Insert the queue data
- local data = {
- Folder = queueFolder,
- Attachments = GetQueueUpPoints(queueFolder:FindFirstChild("QueueUpPoints")),
- CurrentPlayers = {},
- MaxPlayers = (#queueFolder:FindFirstChild("QueueUpPoints"):GetChildren() - 1),
- Signs = queueFolder:FindFirstChild("Signs"),
- Queue = {},
- }
- data.EndPosition = data.Attachments[#data.Attachments]
- -- For each attachment, provide data.
- for _, attachment in ipairs(data.Attachments) do
- data.Queue[_] = {
- Model = attachment,
- Open = true,
- Owner = nil,
- }
- end
- table.insert(QueueHandler.Queues, data)
- -- Handle run into start part.
- queueFolder.Ride.Touched:Connect(function(hit)
- OnUserInteract(hit, data)
- end)
- -- DEBUG, show points at each attachment
- if Configuration.DEBUG then
- for _, attachment in ipairs(data.Attachments) do
- local clone = script.Debug:Clone()
- clone.Parent = workspace
- clone.Position = attachment.WorldPosition
- if _ == #data.Attachments then
- clone.BrickColor = BrickColor.Green()
- end
- end
- end
- -- Start the core loop, under a new thread.
- task.spawn(QueueLoop, data)
- end
- print("Succesfully initialized QueueSystem.")
- end
- -- [ CONNECTIONS ]
- Players.PlayerRemoving:Connect(OnPlayerRemoved)
- Players.PlayerAdded:Connect(OnPlayerAdded)
- LeaveQueueEvent.OnServerEvent:Connect(OnExitQueue)
- MarketplaceService.PromptProductPurchaseFinished:Connect(OnPromptProductFinished)
- -- [ RETURNING ]
- return QueueHandler
Advertisement
Add Comment
Please, Sign In to add comment