-- Remove any previous copy of this script (and stop its loops) local env = (getgenv and getgenv()) or _G if env.FacilityControlStop then pcall(env.FacilityControlStop) end for _, g in ipairs(game:GetService("CoreGui"):GetChildren()) do if g.Name == "FacilityControlV5" or g.Name == "FacilityControl" then g:Destroy() end end local UserInputService = game:GetService("UserInputService") -- Create ScreenGui inside CoreGui to persist across respawns local screenGui = Instance.new("ScreenGui") screenGui.Name = "FacilityControl" screenGui.ResetOnSpawn = false screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling screenGui.Parent = game:GetService("CoreGui") -- GUI settings local VERSION = "v6.0" local TITLE_H = 36 -- Height of the title bar (also the height when minimized) local MIN_W, MAX_W = 230, 520 -- Resize limits (width) local MIN_H, MAX_H = 180, 700 -- Resize limits (height) -- State variables local running = true -- set to false when a newer copy of the script replaces this one local autoCloseActive = false local autoSprinklersActive = false local spamActive = false local connections = {} -- Event connections to clean up when replaced local originalPositions = {} -- Closed-position baseline for single-part doors and chamber gates local lastFired = {} -- Tracks when each door was last clicked (for auto-lock cooldown) local AUTO_LOCK_COOLDOWN = 2.5 -- Seconds to wait after clicking a door before checking it again local SPRINKLER_COOLDOWN = 3 -- Seconds to wait after clicking the sprinkler lever local SPRINKLER_RESUME_AFTER = math.huge -- Seconds before auto sprinklers re-enable after someone turns them off (math.huge = never) local SPAM_MIN_INTERVAL = 0.25 -- Shortest delay per detector local SPAM_MAX_INTERVAL = 4 -- Longest delay; randomized after every click local spamGeneration = 0 -- Invalidates workers on OFF/ON local spamWorkers = {} local spamRandom = Random.new() local LIFT_COOLDOWN = 12 -- Seconds to wait after calling a lift up before checking it again local CLOSED_TOLERANCE = 0.75 -- Studs of slack when deciding if two door panels are touching local INVERT_096 = false -- Flips open/closed reading for the 096 gate (toggled by its button) local INVERT_106 = false -- Flips open/closed reading for the grey box (106) gate (toggled by its button) local function track(conn) table.insert(connections, conn) return conn end env.FacilityControlStop = function() running = false autoCloseActive = false autoSprinklersActive = false spamActive = false for _, c in ipairs(connections) do pcall(function() c:Disconnect() end) end pcall(function() screenGui:Destroy() end) end -- GUI -- local COLOR_PANEL = Color3.fromRGB(28, 28, 33) local COLOR_TITLE = Color3.fromRGB(22, 22, 26) local COLOR_CARD = Color3.fromRGB(38, 38, 45) local COLOR_STROKE = Color3.fromRGB(60, 60, 70) local COLOR_ACCENT = Color3.fromRGB(110, 160, 255) local COLOR_TEXT = Color3.fromRGB(240, 240, 240) local COLOR_MUTED = Color3.fromRGB(150, 150, 160) local COLOR_OFF = Color3.fromRGB(75, 75, 80) local expandedHeight = 450 local minimized = false -- Main Frame Panel local mainFrame = Instance.new("Frame") mainFrame.Name = "MainPanel" mainFrame.Size = UDim2.fromOffset(270, expandedHeight) mainFrame.Position = UDim2.new(0.5, -135, 0.5, -225) mainFrame.BackgroundColor3 = COLOR_PANEL mainFrame.BorderSizePixel = 0 mainFrame.Active = true mainFrame.ClipsDescendants = true mainFrame.Parent = screenGui local mainCorner = Instance.new("UICorner") mainCorner.CornerRadius = UDim.new(0, 8) mainCorner.Parent = mainFrame local mainStroke = Instance.new("UIStroke") mainStroke.Color = COLOR_STROKE mainStroke.Thickness = 1 mainStroke.Parent = mainFrame -- Title Bar (drag handle) local titleBar = Instance.new("Frame") titleBar.Name = "TitleBar" titleBar.Size = UDim2.new(1, 0, 0, TITLE_H) titleBar.BackgroundColor3 = COLOR_TITLE titleBar.BorderSizePixel = 0 titleBar.Active = true titleBar.Parent = mainFrame local titleCorner = Instance.new("UICorner") titleCorner.CornerRadius = UDim.new(0, 8) titleCorner.Parent = titleBar local titleLabel = Instance.new("TextLabel") titleLabel.Size = UDim2.new(1, -80, 1, 0) titleLabel.Position = UDim2.new(0, 12, 0, 0) titleLabel.BackgroundTransparency = 1 titleLabel.RichText = true titleLabel.Text = 'Facility Control | Created By ContainsCats. Follow ContainsCats on ScripBlox for more! ' .. VERSION .. '' titleLabel.TextColor3 = COLOR_TEXT titleLabel.TextSize = 15 titleLabel.Font = Enum.Font.SourceSansBold titleLabel.TextXAlignment = Enum.TextXAlignment.Left titleLabel.Parent = titleBar local function createTitleButton(text, xOffset, textColor) local btn = Instance.new("TextButton") btn.Size = UDim2.new(0, 24, 0, 24) btn.Position = UDim2.new(1, xOffset, 0, 6) btn.BackgroundColor3 = Color3.fromRGB(50, 50, 55) btn.Text = text btn.TextColor3 = textColor btn.TextSize = 16 btn.Font = Enum.Font.SourceSansBold btn.AutoButtonColor = true btn.Parent = titleBar local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 4) corner.Parent = btn return btn end local minimizeBtn = createTitleButton("-", -58, COLOR_TEXT) local closeGuiBtn = createTitleButton("X", -30, Color3.fromRGB(230, 80, 80)) -- Scrollable body that holds the button groups local body = Instance.new("ScrollingFrame") body.Name = "Body" body.Position = UDim2.new(0, 0, 0, TITLE_H) body.Size = UDim2.new(1, 0, 1, -TITLE_H) body.BackgroundTransparency = 1 body.BorderSizePixel = 0 body.ScrollBarThickness = 4 body.ScrollBarImageColor3 = COLOR_STROKE body.CanvasSize = UDim2.new(0, 0, 0, 0) body.AutomaticCanvasSize = Enum.AutomaticSize.Y body.Parent = mainFrame local bodyLayout = Instance.new("UIListLayout") bodyLayout.Padding = UDim.new(0, 8) bodyLayout.SortOrder = Enum.SortOrder.LayoutOrder bodyLayout.Parent = body local bodyPadding = Instance.new("UIPadding") bodyPadding.PaddingLeft = UDim.new(0, 8) bodyPadding.PaddingRight = UDim.new(0, 10) bodyPadding.PaddingTop = UDim.new(0, 8) bodyPadding.PaddingBottom = UDim.new(0, 24) -- room for the resize grip bodyPadding.Parent = body -- Builds a group card with a heading and description. Buttons are parented to the returned card. local function createGroup(title, description, order) local card = Instance.new("Frame") card.Name = title card.Size = UDim2.new(1, 0, 0, 0) card.AutomaticSize = Enum.AutomaticSize.Y card.BackgroundColor3 = COLOR_CARD card.BorderSizePixel = 0 card.LayoutOrder = order card.Parent = body local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 8) corner.Parent = card local stroke = Instance.new("UIStroke") stroke.Color = COLOR_STROKE stroke.Thickness = 1 stroke.Parent = card local pad = Instance.new("UIPadding") pad.PaddingTop = UDim.new(0, 10) pad.PaddingBottom = UDim.new(0, 10) pad.PaddingLeft = UDim.new(0, 10) pad.PaddingRight = UDim.new(0, 10) pad.Parent = card local layout = Instance.new("UIListLayout") layout.Padding = UDim.new(0, 6) layout.SortOrder = Enum.SortOrder.LayoutOrder layout.Parent = card local header = Instance.new("TextLabel") header.Size = UDim2.new(1, 0, 0, 18) header.BackgroundTransparency = 1 header.Text = string.upper(title) header.TextColor3 = COLOR_ACCENT header.TextSize = 13 header.Font = Enum.Font.SourceSansBold header.TextXAlignment = Enum.TextXAlignment.Left header.LayoutOrder = 0 header.Parent = card local desc = Instance.new("TextLabel") desc.Size = UDim2.new(1, 0, 0, 0) desc.AutomaticSize = Enum.AutomaticSize.Y desc.BackgroundTransparency = 1 desc.Text = description desc.TextColor3 = COLOR_MUTED desc.TextSize = 13 desc.Font = Enum.Font.SourceSans desc.TextWrapped = true desc.TextXAlignment = Enum.TextXAlignment.Left desc.TextYAlignment = Enum.TextYAlignment.Top desc.LayoutOrder = 1 desc.Parent = card return card end -- Full-width button inside a group card local function createButton(parent, text, color, order) local btn = Instance.new("TextButton") btn.Size = UDim2.new(1, 0, 0, 36) btn.BackgroundColor3 = color btn.Text = text btn.TextColor3 = Color3.fromRGB(255, 255, 255) btn.TextSize = 15 btn.Font = Enum.Font.SourceSansBold btn.LayoutOrder = order btn.Parent = parent local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 6) corner.Parent = btn return btn end -- A row holding two half-width buttons side by side local function createSplitRow(parent, order) local row = Instance.new("Frame") row.Size = UDim2.new(1, 0, 0, 36) row.BackgroundTransparency = 1 row.LayoutOrder = order row.Parent = parent return row end local function createHalfButton(row, text, rightSide) local btn = Instance.new("TextButton") btn.Size = UDim2.new(0.5, -3, 1, 0) btn.Position = rightSide and UDim2.new(0.5, 3, 0, 0) or UDim2.new(0, 0, 0, 0) btn.BackgroundColor3 = COLOR_OFF btn.Text = text btn.TextColor3 = Color3.fromRGB(255, 255, 255) btn.TextSize = 14 btn.Font = Enum.Font.SourceSansBold btn.Parent = row local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 6) corner.Parent = btn return btn end -- GROUP 1: Door Control local doorGroup = createGroup("Door Control", "One-shot commands that close or open every door and gate in the facility.", 1) local closeDoorsBtn = createButton(doorGroup, "Force Close All Doors", Color3.fromRGB(180, 55, 55), 2) local openDoorsBtn = createButton(doorGroup, "Force Open All Doors", Color3.fromRGB(55, 150, 55), 3) -- GROUP 2: Automation local autoGroup = createGroup("Automation", "Loops that run until you switch them off. Auto-Lock re-closes any door that gets opened and keeps the lifts parked on top. Auto Sprinklers turns the sprinklers on once.", 2) local autoCloseBtn = createButton(autoGroup, "Auto-Lock Loop: OFF", COLOR_OFF, 2) local autoSprinklerBtn = createButton(autoGroup, "Auto Sprinklers: OFF", COLOR_OFF, 3) -- GROUP 3: Chamber Gates local gateGroup = createGroup("Chamber Gates", "If the 096 gate or the grey box (106) gate acts backwards, press its button to invert how open and closed are read.", 3) local gateRow = createSplitRow(gateGroup, 2) local invert096Btn = createHalfButton(gateRow, "096: Normal", false) local invert106Btn = createHalfButton(gateRow, "106: Normal", true) -- GROUP 4: Utilities local utilGroup = createGroup("Utilities", "Spam clicks gameplay buttons independently at random intervals, skipping purchases, morphs, clothing changes and team selectors. The report prints to F9 and copies door, lift and sprinkler info.", 4) local spamButtonsBtn = createButton(utilGroup, "Spam Gameplay Buttons: OFF", Color3.fromRGB(55, 110, 180), 2) local debugBtn = createButton(utilGroup, "Print & Copy Door States", Color3.fromRGB(90, 70, 140), 3) -- Footer hint local hint = Instance.new("TextLabel") hint.Size = UDim2.new(1, 0, 0, 16) hint.BackgroundTransparency = 1 hint.Text = "Press P to show / hide this panel" hint.TextColor3 = COLOR_MUTED hint.TextSize = 12 hint.Font = Enum.Font.SourceSans hint.LayoutOrder = 5 hint.Parent = body -- Resize grip (bottom-right corner) local resizeGrip = Instance.new("TextButton") resizeGrip.Name = "ResizeGrip" resizeGrip.Size = UDim2.new(0, 20, 0, 20) resizeGrip.Position = UDim2.new(1, -20, 1, -20) resizeGrip.BackgroundTransparency = 1 resizeGrip.Text = "◢" resizeGrip.TextColor3 = COLOR_MUTED resizeGrip.TextSize = 16 resizeGrip.Font = Enum.Font.SourceSansBold resizeGrip.ZIndex = 5 resizeGrip.Parent = mainFrame -- Dragging (title bar) and resizing (grip) local dragging, dragStart, dragStartPos = false, nil, nil local resizing, resizeStart, resizeStartSize = false, nil, nil local function isPointerStart(input) return input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch end track(titleBar.InputBegan:Connect(function(input) if isPointerStart(input) then dragging = true dragStart = input.Position dragStartPos = mainFrame.Position input.Changed:Connect(function() if input.UserInputState == Enum.UserInputState.End then dragging = false end end) end end)) track(resizeGrip.InputBegan:Connect(function(input) if isPointerStart(input) and not minimized then resizing = true resizeStart = input.Position resizeStartSize = mainFrame.AbsoluteSize input.Changed:Connect(function() if input.UserInputState == Enum.UserInputState.End then resizing = false end end) end end)) track(UserInputService.InputChanged:Connect(function(input) if input.UserInputType ~= Enum.UserInputType.MouseMovement and input.UserInputType ~= Enum.UserInputType.Touch then return end if dragging then local d = input.Position - dragStart mainFrame.Position = UDim2.new( dragStartPos.X.Scale, dragStartPos.X.Offset + d.X, dragStartPos.Y.Scale, dragStartPos.Y.Offset + d.Y) elseif resizing then local d = input.Position - resizeStart local w = math.clamp(resizeStartSize.X + d.X, MIN_W, MAX_W) local h = math.clamp(resizeStartSize.Y + d.Y, MIN_H, MAX_H) expandedHeight = h mainFrame.Size = UDim2.fromOffset(w, h) end end)) -- Minimize / restore minimizeBtn.MouseButton1Click:Connect(function() minimized = not minimized body.Visible = not minimized resizeGrip.Visible = not minimized minimizeBtn.Text = minimized and "+" or "-" local w = mainFrame.AbsoluteSize.X mainFrame.Size = UDim2.fromOffset(w, minimized and TITLE_H or expandedHeight) end) -- Toggle Visibility Hook (X Button hides, pressing 'P' shows / hides) closeGuiBtn.MouseButton1Click:Connect(function() mainFrame.Visible = false end) track(UserInputService.InputBegan:Connect(function(input, gameProcessed) if not gameProcessed and input.KeyCode == Enum.KeyCode.P then mainFrame.Visible = not mainFrame.Visible end end)) -- CORE LOGIC FOR SYSTEM FILTERING AND STATE MANAGEMENT -- -- Ancestor-name keywords that mark a button as off-limits (purchases, morphs, teams, spawns) local BLOCKED_KEYWORDS = { "gamepass", "game pass", "robux", "donate", "buy", "purchase", "product", "vip", "shop", "team", "morph", "skin", "outfit", "costume", "cosmetic", "spawn", "cloth", "shirt", "pants", "trouser", "uniform", "dress", "wardrobe", "accessor", "avatar", } -- Filter out purchasing interfaces, dev products, gamepasses, morphs, and team selectors local function isSafeGameplayButton(detector) local parent = detector.Parent if not parent then return false end local current = detector while current and current ~= workspace do local nameLower = string.lower(current.Name) for _, word in ipairs(BLOCKED_KEYWORDS) do if string.find(nameLower, word, 1, true) then return false end end current = current.Parent end return true end -- A "door" is any model named Door/Gate, inside a Gates folder, or containing a Door1 part -- (this also catches things like Lift > BottomGate). The Lift model itself is NOT a door. local function isDoorModel(model) if not model:IsA("Model") then return false end if model.Name == "Lift" then return false end if model.Name == "Gate" or model.Name == "Door" then return true end if model.Parent and string.lower(model.Parent.Name) == "gates" then return true end local d1 = model:FindFirstChild("Door1") if d1 and d1:IsA("BasePart") then return true end return false end -- True for the TopGate / BottomGate models that belong to a lift local function isLiftGate(model) return model.Parent ~= nil and model.Parent.Name == "Lift" and (model.Name == "TopGate" or model.Name == "BottomGate") end -- Picks the part used to track whether a door has moved. -- Returns the part and whether it's a real door panel (Door1/Door) vs a chamber's Main part. local function getReferencePart(model) local p = model:FindFirstChild("Door1") or model:FindFirstChild("Door") if p and p:IsA("BasePart") then return p, true end p = model:FindFirstChild("Main") if p and p:IsA("BasePart") then return p, false end if model.PrimaryPart then return model.PrimaryPart, false end return nil, false end -- Baseline positions (assumed CLOSED at launch) for doors that only have a single moving part local function cacheOriginalPositions() for _, model in ipairs(workspace:GetDescendants()) do if isDoorModel(model) then local ref = getReferencePart(model) if ref and not originalPositions[ref] then originalPositions[ref] = ref.Position end end end end cacheOriginalPositions() -- Returns true if the door is open. Also returns the measured gap/movement (for debugging). local function isGateOpen(model) local ref, isPanel = getReferencePart(model) if not ref then return false, 0 end -- Instant-delete style doors (only meaningful on real door panels) if isPanel and (ref.Transparency > 0.5 or ref.CanCollide == false) then return true, 0 end -- Two-panel doors: Door1 and Door2 touch when closed and slide apart when open local door2 = model:FindFirstChild("Door2") if isPanel and door2 and door2:IsA("BasePart") then local rel = ref.CFrame:VectorToObjectSpace(door2.Position - ref.Position) local comps = {rel.X, rel.Y, rel.Z} local sizes1 = {ref.Size.X, ref.Size.Y, ref.Size.Z} local sizes2 = {door2.Size.X, door2.Size.Y, door2.Size.Z} -- The slide axis is whichever axis the panels are offset along the most local axis = 1 for i = 2, 3 do if math.abs(comps[i]) > math.abs(comps[axis]) then axis = i end end local gap = math.abs(comps[axis]) local closedGap = (sizes1[axis] + sizes2[axis]) / 2 return gap > closedGap + CLOSED_TOLERANCE, gap - closedGap end -- Single-part doors and chamber gates: open = moved away from the saved position local initialPos = originalPositions[ref] if initialPos then local moved = (ref.Position - initialPos).Magnitude local open = moved > 1.5 if not isPanel then -- Grey box (106) gate has plain "Wall" children; the 096 gate does not if model:FindFirstChild("Wall") then if INVERT_106 then open = not open end else if INVERT_096 then open = not open end end end return open, moved end return false, 0 end -- Finds ONE safe ClickDetector for a door. Doors with two button hitboxes -- (one per side) toggle twice if both are fired, which cancels the action out. local function findDoorClickDetector(model) for _, d in ipairs(model:GetDescendants()) do if d:IsA("ClickDetector") and isSafeGameplayButton(d) then return d end end return nil end -- action: "close" or "open" -- cooldown (optional): seconds to skip a door after it was last clicked. -- skipLifts (optional): leave lift gates alone (used by auto-lock). local function executeSynchronizedAction(action, cooldown, skipLifts) cacheOriginalPositions() for _, model in ipairs(workspace:GetDescendants()) do if isDoorModel(model) and not (skipLifts and isLiftGate(model)) then local onCooldown = cooldown and lastFired[model] and (os.clock() - lastFired[model]) < cooldown if not onCooldown then local isOpen = isGateOpen(model) -- Close = click doors that are open. Open = click doors that are closed. local shouldFire = (action == "close" and isOpen) or (action == "open" and not isOpen) if shouldFire then local cd = findDoorClickDetector(model) if cd then fireclickdetector(cd) -- only ONE click per door lastFired[model] = os.clock() end end end end end end -- Each detector has independent timing, including detectors added later. local function startSpamWorker(cd) if not running or not spamActive or not cd:IsA("ClickDetector") or not cd:IsDescendantOf(workspace) or not isSafeGameplayButton(cd) or spamWorkers[cd] then return end local generation = spamGeneration local worker = {} spamWorkers[cd] = worker task.spawn(function() while running and spamActive and spamGeneration == generation and spamWorkers[cd] == worker and cd:IsDescendantOf(workspace) do -- Stagger the first click as well as every subsequent click. task.wait(spamRandom:NextNumber(SPAM_MIN_INTERVAL, SPAM_MAX_INTERVAL)) if not running or not spamActive or spamGeneration ~= generation or spamWorkers[cd] ~= worker or not cd:IsDescendantOf(workspace) then break end -- Recheck in case the detector was renamed or moved into an excluded group. if isSafeGameplayButton(cd) then pcall(function() fireclickdetector(cd) end) end end if spamWorkers[cd] == worker then spamWorkers[cd] = nil end end) end track(workspace.DescendantAdded:Connect(startSpamWorker)) track(workspace.DescendantRemoving:Connect(function(instance) spamWorkers[instance] = nil end)) -- LIFT LOGIC (keeps lifts parked on the top floor during auto-lock) -- local liftCache = {} local liftCacheTime = -math.huge local liftState = {} -- [lift] = {lastPos, lastCall} local function getLifts() if os.clock() - liftCacheTime > 10 then liftCache = {} for _, m in ipairs(workspace:GetDescendants()) do if m:IsA("Model") and m.Name == "Lift" and m:FindFirstChild("Platform") and m:FindFirstChild("TopGate") and m:FindFirstChild("BottomGate") then table.insert(liftCache, m) end end liftCacheTime = os.clock() end return liftCache end -- Static reference point for a lift gate (its DoorHitbox if it has one) local function liftGatePos(gate) local hb = gate:FindFirstChild("DoorHitbox") if hb and hb:IsA("BasePart") then return hb.Position end return gate:GetPivot().Position end -- True if the platform is closer to the top gate than the bottom gate local function isLiftAtTop(lift) local pos = lift.Platform:GetPivot().Position local topDist = (pos - liftGatePos(lift.TopGate)).Magnitude local bottomDist = (pos - liftGatePos(lift.BottomGate)).Magnitude return topDist <= bottomDist end -- If a lift has stopped at the bottom, call it up using its TopGate button local function keepLiftsOnTop() for _, lift in ipairs(getLifts()) do if lift.Parent and lift:FindFirstChild("Platform") then local st = liftState[lift] if not st then st = {lastPos = nil, lastCall = -math.huge} liftState[lift] = st end local pos = lift.Platform:GetPivot().Position local stationary = st.lastPos ~= nil and (pos - st.lastPos).Magnitude < 0.05 st.lastPos = pos if stationary and not isLiftAtTop(lift) and (os.clock() - st.lastCall) >= LIFT_COOLDOWN then local cd = findDoorClickDetector(lift.TopGate) if cd then fireclickdetector(cd) st.lastCall = os.clock() end end end end end -- SPRINKLER LOGIC -- local sprinklerLeverCD = nil -- Cached lever ClickDetector local sprinklerEmitters = {} -- Cached sprinkler particle emitters local sprinklerSounds = {} -- Cached sprinkler audio local lastSprinklerClick = -math.huge local lastSprinklerSearch = -math.huge local warnedNoParts = false local warnedUnverified = false local sprinklersSeenOn = false -- True once we've watched them be ON during this auto session local sprinklerPausedAt = nil -- Time someone else turned them off (auto stays hands-off) local sprinklerUnverified = 0 -- Clicks we made that we haven't yet seen turn the sprinklers ON local sprinklerBlindClicked = false -- Fallback: one click made when the state can't be read -- Finds the lever ClickDetector, sprinkler emitters and sprinkler audio local function findSprinklerParts() lastSprinklerSearch = os.clock() sprinklerLeverCD = nil sprinklerEmitters = {} sprinklerSounds = {} for _, d in ipairs(workspace:GetDescendants()) do if d:IsA("ClickDetector") and not sprinklerLeverCD and d:FindFirstAncestor("Sprinkler System Controls") and isSafeGameplayButton(d) then sprinklerLeverCD = d elseif d:FindFirstAncestor("Sprinkler System") then if d:IsA("ParticleEmitter") then table.insert(sprinklerEmitters, d) elseif d:IsA("Sound") then table.insert(sprinklerSounds, d) end end end end -- Sprinklers count as ON if an emitter is actively emitting OR the sprinkler audio is playing local function areSprinklersOn() for _, e in ipairs(sprinklerEmitters) do if e.Parent and e.Enabled and e.Rate > 0 then return true end end for _, s in ipairs(sprinklerSounds) do if s.Parent and s.IsPlaying then return true end end return false end local function runAutoSprinklers() -- Re-find parts if we don't have them (searched at most every 5 seconds) if (not sprinklerLeverCD or not sprinklerLeverCD.Parent or (#sprinklerEmitters + #sprinklerSounds) == 0) and (os.clock() - lastSprinklerSearch) > 5 then findSprinklerParts() end if not sprinklerLeverCD then if not warnedNoParts then warnedNoParts = true warn("[Auto Sprinklers] Couldn't find the sprinkler lever. Not clicking.") end return end -- Fallback: the state can't be read, so click the lever ONCE and then stay hands-off if (#sprinklerEmitters + #sprinklerSounds) == 0 then if not sprinklerBlindClicked then sprinklerBlindClicked = true fireclickdetector(sprinklerLeverCD) warn("[Auto Sprinklers] Can't read the sprinkler state, so the lever was clicked once. Make sure they were OFF when you enabled this.") end return end if areSprinklersOn() then sprinklersSeenOn = true sprinklerPausedAt = nil sprinklerUnverified = 0 return end -- Sprinklers are OFF. If we'd seen them ON, someone turned them off, so leave them off. if sprinklersSeenOn and not sprinklerPausedAt then sprinklerPausedAt = os.clock() sprinklersSeenOn = false end if sprinklerPausedAt then if (os.clock() - sprinklerPausedAt) < SPRINKLER_RESUME_AFTER then return -- stay off end sprinklerPausedAt = nil -- resume delay passed end -- We already clicked once and never saw them turn ON: stop instead of flipping them back off if sprinklerUnverified >= 1 then if not warnedUnverified then warnedUnverified = true warn("[Auto Sprinklers] Clicked the lever but the sprinklers never read as ON. Press 'Print & Copy Door States' and check the SPRINKLERS section.") end return end if (os.clock() - lastSprinklerClick) >= SPRINKLER_COOLDOWN then fireclickdetector(sprinklerLeverCD) lastSprinklerClick = os.clock() sprinklerUnverified += 1 end end -- Builds the report, prints it to F9, and copies it to the clipboard. -- Returns true if the clipboard copy worked. local function printDoorStates() local lines = {"---------- DOOR STATES ----------"} local count = 0 for _, model in ipairs(workspace:GetDescendants()) do if isDoorModel(model) then count += 1 local isOpen, measure = isGateOpen(model) local ref = getReferencePart(model) table.insert(lines, string.format("%s | %s | ref: %s | open-gap: %.2f", model:GetFullName(), isOpen and "OPEN" or "CLOSED", ref and ref.Name or "NONE", measure or 0)) end end table.insert(lines, "Total doors found: " .. count) table.insert(lines, "---------- LIFTS ----------") for _, lift in ipairs(getLifts()) do if lift.Parent then table.insert(lines, string.format("%s | platform at %s", lift:GetFullName(), isLiftAtTop(lift) and "TOP" or "BOTTOM")) end end findSprinklerParts() table.insert(lines, "---------- SPRINKLERS ----------") table.insert(lines, "Lever ClickDetector: " .. (sprinklerLeverCD and sprinklerLeverCD:GetFullName() or "NOT FOUND")) for i, e in ipairs(sprinklerEmitters) do table.insert(lines, string.format("Emitter %d: Enabled=%s Rate=%s", i, tostring(e.Enabled), tostring(e.Rate))) end for i, s in ipairs(sprinklerSounds) do table.insert(lines, string.format("Sound %d: IsPlaying=%s Looped=%s", i, tostring(s.IsPlaying), tostring(s.Looped))) end -- List anything sprinkler-related with its class so we can see what the objects really are for _, d in ipairs(workspace:GetDescendants()) do if d:FindFirstAncestor("Sprinkler System") and (d.Name == "Sprinkler" or d.Name == "Emitter" or d.Name == "SprinklerAudio" or d.Name == "Sound") then table.insert(lines, string.format("Found: %s [%s]", d:GetFullName(), d.ClassName)) end end table.insert(lines, "Script reads sprinklers as: " .. (areSprinklersOn() and "ON" or "OFF")) local report = table.concat(lines, "\n") print(report) local copyFn = setclipboard or toclipboard if copyFn then local ok = pcall(copyFn, report) return ok end return false end -- BUTTON ACTIONS & CONNECTIONS -- closeDoorsBtn.MouseButton1Click:Connect(function() executeSynchronizedAction("close") end) openDoorsBtn.MouseButton1Click:Connect(function() executeSynchronizedAction("open") end) -- Spam Gameplay Buttons toggle spamButtonsBtn.MouseButton1Click:Connect(function() spamActive = not spamActive spamGeneration += 1 table.clear(spamWorkers) if spamActive then spamButtonsBtn.Text = "Spam Gameplay Buttons: ON" spamButtonsBtn.BackgroundColor3 = Color3.fromRGB(40, 80, 150) for _, cd in ipairs(workspace:GetDescendants()) do startSpamWorker(cd) end else spamButtonsBtn.Text = "Spam Gameplay Buttons: OFF" spamButtonsBtn.BackgroundColor3 = Color3.fromRGB(55, 110, 180) end end) -- 096 / 106 invert toggles: flip how open/closed is read for that gate invert096Btn.MouseButton1Click:Connect(function() INVERT_096 = not INVERT_096 invert096Btn.Text = INVERT_096 and "096: Inverted" or "096: Normal" invert096Btn.BackgroundColor3 = INVERT_096 and Color3.fromRGB(170, 120, 40) or COLOR_OFF end) invert106Btn.MouseButton1Click:Connect(function() INVERT_106 = not INVERT_106 invert106Btn.Text = INVERT_106 and "106: Inverted" or "106: Normal" invert106Btn.BackgroundColor3 = INVERT_106 and Color3.fromRGB(170, 120, 40) or COLOR_OFF end) debugBtn.MouseButton1Click:Connect(function() local copied = printDoorStates() debugBtn.Text = copied and "Copied!" or "Copy not supported" task.delay(1.5, function() debugBtn.Text = "Print & Copy Door States" end) end) -- Auto-Lock Loop implementation (also keeps the lifts parked on top) local warnedLift = false autoCloseBtn.MouseButton1Click:Connect(function() autoCloseActive = not autoCloseActive if autoCloseActive then autoCloseBtn.Text = "Auto-Lock Loop: ON" autoCloseBtn.BackgroundColor3 = Color3.fromRGB(150, 45, 45) task.spawn(function() while running and autoCloseActive do executeSynchronizedAction("close", AUTO_LOCK_COOLDOWN, true) -- true = leave lift gates alone local ok, err = pcall(keepLiftsOnTop) if not ok and not warnedLift then warnedLift = true warn("[Lift] " .. tostring(err)) end task.wait(0.3) end end) else autoCloseBtn.Text = "Auto-Lock Loop: OFF" autoCloseBtn.BackgroundColor3 = COLOR_OFF end end) -- Auto Sprinklers implementation autoSprinklerBtn.MouseButton1Click:Connect(function() autoSprinklersActive = not autoSprinklersActive if autoSprinklersActive then autoSprinklerBtn.Text = "Auto Sprinklers: ON" autoSprinklerBtn.BackgroundColor3 = Color3.fromRGB(55, 130, 170) warnedNoParts = false warnedUnverified = false sprinklersSeenOn = false -- fresh session: turn them on once, then respect anyone turning them off sprinklerPausedAt = nil sprinklerUnverified = 0 sprinklerBlindClicked = false findSprinklerParts() -- Refresh in case the map changed task.spawn(function() while running and autoSprinklersActive do runAutoSprinklers() task.wait(0.5) end end) else autoSprinklerBtn.Text = "Auto Sprinklers: OFF" autoSprinklerBtn.BackgroundColor3 = COLOR_OFF end end)